eslint-plugin-ts-std-forge 0.1.4 → 0.1.6

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 (54) hide show
  1. package/dist/index.d.mts +1 -1
  2. package/dist/index.d.mts.map +1 -1
  3. package/dist/plugin.d.mts +1 -1
  4. package/dist/plugin.d.mts.map +1 -1
  5. package/dist/plugin.mjs +1 -0
  6. package/dist/plugin.mjs.map +1 -1
  7. package/dist/rule-types.d.mts +1 -1
  8. package/dist/rule-types.d.mts.map +1 -1
  9. package/dist/rules/ast-utils.d.mts +41 -0
  10. package/dist/rules/ast-utils.d.mts.map +1 -0
  11. package/dist/rules/ast-utils.mjs +101 -0
  12. package/dist/rules/ast-utils.mjs.map +1 -0
  13. package/dist/rules/import-utils.d.mts +1 -1
  14. package/dist/rules/import-utils.d.mts.map +1 -1
  15. package/dist/rules/prefer-is-non-null-object.mjs +26 -12
  16. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
  17. package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -1
  18. package/dist/rules/prefer-is-record-and-has-key.mjs +12 -12
  19. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
  20. package/dist/rules/prefer-safe-array-is-array.d.mts.map +1 -1
  21. package/dist/rules/prefer-safe-array-is-array.mjs +15 -13
  22. package/dist/rules/prefer-safe-array-is-array.mjs.map +1 -1
  23. package/dist/rules/prefer-safe-array-length-guard.d.mts.map +1 -1
  24. package/dist/rules/prefer-safe-array-length-guard.mjs +11 -13
  25. package/dist/rules/prefer-safe-array-length-guard.mjs.map +1 -1
  26. package/dist/rules/prefer-safe-number-parse-integer.d.mts.map +1 -1
  27. package/dist/rules/prefer-safe-number-parse-integer.mjs +9 -16
  28. package/dist/rules/prefer-safe-number-parse-integer.mjs.map +1 -1
  29. package/dist/rules/prefer-safe-number-parse.d.mts.map +1 -1
  30. package/dist/rules/prefer-safe-number-parse.mjs +7 -15
  31. package/dist/rules/prefer-safe-number-parse.mjs.map +1 -1
  32. package/dist/types.d.mts +2 -2
  33. package/dist/types.d.mts.map +1 -1
  34. package/package.json +7 -7
  35. package/src/index.mts +1 -1
  36. package/src/plugin.mts +5 -4
  37. package/src/plugin.test.mts +0 -13
  38. package/src/rule-types.mts +2 -2
  39. package/src/rules/ast-utils.mts +147 -0
  40. package/src/rules/import-utils.mts +1 -1
  41. package/src/rules/prefer-is-non-null-object.mts +34 -13
  42. package/src/rules/prefer-is-non-null-object.test.mts +54 -0
  43. package/src/rules/prefer-is-record-and-has-key.mts +19 -16
  44. package/src/rules/prefer-is-record-and-has-key.test.mts +128 -0
  45. package/src/rules/prefer-safe-array-is-array.mts +22 -18
  46. package/src/rules/prefer-safe-array-is-array.test.mts +93 -0
  47. package/src/rules/prefer-safe-array-length-guard.mts +20 -19
  48. package/src/rules/prefer-safe-array-length-guard.test.mts +98 -0
  49. package/src/rules/prefer-safe-number-parse-integer.mts +18 -24
  50. package/src/rules/prefer-safe-number-parse-integer.test.mts +104 -0
  51. package/src/rules/prefer-safe-number-parse.mts +14 -22
  52. package/src/rules/prefer-safe-number-parse.test.mts +97 -0
  53. package/src/rules/rules.mts +1 -1
  54. package/src/types.mts +2 -2
@@ -1,5 +1,6 @@
1
1
  import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
2
  import * as ts from 'typescript';
