eslint-rule-forge 0.2.0 → 0.3.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.
Files changed (42) hide show
  1. package/README.md +28 -9
  2. package/dist/index.d.mts +1 -0
  3. package/dist/index.d.mts.map +1 -1
  4. package/dist/index.mjs +1 -0
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/rules/index.d.mts +6 -0
  7. package/dist/rules/index.d.mts.map +1 -0
  8. package/dist/rules/index.mjs +6 -0
  9. package/dist/rules/index.mjs.map +1 -0
  10. package/dist/rules/no-side-effect-import.d.mts +18 -0
  11. package/dist/rules/no-side-effect-import.d.mts.map +1 -0
  12. package/dist/rules/no-side-effect-import.mjs +45 -0
  13. package/dist/rules/no-side-effect-import.mjs.map +1 -0
  14. package/dist/rules/prefer-array-is-array.d.mts +13 -0
  15. package/dist/rules/prefer-array-is-array.d.mts.map +1 -0
  16. package/dist/rules/prefer-array-is-array.mjs +81 -0
  17. package/dist/rules/prefer-array-is-array.mjs.map +1 -0
  18. package/dist/rules/prefer-is-non-null-object.d.mts +11 -0
  19. package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -0
  20. package/dist/rules/prefer-is-non-null-object.mjs +192 -0
  21. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -0
  22. package/dist/rules/prefer-is-record-and-has-key.d.mts +11 -0
  23. package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -0
  24. package/dist/rules/prefer-is-record-and-has-key.mjs +226 -0
  25. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -0
  26. package/dist/rules/prefer-safe-parse.d.mts +31 -0
  27. package/dist/rules/prefer-safe-parse.d.mts.map +1 -0
  28. package/dist/rules/prefer-safe-parse.mjs +139 -0
  29. package/dist/rules/prefer-safe-parse.mjs.map +1 -0
  30. package/dist/type-wrappers.d.mts +17 -1
  31. package/dist/type-wrappers.d.mts.map +1 -1
  32. package/dist/type-wrappers.mjs +35 -1
  33. package/dist/type-wrappers.mjs.map +1 -1
  34. package/package.json +9 -9
  35. package/src/index.mts +1 -0
  36. package/src/rules/index.mts +5 -0
  37. package/src/rules/no-side-effect-import.mts +68 -0
  38. package/src/rules/prefer-array-is-array.mts +119 -0
  39. package/src/rules/prefer-is-non-null-object.mts +276 -0
  40. package/src/rules/prefer-is-record-and-has-key.mts +321 -0
  41. package/src/rules/prefer-safe-parse.mts +252 -0
  42. 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 | 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
-
33
- All of them take a readonly (`DeepReadonly<TSESTree.Node>`) or a mutable node
34
- and give back the same kind.
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:
@@ -207,6 +210,22 @@ export const preferIsNullish: TSESLint.RuleModule<'preferIsNullish'> = {
207
210
  } as const;
