Skip to content

Commit b858aee

Browse files
authored
fix(export-map): incorrect internal namespaces info (#64)
1 parent 2eca701 commit b858aee

File tree

10 files changed

+82
-71
lines changed

10 files changed

+82
-71
lines changed

.changeset/stale-sheep-yawn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
fix(export-map): incorrect internal `namespaces` info

src/rules/export.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import type { TSESTree } from '@typescript-eslint/utils'
22

3-
import { ExportMap, recursivePatternCapture, createRule } from '../utils'
3+
import {
4+
ExportMap,
5+
recursivePatternCapture,
6+
createRule,
7+
getValue,
8+
} from '../utils'
49

510
/*
611
Notes on TypeScript namespaces aka TSModuleDeclaration:
@@ -181,9 +186,7 @@ export = createRule<[], MessageId>({
181186

182187
ExportSpecifier(node) {
183188
addNamed(
184-
node.exported.name ||
185-
// @ts-expect-error - legacy parser type
186-
node.exported.value,
189+
getValue(node.exported),
187190
node.exported,
188191
getParent(node.parent!),
189192
)

src/rules/namespace.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ExportMap,
77
createRule,
88
declaredScope,
9+
getValue,
910
} from '../utils'
1011

1112
type MessageId =
@@ -62,9 +63,7 @@ function processBodyStatement(
6263
case 'ImportSpecifier': {
6364
const meta = imports.get(
6465
'imported' in specifier && specifier.imported
65-
? specifier.imported.name ||
66-
// @ts-expect-error - legacy parser node
67-
specifier.imported.value
66+
? getValue(specifier.imported)
6867
: // default to 'default' for default
6968
'default',
7069
)

src/rules/no-default-export.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export = createRule({
4242
'default',
4343
)) {
4444
const { loc } = getSourceCode(context).getFirstTokens(node)[1] || {}
45-
// @ts-expect-error - legacy parser type
45+
// @ts-expect-error - experimental parser type
4646
if (specifier.type === 'ExportDefaultSpecifier') {
4747
context.report({
4848
node,

src/rules/no-named-default.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createRule } from '../utils'
1+
import { createRule, getValue } from '../utils'
22

33
type MessageId = 'default'
44

@@ -31,9 +31,7 @@ export = createRule<[], MessageId>({
3131

3232
if (
3333
im.type === 'ImportSpecifier' &&
34-
(im.imported.name ||
35-
// @ts-expect-error - legacy parser type
36-
im.imported.value) === 'default'
34+
getValue(im.imported) === 'default'
3735
) {
3836
context.report({
3937
node: im.local,

src/rules/no-unused-modules.ts

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
getFileExtensions,
1818
readPkgUp,
1919
visit,
20+
getValue,
2021
} from '../utils'
2122

2223
function listFilesToProcess(src: string[], extensions: FileExtension[]) {
@@ -633,11 +634,7 @@ export = createRule<Options[], MessageId>({
633634
if (s.specifiers.length > 0) {
634635
for (const specifier of s.specifiers) {
635636
if (specifier.exported) {
636-
newExportIdentifiers.add(
637-
specifier.exported.name ||
638-
// @ts-expect-error - legacy parser type
639-
specifier.exported.value,
640-
)
637+
newExportIdentifiers.add(getValue(specifier.exported))
641638
}
642639
}
643640
}
@@ -753,10 +750,7 @@ export = createRule<Options[], MessageId>({
753750
context,
754751
)
755752
for (const specifier of astNode.specifiers) {
756-
const name =
757-
specifier.local.name ||
758-
// @ts-expect-error - legacy parser type
759-
specifier.local.value
753+
const name = getValue(specifier.local)
760754
if (name === DEFAULT) {
761755
newDefaultImports.add(resolvedPath!)
762756
} else {
@@ -800,12 +794,7 @@ export = createRule<Options[], MessageId>({
800794
specifier.type !== AST_NODE_TYPES.ImportNamespaceSpecifier,
801795
)) {
802796
if ('imported' in specifier) {
803-
newImports.set(
804-
specifier.imported.name ||
805-
// @ts-expect-error - legacy parser type
806-
specifier.imported.value,
807-
resolvedPath,
808-
)
797+
newImports.set(getValue(specifier.imported), resolvedPath)
809798
}
810799
}
811800
}
@@ -1004,12 +993,7 @@ export = createRule<Options[], MessageId>({
1004993
},
1005994
ExportNamedDeclaration(node) {
1006995
for (const specifier of node.specifiers) {
1007-
checkUsage(
1008-
specifier,
1009-
specifier.exported.name ||
1010-
// @ts-expect-error - legacy parser type
1011-
specifier.exported.value,
1012-
)
996+
checkUsage(specifier, getValue(specifier.exported))
1013997
}
1014998
forEachDeclarationIdentifier(node.declaration, name => {
1015999
checkUsage(node, name)

src/utils/export-map.ts

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
RuleContext,
2020
} from '../types'
2121

22+
import { getValue } from './get-value'
2223
import { hashObject } from './hash'
2324
import { hasValidExtension, ignore } from './ignore'
2425
import { parse } from './parse'
@@ -212,7 +213,7 @@ export class ExportMap {
212213
})
213214
}
214215

215-
const namespaces = new Map()
216+
const namespaces = new Map</* identifier */ string, /* source */ string>()
216217

217218
function remotePath(value: string) {
218219
return relative(value, filepath, context.settings)
@@ -226,25 +227,18 @@ export class ExportMap {
226227
return ExportMap.for(childContext(rp, context))
227228
}
228229

229-
function getNamespace(identifier: TSESTree.Identifier | string) {
230-
const namespace =
231-
typeof identifier === 'string' ? identifier : identifier.name
230+
function getNamespace(namespace: string) {
232231
if (!namespaces.has(namespace)) {
233232
return
234233
}
235234

236235
return function () {
237-
return resolveImport(namespaces.get(namespace))
236+
return resolveImport(namespaces.get(namespace)!)
238237
}
239238
}
240239

241-
function addNamespace(
242-
object: object,
243-
identifier: TSESTree.Identifier | string,
244-
) {
245-
const namespace =
246-
typeof identifier === 'string' ? identifier : identifier.name
247-
const nsfn = getNamespace(namespace)
240+
function addNamespace(object: object, identifier: TSESTree.Identifier) {
241+
const nsfn = getNamespace(getValue(identifier))
248242
if (nsfn) {
249243
Object.defineProperty(object, 'namespace', { get: nsfn })
250244
}
@@ -289,19 +283,16 @@ export class ExportMap {
289283
}
290284
case 'ExportAllDeclaration': {
291285
m.namespace.set(
292-
s.exported!.name ||
293-
// @ts-expect-error - legacy parser type
294-
s.exported!.value,
295-
addNamespace(exportMeta, s.source.value),
286+
getValue(s.exported!),
287+
addNamespace(exportMeta, s.exported!),
296288
)
289+
297290
return
298291
}
299292
case 'ExportSpecifier': {
300293
if (!('source' in n && n.source)) {
301294
m.namespace.set(
302-
s.exported.name ||
303-
// @ts-expect-error - legacy parser type
304-
s.exported.value,
295+
getValue(s.exported),
305296
addNamespace(exportMeta, s.local),
306297
)
307298
return
@@ -342,11 +333,7 @@ export class ExportMap {
342333
const importedSpecifiers = new Set<string>()
343334
for (const specifier of n.specifiers) {
344335
if (specifier.type === 'ImportSpecifier') {
345-
importedSpecifiers.add(
346-
specifier.imported.name ||
347-
// @ts-expect-error - legacy parser type
348-
specifier.imported.value,
349-
)
336+
importedSpecifiers.add(getValue(specifier.imported))
350337
} else if (supportedImportTypes.has(specifier.type)) {
351338
importedSpecifiers.add(specifier.type)
352339
}
@@ -460,6 +447,7 @@ export class ExportMap {
460447
m.dependencies.add(getter)
461448
}
462449
if (n.exported) {
450+
namespaces.set(n.exported.name, n.source.value)
463451
processSpecifier(n, n.exported, m)
464452
}
465453
continue
@@ -520,7 +508,9 @@ export class ExportMap {
520508
}
521509
}
522510

523-
for (const s of n.specifiers) processSpecifier(s, n, m)
511+
for (const s of n.specifiers) {
512+
processSpecifier(s, n, m)
513+
}
524514
}
525515

526516
const exports = ['TSExportAssignment']

src/utils/get-value.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { TSESTree } from '@typescript-eslint/utils'
2+
3+
export const getValue = (
4+
node: TSESTree.Identifier | TSESTree.StringLiteral,
5+
) => {
6+
switch (node.type) {
7+
case TSESTree.AST_NODE_TYPES.Identifier: {
8+
return node.name
9+
}
10+
case TSESTree.AST_NODE_TYPES.Literal: {
11+
return node.value
12+
}
13+
default: {
14+
throw new Error(`Unsupported node type: ${(node as TSESTree.Node).type}`)
15+
}
16+
}
17+
}

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './docs-url'
55
export * from './export-map'
66
export * from './get-ancestors'
77
export * from './get-scope'
8+
export * from './get-value'
89
export * from './hash'
910
export * from './ignore'
1011
export * from './import-declaration'

test/utils/export-map.spec.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,14 @@ describe('ExportMap', () => {
107107
.readFileSync(path, { encoding: 'utf8' })
108108
.replaceAll(/\r?\n/g, lineEnding)
109109

110-
const imports = ExportMap.parse(path, contents, parseContext)!
110+
let imports: ExportMap
111111

112-
// sanity checks
113-
expect(imports.errors).toHaveLength(0)
112+
beforeAll(() => {
113+
imports = ExportMap.parse(path, contents, parseContext)!
114+
115+
// sanity checks
116+
expect(imports.errors).toHaveLength(0)
117+
})
114118

115119
it('works with named imports.', () => {
116120
expect(imports.has('fn')).toBe(true)
@@ -184,10 +188,15 @@ describe('ExportMap', () => {
184188
describe('full module', () => {
185189
const path = testFilePath('deprecated-file.js')
186190
const contents = fs.readFileSync(path, { encoding: 'utf8' })
187-
const imports = ExportMap.parse(path, contents, parseContext)!
188191

189-
// sanity checks
190-
expect(imports.errors).toHaveLength(0)
192+
let imports: ExportMap
193+
194+
beforeAll(() => {
195+
imports = ExportMap.parse(path, contents, parseContext)!
196+
197+
// sanity checks
198+
expect(imports.errors).toHaveLength(0)
199+
})
191200

192201
it('has JSDoc metadata', () => {
193202
expect(imports.doc).toBeDefined()
@@ -281,8 +290,7 @@ describe('ExportMap', () => {
281290
expect(def.get('default')!.namespace!.has('c')).toBe(true)
282291
})
283292

284-
// FIXME: check and enable
285-
it.skip('works with @babel/eslint-parser & ES7 namespace exports', function () {
293+
it('works with @babel/eslint-parser & ES7 namespace exports', function () {
286294
const path = testFilePath('deep-es7/a.js')
287295
const contents = fs.readFileSync(path, { encoding: 'utf8' })
288296
const a = ExportMap.parse(path, contents, babelContext)!
@@ -376,10 +384,8 @@ describe('ExportMap', () => {
376384
settings: { 'import-x/extensions': ['.js'] as const },
377385
}
378386

379-
const imports = ExportMap.get('./typescript.ts', context)
380-
381387
it('returns nothing for a TypeScript file', () => {
382-
expect(imports).toBeFalsy()
388+
expect(ExportMap.get('./typescript.ts', context)).toBeFalsy()
383389
})
384390
})
385391

@@ -395,9 +401,17 @@ describe('ExportMap', () => {
395401

396402
jest.setTimeout(20e3) // takes a long time :shrug:
397403

398-
const spied = jest.spyOn(getTsconfig, 'getTsconfig').mockClear()
404+
const spied = jest.spyOn(getTsconfig, 'getTsconfig')
405+
406+
let imports: ExportMap
399407

400-
const imports = ExportMap.get('./typescript.ts', context)!
408+
beforeAll(() => {
409+
imports = ExportMap.get('./typescript.ts', context)!
410+
})
411+
412+
beforeEach(() => {
413+
spied.mockClear()
414+
})
401415

402416
afterAll(() => {
403417
spied.mockRestore()

0 commit comments

Comments
 (0)