// @ts-check import path from 'path'; import tsm, { Identifier, ImportSpecifier, StringLiteral, SyntaxKind, ts, } from 'ts-morph'; import { LibraryTypesRecorder } from './libcalls.mjs'; /** * * @param {tsm.StringLiteral[]} importDecls * @param {tsm.TypeChecker} checker * @param {string} mainFilePath Main file path for the script being analyzed * @returns {LibraryTypesRecorder} instance of recorded library calls */ export function getImportCallsAndArgumentTypes(importDecls, checker, mainFilePath) { const libraryCallsRecorder = new LibraryTypesRecorder(); for (const importStringDecl of importDecls) { // console.log(importStringDecl); const importDecl = importStringDecl.getFirstAncestor(); if (importDecl === undefined) { console.error("Import declaration is undefined for", importStringDecl.getText()); continue; } if (importDecl.isKind(SyntaxKind.CallExpression)) { // the declaration is callExpression. Verify its based an identifier aliasing import or require const importExpr = importDecl.getExpression(); const type = checker.getTypeAtLocation(importExpr); console.log("Type of import expression", checker.getTypeText(type)); // console.log(importExpr); if (importExpr.isKind(SyntaxKind.Identifier)) { // import is a require or import const importName = importExpr.getText(); const importId = importExpr; // check if the require is from node if (importName === 'require') { const importSymbol = importId.getType().getSymbol(); if (importSymbol === undefined) { console.error("Import identifier has no symbol", importId.getText()); } else { const importSymbolFullyQualifiedName = checker.getFullyQualifiedName(importSymbol); if (importSymbolFullyQualifiedName !== 'global.NodeJS.Require') { console.warn("Found require call but not from NodeJS global require"); } } console.log("Found require/import call", importExpr); // extract the variables imported from the callexpression const importArgs = importDecl.getArguments(); const parent = importDecl.getParent(); if (parent?.isKind(SyntaxKind.VariableDeclaration)) { // this is a variable declaration const varDecl = parent; const varName = varDecl.getName(); console.log("Variable name", varName); // check if declaration is identifier or object pattern } throw Error("Not implemented yet"); } } } else if (importDecl.isKind(SyntaxKind.ImportDeclaration)) {// import {x,z} from 'module'; console.log("Found import declaration", importDecl.getPos()); console.log("Named imports", importDecl.getNamedImports().length); const namedImports = importDecl.getNamedImports(); for (const namedImport of namedImports) { // TODO handle aliases handleImportForGivenImport(importStringDecl,namedImport, mainFilePath, libraryCallsRecorder); } const defaultImportIdentifier = importDecl.getDefaultImport(); console.log("Default import",defaultImportIdentifier); if( defaultImportIdentifier !== undefined) { recordImportedIdentifierUsage(defaultImportIdentifier, mainFilePath, libraryCallsRecorder, importStringDecl, true); } // console.log("Namespace import",importDecl.getNamespaceImport()); // recordImportedIdentifierUsage(defaultImportIdentifier, mainFilePath, libraryCallsRecorder, importStringDecl, true); console.log("STOP"); } else { console.error("Unexpected import specifier", SyntaxKind[importDecl.getKind()]); } const importThing = importStringDecl.getParent() } // throw Error("Not implemented yet"); return libraryCallsRecorder; } /** * * @param {tsm.StringLiteral} importStringLiteral * @param {ImportSpecifier} namedImport * @param {string} mainFilePath * @param {LibraryTypesRecorder} libraryCallsRecorder */ function handleImportForGivenImport(importStringLiteral,namedImport, mainFilePath, libraryCallsRecorder) { const aliasNode = namedImport.getAliasNode(); if (aliasNode !== undefined) { console.error("Unhandled named import alias", aliasNode.getText()); } console.log("Named import", namedImport.getNameNode().getText()); const importNode = namedImport.getNameNode(); if (importNode.isKind(SyntaxKind.StringLiteral)) { throw Error("Unexpected string literal import node. Expected identifier"); } recordImportedIdentifierUsage(importNode, mainFilePath, libraryCallsRecorder, importStringLiteral); } /** * * @param {Identifier} importNode * @param {string} mainFilePath * @param {LibraryTypesRecorder} libraryCallsRecorder * @param {StringLiteral} importStringLiteral * @param {boolean} [isDefaultImport=false] */ function recordImportedIdentifierUsage(importNode, mainFilePath, libraryCallsRecorder, importStringLiteral, isDefaultImport = false) { const importRefs = importNode.findReferences(); for (const importRef of importRefs) { const referenceSourceFile = importRef.getDefinition().getSourceFile(); const comparePath = path.relative(mainFilePath, referenceSourceFile.getFilePath()); if (comparePath !== '') { console.warn("Skipping import reference from other file", referenceSourceFile.getFilePath()); continue; } console.log("Compare path", comparePath === ''); // const filePath = referenceSourceFile.getFilePath(); // console.log("Refset for import",filePath); for (const ref of importRef.getReferences()) { if (ref.isDefinition()) { continue; } // console.log("I am ",ref.isDefinition()); const callExpression = ref.getNode().getFirstAncestorByKind(SyntaxKind.CallExpression); const callExpressionArguments = callExpression?.getArguments(); if (callExpressionArguments === undefined || callExpressionArguments.length === 0) { console.warn("No call expressions found for import reference", ref.getNode().getText()); continue; } // for(const argument of callExpressionArguments){ // console.log(`Arg ${idx} is ${arg.getText()}, type is ${arg.getType()}`); // } const getImportSection = '.' + (isDefaultImport? 'default':importNode.getText()); libraryCallsRecorder.pushToMap(importStringLiteral.getLiteralValue(), getImportSection, callExpressionArguments.map(arg => arg.getType())); console.log("I am ", callExpression?.getText()); } } }