208
211
  ```
209
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
+
210
229
  ## License
211
230
 
212
231
  [Apache-2.0](https://github.com/noshiro-pf/mono/blob/main/libs/eslint-rule-forge/LICENSE)
package/dist/index.d.mts CHANGED
@@ -2,5 +2,6 @@ export * from './bindings.mjs';
2
2
  export * from './imports.mjs';
3
3
  export * from './negation.mjs';
4
4
  export * from './parentheses.mjs';
5
+ export * from './rules/index.mjs';
5
6
  export * from './type-wrappers.mjs';
6
7
  //# sourceMappingURL=index.d.mts.map
@@ -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,gBAAgB,CAAC;AAC/B,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
@@ -2,5 +2,6 @@ export * from './bindings.mjs';
2
2
  export * from './imports.mjs';
3
3
  export * from './negation.mjs';
4
4
  export * from './parentheses.mjs';
5
+ export * from './rules/index.mjs';
5
6
  export * from './type-wrappers.mjs';
6
7
  //# sourceMappingURL=index.mjs.map
@@ -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,gBAAgB,CAAC;AAC/B,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,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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"}
@@ -0,0 +1,11 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ /**
3
+ * Builds a rule that replaces `typeof u === 'object' && u !== null`, in either
4
+ * order and anywhere in an `&&` chain, with `isNonNullObject(u)` imported from
5
+ * `moduleName`.
6
+ */
7
+ export declare const createPreferIsNonNullObject: ({ moduleName, }: Readonly<{
8
+ /** The module that exports `isNonNullObject`. */
9
+ moduleName: string;
10
+ }>) => TSESLint.RuleModule<'useIsNonNullObject', readonly []>;
11
+ //# sourceMappingURL=prefer-is-non-null-object.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-is-non-null-object.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-is-non-null-object.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AASlC;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,oBAErC,QAAQ,CAAC;IACV,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,KAAG,QAAQ,CAAC,UAAU,CAAC,oBAAoB,EAAE,SAAS,EAAE,CAwE7C,CAAC"}
@@ -0,0 +1,192 @@
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
+ import { buildNamedImportFix, getImportDeclarations, getNamedImports, } from '../imports.mjs';
3
+ import { getRangeWithParens, parenthesesDepth } from '../parentheses.mjs';
4
+ import { skipTypeWrappers } from '../type-wrappers.mjs';
5
+
6
+
7
+
8
+
9
+
10
+ export const createPreferIsNonNullObject = ({ moduleName, }) => ({
11
+ meta: {
12
+ type: 'suggestion',
13
+ docs: {
14
+ description: `Replace \`typeof u === "object" && u !== null\`, in either order and anywhere in an \`&&\` chain, with \`isNonNullObject(u)\` from ${moduleName}.`,
15
+ },
16
+ fixable: 'code',
17
+ schema: [],
18
+ messages: {
19
+ useIsNonNullObject: `Replace the object/null check with \`isNonNullObject()\` from ${moduleName}.`,
20
+ },
21
+ },
22
+ create: (context) => {
23
+ const sourceCode = context.sourceCode;
24
+ const program = sourceCode.ast;
25
+ const moduleImports = getImportDeclarations(program, moduleName);
26
+ const mut_pairsToFix = [];
27
+ return {
28
+ LogicalExpression: (node) => {
29
+
30
+ if (node.operator !== '&&' || isChainOperand(sourceCode, node)) {
31
+ return;
32
+ }
33
+ mut_pairsToFix.push(...findNonNullObjectPairs(sourceCode, node));
34
+ },
35
+ 'Program:exit': () => {
36
+ const hasIsNonNullObjectImport = getNamedImports(moduleImports).includes('isNonNullObject');
37
+
38
+
39
+
40
+
41
+
42
+ for (const [index, { range, identifierName },] of mut_pairsToFix.entries()) {
43
+ context.report({
44
+ loc: {
45
+ start: sourceCode.getLocFromIndex(range[0]),
46
+ end: sourceCode.getLocFromIndex(range[1]),
47
+ },
48
+ messageId: 'useIsNonNullObject',
49
+ fix: (fixer) => {
50
+ const replacement = `isNonNullObject(${identifierName})`;
51
+ const importFixes = index === 0 && !hasIsNonNullObjectImport
52
+ ? buildNamedImportFix(fixer, program, moduleName, [
53
+ 'isNonNullObject',
54
+ ])
55
+ : [];
56
+ return [
57
+ ...importFixes,
58
+ fixer.replaceTextRange(range, replacement),
59
+ ];
60
+ },
61
+ });
62
+ }
63
+ },
64
+ };
65
+ },
66
+ defaultOptions: [],
67
+ });
68
+
69
+
70
+
71
+
72
+ const isChainOperand = (
73
+
74
+ sourceCode,
75
+
76
+ node) => node.parent.type === AST_NODE_TYPES.LogicalExpression &&
77
+ node.parent.operator === '&&' &&
78
+ parenthesesDepth(sourceCode, node) === 0;
79
+
80
+
81
+
82
+
83
+
84
+
85
+ const findNonNullObjectPairs = (
86
+
87
+ sourceCode,
88
+
89
+ node) => {
90
+ const operands = chainOperands(sourceCode, node);
91
+ const pairsFrom = (index) => {
92
+ const first = operands[index];
93
+ const second = operands[index + 1];
94
+ if (first === undefined || second === undefined) {
95
+ return [];
96
+ }
97
+ const identifierName = pairIdentifierName(first, second) ?? pairIdentifierName(second, first);
98
+ return identifierName === undefined
99
+ ? pairsFrom(index + 1)
100
+ : [
101
+ {
102
+ range: [
103
+ getRangeWithParens(sourceCode, first)[0],
104
+ getRangeWithParens(sourceCode, second)[1],
105
+ ],
106
+ identifierName,
107
+ },
108
+ ...pairsFrom(index + 2),
109
+ ];
110
+ };
111
+ return pairsFrom(0);
112
+ };
113
+
114
+
115
+
116
+
117
+ const chainOperands = (
118
+
119
+ sourceCode,
120
+
121
+ node) => [node.left, node.right].flatMap((operand) => operand.type === AST_NODE_TYPES.LogicalExpression &&
122
+ operand.operator === '&&' &&
123
+ parenthesesDepth(sourceCode, operand) === 0
124
+ ? chainOperands(sourceCode, operand)
125
+ : [operand]);
126
+
127
+
128
+
129
+
130
+
131
+
132
+ const pairIdentifierName = (
133
+
134
+ typeofCheck,
135
+
136
+ nullCheck) => {
137
+ const identifierName = getTypeofObjectIdentifierName(skipTypeWrappers(typeofCheck));
138
+ return identifierName !== undefined &&
139
+ identifierName ===
140
+ getNonNullCheckIdentifierName(skipTypeWrappers(nullCheck))
141
+ ? identifierName
142
+ : undefined;
143
+ };
144
+
145
+ const getTypeofObjectIdentifierName = (
146
+
147
+ node) => {
148
+ if (node.type !== AST_NODE_TYPES.BinaryExpression ||
149
+ node.operator !== '===') {
150
+ return undefined;
151
+ }
152
+ const left = skipTypeWrappers(node.left);
153
+ const right = skipTypeWrappers(node.right);
154
+ const typeofSide = isLiteralOf(right, 'object')
155
+ ? left
156
+ : isLiteralOf(left, 'object')
157
+ ? right
158
+ : undefined;
159
+ if (typeofSide?.type !== AST_NODE_TYPES.UnaryExpression ||
160
+ typeofSide.operator !== 'typeof') {
161
+ return undefined;
162
+ }
163
+ const argument = skipTypeWrappers(typeofSide.argument);
164
+ return argument.type === AST_NODE_TYPES.Identifier
165
+ ? argument.name
166
+ : undefined;
167
+ };
168
+
169
+
170
+
171
+
172
+
173
+ const getNonNullCheckIdentifierName = (
174
+
175
+ node) => {
176
+ if (node.type !== AST_NODE_TYPES.BinaryExpression ||
177
+ (node.operator !== '!==' && node.operator !== '!=')) {
178
+ return undefined;
179
+ }
180
+ const left = skipTypeWrappers(node.left);
181
+ const right = skipTypeWrappers(node.right);
182
+ const checked = isLiteralOf(right, null)
183
+ ? left
184
+ : isLiteralOf(left, null)
185
+ ? right
186
+ : undefined;
187
+ return checked?.type === AST_NODE_TYPES.Identifier ? checked.name : undefined;
188
+ };
189
+ const isLiteralOf = (
190
+
191
+ node, value) => node.type === AST_NODE_TYPES.Literal && node.value === value;
192
+ //# sourceMappingURL=prefer-is-non-null-object.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-is-non-null-object.mjs","sourceRoot":"","sources":["../../src/rules/prefer-is-non-null-object.mts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ /**
3
+ * Builds a rule that replaces `Object.hasOwn(obj, key)` and `key in obj` with
4
+ * `isRecord(obj) && hasKey(obj, key)` imported from `moduleName`, or with
5
+ * `hasKey(obj, key)` alone when `obj` is already a record.
6
+ */
7
+ export declare const createPreferIsRecordAndHasKey: ({ moduleName, }: Readonly<{
8
+ /** The module that exports `isRecord` and `hasKey`. */
9
+ moduleName: string;
10
+ }>) => TSESLint.RuleModule<'useIsRecordAndHasKey' | 'useHasKey', readonly []>;
11
+ //# sourceMappingURL=prefer-is-record-and-has-key.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-is-record-and-has-key.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-is-record-and-has-key.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAclC;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,oBAEvC,QAAQ,CAAC;IACV,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,KAAG,QAAQ,CAAC,UAAU,CAAC,sBAAsB,GAAG,WAAW,EAAE,SAAS,EAAE,CAqN7D,CAAC"}