eslint-plugin-ts-std-forge 0.1.10 → 0.1.12

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 (40) hide show
  1. package/dist/plugin.d.mts +3 -3
  2. package/dist/rules/prefer-is-non-null-object.d.mts +1 -5
  3. package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -1
  4. package/dist/rules/prefer-is-non-null-object.mjs +4 -184
  5. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
  6. package/dist/rules/prefer-is-record-and-has-key.d.mts +1 -5
  7. package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -1
  8. package/dist/rules/prefer-is-record-and-has-key.mjs +4 -219
  9. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
  10. package/dist/rules/prefer-safe-array-is-array.d.mts +1 -5
  11. package/dist/rules/prefer-safe-array-is-array.d.mts.map +1 -1
  12. package/dist/rules/prefer-safe-array-is-array.mjs +5 -78
  13. package/dist/rules/prefer-safe-array-is-array.mjs.map +1 -1
  14. package/dist/rules/prefer-safe-array-length-guard.mjs +1 -2
  15. package/dist/rules/prefer-safe-array-length-guard.mjs.map +1 -1
  16. package/dist/rules/prefer-safe-number-parse-integer.d.mts +1 -12
  17. package/dist/rules/prefer-safe-number-parse-integer.d.mts.map +1 -1
  18. package/dist/rules/prefer-safe-number-parse-integer.mjs +6 -108
  19. package/dist/rules/prefer-safe-number-parse-integer.mjs.map +1 -1
  20. package/dist/rules/prefer-safe-number-parse.d.mts +1 -12
  21. package/dist/rules/prefer-safe-number-parse.d.mts.map +1 -1
  22. package/dist/rules/prefer-safe-number-parse.mjs +6 -103
  23. package/dist/rules/prefer-safe-number-parse.mjs.map +1 -1
  24. package/dist/rules/rules.d.mts +3 -3
  25. package/package.json +6 -6
  26. package/src/rules/prefer-is-non-null-object.mts +4 -274
  27. package/src/rules/prefer-is-record-and-has-key.mts +4 -322
  28. package/src/rules/prefer-is-record-and-has-key.test.mts +48 -0
  29. package/src/rules/prefer-safe-array-is-array.mts +5 -119
  30. package/src/rules/prefer-safe-array-is-array.test.mts +18 -14
  31. package/src/rules/prefer-safe-array-length-guard.mts +1 -1
  32. package/src/rules/prefer-safe-number-parse-integer.mts +6 -172
  33. package/src/rules/prefer-safe-number-parse-integer.test.mts +22 -8
  34. package/src/rules/prefer-safe-number-parse.mts +6 -163
  35. package/src/rules/prefer-safe-number-parse.test.mts +30 -9
  36. package/dist/rules/ast-utils.d.mts +0 -18
  37. package/dist/rules/ast-utils.d.mts.map +0 -1
  38. package/dist/rules/ast-utils.mjs +0 -39
  39. package/dist/rules/ast-utils.mjs.map +0 -1
  40. package/src/rules/ast-utils.mts +0 -57
