eslint-plugin-ts-std-forge 0.1.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.
Files changed (80) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +104 -0
  3. package/dist/index.d.mts +5 -0
  4. package/dist/index.d.mts.map +1 -0
  5. package/dist/index.mjs +3 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/dist/plugin.d.mts +32 -0
  8. package/dist/plugin.d.mts.map +1 -0
  9. package/dist/plugin.mjs +41 -0
  10. package/dist/plugin.mjs.map +1 -0
  11. package/dist/rule-types.d.mts +96 -0
  12. package/dist/rule-types.d.mts.map +1 -0
  13. package/dist/rule-types.mjs +6 -0
  14. package/dist/rule-types.mjs.map +1 -0
  15. package/dist/rules/constants.d.mts +2 -0
  16. package/dist/rules/constants.d.mts.map +1 -0
  17. package/dist/rules/constants.mjs +2 -0
  18. package/dist/rules/constants.mjs.map +1 -0
  19. package/dist/rules/import-utils.d.mts +6 -0
  20. package/dist/rules/import-utils.d.mts.map +1 -0
  21. package/dist/rules/import-utils.mjs +27 -0
  22. package/dist/rules/import-utils.mjs.map +1 -0
  23. package/dist/rules/index.d.mts +2 -0
  24. package/dist/rules/index.d.mts.map +1 -0
  25. package/dist/rules/index.mjs +2 -0
  26. package/dist/rules/index.mjs.map +1 -0
  27. package/dist/rules/prefer-is-non-null-object.d.mts +6 -0
  28. package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -0
  29. package/dist/rules/prefer-is-non-null-object.mjs +97 -0
  30. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -0
  31. package/dist/rules/prefer-is-record-and-has-key.d.mts +6 -0
  32. package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -0
  33. package/dist/rules/prefer-is-record-and-has-key.mjs +154 -0
  34. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -0
  35. package/dist/rules/prefer-safe-array-is-array.d.mts +6 -0
  36. package/dist/rules/prefer-safe-array-is-array.d.mts.map +1 -0
  37. package/dist/rules/prefer-safe-array-is-array.mjs +68 -0
  38. package/dist/rules/prefer-safe-array-is-array.mjs.map +1 -0
  39. package/dist/rules/prefer-safe-array-length-guard.d.mts +6 -0
  40. package/dist/rules/prefer-safe-array-length-guard.d.mts.map +1 -0
  41. package/dist/rules/prefer-safe-array-length-guard.mjs +143 -0
  42. package/dist/rules/prefer-safe-array-length-guard.mjs.map +1 -0
  43. package/dist/rules/prefer-safe-number-parse-integer.d.mts +13 -0
  44. package/dist/rules/prefer-safe-number-parse-integer.d.mts.map +1 -0
  45. package/dist/rules/prefer-safe-number-parse-integer.mjs +109 -0
  46. package/dist/rules/prefer-safe-number-parse-integer.mjs.map +1 -0
  47. package/dist/rules/prefer-safe-number-parse.d.mts +19 -0
  48. package/dist/rules/prefer-safe-number-parse.d.mts.map +1 -0
  49. package/dist/rules/prefer-safe-number-parse.mjs +111 -0
  50. package/dist/rules/prefer-safe-number-parse.mjs.map +1 -0
  51. package/dist/rules/rules.d.mts +9 -0
  52. package/dist/rules/rules.d.mts.map +1 -0
  53. package/dist/rules/rules.mjs +15 -0
  54. package/dist/rules/rules.mjs.map +1 -0
  55. package/dist/types.d.mts +5 -0
  56. package/dist/types.d.mts.map +1 -0
  57. package/dist/types.mjs +2 -0
  58. package/dist/types.mjs.map +1 -0
  59. package/package.json +84 -0
  60. package/src/index.mts +7 -0
  61. package/src/plugin.mts +48 -0
  62. package/src/plugin.test.mts +24 -0
  63. package/src/rule-types.mts +116 -0
  64. package/src/rules/constants.mts +1 -0
  65. package/src/rules/import-utils.mts +56 -0
  66. package/src/rules/index.mts +1 -0
  67. package/src/rules/prefer-is-non-null-object.mts +142 -0
  68. package/src/rules/prefer-is-non-null-object.test.mts +54 -0
  69. package/src/rules/prefer-is-record-and-has-key.mts +217 -0
  70. package/src/rules/prefer-is-record-and-has-key.test.mts +80 -0
  71. package/src/rules/prefer-safe-array-is-array.mts +99 -0
  72. package/src/rules/prefer-safe-array-is-array.test.mts +65 -0
  73. package/src/rules/prefer-safe-array-length-guard.mts +214 -0
  74. package/src/rules/prefer-safe-array-length-guard.test.mts +152 -0
  75. package/src/rules/prefer-safe-number-parse-integer.mts +162 -0
  76. package/src/rules/prefer-safe-number-parse-integer.test.mts +67 -0
  77. package/src/rules/prefer-safe-number-parse.mts +161 -0
  78. package/src/rules/prefer-safe-number-parse.test.mts +109 -0
  79. package/src/rules/rules.mts +16 -0
  80. package/src/types.mts +6 -0
