eslint-plugin-ts-fortress 0.2.0 → 0.4.0
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 +101 -24
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/plugin.d.mts +20 -1
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +23 -1
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +3 -3
- package/dist/rule-types.d.mts.map +1 -1
- package/dist/rules/constants.d.mts +13 -0
- package/dist/rules/constants.d.mts.map +1 -1
- package/dist/rules/constants.mjs +7 -1
- package/dist/rules/constants.mjs.map +1 -1
- package/dist/rules/import-utils.d.mts +15 -8
- package/dist/rules/import-utils.d.mts.map +1 -1
- package/dist/rules/import-utils.mjs +13 -24
- package/dist/rules/import-utils.mjs.map +1 -1
- package/dist/rules/prefer-canonical-length-constrained-type.d.mts +6 -0
- package/dist/rules/prefer-canonical-length-constrained-type.d.mts.map +1 -0
- package/dist/rules/prefer-canonical-length-constrained-type.mjs +169 -0
- package/dist/rules/prefer-canonical-length-constrained-type.mjs.map +1 -0
- package/dist/rules/rules.d.mts +1 -1
- package/dist/rules/rules.d.mts.map +1 -1
- package/dist/rules/rules.mjs +2 -2
- package/dist/rules/rules.mjs.map +1 -1
- package/dist/types.d.mts +1 -0
- package/dist/types.d.mts.map +1 -1
- package/package.json +1 -1
- package/src/index.mts +1 -1
- package/src/plugin.mts +35 -3
- package/src/plugin.test.mts +24 -0
- package/src/rule-types.mts +3 -3
- package/src/rules/constants.mts +19 -0
- package/src/rules/import-utils.mts +50 -62
- package/src/rules/prefer-canonical-length-constrained-type.mts +360 -0
- package/src/rules/prefer-canonical-length-constrained-type.test.mts +435 -0
- package/src/rules/rules.mts +3 -2
- package/src/types.mts +2 -0
- package/dist/rules/prefer-non-empty-array.d.mts +0 -6
- package/dist/rules/prefer-non-empty-array.d.mts.map +0 -1
- package/dist/rules/prefer-non-empty-array.mjs +0 -100
- package/dist/rules/prefer-non-empty-array.mjs.map +0 -1
- package/src/rules/prefer-non-empty-array.mts +0 -190
- package/src/rules/prefer-non-empty-array.test.mts +0 -222
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
ESLint rules that steer schema definitions toward
|
|
4
4
|
[`ts-fortress`](https://www.npmjs.com/package/ts-fortress) idioms. Every rule is
|
|
5
|
-
auto-fixable
|
|
5
|
+
auto-fixable, and every rewrite is **type-preserving**.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -15,6 +15,39 @@ configured TypeScript project is not required.
|
|
|
15
15
|
|
|
16
16
|
## Usage (flat config)
|
|
17
17
|
|
|
18
|
+
The plugin ships a `recommended` config preset that registers the plugin and
|
|
19
|
+
turns on **every** rule at `error`:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// eslint.config.mts
|
|
23
|
+
import { eslintPluginTsFortress } from 'eslint-plugin-ts-fortress';
|
|
24
|
+
|
|
25
|
+
export default [eslintPluginTsFortress.configs.recommended];
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Since the preset is a plain flat-config object, individual rules can be
|
|
29
|
+
adjusted by a later config entry:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
// eslint.config.mts
|
|
33
|
+
import {
|
|
34
|
+
eslintPluginTsFortress,
|
|
35
|
+
type EslintTsFortressRules,
|
|
36
|
+
} from 'eslint-plugin-ts-fortress';
|
|
37
|
+
|
|
38
|
+
export default [
|
|
39
|
+
eslintPluginTsFortress.configs.recommended,
|
|
40
|
+
{
|
|
41
|
+
files: ['src/legacy/**'],
|
|
42
|
+
rules: {
|
|
43
|
+
'ts-fortress/prefer-canonical-length-constrained-type': 'off',
|
|
44
|
+
} satisfies Partial<EslintTsFortressRules>,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or register the plugin yourself and pick the rules one by one:
|
|
50
|
+
|
|
18
51
|
```ts
|
|
19
52
|
// eslint.config.mts
|
|
20
53
|
import {
|
|
@@ -26,7 +59,7 @@ export default [
|
|
|
26
59
|
{
|
|
27
60
|
plugins: { 'ts-fortress': eslintPluginTsFortress },
|
|
28
61
|
rules: {
|
|
29
|
-
'ts-fortress/prefer-
|
|
62
|
+
'ts-fortress/prefer-canonical-length-constrained-type': 'error',
|
|
30
63
|
} satisfies Partial<EslintTsFortressRules>,
|
|
31
64
|
},
|
|
32
65
|
];
|
|
@@ -34,43 +67,87 @@ export default [
|
|
|
34
67
|
|
|
35
68
|
## Rules
|
|
36
69
|
|
|
37
|
-
| Rule
|
|
38
|
-
|
|
|
39
|
-
| `prefer-
|
|
70
|
+
| Rule | Description |
|
|
71
|
+
| :----------------------------------------- | :-------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `prefer-canonical-length-constrained-type` | Normalize a length-constrained array combinator with degenerate bounds to its canonical form. |
|
|
73
|
+
|
|
74
|
+
### `prefer-canonical-length-constrained-type`
|
|
40
75
|
|
|
41
|
-
|
|
76
|
+
Several of ts-fortress's array combinators build the very same type once their
|
|
77
|
+
length arguments hit a degenerate value. The rule rewrites each of those to the
|
|
78
|
+
combinator that names the constraint directly:
|
|
42
79
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
`
|
|
80
|
+
| ❌ written as | ✅ canonical form | why they are the same type |
|
|
81
|
+
| :---------------------------- | :----------------------- | :----------------------------------------------------------------- |
|
|
82
|
+
| `minLengthArray(1, x)` | `nonEmptyArray(x)` | `NonEmptyArray<A>` is defined as `MinLengthArray<1, A>` |
|
|
83
|
+
| `minLengthTuple(0, x)` | `array(x)` | `MinLengthTuple<0, A>` is `readonly A[]` — no constraint at all |
|
|
84
|
+
| `maxLengthTuple(0, x)` | `fixedLengthTuple(0, x)` | both are `readonly []` |
|
|
85
|
+
| `boundedLengthTuple(n, n, x)` | `fixedLengthTuple(n, x)` | the length union collapses to its single member |
|
|
86
|
+
| `boundedLengthTuple(0, n, x)` | `maxLengthTuple(n, x)` | `MaxLengthTuple<N, A>` is defined as `BoundedLengthTuple<0, N, A>` |
|
|
48
87
|
|
|
49
88
|
```ts
|
|
50
89
|
import * as t from 'ts-fortress';
|
|
51
90
|
|
|
52
91
|
// ❌
|
|
53
92
|
const Tags = t.minLengthArray(1, t.string());
|
|
93
|
+
const Rgb = t.boundedLengthTuple(3, 3, t.number());
|
|
94
|
+
const Page = t.boundedLengthTuple(0, 20, t.string());
|
|
54
95
|
|
|
55
96
|
// ✅
|
|
56
97
|
const Tags = t.nonEmptyArray(t.string());
|
|
98
|
+
const Rgb = t.fixedLengthTuple(3, t.number());
|
|
99
|
+
const Page = t.maxLengthTuple(20, t.string());
|
|
57
100
|
```
|
|
58
101
|
|
|
102
|
+
Each rewrite keeps the accepted values, the `defaultValue`, and the options
|
|
103
|
+
object exactly as they were. The only observable change is the default
|
|
104
|
+
`typeName` — and the `details.kind` of the length error derived from it — which
|
|
105
|
+
becomes the one that names the constraint actually being checked.
|
|
106
|
+
|
|
107
|
+
#### Why the branded `*Array` family is left alone
|
|
108
|
+
|
|
109
|
+
`boundedLengthArray(0, n, x)` and `boundedLengthArray(n, n, x)` look like the
|
|
110
|
+
same degenerate cases, but their types are branded rather than structural, and
|
|
111
|
+
the analogous rewrites would silently change them:
|
|
112
|
+
|
|
113
|
+
- `BoundedLengthArray<Min, Max, A>` is
|
|
114
|
+
`MaxLengthArray<Max, A> & MinLengthArray<Min, A>`, so rewriting
|
|
115
|
+
`boundedLengthArray(0, n, x)` to `maxLengthArray(n, x)` **drops** the
|
|
116
|
+
`MinLengthArray<0, A>` brand — a widening.
|
|
117
|
+
- `FixedLengthArray<N, A>` additionally intersects the exact tuple
|
|
118
|
+
`FixedLengthTuple<N, A>` for `N <= 10`, so rewriting
|
|
119
|
+
`boundedLengthArray(n, n, x)` to `fixedLengthArray(n, x)` **adds** a
|
|
120
|
+
constraint — a narrowing.
|
|
121
|
+
|
|
122
|
+
Neither is a pure rename, so the rule does not report them.
|
|
123
|
+
|
|
124
|
+
#### Bounds above the structural cap
|
|
125
|
+
|
|
126
|
+
The `*Tuple` combinators only encode lengths up to `10` (ts-type-forge's
|
|
127
|
+
`StructuralPrefixLength`); past that they fall back to an overload that drops
|
|
128
|
+
the constraint from the result type. The rule therefore only fires when the
|
|
129
|
+
bound it carries over to the other combinator is a literal within `0..10`.
|
|
130
|
+
|
|
131
|
+
#### Imports
|
|
132
|
+
|
|
59
133
|
Both the namespace style (`import * as t from 'ts-fortress'`) and named imports
|
|
60
134
|
— including aliases — are recognized, and the autofix reuses whatever binding
|
|
61
|
-
the file already has, adding `import {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
The rule deliberately leaves alone
|
|
65
|
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
- calls passing a `defaultValue
|
|
70
|
-
`
|
|
71
|
-
`MinLengthArray<1, A>` — a blind
|
|
72
|
-
`typeName`-only options object is safe
|
|
73
|
-
|
|
135
|
+
the file already has, adding `import { … } from 'ts-fortress';` only when a
|
|
136
|
+
named call needs one.
|
|
137
|
+
|
|
138
|
+
#### The rule deliberately leaves alone
|
|
139
|
+
|
|
140
|
+
- non-degenerate bounds (`minLengthArray(2, …)`, `boundedLengthTuple(1, 3, …)`);
|
|
141
|
+
- computed bounds and calls with explicit type arguments, since the literal is
|
|
142
|
+
what makes the two forms equivalent;
|
|
143
|
+
- `minLengthArray(1, …)` calls passing a `defaultValue`, because
|
|
144
|
+
`nonEmptyArray` types that option as `NonEmptyTuple<A>` while
|
|
145
|
+
`minLengthArray` types it as the branded `MinLengthArray<1, A>` — a blind
|
|
146
|
+
rewrite could stop type-checking. A `typeName`-only options object is safe
|
|
147
|
+
and is rewritten. (The other rewrites type `defaultValue` identically, so
|
|
148
|
+
they keep it.)
|
|
149
|
+
- calls where the target name is already bound to something else at the call
|
|
150
|
+
site.
|
|
74
151
|
|
|
75
152
|
## License
|
|
76
153
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { eslintPluginTsFortress } from './plugin.mjs';
|
|
2
2
|
export type { EslintTsFortressRules, EslintTsFortressRulesOption, } from './rule-types.mjs';
|
|
3
3
|
export { tsFortressRules } from './rules/index.mjs';
|
|
4
|
-
export { type ESLintPlugin } from './types.mjs';
|
|
4
|
+
export { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';
|
|
5
5
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,YAAY,EACV,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,YAAY,EACV,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/plugin.d.mts
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
1
|
import { type ESLintPlugin } from './types.mjs';
|
|
2
|
-
export declare const eslintPluginTsFortress:
|
|
2
|
+
export declare const eslintPluginTsFortress: {
|
|
3
|
+
readonly meta: {
|
|
4
|
+
readonly name: 'eslint-plugin-ts-fortress';
|
|
5
|
+
};
|
|
6
|
+
readonly rules: {
|
|
7
|
+
readonly 'prefer-canonical-length-constrained-type': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useCanonicalType", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
8
|
+
};
|
|
9
|
+
readonly configs: {
|
|
10
|
+
/** Enables every rule of this plugin at `error`. */
|
|
11
|
+
readonly recommended: {
|
|
12
|
+
readonly name: 'ts-fortress/recommended';
|
|
13
|
+
readonly plugins: {
|
|
14
|
+
readonly 'ts-fortress': ESLintPlugin;
|
|
15
|
+
};
|
|
16
|
+
readonly rules: {
|
|
17
|
+
readonly 'ts-fortress/prefer-canonical-length-constrained-type': 'error';
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
};
|
|
3
22
|
//# sourceMappingURL=plugin.d.mts.map
|
package/dist/plugin.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.mts","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"plugin.d.mts","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AA8BvE,eAAO,MAAM,sBAAsB;;iBAE/B,IAAI,EAAE,2BAA2B;;;;;;QAIjC,oDAAoD;iBACpD,WAAW;2BAtBP,yBAAyB;;yBAQzB,aAAa,EAAI,YAAY;;;iFAdqB,OAAO;;;;CA8BhC,CAAC"}
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
import { tsFortressRules } from './rules/rules.mjs';
|
|
2
2
|
|
|
3
|
+
const recommendedRules = {
|
|
4
|
+
"ts-fortress/prefer-canonical-length-constrained-type": "error"
|
|
5
|
+
};
|
|
6
|
+
const recommendedConfig = {
|
|
7
|
+
name: "ts-fortress/recommended",
|
|
8
|
+
plugins: {
|
|
9
|
+
// Resolved lazily so that this config registers the *same* object that
|
|
10
|
+
// `eslintPluginTsFortress` refers to. ESLint rejects a plugin name that
|
|
11
|
+
// maps to two different objects with `Cannot redefine plugin`, which is
|
|
12
|
+
// what a user combining this preset with their own
|
|
13
|
+
// `plugins: { 'ts-fortress': eslintPluginTsFortress }` entry would hit if
|
|
14
|
+
// the preset embedded a separate copy of the plugin.
|
|
15
|
+
get "ts-fortress"() {
|
|
16
|
+
return eslintPluginTsFortress;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
rules: recommendedRules
|
|
20
|
+
};
|
|
3
21
|
const eslintPluginTsFortress = {
|
|
4
22
|
meta: {
|
|
5
23
|
name: "eslint-plugin-ts-fortress"
|
|
6
24
|
},
|
|
7
|
-
rules: tsFortressRules
|
|
25
|
+
rules: tsFortressRules,
|
|
26
|
+
configs: {
|
|
27
|
+
/** Enables every rule of this plugin at `error`. */
|
|
28
|
+
recommended: recommendedConfig
|
|
29
|
+
}
|
|
8
30
|
};
|
|
9
31
|
|
|
10
32
|
export { eslintPluginTsFortress };
|
package/dist/plugin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","sources":["../src/plugin.mts"],"sourcesContent":["import { tsFortressRules } from './rules/index.mjs';\nimport { type ESLintPlugin } from './types.mjs';\n\
|
|
1
|
+
{"version":3,"file":"plugin.mjs","sources":["../src/plugin.mts"],"sourcesContent":["import { tsFortressRules } from './rules/index.mjs';\nimport { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';\n\n/**\n * Every rule this plugin ships, at `error`.\n *\n * The `satisfies` clause below is keyed off {@link tsFortressRules}, so adding\n * a rule without listing it here fails to type-check.\n */\nconst recommendedRules = {\n 'ts-fortress/prefer-canonical-length-constrained-type': 'error',\n} as const satisfies Readonly<\n Record<`ts-fortress/${keyof typeof tsFortressRules}`, 'error'>\n>;\n\nconst recommendedConfig = {\n name: 'ts-fortress/recommended',\n plugins: {\n // Resolved lazily so that this config registers the *same* object that\n // `eslintPluginTsFortress` refers to. ESLint rejects a plugin name that\n // maps to two different objects with `Cannot redefine plugin`, which is\n // what a user combining this preset with their own\n // `plugins: { 'ts-fortress': eslintPluginTsFortress }` entry would hit if\n // the preset embedded a separate copy of the plugin.\n get 'ts-fortress'(): ESLintPlugin {\n return eslintPluginTsFortress;\n },\n },\n rules: recommendedRules,\n} as const satisfies ESLintFlatConfig;\n\nexport const eslintPluginTsFortress = {\n meta: {\n name: 'eslint-plugin-ts-fortress',\n },\n rules: tsFortressRules,\n configs: {\n /** Enables every rule of this plugin at `error`. */\n recommended: recommendedConfig,\n },\n} as const satisfies ESLintPlugin;\n"],"names":[],"mappings":";;AASA,MAAM,gBAAA,GAAmB;AAAA,EACvB,sDAAA,EAAwD;AAC1D,CAAA;AAIA,MAAM,iBAAA,GAAoB;AAAA,EACxB,IAAA,EAAM,yBAAA;AAAA,EACN,OAAA,EAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOP,IAAI,aAAA,GAA8B;AAChC,MAAA,OAAO,sBAAA;AAAA,IACT;AAAA,GACF;AAAA,EACA,KAAA,EAAO;AACT,CAAA;AAEO,MAAM,sBAAA,GAAyB;AAAA,EACpC,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM;AAAA,GACR;AAAA,EACA,KAAA,EAAO,eAAA;AAAA,EACP,OAAA,EAAS;AAAA;AAAA,IAEP,WAAA,EAAa;AAAA;AAEjB;;;;"}
|
package/dist/rule-types.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import { type Linter } from 'eslint';
|
|
|
2
2
|
import { type TypeEq } from 'ts-type-forge';
|
|
3
3
|
import { type tsFortressRules } from './rules/index.mjs';
|
|
4
4
|
/**
|
|
5
|
-
* @description
|
|
5
|
+
* @description Normalize a length-constrained ts-fortress array combinator whose bounds are degenerate (e.g. `minLengthArray(1, x)`, `boundedLengthTuple(n, n, x)`) to the combinator that names that constraint directly.
|
|
6
6
|
*
|
|
7
7
|
* ```md
|
|
8
8
|
* | key | value |
|
|
@@ -12,11 +12,11 @@ import { type tsFortressRules } from './rules/index.mjs';
|
|
|
12
12
|
* | fixable | code |
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
|
-
declare namespace
|
|
15
|
+
declare namespace PreferCanonicalLengthConstrainedType {
|
|
16
16
|
type RuleEntry = Linter.StringSeverity;
|
|
17
17
|
}
|
|
18
18
|
export type EslintTsFortressRules = Readonly<{
|
|
19
|
-
'ts-fortress/prefer-
|
|
19
|
+
'ts-fortress/prefer-canonical-length-constrained-type': PreferCanonicalLengthConstrainedType.RuleEntry;
|
|
20
20
|
}>;
|
|
21
21
|
export type EslintTsFortressRulesOption = Readonly<Record<never, never>>;
|
|
22
22
|
type AssertTrue<B extends true> = B;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-types.d.mts","sourceRoot":"","sources":["../src/rule-types.mts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,kBAAU,
|
|
1
|
+
{"version":3,"file":"rule-types.d.mts","sourceRoot":"","sources":["../src/rule-types.mts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,kBAAU,oCAAoC,CAAC;IAC7C,KAAY,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC;CAC/C;AAED,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,sDAAsD,EAAE,oCAAoC,CAAC,SAAS,CAAC;CACxG,CAAC,CAAC;AAEH,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAIzE,KAAK,UAAU,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG,UAAU,CACzC,MAAM,CACJ,MAAM,qBAAqB,EAC3B,eAAe,MAAM,OAAO,eAAe,EAAE,CAC9C,CACF,CAAC"}
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export declare const TS_FORTRESS_MODULE = "ts-fortress";
|
|
2
|
+
export declare const ARRAY_FN = "array";
|
|
3
|
+
export declare const BOUNDED_LENGTH_TUPLE_FN = "boundedLengthTuple";
|
|
4
|
+
export declare const FIXED_LENGTH_TUPLE_FN = "fixedLengthTuple";
|
|
5
|
+
export declare const MAX_LENGTH_TUPLE_FN = "maxLengthTuple";
|
|
2
6
|
export declare const MIN_LENGTH_ARRAY_FN = "minLengthArray";
|
|
7
|
+
export declare const MIN_LENGTH_TUPLE_FN = "minLengthTuple";
|
|
3
8
|
export declare const NON_EMPTY_ARRAY_FN = "nonEmptyArray";
|
|
9
|
+
/**
|
|
10
|
+
* Inclusive upper bound of ts-type-forge's `StructuralPrefixLength` (`0..10`) —
|
|
11
|
+
* the lengths the structural `*Tuple` combinators encode in their result type.
|
|
12
|
+
* A bound outside this range selects a different overload (one that drops the
|
|
13
|
+
* constraint), so a rewrite that carries a bound over to another combinator
|
|
14
|
+
* only stays type-identical while the bound is within it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const STRUCTURAL_PREFIX_CAP = 10;
|
|
4
17
|
//# sourceMappingURL=constants.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.mts","sourceRoot":"","sources":["../../src/rules/constants.mts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,gBAAgB,CAAC;AAEhD,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.mts","sourceRoot":"","sources":["../../src/rules/constants.mts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,gBAAgB,CAAC;AAEhD,eAAO,MAAM,QAAQ,UAAU,CAAC;AAEhC,eAAO,MAAM,uBAAuB,uBAAuB,CAAC;AAE5D,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC"}
|
package/dist/rules/constants.mjs
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
const TS_FORTRESS_MODULE = "ts-fortress";
|
|
2
|
+
const ARRAY_FN = "array";
|
|
3
|
+
const BOUNDED_LENGTH_TUPLE_FN = "boundedLengthTuple";
|
|
4
|
+
const FIXED_LENGTH_TUPLE_FN = "fixedLengthTuple";
|
|
5
|
+
const MAX_LENGTH_TUPLE_FN = "maxLengthTuple";
|
|
2
6
|
const MIN_LENGTH_ARRAY_FN = "minLengthArray";
|
|
7
|
+
const MIN_LENGTH_TUPLE_FN = "minLengthTuple";
|
|
3
8
|
const NON_EMPTY_ARRAY_FN = "nonEmptyArray";
|
|
9
|
+
const STRUCTURAL_PREFIX_CAP = 10;
|
|
4
10
|
|
|
5
|
-
export { MIN_LENGTH_ARRAY_FN, NON_EMPTY_ARRAY_FN, TS_FORTRESS_MODULE };
|
|
11
|
+
export { ARRAY_FN, BOUNDED_LENGTH_TUPLE_FN, FIXED_LENGTH_TUPLE_FN, MAX_LENGTH_TUPLE_FN, MIN_LENGTH_ARRAY_FN, MIN_LENGTH_TUPLE_FN, NON_EMPTY_ARRAY_FN, STRUCTURAL_PREFIX_CAP, TS_FORTRESS_MODULE };
|
|
6
12
|
//# sourceMappingURL=constants.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.mjs","sources":["../../src/rules/constants.mts"],"sourcesContent":["export const TS_FORTRESS_MODULE = 'ts-fortress';\n\nexport const MIN_LENGTH_ARRAY_FN = 'minLengthArray';\n\nexport const NON_EMPTY_ARRAY_FN = 'nonEmptyArray';\n"],"names":[],"mappings":"AAAO,MAAM,kBAAA,GAAqB;AAE3B,MAAM,mBAAA,GAAsB;AAE5B,MAAM,kBAAA,GAAqB;;;;"}
|
|
1
|
+
{"version":3,"file":"constants.mjs","sources":["../../src/rules/constants.mts"],"sourcesContent":["export const TS_FORTRESS_MODULE = 'ts-fortress';\n\nexport const ARRAY_FN = 'array';\n\nexport const BOUNDED_LENGTH_TUPLE_FN = 'boundedLengthTuple';\n\nexport const FIXED_LENGTH_TUPLE_FN = 'fixedLengthTuple';\n\nexport const MAX_LENGTH_TUPLE_FN = 'maxLengthTuple';\n\nexport const MIN_LENGTH_ARRAY_FN = 'minLengthArray';\n\nexport const MIN_LENGTH_TUPLE_FN = 'minLengthTuple';\n\nexport const NON_EMPTY_ARRAY_FN = 'nonEmptyArray';\n\n/**\n * Inclusive upper bound of ts-type-forge's `StructuralPrefixLength` (`0..10`) —\n * the lengths the structural `*Tuple` combinators encode in their result type.\n * A bound outside this range selects a different overload (one that drops the\n * constraint), so a rewrite that carries a bound over to another combinator\n * only stays type-identical while the bound is within it.\n */\nexport const STRUCTURAL_PREFIX_CAP = 10;\n"],"names":[],"mappings":"AAAO,MAAM,kBAAA,GAAqB;AAE3B,MAAM,QAAA,GAAW;AAEjB,MAAM,uBAAA,GAA0B;AAEhC,MAAM,qBAAA,GAAwB;AAE9B,MAAM,mBAAA,GAAsB;AAE5B,MAAM,mBAAA,GAAsB;AAE5B,MAAM,mBAAA,GAAsB;AAE5B,MAAM,kBAAA,GAAqB;AAS3B,MAAM,qBAAA,GAAwB;;;;"}
|
|
@@ -25,16 +25,23 @@ export declare const getImportedLocalName: (importDeclarations: readonly TSESTre
|
|
|
25
25
|
*/
|
|
26
26
|
export declare const buildCalleeResolver: (importDeclarations: readonly TSESTree.ImportDeclaration[]) => ((callee: TSESTree.Expression) => ResolvedCallee | undefined);
|
|
27
27
|
/**
|
|
28
|
-
* Whether
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* Whether `name` resolves, at `node`, to a binding that is not a ts-fortress
|
|
29
|
+
* import — a local declaration, a parameter, or an import from another module.
|
|
30
|
+
* An autofix that makes a call reference `name` must bail out in that case,
|
|
31
31
|
* because the rewritten call would resolve to the wrong binding.
|
|
32
|
+
*
|
|
33
|
+
* Scope-based rather than a scan of the top-level statements, so a binding that
|
|
34
|
+
* merely shadows the ts-fortress one around the call site is caught too.
|
|
32
35
|
*/
|
|
33
|
-
export declare const
|
|
36
|
+
export declare const hasForeignBinding: (sourceCode: TSESLint.SourceCode, node: TSESTree.Node, name: string) => boolean;
|
|
34
37
|
/**
|
|
35
|
-
* Inserts `import { <name> } from 'ts-fortress';` at the top of
|
|
36
|
-
* Placement is deliberately naive: prettier-plugin-organize-imports
|
|
37
|
-
* equivalent) merges and sorts
|
|
38
|
+
* Inserts `import { <name> } from 'ts-fortress';` for each name at the top of
|
|
39
|
+
* the file. Placement is deliberately naive: prettier-plugin-organize-imports
|
|
40
|
+
* (or any equivalent) merges and sorts them afterwards.
|
|
41
|
+
*
|
|
42
|
+
* All names go into a single fix on purpose: ESLint merges the fixes of one
|
|
43
|
+
* report into one edit span, so two reports each inserting at the start of the
|
|
44
|
+
* file would conflict and one of them would be dropped.
|
|
38
45
|
*/
|
|
39
|
-
export declare const
|
|
46
|
+
export declare const buildImportsFix: (fixer: TSESLint.RuleFixer, program: TSESTree.Program, names: readonly string[]) => readonly TSESLint.RuleFix[];
|
|
40
47
|
//# sourceMappingURL=import-utils.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-utils.d.mts","sourceRoot":"","sources":["../../src/rules/import-utils.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAKlC,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,4EAA4E;IAC5E,aAAa,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC;IAC5B,2EAA2E;IAC3E,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC,CAAC;AAEH,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,YACtB,QAAQ,CAAC,OAAO,KACxB,SAAS,QAAQ,CAAC,iBAAiB,EAKnC,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,uBACX,SAAS,QAAQ,CAAC,iBAAiB,EAAE,gBAC3C,MAAM,KACnB,MAAM,GAAG,SASK,CAAC;AAElB;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,uBACV,SAAS,QAAQ,CAAC,iBAAiB,EAAE,KACxD,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,KAAK,cAAc,GAAG,SAAS,CAkD9D,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"import-utils.d.mts","sourceRoot":"","sources":["../../src/rules/import-utils.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAKlC,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,4EAA4E;IAC5E,aAAa,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC;IAC5B,2EAA2E;IAC3E,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC,CAAC;AAEH,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,YACtB,QAAQ,CAAC,OAAO,KACxB,SAAS,QAAQ,CAAC,iBAAiB,EAKnC,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,uBACX,SAAS,QAAQ,CAAC,iBAAiB,EAAE,gBAC3C,MAAM,KACnB,MAAM,GAAG,SASK,CAAC;AAElB;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,uBACV,SAAS,QAAQ,CAAC,iBAAiB,EAAE,KACxD,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,KAAK,cAAc,GAAG,SAAS,CAkD9D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,eAChB,QAAQ,CAAC,UAAU,QACzB,QAAQ,CAAC,IAAI,QACb,MAAM,KACX,OAgBF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,UACnB,QAAQ,CAAC,SAAS,WAChB,QAAQ,CAAC,OAAO,SAClB,SAAS,MAAM,EAAE,KACvB,SAAS,QAAQ,CAAC,OAAO,EAUX,CAAC"}
|
|
@@ -35,34 +35,23 @@ const buildCalleeResolver = (importDeclarations) => {
|
|
|
35
35
|
} : void 0;
|
|
36
36
|
};
|
|
37
37
|
};
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const hasForeignBinding = (sourceCode, node, name) => {
|
|
39
|
+
for (let mut_scope = sourceCode.getScope(node); mut_scope !== null; mut_scope = mut_scope.upper) {
|
|
40
|
+
const variable = mut_scope.set.get(name);
|
|
41
|
+
if (variable !== void 0) {
|
|
42
|
+
return variable.defs.some((def) => !isTsFortressImportDef(def.node));
|
|
43
|
+
}
|
|
41
44
|
}
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
const buildImportFix = (fixer, program, name) => [
|
|
45
|
+
return false;
|
|
46
|
+
};
|
|
47
|
+
const buildImportsFix = (fixer, program, names) => names.length === 0 ? [] : [
|
|
48
48
|
fixer.insertTextBefore(
|
|
49
49
|
program,
|
|
50
|
-
`import { ${name} } from '${TS_FORTRESS_MODULE}';
|
|
51
|
-
`
|
|
50
|
+
names.map((name) => `import { ${name} } from '${TS_FORTRESS_MODULE}';
|
|
51
|
+
`).join("")
|
|
52
52
|
)
|
|
53
53
|
];
|
|
54
|
-
const
|
|
55
|
-
if (node === null || node === void 0) return false;
|
|
56
|
-
if (node.type === AST_NODE_TYPES.TSTypeAliasDeclaration || node.type === AST_NODE_TYPES.TSInterfaceDeclaration || node.type === AST_NODE_TYPES.TSEnumDeclaration) {
|
|
57
|
-
return node.id.name === name;
|
|
58
|
-
}
|
|
59
|
-
if (node.type === AST_NODE_TYPES.ClassDeclaration || node.type === AST_NODE_TYPES.FunctionDeclaration) {
|
|
60
|
-
return node.id?.name === name;
|
|
61
|
-
}
|
|
62
|
-
return node.type === AST_NODE_TYPES.VariableDeclaration && node.declarations.some(
|
|
63
|
-
(declarator) => declarator.id.type === AST_NODE_TYPES.Identifier && declarator.id.name === name
|
|
64
|
-
);
|
|
65
|
-
};
|
|
54
|
+
const isTsFortressImportDef = (node) => (node.type === AST_NODE_TYPES.ImportSpecifier || node.type === AST_NODE_TYPES.ImportDefaultSpecifier || node.type === AST_NODE_TYPES.ImportNamespaceSpecifier) && node.parent.type === AST_NODE_TYPES.ImportDeclaration && node.parent.source.value === TS_FORTRESS_MODULE;
|
|
66
55
|
|
|
67
|
-
export { buildCalleeResolver,
|
|
56
|
+
export { buildCalleeResolver, buildImportsFix, getImportedLocalName, getTsFortressImports, hasForeignBinding };
|
|
68
57
|
//# sourceMappingURL=import-utils.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-utils.mjs","sources":["../../src/rules/import-utils.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { TS_FORTRESS_MODULE } from './constants.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\nexport type ResolvedCallee = Readonly<{\n /** The canonical (imported) ts-fortress name, regardless of local alias. */\n canonicalName: string;\n /** The identifier node to rewrite when replacing the callee name. */\n propertyNode: TSESTree.Node;\n /** Whether the call was made through a namespace import (`t.foo(...)`). */\n isNamespace: boolean;\n}>;\n\n/** Every `import ... from 'ts-fortress'` declaration in the file. */\nexport const getTsFortressImports = (\n program: TSESTree.Program,\n): readonly TSESTree.ImportDeclaration[] =>\n program.body.filter(\n (node): node is TSESTree.ImportDeclaration =>\n node.type === AST_NODE_TYPES.ImportDeclaration &&\n node.source.value === TS_FORTRESS_MODULE,\n );\n\n/**\n * Returns the in-scope local name a given ts-fortress export is imported as\n * (handling aliases such as `import { nonEmptyArray as nea }` → `'nea'`), or\n * `undefined` when it is not imported. Used so autofixes reference the binding\n * that actually exists rather than the canonical name.\n */\nexport const getImportedLocalName = (\n importDeclarations: readonly TSESTree.ImportDeclaration[],\n importedName: string,\n): string | undefined =>\n importDeclarations\n .flatMap((declaration) => declaration.specifiers)\n .find(\n (specifier) =>\n specifier.type === AST_NODE_TYPES.ImportSpecifier &&\n (specifier.imported.type === AST_NODE_TYPES.Identifier\n ? specifier.imported.name\n : specifier.imported.value) === importedName,\n )?.local.name;\n\n/**\n * Builds a resolver that maps a call expression's callee to the canonical\n * ts-fortress function it refers to. Handles named imports (including aliases)\n * and namespace imports (`import * as t` → `t.foo(...)`) — the latter being the\n * idiomatic way to consume ts-fortress. Returns `undefined` for any callee that\n * does not reference the given ts-fortress imports.\n */\nexport const buildCalleeResolver = (\n importDeclarations: readonly TSESTree.ImportDeclaration[],\n): ((callee: TSESTree.Expression) => ResolvedCallee | undefined) => {\n const specifiers = importDeclarations.flatMap(\n (declaration) => declaration.specifiers,\n );\n\n const namespaceNames = new Set(\n specifiers\n .filter(\n (specifier) =>\n specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,\n )\n .map((specifier) => specifier.local.name),\n );\n\n const localToCanonical = new Map<string, string>(\n specifiers.flatMap((specifier) => {\n if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) return [];\n\n const importedName =\n specifier.imported.type === AST_NODE_TYPES.Identifier\n ? specifier.imported.name\n : specifier.imported.value;\n\n return typeof importedName === 'string'\n ? [[specifier.local.name, importedName] as const]\n : [];\n }),\n );\n\n return (callee) => {\n if (callee.type === AST_NODE_TYPES.Identifier) {\n const canonicalName = localToCanonical.get(callee.name);\n\n return canonicalName === undefined\n ? undefined\n : { canonicalName, propertyNode: callee, isNamespace: false };\n }\n\n return callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n namespaceNames.has(callee.object.name) &&\n callee.property.type === AST_NODE_TYPES.Identifier\n ? {\n canonicalName: callee.property.name,\n propertyNode: callee.property,\n isNamespace: true,\n }\n : undefined;\n };\n};\n\n/**\n * Whether
|
|
1
|
+
{"version":3,"file":"import-utils.mjs","sources":["../../src/rules/import-utils.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { TS_FORTRESS_MODULE } from './constants.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\nexport type ResolvedCallee = Readonly<{\n /** The canonical (imported) ts-fortress name, regardless of local alias. */\n canonicalName: string;\n /** The identifier node to rewrite when replacing the callee name. */\n propertyNode: TSESTree.Node;\n /** Whether the call was made through a namespace import (`t.foo(...)`). */\n isNamespace: boolean;\n}>;\n\n/** Every `import ... from 'ts-fortress'` declaration in the file. */\nexport const getTsFortressImports = (\n program: TSESTree.Program,\n): readonly TSESTree.ImportDeclaration[] =>\n program.body.filter(\n (node): node is TSESTree.ImportDeclaration =>\n node.type === AST_NODE_TYPES.ImportDeclaration &&\n node.source.value === TS_FORTRESS_MODULE,\n );\n\n/**\n * Returns the in-scope local name a given ts-fortress export is imported as\n * (handling aliases such as `import { nonEmptyArray as nea }` → `'nea'`), or\n * `undefined` when it is not imported. Used so autofixes reference the binding\n * that actually exists rather than the canonical name.\n */\nexport const getImportedLocalName = (\n importDeclarations: readonly TSESTree.ImportDeclaration[],\n importedName: string,\n): string | undefined =>\n importDeclarations\n .flatMap((declaration) => declaration.specifiers)\n .find(\n (specifier) =>\n specifier.type === AST_NODE_TYPES.ImportSpecifier &&\n (specifier.imported.type === AST_NODE_TYPES.Identifier\n ? specifier.imported.name\n : specifier.imported.value) === importedName,\n )?.local.name;\n\n/**\n * Builds a resolver that maps a call expression's callee to the canonical\n * ts-fortress function it refers to. Handles named imports (including aliases)\n * and namespace imports (`import * as t` → `t.foo(...)`) — the latter being the\n * idiomatic way to consume ts-fortress. Returns `undefined` for any callee that\n * does not reference the given ts-fortress imports.\n */\nexport const buildCalleeResolver = (\n importDeclarations: readonly TSESTree.ImportDeclaration[],\n): ((callee: TSESTree.Expression) => ResolvedCallee | undefined) => {\n const specifiers = importDeclarations.flatMap(\n (declaration) => declaration.specifiers,\n );\n\n const namespaceNames = new Set(\n specifiers\n .filter(\n (specifier) =>\n specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,\n )\n .map((specifier) => specifier.local.name),\n );\n\n const localToCanonical = new Map<string, string>(\n specifiers.flatMap((specifier) => {\n if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) return [];\n\n const importedName =\n specifier.imported.type === AST_NODE_TYPES.Identifier\n ? specifier.imported.name\n : specifier.imported.value;\n\n return typeof importedName === 'string'\n ? [[specifier.local.name, importedName] as const]\n : [];\n }),\n );\n\n return (callee) => {\n if (callee.type === AST_NODE_TYPES.Identifier) {\n const canonicalName = localToCanonical.get(callee.name);\n\n return canonicalName === undefined\n ? undefined\n : { canonicalName, propertyNode: callee, isNamespace: false };\n }\n\n return callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n namespaceNames.has(callee.object.name) &&\n callee.property.type === AST_NODE_TYPES.Identifier\n ? {\n canonicalName: callee.property.name,\n propertyNode: callee.property,\n isNamespace: true,\n }\n : undefined;\n };\n};\n\n/**\n * Whether `name` resolves, at `node`, to a binding that is not a ts-fortress\n * import — a local declaration, a parameter, or an import from another module.\n * An autofix that makes a call reference `name` must bail out in that case,\n * because the rewritten call would resolve to the wrong binding.\n *\n * Scope-based rather than a scan of the top-level statements, so a binding that\n * merely shadows the ts-fortress one around the call site is caught too.\n */\nexport const hasForeignBinding = (\n sourceCode: TSESLint.SourceCode,\n node: TSESTree.Node,\n name: string,\n): boolean => {\n for (\n let mut_scope: TSESLint.Scope.Scope | null = sourceCode.getScope(node);\n mut_scope !== null;\n mut_scope = mut_scope.upper\n ) {\n const variable = mut_scope.set.get(name);\n\n // The innermost binding wins, so the first scope that declares `name`\n // decides — an outer ts-fortress import does not rescue a shadowed name.\n if (variable !== undefined) {\n return variable.defs.some((def) => !isTsFortressImportDef(def.node));\n }\n }\n\n return false;\n};\n\n/**\n * Inserts `import { <name> } from 'ts-fortress';` for each name at the top of\n * the file. Placement is deliberately naive: prettier-plugin-organize-imports\n * (or any equivalent) merges and sorts them afterwards.\n *\n * All names go into a single fix on purpose: ESLint merges the fixes of one\n * report into one edit span, so two reports each inserting at the start of the\n * file would conflict and one of them would be dropped.\n */\nexport const buildImportsFix = (\n fixer: TSESLint.RuleFixer,\n program: TSESTree.Program,\n names: readonly string[],\n): readonly TSESLint.RuleFix[] =>\n names.length === 0\n ? ([] as const)\n : ([\n fixer.insertTextBefore(\n program,\n names\n .map((name) => `import { ${name} } from '${TS_FORTRESS_MODULE}';\\n`)\n .join(''),\n ),\n ] as const);\n\nconst isTsFortressImportDef = (node: TSESTree.Node): boolean =>\n (node.type === AST_NODE_TYPES.ImportSpecifier ||\n node.type === AST_NODE_TYPES.ImportDefaultSpecifier ||\n node.type === AST_NODE_TYPES.ImportNamespaceSpecifier) &&\n node.parent.type === AST_NODE_TYPES.ImportDeclaration &&\n node.parent.source.value === TS_FORTRESS_MODULE;\n\n/* eslint-enable @typescript-eslint/prefer-readonly-parameter-types */\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type Options = readonly [];
|
|
3
|
+
type MessageIds = 'useCanonicalType';
|
|
4
|
+
export declare const preferCanonicalLengthConstrainedType: TSESLint.RuleModule<MessageIds, Options>;
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=prefer-canonical-length-constrained-type.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-canonical-length-constrained-type.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-canonical-length-constrained-type.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAmBlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,kBAAkB,CAAC;AAyFrC,eAAO,MAAM,oCAAoC,EAAE,QAAQ,CAAC,UAAU,CACpE,UAAU,EACV,OAAO,CA+HC,CAAC"}
|