eslint-rule-forge 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/dist/bindings.d.mts +8 -2
- package/dist/bindings.d.mts.map +1 -1
- package/dist/bindings.mjs +33 -2
- package/dist/bindings.mjs.map +1 -1
- package/package.json +6 -6
- package/src/bindings.mts +43 -3
package/README.md
CHANGED
|
@@ -129,20 +129,20 @@ The range and the text of a node leave out the parentheses written around it.
|
|
|
129
129
|
|
|
130
130
|
## Imports and bindings
|
|
131
131
|
|
|
132
|
-
| Helper | What it gives
|
|
133
|
-
| :---------------------- |
|
|
134
|
-
| `getImportDeclarations` | every `import … from '<module>'` in the file, not only the first
|
|
135
|
-
| `getNamedImports` | the export names those declarations import
|
|
136
|
-
| `getImportedLocalName` | the local name an export is imported as (`'y'` for `import { x as y }`)
|
|
137
|
-
| `importedNameOf` | the export name an import specifier imports (`'x'` for `import { x as y }`)
|
|
138
|
-
| `buildCalleeResolver` | which export a callee calls, through aliases, namespace imports and type wrappers
|
|
139
|
-
| `bindValueImports` | the local names to write for exports, and the edits binding them as values (in the existing import)
|
|
140
|
-
| `buildNamedImportFix` | a fix inserting a new `import { … } from '<module>';` after the directive prologue
|
|
141
|
-
| `addTypeImports` | the edits importing types: into an `import type { … }`, else as `type X` into another import
|
|
142
|
-
| `addToImportBraces` | a fix adding specifiers to a declaration's braces, empty ones included, or `undefined` without any
|
|
143
|
-
| `hasForeignBinding` | whether a name, at a node, is bound to something other than the module's import (a fix must give up) |
|
|
144
|
-
| `isLocallyBound` | whether a name, at a node, is declared in the file rather than a global or a standard-library type
|
|
145
|
-
| `hasCommentWithin` | whether a comment inside a node lies in ranges a fix would delete (the fix must give up)
|
|
132
|
+
| Helper | What it gives |
|
|
133
|
+
| :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
134
|
+
| `getImportDeclarations` | every `import … from '<module>'` in the file, not only the first |
|
|
135
|
+
| `getNamedImports` | the export names those declarations import |
|
|
136
|
+
| `getImportedLocalName` | the local name an export is imported as (`'y'` for `import { x as y }`) |
|
|
137
|
+
| `importedNameOf` | the export name an import specifier imports (`'x'` for `import { x as y }`) |
|
|
138
|
+
| `buildCalleeResolver` | which export a callee calls, through aliases, namespace imports and type wrappers |
|
|
139
|
+
| `bindValueImports` | the local names to write for exports, and the edits binding them as values (in the existing import) |
|
|
140
|
+
| `buildNamedImportFix` | a fix inserting a new `import { … } from '<module>';` after the directive prologue |
|
|
141
|
+
| `addTypeImports` | the edits importing types: into an `import type { … }`, else as `type X` into another import |
|
|
142
|
+
| `addToImportBraces` | a fix adding specifiers to a declaration's braces, empty ones included, or `undefined` without any |
|
|
143
|
+
| `hasForeignBinding` | whether a name, at a node, is bound to something other than the module's import (a fix must give up); a nested type-only binding does not count |
|
|
144
|
+
| `isLocallyBound` | whether a name, at a node, is declared in the file rather than a global or a standard-library type |
|
|
145
|
+
| `hasCommentWithin` | whether a comment inside a node lies in ranges a fix would delete (the fix must give up) |
|
|
146
146
|
|
|
147
147
|
```ts
|
|
148
148
|
import type { TSESLint } from '@typescript-eslint/utils';
|
package/dist/bindings.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TSESLint, type TSESTree } from '@typescript-eslint/utils';
|
|
2
2
|
/**
|
|
3
3
|
* Whether `name`, read at `node`, resolves to something this file writes down —
|
|
4
4
|
* a declaration, a parameter, an import — rather than to a global or a
|
|
@@ -16,7 +16,13 @@ export declare const isLocallyBound: (sourceCode: TSESLint.SourceCode, node: TSE
|
|
|
16
16
|
* has to give up then, because the name would refer to that binding instead.
|
|
17
17
|
*
|
|
18
18
|
* The innermost binding decides: an import from `moduleName` at the top of the
|
|
19
|
-
* file does not help a name a function shadows around `node`.
|
|
19
|
+
* file does not help a name a function shadows around `node`. A binding with no
|
|
20
|
+
* value meaning inside the file — a type parameter, a local type alias or
|
|
21
|
+
* interface — is passed over, since a type does not shadow a value or the head
|
|
22
|
+
* of a qualified name. At the top of the file it still counts: the import a fix
|
|
23
|
+
* adds there conflicts with it when the export has a type meaning too. A fix
|
|
24
|
+
* that writes the name alone in a type position, where such a binding does
|
|
25
|
+
* capture it, has to check for one itself.
|
|
20
26
|
*/
|
|
21
27
|
export declare const hasForeignBinding: (sourceCode: TSESLint.SourceCode, node: TSESTree.Node, name: string, moduleName: string) => boolean;
|
|
22
28
|
//# sourceMappingURL=bindings.d.mts.map
|
package/dist/bindings.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.d.mts","sourceRoot":"","sources":["../src/bindings.mts"],"names":[],"mappings":"AAAA,OAAO,EAGL,
|
|
1
|
+
{"version":3,"file":"bindings.d.mts","sourceRoot":"","sources":["../src/bindings.mts"],"names":[],"mappings":"AAAA,OAAO,EAGL,QAAQ,EACR,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAIlC;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,eACb,QAAQ,CAAC,UAAU,QACzB,QAAQ,CAAC,IAAI,QACb,MAAM,KACX,OAIF,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,eAChB,QAAQ,CAAC,UAAU,QACzB,QAAQ,CAAC,IAAI,QACb,MAAM,cACA,MAAM,KACjB,OAIF,CAAC"}
|
package/dist/bindings.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AST_NODE_TYPES, ASTUtils, } from '@typescript-eslint/utils';
|
|
1
|
+
import { AST_NODE_TYPES, ASTUtils, TSESLint, } from '@typescript-eslint/utils';
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
@@ -22,14 +22,45 @@ export const isLocallyBound = (sourceCode, node, name) => {
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
25
31
|
export const hasForeignBinding = (sourceCode, node, name, moduleName) => {
|
|
26
|
-
const variable =
|
|
32
|
+
const variable = findShadowingVariable(sourceCode.getScope(node), name);
|
|
27
33
|
return variable?.defs.some((def) => !isImportFrom(def, moduleName)) ?? false;
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
|
|
31
37
|
|
|
32
38
|
|
|
39
|
+
|
|
40
|
+
const isTypeOnly = (variable) => variable.defs.length > 0 &&
|
|
41
|
+
variable.defs.every((def) => def.type === TSESLint.Scope.DefinitionType.Type);
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
const isTopLevel = (scope) => scope.block.type === AST_NODE_TYPES.Program;
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
const findShadowingVariable = (scope, name) => {
|
|
53
|
+
const variable = scope.set.get(name);
|
|
54
|
+
return variable !== undefined && (!isTypeOnly(variable) || isTopLevel(scope))
|
|
55
|
+
? variable
|
|
56
|
+
: scope.upper === null
|
|
57
|
+
? undefined
|
|
58
|
+
: findShadowingVariable(scope.upper, name);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
33
64
|
const isImportFrom = (def, moduleName) => (def.node.type === AST_NODE_TYPES.ImportSpecifier ||
|
|
34
65
|
def.node.type === AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
35
66
|
def.node.type === AST_NODE_TYPES.ImportNamespaceSpecifier) &&
|
package/dist/bindings.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.mjs","sourceRoot":"","sources":["../src/bindings.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,QAAQ,
|
|
1
|
+
{"version":3,"file":"bindings.mjs","sourceRoot":"","sources":["../src/bindings.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,QAAQ,EACR,QAAQ,GAET,MAAM,0BAA0B,CAAC;AAElC,uEAAuE;AAEvE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,UAA+B,EAC/B,IAAmB,EACnB,IAAY,EACH,EAAE;IACX,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAExE,OAAO,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;AACvD,CAAC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,UAA+B,EAC/B,IAAmB,EACnB,IAAY,EACZ,UAAkB,EACT,EAAE;IACX,MAAM,QAAQ,GAAG,qBAAqB,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAExE,OAAO,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,IAAI,KAAK,CAAC;AAC/E,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC,QAAiC,EAAW,EAAE,CAChE,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;AAEhF;;;GAGG;AACH,MAAM,UAAU,GAAG,CAAC,KAA2B,EAAW,EAAE,CAC1D,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,OAAO,CAAC;AAE9C;;;;GAIG;AACH,MAAM,qBAAqB,GAAG,CAC5B,KAA2B,EAC3B,IAAY,EACyB,EAAE;IACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAErC,OAAO,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;QAC3E,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI;YACpB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,qBAAqB,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,YAAY,GAAG,CACnB,GAA8B,EAC9B,UAAkB,EACT,EAAE,CACX,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,eAAe;IAC/C,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,sBAAsB;IACvD,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,wBAAwB,CAAC;IAC5D,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,cAAc,CAAC,iBAAiB;IACzD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,KAAK,UAAU,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-rule-forge",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Helpers for writing typescript-eslint rules that see through type wrappers and parentheses, and resolve what a file imports.",
|
|
6
6
|
"keywords": [
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"@typescript-eslint/rule-tester": "8.72.0",
|
|
42
42
|
"dedent": "1.7.2",
|
|
43
43
|
"eslint": "10.13.0",
|
|
44
|
-
"eslint-config-typed": "7.
|
|
45
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
46
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
44
|
+
"eslint-config-typed": "7.4.0",
|
|
45
|
+
"eslint-plugin-ts-fortress": "1.3.13",
|
|
46
|
+
"eslint-plugin-ts-type-forge": "0.8.18",
|
|
47
47
|
"prettier": "3.9.10",
|
|
48
48
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
49
49
|
"prettier-plugin-packagejson": "3.0.2",
|
|
50
|
-
"ts-data-forge": "14.
|
|
51
|
-
"ts-repo-utils": "10.7.
|
|
50
|
+
"ts-data-forge": "14.11.0",
|
|
51
|
+
"ts-repo-utils": "10.7.2"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"eslint": ">=9.0.0",
|
package/src/bindings.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AST_NODE_TYPES,
|
|
3
3
|
ASTUtils,
|
|
4
|
-
|
|
4
|
+
TSESLint,
|
|
5
5
|
type TSESTree,
|
|
6
6
|
} from '@typescript-eslint/utils';
|
|
7
7
|
|
|
@@ -33,7 +33,13 @@ export const isLocallyBound = (
|
|
|
33
33
|
* has to give up then, because the name would refer to that binding instead.
|
|
34
34
|
*
|
|
35
35
|
* The innermost binding decides: an import from `moduleName` at the top of the
|
|
36
|
-
* file does not help a name a function shadows around `node`.
|
|
36
|
+
* file does not help a name a function shadows around `node`. A binding with no
|
|
37
|
+
* value meaning inside the file — a type parameter, a local type alias or
|
|
38
|
+
* interface — is passed over, since a type does not shadow a value or the head
|
|
39
|
+
* of a qualified name. At the top of the file it still counts: the import a fix
|
|
40
|
+
* adds there conflicts with it when the export has a type meaning too. A fix
|
|
41
|
+
* that writes the name alone in a type position, where such a binding does
|
|
42
|
+
* capture it, has to check for one itself.
|
|
37
43
|
*/
|
|
38
44
|
export const hasForeignBinding = (
|
|
39
45
|
sourceCode: TSESLint.SourceCode,
|
|
@@ -41,11 +47,45 @@ export const hasForeignBinding = (
|
|
|
41
47
|
name: string,
|
|
42
48
|
moduleName: string,
|
|
43
49
|
): boolean => {
|
|
44
|
-
const variable =
|
|
50
|
+
const variable = findShadowingVariable(sourceCode.getScope(node), name);
|
|
45
51
|
|
|
46
52
|
return variable?.defs.some((def) => !isImportFrom(def, moduleName)) ?? false;
|
|
47
53
|
};
|
|
48
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Whether every definition of `variable` is a type parameter, type alias or
|
|
57
|
+
* interface. Read from `def.type` rather than `isValueVariable`, which ESLint's
|
|
58
|
+
* own scope manager, used with its default parser, does not have.
|
|
59
|
+
*/
|
|
60
|
+
const isTypeOnly = (variable: TSESLint.Scope.Variable): boolean =>
|
|
61
|
+
variable.defs.length > 0 &&
|
|
62
|
+
variable.defs.every((def) => def.type === TSESLint.Scope.DefinitionType.Type);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Whether `scope` is the file's own, where an added import lands: the module or
|
|
66
|
+
* global scope, or the function scope a CommonJS file wraps around them.
|
|
67
|
+
*/
|
|
68
|
+
const isTopLevel = (scope: TSESLint.Scope.Scope): boolean =>
|
|
69
|
+
scope.block.type === AST_NODE_TYPES.Program;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The innermost variable named `name` from `scope` outwards that a value of
|
|
73
|
+
* that name written in `scope` would meet: one with a value meaning, or any at
|
|
74
|
+
* the top of the file.
|
|
75
|
+
*/
|
|
76
|
+
const findShadowingVariable = (
|
|
77
|
+
scope: TSESLint.Scope.Scope,
|
|
78
|
+
name: string,
|
|
79
|
+
): TSESLint.Scope.Variable | undefined => {
|
|
80
|
+
const variable = scope.set.get(name);
|
|
81
|
+
|
|
82
|
+
return variable !== undefined && (!isTypeOnly(variable) || isTopLevel(scope))
|
|
83
|
+
? variable
|
|
84
|
+
: scope.upper === null
|
|
85
|
+
? undefined
|
|
86
|
+
: findShadowingVariable(scope.upper, name);
|
|
87
|
+
};
|
|
88
|
+
|
|
49
89
|
/**
|
|
50
90
|
* Whether `def` is a binding an `import … from '<moduleName>'` declaration
|
|
51
91
|
* introduces.
|