@@ -0,0 +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;oBAAE,OAAO;gBAEnD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;gBAE5B,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBAEzB,IAAI,QAAQ,KAAK,SAAS;oBAAE,OAAO;gBAEnC,oEAAoE;gBACpE,IAAI,QAAQ,CAAC,IAAI,KAAK,cAAc,CAAC,aAAa;oBAAE,OAAO;gBAE3D,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;oBAAE,OAAO;gBAEtC,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;oBAAE,OAAO;gBAEjC,MAAM,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;gBAElD,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC;oBAAE,OAAO;gBAEnC,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"}
@@ -0,0 +1,19 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ type Options = readonly [];
3
+ type MessageIds = 'useSafeNumberParse';
4
+ /**
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
+ * The ts-data-forge rule of the same shape names `Num.safeParseFloat`, which
13
+ * has delegated to `SafeNumber.parse` since the D-49 inversion and wraps the
14
+ * result in a `FiniteNumber` brand. This one names the implementation, for
15
+ * callers who do not want the brand or the extra dependency.
16
+ */
17
+ export declare const preferSafeNumberParse: TSESLint.RuleModule<MessageIds, Options>;
18
+ export {};
19
+ //# sourceMappingURL=prefer-safe-number-parse.d.mts.map
@@ -0,0 +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,CAmHjE,CAAC"}
@@ -0,0 +1,111 @@
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
+ import * as ts from 'typescript';
3
+ import { buildImportFixes, getNamedImports, getTsStdForgeImport, } from './import-utils.mjs';
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+ const isStringType = (type) => {
12
+ const parts = type.isUnion() ? type.types : [type];
13
+ return (parts.length > 0 &&
14
+ parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0));
15
+ };
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+ export const preferSafeNumberParse = {
30
+ meta: {
31
+ type: 'suggestion',
32
+ docs: {
33
+ 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.',
34
+ },
35
+ fixable: 'code',
36
+ schema: [],
37
+ messages: {
38
+ useSafeNumberParse: 'Replace `{{original}}` with `Result.unwrapOkOr(SafeNumber.parse({{argName}}), Number.NaN)` from ts-std-forge.',
39
+ },
40
+ },
41
+ create: (context) => {
42
+ const sourceCode = context.sourceCode;
43
+ const program = sourceCode.ast;
44
+ const tsStdForgeImport = getTsStdForgeImport(program);
45
+ const services = sourceCode.parserServices;
46
+ const mut_nodesToFix = [];
47
+ return {
48
+ CallExpression: (node) => {
49
+ const { callee } = node;
50
+
51
+ const isGlobalParseFloat = callee.type === AST_NODE_TYPES.Identifier &&
52
+ callee.name === 'parseFloat';
53
+
54
+ const isNumberParseFloat = callee.type === AST_NODE_TYPES.MemberExpression &&
55
+ !callee.computed &&
56
+ callee.object.type === AST_NODE_TYPES.Identifier &&
57
+ callee.object.name === '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
+ const args = node.arguments;
65
+ const firstArg = args[0];
66
+ if (firstArg === undefined)
67
+ return;
68
+
69
+ if (firstArg.type === AST_NODE_TYPES.SpreadElement)
70
+ return;
71
+
72
+
73
+
74
+ if (services?.program == null)
75
+ return;
76
+ const checker = services.program.getTypeChecker();
77
+ const tsNode = services.esTreeNodeToTSNodeMap?.get(firstArg);
78
+ if (tsNode === undefined)
79
+ return;
80
+ const argType = checker.getTypeAtLocation(tsNode);
81
+ if (!isStringType(argType))
82
+ return;
83
+ mut_nodesToFix.push({ node, argExpression: firstArg });
84
+ },
85
+ 'Program:exit': () => {
86
+ const namedImports = getNamedImports(tsStdForgeImport);
87
+ const missingImports = ['Result', 'SafeNumber'].filter((name) => !namedImports.includes(name));
88
+ for (const [index, { node, argExpression },] of mut_nodesToFix.entries()) {
89
+ const argText = sourceCode.getText(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
+ ? buildImportFixes(fixer, program, missingImports)
101
+ : [];
102
+ return [...importFixes, fixer.replaceText(node, core)];
103
+ },
104
+ });
105
+ }
106
+ },
107
+ };
108
+ },
109
+ defaultOptions: [],
110
+ };
111
+ //# sourceMappingURL=prefer-safe-number-parse.mjs.map
@@ -0,0 +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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"}
@@ -0,0 +1,9 @@
1
+ export declare const tsStdForgeRules: {
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
+ 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>;
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>;
8
+ };
9
+ //# sourceMappingURL=rules.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../src/rules/rules.mts"],"names":[],"mappings":"AAQA,eAAO,MAAM,eAAe;;;;;;;CAOc,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { preferIsNonNullObject } from './prefer-is-non-null-object.mjs';
2
+ import { preferIsRecordAndHasKey } from './prefer-is-record-and-has-key.mjs';
3
+ import { preferSafeArrayIsArray } from './prefer-safe-array-is-array.mjs';
4
+ import { preferSafeArrayLengthGuard } from './prefer-safe-array-length-guard.mjs';
5
+ import { preferSafeNumberParseInteger } from './prefer-safe-number-parse-integer.mjs';
6
+ import { preferSafeNumberParse } from './prefer-safe-number-parse.mjs';
7
+ export const tsStdForgeRules = {
8
+ 'prefer-is-non-null-object': preferIsNonNullObject,
9
+ 'prefer-is-record-and-has-key': preferIsRecordAndHasKey,
10
+ 'prefer-safe-array-is-array': preferSafeArrayIsArray,
11
+ 'prefer-safe-array-length-guard': preferSafeArrayLengthGuard,
12
+ 'prefer-safe-number-parse': preferSafeNumberParse,
13
+ 'prefer-safe-number-parse-integer': preferSafeNumberParseInteger,
14
+ };
15
+ //# sourceMappingURL=rules.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.mjs","sourceRoot":"","sources":["../../src/rules/rules.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAClF,OAAO,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AACtF,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAEvE,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,2BAA2B,EAAE,qBAAqB;IAClD,8BAA8B,EAAE,uBAAuB;IACvD,4BAA4B,EAAE,sBAAsB;IACpD,gCAAgC,EAAE,0BAA0B;IAC5D,0BAA0B,EAAE,qBAAqB;IACjD,kCAAkC,EAAE,4BAA4B;CACxB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ import { type DeepReadonly } from 'ts-type-forge';
3
+ export type ESLintPlugin = DeepReadonly<TSESLint.FlatConfig.Plugin>;
4
+ export type ESLintFlatConfig = DeepReadonly<TSESLint.FlatConfig.Config>;
5
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +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"}
package/dist/types.mjs ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../src/types.mts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "eslint-plugin-ts-std-forge",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "ESLint rules that steer code toward ts-std-forge idioms.",
6
+ "keywords": [
7
+ "eslint",
8
+ "eslintplugin",
9
+ "eslint-plugin",
10
+ "ts-std-forge",
11
+ "typescript"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/noshiro-pf/mono.git",
16
+ "directory": "libs/eslint-plugin-ts-std-forge"
17
+ },
18
+ "license": "Apache-2.0",
19
+ "author": "noshiro-pf <noshiro.pf@gmail.com>",
20
+ "sideEffects": false,
21
+ "type": "module",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.mts",
25
+ "import": "./dist/index.mjs"
26
+ }
27
+ },
28
+ "module": "./dist/index.mjs",
29
+ "types": "./dist/index.d.mts",
30
+ "files": [
31
+ "src",
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "dependencies": {
37
+ "@typescript-eslint/utils": "^8.69.0",
38
+ "ts-std-forge": "0.4.0",
39
+ "ts-type-forge": "~9.2.2"
40
+ },
41
+ "devDependencies": {
42
+ "@typescript-eslint/parser": "8.69.0",
43
+ "@typescript-eslint/rule-tester": "8.69.0",
44
+ "dedent": "1.7.2",
45
+ "eslint-config-typed": "5.11.6",
46
+ "eslint-plugin-ts-fortress": "1.2.1",
47
+ "eslint-plugin-ts-type-forge": "0.8.2",
48
+ "prettier": "3.9.6",
49
+ "prettier-plugin-organize-imports": "4.3.0",
50
+ "prettier-plugin-packagejson": "3.0.2",
51
+ "ts-repo-utils": "10.6.1"
52
+ },
53
+ "peerDependencies": {
54
+ "eslint": ">=9.0.0",
55
+ "typescript": ">=5.0.0 <7.0.0"
56
+ },
57
+ "engines": {
58
+ "node": ">=22.22.2",
59
+ "pnpm": ">=9.0.0"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public",
63
+ "provenance": true
64
+ },
65
+ "scripts": {
66
+ "build": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts",
67
+ "build:min": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts --skip-check",
68
+ "check:ext": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-ext.mts",
69
+ "fmt": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-uncommitted.mts --cwd .",
70
+ "fmt:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-diff-from.mts origin/main --cwd .",
71
+ "fmt:full": "pnpm run z:prettier .",
72
+ "gen:rule-types": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/gen-rule-types.mts",
73
+ "lint": "eslint .",
74
+ "lint:fix": "eslint . --fix",
75
+ "test": "pnpm run z:vitest run",
76
+ "test:cov": "pnpm run z:vitest run --coverage",
77
+ "testw": "pnpm run z:vitest",
78
+ "tsc": "node ../../node_modules/typescript-native/bin/tsc --noEmit",
79
+ "tscw": "node ../../node_modules/typescript-native/bin/tsc --noEmit --watch -p ./tsconfig.json",
80
+ "type-check": "node ../../node_modules/typescript-native/bin/tsc --noEmit",
81
+ "z:prettier": "prettier --config ../../.prettierrc --ignore-path ../../.prettierignore --ignore-path ../../.gitignore --ignore-unknown --no-error-on-unmatched-pattern --write",
82
+ "z:vitest": "vitest --config ./configs/vitest.config.mts"
83
+ }
84
+ }
package/src/index.mts ADDED
@@ -0,0 +1,7 @@
1
+ export { eslintPluginTsStdForge } from './plugin.mjs';
2
+ export type {
3
+ EslintTsStdForgeRules,
4
+ EslintTsStdForgeRulesOption,
5
+ } from './rule-types.mjs';
6
+ export { tsStdForgeRules } from './rules/index.mjs';
7
+ export { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';
package/src/plugin.mts ADDED
@@ -0,0 +1,48 @@
1
+ import { type ReadonlyRecord } from 'ts-type-forge';
2
+ import { tsStdForgeRules } from './rules/index.mjs';
3
+ import { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';
4
+
5
+ /**
6
+ * Every rule this plugin ships, at `error`.
7
+ *
8
+ * The `satisfies` clause below is keyed off {@link tsStdForgeRules}, so adding
9
+ * a rule without listing it here fails to type-check.
10
+ */
11
+ const recommendedRules = {
12
+ 'ts-std-forge/prefer-is-non-null-object': 'error',
13
+ 'ts-std-forge/prefer-is-record-and-has-key': 'error',
14
+ 'ts-std-forge/prefer-safe-array-is-array': 'error',
15
+ 'ts-std-forge/prefer-safe-array-length-guard': 'error',
16
+ 'ts-std-forge/prefer-safe-number-parse': 'error',
17
+ 'ts-std-forge/prefer-safe-number-parse-integer': 'error',
18
+ } as const satisfies ReadonlyRecord<
19
+ `ts-std-forge/${keyof typeof tsStdForgeRules}`,
20
+ 'error'
21
+ >;
22
+
23
+ const recommendedConfig = {
24
+ name: 'ts-std-forge/recommended',
25
+ plugins: {
26
+ // Resolved lazily so that this config registers the *same* object that
27
+ // `eslintPluginTsStdForge` refers to. ESLint rejects a plugin name that
28
+ // maps to two different objects with `Cannot redefine plugin`, which is
29
+ // what a user combining this preset with their own
30
+ // `plugins: { 'ts-std-forge': eslintPluginTsStdForge }` entry would hit
31
+ // if the preset embedded a separate copy of the plugin.
32
+ get 'ts-std-forge'(): ESLintPlugin {
33
+ return eslintPluginTsStdForge;
34
+ },
35
+ },
36
+ rules: recommendedRules,
37
+ } as const satisfies ESLintFlatConfig;
38
+
39
+ export const eslintPluginTsStdForge = {
40
+ meta: {
41
+ name: 'eslint-plugin-ts-std-forge',
42
+ },
43
+ rules: tsStdForgeRules,
44
+ configs: {
45
+ /** Enables every rule of this plugin at `error`. */
46
+ recommended: recommendedConfig,
47
+ },
48
+ } as const satisfies ESLintPlugin;
@@ -0,0 +1,24 @@
1
+ import { eslintPluginTsStdForge } from './plugin.mjs';
2
+ import { tsStdForgeRules } from './rules/index.mjs';
3
+
4
+ describe('eslintPluginTsStdForge.configs.recommended', () => {
5
+ const recommended = eslintPluginTsStdForge.configs.recommended;
6
+
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
+ test('registers the exported plugin object itself', () => {
20
+ // Registering a *copy* would make `Cannot redefine plugin` errors possible
21
+ // for users who also list the plugin in their own `plugins` record.
22
+ expect(recommended.plugins['ts-std-forge']).toBe(eslintPluginTsStdForge);
23
+ });
24
+ });
@@ -0,0 +1,116 @@
1
+ /* cSpell:disable */
2
+
3
+ // This file is auto-generated by `pnpm run gen:rule-types`
4
+ // (`scripts/cmd/gen-rule-types.mts`). Do not edit it manually.
5
+
6
+ import { type Linter } from 'eslint';
7
+ import { expectType } from 'ts-std-forge';
8
+ import { type tsStdForgeRules } from './rules/index.mjs';
9
+
10
+ /**
11
+ * @description Replace `typeof u === "object" && u !== null` with `isNonNullObject(u)` from ts-std-forge.
12
+ *
13
+ * ```md
14
+ * | key | value |
15
+ * | :--------- | :--------- |
16
+ * | type | suggestion |
17
+ * | deprecated | false |
18
+ * | fixable | code |
19
+ * ```
20
+ */
21
+ namespace PreferIsNonNullObject {
22
+ export type RuleEntry = Linter.StringSeverity;
23
+ }
24
+
25
+ /**
26
+ * @description Replace `Object.hasOwn(obj, key)` or `key in obj` with `isRecord(obj) && hasKey(obj, key)` from ts-std-forge
27
+ *
28
+ * ```md
29
+ * | key | value |
30
+ * | :--------- | :--------- |
31
+ * | type | suggestion |
32
+ * | deprecated | false |
33
+ * | fixable | code |
34
+ * ```
35
+ */
36
+ namespace PreferIsRecordAndHasKey {
37
+ export type RuleEntry = Linter.StringSeverity;
38
+ }
39
+
40
+ /**
41
+ * @description Replace `Array.isArray` with `SafeArray.isArray` from ts-std-forge.
42
+ *
43
+ * ```md
44
+ * | key | value |
45
+ * | :--------- | :--------- |
46
+ * | type | suggestion |
47
+ * | deprecated | false |
48
+ * | fixable | code |
49
+ * ```
50
+ */
51
+ namespace PreferSafeArrayIsArray {
52
+ export type RuleEntry = Linter.StringSeverity;
53
+ }
54
+
55
+ /**
56
+ * @description Replace an emptiness check on an array (`xs.length === 0`, `xs.length > 0`, `xs.length >= 1`, `xs.length !== 0`) with `SafeArray.isEmpty(xs)` / `SafeArray.isNonEmpty(xs)` from ts-std-forge.
57
+ *
58
+ * ```md
59
+ * | key | value |
60
+ * | :--------- | :--------- |
61
+ * | type | suggestion |
62
+ * | deprecated | false |
63
+ * | fixable | code |
64
+ * ```
65
+ */
66
+ namespace PreferSafeArrayLengthGuard {
67
+ export type RuleEntry = Linter.StringSeverity;
68
+ }
69
+
70
+ /**
71
+ * @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.
72
+ *
73
+ * ```md
74
+ * | key | value |
75
+ * | :--------- | :--------- |
76
+ * | type | suggestion |
77
+ * | deprecated | false |
78
+ * | fixable | code |
79
+ * ```
80
+ */
81
+ namespace PreferSafeNumberParse {
82
+ export type RuleEntry = Linter.StringSeverity;
83
+ }
84
+
85
+ /**
86
+ * @description Replace `parseInt(x)` or `Number.parseInt(x, 10)` with `Result.unwrapOkOr(SafeNumber.parseInteger(x), Number.NaN)` from ts-std-forge.
87
+ *
88
+ * ```md
89
+ * | key | value |
90
+ * | :--------- | :--------- |
91
+ * | type | suggestion |
92
+ * | deprecated | false |
93
+ * | fixable | code |
94
+ * ```
95
+ */
96
+ namespace PreferSafeNumberParseInteger {
97
+ export type RuleEntry = Linter.StringSeverity;
98
+ }
99
+
100
+ export type EslintTsStdForgeRules = Readonly<{
101
+ 'ts-std-forge/prefer-is-non-null-object': PreferIsNonNullObject.RuleEntry;
102
+ 'ts-std-forge/prefer-is-record-and-has-key': PreferIsRecordAndHasKey.RuleEntry;
103
+ 'ts-std-forge/prefer-safe-array-is-array': PreferSafeArrayIsArray.RuleEntry;
104
+ 'ts-std-forge/prefer-safe-array-length-guard': PreferSafeArrayLengthGuard.RuleEntry;
105
+ 'ts-std-forge/prefer-safe-number-parse': PreferSafeNumberParse.RuleEntry;
106
+ 'ts-std-forge/prefer-safe-number-parse-integer': PreferSafeNumberParseInteger.RuleEntry;
107
+ }>;
108
+
109
+ export type EslintTsStdForgeRulesOption = Readonly<{}>;
110
+
111
+ // If this assertion fails to type-check, this generated file has drifted from
112
+ // the actual plugin rules — re-run `pnpm run gen:rule-types`.
113
+ expectType<
114
+ keyof EslintTsStdForgeRules,
115
+ `ts-std-forge/${keyof typeof tsStdForgeRules}`
116
+ >('=');
@@ -0,0 +1 @@
1
+ export const TS_STD_FORGE_MODULE = 'ts-std-forge';
@@ -0,0 +1,56 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ type TSESLint,
4
+ type TSESTree,
5
+ } from '@typescript-eslint/utils';
6
+ import { type DeepReadonly } from 'ts-type-forge';
7
+ import { TS_STD_FORGE_MODULE } from './constants.mjs';
8
+
9
+ /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
10
+
11
+ export const getTsStdForgeImport = (
12
+ program: TSESTree.Program,
13
+ ): TSESTree.ImportDeclaration | undefined =>
14
+ program.body.find(
15
+ (node): node is TSESTree.ImportDeclaration =>
16
+ node.type === AST_NODE_TYPES.ImportDeclaration &&
17
+ node.source.value === TS_STD_FORGE_MODULE,
18
+ );
19
+
20
+ export const getNamedImports = (
21
+ node: DeepReadonly<TSESTree.ImportDeclaration> | undefined,
22
+ ): readonly string[] => {
23
+ if (node === undefined) return [];
24
+
25
+ return node.specifiers.flatMap((specifier) =>
26
+ specifier.type === AST_NODE_TYPES.ImportSpecifier
27
+ ? (() => {
28
+ const importedName =
29
+ specifier.imported.type === AST_NODE_TYPES.Identifier
30
+ ? specifier.imported.name
31
+ : specifier.imported.value;
32
+
33
+ return typeof importedName === 'string' ? [importedName] : [];
34
+ })()
35
+ : [],
36
+ );
37
+ };
38
+
39
+ export const buildImportFixes = (
40
+ fixer: TSESLint.RuleFixer,
41
+ program: TSESTree.Program,
42
+ requiredNames: readonly string[],
43
+ ): readonly TSESLint.RuleFix[] => {
44
+ const specifierText = requiredNames.join(', ');
45
+
46
+ const importStatement =
47
+ `import { ${specifierText} } from '${TS_STD_FORGE_MODULE}';` as const;
48
+
49
+ const newLine = '\n';
50
+
51
+ const insertionText = `${importStatement}${newLine}` as const;
52
+
53
+ // Always insert at the beginning of the file
54
+ // (organize-imports will handle merging/deduplication)
55
+ return [fixer.insertTextBefore(program, insertionText)];
56
+ };
@@ -0,0 +1 @@
1
+ export * from './rules.mjs';