UNPKG

dependency-cruiser

Version:

Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.

254 lines (226 loc) 8 kB
/* eslint-disable no-inline-comments */ /* eslint-disable max-classes-per-file */ import tryImport from "semver-try-require"; import meta from "../../meta.js"; /** @type {import('@swc/core/Visitor')} */ const { Visitor } = await tryImport( "@swc/core/Visitor.js", meta.supportedTranspilers.swc ); function pryStringsFromArguments(pArguments) { let lReturnValue = null; if (pArguments[0].expression.type === "StringLiteral") { lReturnValue = pArguments[0].expression.value; } else if (pArguments[0].expression.type === "TemplateLiteral") { /* c8 ignore start */ // @swc/core@1.2.159 and before: cooked.value. // @swc/core@1.2.160 and after: just cooked lReturnValue = pArguments[0].expression.quasis[0].cooked.value || pArguments[0].expression.quasis[0].cooked; } /* c8 ignore stop */ return lReturnValue; } function isPlaceholderlessTemplateLiteral(pArgument) { return ( pArgument.expression.type === "TemplateLiteral" && pArgument.expression.quasis.length === 1 && pArgument.expression.expressions.length === 0 ); } function argumentsAreUsable(pArguments) { return ( pArguments.length > 0 && ["StringLiteral", "TemplateLiteral"].includes( pArguments[0].expression.type ) && (!(pArguments[0].expression.type === "TemplateLiteral") || isPlaceholderlessTemplateLiteral(pArguments[0])) ); } function chunkIntoThing(pString) { const lChunks = pString.split("."); return { object: lChunks[0], property: lChunks[1], string: pString, }; } // to recognize exotic requires like 'window.require' function extractExoticMemberCallExpression(pNode, pExoticRequireStrings) { let lReturnValue = []; if (pNode.callee.type === "MemberExpression") { lReturnValue = pExoticRequireStrings .filter((pString) => pString.includes(".")) .map(chunkIntoThing) .reduce((pAll, pThing) => { if ( pNode.callee.object.value === pThing.object && pNode.callee.property.value === pThing.property && argumentsAreUsable(pNode.arguments) ) { return pAll.concat({ module: pryStringsFromArguments(pNode.arguments), moduleSystem: "cjs", exoticallyRequired: true, exoticRequire: pThing.string, dynamic: false, }); } return pAll; }, []); } return lReturnValue; } function isImportCallExpression(pNode) { /* somewhere between swc 1.2.123 and 1.2.133 the swc AST started to represent import call expressions with .callee.type === "Import" instead of .callee.value === "import". Keeping both detection methods in here for backwards compatibility */ return pNode.callee.value === "import" || pNode.callee.type === "Import"; } function isNonExoticallyRequiredExpression(pNode) { return pNode.callee.value === "require" || isImportCallExpression(pNode); } function isInterestingCallExpression(pExoticRequireStrings, pNode) { /* somewhere between swc 1.2.123 and 1.2.133 the swc AST started to represent import call expressions with .callee.type === "Import" instead of .callee.value === "import". Keeping both detection methods in here for backwards compatibility */ return ( pNode.callee.type === "Import" || ["require", "import"] .concat(pExoticRequireStrings.filter((pString) => !pString.includes("."))) .includes(pNode.callee.value) ); } export default Visitor ? class SwcDependencyVisitor extends Visitor { constructor(pExoticRequireStrings) { super(); this.lExoticRequireStrings = pExoticRequireStrings; } pushImportExportSource(pNode) { if (pNode.source) { this.lResult.push({ module: pNode.source.value, moduleSystem: "es6", exoticallyRequired: false, }); } } visitImportDeclaration(pNode) { this.pushImportExportSource(pNode); return super.visitImportDeclaration(pNode); } visitTsImportEqualsDeclaration(pNode) { if (pNode.moduleRef.type === "TsExternalModuleReference") { this.lResult.push({ module: pNode.moduleRef.expression.value, moduleSystem: "cjs", exoticallyRequired: false, }); } return super.visitTsImportEqualsDeclaration(pNode); } // note: super class contains a typo. This will eventually be corrected. // To anticipate that (and to remain backward compatible when that happens) // also include the same method, but with the correct spelling. visitExportAllDeclration(pNode) { this.pushImportExportSource(pNode); /* c8 ignore start */ // @ts-expect-error see above if (super.visitExportAllDeclration) { // @ts-expect-error see above return super.visitExportAllDeclration(pNode); } else { /* c8 ignore stop */ return super.visitExportAllDeclaration(pNode); } } /* c8 ignore start */ visitExportAllDeclaration(pNode) { return this.visitExportAllDeclration(pNode); } /* c8 ignore stop */ // same spelling error as the above - same solution visitExportNamedDeclration(pNode) { this.pushImportExportSource(pNode); /* c8 ignore start */ // @ts-expect-error see above if (super.visitExportNamedDeclration) { // @ts-expect-error see above return super.visitExportNamedDeclration(pNode); } else { /* c8 ignore stop */ return super.visitExportNamedDeclaration(pNode); } } /* c8 ignore start */ visitExportNamedDeclaration(pNode) { return this.visitExportNamedDeclration(pNode); } /* c8 ignore stop */ visitCallExpression(pNode) { if ( isInterestingCallExpression(this.lExoticRequireStrings, pNode) && argumentsAreUsable(pNode.arguments) ) { this.lResult.push({ module: pryStringsFromArguments(pNode.arguments), ...(isImportCallExpression(pNode) ? { moduleSystem: "es6", dynamic: true } : { moduleSystem: "cjs", dynamic: false }), ...(isNonExoticallyRequiredExpression(pNode) ? { exoticallyRequired: false } : { exoticallyRequired: true, exoticRequire: pNode.callee.value, }), }); } // using "window.require" as an exotic require string... this.lResult = this.lResult.concat( extractExoticMemberCallExpression(pNode, this.lExoticRequireStrings) ); return super.visitCallExpression(pNode); } /* c8 ignore start */ visitTsType(pNode) { // override so the 'visitTsType not implemented' error message // as defined in the super class doesn't appear return pNode; } /* c8 ignore stop */ visitTsTypeAnnotation(pNode) { // as visitors for some shapes of type annotations aren't completely // implemented yet (1.2.51) pNode can come in as null (also see // comments in accompanying unit test) if ( Boolean(pNode) && Boolean(pNode.typeAnnotation) && Boolean(pNode.typeAnnotation.argument) ) this.lResult.push({ module: pNode.typeAnnotation.argument.value, moduleSystem: "es6", exoticallyRequired: false, }); return super.visitTsTypeAnnotation(pNode); } // as far as I can tell swc doesn't do tripple slash directives (yet?) // visitTrippleSlashDirective(pNode)) {} getDependencies(pAST) { this.lResult = []; this.visitModule(pAST); return this.lResult; } } : /* c8 ignore start */ class Empty {}; /* c8 ignore stop */