extract calls recorder for multifile analysis

This commit is contained in:
2025-08-04 12:45:46 +01:00
parent 6bb5d5e554
commit 4e1d371026
2 changed files with 53 additions and 21 deletions

View File

@@ -57,6 +57,11 @@ export function wpCompress(l, outputPath = path.resolve('./output/')) {
minimize: false, minimize: false,
}, },
resolve:{
fallback:{
"path": false
}
},
output: { output: {
path: outputPath, path: outputPath,
filename: outputFile, filename: outputFile,
@@ -70,9 +75,8 @@ export function wpCompress(l, outputPath = path.resolve('./output/')) {
}, },
}, (err, stats) => { }, (err, stats) => {
if (err || stats.hasErrors()) { if (err || stats.hasErrors()) {
// console.log(err?.stack); console.error(`[WebPack] Error Stack`,err?.stack);
// console.log(stats?.hasErrors()); console.log(`[WebPack]`,stats?.toJson().errors);
// console.log(stats?.toJson());
reject(err || stats); reject(err || stats);
}else{ }else{
resolve(path.resolve(outputPath, outputFile)); resolve(path.resolve(outputPath, outputFile));

View File

@@ -8,10 +8,12 @@ import { LibraryTypesRecorder } from './libcalls.mjs';
* @param {tsm.StringLiteral[]} importDecls * @param {tsm.StringLiteral[]} importDecls
* @param {tsm.TypeChecker} checker * @param {tsm.TypeChecker} checker
* @param {string} mainFilePath Main file path for the script being analyzed * @param {string} mainFilePath Main file path for the script being analyzed
* @param {LibraryTypesRecorder} libraryTypesRecorder recorder to use for library calls
* @returns {LibraryTypesRecorder} instance of recorded library calls * @returns {LibraryTypesRecorder} instance of recorded library calls
*
*/ */
export function getImportCallsAndArgumentTypes(importDecls, checker, mainFilePath) { export function getImportCallsAndArgumentTypes(importDecls, checker, mainFilePath, libraryTypesRecorder) {
const libraryCallsRecorder = new LibraryTypesRecorder(checker); // const libraryTypesRecorder = new LibraryTypesRecorder(checker);
for (const importStringDecl of importDecls) { for (const importStringDecl of importDecls) {
// console.log(importStringDecl); // console.log(importStringDecl);
const importDecl = importStringDecl.getFirstAncestor(); const importDecl = importStringDecl.getFirstAncestor();
@@ -43,9 +45,9 @@ export function getImportCallsAndArgumentTypes(importDecls, checker, mainFilePat
} }
console.log("Found require/import call", importExpr); // console.log("Found require/import call", importExpr);
// extract the variables imported from the callexpression // extract the variables imported from the callexpression
const importArgs = importDecl.getArguments(); // const importArgs = importDecl.getArguments();
const parent = importDecl.getParent(); const parent = importDecl.getParent();
if (parent?.isKind(SyntaxKind.VariableDeclaration)) { if (parent?.isKind(SyntaxKind.VariableDeclaration)) {
@@ -55,13 +57,14 @@ export function getImportCallsAndArgumentTypes(importDecls, checker, mainFilePat
const varDecls = varDecl.getNameNode(); const varDecls = varDecl.getNameNode();
// default import // default import
if( varDecls.isKind(SyntaxKind.Identifier)) { if( varDecls.isKind(SyntaxKind.Identifier)) {
recordImportedIdentifierUsage(varDecls, mainFilePath, libraryCallsRecorder, importStringDecl, true); // this is like a namespace import. this is not a default import because default imports in require are indicated by `.default`
recordNamespaceImportIdentifierUsage(checker, varDecls, mainFilePath, libraryTypesRecorder, importStringDecl);
}else if(varDecls.isKind(SyntaxKind.ObjectBindingPattern)) { }else if(varDecls.isKind(SyntaxKind.ObjectBindingPattern)) {
const destructuredElements = varDecls.getElements(); const destructuredElements = varDecls.getElements();
for (const destructuredElement of destructuredElements) { for (const destructuredElement of destructuredElements) {
const destructuredElementName = destructuredElement.getNameNode(); const destructuredElementName = destructuredElement.getNameNode();
if (destructuredElementName.isKind(SyntaxKind.Identifier)) { if (destructuredElementName.isKind(SyntaxKind.Identifier)) {
recordImportedIdentifierUsage(destructuredElementName, mainFilePath, libraryCallsRecorder, importStringDecl); recordImportedIdentifierUsage(checker, destructuredElementName, mainFilePath, libraryTypesRecorder, importStringDecl);
} else if (destructuredElementName.isKind(SyntaxKind.ObjectBindingPattern)) { } else if (destructuredElementName.isKind(SyntaxKind.ObjectBindingPattern)) {
// TODO handle object binding pattern // TODO handle object binding pattern
console.warn("Nested binding pattern not handled yet", destructuredElementName.getText()); console.warn("Nested binding pattern not handled yet", destructuredElementName.getText());
@@ -87,19 +90,19 @@ export function getImportCallsAndArgumentTypes(importDecls, checker, mainFilePat
for (const namedImport of namedImports) { for (const namedImport of namedImports) {
// TODO handle aliases // TODO handle aliases
handleImportForGivenImport(importStringDecl,namedImport, mainFilePath, libraryCallsRecorder); handleImportForGivenImport(checker, importStringDecl,namedImport, mainFilePath, libraryTypesRecorder);
} }
const defaultImportIdentifier = importDecl.getDefaultImport(); const defaultImportIdentifier = importDecl.getDefaultImport();
// console.log("Default import",defaultImportIdentifier); // console.log("Default import",defaultImportIdentifier);
if( defaultImportIdentifier !== undefined) { if( defaultImportIdentifier !== undefined) {
recordImportedIdentifierUsage(defaultImportIdentifier, mainFilePath, libraryCallsRecorder, importStringDecl, true); recordImportedIdentifierUsage(checker, defaultImportIdentifier, mainFilePath, libraryTypesRecorder, importStringDecl, true);
} }
const namespaceImportIdentifier = importDecl.getNamespaceImport(); const namespaceImportIdentifier = importDecl.getNamespaceImport();
// console.log("Namespace import",namespaceImportIdentifier); // console.log("Namespace import",namespaceImportIdentifier);
if( namespaceImportIdentifier !== undefined) { if( namespaceImportIdentifier !== undefined) {
recordNamespaceImportIdentifierUsage(namespaceImportIdentifier, mainFilePath, libraryCallsRecorder, importStringDecl); recordNamespaceImportIdentifierUsage(checker, namespaceImportIdentifier, mainFilePath, libraryTypesRecorder, importStringDecl);
} }
// recordImportedIdentifierUsage(defaultImportIdentifier, mainFilePath, libraryCallsRecorder, importStringDecl, true); // recordImportedIdentifierUsage(defaultImportIdentifier, mainFilePath, libraryCallsRecorder, importStringDecl, true);
@@ -113,17 +116,18 @@ export function getImportCallsAndArgumentTypes(importDecls, checker, mainFilePat
} }
// throw Error("Not implemented yet"); // throw Error("Not implemented yet");
return libraryCallsRecorder; return libraryTypesRecorder;
} }
/** /**
* *
* @param {tsm.TypeChecker} checker
* @param {tsm.StringLiteral} importStringLiteral * @param {tsm.StringLiteral} importStringLiteral
* @param {ImportSpecifier} namedImport * @param {ImportSpecifier} namedImport
* @param {string} mainFilePath * @param {string} mainFilePath
* @param {LibraryTypesRecorder} libraryCallsRecorder * @param {LibraryTypesRecorder} libraryCallsRecorder
*/ */
function handleImportForGivenImport(importStringLiteral,namedImport, mainFilePath, libraryCallsRecorder) { function handleImportForGivenImport(checker, importStringLiteral,namedImport, mainFilePath, libraryCallsRecorder) {
const aliasNode = namedImport.getAliasNode(); const aliasNode = namedImport.getAliasNode();
if (aliasNode !== undefined) { if (aliasNode !== undefined) {
console.error("Unhandled named import alias", aliasNode.getText()); console.error("Unhandled named import alias", aliasNode.getText());
@@ -135,16 +139,17 @@ function handleImportForGivenImport(importStringLiteral,namedImport, mainFilePat
throw Error("Unexpected string literal import node. Expected identifier"); throw Error("Unexpected string literal import node. Expected identifier");
} }
recordImportedIdentifierUsage(importNode, mainFilePath, libraryCallsRecorder, importStringLiteral); recordImportedIdentifierUsage(checker, importNode, mainFilePath, libraryCallsRecorder, importStringLiteral);
} }
/** /**
* *
* @param {tsm.TypeChecker} checker
* @param {Identifier} importNode * @param {Identifier} importNode
* @param {string} mainFilePath * @param {string} mainFilePath
* @param {LibraryTypesRecorder} libraryCallsRecorder * @param {LibraryTypesRecorder} libraryCallsRecorder
* @param {StringLiteral} importStringLiteral * @param {StringLiteral} importStringLiteral
*/ */
function recordNamespaceImportIdentifierUsage(importNode, mainFilePath, libraryCallsRecorder, importStringLiteral) { function recordNamespaceImportIdentifierUsage(checker, importNode, mainFilePath, libraryCallsRecorder, importStringLiteral) {
const importRefs = importNode.findReferences(); const importRefs = importNode.findReferences();
for (const importRef of importRefs) { for (const importRef of importRefs) {
const referenceSourceFile = importRef.getDefinition().getSourceFile(); const referenceSourceFile = importRef.getDefinition().getSourceFile();
@@ -173,15 +178,20 @@ function recordNamespaceImportIdentifierUsage(importNode, mainFilePath, libraryC
// asserted that the call expression is using the importNode // asserted that the call expression is using the importNode
if(callExpression.getExpression().isKind(SyntaxKind.PropertyAccessExpression)){ if(callExpression.getExpression().isKind(SyntaxKind.PropertyAccessExpression)){
console.log("Used a submethod of import", ref.getNode().getText(),callExpression.getExpression().getText()); console.log("Used a submethod of import", ref.getNode().getText(),callExpression.getExpression().getText());
ref.getNode().getText(); // ref.getNode().getText();
const expressionImportSection = callExpression.getExpression().getText().split('.'); const expressionImportSection = callExpression.getExpression().getText().split('.');
expressionImportSection.shift(); expressionImportSection.shift();
getImportSection = '.'+expressionImportSection.join('.'); getImportSection = '.'+expressionImportSection.join('.');
}else{ }else{
console.warn("Call expression is not using the import node as property access", ref.getNode().getText()); console.warn("Call expression is not using the import node as property access", ref.getNode().getText());
continue; continue;
} }
}else{ }else if(callExpression?.getExpression().isKind(SyntaxKind.Identifier)) {
// the call expression is using the import node as identifier
getImportSection = '.'
}else {
console.warn("Call expression is not using the import node", callExpression?.getText()); console.warn("Call expression is not using the import node", callExpression?.getText());
continue; continue;
} }
@@ -191,24 +201,42 @@ function recordNamespaceImportIdentifierUsage(importNode, mainFilePath, libraryC
continue; continue;
} }
const callArguments = callExpressionArguments.map((arg,i) => {
const callExpressionArg = arg.getType();
if(callExpressionArg.isAny()){
const funcCall = callExpression.getExpression();
const funcType = checker.getTypeAtLocation(funcCall);
const paramType = checker.getTypeAtLocation(funcCall)?.getCallSignatures()[0]?.getParameters()[i]
if(paramType !== undefined){
const paramArgType = checker.getTypeOfSymbolAtLocation(paramType,funcCall);
if(!paramArgType.isAny()){
console.log("[analyzer] Using scoped argument", paramArgType.getText(), "for argument", i, "of call", funcCall.getText());
return paramArgType;
}
}
}
return callExpressionArg;
});
// for(const argument of callExpressionArguments){ // for(const argument of callExpressionArguments){
// console.log(`Arg ${idx} is ${arg.getText()}, type is ${arg.getType()}`); // console.log(`Arg ${idx} is ${arg.getText()}, type is ${arg.getType()}`);
// } // }
// console.log("Noted call for namespace import", importStringLiteral.getLiteralValue(), getImportSection, callExpressionArguments.map(arg => arg.getType().getText())); // console.log("Noted call for namespace import", importStringLiteral.getLiteralValue(), getImportSection, callExpressionArguments.map(arg => arg.getType().getText()));
libraryCallsRecorder.pushToMap(importStringLiteral.getLiteralValue(), getImportSection, callExpressionArguments.map(arg => arg.getType())); libraryCallsRecorder.pushToMap(importStringLiteral.getLiteralValue(), getImportSection, callArguments);
} }
} }
} }
/** /**
* *
* @param {tsm.TypeChecker} checker
* @param {Identifier} importNode * @param {Identifier} importNode
* @param {string} mainFilePath * @param {string} mainFilePath
* @param {LibraryTypesRecorder} libraryCallsRecorder * @param {LibraryTypesRecorder} libraryCallsRecorder
* @param {StringLiteral} importStringLiteral * @param {StringLiteral} importStringLiteral
* @param {boolean} [isDefaultImport=false] * @param {boolean} [isDefaultImport=false]
*/ */
function recordImportedIdentifierUsage(importNode, mainFilePath, libraryCallsRecorder, importStringLiteral, isDefaultImport = false) { function recordImportedIdentifierUsage(checker, importNode, mainFilePath, libraryCallsRecorder, importStringLiteral, isDefaultImport = false) {
const importRefs = importNode.findReferences(); const importRefs = importNode.findReferences();
for (const importRef of importRefs) { for (const importRef of importRefs) {
const referenceSourceFile = importRef.getDefinition().getSourceFile(); const referenceSourceFile = importRef.getDefinition().getSourceFile();
@@ -271,7 +299,7 @@ export function isRelativeModule(moduleName) {
*/ */
export function isNodeModule(moduleName) { export function isNodeModule(moduleName) {
if (moduleName.startsWith('node:')) return true; if (moduleName.startsWith('node:')) return true;
const nodeModules = ['fs', 'fs/promises', 'path', 'http', 'https', 'os', 'crypto']; const nodeModules = ['fs', 'fs/promises', 'path', 'http', 'https', 'os', 'crypto','assert'];
return nodeModules.includes(moduleName); return nodeModules.includes(moduleName);
} }