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.
- package/LICENSE +201 -0
- package/README.md +104 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -0
- package/dist/plugin.d.mts +32 -0
- package/dist/plugin.d.mts.map +1 -0
- package/dist/plugin.mjs +41 -0
- package/dist/plugin.mjs.map +1 -0
- package/dist/rule-types.d.mts +96 -0
- package/dist/rule-types.d.mts.map +1 -0
- package/dist/rule-types.mjs +6 -0
- package/dist/rule-types.mjs.map +1 -0
- package/dist/rules/constants.d.mts +2 -0
- package/dist/rules/constants.d.mts.map +1 -0
- package/dist/rules/constants.mjs +2 -0
- package/dist/rules/constants.mjs.map +1 -0
- package/dist/rules/import-utils.d.mts +6 -0
- package/dist/rules/import-utils.d.mts.map +1 -0
- package/dist/rules/import-utils.mjs +27 -0
- package/dist/rules/import-utils.mjs.map +1 -0
- package/dist/rules/index.d.mts +2 -0
- package/dist/rules/index.d.mts.map +1 -0
- package/dist/rules/index.mjs +2 -0
- package/dist/rules/index.mjs.map +1 -0
- package/dist/rules/prefer-is-non-null-object.d.mts +6 -0
- package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -0
- package/dist/rules/prefer-is-non-null-object.mjs +97 -0
- package/dist/rules/prefer-is-non-null-object.mjs.map +1 -0
- package/dist/rules/prefer-is-record-and-has-key.d.mts +6 -0
- package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -0
- package/dist/rules/prefer-is-record-and-has-key.mjs +154 -0
- package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -0
- package/dist/rules/prefer-safe-array-is-array.d.mts +6 -0
- package/dist/rules/prefer-safe-array-is-array.d.mts.map +1 -0
- package/dist/rules/prefer-safe-array-is-array.mjs +68 -0
- package/dist/rules/prefer-safe-array-is-array.mjs.map +1 -0
- package/dist/rules/prefer-safe-array-length-guard.d.mts +6 -0
- package/dist/rules/prefer-safe-array-length-guard.d.mts.map +1 -0
- package/dist/rules/prefer-safe-array-length-guard.mjs +143 -0
- package/dist/rules/prefer-safe-array-length-guard.mjs.map +1 -0
- package/dist/rules/prefer-safe-number-parse-integer.d.mts +13 -0
- package/dist/rules/prefer-safe-number-parse-integer.d.mts.map +1 -0
- package/dist/rules/prefer-safe-number-parse-integer.mjs +109 -0
- package/dist/rules/prefer-safe-number-parse-integer.mjs.map +1 -0
- package/dist/rules/prefer-safe-number-parse.d.mts +19 -0
- package/dist/rules/prefer-safe-number-parse.d.mts.map +1 -0
- package/dist/rules/prefer-safe-number-parse.mjs +111 -0
- package/dist/rules/prefer-safe-number-parse.mjs.map +1 -0
- package/dist/rules/rules.d.mts +9 -0
- package/dist/rules/rules.d.mts.map +1 -0
- package/dist/rules/rules.mjs +15 -0
- package/dist/rules/rules.mjs.map +1 -0
- package/dist/types.d.mts +5 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +2 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +84 -0
- package/src/index.mts +7 -0
- package/src/plugin.mts +48 -0
- package/src/plugin.test.mts +24 -0
- package/src/rule-types.mts +116 -0
- package/src/rules/constants.mts +1 -0
- package/src/rules/import-utils.mts +56 -0
- package/src/rules/index.mts +1 -0
- package/src/rules/prefer-is-non-null-object.mts +142 -0
- package/src/rules/prefer-is-non-null-object.test.mts +54 -0
- package/src/rules/prefer-is-record-and-has-key.mts +217 -0
- package/src/rules/prefer-is-record-and-has-key.test.mts +80 -0
- package/src/rules/prefer-safe-array-is-array.mts +99 -0
- package/src/rules/prefer-safe-array-is-array.test.mts +65 -0
- package/src/rules/prefer-safe-array-length-guard.mts +214 -0
- package/src/rules/prefer-safe-array-length-guard.test.mts +152 -0
- package/src/rules/prefer-safe-number-parse-integer.mts +162 -0
- package/src/rules/prefer-safe-number-parse-integer.test.mts +67 -0
- package/src/rules/prefer-safe-number-parse.mts +161 -0
- package/src/rules/prefer-safe-number-parse.test.mts +109 -0
- package/src/rules/rules.mts +16 -0
- 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":"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"}
|
|
@@ -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"}
|
package/dist/types.d.mts
ADDED
|
@@ -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 @@
|
|
|
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';
|