diff --git a/src/analyze.ts b/src/analyze.ts index b4c0371..04f97c0 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -84,11 +84,17 @@ const EXPORT_DEFAULT_RE = /\bexport\s+default\s+/g; const TYPE_RE = /^\s*?type\s/; export function findStaticImports(code: string): StaticImport[] { - return matchAll(ESM_STATIC_IMPORT_RE, code, { type: "static" }); + return _filterStatement( + _tryGetLocations(code, "import"), + matchAll(ESM_STATIC_IMPORT_RE, code, { type: "static" }), + ); } export function findDynamicImports(code: string): DynamicImport[] { - return matchAll(DYNAMIC_IMPORT_RE, code, { type: "dynamic" }); + return _filterStatement( + _tryGetLocations(code, "import"), + matchAll(DYNAMIC_IMPORT_RE, code, { type: "dynamic" }), + ); } export function findTypeImports(code: string): TypeImport[] { @@ -217,17 +223,14 @@ export function findExports(code: string): ESMExport[] { if (exports.length === 0) { return []; } - const exportLocations = _tryGetExportLocations(code); + const exportLocations = _tryGetLocations(code, "export"); if (exportLocations && exportLocations.length === 0) { return []; } return ( - exports - // Filter false positive export matches - .filter( - (exp) => !exportLocations || _isExportStatement(exportLocations, exp), - ) + // Filter false positive export matches + _filterStatement(exportLocations, exports) // Prevent multiple exports of same function, only keep latest iteration of signatures .filter((exp, index, exports) => { const nextExport = exports[index + 1]; @@ -266,17 +269,14 @@ export function findTypeExports(code: string): ESMExport[] { if (exports.length === 0) { return []; } - const exportLocations = _tryGetExportLocations(code); + const exportLocations = _tryGetLocations(code, "export"); if (exportLocations && exportLocations.length === 0) { return []; } return ( - exports - // Filter false positive export matches - .filter( - (exp) => !exportLocations || _isExportStatement(exportLocations, exp), - ) + // Filter false positive export matches + _filterStatement(exportLocations, exports) // Prevent multiple exports of same function, only keep latest iteration of signatures .filter((exp, index, exports) => { const nextExport = exports[index + 1]; @@ -359,23 +359,31 @@ interface TokenLocation { end: number; } -function _isExportStatement(exportsLocation: TokenLocation[], exp: ESMExport) { - return exportsLocation.some((location) => { - // AST token inside the regex match - return exp.start <= location.start && exp.end >= location.end; - // AST Token start or end is within the regex match - // return (exp.start <= location.start && location.start <= exp.end) || - // (exp.start <= location.end && location.end <= exp.end) +function _filterStatement( + locations: TokenLocation[] | undefined, + statements: T[], +): T[] { + return statements.filter((exp) => { + return ( + !locations || + locations.some((location) => { + // AST token inside the regex match + return exp.start <= location.start && exp.end >= location.end; + // AST Token start or end is within the regex match + // return (exp.start <= location.start && location.start <= exp.end) || + // (exp.start <= location.end && location.end <= exp.end) + }) + ); }); } -function _tryGetExportLocations(code: string) { +function _tryGetLocations(code: string, label: string) { try { - return _getExportLocations(code); + return _getLocations(code, label); } catch {} } -function _getExportLocations(code: string) { +function _getLocations(code: string, label: string) { const tokens = tokenizer(code, { ecmaVersion: "latest", sourceType: "module", @@ -385,7 +393,7 @@ function _getExportLocations(code: string) { }); const locations: TokenLocation[] = []; for (const token of tokens) { - if (token.type.label === "export") { + if (token.type.label === label) { locations.push({ start: token.start, end: token.end, diff --git a/test/imports.test.ts b/test/imports.test.ts index d17ede4..9a1181f 100644 --- a/test/imports.test.ts +++ b/test/imports.test.ts @@ -136,6 +136,32 @@ staticTests['import { foo, type Foo } from "foo"'] = { namedImports: { foo: "foo" }, }; +staticTests[ + ` + // import { foo } from "foo" + import { too } from "too" + + /** + * import { zoo } from "zoo" + */ + + const start = '/*' + import { ioo } from "ioo" + const end = '*/' +` +] = [ + { + specifier: "too", + type: "static", + namedImports: { too: "too" }, + }, + { + specifier: "ioo", + type: "static", + namedImports: { ioo: "ioo" }, + }, +]; + // -- Dynamic import -- const dynamicTests = { 'const { test, /* here */, another, } = await import ( "module-name" );': { @@ -153,6 +179,8 @@ const dynamicTests = { 'import("abc").then(r => r.default)': { expression: '"abc"', }, + '// import("abc").then(r => r.default)': [], + '/* import("abc").then(r => r.default) */': [], }; const TypeTests = { @@ -237,10 +265,12 @@ describe("findDynamicImports", () => { for (const [input, test] of Object.entries(dynamicTests)) { it(input.replace(/\n/g, "\\n"), () => { const matches = findDynamicImports(input); - expect(matches.length).to.equal(1); + expect(matches.length).to.equal(Array.isArray(test) ? test.length : 1); const match = matches[0]; - expect(match.type).to.equal("dynamic"); - expect(match.expression.trim()).to.equal(test.expression); + if (match) { + expect(match.type).to.equal("dynamic"); + expect(match.expression.trim()).to.equal(test.expression); + } }); } });