eslint-plugin-wyrm 0.0.10 → 0.0.13
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/CHANGELOG.md +79 -0
- package/README.md +114 -58
- package/bin/wyrm.mjs +3 -0
- package/dist/_virtual/_rolldown/runtime.mjs +14 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +23 -0
- package/dist/configs/index.d.mts +2 -1
- package/dist/configs/index.mjs +6 -6
- package/dist/index.mjs +1 -4
- package/dist/plugin.mjs +1 -3
- package/dist/rules/array-from-array.d.mts +9 -0
- package/dist/rules/array-from-array.mjs +56 -0
- package/dist/rules/as-unknown-as.d.mts +9 -0
- package/dist/rules/as-unknown-as.mjs +30 -0
- package/dist/rules/await-promise-resolve.d.mts +9 -0
- package/dist/rules/await-promise-resolve.mjs +41 -0
- package/dist/rules/boolean-coalescing.d.mts +9 -0
- package/dist/rules/boolean-coalescing.mjs +141 -0
- package/dist/rules/comment-duplicate-leading-space.d.mts +9 -0
- package/dist/rules/comment-duplicate-leading-space.mjs +34 -0
- package/dist/rules/conditional-boolean.d.mts +9 -0
- package/dist/rules/conditional-boolean.mjs +70 -0
- package/dist/rules/constant-boolean-cast.d.mts +9 -0
- package/dist/rules/constant-boolean-cast.mjs +203 -0
- package/dist/rules/de-morgan.d.mts +2 -2
- package/dist/rules/de-morgan.mjs +1 -3
- package/dist/rules/discarded-expression-statement.d.mts +14 -0
- package/dist/rules/discarded-expression-statement.mjs +243 -0
- package/dist/rules/distribute-boolean-casts.d.mts +2 -2
- package/dist/rules/distribute-boolean-casts.mjs +3 -3
- package/dist/rules/duplicate-destructuring.d.mts +2 -2
- package/dist/rules/duplicate-destructuring.mjs +1 -3
- package/dist/rules/duplicate-object-spread.d.mts +9 -0
- package/dist/rules/duplicate-object-spread.mjs +44 -0
- package/dist/rules/e.d.mts +11 -0
- package/dist/rules/e.mjs +87 -0
- package/dist/rules/empty-for.d.mts +9 -0
- package/dist/rules/empty-for.mjs +34 -0
- package/dist/rules/enum-member.d.mts +9 -0
- package/dist/rules/enum-member.mjs +51 -0
- package/dist/rules/eqeq-null.d.mts +9 -0
- package/dist/rules/eqeq-null.mjs +101 -0
- package/dist/rules/exact-string-regex.d.mts +9 -0
- package/dist/rules/exact-string-regex.mjs +83 -0
- package/dist/rules/export-using.d.mts +9 -0
- package/dist/rules/export-using.mjs +58 -0
- package/dist/rules/generic-constructor-with-hook.d.mts +11 -0
- package/dist/rules/generic-constructor-with-hook.mjs +103 -0
- package/dist/rules/idiomatic-cast.mjs +7 -10
- package/dist/rules/index.d.mts +112 -57
- package/dist/rules/index.mjs +113 -5
- package/dist/rules/inferable-type-predicate.d.mts +9 -0
- package/dist/rules/inferable-type-predicate.mjs +100 -0
- package/dist/rules/inner-as-const.d.mts +9 -0
- package/dist/rules/inner-as-const.mjs +70 -0
- package/dist/rules/jsx-tostring.d.mts +9 -0
- package/dist/rules/jsx-tostring.mjs +120 -0
- package/dist/rules/literal-destructuring.d.mts +9 -0
- package/dist/rules/literal-destructuring.mjs +102 -0
- package/dist/rules/named-export-with-side-effects.d.mts +11 -0
- package/dist/rules/named-export-with-side-effects.mjs +72 -0
- package/dist/rules/nested-reduce.d.mts +9 -0
- package/dist/rules/nested-reduce.mjs +46 -0
- package/dist/rules/nested-try-catch.d.mts +9 -0
- package/dist/rules/nested-try-catch.mjs +37 -0
- package/dist/rules/no-commented-out-comment.d.mts +9 -0
- package/dist/rules/no-commented-out-comment.mjs +31 -0
- package/dist/rules/no-constant-template-expression.d.mts +3 -4
- package/dist/rules/no-constant-template-expression.mjs +12 -9
- package/dist/rules/no-constructed-error-cause.d.mts +9 -0
- package/dist/rules/no-constructed-error-cause.mjs +75 -0
- package/dist/rules/no-convoluted-boolean-expressions.d.mts +9 -0
- package/dist/rules/no-convoluted-boolean-expressions.mjs +130 -0
- package/dist/rules/no-convoluted-logical-expressions.d.mts +2 -2
- package/dist/rules/no-convoluted-logical-expressions.mjs +1 -3
- package/dist/rules/no-custom-url-parsing.d.mts +2 -3
- package/dist/rules/no-custom-url-parsing.mjs +1 -3
- package/dist/rules/no-disallowed-warning-comments.d.mts +2 -2
- package/dist/rules/no-disallowed-warning-comments.mjs +1 -3
- package/dist/rules/no-duplicated-return.d.mts +2 -2
- package/dist/rules/no-duplicated-return.mjs +2 -3
- package/dist/rules/no-else-break.d.mts +2 -2
- package/dist/rules/no-else-break.mjs +3 -5
- package/dist/rules/no-else-continue.d.mts +2 -2
- package/dist/rules/no-else-continue.mjs +3 -5
- package/dist/rules/no-else-never.d.mts +9 -0
- package/dist/rules/no-else-never.mjs +75 -0
- package/dist/rules/no-else-return.d.mts +2 -3
- package/dist/rules/no-else-return.mjs +4 -5
- package/dist/rules/no-else-throw.d.mts +2 -2
- package/dist/rules/no-else-throw.mjs +3 -5
- package/dist/rules/no-empty-attribute.d.mts +3 -3
- package/dist/rules/no-empty-attribute.mjs +1 -3
- package/dist/rules/no-empty-comment.d.mts +3 -3
- package/dist/rules/no-empty-comment.mjs +1 -3
- package/dist/rules/no-empty-jsx-expression.d.mts +2 -2
- package/dist/rules/no-empty-jsx-expression.mjs +1 -3
- package/dist/rules/no-empty-literal-iteration.d.mts +2 -2
- package/dist/rules/no-empty-literal-iteration.mjs +1 -3
- package/dist/rules/no-extra-false-fallback.d.mts +2 -3
- package/dist/rules/no-extra-false-fallback.mjs +1 -3
- package/dist/rules/no-extra-nested-boolean-cast.d.mts +2 -3
- package/dist/rules/no-extra-nested-boolean-cast.mjs +2 -3
- package/dist/rules/no-first-last.d.mts +2 -2
- package/dist/rules/no-first-last.mjs +2 -5
- package/dist/rules/no-float-length-check.d.mts +2 -2
- package/dist/rules/no-float-length-check.mjs +1 -3
- package/dist/rules/no-huge-try-block.d.mts +3 -3
- package/dist/rules/no-huge-try-block.mjs +1 -3
- package/dist/rules/no-huge-useeffect.d.mts +11 -0
- package/dist/rules/no-huge-useeffect.mjs +52 -0
- package/dist/rules/no-if-length-for.d.mts +9 -0
- package/dist/rules/no-if-length-for.mjs +91 -0
- package/dist/rules/no-inline-jsdoc-tag.d.mts +3 -3
- package/dist/rules/no-inline-jsdoc-tag.mjs +1 -3
- package/dist/rules/no-invalid-date-literal.d.mts +2 -2
- package/dist/rules/no-invalid-date-literal.mjs +42 -19
- package/dist/rules/no-jsx-statement.d.mts +2 -2
- package/dist/rules/no-jsx-statement.mjs +1 -3
- package/dist/rules/no-lax-array-type.d.mts +9 -0
- package/dist/rules/no-lax-array-type.mjs +129 -0
- package/dist/rules/no-lax-return-type.d.mts +9 -0
- package/dist/rules/no-lax-return-type.mjs +138 -0
- package/dist/rules/no-mutable-literal-fill.d.mts +2 -3
- package/dist/rules/no-mutable-literal-fill.mjs +1 -3
- package/dist/rules/no-nullish-ternary.d.mts +9 -0
- package/dist/rules/no-nullish-ternary.mjs +121 -0
- package/dist/rules/no-numbered-comments.d.mts +2 -2
- package/dist/rules/no-numbered-comments.mjs +1 -3
- package/dist/rules/no-obvious-any.mjs +35 -27
- package/dist/rules/no-optional-type-guard-param.d.mts +2 -3
- package/dist/rules/no-optional-type-guard-param.mjs +1 -3
- package/dist/rules/no-out-of-order-comments.d.mts +2 -2
- package/dist/rules/no-out-of-order-comments.mjs +4 -8
- package/dist/rules/no-possibly-nullish-equality.d.mts +0 -1
- package/dist/rules/no-possibly-nullish-equality.mjs +6 -13
- package/dist/rules/no-redundant-function-declaration.d.mts +2 -2
- package/dist/rules/no-redundant-function-declaration.mjs +1 -3
- package/dist/rules/no-return-to-void.mjs +2 -4
- package/dist/rules/no-self-object-assign.d.mts +2 -2
- package/dist/rules/no-self-object-assign.mjs +1 -3
- package/dist/rules/no-sloppy-length-check.d.mts +2 -3
- package/dist/rules/no-sloppy-length-check.mjs +35 -37
- package/dist/rules/no-suspicious-jsx-semicolon.d.mts +2 -3
- package/dist/rules/no-suspicious-jsx-semicolon.mjs +2 -4
- package/dist/rules/no-ternary-return.d.mts +3 -4
- package/dist/rules/no-ternary-return.mjs +1 -3
- package/dist/rules/no-type-guard.d.mts +9 -0
- package/dist/rules/no-type-guard.mjs +36 -0
- package/dist/rules/no-unassigned-todo.d.mts +2 -2
- package/dist/rules/no-unassigned-todo.mjs +1 -3
- package/dist/rules/no-unbound-catch-error.d.mts +2 -2
- package/dist/rules/no-unbound-catch-error.mjs +1 -3
- package/dist/rules/no-unused-mutually-referential.d.mts +9 -0
- package/dist/rules/no-unused-mutually-referential.mjs +215 -0
- package/dist/rules/no-unused-param-read.d.mts +2 -2
- package/dist/rules/no-unused-param-read.mjs +1 -3
- package/dist/rules/no-useless-computed-key.d.mts +2 -2
- package/dist/rules/no-useless-computed-key.mjs +1 -3
- package/dist/rules/no-useless-iife.d.mts +2 -2
- package/dist/rules/no-useless-iife.mjs +2 -3
- package/dist/rules/no-useless-logical-fallback.d.mts +0 -1
- package/dist/rules/no-useless-logical-fallback.mjs +32 -24
- package/dist/rules/no-useless-return-undefined.mjs +11 -11
- package/dist/rules/no-useless-ts-check.d.mts +9 -0
- package/dist/rules/no-useless-ts-check.mjs +34 -0
- package/dist/rules/no-useless-use-strict.d.mts +2 -2
- package/dist/rules/no-useless-use-strict.mjs +1 -3
- package/dist/rules/no-useless-usememo.d.mts +2 -2
- package/dist/rules/no-useless-usememo.mjs +8 -6
- package/dist/rules/no-whitespace-property.d.mts +2 -2
- package/dist/rules/no-whitespace-property.mjs +1 -3
- package/dist/rules/nullish-object-spread.d.mts +9 -0
- package/dist/rules/nullish-object-spread.mjs +38 -0
- package/dist/rules/optional-call-expression.d.mts +2 -2
- package/dist/rules/optional-call-expression.mjs +20 -3
- package/dist/rules/prefer-array-from.d.mts +9 -0
- package/dist/rules/prefer-array-from.mjs +87 -0
- package/dist/rules/prefer-catch-method.d.mts +2 -2
- package/dist/rules/prefer-catch-method.mjs +9 -12
- package/dist/rules/prefer-early-return.d.mts +2 -2
- package/dist/rules/prefer-early-return.mjs +23 -20
- package/dist/rules/prefer-eqeq-null.d.mts +9 -0
- package/dist/rules/prefer-eqeq-null.mjs +122 -0
- package/dist/rules/prefer-finally.d.mts +9 -0
- package/dist/rules/prefer-finally.mjs +129 -0
- package/dist/rules/prefer-getorinsert.d.mts +9 -0
- package/dist/rules/prefer-getorinsert.mjs +180 -0
- package/dist/rules/prefer-has.d.mts +9 -0
- package/dist/rules/prefer-has.mjs +69 -0
- package/dist/rules/prefer-in.d.mts +9 -0
- package/dist/rules/prefer-in.mjs +81 -0
- package/dist/rules/prefer-object-keys-values.d.mts +9 -0
- package/dist/rules/prefer-object-keys-values.mjs +78 -0
- package/dist/rules/prefer-repeat.d.mts +2 -2
- package/dist/rules/prefer-repeat.mjs +1 -5
- package/dist/rules/prefer-satisfies.d.mts +9 -0
- package/dist/rules/prefer-satisfies.mjs +86 -0
- package/dist/rules/prefer-string-join.d.mts +9 -0
- package/dist/rules/prefer-string-join.mjs +67 -0
- package/dist/rules/primitive-valueof.mjs +6 -8
- package/dist/rules/slim-try.d.mts +9 -0
- package/dist/rules/slim-try.mjs +150 -0
- package/dist/rules/styled-button-has-type.d.mts +9 -0
- package/dist/rules/styled-button-has-type.mjs +82 -0
- package/dist/rules/styled-transient-props.d.mts +9 -0
- package/dist/rules/styled-transient-props.mjs +52 -0
- package/dist/rules/suspicious-map-length.d.mts +2 -2
- package/dist/rules/suspicious-map-length.mjs +1 -3
- package/dist/rules/template-tostring.d.mts +9 -0
- package/dist/rules/template-tostring.mjs +48 -0
- package/dist/rules/unsafe-asserted-chain.d.mts +0 -1
- package/dist/rules/unsafe-asserted-chain.mjs +5 -8
- package/dist/rules/unused-object-assign.d.mts +2 -2
- package/dist/rules/unused-object-assign.mjs +1 -3
- package/dist/rules/unused-object-freeze.d.mts +9 -0
- package/dist/rules/unused-object-freeze.mjs +37 -0
- package/dist/rules/useless-as-const.d.mts +9 -0
- package/dist/rules/useless-as-const.mjs +59 -0
- package/dist/rules/useless-assign.d.mts +11 -0
- package/dist/rules/useless-assign.mjs +69 -0
- package/dist/rules/useless-conditional-assign.d.mts +9 -0
- package/dist/rules/useless-conditional-assign.mjs +58 -0
- package/dist/rules/useless-intermediary-variable.mjs +18 -21
- package/dist/rules/useless-mock.d.mts +9 -0
- package/dist/rules/useless-mock.mjs +180 -0
- package/dist/rules/useless-required.d.mts +9 -0
- package/dist/rules/useless-required.mjs +257 -0
- package/dist/utils/compareTokens.mjs +1 -3
- package/dist/utils/createRule.mjs +1 -3
- package/dist/utils/negateExpression.mjs +1 -3
- package/dist/utils/option.mjs +5 -2
- package/package.json +30 -23
- package/dist/_virtual/rolldown_runtime.mjs +0 -20
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createRule } from "../utils/createRule.mjs";
|
|
2
|
+
import { None, Some, getFirstOption } from "../utils/option.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
5
|
+
//#region lib/rules/prefer-in.ts
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview
|
|
8
|
+
*
|
|
9
|
+
* `x in y` does not have the same behavior as `Object.hasOwn(y, x)` and `Object.prototype.hasOwnProperty.call(y, x)`.
|
|
10
|
+
*
|
|
11
|
+
* But in the majority of cases, the difference does not really matter (modern codebases should already avoid objects with prototype chains anyway).
|
|
12
|
+
*
|
|
13
|
+
* `x in y` is also more readable and better supported in TypeScript flow analysis for type narrowing.
|
|
14
|
+
*/
|
|
15
|
+
const { name } = path.parse(import.meta.filename);
|
|
16
|
+
var prefer_in_default = createRule({
|
|
17
|
+
name,
|
|
18
|
+
meta: {
|
|
19
|
+
type: "suggestion",
|
|
20
|
+
docs: {
|
|
21
|
+
description: "Prefer `in` to `Object.hasOwn()` and `Object.prototype.hasOwnProperty.call()`",
|
|
22
|
+
strict: true
|
|
23
|
+
},
|
|
24
|
+
hasSuggestions: true,
|
|
25
|
+
schema: [],
|
|
26
|
+
messages: {
|
|
27
|
+
preferIn: "See if you can use `in` instead",
|
|
28
|
+
useIn: "Use `in`"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
defaultOptions: [],
|
|
32
|
+
create(context) {
|
|
33
|
+
return { CallExpression(node) {
|
|
34
|
+
const maybeArgs = getFirstOption([getObjectHasOwn(node), getHasOwnPropertyCall(node)]);
|
|
35
|
+
if (!maybeArgs.some) return;
|
|
36
|
+
const [key, value] = maybeArgs.value;
|
|
37
|
+
context.report({
|
|
38
|
+
node,
|
|
39
|
+
messageId: "preferIn",
|
|
40
|
+
suggest: [{
|
|
41
|
+
messageId: "useIn",
|
|
42
|
+
fix(fixer) {
|
|
43
|
+
const keyText = context.sourceCode.getText(key);
|
|
44
|
+
const valueText = context.sourceCode.getText(value);
|
|
45
|
+
return fixer.replaceText(node, `${keyText} in ${valueText}`);
|
|
46
|
+
}
|
|
47
|
+
}]
|
|
48
|
+
});
|
|
49
|
+
} };
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
function getObjectHasOwn(node) {
|
|
53
|
+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return None;
|
|
54
|
+
if (node.callee.object.type !== AST_NODE_TYPES.Identifier) return None;
|
|
55
|
+
if (node.callee.property.type !== AST_NODE_TYPES.Identifier) return None;
|
|
56
|
+
if (node.callee.object.name !== "Object") return None;
|
|
57
|
+
if (node.callee.property.name !== "hasOwn") return None;
|
|
58
|
+
const [arg1, arg2] = node.arguments;
|
|
59
|
+
if (!arg1) return None;
|
|
60
|
+
if (!arg2) return None;
|
|
61
|
+
return Some([arg2, arg1]);
|
|
62
|
+
}
|
|
63
|
+
function getHasOwnPropertyCall(node) {
|
|
64
|
+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return None;
|
|
65
|
+
if (node.callee.property.type !== AST_NODE_TYPES.Identifier) return None;
|
|
66
|
+
if (node.callee.property.name !== "call") return None;
|
|
67
|
+
if (node.callee.object.type !== AST_NODE_TYPES.MemberExpression) return None;
|
|
68
|
+
if (node.callee.object.property.type !== AST_NODE_TYPES.Identifier) return None;
|
|
69
|
+
if (node.callee.object.property.name !== "hasOwnProperty") return None;
|
|
70
|
+
if (node.callee.object.object.type !== AST_NODE_TYPES.MemberExpression) return None;
|
|
71
|
+
if (node.callee.object.object.property.type !== AST_NODE_TYPES.Identifier) return None;
|
|
72
|
+
if (node.callee.object.object.property.name !== "prototype") return None;
|
|
73
|
+
if (node.callee.object.object.object.type !== AST_NODE_TYPES.Identifier) return None;
|
|
74
|
+
if (node.callee.object.object.object.name !== "Object") return None;
|
|
75
|
+
const [arg1, arg2] = node.arguments;
|
|
76
|
+
if (!arg1) return None;
|
|
77
|
+
if (!arg2) return None;
|
|
78
|
+
return Some([arg2, arg1]);
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { prefer_in_default as default };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { WyrmPluginDocs } from "../utils/createRule.mjs";
|
|
2
|
+
import * as _$_typescript_eslint_utils_ts_eslint0 from "@typescript-eslint/utils/ts-eslint";
|
|
3
|
+
|
|
4
|
+
//#region lib/rules/prefer-object-keys-values.d.ts
|
|
5
|
+
declare const _default: _$_typescript_eslint_utils_ts_eslint0.RuleModule<"preferObjectKeys" | "preferObjectValues" | "useObjectKeys" | "useObjectValues", [], WyrmPluginDocs, _$_typescript_eslint_utils_ts_eslint0.RuleListener> & {
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { _default };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createRule } from "../utils/createRule.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
4
|
+
//#region lib/rules/prefer-object-keys-values.ts
|
|
5
|
+
const { name } = path.parse(import.meta.filename);
|
|
6
|
+
var prefer_object_keys_values_default = createRule({
|
|
7
|
+
name,
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Enforce using `Object.keys()` and `Object.values()` rather than `Object.entries()`",
|
|
12
|
+
strict: true
|
|
13
|
+
},
|
|
14
|
+
hasSuggestions: true,
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
preferObjectKeys: "You are only using keys, use `Object.keys()` instead",
|
|
18
|
+
preferObjectValues: "You are only using values, use `Object.values()` instead",
|
|
19
|
+
useObjectKeys: "Use `Object.keys()`",
|
|
20
|
+
useObjectValues: "Use `Object.values()`"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
defaultOptions: [],
|
|
24
|
+
create(context) {
|
|
25
|
+
return { CallExpression(node) {
|
|
26
|
+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
27
|
+
if (node.callee.property.type !== AST_NODE_TYPES.Identifier) return;
|
|
28
|
+
if (node.callee.object.type !== AST_NODE_TYPES.CallExpression) return;
|
|
29
|
+
if (node.callee.object.callee.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
30
|
+
if (node.callee.object.callee.object.type !== AST_NODE_TYPES.Identifier) return;
|
|
31
|
+
if (node.callee.object.callee.property.type !== AST_NODE_TYPES.Identifier) return;
|
|
32
|
+
if (node.callee.object.callee.object.name !== "Object") return;
|
|
33
|
+
const objectMethod = node.callee.object.callee.property;
|
|
34
|
+
if (objectMethod.name !== "entries") return;
|
|
35
|
+
if (![
|
|
36
|
+
"map",
|
|
37
|
+
"forEach",
|
|
38
|
+
"every",
|
|
39
|
+
"some"
|
|
40
|
+
].includes(node.callee.property.name)) return;
|
|
41
|
+
const [arg] = node.arguments;
|
|
42
|
+
if (!arg) return;
|
|
43
|
+
if (arg.type !== AST_NODE_TYPES.FunctionExpression && arg.type !== AST_NODE_TYPES.ArrowFunctionExpression) return;
|
|
44
|
+
const [param] = arg.params;
|
|
45
|
+
if (!param) return;
|
|
46
|
+
if (param.type !== AST_NODE_TYPES.ArrayPattern) return;
|
|
47
|
+
const [keyParam, valueParam, ...rest] = param.elements;
|
|
48
|
+
if (rest.length) return;
|
|
49
|
+
if (!valueParam && keyParam?.type === AST_NODE_TYPES.Identifier) {
|
|
50
|
+
context.report({
|
|
51
|
+
node,
|
|
52
|
+
messageId: "preferObjectKeys",
|
|
53
|
+
suggest: [{
|
|
54
|
+
messageId: "useObjectKeys",
|
|
55
|
+
*fix(fixer) {
|
|
56
|
+
yield fixer.replaceText(objectMethod, "keys");
|
|
57
|
+
yield fixer.replaceText(param, keyParam.name);
|
|
58
|
+
}
|
|
59
|
+
}]
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (keyParam === null && valueParam?.type === AST_NODE_TYPES.Identifier) context.report({
|
|
64
|
+
node,
|
|
65
|
+
messageId: "preferObjectValues",
|
|
66
|
+
suggest: [{
|
|
67
|
+
messageId: "useObjectValues",
|
|
68
|
+
*fix(fixer) {
|
|
69
|
+
yield fixer.replaceText(objectMethod, "values");
|
|
70
|
+
yield fixer.replaceText(param, valueParam.name);
|
|
71
|
+
}
|
|
72
|
+
}]
|
|
73
|
+
});
|
|
74
|
+
} };
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
//#endregion
|
|
78
|
+
export { prefer_object_keys_values_default as default };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { WyrmPluginDocs } from "../utils/createRule.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as _$_typescript_eslint_utils_ts_eslint0 from "@typescript-eslint/utils/ts-eslint";
|
|
3
3
|
|
|
4
4
|
//#region lib/rules/prefer-repeat.d.ts
|
|
5
|
-
declare const _default:
|
|
5
|
+
declare const _default: _$_typescript_eslint_utils_ts_eslint0.RuleModule<"preferRepeat" | "replaceByRepeat", [], WyrmPluginDocs, _$_typescript_eslint_utils_ts_eslint0.RuleListener> & {
|
|
6
6
|
name: string;
|
|
7
7
|
};
|
|
8
8
|
//#endregion
|
|
@@ -2,7 +2,6 @@ import { createRule } from "../utils/createRule.mjs";
|
|
|
2
2
|
import { None, Option, Some } from "../utils/option.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
5
|
-
|
|
6
5
|
//#region lib/rules/prefer-repeat.ts
|
|
7
6
|
const { name } = path.parse(import.meta.filename);
|
|
8
7
|
var prefer_repeat_default = createRule({
|
|
@@ -88,13 +87,11 @@ function extractRepeatedStringFromTemplateLiteral(expr, acc) {
|
|
|
88
87
|
const str = expr.expressions.find((expression) => expression.type !== AST_NODE_TYPES.Identifier || expression.name !== acc.name);
|
|
89
88
|
return Option.fromUndef(str);
|
|
90
89
|
}
|
|
91
|
-
if (expr.expressions.length !== 1) return None;
|
|
92
90
|
if (expr.expressions[0]?.type !== AST_NODE_TYPES.Identifier) return None;
|
|
93
91
|
if (expr.expressions[0].name !== acc.name) return None;
|
|
94
92
|
const nonEmptyQuasis = expr.quasis.map((q) => q.value.cooked).filter(Boolean);
|
|
95
93
|
const firstQuasi = nonEmptyQuasis.at(0);
|
|
96
94
|
if (!firstQuasi) return None;
|
|
97
|
-
if (nonEmptyQuasis.length === 1) return Some(firstQuasi);
|
|
98
95
|
if (nonEmptyQuasis.every((quasi) => quasi === firstQuasi)) return Some(firstQuasi.repeat(nonEmptyQuasis.length));
|
|
99
96
|
return None;
|
|
100
97
|
}
|
|
@@ -106,6 +103,5 @@ function extractRepeatedStringFromCallExpression(expr, acc) {
|
|
|
106
103
|
if (expr.callee.object.name !== acc.name) return None;
|
|
107
104
|
return Option.fromUndef(expr.arguments[0]);
|
|
108
105
|
}
|
|
109
|
-
|
|
110
106
|
//#endregion
|
|
111
|
-
export { prefer_repeat_default as default };
|
|
107
|
+
export { prefer_repeat_default as default };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { WyrmPluginDocs } from "../utils/createRule.mjs";
|
|
2
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
3
|
+
|
|
4
|
+
//#region lib/rules/prefer-satisfies.d.ts
|
|
5
|
+
declare const _default: ESLintUtils.RuleModule<"preferSatisfies", [], WyrmPluginDocs, ESLintUtils.RuleListener> & {
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { _default };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createRule } from "../utils/createRule.mjs";
|
|
2
|
+
import { None, Some } from "../utils/option.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
|
|
5
|
+
import ts from "typescript";
|
|
6
|
+
//#region lib/rules/prefer-satisfies.ts
|
|
7
|
+
const { name } = path.parse(import.meta.filename);
|
|
8
|
+
var prefer_satisfies_default = createRule({
|
|
9
|
+
name,
|
|
10
|
+
meta: {
|
|
11
|
+
type: "suggestion",
|
|
12
|
+
docs: {
|
|
13
|
+
description: "Prefer `satisfies` to type assertions",
|
|
14
|
+
strict: true,
|
|
15
|
+
requiresTypeChecking: true
|
|
16
|
+
},
|
|
17
|
+
hasSuggestions: true,
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: { preferSatisfies: "Use `satisfies` instead of `as`" }
|
|
20
|
+
},
|
|
21
|
+
defaultOptions: [],
|
|
22
|
+
create(context) {
|
|
23
|
+
let services;
|
|
24
|
+
function getServices() {
|
|
25
|
+
services ??= ESLintUtils.getParserServices(context);
|
|
26
|
+
return services;
|
|
27
|
+
}
|
|
28
|
+
let checker;
|
|
29
|
+
function getChecker() {
|
|
30
|
+
checker ??= getServices().program.getTypeChecker();
|
|
31
|
+
return checker;
|
|
32
|
+
}
|
|
33
|
+
return { TSAsExpression(node) {
|
|
34
|
+
if (node.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && node.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && node.typeAnnotation.typeName.name === "const") return;
|
|
35
|
+
const assertedType = getServices().getTypeFromTypeNode(node.typeAnnotation);
|
|
36
|
+
if (hasOptionalProperties(assertedType)) return;
|
|
37
|
+
if (getObjectValueTypes(assertedType, getChecker()).some((t) => hasOptionalProperties(t))) return;
|
|
38
|
+
if (getChecker().getIndexInfoOfType(assertedType, ts.IndexKind.String) !== void 0) return;
|
|
39
|
+
if (isPromiseType(assertedType)) return;
|
|
40
|
+
const flags = ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never | ts.TypeFlags.EnumLike;
|
|
41
|
+
if (assertedType.getFlags() & flags) return;
|
|
42
|
+
const exprType = getServices().getTypeAtLocation(node.expression);
|
|
43
|
+
if (exprType.getFlags() & flags) return;
|
|
44
|
+
if (isPromiseType(exprType)) return;
|
|
45
|
+
if (!exprType.getProperties().length) return;
|
|
46
|
+
if (!getChecker().isTypeAssignableTo(exprType, assertedType)) return;
|
|
47
|
+
if (!getChecker().isTypeAssignableTo(assertedType, exprType)) return;
|
|
48
|
+
context.report({
|
|
49
|
+
node,
|
|
50
|
+
messageId: "preferSatisfies",
|
|
51
|
+
suggest: [{
|
|
52
|
+
messageId: "preferSatisfies",
|
|
53
|
+
fix(fixer) {
|
|
54
|
+
const asToken = context.sourceCode.getTokenBefore(node.typeAnnotation);
|
|
55
|
+
return fixer.replaceText(asToken, "satisfies");
|
|
56
|
+
}
|
|
57
|
+
}]
|
|
58
|
+
});
|
|
59
|
+
} };
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
function isPromiseType(type) {
|
|
63
|
+
return type.getSymbol()?.getName() === "Promise";
|
|
64
|
+
}
|
|
65
|
+
function getObjectValueTypes(type, checker) {
|
|
66
|
+
if (type.isUnionOrIntersection()) return type.types.flatMap((t) => getObjectValueTypes(t, checker));
|
|
67
|
+
if (!isObjectType(type)) return [];
|
|
68
|
+
return checker.getPropertiesOfType(type).map((prop) => checker.getTypeOfSymbol(prop));
|
|
69
|
+
}
|
|
70
|
+
function hasOptionalProperties(type) {
|
|
71
|
+
if (type.isUnionOrIntersection()) return type.types.some((t) => hasOptionalProperties(t));
|
|
72
|
+
if (!isObjectType(type)) return type.isTypeParameter();
|
|
73
|
+
const maybeMappedOptional = getOptionalMappedType(type);
|
|
74
|
+
if (maybeMappedOptional.some) return maybeMappedOptional.value;
|
|
75
|
+
return type.getProperties().some((prop) => (prop.flags & ts.SymbolFlags.Optional) !== 0);
|
|
76
|
+
}
|
|
77
|
+
function isObjectType(type) {
|
|
78
|
+
return (type.getFlags() & ts.TypeFlags.Object) !== 0;
|
|
79
|
+
}
|
|
80
|
+
function getOptionalMappedType(type) {
|
|
81
|
+
if ((type.objectFlags & ts.ObjectFlags.Mapped) === 0) return None;
|
|
82
|
+
if ((type.objectFlags & ts.ObjectFlags.Instantiated) !== 0) return None;
|
|
83
|
+
return Some(!!type.getSymbol()?.declarations?.some((decl) => ts.isMappedTypeNode(decl) && decl.questionToken));
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { prefer_satisfies_default as default };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { WyrmPluginDocs } from "../utils/createRule.mjs";
|
|
2
|
+
import { TSESLint } from "@typescript-eslint/utils";
|
|
3
|
+
|
|
4
|
+
//#region lib/rules/prefer-string-join.d.ts
|
|
5
|
+
declare const _default: TSESLint.RuleModule<"preferJoin", [], WyrmPluginDocs, TSESLint.RuleListener> & {
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { _default };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createRule } from "../utils/createRule.mjs";
|
|
2
|
+
import { None, Option, Some } from "../utils/option.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { ASTUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
5
|
+
//#region lib/rules/prefer-string-join.ts
|
|
6
|
+
const { name } = path.parse(import.meta.filename);
|
|
7
|
+
var prefer_string_join_default = createRule({
|
|
8
|
+
name,
|
|
9
|
+
meta: {
|
|
10
|
+
type: "suggestion",
|
|
11
|
+
docs: {
|
|
12
|
+
description: "Enforce usage of `String.prototype.join`",
|
|
13
|
+
recommended: true
|
|
14
|
+
},
|
|
15
|
+
fixable: "code",
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: { preferJoin: "This could be simplified as `{{fixed}}`" }
|
|
18
|
+
},
|
|
19
|
+
defaultOptions: [],
|
|
20
|
+
create(context) {
|
|
21
|
+
return { ForOfStatement(node) {
|
|
22
|
+
const maybeStmt = getUniqueStatement(node.body);
|
|
23
|
+
if (!maybeStmt.some) return;
|
|
24
|
+
const stmt = maybeStmt.value;
|
|
25
|
+
if (stmt.type !== AST_NODE_TYPES.ExpressionStatement) return;
|
|
26
|
+
if (stmt.expression.type !== AST_NODE_TYPES.AssignmentExpression) return;
|
|
27
|
+
if (stmt.expression.operator !== "+=") return;
|
|
28
|
+
if (stmt.expression.left.type !== AST_NODE_TYPES.Identifier) return;
|
|
29
|
+
if (stmt.expression.right.type !== AST_NODE_TYPES.Identifier) return;
|
|
30
|
+
const scope = context.sourceCode.getScope(node);
|
|
31
|
+
const leftDef = findDef(scope, stmt.expression.left);
|
|
32
|
+
if (!leftDef) return;
|
|
33
|
+
if (leftDef.node.type !== AST_NODE_TYPES.VariableDeclarator) return;
|
|
34
|
+
const { init } = leftDef.node;
|
|
35
|
+
if (!init) return;
|
|
36
|
+
if (init.type !== AST_NODE_TYPES.Literal) return;
|
|
37
|
+
const initValue = init.value;
|
|
38
|
+
if (typeof initValue !== "string") return;
|
|
39
|
+
const rightDef = findDef(scope, stmt.expression.right);
|
|
40
|
+
if (!rightDef) return;
|
|
41
|
+
if (rightDef.node.parent !== node.left) return;
|
|
42
|
+
const stringsTxt = context.sourceCode.getText(node.right);
|
|
43
|
+
const initTxt = context.sourceCode.getText(init);
|
|
44
|
+
const fixed = `${stringsTxt}.join()`;
|
|
45
|
+
const txt = initValue ? `${initTxt}.concat(${fixed})` : fixed;
|
|
46
|
+
context.report({
|
|
47
|
+
node,
|
|
48
|
+
messageId: "preferJoin",
|
|
49
|
+
data: { fixed },
|
|
50
|
+
*fix(fixer) {
|
|
51
|
+
yield fixer.replaceText(init, txt);
|
|
52
|
+
yield fixer.remove(node);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
} };
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
function findDef(scope, ident) {
|
|
59
|
+
return ASTUtils.findVariable(scope, ident)?.defs.at(-1);
|
|
60
|
+
}
|
|
61
|
+
function getUniqueStatement(stmt) {
|
|
62
|
+
if (stmt.type !== AST_NODE_TYPES.BlockStatement) return Some(stmt);
|
|
63
|
+
if (stmt.body.length !== 1) return None;
|
|
64
|
+
return Option.fromUndef(stmt.body[0]);
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { prefer_string_join_default as default };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createRule } from "../utils/createRule.mjs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
|
|
4
|
-
import * as ts from "typescript";
|
|
5
|
-
|
|
4
|
+
import * as ts$1 from "typescript";
|
|
6
5
|
//#region lib/rules/primitive-valueof.ts
|
|
7
6
|
const { name } = path.parse(import.meta.filename);
|
|
8
7
|
var primitive_valueof_default = createRule({
|
|
@@ -39,12 +38,11 @@ var primitive_valueof_default = createRule({
|
|
|
39
38
|
});
|
|
40
39
|
function isPrimitiveOnly(type) {
|
|
41
40
|
if (type.isUnion()) return type.types.every((t) => isPrimitiveOnly(t));
|
|
42
|
-
if (type.flags & ts.TypeFlags.NumberLike) return true;
|
|
43
|
-
if (type.flags & ts.TypeFlags.BooleanLike) return true;
|
|
44
|
-
if (type.flags & ts.TypeFlags.StringLike) return true;
|
|
45
|
-
if (type.flags & ts.TypeFlags.BigIntLike) return true;
|
|
41
|
+
if (type.flags & ts$1.TypeFlags.NumberLike) return true;
|
|
42
|
+
if (type.flags & ts$1.TypeFlags.BooleanLike) return true;
|
|
43
|
+
if (type.flags & ts$1.TypeFlags.StringLike) return true;
|
|
44
|
+
if (type.flags & ts$1.TypeFlags.BigIntLike) return true;
|
|
46
45
|
return false;
|
|
47
46
|
}
|
|
48
|
-
|
|
49
47
|
//#endregion
|
|
50
|
-
export { primitive_valueof_default as default };
|
|
48
|
+
export { primitive_valueof_default as default };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { WyrmPluginDocs } from "../utils/createRule.mjs";
|
|
2
|
+
import * as _$_typescript_eslint_utils_ts_eslint0 from "@typescript-eslint/utils/ts-eslint";
|
|
3
|
+
|
|
4
|
+
//#region lib/rules/slim-try.d.ts
|
|
5
|
+
declare const _default: _$_typescript_eslint_utils_ts_eslint0.RuleModule<"noSafeLine", [], WyrmPluginDocs, _$_typescript_eslint_utils_ts_eslint0.RuleListener> & {
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { _default };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createRule } from "../utils/createRule.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
4
|
+
//#region lib/rules/slim-try.ts
|
|
5
|
+
/**
|
|
6
|
+
* @fileoverview
|
|
7
|
+
*
|
|
8
|
+
* This rule warns when the first statement in a `try` block is unlikely to raise any exception.
|
|
9
|
+
*
|
|
10
|
+
* The intention is to reduce the size of `try` blocks that can sometimes be unnecessarily huge.
|
|
11
|
+
*/
|
|
12
|
+
const { name } = path.parse(import.meta.filename);
|
|
13
|
+
var slim_try_default = createRule({
|
|
14
|
+
name,
|
|
15
|
+
meta: {
|
|
16
|
+
type: "problem",
|
|
17
|
+
docs: {
|
|
18
|
+
description: "Enforce moving safe statements out of `try` blocks",
|
|
19
|
+
strict: true
|
|
20
|
+
},
|
|
21
|
+
fixable: "code",
|
|
22
|
+
schema: [],
|
|
23
|
+
messages: { noSafeLine: "It looks like this statement can safely be moved before the try block" }
|
|
24
|
+
},
|
|
25
|
+
defaultOptions: [],
|
|
26
|
+
create(context) {
|
|
27
|
+
return { TryStatement(node) {
|
|
28
|
+
const [stmt] = node.block.body;
|
|
29
|
+
if (!stmt) return;
|
|
30
|
+
if (!isSafeStatement(stmt)) return;
|
|
31
|
+
context.report({
|
|
32
|
+
node: stmt,
|
|
33
|
+
messageId: "noSafeLine",
|
|
34
|
+
*fix(fixer) {
|
|
35
|
+
const txt = context.sourceCode.getText(stmt);
|
|
36
|
+
yield fixer.remove(stmt);
|
|
37
|
+
yield fixer.insertTextBefore(node, `${txt}\n`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
} };
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
function isSafeStatement(stmt) {
|
|
44
|
+
const stmtType = stmt.type;
|
|
45
|
+
switch (stmtType) {
|
|
46
|
+
case AST_NODE_TYPES.BlockStatement: return stmt.body.every((statement) => isSafeStatement(statement));
|
|
47
|
+
case AST_NODE_TYPES.BreakStatement:
|
|
48
|
+
case AST_NODE_TYPES.ClassDeclaration:
|
|
49
|
+
case AST_NODE_TYPES.ContinueStatement:
|
|
50
|
+
case AST_NODE_TYPES.DebuggerStatement: return true;
|
|
51
|
+
case AST_NODE_TYPES.DoWhileStatement: return isSafeStatement(stmt.body);
|
|
52
|
+
case AST_NODE_TYPES.EmptyStatement: return true;
|
|
53
|
+
case AST_NODE_TYPES.ExportAllDeclaration:
|
|
54
|
+
case AST_NODE_TYPES.ExportDefaultDeclaration:
|
|
55
|
+
case AST_NODE_TYPES.ExportNamedDeclaration: return false;
|
|
56
|
+
case AST_NODE_TYPES.ExpressionStatement: return isSafeExpression(stmt.expression);
|
|
57
|
+
case AST_NODE_TYPES.ForInStatement:
|
|
58
|
+
case AST_NODE_TYPES.ForOfStatement: return isSafeInitialiser(stmt.left) && isSafeExpression(stmt.right) && isSafeStatement(stmt.body);
|
|
59
|
+
case AST_NODE_TYPES.ForStatement: return isSafeInitialiser(stmt.init) && isSafeInitialiser(stmt.test) && isSafeInitialiser(stmt.update) && isSafeStatement(stmt.body);
|
|
60
|
+
case AST_NODE_TYPES.FunctionDeclaration: return true;
|
|
61
|
+
case AST_NODE_TYPES.IfStatement: return isSafeStatement(stmt.consequent) && (!stmt.alternate || isSafeStatement(stmt.alternate));
|
|
62
|
+
case AST_NODE_TYPES.ImportDeclaration: return false;
|
|
63
|
+
case AST_NODE_TYPES.LabeledStatement: return isSafeStatement(stmt.body);
|
|
64
|
+
case AST_NODE_TYPES.ReturnStatement: return !stmt.argument || isSafeExpression(stmt.argument);
|
|
65
|
+
case AST_NODE_TYPES.SwitchStatement: return isSafeExpression(stmt.discriminant) && stmt.cases.every((c) => (!c.test || isSafeExpression(c.test)) && c.consequent.every((statement) => isSafeStatement(statement)));
|
|
66
|
+
case AST_NODE_TYPES.ThrowStatement:
|
|
67
|
+
case AST_NODE_TYPES.TryStatement: return false;
|
|
68
|
+
case AST_NODE_TYPES.TSDeclareFunction:
|
|
69
|
+
case AST_NODE_TYPES.TSEnumDeclaration:
|
|
70
|
+
case AST_NODE_TYPES.TSExportAssignment:
|
|
71
|
+
case AST_NODE_TYPES.TSImportEqualsDeclaration:
|
|
72
|
+
case AST_NODE_TYPES.TSInterfaceDeclaration:
|
|
73
|
+
case AST_NODE_TYPES.TSModuleDeclaration:
|
|
74
|
+
case AST_NODE_TYPES.TSNamespaceExportDeclaration:
|
|
75
|
+
case AST_NODE_TYPES.TSTypeAliasDeclaration: return true;
|
|
76
|
+
case AST_NODE_TYPES.VariableDeclaration: return stmt.declarations.every((decl) => !decl.init || isSafeExpression(decl.id) && isSafeExpression(decl.init));
|
|
77
|
+
case AST_NODE_TYPES.WhileStatement: return isSafeExpression(stmt.test) && isSafeStatement(stmt.body);
|
|
78
|
+
case AST_NODE_TYPES.WithStatement: return isSafeExpression(stmt.object) && isSafeStatement(stmt.body);
|
|
79
|
+
/* v8 ignore next -- @preserve */
|
|
80
|
+
default:
|
|
81
|
+
console.error(`[wyrm] Unexpected statement type: ${stmtType}`);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function isSafeExpression(expr) {
|
|
86
|
+
const exprType = expr.type;
|
|
87
|
+
switch (exprType) {
|
|
88
|
+
case AST_NODE_TYPES.ArrayExpression: return expr.elements.every((elt) => {
|
|
89
|
+
if (!elt) return true;
|
|
90
|
+
if (elt.type === AST_NODE_TYPES.SpreadElement) return isSafeExpression(elt.argument);
|
|
91
|
+
return isSafeExpression(elt);
|
|
92
|
+
});
|
|
93
|
+
case AST_NODE_TYPES.ArrayPattern: return expr.elements.every((elt) => !elt || isSafeExpression(elt));
|
|
94
|
+
case AST_NODE_TYPES.ArrowFunctionExpression: return true;
|
|
95
|
+
case AST_NODE_TYPES.AssignmentPattern:
|
|
96
|
+
case AST_NODE_TYPES.AssignmentExpression: return isSafeExpression(expr.right);
|
|
97
|
+
case AST_NODE_TYPES.AwaitExpression: return false;
|
|
98
|
+
case AST_NODE_TYPES.BinaryExpression:
|
|
99
|
+
if (expr.operator === "in" || expr.operator === "instanceof") return false;
|
|
100
|
+
return isSafeExpression(expr.left) && isSafeExpression(expr.right);
|
|
101
|
+
case AST_NODE_TYPES.CallExpression: return false;
|
|
102
|
+
case AST_NODE_TYPES.ChainExpression: return isSafeExpression(expr.expression);
|
|
103
|
+
case AST_NODE_TYPES.ClassExpression: return true;
|
|
104
|
+
case AST_NODE_TYPES.ConditionalExpression: return isSafeExpression(expr.test) && isSafeExpression(expr.consequent) && isSafeExpression(expr.alternate);
|
|
105
|
+
case AST_NODE_TYPES.FunctionExpression:
|
|
106
|
+
case AST_NODE_TYPES.Identifier: return true;
|
|
107
|
+
case AST_NODE_TYPES.ImportExpression:
|
|
108
|
+
case AST_NODE_TYPES.JSXElement:
|
|
109
|
+
case AST_NODE_TYPES.JSXFragment: return false;
|
|
110
|
+
case AST_NODE_TYPES.Literal: return true;
|
|
111
|
+
case AST_NODE_TYPES.LogicalExpression: return isSafeExpression(expr.left) && isSafeExpression(expr.right);
|
|
112
|
+
case AST_NODE_TYPES.MemberExpression: return false;
|
|
113
|
+
case AST_NODE_TYPES.MetaProperty: return true;
|
|
114
|
+
case AST_NODE_TYPES.NewExpression: return false;
|
|
115
|
+
case AST_NODE_TYPES.ObjectExpression: return expr.properties.every((prop) => {
|
|
116
|
+
if (prop.type === AST_NODE_TYPES.SpreadElement) return isSafeExpression(prop.argument);
|
|
117
|
+
return isSafeExpression(prop.key) && isSafeExpression(prop.value);
|
|
118
|
+
});
|
|
119
|
+
case AST_NODE_TYPES.ObjectPattern: return expr.properties.every((prop) => isSafeExpression(prop));
|
|
120
|
+
case AST_NODE_TYPES.Property: return isSafeExpression(expr.key) && isSafeExpression(expr.value);
|
|
121
|
+
case AST_NODE_TYPES.RestElement: return isSafeExpression(expr.argument);
|
|
122
|
+
case AST_NODE_TYPES.SequenceExpression: return expr.expressions.every((expression) => isSafeExpression(expression));
|
|
123
|
+
/* v8 ignore next -- @preserve */
|
|
124
|
+
case AST_NODE_TYPES.Super:
|
|
125
|
+
case AST_NODE_TYPES.TaggedTemplateExpression: return false;
|
|
126
|
+
case AST_NODE_TYPES.TemplateLiteral: return expr.expressions.every((expression) => isSafeExpression(expression));
|
|
127
|
+
case AST_NODE_TYPES.ThisExpression: return true;
|
|
128
|
+
case AST_NODE_TYPES.TSAsExpression: return isSafeExpression(expr.expression);
|
|
129
|
+
/* v8 ignore next -- @preserve */
|
|
130
|
+
case AST_NODE_TYPES.TSEmptyBodyFunctionExpression:
|
|
131
|
+
case AST_NODE_TYPES.TSInstantiationExpression: return true;
|
|
132
|
+
case AST_NODE_TYPES.TSNonNullExpression:
|
|
133
|
+
case AST_NODE_TYPES.TSSatisfiesExpression:
|
|
134
|
+
case AST_NODE_TYPES.TSTypeAssertion: return isSafeExpression(expr.expression);
|
|
135
|
+
case AST_NODE_TYPES.UnaryExpression:
|
|
136
|
+
case AST_NODE_TYPES.UpdateExpression:
|
|
137
|
+
case AST_NODE_TYPES.YieldExpression: return !expr.argument || isSafeExpression(expr.argument);
|
|
138
|
+
/* v8 ignore next -- @preserve */
|
|
139
|
+
default:
|
|
140
|
+
console.error(`[wyrm] Unexpected expression type: ${exprType}`);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function isSafeInitialiser(initialiser) {
|
|
145
|
+
if (!initialiser) return true;
|
|
146
|
+
if (initialiser.type === AST_NODE_TYPES.VariableDeclaration) return isSafeStatement(initialiser);
|
|
147
|
+
return isSafeExpression(initialiser);
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
export { slim_try_default as default };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { WyrmPluginDocs } from "../utils/createRule.mjs";
|
|
2
|
+
import { TSESLint } from "@typescript-eslint/utils";
|
|
3
|
+
|
|
4
|
+
//#region lib/rules/styled-button-has-type.d.ts
|
|
5
|
+
declare const _default: TSESLint.RuleModule<"missingType" | "complexType" | "invalidValue", [], WyrmPluginDocs, TSESLint.RuleListener> & {
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { _default };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createRule } from "../utils/createRule.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ASTUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
4
|
+
//#region lib/rules/styled-button-has-type.ts
|
|
5
|
+
/**
|
|
6
|
+
* @fileoverview
|
|
7
|
+
*
|
|
8
|
+
* This will only work if the styled component is declared in the same file where it is used.
|
|
9
|
+
*/
|
|
10
|
+
const { name } = path.parse(import.meta.filename);
|
|
11
|
+
var styled_button_has_type_default = createRule({
|
|
12
|
+
name,
|
|
13
|
+
meta: {
|
|
14
|
+
type: "problem",
|
|
15
|
+
docs: {
|
|
16
|
+
description: "Disallow usage of `styled.button` without an explicit `type` attribute.",
|
|
17
|
+
recommended: true
|
|
18
|
+
},
|
|
19
|
+
schema: [],
|
|
20
|
+
messages: {
|
|
21
|
+
missingType: "Missing an explicit type attribute for button",
|
|
22
|
+
complexType: "The button type attribute must be specified by a static string",
|
|
23
|
+
invalidValue: "\"{{value}}\" is an invalid value for button type attribute"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
defaultOptions: [],
|
|
27
|
+
create(context) {
|
|
28
|
+
return { JSXElement(node) {
|
|
29
|
+
if (node.openingElement.name.type !== AST_NODE_TYPES.JSXIdentifier) return;
|
|
30
|
+
const def = findDef(context.sourceCode.getScope(node), node.openingElement.name.name);
|
|
31
|
+
if (!def) return;
|
|
32
|
+
if (def.node.type !== AST_NODE_TYPES.VariableDeclarator) return;
|
|
33
|
+
const { init } = def.node;
|
|
34
|
+
if (!init) return;
|
|
35
|
+
if (!isStyledHtmlButton(init)) return;
|
|
36
|
+
const found = node.openingElement.attributes.filter((attr) => attr.type === AST_NODE_TYPES.JSXAttribute).find((attr) => attr.name.name === "type");
|
|
37
|
+
if (!found) {
|
|
38
|
+
context.report({
|
|
39
|
+
node,
|
|
40
|
+
messageId: "missingType"
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (found.value?.type !== AST_NODE_TYPES.Literal || typeof found.value.value !== "string") {
|
|
45
|
+
context.report({
|
|
46
|
+
node,
|
|
47
|
+
messageId: "complexType"
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if ([
|
|
52
|
+
"button",
|
|
53
|
+
"submit",
|
|
54
|
+
"reset"
|
|
55
|
+
].includes(found.value.value)) return;
|
|
56
|
+
context.report({
|
|
57
|
+
node,
|
|
58
|
+
messageId: "invalidValue",
|
|
59
|
+
data: { value: found.value.value }
|
|
60
|
+
});
|
|
61
|
+
} };
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
function findDef(scope, ident) {
|
|
65
|
+
return ASTUtils.findVariable(scope, ident)?.defs.at(-1);
|
|
66
|
+
}
|
|
67
|
+
function isStyledHtmlButton(expr) {
|
|
68
|
+
if (expr.type !== AST_NODE_TYPES.TaggedTemplateExpression) return false;
|
|
69
|
+
return isStyledHtmlElement(expr.tag, "button");
|
|
70
|
+
}
|
|
71
|
+
function isStyledHtmlElement(expr, htmlTag) {
|
|
72
|
+
if (expr.type === AST_NODE_TYPES.MemberExpression) return expr.object.type === AST_NODE_TYPES.Identifier && expr.object.name === "styled" && expr.property.type === AST_NODE_TYPES.Identifier && expr.property.name === htmlTag;
|
|
73
|
+
if (expr.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
74
|
+
const [arg] = expr.arguments;
|
|
75
|
+
if (!arg) return false;
|
|
76
|
+
if (arg.type !== AST_NODE_TYPES.Literal) return false;
|
|
77
|
+
if (arg.value !== htmlTag) return false;
|
|
78
|
+
if (expr.callee.type !== AST_NODE_TYPES.Identifier) return false;
|
|
79
|
+
return expr.callee.name === "styled";
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { styled_button_has_type_default as default };
|