eslint-rule-forge 0.1.0 → 0.3.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 +49 -9
- package/dist/index.d.mts +2 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/negation.d.mts +102 -0
- package/dist/negation.d.mts.map +1 -0
- package/dist/negation.mjs +215 -0
- package/dist/negation.mjs.map +1 -0
- package/dist/rules/index.d.mts +6 -0
- package/dist/rules/index.d.mts.map +1 -0
- package/dist/rules/index.mjs +6 -0
- package/dist/rules/index.mjs.map +1 -0
- package/dist/rules/no-side-effect-import.d.mts +18 -0
- package/dist/rules/no-side-effect-import.d.mts.map +1 -0
- package/dist/rules/no-side-effect-import.mjs +45 -0
- package/dist/rules/no-side-effect-import.mjs.map +1 -0
- package/dist/rules/prefer-array-is-array.d.mts +13 -0
- package/dist/rules/prefer-array-is-array.d.mts.map +1 -0
- package/dist/rules/prefer-array-is-array.mjs +81 -0
- package/dist/rules/prefer-array-is-array.mjs.map +1 -0
- package/dist/rules/prefer-is-non-null-object.d.mts +11 -0
- package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -0
- package/dist/rules/prefer-is-non-null-object.mjs +192 -0
- package/dist/rules/prefer-is-non-null-object.mjs.map +1 -0
- package/dist/rules/prefer-is-record-and-has-key.d.mts +11 -0
- package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -0
- package/dist/rules/prefer-is-record-and-has-key.mjs +226 -0
- package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -0
- package/dist/rules/prefer-safe-parse.d.mts +31 -0
- package/dist/rules/prefer-safe-parse.d.mts.map +1 -0
- package/dist/rules/prefer-safe-parse.mjs +139 -0
- package/dist/rules/prefer-safe-parse.mjs.map +1 -0
- package/dist/type-wrappers.d.mts +17 -1
- package/dist/type-wrappers.d.mts.map +1 -1
- package/dist/type-wrappers.mjs +35 -1
- package/dist/type-wrappers.mjs.map +1 -1
- package/package.json +10 -10
- package/src/index.mts +2 -0
- package/src/negation.mts +356 -0
- package/src/rules/index.mts +5 -0
- package/src/rules/no-side-effect-import.mts +68 -0
- package/src/rules/prefer-array-is-array.mts +119 -0
- package/src/rules/prefer-is-non-null-object.mts +276 -0
- package/src/rules/prefer-is-record-and-has-key.mts +321 -0
- package/src/rules/prefer-safe-parse.mts +252 -0
- package/src/type-wrappers.mts +55 -1
package/README.md
CHANGED
|
@@ -23,15 +23,18 @@ dependencies.
|
|
|
23
23
|
|
|
24
24
|
`x as T`, `x!`, `x satisfies T` and `<T>x` change a type and never a value.
|
|
25
25
|
|
|
26
|
-
| Helper
|
|
27
|
-
|
|
|
28
|
-
| `isTypeWrapper`
|
|
29
|
-
| `skipTypeWrappers`
|
|
30
|
-
| `typeWrapperLayers`
|
|
31
|
-
| `TypeWrapper`
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
| Helper | What it gives |
|
|
27
|
+
| :---------------------- | :------------------------------------------------------------------------------------ |
|
|
28
|
+
| `isTypeWrapper` | whether a node is one of the four |
|
|
29
|
+
| `skipTypeWrappers` | the expression inside every wrapper around a node: judge syntax on this |
|
|
30
|
+
| `typeWrapperLayers` | the node and every expression inside its wrappers, outermost first: ask types of each |
|
|
31
|
+
| `TypeWrapper` | the four node types |
|
|
32
|
+
| `isIdentifierNamed` | whether a node, read through its wrappers, is the identifier of a given name |
|
|
33
|
+
| `typeHoldsAtEveryLayer` | whether a check holds of the type at every layer, `false` without type information |
|
|
34
|
+
|
|
35
|
+
All of them but `typeHoldsAtEveryLayer` take a readonly
|
|
36
|
+
(`DeepReadonly<TSESTree.Node>`) or a mutable node, and those that return a node
|
|
37
|
+
give back the same kind.
|
|
35
38
|
|
|
36
39
|
Judge syntax on what `skipTypeWrappers` returns, and write the fix from the
|
|
37
40
|
original node, so that the wrapper is kept:
|
|
@@ -87,6 +90,27 @@ assertion (`x as string | null`) then makes the rule more cautious, and a
|
|
|
87
90
|
narrowing one (`x!`, `v as string` on an `unknown`), which the checker cannot
|
|
88
91
|
verify, changes nothing.
|
|
89
92
|
|
|
93
|
+
## Negation
|
|
94
|
+
|
|
95
|
+
Writing the negation of a condition the short way, as a fix that removes a
|
|
96
|
+
ternary or turns a filter into a loop has to.
|
|
97
|
+
|
|
98
|
+
| Helper | What it gives |
|
|
99
|
+
| :---------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
|
|
100
|
+
| `negationOf` | `a !== b` for `a === b`, `!a && !b` for `a \|\| b`, `a` for `!a` (when a boolean), else `!a`; parenthesized as an operand |
|
|
101
|
+
| `prefixedNegationOf` | `!a`, `!f(x)`, `!(a === b)`: a `!` in front, with parentheses only when it needs them |
|
|
102
|
+
| `isComparison` | whether a node is an equality or a relational comparison |
|
|
103
|
+
| `isExactlyInvertible` | whether inverting a comparison's operator is its exact negation: always for an equality, for `<` only without `NaN` |
|
|
104
|
+
| `invertedComparisonText` | the comparison's source with its operator inverted |
|
|
105
|
+
| `needsParenthesesAsOperandOf` | whether a node needs parentheses as an operand of `&&` or `\|\|` |
|
|
106
|
+
| `createGetLayerTypes` | a function giving a node's type at every type-wrapper layer, which the checks above ask |
|
|
107
|
+
|
|
108
|
+
`negationOf` gives `undefined` when the only exact negation is `!!a` (for an
|
|
109
|
+
`a` that may not be a boolean) or `!(a < b)` (for operands that may be `NaN`),
|
|
110
|
+
so that a rule can choose between `prefixedNegationOf` and giving up. Where
|
|
111
|
+
only the truthiness of the negation is read, such as the test of an `if`, its
|
|
112
|
+
context may call every node a boolean.
|
|
113
|
+
|
|
90
114
|
## Parentheses
|
|
91
115
|
|
|
92
116
|
The range and the text of a node leave out the parentheses written around it.
|
|
@@ -186,6 +210,22 @@ export const preferIsNullish: TSESLint.RuleModule<'preferIsNullish'> = {
|
|
|
186
210
|
} as const;
|
|
187
211
|
```
|
|
188
212
|
|
|
213
|
+
## Rule factories
|
|
214
|
+
|
|
215
|
+
Rules that several plugins carry, differing only in the module whose functions
|
|
216
|
+
the fix writes. Each factory takes the module name (and the namespace the
|
|
217
|
+
function is reached through, where there is one) and returns a complete rule,
|
|
218
|
+
so that a fix to the rule reaches every plugin that builds it.
|
|
219
|
+
|
|
220
|
+
| Factory | The rule it builds |
|
|
221
|
+
| :------------------------------ | :-------------------------------------------------------------------------------------------------------- |
|
|
222
|
+
| `createPreferIsNonNullObject` | `typeof u === 'object' && u !== null` → `isNonNullObject(u)` |
|
|
223
|
+
| `createPreferIsRecordAndHasKey` | `Object.hasOwn(obj, key)` and `key in obj` → `isRecord(obj) && hasKey(obj, key)`, or `hasKey` on a record |
|
|
224
|
+
| `createPreferArrayIsArray` | `Array.isArray` → `<namespace>.isArray` |
|
|
225
|
+
| `createPreferSafeParseInt` | `parseInt(x)`, `parseInt(x, 10)` → `Result.unwrapOkOr(<namespace>.<method>(x), Number.NaN)` |
|
|
226
|
+
| `createPreferSafeParseFloat` | `parseFloat(x)`, `Number.parseFloat(x)`, `Number(x)` → the same, for a `string` `x` |
|
|
227
|
+
| `createNoSideEffectImport` | removes `import '<module>';`, with the reason the caller gives |
|
|
228
|
+
|
|
189
229
|
## License
|
|
190
230
|
|
|
191
231
|
[Apache-2.0](https://github.com/noshiro-pf/mono/blob/main/libs/eslint-rule-forge/LICENSE)
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.mjs
CHANGED
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type ParserServicesWithTypeInformation, type TSESLint, type TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
import type { DeepReadonly, StrictPick } from 'ts-type-forge';
|
|
3
|
+
import * as ts from 'typescript';
|
|
4
|
+
/** Each comparison operator, and the one that is true exactly when it is not. */
|
|
5
|
+
export declare const INVERTED_COMPARISON_OPERATOR: {
|
|
6
|
+
readonly '===': '!==';
|
|
7
|
+
readonly '!==': '===';
|
|
8
|
+
readonly '==': '!=';
|
|
9
|
+
readonly '!=': '==';
|
|
10
|
+
readonly '<': '>=';
|
|
11
|
+
readonly '>=': '<';
|
|
12
|
+
readonly '>': '<=';
|
|
13
|
+
readonly '<=': '>';
|
|
14
|
+
};
|
|
15
|
+
export type ComparisonOperator = keyof typeof INVERTED_COMPARISON_OPERATOR;
|
|
16
|
+
export type ComparisonExpression = DeepReadonly<TSESTree.BinaryExpression> & Readonly<{
|
|
17
|
+
operator: ComparisonOperator;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* The type of a node at each of its type-wrapper layers, outermost first (see
|
|
21
|
+
* {@link typeWrapperLayers}).
|
|
22
|
+
*/
|
|
23
|
+
export type GetLayerTypes = (node: DeepReadonly<TSESTree.Node>) => readonly ts.Type[];
|
|
24
|
+
/**
|
|
25
|
+
* Builds a {@link GetLayerTypes}: `x as A` gives the types of `x as A` and of
|
|
26
|
+
* `x`.
|
|
27
|
+
*
|
|
28
|
+
* A conclusion a rule draws from a type is drawn only when it holds for every
|
|
29
|
+
* one of them. A widening `as` (`b as boolean | number`) is then the author
|
|
30
|
+
* saying the value may be something else, and is respected, while a narrowing
|
|
31
|
+
* one (`n as unknown as boolean`) leaves the value's own type to decide.
|
|
32
|
+
*/
|
|
33
|
+
export declare const createGetLayerTypes: (services: StrictPick<ParserServicesWithTypeInformation, 'esTreeNodeToTSNodeMap' | 'program'>) => GetLayerTypes;
|
|
34
|
+
export declare const isComparison: (node: DeepReadonly<TSESTree.Node>) => node is ComparisonExpression;
|
|
35
|
+
/**
|
|
36
|
+
* Whether inverting the operator gives exactly the negation.
|
|
37
|
+
*
|
|
38
|
+
* An equality always does. A relational comparison does unless an operand
|
|
39
|
+
* converts to `NaN`: every relational comparison with `NaN` is `false`, so
|
|
40
|
+
* `!(a < b)` is `true` there while `a >= b` is `false`. It cannot when both
|
|
41
|
+
* sides are strings (compared as strings), or both are numeric values that are
|
|
42
|
+
* never `NaN` — number literals, `bigint`, booleans, and number brands that
|
|
43
|
+
* declare `NaNValue: false` (`ValidNumber`, `Int`, `FiniteNumber`, …).
|
|
44
|
+
*
|
|
45
|
+
* `getLayerTypes` gives an operand's type at each of its type-wrapper layers,
|
|
46
|
+
* and every one of them has to say so: `(v as number) < 1` may be `NaN` even
|
|
47
|
+
* when `v` is a `ValidNumber`, and `(n as ValidNumber) < 1` still may when `n`
|
|
48
|
+
* is a `number`.
|
|
49
|
+
*/
|
|
50
|
+
export declare const isExactlyInvertible: (node: ComparisonExpression, getLayerTypes: GetLayerTypes, checker: ts.TypeChecker) => boolean;
|
|
51
|
+
/**
|
|
52
|
+
* The source of `node` with its operator inverted, and everything else —
|
|
53
|
+
* operands, comments, line breaks — as written.
|
|
54
|
+
*/
|
|
55
|
+
export declare const invertedComparisonText: (node: ComparisonExpression, sourceCode: DeepReadonly<TSESLint.SourceCode>) => string;
|
|
56
|
+
/** What {@link negationOf} needs to know about the code it negates. */
|
|
57
|
+
export type NegationContext = Readonly<{
|
|
58
|
+
sourceCode: DeepReadonly<TSESLint.SourceCode>;
|
|
59
|
+
checker: ts.TypeChecker;
|
|
60
|
+
getLayerTypes: GetLayerTypes;
|
|
61
|
+
/**
|
|
62
|
+
* Whether `node`'s value is a boolean, so that the negation of `!node` may
|
|
63
|
+
* be `node` itself. Where only the truthiness of the negation is read — the
|
|
64
|
+
* test of an `if` — any value will do, and this may answer `true` for every
|
|
65
|
+
* node.
|
|
66
|
+
*/
|
|
67
|
+
isBoolean: (node: DeepReadonly<TSESTree.Expression>) => boolean;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* The negation of `node`, written the short way, as the source text of an
|
|
71
|
+
* operand of `operandOf` (parenthesized if need be), or of a whole expression
|
|
72
|
+
* when `operandOf` is `undefined`:
|
|
73
|
+
*
|
|
74
|
+
* | `node` | negation | when |
|
|
75
|
+
* | :----------------- | :---------------- | :------------------------------------ |
|
|
76
|
+
* | `!a` | `a` | `a` is a boolean (`context.isBoolean`) |
|
|
77
|
+
* | `a === b` | `a !== b` | always |
|
|
78
|
+
* | `a < b` | `a >= b` | no operand can be `NaN` |
|
|
79
|
+
* | `a && b`, `a \|\| b` | `!a \|\| !b`, `!a && !b` | always, each side negated the same way |
|
|
80
|
+
* | anything else | `!a`, `!(a)` | always |
|
|
81
|
+
*
|
|
82
|
+
* `undefined` when the first two rows' condition does not hold: then the only
|
|
83
|
+
* exact negation is `!!a` or `!(a < b)`, and a caller decides whether to
|
|
84
|
+
* write it (with {@link prefixedNegationOf}) or to give up. Inside `&&` and
|
|
85
|
+
* `||`, a side with no short negation is written with `!`.
|
|
86
|
+
*
|
|
87
|
+
* The node is taken as written: a comparison under a type wrapper is not
|
|
88
|
+
* looked into, since inverting it would drop the wrapper.
|
|
89
|
+
*/
|
|
90
|
+
export declare const negationOf: (node: DeepReadonly<TSESTree.Expression>, context: NegationContext, operandOf: '&&' | '||' | undefined) => string | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* `!node`, with `node` parenthesized unless `!` already binds to all of it:
|
|
93
|
+
* `!a`, `!f(x)`, `!(a === b)`.
|
|
94
|
+
*/
|
|
95
|
+
export declare const prefixedNegationOf: (node: DeepReadonly<TSESTree.Expression>, sourceCode: DeepReadonly<TSESLint.SourceCode>) => string;
|
|
96
|
+
/**
|
|
97
|
+
* Whether `node`, written as an operand of `operator`, needs parentheses to
|
|
98
|
+
* stay one operand: `a || b` under `&&`, `a ?? b` under either, and anything
|
|
99
|
+
* binding looser than both (`a ? b : c`, `a = b`, …).
|
|
100
|
+
*/
|
|
101
|
+
export declare const needsParenthesesAsOperandOf: (node: DeepReadonly<TSESTree.Node>, operator: '&&' | '||') => boolean;
|
|
102
|
+
//# sourceMappingURL=negation.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negation.d.mts","sourceRoot":"","sources":["../src/negation.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iCAAiC,EACtC,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAKjC,iFAAiF;AACjF,eAAO,MAAM,4BAA4B;aACvC,KAAK,EAAE,KAAK;aACZ,KAAK,EAAE,KAAK;aACZ,IAAI,EAAE,IAAI;aACV,IAAI,EAAE,IAAI;aACV,GAAG,EAAE,IAAI;aACT,IAAI,EAAE,GAAG;aACT,GAAG,EAAE,IAAI;aACT,IAAI,EAAE,GAAG;CACD,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,MAAM,OAAO,4BAA4B,CAAC;AAE3E,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GACxE,QAAQ,CAAC;IAAE,QAAQ,EAAE,kBAAkB,CAAA;CAAE,CAAC,CAAC;AAE7C;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,KAC9B,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;AAExB;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,aAElB,UAAU,CAClB,iCAAiC,EACjC,uBAAuB,GAAG,SAAS,CACpC,KACA,aASF,CAAC;AAEJ,eAAO,MAAM,YAAY,SACjB,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,KAChC,IAAI,IAAI,oBAEiD,CAAC;AAE7D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,SACxB,oBAAoB,iBACX,aAAa,WACnB,EAAE,CAAC,WAAW,KACtB,OAcF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,SAC3B,oBAAoB,cACd,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,KAC5C,MAoBF,CAAC;AAEF,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,UAAU,EAAE,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC9C,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC;IACxB,aAAa,EAAE,aAAa,CAAC;IAC7B;;;;;OAKG;IACH,SAAS,EAAE,CAAC,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,OAAO,CAAC;CACjE,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,UAAU,SACf,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,WAC9B,eAAe,aACb,IAAI,GAAG,IAAI,GAAG,SAAS,KACjC,MAAM,GAAG,SAIX,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,SACvB,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,cAC3B,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,KAC5C,MAIF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,SAChC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,YACvB,IAAI,GAAG,IAAI,KACpB,OAAqD,CAAC"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
|
|
2
|
+
import * as ts from 'typescript';
|
|
3
|
+
import { typeWrapperLayers } from './type-wrappers.mjs';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const INVERTED_COMPARISON_OPERATOR = {
|
|
7
|
+
'===': '!==',
|
|
8
|
+
'!==': '===',
|
|
9
|
+
'==': '!=',
|
|
10
|
+
'!=': '==',
|
|
11
|
+
'<': '>=',
|
|
12
|
+
'>=': '<',
|
|
13
|
+
'>': '<=',
|
|
14
|
+
'<=': '>',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export const createGetLayerTypes = (services) => (node) => {
|
|
26
|
+
const checker = services.program.getTypeChecker();
|
|
27
|
+
return typeWrapperLayers(node).map((layer) => checker.getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(asNode(layer))));
|
|
28
|
+
};
|
|
29
|
+
export const isComparison = (node) => node.type === AST_NODE_TYPES.BinaryExpression &&
|
|
30
|
+
Object.hasOwn(INVERTED_COMPARISON_OPERATOR, node.operator);
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export const isExactlyInvertible = (node, getLayerTypes, checker) => {
|
|
47
|
+
if (!RELATIONAL_OPERATORS.has(node.operator)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
const sides = [
|
|
51
|
+
...getLayerTypes(node.left),
|
|
52
|
+
...getLayerTypes(node.right),
|
|
53
|
+
].flatMap(constituentsOf);
|
|
54
|
+
return (sides.every((type) => (type.flags & ts.TypeFlags.StringLike) !== 0) ||
|
|
55
|
+
sides.every((type) => isNeverNaN(type, checker)));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
export const invertedComparisonText = (node, sourceCode) => {
|
|
62
|
+
const text = sourceCode.getText(asNode(node));
|
|
63
|
+
const operator = sourceCode.getFirstTokenBetween(asNode(node.left), asNode(node.right), (token) => token.value === node.operator);
|
|
64
|
+
if (operator === null) {
|
|
65
|
+
return `${sourceCode.getText(asNode(node.left))} ${INVERTED_COMPARISON_OPERATOR[node.operator]} ${sourceCode.getText(asNode(node.right))}`;
|
|
66
|
+
}
|
|
67
|
+
const [start] = node.range;
|
|
68
|
+
return [
|
|
69
|
+
text.slice(0, operator.range[0] - start),
|
|
70
|
+
INVERTED_COMPARISON_OPERATOR[node.operator],
|
|
71
|
+
text.slice(operator.range[1] - start),
|
|
72
|
+
].join('');
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
export const negationOf = (node, context, operandOf) => {
|
|
96
|
+
const negation = shortNegationOf(node, context);
|
|
97
|
+
return negation === undefined ? undefined : asOperand(negation, operandOf);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
export const prefixedNegationOf = (node, sourceCode) => {
|
|
104
|
+
const text = sourceCode.getText(asNode(node));
|
|
105
|
+
return BINDS_AS_TIGHT_AS_UNARY.has(node.type) ? `!${text}` : `!(${text})`;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
export const needsParenthesesAsOperandOf = (node, operator) => isLooserThan(precedenceOf(node), operator);
|
|
113
|
+
const shortNegationOf = (node, context) => {
|
|
114
|
+
if (node.type === AST_NODE_TYPES.UnaryExpression && node.operator === '!') {
|
|
115
|
+
return context.isBoolean(node.argument)
|
|
116
|
+
? {
|
|
117
|
+
text: context.sourceCode.getText(asNode(node.argument)),
|
|
118
|
+
precedence: precedenceOf(node.argument),
|
|
119
|
+
}
|
|
120
|
+
: undefined;
|
|
121
|
+
}
|
|
122
|
+
if (isComparison(node)) {
|
|
123
|
+
return isExactlyInvertible(node, context.getLayerTypes, context.checker)
|
|
124
|
+
? {
|
|
125
|
+
text: invertedComparisonText(node, context.sourceCode),
|
|
126
|
+
precedence: 'tighter',
|
|
127
|
+
}
|
|
128
|
+
: undefined;
|
|
129
|
+
}
|
|
130
|
+
if (node.type === AST_NODE_TYPES.LogicalExpression &&
|
|
131
|
+
node.operator !== '??') {
|
|
132
|
+
const operator = node.operator === '&&' ? '||' : '&&';
|
|
133
|
+
const side = (operand) => asOperand(shortNegationOf(operand, context) ?? {
|
|
134
|
+
text: prefixedNegationOf(operand, context.sourceCode),
|
|
135
|
+
precedence: 'tighter',
|
|
136
|
+
}, operator);
|
|
137
|
+
return {
|
|
138
|
+
text: `${side(node.left)} ${operator} ${side(node.right)}`,
|
|
139
|
+
precedence: operator,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
text: prefixedNegationOf(node, context.sourceCode),
|
|
144
|
+
precedence: 'tighter',
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
const asOperand = (negation, operator) => operator !== undefined && isLooserThan(negation.precedence, operator)
|
|
148
|
+
? `(${negation.text})`
|
|
149
|
+
: negation.text;
|
|
150
|
+
const isLooserThan = (precedence, operator) => precedence === 'looser' || (operator === '&&' && precedence === '||');
|
|
151
|
+
const precedenceOf = (node) => {
|
|
152
|
+
if (node.type === AST_NODE_TYPES.LogicalExpression) {
|
|
153
|
+
|
|
154
|
+
return node.operator === '??' ? 'looser' : node.operator;
|
|
155
|
+
}
|
|
156
|
+
return LOOSER_THAN_LOGICAL.has(node.type) ? 'looser' : 'tighter';
|
|
157
|
+
};
|
|
158
|
+
const LOOSER_THAN_LOGICAL = new Set([
|
|
159
|
+
AST_NODE_TYPES.ArrowFunctionExpression,
|
|
160
|
+
AST_NODE_TYPES.AssignmentExpression,
|
|
161
|
+
AST_NODE_TYPES.ConditionalExpression,
|
|
162
|
+
AST_NODE_TYPES.SequenceExpression,
|
|
163
|
+
AST_NODE_TYPES.YieldExpression,
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
const BINDS_AS_TIGHT_AS_UNARY = new Set([
|
|
167
|
+
AST_NODE_TYPES.ArrayExpression,
|
|
168
|
+
AST_NODE_TYPES.AwaitExpression,
|
|
169
|
+
AST_NODE_TYPES.CallExpression,
|
|
170
|
+
AST_NODE_TYPES.ChainExpression,
|
|
171
|
+
AST_NODE_TYPES.Identifier,
|
|
172
|
+
AST_NODE_TYPES.Literal,
|
|
173
|
+
AST_NODE_TYPES.MemberExpression,
|
|
174
|
+
AST_NODE_TYPES.MetaProperty,
|
|
175
|
+
AST_NODE_TYPES.NewExpression,
|
|
176
|
+
AST_NODE_TYPES.ObjectExpression,
|
|
177
|
+
AST_NODE_TYPES.TaggedTemplateExpression,
|
|
178
|
+
AST_NODE_TYPES.TemplateLiteral,
|
|
179
|
+
AST_NODE_TYPES.ThisExpression,
|
|
180
|
+
AST_NODE_TYPES.TSNonNullExpression,
|
|
181
|
+
AST_NODE_TYPES.UnaryExpression,
|
|
182
|
+
]);
|
|
183
|
+
const RELATIONAL_OPERATORS = new Set(['<', '<=', '>', '>=']);
|
|
184
|
+
const constituentsOf = (type) => type.isUnion() ? type.types : [type];
|
|
185
|
+
const isNeverNaN = (type, checker) => {
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
const neverNaNFlags = ts.TypeFlags.NumberLiteral |
|
|
190
|
+
ts.TypeFlags.BigIntLike |
|
|
191
|
+
ts.TypeFlags.BooleanLike |
|
|
192
|
+
ts.TypeFlags.Null;
|
|
193
|
+
if ((type.flags & neverNaNFlags) !== 0) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
if (!type.isIntersection()) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const brand = checker.getPropertyOfType(type, 'NaNValue');
|
|
200
|
+
if (brand === undefined) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
const brandType = checker.getTypeOfSymbol(brand);
|
|
204
|
+
return (type.types.some((member) => (member.flags & ts.TypeFlags.NumberLike) !== 0) &&
|
|
205
|
+
(brandType.flags & ts.TypeFlags.BooleanLiteral) !== 0 &&
|
|
206
|
+
checker.typeToString(brandType) === 'false');
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
const asNode = (node) =>
|
|
213
|
+
|
|
214
|
+
node;
|
|
215
|
+
//# sourceMappingURL=negation.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negation.mjs","sourceRoot":"","sources":["../src/negation.mts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './no-side-effect-import.mjs';
|
|
2
|
+
export * from './prefer-array-is-array.mjs';
|
|
3
|
+
export * from './prefer-is-non-null-object.mjs';
|
|
4
|
+
export * from './prefer-is-record-and-has-key.mjs';
|
|
5
|
+
export * from './prefer-safe-parse.mjs';
|
|
6
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/rules/index.mts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './no-side-effect-import.mjs';
|
|
2
|
+
export * from './prefer-array-is-array.mjs';
|
|
3
|
+
export * from './prefer-is-non-null-object.mjs';
|
|
4
|
+
export * from './prefer-is-record-and-has-key.mjs';
|
|
5
|
+
export * from './prefer-safe-parse.mjs';
|
|
6
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/rules/index.mts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a rule that removes `import '<moduleName>';`, an import that binds no
|
|
4
|
+
* name. The rule matches the bare specifier exactly, so a subpath such as
|
|
5
|
+
* `<moduleName>/global` and every other module are left alone.
|
|
6
|
+
*
|
|
7
|
+
* Why such an import is wrong depends on the module — one with
|
|
8
|
+
* `sideEffects: false` loads for nothing, one with no runtime entry point fails
|
|
9
|
+
* to resolve — so the caller says it, in `description` and `message`, and
|
|
10
|
+
* chooses `type` accordingly.
|
|
11
|
+
*/
|
|
12
|
+
export declare const createNoSideEffectImport: ({ moduleName, type, description, message, }: Readonly<{
|
|
13
|
+
moduleName: string;
|
|
14
|
+
type: TSESLint.RuleMetaData<string, unknown>['type'];
|
|
15
|
+
description: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}>) => TSESLint.RuleModule<'removeSideEffectImport', readonly []>;
|
|
18
|
+
//# sourceMappingURL=no-side-effect-import.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-side-effect-import.d.mts","sourceRoot":"","sources":["../../src/rules/no-side-effect-import.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,0BAA0B,CAAC;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,gDAKlC,QAAQ,CAAC;IACV,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,KAAG,QAAQ,CAAC,UAAU,CAAC,wBAAwB,EAAE,SAAS,EAAE,CAwBjD,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export const createNoSideEffectImport = ({ moduleName, type, description, message, }) => ({
|
|
12
|
+
meta: {
|
|
13
|
+
type,
|
|
14
|
+
docs: { description },
|
|
15
|
+
fixable: 'code',
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: { removeSideEffectImport: message },
|
|
18
|
+
},
|
|
19
|
+
create: (context) => ({
|
|
20
|
+
ImportDeclaration: (node) => {
|
|
21
|
+
if (node.source.value !== moduleName || node.specifiers.length > 0) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
context.report({
|
|
25
|
+
node,
|
|
26
|
+
messageId: 'removeSideEffectImport',
|
|
27
|
+
fix: (fixer) => removeStatement(fixer, context.sourceCode, node),
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
defaultOptions: [],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
const removeStatement = (fixer, sourceCode, node) => {
|
|
39
|
+
const restOfLine = /^[^\S\n]*\n/u.exec(sourceCode.getText().slice(node.range[1]));
|
|
40
|
+
return fixer.removeRange([
|
|
41
|
+
node.range[0],
|
|
42
|
+
node.range[1] + (restOfLine?.[0].length ?? 0),
|
|
43
|
+
]);
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=no-side-effect-import.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-side-effect-import.mjs","sourceRoot":"","sources":["../../src/rules/no-side-effect-import.mts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,EACvC,UAAU,EACV,IAAI,EACJ,WAAW,EACX,OAAO,GAMP,EAA8D,EAAE,CAChE,CAAC;IACC,IAAI,EAAE;QACJ,IAAI;QACJ,IAAI,EAAE,EAAE,WAAW,EAAE;QACrB,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE,EAAE,sBAAsB,EAAE,OAAO,EAAE;KAC9C;IAED,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACpB,iBAAiB,EAAE,CAAC,IAAI,EAAE,EAAE;YAC1B,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnE,OAAO;YACT,CAAC;YAED,OAAO,CAAC,MAAM,CAAC;gBACb,IAAI;gBACJ,SAAS,EAAE,wBAAwB;gBACnC,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC;aACjE,CAAC,CAAC;QACL,CAAC;KACF,CAAC;IACF,cAAc,EAAE,EAAE;CACnB,CAAU,CAAC;AAEd,uEAAuE;AAEvE;;;GAGG;AACH,MAAM,eAAe,GAAG,CACtB,KAAyB,EACzB,UAA+B,EAC/B,IAAmB,EACD,EAAE;IACpB,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CACpC,UAAU,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAC1C,CAAC;IAEF,OAAO,KAAK,CAAC,WAAW,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;KAC9C,CAAC,CAAC;AACL,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a rule that replaces `Array.isArray` with `<namespace>.isArray`
|
|
4
|
+
* imported from `moduleName`. Only the callee is rewritten; the arguments stay
|
|
5
|
+
* as written.
|
|
6
|
+
*/
|
|
7
|
+
export declare const createPreferArrayIsArray: ({ moduleName, namespace, }: Readonly<{
|
|
8
|
+
/** The module that exports `namespace`. */
|
|
9
|
+
moduleName: string;
|
|
10
|
+
/** The exported namespace whose `isArray` replaces `Array.isArray`. */
|
|
11
|
+
namespace: string;
|
|
12
|
+
}>) => TSESLint.RuleModule<'useIsArray', readonly []>;
|
|
13
|
+
//# sourceMappingURL=prefer-array-is-array.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-array-is-array.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-array-is-array.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AASlC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,+BAGlC,QAAQ,CAAC;IACV,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IAEnB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,KAAG,QAAQ,CAAC,UAAU,CAAC,YAAY,EAAE,SAAS,EAAE,CAuEjD,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
|
|
2
|
+
import { buildNamedImportFix, getImportDeclarations, getNamedImports, } from '../imports.mjs';
|
|
3
|
+
import { getRangeWithParens } from '../parentheses.mjs';
|
|
4
|
+
import { isIdentifierNamed, skipTypeWrappers } from '../type-wrappers.mjs';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export const createPreferArrayIsArray = ({ moduleName, namespace, }) => {
|
|
11
|
+
const replacement = `${namespace}.isArray`;
|
|
12
|
+
const text = `Replace \`Array.isArray\` with \`${replacement}\` from ${moduleName}.`;
|
|
13
|
+
return {
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'suggestion',
|
|
16
|
+
docs: { description: text },
|
|
17
|
+
fixable: 'code',
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: { useIsArray: text },
|
|
20
|
+
},
|
|
21
|
+
create: (context) => {
|
|
22
|
+
const sourceCode = context.sourceCode;
|
|
23
|
+
const program = sourceCode.ast;
|
|
24
|
+
const moduleImports = getImportDeclarations(program, moduleName);
|
|
25
|
+
const mut_nodesToFix = [];
|
|
26
|
+
return {
|
|
27
|
+
CallExpression: (node) => {
|
|
28
|
+
if (!isArrayIsArrayCall(node)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
mut_nodesToFix.push(node);
|
|
32
|
+
},
|
|
33
|
+
'Program:exit': () => {
|
|
34
|
+
const hasNamespaceImport = getNamedImports(moduleImports).includes(namespace);
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
for (const [index, node] of mut_nodesToFix.entries()) {
|
|
41
|
+
context.report({
|
|
42
|
+
node,
|
|
43
|
+
messageId: 'useIsArray',
|
|
44
|
+
fix: (fixer) => {
|
|
45
|
+
const importFixes = index === 0 && !hasNamespaceImport
|
|
46
|
+
? buildNamedImportFix(fixer, program, moduleName, [
|
|
47
|
+
namespace,
|
|
48
|
+
])
|
|
49
|
+
: [];
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
return [
|
|
54
|
+
...importFixes,
|
|
55
|
+
fixer.replaceTextRange(getRangeWithParens(sourceCode, node.callee), replacement),
|
|
56
|
+
];
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
defaultOptions: [],
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
const isArrayIsArrayCall = (
|
|
72
|
+
|
|
73
|
+
node) => {
|
|
74
|
+
const callee = skipTypeWrappers(node.callee);
|
|
75
|
+
return (callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
76
|
+
!callee.computed &&
|
|
77
|
+
isIdentifierNamed(callee.object, 'Array') &&
|
|
78
|
+
callee.property.type === AST_NODE_TYPES.Identifier &&
|
|
79
|
+
callee.property.name === 'isArray');
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=prefer-array-is-array.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-array-is-array.mjs","sourceRoot":"","sources":["../../src/rules/prefer-array-is-array.mts"],"names":[],"mappings":"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"}
|