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 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';
@@ -1,4 +1,4 @@
1
- import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
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
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.d.mts","sourceRoot":"","sources":["../src/bindings.mts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EACb,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;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,eAChB,QAAQ,CAAC,UAAU,QACzB,QAAQ,CAAC,IAAI,QACb,MAAM,cACA,MAAM,KACjB,OAIF,CAAC"}
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 = ASTUtils.findVariable(sourceCode.getScope(node), name);
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) &&
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.mjs","sourceRoot":"","sources":["../src/bindings.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,QAAQ,GAGT,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;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,UAA+B,EAC/B,IAAmB,EACnB,IAAY,EACZ,UAAkB,EACT,EAAE;IACX,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,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;;;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"}
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.0",
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.3.0",
45
- "eslint-plugin-ts-fortress": "1.3.12",
46
- "eslint-plugin-ts-type-forge": "0.8.17",
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.10.1",
51
- "ts-repo-utils": "10.7.1"
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
- type TSESLint,
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 = ASTUtils.findVariable(sourceCode.getScope(node), name);
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.