3
+ import { getArgumentText, getValueType, isIdentifierNamed, skipTypeWrappers, } from './ast-utils.mjs';
3
4
  import { buildImportFixes, getNamedImports, getTsStdForgeImport, } from './import-utils.mjs';
4
5
 
5
6
 
@@ -36,18 +37,16 @@ export const preferSafeNumberParseInteger = {
36
37
  const sourceCode = context.sourceCode;
37
38
  const program = sourceCode.ast;
38
39
  const tsStdForgeImport = getTsStdForgeImport(program);
39
- const services = sourceCode.parserServices;
40
40
  const mut_nodesToFix = [];
41
41
  return {
42
42
  CallExpression: (node) => {
43
- const { callee } = node;
43
+ const callee = skipTypeWrappers(node.callee);
44
44
 
45
45
  const isGlobalParseInt = callee.type === AST_NODE_TYPES.Identifier &&
46
46
  callee.name === 'parseInt';
47
47
  const isNumberParseInt = callee.type === AST_NODE_TYPES.MemberExpression &&
48
48
  !callee.computed &&
49
- callee.object.type === AST_NODE_TYPES.Identifier &&
50
- callee.object.name === 'Number' &&
49
+ isIdentifierNamed(callee.object, 'Number') &&
51
50
  callee.property.type === AST_NODE_TYPES.Identifier &&
52
51
  callee.property.name === 'parseInt';
53
52
  if (!isGlobalParseInt && !isNumberParseInt) {
@@ -63,8 +62,9 @@ export const preferSafeNumberParseInteger = {
63
62
  return;
64
63
  }
65
64
 
65
+
66
66
  if (args.length >= 2) {
67
- const radix = args[1];
67
+ const radix = args[1] === undefined ? undefined : skipTypeWrappers(args[1]);
68
68
  if (radix?.type !== AST_NODE_TYPES.Literal || radix.value !== 10) {
69
69
  return;
70
70
  }
@@ -72,16 +72,9 @@ export const preferSafeNumberParseInteger = {
72
72
 
73
73
 
74
74
 
75
- if (services?.program == null) {
76
- return;
77
- }
78
- const checker = services.program.getTypeChecker();
79
- const tsNode = services.esTreeNodeToTSNodeMap?.get(firstArg);
80
- if (tsNode === undefined) {
81
- return;
82
- }
83
- const argType = checker.getTypeAtLocation(tsNode);
84
- if (!isStringType(argType)) {
75
+
76
+ const argType = getValueType(sourceCode, firstArg);
77
+ if (argType === undefined || !isStringType(argType)) {
85
78
  return;
86
79
  }
87
80
  mut_nodesToFix.push({ node, argExpression: firstArg });
@@ -90,7 +83,7 @@ export const preferSafeNumberParseInteger = {
90
83
  const namedImports = getNamedImports(tsStdForgeImport);
91
84
  const missingImports = ['Result', 'SafeNumber'].filter((name) => !namedImports.includes(name));
92
85
  for (const [index, { node, argExpression },] of mut_nodesToFix.entries()) {
93
- const argText = sourceCode.getText(argExpression);
86
+ const argText = getArgumentText(sourceCode, argExpression);
94
87
  context.report({
95
88
  node,
96
89
  messageId: 'useSafeNumberParseInteger',
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-safe-number-parse-integer.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse-integer.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAM5B;;;;;GAKG;AACH,8EAA8E;AAC9E,MAAM,YAAY,GAAG,CAAC,IAAa,EAAW,EAAE;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,CAAW,CAAC;IAE9D,OAAO,CACL,KAAK,CAAC,MAAM,GAAG,CAAC;QAChB,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAC9D,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAGrC;IACF,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EACT,uIAAuI;SAC1I;QACD,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR,yBAAyB,EACvB,sHAAsH;SACzH;KACF;IAED,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;QAClB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QAEtC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC;QAE/B,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAEtD,MAAM,QAAQ,GAAG,UAAU,CAAC,cAAc,CAAC;QAE3C,MAAM,cAAc,GAGd,EAAE,CAAC;QAET,OAAO;YACL,cAAc,EAAE,CAAC,IAAI,EAAE,EAAE;gBACvB,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;gBAExB,mDAAmD;gBACnD,MAAM,gBAAgB,GACpB,MAAM,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU;oBACzC,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC;gBAE7B,MAAM,gBAAgB,GACpB,MAAM,CAAC,IAAI,KAAK,cAAc,CAAC,gBAAgB;oBAC/C,CAAC,MAAM,CAAC,QAAQ;oBAChB,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU;oBAChD,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ;oBAC/B,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU;oBAClD,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,UAAU,CAAC;gBAEtC,IAAI,CAAC,gBAAgB,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBAC3C,OAAO;gBACT,CAAC;gBAED,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;gBAE5B,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBAEzB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC3B,OAAO;gBACT,CAAC;gBAED,oEAAoE;gBACpE,IAAI,QAAQ,CAAC,IAAI,KAAK,cAAc,CAAC,aAAa,EAAE,CAAC;oBACnD,OAAO;gBACT,CAAC;gBAED,oEAAoE;gBACpE,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;oBACrB,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;oBAEtB,IAAI,KAAK,EAAE,IAAI,KAAK,cAAc,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,KAAK,EAAE,EAAE,CAAC;wBACjE,OAAO;oBACT,CAAC;gBACH,CAAC;gBAED,sEAAsE;gBACtE,sEAAsE;gBACtE,QAAQ;gBACR,IAAI,QAAQ,EAAE,OAAO,IAAI,IAAI,EAAE,CAAC;oBAC9B,OAAO;gBACT,CAAC;gBAED,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;gBAElD,MAAM,MAAM,GAAG,QAAQ,CAAC,qBAAqB,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;gBAE7D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;oBACzB,OAAO;gBACT,CAAC;gBAED,MAAM,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;gBAElD,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC3B,OAAO;gBACT,CAAC;gBAED,cAAc,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAC;YACzD,CAAC;YACD,cAAc,EAAE,GAAG,EAAE;gBACnB,MAAM,YAAY,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAC;gBAEvD,MAAM,cAAc,GAAI,CAAC,QAAQ,EAAE,YAAY,CAAW,CAAC,MAAM,CAC/D,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CACvC,CAAC;gBAEF,KAAK,MAAM,CACT,KAAK,EACL,EAAE,IAAI,EAAE,aAAa,EAAE,EACxB,IAAI,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC;oBAC9B,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;oBAElD,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,SAAS,EAAE,2BAA2B;wBACtC,IAAI,EAAE;4BACJ,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC;4BAClC,OAAO,EAAE,OAAO;yBACjB;wBACD,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE;4BACb,MAAM,IAAI,GAAG,6CAA6C,OAAO,gBAAgB,CAAC;4BAElF,MAAM,WAAW,GACf,KAAK,KAAK,CAAC,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC;gCACtC,CAAC,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,cAAc,CAAC;gCAClD,CAAC,CAAC,EAAE,CAAC;4BAET,OAAO,CAAC,GAAG,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;wBACzD,CAAC;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;IACD,cAAc,EAAE,EAAE;CACV,CAAC"}
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 +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;AAQlC,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,CA+HjE,CAAC"}
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;AAclC,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,CAiHjE,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
2
  import * as ts from 'typescript';
3
+ import { getArgumentText, getValueType, isIdentifierNamed, skipTypeWrappers, } from './ast-utils.mjs';
3
4
  import { buildImportFixes, getNamedImports, getTsStdForgeImport, } from './import-utils.mjs';
4
5
 
5
6
 
@@ -42,19 +43,17 @@ export const preferSafeNumberParse = {
42
43
  const sourceCode = context.sourceCode;
43
44
  const program = sourceCode.ast;
44
45
  const tsStdForgeImport = getTsStdForgeImport(program);
45
- const services = sourceCode.parserServices;
46
46
  const mut_nodesToFix = [];
47
47
  return {
48
48
  CallExpression: (node) => {
49
- const { callee } = node;
49
+ const callee = skipTypeWrappers(node.callee);
50
50
 
51
51
  const isGlobalParseFloat = callee.type === AST_NODE_TYPES.Identifier &&
52
52
  callee.name === 'parseFloat';
53
53
 
54
54
  const isNumberParseFloat = callee.type === AST_NODE_TYPES.MemberExpression &&
55
55
  !callee.computed &&
56
- callee.object.type === AST_NODE_TYPES.Identifier &&
57
- callee.object.name === 'Number' &&
56
+ isIdentifierNamed(callee.object, 'Number') &&
58
57
  callee.property.type === AST_NODE_TYPES.Identifier &&
59
58
  callee.property.name === 'parseFloat';
60
59
 
@@ -74,16 +73,9 @@ export const preferSafeNumberParse = {
74
73
 
75
74
 
76
75
 
77
- if (services?.program == null) {
78
- return;
79
- }
80
- const checker = services.program.getTypeChecker();
81
- const tsNode = services.esTreeNodeToTSNodeMap?.get(firstArg);
82
- if (tsNode === undefined) {
83
- return;
84
- }
85
- const argType = checker.getTypeAtLocation(tsNode);
86
- if (!isStringType(argType)) {
76
+
77
+ const argType = getValueType(sourceCode, firstArg);
78
+ if (argType === undefined || !isStringType(argType)) {
87
79
  return;
88
80
  }
89
81
  mut_nodesToFix.push({ node, argExpression: firstArg });
@@ -92,7 +84,7 @@ export const preferSafeNumberParse = {
92
84
  const namedImports = getNamedImports(tsStdForgeImport);
93
85
  const missingImports = ['Result', 'SafeNumber'].filter((name) => !namedImports.includes(name));
94
86
  for (const [index, { node, argExpression },] of mut_nodesToFix.entries()) {
95
- const argText = sourceCode.getText(argExpression);
87
+ const argText = getArgumentText(sourceCode, argExpression);
96
88
  context.report({
97
89
  node,
98
90
  messageId: 'useSafeNumberParse',
@@ -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":"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"}
package/dist/types.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { type TSESLint } from '@typescript-eslint/utils';
2
- import { type DeepReadonly } from 'ts-type-forge';
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ import type { DeepReadonly } from 'ts-type-forge';
3
3
  export type ESLintPlugin = DeepReadonly<TSESLint.FlatConfig.Plugin>;
4
4
  export type ESLintFlatConfig = DeepReadonly<TSESLint.FlatConfig.Config>;
5
5
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../src/types.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAEpE,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../src/types.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAEpE,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-ts-std-forge",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "private": false,
5
5
  "description": "ESLint rules that steer code toward ts-std-forge idioms.",
6
6
  "keywords": [
@@ -34,17 +34,17 @@
34
34
  "LICENSE"
35
35
  ],
36
36
  "dependencies": {
37
- "@typescript-eslint/utils": "^8.70.1",
37
+ "@typescript-eslint/utils": "^8.71.0",
38
38
  "ts-std-forge": "0.5.1",
39
39
  "ts-type-forge": "~9.2.2"
40
40
  },
41
41
  "devDependencies": {
42
- "@typescript-eslint/parser": "8.70.1",
43
- "@typescript-eslint/rule-tester": "8.70.1",
42
+ "@typescript-eslint/parser": "8.71.0",
43
+ "@typescript-eslint/rule-tester": "8.71.0",
44
44
  "dedent": "1.7.2",
45
- "eslint-config-typed": "5.16.0",
46
- "eslint-plugin-ts-fortress": "1.2.4",
47
- "eslint-plugin-ts-type-forge": "0.8.5",
45
+ "eslint-config-typed": "5.17.1",
46
+ "eslint-plugin-ts-fortress": "1.3.1",
47
+ "eslint-plugin-ts-type-forge": "0.8.6",
48
48
  "prettier": "3.9.9",
49
49
  "prettier-plugin-organize-imports": "4.3.0",
50
50
  "prettier-plugin-packagejson": "3.0.2",
package/src/index.mts CHANGED
@@ -4,4 +4,4 @@ export type {
4
4
  EslintTsStdForgeRulesOption,
5
5
  } from './rule-types.mjs';
6
6
  export { tsStdForgeRules } from './rules/index.mjs';
7
- export { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';
7
+ export type { ESLintFlatConfig, ESLintPlugin } from './types.mjs';
package/src/plugin.mts CHANGED
@@ -1,12 +1,13 @@
1
- import { type ReadonlyRecord } from 'ts-type-forge';
1
+ import type { ReadonlyRecord } from 'ts-type-forge';
2
2
  import { tsStdForgeRules } from './rules/index.mjs';
3
- import { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';
3
+ import type { ESLintFlatConfig, ESLintPlugin } from './types.mjs';
4
4
 
5
5
  /**
6
6
  * Every rule this plugin ships, at `error`.
7
7
  *
8
- * The `satisfies` clause below is keyed off {@link tsStdForgeRules}, so adding
9
- * a rule without listing it here fails to type-check.
8
+ * The `satisfies` clause below is keyed off {@link tsStdForgeRules}, so leaving a
9
+ * rule out, naming one that does not exist, or setting one to anything
10
+ * but `error` fails to type-check. That is the whole check; no test repeats it.
10
11
  */
11
12
  const recommendedRules = {
12
13
  'ts-std-forge/prefer-is-non-null-object': 'error',
@@ -1,21 +1,8 @@
1
1
  import { eslintPluginTsStdForge } from './plugin.mjs';
2
- import { tsStdForgeRules } from './rules/index.mjs';
3
2
 
4
3
  describe('eslintPluginTsStdForge.configs.recommended', () => {
5
4
  const recommended = eslintPluginTsStdForge.configs.recommended;
6
5
 
7
- test('enables every rule of the plugin, and nothing else, at "error"', () => {
8
- assert.deepStrictEqual(
9
- recommended.rules,
10
- Object.fromEntries(
11
- Object.keys(tsStdForgeRules).map((name) => [
12
- `ts-std-forge/${name}`,
13
- 'error',
14
- ]),
15
- ),
16
- );
17
- });
18
-
19
6
  test('registers the exported plugin object itself', () => {
20
7
  // Registering a *copy* would make `Cannot redefine plugin` errors possible
21
8
  // for users who also list the plugin in their own `plugins` record.
@@ -3,9 +3,9 @@
3
3
  // This file is auto-generated by `pnpm run gen:rule-types`
4
4
  // (`scripts/cmd/gen-rule-types.mts`). Do not edit it manually.
5
5
 
6
- import { type Linter } from 'eslint';
6
+ import type { Linter } from 'eslint';
7
7
  import { expectType } from 'ts-std-forge';
8
- import { type tsStdForgeRules } from './rules/index.mjs';
8
+ import type { tsStdForgeRules } from './rules/index.mjs';
9
9
 
10
10
  /**
11
11
  * @description Replace `typeof u === "object" && u !== null` with `isNonNullObject(u)` from ts-std-forge.
@@ -0,0 +1,147 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ ASTUtils,
4
+ type TSESLint,
5
+ type TSESTree,
6
+ } from '@typescript-eslint/utils';
7
+ import type * as ts from 'typescript';
8
+
9
+ /**
10
+ * `node` with every `as`, `satisfies`, `!` and `<T>` around it taken off: the
11
+ * expression whose value it is. Judge syntax and ask types on what this
12
+ * returns, so that a wrapper neither hides a pattern (`xs.length > (0 as
13
+ * const)`) nor vouches for a type (`Number(b as unknown as string)`, whose
14
+ * value is still a boolean), and keep the original node for fix text. See
15
+ * `docs/writing-lint-rules.md` at the repository root.
16
+ *
17
+ * A copy of the helper in `eslint-config-typed`, which this package, published
18
+ * on its own, cannot import.
19
+ */
20
+ export const skipTypeWrappers = (
21
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
22
+ node: TSESTree.Node,
23
+ ): TSESTree.Node =>
24
+ isTypeWrapper(node) ? skipTypeWrappers(node.expression) : node;
25
+
26
+ export const isTypeWrapper = (
27
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
28
+ node: TSESTree.Node,
29
+ ): node is TypeWrapper => TYPE_WRAPPERS.has(node.type);
30
+
31
+ /**
32
+ * Whether `node`, read through its type wrappers, is the identifier `name`:
33
+ * `Number`, `(Number as NumberConstructor)` and `Number!` are the same object.
34
+ */
35
+ export const isIdentifierNamed = (
36
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
37
+ node: TSESTree.Node,
38
+ name: string,
39
+ ): boolean => {
40
+ const unwrapped = skipTypeWrappers(node);
41
+
42
+ return (
43
+ unwrapped.type === AST_NODE_TYPES.Identifier && unwrapped.name === name
44
+ );
45
+ };
46
+
47
+ export type TypeWrapper =
48
+ | TSESTree.TSAsExpression
49
+ | TSESTree.TSNonNullExpression
50
+ | TSESTree.TSSatisfiesExpression
51
+ | TSESTree.TSTypeAssertion;
52
+
53
+ /**
54
+ * The type of the value `node` evaluates to, asked of the expression inside
55
+ * its type wrappers rather than of the cast, or `undefined` without type
56
+ * information. A `!` among the wrappers still takes `null` and `undefined`
57
+ * out, since that much the source asserts about the value itself.
58
+ */
59
+ export const getValueType = (
60
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
61
+ sourceCode: TSESLint.SourceCode,
62
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
63
+ node: TSESTree.Node,
64
+ ): ts.Type | undefined => {
65
+ const services = sourceCode.parserServices;
66
+
67
+ const checker = services?.program?.getTypeChecker();
68
+
69
+ const tsNode = services?.esTreeNodeToTSNodeMap?.get(skipTypeWrappers(node));
70
+
71
+ if (checker === undefined || tsNode === undefined) {
72
+ return undefined;
73
+ }
74
+
75
+ const type = checker.getTypeAtLocation(tsNode);
76
+
77
+ return hasNonNullAssertion(node) ? checker.getNonNullableType(type) : type;
78
+ };
79
+
80
+ /**
81
+ * The source text of `node` to put in a call's argument list. `getText`
82
+ * leaves out the parentheses the source puts around a node, and a sequence
83
+ * `(a, b)` without them would be read as two arguments.
84
+ */
85
+ export const getArgumentText = (
86
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
87
+ sourceCode: TSESLint.SourceCode,
88
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
89
+ node: TSESTree.Node,
90
+ ): string => {
91
+ const text = sourceCode.getText(node);
92
+
93
+ return node.type === AST_NODE_TYPES.SequenceExpression ? `(${text})` : text;
94
+ };
95
+
96
+ /**
97
+ * The range of `node` together with the parentheses the source puts around it,
98
+ * which `node.range` leaves out: all of `((Array.isArray))` for the member
99
+ * expression inside.
100
+ */
101
+ export const getRangeWithParens = (
102
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
103
+ sourceCode: TSESLint.SourceCode,
104
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
105
+ node: TSESTree.Node,
106
+ ): TSESTree.Range => {
107
+ const depth = parenthesesDepth(sourceCode, node);
108
+
109
+ if (depth === 0) {
110
+ return node.range;
111
+ }
112
+
113
+ const opening = sourceCode.getTokenBefore(node, { skip: depth - 1 });
114
+
115
+ const closing = sourceCode.getTokenAfter(node, { skip: depth - 1 });
116
+
117
+ return opening === null || closing === null
118
+ ? node.range
119
+ : [opening.range[0], closing.range[1]];
120
+ };
121
+
122
+ /** How many pairs of parentheses the source puts around `node`. */
123
+ const parenthesesDepth = (
124
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
125
+ sourceCode: TSESLint.SourceCode,
126
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
127
+ node: TSESTree.Node,
128
+ depth: number = 0,
129
+ ): number =>
130
+ ASTUtils.isParenthesized(depth + 1, node, sourceCode)
131
+ ? parenthesesDepth(sourceCode, node, depth + 1)
132
+ : depth;
133
+
134
+ const hasNonNullAssertion = (
135
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
136
+ node: TSESTree.Node,
137
+ ): boolean =>
138
+ isTypeWrapper(node) &&
139
+ (node.type === AST_NODE_TYPES.TSNonNullExpression ||
140
+ hasNonNullAssertion(node.expression));
141
+
142
+ const TYPE_WRAPPERS: ReadonlySet<AST_NODE_TYPES> = new Set([
143
+ AST_NODE_TYPES.TSAsExpression,
144
+ AST_NODE_TYPES.TSNonNullExpression,
145
+ AST_NODE_TYPES.TSSatisfiesExpression,
146
+ AST_NODE_TYPES.TSTypeAssertion,
147
+ ]);
@@ -3,7 +3,7 @@ import {
3
3
  type TSESLint,
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
- import { type DeepReadonly } from 'ts-type-forge';
6
+ import type { DeepReadonly } from 'ts-type-forge';
7
7
  import { TS_STD_FORGE_MODULE } from './constants.mjs';
8
8
 
9
9
  /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
@@ -3,7 +3,7 @@ import {
3
3
  type TSESLint,
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
- import { type DeepReadonly } from 'ts-type-forge';
6
+ import { skipTypeWrappers } from './ast-utils.mjs';
7
7
  import {
8
8
  buildImportFixes,
9
9
  getNamedImports,
@@ -85,16 +85,24 @@ export const preferIsNonNullObject: TSESLint.RuleModule<MessageIds, Options> = {
85
85
  defaultOptions: [],
86
86
  } as const;
87
87
 
88
+ // Every operand below is read through its type wrappers: `typeof (u satisfies
89
+ // unknown) === 'object' && u! !== null` is the same check.
90
+
88
91
  const getNonNullObjectIdentifierName = (
89
- node: DeepReadonly<TSESTree.LogicalExpression>,
92
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
93
+ node: TSESTree.LogicalExpression,
90
94
  ): string | undefined => {
91
95
  if (node.operator !== '&&') {
92
96
  return undefined;
93
97
  }
94
98
 
95
- const leftIdentifierName = getTypeofObjectIdentifierName(node.left);
99
+ const leftIdentifierName = getTypeofObjectIdentifierName(
100
+ skipTypeWrappers(node.left),
101
+ );
96
102
 
97
- const rightIdentifierName = getNonNullCheckIdentifierName(node.right);
103
+ const rightIdentifierName = getNonNullCheckIdentifierName(
104
+ skipTypeWrappers(node.right),
105
+ );
98
106
 
99
107
  return leftIdentifierName === undefined ||
100
108
  rightIdentifierName === undefined ||
@@ -104,7 +112,8 @@ const getNonNullObjectIdentifierName = (
104
112
  };
105
113
 
106
114
  const getTypeofObjectIdentifierName = (
107
- node: DeepReadonly<TSESTree.Expression>,
115
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
116
+ node: TSESTree.Node,
108
117
  ): string | undefined => {
109
118
  if (node.type !== AST_NODE_TYPES.BinaryExpression) {
110
119
  return undefined;
@@ -114,19 +123,29 @@ const getTypeofObjectIdentifierName = (
114
123
  return undefined;
115
124
  }
116
125
 
117
- const { left, right } = node;
126
+ const left = skipTypeWrappers(node.left);
127
+
128
+ const right = skipTypeWrappers(node.right);
118
129
 
119
- return left.type !== AST_NODE_TYPES.UnaryExpression ||
130
+ if (
131
+ left.type !== AST_NODE_TYPES.UnaryExpression ||
120
132
  left.operator !== 'typeof' ||
121
133
  right.type !== AST_NODE_TYPES.Literal ||
122
- right.value !== 'object' ||
123
- left.argument.type !== AST_NODE_TYPES.Identifier
124
- ? undefined
125
- : left.argument.name;
134
+ right.value !== 'object'
135
+ ) {
136
+ return undefined;
137
+ }
138
+
139
+ const argument = skipTypeWrappers(left.argument);
140
+
141
+ return argument.type === AST_NODE_TYPES.Identifier
142
+ ? argument.name
143
+ : undefined;
126
144
  };
127
145
 
128
146
  const getNonNullCheckIdentifierName = (
129
- node: DeepReadonly<TSESTree.Expression>,
147
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
148
+ node: TSESTree.Node,
130
149
  ): string | undefined => {
131
150
  if (node.type !== AST_NODE_TYPES.BinaryExpression) {
132
151
  return undefined;
@@ -136,7 +155,9 @@ const getNonNullCheckIdentifierName = (
136
155
  return undefined;
137
156
  }
138
157
 
139
- const { left, right } = node;
158
+ const left = skipTypeWrappers(node.left);
159
+
160
+ const right = skipTypeWrappers(node.right);
140
161
 
141
162
  return left.type !== AST_NODE_TYPES.Identifier ||
142
163
  right.type !== AST_NODE_TYPES.Literal ||
@@ -52,3 +52,57 @@ ruleTester.run('prefer-is-non-null-object', preferIsNonNullObject, {
52
52
  },
53
53
  ],
54
54
  });
55
+
56
+ // The replacement names the bare identifier: that is the reference both
57
+ // halves narrow, and a wrapper around the argument would stop the guard from
58
+ // narrowing it.
59
+ describe('prefer-is-non-null-object through type wrappers', () => {
60
+ ruleTester.run('prefer-is-non-null-object', preferIsNonNullObject, {
61
+ valid: [
62
+ {
63
+ name: 'The two halves are about different identifiers behind wrappers',
64
+ code: dedent`
65
+ const ok = typeof (u satisfies unknown) === 'object' && v! !== null;
66
+ `,
67
+ },
68
+ ],
69
+ invalid: [
70
+ {
71
+ name: 'reads the typeof operand through `satisfies`',
72
+ code: dedent`
73
+ const ok = typeof (u satisfies unknown) === 'object' && u !== null;
74
+ `,
75
+ output: dedent`
76
+ import { isNonNullObject } from 'ts-std-forge';
77
+ const ok = isNonNullObject(u);
78
+ `,
79
+ errors: [{ messageId: 'useIsNonNullObject' }],
80
+ },
81
+ {
82
+ name: 'reads the null check through `!` and `as`',
83
+ code: dedent`
84
+ const ok = typeof u === 'object' && u! !== (null as null);
85
+ `,
86
+ output: dedent`
87
+ import { isNonNullObject } from 'ts-std-forge';
88
+ const ok = isNonNullObject(u);
89
+ `,
90
+ errors: [{ messageId: 'useIsNonNullObject' }],
91
+ },
92
+ {
93
+ name: 'reads the literal and both halves through wrappers',
94
+ code: dedent`
95
+ const ok =
96
+ ((typeof (<unknown>u) === ('object' as const)) satisfies boolean) &&
97
+ ((u !== null) as boolean);
98
+ `,
99
+ output: dedent`
100
+ import { isNonNullObject } from 'ts-std-forge';
101
+ const ok =
102
+ isNonNullObject(u);
103
+ `,
104
+ errors: [{ messageId: 'useIsNonNullObject' }],
105
+ },
106
+ ],
107
+ });
108
+ });