@@ -1,110 +1,8 @@
1
- import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
- import { buildNamedImportFix, getArgumentText, getImportDeclarations, getNamedImports, skipTypeWrappers, } from 'eslint-rule-forge';
3
- import * as ts from 'typescript';
4
- import { isIdentifierNamed, typeHoldsAtEveryLayer } from './ast-utils.mjs';
1
+ import { createPreferSafeParseInt } from 'eslint-rule-forge';
5
2
  import { TS_STD_FORGE_MODULE } from './constants.mjs';
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
- const isStringType = (type) => {
14
- const parts = type.isUnion() ? type.types : [type];
15
- return (parts.length > 0 &&
16
- parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0));
17
- };
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
- export const preferSafeNumberParseInteger = {
26
- meta: {
27
- type: 'suggestion',
28
- docs: {
29
- description: 'Replace `parseInt(x)` or `Number.parseInt(x, 10)` with `Result.unwrapOkOr(SafeNumber.parseInteger(x), Number.NaN)` from ts-std-forge.',
30
- },
31
- fixable: 'code',
32
- schema: [],
33
- messages: {
34
- useSafeNumberParseInteger: 'Replace `{{original}}` with `Result.unwrapOkOr(SafeNumber.parseInteger({{argName}}), Number.NaN)` from ts-std-forge.',
35
- },
36
- },
37
- create: (context) => {
38
- const sourceCode = context.sourceCode;
39
- const program = sourceCode.ast;
40
- const tsStdForgeImports = getImportDeclarations(program, TS_STD_FORGE_MODULE);
41
- const mut_nodesToFix = [];
42
- return {
43
- CallExpression: (node) => {
44
- const callee = skipTypeWrappers(node.callee);
45
-
46
- const isGlobalParseInt = callee.type === AST_NODE_TYPES.Identifier &&
47
- callee.name === 'parseInt';
48
- const isNumberParseInt = callee.type === AST_NODE_TYPES.MemberExpression &&
49
- !callee.computed &&
50
- isIdentifierNamed(callee.object, 'Number') &&
51
- callee.property.type === AST_NODE_TYPES.Identifier &&
52
- callee.property.name === 'parseInt';
53
- if (!isGlobalParseInt && !isNumberParseInt) {
54
- return;
55
- }
56
- const args = node.arguments;
57
- const firstArg = args[0];
58
- if (firstArg === undefined) {
59
- return;
60
- }
61
-
62
- if (firstArg.type === AST_NODE_TYPES.SpreadElement) {
63
- return;
64
- }
65
-
66
-
67
- if (args.length >= 2) {
68
- const radix = args[1] === undefined ? undefined : skipTypeWrappers(args[1]);
69
- if (radix?.type !== AST_NODE_TYPES.Literal || radix.value !== 10) {
70
- return;
71
- }
72
- }
73
-
74
-
75
-
76
-
77
-
78
-
79
- if (!typeHoldsAtEveryLayer(sourceCode, firstArg, isStringType)) {
80
- return;
81
- }
82
- mut_nodesToFix.push({ node, argExpression: firstArg });
83
- },
84
- 'Program:exit': () => {
85
- const namedImports = getNamedImports(tsStdForgeImports);
86
- const missingImports = ['Result', 'SafeNumber'].filter((name) => !namedImports.includes(name));
87
- for (const [index, { node, argExpression },] of mut_nodesToFix.entries()) {
88
- const argText = getArgumentText(sourceCode, argExpression);
89
- context.report({
90
- node,
91
- messageId: 'useSafeNumberParseInteger',
92
- data: {
93
- original: sourceCode.getText(node),
94
- argName: argText,
95
- },
96
- fix: (fixer) => {
97
- const core = `Result.unwrapOkOr(SafeNumber.parseInteger(${argText}), Number.NaN)`;
98
- const importFixes = index === 0 && missingImports.length > 0
99
- ? buildNamedImportFix(fixer, program, TS_STD_FORGE_MODULE, missingImports)
100
- : [];
101
- return [...importFixes, fixer.replaceText(node, core)];
102
- },
103
- });
104
- }
105
- },
106
- };
107
- },
108
- defaultOptions: [],
109
- };
3
+ export const preferSafeNumberParseInteger = createPreferSafeParseInt({
4
+ moduleName: TS_STD_FORGE_MODULE,
5
+ namespace: 'SafeNumber',
6
+ method: 'parseInteger',
7
+ });
110
8
  //# sourceMappingURL=prefer-safe-number-parse-integer.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-safe-number-parse-integer.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse-integer.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-safe-number-parse-integer.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse-integer.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,CAAC,MAAM,4BAA4B,GAAG,wBAAwB,CAAC;IACnE,UAAU,EAAE,mBAAmB;IAC/B,SAAS,EAAE,YAAY;IACvB,MAAM,EAAE,cAAc;CACvB,CAAC,CAAC"}
@@ -1,19 +1,8 @@
1
- import { type TSESLint } from '@typescript-eslint/utils';
2
- type Options = readonly [];
3
- type MessageIds = 'useSafeNumberParse';
4
1
  /**
5
- * Detects `parseFloat(x)`, `Number.parseFloat(x)`, and `Number(x)` (when x
6
- * is typed as `string`) and replaces them with
7
- * `Result.unwrapOkOr(SafeNumber.parse(x), Number.NaN)` (ts-std-forge).
8
- *
9
- * `Number(x)` is only flagged when x is purely a string type to avoid false
10
- * positives for `Number(someBoolean)` or `Number(someNumber)` uses.
11
- *
12
2
  * The ts-data-forge rule of the same shape names `Num.safeParseFloat`, which
13
3
  * has delegated to `SafeNumber.parse` since the D-49 inversion and wraps the
14
4
  * result in a `FiniteNumber` brand. This one names the implementation, for
15
5
  * callers who do not want the brand or the extra dependency.
16
6
  */
