Skip to content

Commit 9715220

Browse files
authoredOct 30, 2024··
refactor: backport a few upstream changes (#171)
1 parent 0bac033 commit 9715220

19 files changed

+135
-39
lines changed
 

‎.changeset/fast-carpets-teach.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
Perf: avoid regexp during parser choosing

‎.changeset/hot-jokes-grab.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
Add extra guard for rule `no-named-as-default`. A few guards are borrowed from https://github.com/import-js/eslint-plugin-import/pull/3032, but we don't sync the rest of changes from upstream since we have already implemented a way more performant check.

‎.changeset/old-years-rest.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
More test cases for `no-named-export` and `no-defualt-export` rule specifically with non-module `sourceType`

‎.changeset/orange-dryers-mix.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
Fix `export` when there is only one `TSDeclareFunction` (https://github.com/import-js/eslint-plugin-import/pull/3065)

‎.changeset/shaggy-mayflies-applaud.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
Prevent `ExportMap`'s cache is being tainted by incompatible parser (e.g. old `babel-eslint`). The cache is now skipped w/ incompatible parsers, which might introduce performance impacts only for those who are using incompatible parsers. (https://github.com/import-js/eslint-plugin-import/pull/3062)

‎.changeset/spicy-mangos-pull.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
Docs: fix a few typos here and there

‎.changeset/tough-elephants-divide.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
Properly fix espree parser w/ ESLint Flat Config

‎docs/rules/no-relative-packages.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66

77
Use this rule to prevent importing packages through relative paths.
88

9-
It's useful in Yarn/Lerna workspaces, were it's possible to import a sibling
10-
package using `../package` relative path, while direct `package` is the correct one.
9+
It's useful in a monorepo setup, where it's possible to import a sibling package using `../package` relative path, while direct `package` is the correct one.
1110

1211
## Examples
1312

‎docs/rules/no-restricted-paths.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Some projects contain files which are not always meant to be executed in the same environment.
66
For example consider a web application that contains specific code for the server and some specific code for the browser/client. In this case you don’t want to import server-only files in your client code.
77

8-
In order to prevent such scenarios this rule allows you to define restricted zones where you can forbid files from imported if they match a specific path.
8+
In order to prevent such scenarios this rule allows you to define restricted zones where you can forbid files from being imported if they match a specific path.
99

1010
## Rule Details
1111

‎src/rules/export.ts

+19-28
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
12
import type { TSESTree } from '@typescript-eslint/utils'
23

34
import {
@@ -30,37 +31,24 @@ const rootProgram = 'root'
3031
const tsTypePrefix = 'type:'
3132

3233
/**
33-
* Detect function overloads like:
34+
* Remove function overloads like:
35+
*
3436
* ```ts
3537
* export function foo(a: number);
3638
* export function foo(a: string);
3739
* export function foo(a: number|string) { return a; }
3840
* ```
3941
*/
40-
function isTypescriptFunctionOverloads(nodes: Set<TSESTree.Node>) {
41-
const nodesArr = [...nodes]
42-
43-
const idents = nodesArr.flatMap(node =>
44-
'declaration' in node && node.declaration?.type === 'TSDeclareFunction'
45-
? node.declaration.id!.name
46-
: [],
47-
)
48-
49-
if (new Set(idents).size !== idents.length) {
50-
return true
51-
}
52-
53-
const types = new Set(nodesArr.map(node => `${node.parent!.type}` as const))
54-
if (!types.has('TSDeclareFunction')) {
55-
return false
56-
}
57-
if (types.size === 1) {
58-
return true
59-
}
60-
if (types.size === 2 && types.has('FunctionDeclaration')) {
61-
return true
42+
function removeTypescriptFunctionOverloads(nodes: Set<TSESTree.Node>) {
43+
for (const node of nodes) {
44+
const declType =
45+
node.type === AST_NODE_TYPES.ExportDefaultDeclaration
46+
? node.declaration.type
47+
: node.parent?.type
48+
if (declType === AST_NODE_TYPES.TSDeclareFunction) {
49+
nodes.delete(node)
50+
}
6251
}
63-
return false
6452
}
6553

6654
/**
@@ -277,14 +265,17 @@ export = createRule<[], MessageId>({
277265
'Program:exit'() {
278266
for (const [, named] of namespace) {
279267
for (const [name, nodes] of named) {
268+
if (nodes.size === 0) {
269+
continue
270+
}
271+
272+
removeTypescriptFunctionOverloads(nodes)
273+
280274
if (nodes.size <= 1) {
281275
continue
282276
}
283277

284-
if (
285-
isTypescriptFunctionOverloads(nodes) ||
286-
isTypescriptNamespaceMerging(nodes)
287-
) {
278+
if (isTypescriptNamespaceMerging(nodes)) {
288279
continue
289280
}
290281

‎src/rules/no-named-as-default.ts

+11
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,17 @@ export = createRule<[], MessageId>({
4949
return
5050
}
5151

52+
if (!exportMapOfImported.hasDefault) {
53+
// The rule is triggered for default imports/exports, so if the imported module has no default
54+
// this means we're dealing with incorrect source code anyway
55+
return
56+
}
57+
58+
if (!exportMapOfImported.has(nameValue)) {
59+
// The name used locally for the default import was not even used in the imported module.
60+
return
61+
}
62+
5263
if (
5364
exportMapOfImported.exports.has('default') &&
5465
exportMapOfImported.exports.has(nameValue)

‎src/utils/export-map.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,11 @@ export class ExportMap {
128128

129129
exportMap.mtime = stats().mtime.valueOf()
130130

131-
exportCache.set(cacheKey, exportMap)
131+
// If the visitor keys were not populated, then we shouldn't save anything to the cache,
132+
// since the parse results may not be reliable.
133+
if (exportMap.visitorKeys) {
134+
exportCache.set(cacheKey, exportMap)
135+
}
132136

133137
return exportMap
134138
}

‎src/utils/parse.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,22 @@ function withoutProjectParserOptions(
2929
const log = debug('eslint-plugin-import-x:parse')
3030

3131
function keysFromParser(
32-
parserPath: string | TSESLint.Parser.ParserModule,
32+
_parserPath: string | TSESLint.Parser.ParserModule,
3333
parserInstance: TSESLint.Parser.ParserModule,
3434
parsedResult?: TSESLint.Parser.ParseResult,
3535
) {
3636
// Exposed by @typescript-eslint/parser and @babel/eslint-parser
3737
if (parsedResult && parsedResult.visitorKeys) {
3838
return parsedResult.visitorKeys
3939
}
40-
if (typeof parserPath === 'string' && /.*espree.*/.test(parserPath)) {
41-
// @ts-expect-error - no type yet
40+
41+
// The espree parser doesn't have the `parseForESLint` function, so we don't ended up with a
42+
// `parsedResult` here, but it does expose the visitor keys on the parser instance that we can use.
43+
if (
44+
parserInstance &&
45+
'VisitorKeys' in parserInstance &&
46+
parserInstance.VisitorKeys
47+
) {
4248
return parserInstance.VisitorKeys as TSESLint.SourceCode.VisitorKeys
4349
}
4450
return null
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "order-redirect-module",
2+
"name": "order-redirect-module-scoped",
33
"private": true,
44
"main": "../other-module/file.js"
55
}

‎test/rules/export.spec.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ ruleTester.run('export', rule, {
5858
}
5959
`,
6060
},
61+
{
62+
code: `
63+
export default function foo(param: string): boolean;
64+
export default function foo(param: string, param1?: number): boolean {
65+
return param && param1;
66+
}
67+
`,
68+
},
6169
],
6270

6371
invalid: [
@@ -160,6 +168,15 @@ ruleTester.run('export', rule, {
160168
},
161169
},
162170
}),
171+
172+
test({
173+
code: `
174+
export default function a(): void;
175+
export default function a() {}
176+
export { x as default };
177+
`,
178+
errors: ['Multiple default exports.', 'Multiple default exports.'],
179+
}),
163180
],
164181
})
165182

@@ -510,7 +527,7 @@ describe('TypeScript', () => {
510527
}),
511528
test({
512529
code: `
513-
export function Foo();
530+
export function Foo() { };
514531
export class Foo { }
515532
export namespace Foo { }
516533
`,
@@ -529,7 +546,7 @@ describe('TypeScript', () => {
529546
test({
530547
code: `
531548
export const Foo = 'bar';
532-
export function Foo();
549+
export function Foo() { };
533550
export namespace Foo { }
534551
`,
535552
errors: [

‎test/rules/no-cycle.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ const error = (message: string) => ({ message })
1111

1212
const test = <T extends ValidTestCase>(def: T) =>
1313
_test({
14-
...def,
1514
filename: testFilePath('./cycles/depth-zero.js'),
15+
...def,
1616
})
1717

1818
ruleTester.run('no-cycle', rule, {

‎test/rules/no-default-export.spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ const ruleTester = new TSESLintRuleTester()
88

99
ruleTester.run('no-default-export', rule, {
1010
valid: [
11+
test({
12+
code: 'module.exports = function foo() {}',
13+
languageOptions: {
14+
parserOptions: {
15+
sourceType: 'script',
16+
},
17+
},
18+
}),
19+
test({
20+
code: 'module.exports = function foo() {}',
21+
}),
1122
test({
1223
code: `
1324
export const foo = 'foo';

‎test/rules/no-named-export.spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ const ruleTester = new TSESLintRuleTester()
88

99
ruleTester.run('no-named-export', rule, {
1010
valid: [
11+
test({
12+
code: 'module.export.foo = function () {}',
13+
}),
14+
test({
15+
code: 'module.exports = function foo() {}',
16+
languageOptions: {
17+
parserOptions: {
18+
sourceType: 'script',
19+
},
20+
},
21+
}),
1122
test({
1223
code: 'export default function bar() {};',
1324
}),

‎test/utils/export-map.spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ describe('ExportMap', () => {
3030
)
3131
})
3232

33+
it('does not return a cached copy if the parse does not yield a visitor keys', function () {
34+
const mockContext = {
35+
...fakeContext,
36+
parserPath: 'not-real',
37+
}
38+
expect(ExportMap.get('./named-exports', mockContext)).toBeDefined()
39+
expect(ExportMap.get('./named-exports', mockContext)).not.toBe(
40+
ExportMap.get('./named-exports', mockContext),
41+
)
42+
})
43+
3344
it('does not return a cached copy after modification', done => {
3445
const firstAccess = ExportMap.get('./mutator', fakeContext)
3546
expect(firstAccess).toBeDefined()

0 commit comments

Comments
 (0)
Please sign in to comment.