From 7e877b65b08fc24737ad688e86cfad72f5800654 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 5 Jan 2023 12:19:11 +0100 Subject: [PATCH] feat(findExports): support for exports destructuring (#133) --- src/analyze.ts | 17 +++++++++++++++++ src/utils.ts | 6 +++--- test/exports.test.ts | 6 +++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/analyze.ts b/src/analyze.ts index 3a44a9c..cf1a83c 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -60,6 +60,7 @@ export const DYNAMIC_IMPORT_RE = /import\s*\((?(?:[^()]+|\((?:[^()]+ export const EXPORT_DECAL_RE = /\bexport\s+(?(async function|function|let|const enum|const|enum|var|class))\s+(?[\w$]+)/g; const EXPORT_NAMED_RE = /\bexport\s+{(?[^}]+?)[\s,]*}(\s*from\s*["']\s*(?(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][^\n;]*)?/g; +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; const TYPE_RE = /^\s*?type\s/; @@ -110,6 +111,21 @@ export function findExports (code: string): ESMExport[] { .map(name => name.replace(/^.*?\sas\s/, "").trim()); } + const destructuredExports: NamedExport[] = matchAll(EXPORT_NAMED_DESTRUCT, code, { type: "named" }); + for (const namedExport of destructuredExports) { + // @ts-expect-error groups + namedExport.exports = namedExport.exports1 || namedExport.exports2 + namedExport.names = namedExport.exports + .replace(/^\r?\n?/, "") + .split(/\s*,\s*/g) + .filter(name => !TYPE_RE.test(name)) + .map(name => name + .replace(/^.*?\s*:\s*/, "") + .replace(/\s*=\s*.*$/, "") + .trim() + ) + } + // Find export default const defaultExport: DefaultExport[] = matchAll(EXPORT_DEFAULT_RE, code, { type: "default", name: "default" }); @@ -121,6 +137,7 @@ export function findExports (code: string): ESMExport[] { const exports: ESMExport[] = [ ...declaredExports, ...namedExports, + ...destructuredExports, ...defaultExport, ...starExports ]; diff --git a/src/utils.ts b/src/utils.ts index 2c8ac0d..0dfff56 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,11 +13,11 @@ export function fileURLToPath (id: string): string { // eslint-disable-next-line no-control-regex const INVALID_CHAR_RE = /[\u0000-\u001F"#$&*+,/:;<=>?@[\]^`{|}\u007F]+/g; -export function sanitizeURIComponent (name: string = "", replacement: string = "_"): string { +export function sanitizeURIComponent (name = "", replacement = "_"): string { return name.replace(INVALID_CHAR_RE, replacement); } -export function sanitizeFilePath (filePath: string = "") { +export function sanitizeFilePath (filePath = "") { return filePath.split(/[/\\]/g).map(p => sanitizeURIComponent(p)).join("/") .replace(/^([A-Za-z])_\//, "$1:/"); } @@ -46,7 +46,7 @@ export function toDataURL (code: string): string { return `data:text/javascript;base64,${base64}`; } -export function isNodeBuiltin (id: string = "") { +export function isNodeBuiltin (id = "") { // node:fs/promises => fs id = id.replace(/^node:/, "").split("/")[0]; return BUILTIN_MODULES.has(id); diff --git a/test/exports.test.ts b/test/exports.test.ts index 0182843..e4ba577 100644 --- a/test/exports.test.ts +++ b/test/exports.test.ts @@ -19,7 +19,11 @@ describe("findExports", () => { // eslint-disable-next-line no-template-curly-in-string "const a = ``;\nexport default true;": { type: "default", name: "default", names: ["default"] }, "export const enum foo { a = 'xx' }": { type: "declaration", names: ["foo"] }, - "export enum bar { a = 'xx' }": { type: "declaration", names: ["bar"] } + "export enum bar { a = 'xx' }": { type: "declaration", names: ["bar"] }, + "export const { a, b } = foo": { type: "named", names: ["a", "b"] }, + "export const [ a, b ] = foo": { type: "named", names: ["a", "b"] }, + "export const [\na\n, b ] = foo": { type: "named", names: ["a", "b"] }, + "export const [ a:b,\nc = 1] = foo": { type: "named", names: ["b", "c"] } }; for (const [input, test] of Object.entries(tests)) {