17
- export declare const preferSafeNumberParse: TSESLint.RuleModule<MessageIds, Options>;
18
- export {};
7
+ export declare const preferSafeNumberParse: import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
19
8
  //# sourceMappingURL=prefer-safe-number-parse.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-safe-number-parse.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAYlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,oBAAoB,CAAC;AAkBvC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CAyHjE,CAAC"}
1
+ {"version":3,"file":"prefer-safe-number-parse.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse.mts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,+JAIhC,CAAC"}
@@ -1,7 +1,4 @@
1
- import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
- import { buildNamedImportFix, getArgumentText, getImportDeclarations, getNamedImports, skipTypeWrappers, } from 'eslint-rule-forge';
3
- import * as ts from 'typescript';
4
- import { isIdentifierNamed, typeHoldsAtEveryLayer } from './ast-utils.mjs';
1
+ import { createPreferSafeParseFloat } from 'eslint-rule-forge';
5
2
  import { TS_STD_FORGE_MODULE } from './constants.mjs';
6
3
 
7
4
 
@@ -9,103 +6,9 @@ import { TS_STD_FORGE_MODULE } from './constants.mjs';
9
6
 
10
7
 
11
8
 
12
-
13
- const isStringType = (type) => {
14
- const parts = type.isUnion() ? type.types : [type];
15
- return (parts.length > 0 &&
16
- parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0));
17
- };
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
- export const preferSafeNumberParse = {
32
- meta: {
33
- type: 'suggestion',
34
- docs: {
35
- description: 'Replace `parseFloat(x)`, `Number.parseFloat(x)`, or `Number(x)` (when x is a string) with `Result.unwrapOkOr(SafeNumber.parse(x), Number.NaN)` from ts-std-forge.',
36
- },
37
- fixable: 'code',
38
- schema: [],
39
- messages: {
40
- useSafeNumberParse: 'Replace `{{original}}` with `Result.unwrapOkOr(SafeNumber.parse({{argName}}), Number.NaN)` from ts-std-forge.',
41
- },
42
- },
43
- create: (context) => {
44
- const sourceCode = context.sourceCode;
45
- const program = sourceCode.ast;
46
- const tsStdForgeImports = getImportDeclarations(program, TS_STD_FORGE_MODULE);
47
- const mut_nodesToFix = [];
48
- return {
49
- CallExpression: (node) => {
50
- const callee = skipTypeWrappers(node.callee);
51
-
52
- const isGlobalParseFloat = callee.type === AST_NODE_TYPES.Identifier &&
53
- callee.name === 'parseFloat';
54
-
55
- const isNumberParseFloat = callee.type === AST_NODE_TYPES.MemberExpression &&
56
- !callee.computed &&
57
- isIdentifierNamed(callee.object, 'Number') &&
58
- callee.property.type === AST_NODE_TYPES.Identifier &&
59
- callee.property.name === 'parseFloat';
60
-
61
- const isNumberCall = callee.type === AST_NODE_TYPES.Identifier && callee.name === 'Number';
62
- if (!isGlobalParseFloat && !isNumberParseFloat && !isNumberCall) {
63
- return;
64
- }
65
- const args = node.arguments;
66
- const firstArg = args[0];
67
- if (firstArg === undefined) {
68
- return;
69
- }
70
-
71
- if (firstArg.type === AST_NODE_TYPES.SpreadElement) {
72
- return;
73
- }
74
-
75
-
76
-
77
-
78
-
79
-
80
- if (!typeHoldsAtEveryLayer(sourceCode, firstArg, isStringType)) {
81
- return;
82
- }
83
- mut_nodesToFix.push({ node, argExpression: firstArg });
84
- },
85
- 'Program:exit': () => {
86
- const namedImports = getNamedImports(tsStdForgeImports);
87
- const missingImports = ['Result', 'SafeNumber'].filter((name) => !namedImports.includes(name));
88
- for (const [index, { node, argExpression },] of mut_nodesToFix.entries()) {
89
- const argText = getArgumentText(sourceCode, argExpression);
90
- context.report({
91
- node,
92
- messageId: 'useSafeNumberParse',
93
- data: {
94
- original: sourceCode.getText(node),
95
- argName: argText,
96
- },
97
- fix: (fixer) => {
98
- const core = `Result.unwrapOkOr(SafeNumber.parse(${argText}), Number.NaN)`;
99
- const importFixes = index === 0 && missingImports.length > 0
100
- ? buildNamedImportFix(fixer, program, TS_STD_FORGE_MODULE, missingImports)
101
- : [];
102
- return [...importFixes, fixer.replaceText(node, core)];
103
- },
104
- });
105
- }
106
- },
107
- };
108
- },
109
- defaultOptions: [],
110
- };
9
+ export const preferSafeNumberParse = createPreferSafeParseFloat({
10
+ moduleName: TS_STD_FORGE_MODULE,
11
+ namespace: 'SafeNumber',
12
+ method: 'parse',
13
+ });
111
14
  //# sourceMappingURL=prefer-safe-number-parse.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-safe-number-parse.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-safe-number-parse.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;IAC9D,UAAU,EAAE,mBAAmB;IAC/B,SAAS,EAAE,YAAY;IACvB,MAAM,EAAE,OAAO;CAChB,CAAC,CAAC"}
