From 08a102b1ccc8846da15c658f9861a2b1f8e19581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nagy=20Ma=CC=81te=CC=81?= Date: Sat, 8 Jul 2023 13:26:48 +0200 Subject: [PATCH 1/5] named default export --- src/analyze.ts | 33 +++++++++++++++++++++++++++++---- test/exports.test.ts | 2 +- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/analyze.ts b/src/analyze.ts index f499f84..6c271ec 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -34,8 +34,8 @@ export interface TypeImport extends Omit { } export interface ESMExport { - _type?: "declaration" | "named" | "default" | "star"; - type: "declaration" | "named" | "default" | "star"; + _type?: "declaration" | "named" | "default" | "namedDefault" | "star"; + type: "declaration" | "named" | "default" | "namedDefault" | "star"; code: string; start: number; end: number; @@ -80,7 +80,12 @@ const EXPORT_NAMED_DESTRUCT = /\bexport\s+(let|var|const)\s+(?:{(?[^}]+?)[\s,]*}|\[(?[^\]]+?)[\s,]*])\s+=/gm; const EXPORT_STAR_RE = /\bexport\s*(\*)(\s*as\s+(?[\w$]+)\s+)?\s*(\s*from\s*["']\s*(?(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][^\n;]*)?/g; -const EXPORT_DEFAULT_RE = /\bexport\s+default\s+/g; +// updated export default to prevent duplication when named export deafult happen +const EXPORT_DEFAULT_RE = + /\bexport\s+default\s+(async function|function|class|true|false|(\W\D)|\d)/g; +// named export default +const EXPORT_NAMED_DEFAULT_RE = + /\bexport\s+default\s+(?!async function|function|class|true|false|\W|\d)\w*/g; const TYPE_RE = /^\s*?type\s/; export function findStaticImports(code: string): StaticImport[] { @@ -177,6 +182,7 @@ export function findExports(code: string): ESMExport[] { code, { type: "named" } ); + for (const namedExport of destructuredExports) { // @ts-expect-error groups namedExport.exports = namedExport.exports1 || namedExport.exports2; @@ -198,6 +204,20 @@ export function findExports(code: string): ESMExport[] { name: "default", }); + // Find export default + const namedDefaultExport: DefaultExport[] = matchAll( + EXPORT_NAMED_DEFAULT_RE, + code, + { + type: "namedDefault", + code, + } + ).map((exp) => { + exp.name = "default"; + + return exp; + }); + // Find export star const starExports: ESMExport[] = matchAll(EXPORT_STAR_RE, code, { type: "star", @@ -210,6 +230,7 @@ export function findExports(code: string): ESMExport[] { ...namedExports, ...destructuredExports, ...defaultExport, + ...namedDefaultExport, ...starExports, ]); @@ -295,7 +316,11 @@ function normalizeExports(exports: ESMExport[]) { if (!exp.name && exp.names && exp.names.length === 1) { exp.name = exp.names[0]; } - if (exp.name === "default" && exp.type !== "default") { + if ( + exp.name === "default" && + exp.type !== "default" && + exp.type !== "namedDefault" + ) { exp._type = exp.type; exp.type = "default"; } diff --git a/test/exports.test.ts b/test/exports.test.ts index 4fbba3a..5a447f4 100644 --- a/test/exports.test.ts +++ b/test/exports.test.ts @@ -22,7 +22,7 @@ describe("findExports", () => { type: "named", }, "export default foo": { - type: "default", + type: "namedDefault", name: "default", names: ["default"], }, From 1784a7545b9fdf652e0a1f04224c4f3a21c57975 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 11:14:56 +0000 Subject: [PATCH 2/5] chore: apply automated lint fixes --- src/analyze.ts | 60 ++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/analyze.ts b/src/analyze.ts index a254cbd..6a38524 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -89,11 +89,17 @@ const EXPORT_NAMED_DEFAULT_RE = 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[] { @@ -211,7 +217,7 @@ export function findExports(code: string): ESMExport[] { { type: "namedDefault", code, - } + }, ).map((exp) => { exp.name = "default"; @@ -238,17 +244,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]; @@ -287,17 +290,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]; @@ -384,23 +384,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", @@ -410,7 +418,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, From e602bc1ac9adaa9b8cdab99b8eff650e2ea46b99 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 11 Jan 2024 12:31:22 +0100 Subject: [PATCH 3/5] refactor: expose as `defaultName` --- src/analyze.ts | 28 +++------------------------- test/exports.test.ts | 11 ++++++++++- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/analyze.ts b/src/analyze.ts index 6a38524..f53017e 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -40,6 +40,7 @@ export interface ESMExport { start: number; end: number; name?: string; + defaultName?: string; names: string[]; specifier?: string; } @@ -80,12 +81,8 @@ const EXPORT_NAMED_DESTRUCT = /\bexport\s+(let|var|const)\s+(?:{(?[^}]+?)[\s,]*}|\[(?[^\]]+?)[\s,]*])\s+=/gm; const EXPORT_STAR_RE = /\bexport\s*(\*)(\s*as\s+(?[\w$]+)\s+)?\s*(\s*from\s*["']\s*(?(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][^\n;]*)?/g; -// updated export default to prevent duplication when named export deafult happen const EXPORT_DEFAULT_RE = - /\bexport\s+default\s+(async function|function|class|true|false|(\W\D)|\d)/g; -// named export default -const EXPORT_NAMED_DEFAULT_RE = - /\bexport\s+default\s+(?!async function|function|class|true|false|\W|\d)\w*/g; + /\bexport\s+default\s+(async function|function|class|true|false|(\W\D)|\d)|\bexport\s+default\s+(?!async function|function|class|true|false|\W|\d)(?\w+)/g; const TYPE_RE = /^\s*?type\s/; export function findStaticImports(code: string): StaticImport[] { @@ -210,20 +207,6 @@ export function findExports(code: string): ESMExport[] { name: "default", }); - // Find export default - const namedDefaultExport: DefaultExport[] = matchAll( - EXPORT_NAMED_DEFAULT_RE, - code, - { - type: "namedDefault", - code, - }, - ).map((exp) => { - exp.name = "default"; - - return exp; - }); - // Find export star const starExports: ESMExport[] = matchAll(EXPORT_STAR_RE, code, { type: "star", @@ -236,7 +219,6 @@ export function findExports(code: string): ESMExport[] { ...namedExports, ...destructuredExports, ...defaultExport, - ...namedDefaultExport, ...starExports, ]); @@ -316,11 +298,7 @@ function normalizeExports(exports: ESMExport[]) { if (!exp.name && exp.names && exp.names.length === 1) { exp.name = exp.names[0]; } - if ( - exp.name === "default" && - exp.type !== "default" && - exp.type !== "namedDefault" - ) { + if (exp.name === "default" && exp.type !== "default") { exp._type = exp.type; exp.type = "default"; } diff --git a/test/exports.test.ts b/test/exports.test.ts index e8f6f85..c4ed759 100644 --- a/test/exports.test.ts +++ b/test/exports.test.ts @@ -22,7 +22,7 @@ describe("findExports", () => { type: "named", }, "export default foo": { - type: "namedDefault", + type: "default", name: "default", names: ["default"], }, @@ -53,6 +53,12 @@ describe("findExports", () => { "export async function* foo ()": { type: "declaration", names: ["foo"] }, "export async function *foo ()": { type: "declaration", names: ["foo"] }, "export const $foo = () => {}": { type: "declaration", names: ["$foo"] }, + "export default something": { + type: "default", + name: "default", + defaultName: "something", + names: ["default"], + }, "export { foo as default }": { type: "default", name: "default", @@ -95,6 +101,9 @@ describe("findExports", () => { if (test.specifier) { expect(match.specifier).toEqual(test.specifier); } + if (test.defaultName) { + expect(match.defaultName).toEqual(test.defaultName); + } }); } it("handles multiple exports", () => { From 7164671b3a920757ff0a60db6a654a1b78d6c274 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 11 Jan 2024 12:35:21 +0100 Subject: [PATCH 4/5] manual rebase --- src/analyze.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/analyze.ts b/src/analyze.ts index f53017e..cc9688c 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -34,13 +34,12 @@ export interface TypeImport extends Omit { } export interface ESMExport { - _type?: "declaration" | "named" | "default" | "namedDefault" | "star"; - type: "declaration" | "named" | "default" | "namedDefault" | "star"; + _type?: "declaration" | "named" | "default" | "star"; + type: "declaration" | "named" | "default" | "star"; code: string; start: number; end: number; name?: string; - defaultName?: string; names: string[]; specifier?: string; } @@ -185,7 +184,6 @@ export function findExports(code: string): ESMExport[] { code, { type: "named" }, ); - for (const namedExport of destructuredExports) { // @ts-expect-error groups namedExport.exports = namedExport.exports1 || namedExport.exports2; From 2f9f99cb4be42d58ad4e2e303c0b5df02719168d Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 11 Jan 2024 12:39:33 +0100 Subject: [PATCH 5/5] minify regex --- src/analyze.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analyze.ts b/src/analyze.ts index cc9688c..2bb87e6 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -81,7 +81,7 @@ const EXPORT_NAMED_DESTRUCT = const EXPORT_STAR_RE = /\bexport\s*(\*)(\s*as\s+(?[\w$]+)\s+)?\s*(\s*from\s*["']\s*(?(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][^\n;]*)?/g; const EXPORT_DEFAULT_RE = - /\bexport\s+default\s+(async function|function|class|true|false|(\W\D)|\d)|\bexport\s+default\s+(?!async function|function|class|true|false|\W|\d)(?\w+)/g; + /\bexport\s+default\s+(async function|function|class|true|false|\W|\d)|\bexport\s+default\s+(?.*)/g; const TYPE_RE = /^\s*?type\s/; export function findStaticImports(code: string): StaticImport[] {