Files
safeImport/src/tsCalls.mjs

158 lines
7.1 KiB
JavaScript
Raw Normal View History

2025-08-01 20:19:24 +01:00
// @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());
}
}
}