@@ -1,9 +1,9 @@
1
1
  export declare const tsStdForgeRules: {
2
2
  readonly 'prefer-is-non-null-object': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNonNullObject", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
3
3
  readonly 'prefer-is-record-and-has-key': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useHasKey" | "useIsRecordAndHasKey", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
4
- readonly 'prefer-safe-array-is-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeArrayIsArray", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
4
+ readonly 'prefer-safe-array-is-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsArray", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
5
5
  readonly 'prefer-safe-array-length-guard': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsNonEmpty", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
6
- readonly 'prefer-safe-number-parse': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeNumberParse", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
7
- readonly 'prefer-safe-number-parse-integer': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeNumberParseInteger", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
6
+ readonly 'prefer-safe-number-parse': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
7
+ readonly 'prefer-safe-number-parse-integer': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseInt", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
8
8
  };
9
9
  //# sourceMappingURL=rules.d.mts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-ts-std-forge",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "private": false,
5
5
  "description": "ESLint rules that steer code toward ts-std-forge idioms.",
6
6
  "keywords": [
@@ -35,17 +35,17 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@typescript-eslint/utils": "^8.71.1",
38
- "eslint-rule-forge": "^0.2.0",
39
- "ts-std-forge": "0.5.1",
38
+ "eslint-rule-forge": "^0.3.0",
39
+ "ts-std-forge": "0.5.2",
40
40
  "ts-type-forge": "~9.2.2"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@typescript-eslint/parser": "8.71.1",
44
44
  "@typescript-eslint/rule-tester": "8.71.1",
45
45
  "dedent": "1.7.2",
46
- "eslint-config-typed": "5.19.0",
47
- "eslint-plugin-ts-fortress": "1.3.4",
48
- "eslint-plugin-ts-type-forge": "0.8.9",
46
+ "eslint-config-typed": "5.19.1",
47
+ "eslint-plugin-ts-fortress": "1.3.6",
48
+ "eslint-plugin-ts-type-forge": "0.8.11",
49
49
  "prettier": "3.9.9",
50
50
  "prettier-plugin-organize-imports": "4.3.0",
51
51
  "prettier-plugin-packagejson": "3.0.2",
@@ -1,276 +1,6 @@
1
- import {
2
- AST_NODE_TYPES,
3
- type TSESLint,
4
- type TSESTree,
5
- } from '@typescript-eslint/utils';
6
- import {
7
- buildNamedImportFix,
8
- getImportDeclarations,
9
- getNamedImports,
10
- getRangeWithParens,
11
- parenthesesDepth,
12
- skipTypeWrappers,
13
- } from 'eslint-rule-forge';
1
+ import { createPreferIsNonNullObject } from 'eslint-rule-forge';
14
2
  import { TS_STD_FORGE_MODULE } from './constants.mjs';
15
3
 
16
- type Options = readonly [];
17
-
18
- type MessageIds = 'useIsNonNullObject';
19
-
20
- export const preferIsNonNullObject: TSESLint.RuleModule<MessageIds, Options> = {
21
- meta: {
22
- type: 'suggestion',
23
- docs: {
24
- description:
25
- 'Replace `typeof u === "object" && u !== null`, in either order and anywhere in an `&&` chain, with `isNonNullObject(u)` from ts-std-forge.',
26
- },
27
- fixable: 'code',
28
- schema: [],
29
- messages: {
30
- useIsNonNullObject:
31
- 'Replace the object/null check with `isNonNullObject()` from ts-std-forge.',
32
- },
33
- },
34
-
35
- create: (context) => {
36
- const sourceCode = context.sourceCode;
37
-
38
- const program = sourceCode.ast;
39
-
40
- const tsStdForgeImports = getImportDeclarations(
41
- program,
42
- TS_STD_FORGE_MODULE,
43
- );
44
-
45
- const mut_pairsToFix: NonNullObjectPair[] = [];
46
-
47
- return {
48
- LogicalExpression: (node) => {
49
- // A chain is read once, from its outermost `&&`.
50
- if (node.operator !== '&&' || isChainOperand(sourceCode, node)) {
51
- return;
52
- }
53
-
54
- mut_pairsToFix.push(...findNonNullObjectPairs(sourceCode, node));
55
- },
56
- 'Program:exit': () => {
57
- const hasIsNonNullObjectImport =
58
- getNamedImports(tsStdForgeImports).includes('isNonNullObject');
59
-
60
- // The import fix lives on the first report only:
61
- // `insertTextBefore(program, …)` produces overlapping ranges
62
- // otherwise and only one of them would survive a pass. A first report
63
- // silenced by an `eslint-disable` comment therefore takes the import
64
- // with it.
65
- for (const [
66
- index,
67
- { range, identifierName },
68
- ] of mut_pairsToFix.entries()) {
69
- context.report({
70
- loc: {
71
- start: sourceCode.getLocFromIndex(range[0]),
72
- end: sourceCode.getLocFromIndex(range[1]),
73
- },
74
- messageId: 'useIsNonNullObject',
75
- fix: (fixer) => {
76
- const replacement = `isNonNullObject(${identifierName})`;
77
-
78
- const importFixes =
79
- index === 0 && !hasIsNonNullObjectImport
80
- ? buildNamedImportFix(fixer, program, TS_STD_FORGE_MODULE, [
81
- 'isNonNullObject',
82
- ])
83
- : [];
84
-
85
- return [
86
- ...importFixes,
87
- fixer.replaceTextRange(range, replacement),
88
- ];
89
- },
90
- });
91
- }
92
- },
93
- };
94
- },
95
- defaultOptions: [],
96
- } as const;
97
-
98
- /**
99
- * A `typeof u === 'object'` and a `u !== null` next to each other in an `&&`
100
- * chain, as the source range from the first to the second (parentheses
101
- * included) and the name of `u`.
102
- */
103
- type NonNullObjectPair = Readonly<{
104
- range: TSESTree.Range;
105
- identifierName: string;
106
- }>;
107
-
108
- /**
109
- * Whether `node` is an operand of an enclosing `&&` chain: a `&&` written
110
- * directly under another one, without parentheses.
111
- */
112
- const isChainOperand = (
113
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
114
- sourceCode: TSESLint.SourceCode,
115
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
116
- node: TSESTree.LogicalExpression,
117
- ): boolean =>
118
- node.parent.type === AST_NODE_TYPES.LogicalExpression &&
119
- node.parent.operator === '&&' &&
120
- parenthesesDepth(sourceCode, node) === 0;
121
-
122
- /**
123
- * Every non-overlapping pair in the `&&` chain `node` heads, left to right. The
124
- * two halves may come in either order, and the pair may have operands before
125
- * and after it: `a && u !== null && typeof u === 'object' && b`. They have to
126
- * be next to each other, so that the pair is one range of the source.
127
- */
128
- const findNonNullObjectPairs = (
129
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
130
- sourceCode: TSESLint.SourceCode,
131
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
132
- node: TSESTree.LogicalExpression,
133
- ): readonly NonNullObjectPair[] => {
134
- const operands = chainOperands(sourceCode, node);
135
-
136
- const pairsFrom = (index: number): readonly NonNullObjectPair[] => {
137
- const first = operands[index];
138
-
139
- const second = operands[index + 1];
140
-
141
- if (first === undefined || second === undefined) {
142
- return [];
143
- }
144
-
145
- const identifierName =
146
- pairIdentifierName(first, second) ?? pairIdentifierName(second, first);
147
-
148
- return identifierName === undefined
149
- ? pairsFrom(index + 1)
150
- : [
151
- {
152
- range: [
153
- getRangeWithParens(sourceCode, first)[0],
154
- getRangeWithParens(sourceCode, second)[1],
155
- ],
156
- identifierName,
157
- },
158
- ...pairsFrom(index + 2),
159
- ];
160
- };
161
-
162
- return pairsFrom(0);
163
- };
164
-
165
- /**
166
- * The operands of the `&&` chain `node`, left to right. A parenthesized `&&`
167
- * is one operand: its halves cannot be replaced without its parentheses.
168
- */
169
- const chainOperands = (
170
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
171
- sourceCode: TSESLint.SourceCode,
172
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
173
- node: TSESTree.LogicalExpression,
174
- ): readonly TSESTree.Expression[] =>
175
- [node.left, node.right].flatMap((operand) =>
176
- operand.type === AST_NODE_TYPES.LogicalExpression &&
177
- operand.operator === '&&' &&
178
- parenthesesDepth(sourceCode, operand) === 0
179
- ? chainOperands(sourceCode, operand)
180
- : [operand],
181
- );
182
-
183
- // Every operand below is read through its type wrappers: `typeof (u satisfies
184
- // unknown) === 'object' && u! !== null` is the same check.
185
-
186
- /**
187
- * The name of `u` when `typeofCheck` is `typeof u === 'object'` and
188
- * `nullCheck` is `u !== null` for the same `u`.
189
- */
190
- const pairIdentifierName = (
191
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
192
- typeofCheck: TSESTree.Expression,
193
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
194
- nullCheck: TSESTree.Expression,
195
- ): string | undefined => {
196
- const identifierName = getTypeofObjectIdentifierName(
197
- skipTypeWrappers(typeofCheck),
198
- );
199
-
200
- return identifierName !== undefined &&
201
- identifierName ===
202
- getNonNullCheckIdentifierName(skipTypeWrappers(nullCheck))
203
- ? identifierName
204
- : undefined;
205
- };
206
-
207
- /** `u` of `typeof u === 'object'`, in either operand order. */
208
- const getTypeofObjectIdentifierName = (
209
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
210
- node: TSESTree.Node,
211
- ): string | undefined => {
212
- if (
213
- node.type !== AST_NODE_TYPES.BinaryExpression ||
214
- node.operator !== '==='
215
- ) {
216
- return undefined;
217
- }
218
-
219
- const left = skipTypeWrappers(node.left);
220
-
221
- const right = skipTypeWrappers(node.right);
222
-
223
- const typeofSide = isLiteralOf(right, 'object')
224
- ? left
225
- : isLiteralOf(left, 'object')
226
- ? right
227
- : undefined;
228
-
229
- if (
230
- typeofSide?.type !== AST_NODE_TYPES.UnaryExpression ||
231
- typeofSide.operator !== 'typeof'
232
- ) {
233
- return undefined;
234
- }
235
-
236
- const argument = skipTypeWrappers(typeofSide.argument);
237
-
238
- return argument.type === AST_NODE_TYPES.Identifier
239
- ? argument.name
240
- : undefined;
241
- };
242
-
243
- /**
244
- * `u` of `u !== null`, in either operand order. `u != null` also excludes
245
- * `undefined`, which `typeof u === 'object'` has already excluded, so it is
246
- * the same check here.
247
- */
248
- const getNonNullCheckIdentifierName = (
249
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
250
- node: TSESTree.Node,
251
- ): string | undefined => {
252
- if (
253
- node.type !== AST_NODE_TYPES.BinaryExpression ||
254
- (node.operator !== '!==' && node.operator !== '!=')
255
- ) {
256
- return undefined;
257
- }
258
-
259
- const left = skipTypeWrappers(node.left);
260
-
261
- const right = skipTypeWrappers(node.right);
262
-
263
- const checked = isLiteralOf(right, null)
264
- ? left
265
- : isLiteralOf(left, null)
266
- ? right
267
- : undefined;
268
-
269
- return checked?.type === AST_NODE_TYPES.Identifier ? checked.name : undefined;
270
- };
271
-
272
- const isLiteralOf = (
273
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
274
- node: TSESTree.Node,
275
- value: string | null,
276
- ): boolean => node.type === AST_NODE_TYPES.Literal && node.value === value;
4
+ export const preferIsNonNullObject = createPreferIsNonNullObject({
5
+ moduleName: TS_STD_FORGE_MODULE,
6
+ });