Visitar URL original
fix: filter out reserved words from `exp.names` (#504) · unjs/unimport@464a023 · GitHub
Skip to content

Commit 464a023

Browse files
authored
fix: filter out reserved words from exp.names (#504)
1 parent 47cffcf commit 464a023

4 files changed

Lines changed: 82 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Regression test: mlly's regex parser incorrectly captures `async` from
2+
// arrow function bodies as an export name. unimport should filter it out.
3+
export const useSleep = () => useAsyncData('sleep', async () => { })

‎src/node/scan-dirs.ts‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,65 @@ import pm from 'picomatch'
1111
import { camelCase } from 'scule'
1212
import { glob } from 'tinyglobby'
1313

14+
// JavaScript reserved words and keywords that mlly's regex parser may
15+
// incorrectly capture as export names from declaration expressions.
16+
// See: https://github.com/unjs/unimport/issues/303
17+
const JS_RESERVED_WORDS = new Set([
18+
// Keywords
19+
'abstract',
20+
'arguments',
21+
'async',
22+
'await',
23+
'break',
24+
'case',
25+
'catch',
26+
'class',
27+
'const',
28+
'continue',
29+
'debugger',
30+
'default',
31+
'delete',
32+
'do',
33+
'else',
34+
'enum',
35+
'eval',
36+
'export',
37+
'extends',
38+
'false',
39+
'finally',
40+
'for',
41+
'function',
42+
'if',
43+
'implements',
44+
'import',
45+
'in',
46+
'instanceof',
47+
'interface',
48+
'let',
49+
'new',
50+
'null',
51+
'of',
52+
'package',
53+
'private',
54+
'protected',
55+
'public',
56+
'return',
57+
'static',
58+
'super',
59+
'switch',
60+
'this',
61+
'throw',
62+
'true',
63+
'try',
64+
'typeof',
65+
'undefined',
66+
'var',
67+
'void',
68+
'while',
69+
'with',
70+
'yield',
71+
])
72+
1473
const FileExtensionLookup = [
1574
'mts',
1675
'cts',
@@ -139,6 +198,8 @@ export async function scanExports(filepath: string, includeTypes: boolean, seen
139198
}
140199
else if (exp.type === 'declaration') {
141200
for (const name of exp.names) {
201+
if (JS_RESERVED_WORDS.has(name))
202+
continue
142203
imports.push({ name, as: name, from: filepath, ...additional })
143204
if (exp.declarationType === 'enum' || exp.declarationType === 'const enum' || exp.declarationType === 'class') {
144205
imports.push({ name, as: name, from: filepath, type: true, declarationType: exp.declarationType, ...additional })

‎test/dts.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ it('dts', async () => {
9696
const useDoubled: typeof import('<root>/playground/composables/index').useDoubled
9797
const useEffect: typeof import('react').useEffect
9898
const useRef: typeof import('react').useRef
99+
const useSleep: typeof import('<root>/playground/composables/async-value').useSleep
99100
const useState: typeof import('react').useState
100101
const vanillaA: typeof import('<root>/playground/composables/vanilla').vanillaA
101102
const vanillaAMJS: typeof import('<root>/playground/composables/vanilla').vanillaAMJS

‎test/scan-dirs.test.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { join, relative } from 'pathe'
22
import { describe, expect, it } from 'vitest'
33
import { normalizeScanDirs, scanDirExports, stringifyImports } from '../src'
4+
import { scanExports } from '../src/node/scan-dirs'
45

56
describe('scan-dirs', () => {
67
it('scanDirExports', async () => {
@@ -90,6 +91,11 @@ describe('scan-dirs', () => {
9091
"from": "index.ts",
9192
"name": "useDoubled",
9293
},
94+
{
95+
"as": "useSleep",
96+
"from": "async-value.ts",
97+
"name": "useSleep",
98+
},
9399
{
94100
"as": "vanillaA",
95101
"from": "vanilla.js",
@@ -279,6 +285,17 @@ describe('scan-dirs', () => {
279285
const singleAsteriskExports = await scanDirExports([dirWithSingleAsterisk])
280286
expect(singleAsteriskExports.some(i => i.name === 'CustomType2')).toEqual(true)
281287
})
288+
289+
it('should not register JS reserved words as exports from declaration expressions', async () => {
290+
// mlly's regex parser incorrectly captures `async` from arrow function
291+
// bodies (e.g. `export const x = () => foo(async () => {})`) as an export
292+
// name. unimport should filter these out.
293+
const filepath = join(__dirname, '../playground/composables/async-value.ts')
294+
const exports = await scanExports(filepath, false)
295+
const names = exports.map(i => i.name)
296+
expect(names).toContain('useSleep')
297+
expect(names).not.toContain('async')
298+
})
282299
})
283300

284301
it('normalizeScanDirs', () => {

0 commit comments

Comments
 (0)