oxlint-plugin-arch 0.11.0 → 0.12.1
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 +241 -0
- package/README.md +52 -218
- package/dist/index.js +5205 -4073
- package/dist/preset/groups/backend.d.ts +2 -0
- package/dist/preset/groups/basics.d.ts +2 -0
- package/dist/preset/groups/comments.d.ts +2 -0
- package/dist/preset/groups/console.d.ts +2 -0
- package/dist/preset/groups/deny.d.ts +2 -0
- package/dist/preset/groups/expo.d.ts +2 -0
- package/dist/preset/groups/externals.d.ts +2 -0
- package/dist/preset/groups/forms.d.ts +2 -0
- package/dist/preset/groups/imports.d.ts +2 -0
- package/dist/preset/groups/layout.d.ts +2 -0
- package/dist/preset/groups/limits.d.ts +2 -0
- package/dist/preset/groups/naming.d.ts +2 -0
- package/dist/preset/groups/next.d.ts +2 -0
- package/dist/preset/groups/orpc.d.ts +2 -0
- package/dist/preset/groups/react.d.ts +2 -0
- package/dist/preset/groups/schemas.d.ts +2 -0
- package/dist/preset/groups/shadcn.d.ts +2 -0
- package/dist/preset/groups/sql.d.ts +2 -0
- package/dist/preset/groups/style.d.ts +2 -0
- package/dist/preset/groups/tailwind.d.ts +2 -0
- package/dist/preset/groups/tanstack.d.ts +2 -0
- package/dist/preset/groups/tests.d.ts +2 -0
- package/dist/preset/groups/types.d.ts +2 -0
- package/dist/preset/preset-detect.d.ts +3 -0
- package/dist/preset/preset-folders.d.ts +2 -0
- package/dist/preset/preset-recipes.d.ts +82 -0
- package/dist/preset/preset-stylesheet.d.ts +1 -0
- package/dist/preset/preset-units.d.ts +2 -0
- package/dist/preset/preset.constants.d.ts +79 -0
- package/dist/preset/preset.d.ts +89 -0
- package/dist/preset/preset.js +2299 -0
- package/dist/preset/preset.utils.d.ts +14 -0
- package/dist/rules/backend/backend-orpc-output.d.ts +1 -0
- package/dist/rules/backend/backend-registry.d.ts +1 -0
- package/dist/rules/backend/backend-service-functions.d.ts +1 -0
- package/dist/rules/backend/backend-sql-like.d.ts +1 -0
- package/dist/rules/boundaries/boundaries-deny.d.ts +1 -0
- package/dist/rules/boundaries/boundaries-imports.d.ts +1 -0
- package/dist/rules/boundaries/boundaries-next.d.ts +1 -0
- package/dist/rules/boundaries/boundaries-rpc-database.d.ts +1 -0
- package/dist/rules/boundaries/boundaries-tanstack.d.ts +1 -0
- package/dist/rules/boundaries/boundaries-token-owner.d.ts +1 -0
- package/dist/rules/code/code-constructor.d.ts +1 -0
- package/dist/rules/code/code-double-negation.d.ts +1 -0
- package/dist/rules/code/code-literal-in.d.ts +1 -0
- package/dist/rules/code/code-mutable-state.d.ts +1 -0
- package/dist/rules/code/code-object-params.d.ts +1 -0
- package/dist/rules/code/code-paired-call.d.ts +1 -0
- package/dist/rules/code/code-promise-mutation.d.ts +1 -0
- package/dist/rules/code/code-switch.d.ts +1 -0
- package/dist/rules/code/code-trivial-functions.d.ts +1 -0
- package/dist/rules/comments/comments-code.d.ts +1 -0
- package/dist/rules/comments/comments-directives.d.ts +1 -0
- package/dist/rules/copy/copy-dashes.d.ts +1 -0
- package/dist/rules/files/files-components-only.d.ts +1 -0
- package/dist/rules/files/files-constants-only.d.ts +1 -0
- package/dist/rules/files/files-domain-constants.d.ts +1 -0
- package/dist/rules/files/files-exports.d.ts +1 -0
- package/dist/rules/files/files-externals.d.ts +1 -0
- package/dist/rules/files/files-factory-keys.d.ts +1 -0
- package/dist/rules/files/files-helpers.d.ts +1 -0
- package/dist/rules/files/files-no-functions.d.ts +1 -0
- package/dist/rules/files/files-restricted.d.ts +1 -0
- package/dist/rules/files/files-route.d.ts +1 -0
- package/dist/rules/files/files-types-only.d.ts +1 -0
- package/dist/rules/layout/layout-call-arguments.d.ts +1 -0
- package/dist/rules/layout/layout-call-array.d.ts +1 -0
- package/dist/rules/layout/layout-chain.d.ts +1 -0
- package/dist/rules/layout/layout-jsx-attributes.d.ts +1 -0
- package/dist/rules/layout/layout-key-value.d.ts +1 -0
- package/dist/rules/layout/layout-object.d.ts +1 -0
- package/dist/rules/layout/layout-padding.d.ts +1 -0
- package/dist/rules/layout/layout-ternary.d.ts +1 -0
- package/dist/rules/naming/naming-declarations.d.ts +1 -0
- package/dist/rules/naming/naming-exports.d.ts +1 -0
- package/dist/rules/naming/naming-file.d.ts +1 -0
- package/dist/rules/naming/naming-folder.d.ts +1 -0
- package/dist/rules/naming/naming-types.d.ts +1 -0
- package/dist/rules/react/react-dynamic-classes.d.ts +1 -0
- package/dist/rules/react/react-empty-effect.d.ts +1 -0
- package/dist/rules/react/react-memo.d.ts +1 -0
- package/dist/rules/react/react-props.d.ts +1 -0
- package/dist/rules/schemas/schemas-inline.d.ts +1 -0
- package/dist/rules/schemas/schemas-local.d.ts +1 -0
- package/dist/rules/schemas/schemas-rederive.d.ts +1 -0
- package/dist/rules/schemas/schemas-single-use.d.ts +1 -0
- package/dist/rules/tests/tests-assertions.d.ts +1 -0
- package/dist/rules/tests/tests-concurrent-db.d.ts +1 -0
- package/dist/rules/tests/tests-modifiers.d.ts +1 -0
- package/dist/rules/tests/tests-titles.d.ts +1 -0
- package/dist/rules/types/types-declarations.d.ts +1 -0
- package/dist/rules/types/types-imported-alias.d.ts +1 -0
- package/dist/rules/types/types-inline.d.ts +1 -0
- package/dist/rules/types/types-local-alias.d.ts +1 -0
- package/dist/rules/types/types-namespace-import.d.ts +1 -0
- package/dist/rules/types/types-service.d.ts +1 -0
- package/dist/rules/types/types-unknown.d.ts +1 -0
- package/dist/types/ast.types.d.ts +2 -0
- package/dist/types/backend.types.d.ts +24 -0
- package/dist/types/boundaries.types.d.ts +72 -0
- package/dist/types/code.types.d.ts +59 -0
- package/dist/types/comments.types.d.ts +12 -0
- package/dist/types/copy.types.d.ts +4 -0
- package/dist/types/declarations.types.d.ts +20 -0
- package/dist/types/dillon-anti-slop.types.d.ts +55 -0
- package/dist/types/exports.types.d.ts +11 -0
- package/dist/types/files.types.d.ts +85 -0
- package/dist/types/layout.types.d.ts +55 -0
- package/dist/types/naming.types.d.ts +52 -0
- package/dist/types/options.types.d.ts +4 -0
- package/dist/types/preset.types.d.ts +248 -129
- package/dist/types/react.types.d.ts +14 -0
- package/dist/types/schemas.types.d.ts +20 -0
- package/dist/types/source.types.d.ts +1 -0
- package/dist/types/tests.types.d.ts +39 -0
- package/dist/types/types.types.d.ts +20 -0
- package/dist/utils/ast.utils.d.ts +19 -0
- package/dist/utils/boundaries.utils.d.ts +85 -0
- package/dist/utils/declarations.utils.d.ts +21 -0
- package/dist/utils/exports.utils.d.ts +6 -0
- package/dist/utils/naming.utils.d.ts +11 -0
- package/dist/utils/options.utils.d.ts +11 -0
- package/dist/utils/react.utils.d.ts +3 -0
- package/dist/utils/schemas.utils.d.ts +36 -0
- package/dist/utils/source.utils.d.ts +18 -0
- package/dist/vendor/dillon-anti-slop/index.js +1770 -0
- package/dist/vendor/dillon-anti-slop/rules/no-known-value-widening.d.ts +2 -0
- package/dist/vendor/dillon-anti-slop/rules/no-module-mocking.d.ts +2 -0
- package/dist/vendor/dillon-anti-slop/shared/dictionary-types.d.ts +5 -0
- package/dist/vendor/dillon-anti-slop/shared/expressions.d.ts +5 -0
- package/dist/vendor/dillon-anti-slop/shared/lexical-type-parameters.d.ts +2 -0
- package/dist/vendor/dillon-anti-slop/shared/reflect-method.d.ts +1 -0
- package/dist/vendor/dillon-anti-slop/shared/resolve-variable.d.ts +2 -0
- package/dist/vendor/dillon-anti-slop/shared/signatures.d.ts +15 -0
- package/dist/vendor/dillon-anti-slop/shared/type-aliases.d.ts +4 -0
- package/dist/vendor/dillon-anti-slop/shared/type-environment.d.ts +9 -0
- package/dist/vendor/dillon-anti-slop/shared/type-reference-name.d.ts +2 -0
- package/dist/vendor/dillon-anti-slop/shared/widening-targets.d.ts +3 -0
- package/package.json +51 -43
- package/src/{rules → vendor}/dillon-anti-slop/UPSTREAM.md +9 -6
- package/dist/configs/boundaries.d.ts +0 -2
- package/dist/configs/modules.d.ts +0 -2
- package/dist/configs/plugins.d.ts +0 -3
- package/dist/configs/react.d.ts +0 -2
- package/dist/configs/source.d.ts +0 -2
- package/dist/configs/tests.d.ts +0 -2
- package/dist/configs/types.d.ts +0 -2
- package/dist/constants/preset.constants.d.ts +0 -38
- package/dist/constants/test.constants.d.ts +0 -1
- package/dist/presets/index.d.ts +0 -4
- package/dist/presets/index.js +0 -1736
- package/dist/presets/next-react-modules-preset.d.ts +0 -4
- package/dist/presets/next-react-modules-preset.js +0 -1729
- package/dist/presets/react-modules-preset.d.ts +0 -4
- package/dist/presets/react-modules-preset.js +0 -1724
- package/dist/presets/tanstack-start-react-modules-preset.d.ts +0 -4
- package/dist/presets/tanstack-start-react-modules-preset.js +0 -1729
- package/dist/rules/dillon-anti-slop/index.js +0 -2719
- package/dist/rules/dillon-anti-slop/rules/no-known-value-widening.d.ts +0 -1
- package/dist/rules/dillon-anti-slop/rules/no-module-mocking.d.ts +0 -8
- package/dist/rules/dillon-anti-slop/shared/dictionary-types.d.ts +0 -19
- package/dist/rules/dillon-anti-slop/shared/lexical-type-parameters.d.ts +0 -4
- package/dist/rules/dillon-anti-slop/shared/reflect-method.d.ts +0 -2
- package/dist/rules/module-policy/domain-constants.d.ts +0 -2
- package/dist/rules/module-policy/import-boundaries.d.ts +0 -2
- package/dist/rules/module-policy/index.js +0 -2266
- package/dist/rules/module-policy/memoization.d.ts +0 -2
- package/dist/rules/module-policy/next-runtime.d.ts +0 -7
- package/dist/rules/module-policy/no-unknown.d.ts +0 -6
- package/dist/rules/module-policy/reasoned-directives.d.ts +0 -2
- package/dist/rules/module-policy/service-functions.d.ts +0 -2
- package/dist/rules/module-policy/service-types.d.ts +0 -2
- package/dist/rules/module-policy/tanstack-runtime.d.ts +0 -2
- package/dist/rules/module-policy/test-assertions.d.ts +0 -2
- package/dist/rules/module-policy/test-modifiers.d.ts +0 -2
- package/dist/services/preset-compose.d.ts +0 -3
- package/dist/services/preset-context.d.ts +0 -2
- package/dist/types/module-policy.types.d.ts +0 -39
- package/dist/types/test-analysis.types.d.ts +0 -11
- package/dist/utils/helpers/ast.d.ts +0 -12
- package/dist/utils/helpers/boundaries.d.ts +0 -3
- package/dist/utils/helpers/comments.d.ts +0 -6
- package/dist/utils/helpers/declarations.d.ts +0 -35
- package/dist/utils/helpers/exports.d.ts +0 -18
- package/dist/utils/helpers/factories.d.ts +0 -9
- package/dist/utils/helpers/guards.d.ts +0 -11
- package/dist/utils/helpers/index.d.ts +0 -18
- package/dist/utils/helpers/layout.d.ts +0 -15
- package/dist/utils/helpers/mocking.d.ts +0 -2
- package/dist/utils/helpers/naming.d.ts +0 -13
- package/dist/utils/helpers/options.d.ts +0 -22
- package/dist/utils/helpers/orpc.d.ts +0 -3
- package/dist/utils/helpers/preset.d.ts +0 -9
- package/dist/utils/helpers/react-components.d.ts +0 -13
- package/dist/utils/helpers/routes.d.ts +0 -5
- package/dist/utils/helpers/runtime.d.ts +0 -14
- package/dist/utils/helpers/sanitizers.d.ts +0 -3
- package/dist/utils/helpers/schemas.d.ts +0 -10
- package/dist/utils/helpers/syntax.d.ts +0 -1
- package/dist/utils/helpers/test-tables.d.ts +0 -8
- package/dist/utils/helpers/trivial.d.ts +0 -6
- package/dist/utils/index.d.ts +0 -1
- /package/dist/{rules/module-policy/index.d.ts → index.d.ts} +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/index.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-chained-type-assertions.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-conditional-empty-object-spread.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-object-parameters.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-reflect-apply.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-reflect-get.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-runtime-typeof.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-shape-in-symbol-names.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unknown-parameters.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unknown-returns.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unknown-type-aliases.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unsafe-dictionary-type.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-widen-then-assert.d.ts +0 -0
- /package/dist/{rules → vendor}/dillon-anti-slop/rules/require-safety-comment-for-type-assertion.d.ts +0 -0
- /package/src/{rules → vendor}/dillon-anti-slop/LICENSE +0 -0
|
@@ -1,2719 +0,0 @@
|
|
|
1
|
-
// src/rules/dillon-anti-slop/index.ts
|
|
2
|
-
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
3
|
-
|
|
4
|
-
// src/rules/dillon-anti-slop/rules/no-chained-type-assertions.ts
|
|
5
|
-
import { defineRule } from "@oxlint/plugins";
|
|
6
|
-
function isTypeAssertionExpression(node) {
|
|
7
|
-
return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
|
|
8
|
-
}
|
|
9
|
-
function unwrapParenthesizedExpression(expression) {
|
|
10
|
-
let current = expression;
|
|
11
|
-
while (current.type === "ParenthesizedExpression") {
|
|
12
|
-
current = current.expression;
|
|
13
|
-
}
|
|
14
|
-
return current;
|
|
15
|
-
}
|
|
16
|
-
function isConstAssertion(node) {
|
|
17
|
-
const { typeAnnotation } = node;
|
|
18
|
-
return typeAnnotation.type === "TSTypeReference" && typeAnnotation.typeName.type === "Identifier" && typeAnnotation.typeName.name === "const";
|
|
19
|
-
}
|
|
20
|
-
function isOutermostAssertionInChain(node) {
|
|
21
|
-
let current = node;
|
|
22
|
-
let parent = node.parent;
|
|
23
|
-
while (parent.type === "ParenthesizedExpression" && parent.expression === current) {
|
|
24
|
-
current = parent;
|
|
25
|
-
parent = parent.parent;
|
|
26
|
-
}
|
|
27
|
-
return !isTypeAssertionExpression(parent) || parent.expression !== current;
|
|
28
|
-
}
|
|
29
|
-
function isForbiddenAssertionChain(node) {
|
|
30
|
-
let assertionCount = 0;
|
|
31
|
-
let hasNonConstAssertion = false;
|
|
32
|
-
let current = node;
|
|
33
|
-
while (isTypeAssertionExpression(current)) {
|
|
34
|
-
assertionCount += 1;
|
|
35
|
-
hasNonConstAssertion ||= !isConstAssertion(current);
|
|
36
|
-
current = unwrapParenthesizedExpression(current.expression);
|
|
37
|
-
}
|
|
38
|
-
return assertionCount > 1 && hasNonConstAssertion;
|
|
39
|
-
}
|
|
40
|
-
var noChainedTypeAssertionsRule = defineRule({
|
|
41
|
-
meta: {
|
|
42
|
-
type: "problem",
|
|
43
|
-
docs: {
|
|
44
|
-
description: "Disallow chained TypeScript as and angle-bracket assertions, including parenthesized chains."
|
|
45
|
-
},
|
|
46
|
-
messages: {
|
|
47
|
-
chained: "This assertion chain discards type evidence. Keep the original precise type, or parse untrusted input at its boundary before narrowing it."
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
createOnce(context) {
|
|
51
|
-
const checkTypeAssertion = (node) => {
|
|
52
|
-
if (!isOutermostAssertionInChain(node) || !isForbiddenAssertionChain(node))
|
|
53
|
-
return;
|
|
54
|
-
context.report({ node, messageId: "chained" });
|
|
55
|
-
};
|
|
56
|
-
return {
|
|
57
|
-
TSAsExpression: checkTypeAssertion,
|
|
58
|
-
TSTypeAssertion: checkTypeAssertion
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// src/rules/dillon-anti-slop/rules/no-conditional-empty-object-spread.ts
|
|
64
|
-
import { defineRule as defineRule2 } from "@oxlint/plugins";
|
|
65
|
-
function unwrapParentheses(node) {
|
|
66
|
-
let current = node;
|
|
67
|
-
while (current.type === "ParenthesizedExpression") {
|
|
68
|
-
current = current.expression;
|
|
69
|
-
}
|
|
70
|
-
return current;
|
|
71
|
-
}
|
|
72
|
-
function isEmptyObjectExpression(node) {
|
|
73
|
-
return node.type === "ObjectExpression" && node.properties.length === 0;
|
|
74
|
-
}
|
|
75
|
-
function isConditionalEmptyObjectSpread(node) {
|
|
76
|
-
const conditional = unwrapParentheses(node);
|
|
77
|
-
return conditional.type === "ConditionalExpression" && (isEmptyObjectExpression(conditional.consequent) || isEmptyObjectExpression(conditional.alternate));
|
|
78
|
-
}
|
|
79
|
-
var noConditionalEmptyObjectSpreadRule = defineRule2({
|
|
80
|
-
meta: {
|
|
81
|
-
type: "suggestion",
|
|
82
|
-
docs: {
|
|
83
|
-
description: "Disallow object spreads that conditionally spread an empty object to omit fields."
|
|
84
|
-
},
|
|
85
|
-
messages: {
|
|
86
|
-
avoid: "This conditional spread hides property omission behind an empty object. Build the object in separate statements and add the property only when present."
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
createOnce(context) {
|
|
90
|
-
return {
|
|
91
|
-
SpreadElement(node) {
|
|
92
|
-
if (node.parent.type !== "ObjectExpression")
|
|
93
|
-
return;
|
|
94
|
-
if (isConditionalEmptyObjectSpread(node.argument)) {
|
|
95
|
-
context.report({ node, messageId: "avoid" });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// src/rules/dillon-anti-slop/rules/no-known-value-widening.ts
|
|
103
|
-
import { defineRule as defineRule3 } from "@oxlint/plugins";
|
|
104
|
-
|
|
105
|
-
// src/rules/dillon-anti-slop/shared/dictionary-types.ts
|
|
106
|
-
var BUILT_INS = new Set([
|
|
107
|
-
"Record",
|
|
108
|
-
"Readonly",
|
|
109
|
-
"Partial",
|
|
110
|
-
"Required",
|
|
111
|
-
"Pick",
|
|
112
|
-
"Omit",
|
|
113
|
-
"PropertyKey",
|
|
114
|
-
"NonNullable"
|
|
115
|
-
]);
|
|
116
|
-
var TRANSPARENT_WRAPPERS = new Set([
|
|
117
|
-
"Readonly",
|
|
118
|
-
"Partial",
|
|
119
|
-
"Required",
|
|
120
|
-
"NonNullable"
|
|
121
|
-
]);
|
|
122
|
-
function declaredStatement(statement) {
|
|
123
|
-
return statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration ?? null : statement;
|
|
124
|
-
}
|
|
125
|
-
function createTypeEnvironment(program) {
|
|
126
|
-
const aliases = new Map;
|
|
127
|
-
const interfaces = new Map;
|
|
128
|
-
const shadowedBuiltIns = new Set;
|
|
129
|
-
for (const statement of program.body) {
|
|
130
|
-
const declaration = declaredStatement(statement);
|
|
131
|
-
if (declaration?.type === "ImportDeclaration") {
|
|
132
|
-
for (const specifier of declaration.specifiers) {
|
|
133
|
-
if (BUILT_INS.has(specifier.local.name))
|
|
134
|
-
shadowedBuiltIns.add(specifier.local.name);
|
|
135
|
-
}
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
if (declaration?.type === "TSTypeAliasDeclaration") {
|
|
139
|
-
const existing = aliases.get(declaration.id.name);
|
|
140
|
-
if (existing === undefined)
|
|
141
|
-
aliases.set(declaration.id.name, declaration);
|
|
142
|
-
else
|
|
143
|
-
shadowedBuiltIns.add(declaration.id.name);
|
|
144
|
-
if (BUILT_INS.has(declaration.id.name))
|
|
145
|
-
shadowedBuiltIns.add(declaration.id.name);
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
if (declaration?.type === "TSInterfaceDeclaration") {
|
|
149
|
-
const declarations = interfaces.get(declaration.id.name) ?? [];
|
|
150
|
-
declarations.push(declaration);
|
|
151
|
-
interfaces.set(declaration.id.name, declarations);
|
|
152
|
-
if (BUILT_INS.has(declaration.id.name))
|
|
153
|
-
shadowedBuiltIns.add(declaration.id.name);
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
if (declaration?.type === "TSEnumDeclaration") {
|
|
157
|
-
if (BUILT_INS.has(declaration.id.name))
|
|
158
|
-
shadowedBuiltIns.add(declaration.id.name);
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
if ((declaration?.type === "ClassDeclaration" || declaration?.type === "FunctionDeclaration") && declaration.id !== null) {
|
|
162
|
-
if (BUILT_INS.has(declaration.id.name))
|
|
163
|
-
shadowedBuiltIns.add(declaration.id.name);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return { aliases, interfaces, shadowedBuiltIns };
|
|
167
|
-
}
|
|
168
|
-
function typeReferenceName(type) {
|
|
169
|
-
return type.typeName.type === "Identifier" ? type.typeName.name : null;
|
|
170
|
-
}
|
|
171
|
-
function isBuiltIn(name, environment) {
|
|
172
|
-
return BUILT_INS.has(name) && !environment.shadowedBuiltIns.has(name);
|
|
173
|
-
}
|
|
174
|
-
function isUnappliedReferenceTo(type, name) {
|
|
175
|
-
const unwrapped = unwrapTransparentType(type);
|
|
176
|
-
return unwrapped.type === "TSTypeReference" && typeReferenceName(unwrapped) === name && (unwrapped.typeArguments === null || unwrapped.typeArguments === undefined || unwrapped.typeArguments.params.length === 0);
|
|
177
|
-
}
|
|
178
|
-
function unwrapTransparentType(type) {
|
|
179
|
-
let current = type;
|
|
180
|
-
while (current.type === "TSParenthesizedType" || current.type === "TSTypeOperator" && current.operator === "readonly") {
|
|
181
|
-
current = current.typeAnnotation;
|
|
182
|
-
}
|
|
183
|
-
return current;
|
|
184
|
-
}
|
|
185
|
-
function isNeverType(type) {
|
|
186
|
-
return unwrapTransparentType(type).type === "TSNeverKeyword";
|
|
187
|
-
}
|
|
188
|
-
function isEffectivelyEmptyMember(member) {
|
|
189
|
-
return member.type === "TSPropertySignature" && member.optional === true && member.typeAnnotation !== null && member.typeAnnotation !== undefined && isNeverType(member.typeAnnotation.typeAnnotation);
|
|
190
|
-
}
|
|
191
|
-
function isEffectivelyEmptyTypeLiteral(type) {
|
|
192
|
-
return type.members.length === 0 || type.members.every(isEffectivelyEmptyMember);
|
|
193
|
-
}
|
|
194
|
-
function isEffectivelyEmptyInterface(declarations) {
|
|
195
|
-
if (declarations.length !== 1)
|
|
196
|
-
return false;
|
|
197
|
-
const [type] = declarations;
|
|
198
|
-
return type !== undefined && type.extends.length === 0 && (type.body.body.length === 0 || type.body.body.every(isEffectivelyEmptyMember));
|
|
199
|
-
}
|
|
200
|
-
function resolvedSubstitutionArgument(type, base, resolving = new Set) {
|
|
201
|
-
const unwrapped = unwrapTransparentType(type);
|
|
202
|
-
if (unwrapped.type !== "TSTypeReference")
|
|
203
|
-
return type;
|
|
204
|
-
const name = typeReferenceName(unwrapped);
|
|
205
|
-
if (name === null || resolving.has(name))
|
|
206
|
-
return type;
|
|
207
|
-
const substitution = base.get(name);
|
|
208
|
-
if (substitution === undefined)
|
|
209
|
-
return type;
|
|
210
|
-
const nextResolving = new Set(resolving);
|
|
211
|
-
nextResolving.add(name);
|
|
212
|
-
return resolvedSubstitutionArgument(substitution, base, nextResolving);
|
|
213
|
-
}
|
|
214
|
-
function aliasSubstitution(alias, type, base) {
|
|
215
|
-
const parameters = alias.typeParameters?.params ?? [];
|
|
216
|
-
const arguments_ = type.typeArguments?.params ?? [];
|
|
217
|
-
const next = new Map(base);
|
|
218
|
-
for (const [index, parameter] of parameters.entries()) {
|
|
219
|
-
const argument = arguments_[index] ?? parameter.default;
|
|
220
|
-
if (argument === null || argument === undefined)
|
|
221
|
-
return null;
|
|
222
|
-
next.set(parameter.name.name, resolvedSubstitutionArgument(argument, next));
|
|
223
|
-
}
|
|
224
|
-
return next;
|
|
225
|
-
}
|
|
226
|
-
function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
|
|
227
|
-
const unwrapped = unwrapTransparentType(type);
|
|
228
|
-
if (unwrapped.type === "TSUnknownKeyword")
|
|
229
|
-
return "unknown";
|
|
230
|
-
if (unwrapped.type === "TSAnyKeyword")
|
|
231
|
-
return "any";
|
|
232
|
-
if (unwrapped.type === "TSObjectKeyword")
|
|
233
|
-
return "object";
|
|
234
|
-
if (unwrapped.type === "TSTypeLiteral" && isEffectivelyEmptyTypeLiteral(unwrapped))
|
|
235
|
-
return "empty-object";
|
|
236
|
-
if (unwrapped.type === "TSUnionType") {
|
|
237
|
-
return unwrapped.types.some((member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases) !== null) ? "union" : null;
|
|
238
|
-
}
|
|
239
|
-
if (unwrapped.type === "TSIntersectionType") {
|
|
240
|
-
const unsafeMembers = unwrapped.types.map((member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases));
|
|
241
|
-
if (unsafeMembers.includes("any"))
|
|
242
|
-
return "any";
|
|
243
|
-
return unsafeMembers.length > 0 && unsafeMembers.every((member) => member !== null) ? unsafeMembers[0] : null;
|
|
244
|
-
}
|
|
245
|
-
if (unwrapped.type !== "TSTypeReference")
|
|
246
|
-
return null;
|
|
247
|
-
const name = typeReferenceName(unwrapped);
|
|
248
|
-
if (name === null)
|
|
249
|
-
return null;
|
|
250
|
-
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
251
|
-
const wrapped = unwrapped.typeArguments?.params[0];
|
|
252
|
-
return wrapped === undefined ? null : unsafeDirectValue(wrapped, environment, substitutions, resolvingAliases);
|
|
253
|
-
}
|
|
254
|
-
const substitution = substitutions.get(name);
|
|
255
|
-
if (substitution !== undefined) {
|
|
256
|
-
return isUnappliedReferenceTo(substitution, name) ? null : unsafeDirectValue(substitution, environment, substitutions, resolvingAliases);
|
|
257
|
-
}
|
|
258
|
-
const interfaceDeclarations = environment.interfaces.get(name);
|
|
259
|
-
if (interfaceDeclarations !== undefined) {
|
|
260
|
-
return isEffectivelyEmptyInterface(interfaceDeclarations) ? "empty-object" : null;
|
|
261
|
-
}
|
|
262
|
-
const alias = environment.aliases.get(name);
|
|
263
|
-
if (alias === undefined || resolvingAliases.has(name))
|
|
264
|
-
return null;
|
|
265
|
-
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
266
|
-
if (nextSubstitutions === null)
|
|
267
|
-
return null;
|
|
268
|
-
const nextResolving = new Set(resolvingAliases);
|
|
269
|
-
nextResolving.add(name);
|
|
270
|
-
return unsafeDirectValue(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
271
|
-
}
|
|
272
|
-
function dictionaryValueTypes(type, environment, substitutions, resolvingAliases) {
|
|
273
|
-
const unwrapped = unwrapTransparentType(type);
|
|
274
|
-
if (unwrapped.type === "TSTypeLiteral") {
|
|
275
|
-
return unwrapped.members.flatMap((member) => member.type === "TSIndexSignature" && member.typeAnnotation !== null ? [{ type: member.typeAnnotation.typeAnnotation, substitutions }] : []);
|
|
276
|
-
}
|
|
277
|
-
if (unwrapped.type === "TSMappedType") {
|
|
278
|
-
return unwrapped.typeAnnotation === null ? [] : [{ type: unwrapped.typeAnnotation, substitutions }];
|
|
279
|
-
}
|
|
280
|
-
if (unwrapped.type !== "TSTypeReference")
|
|
281
|
-
return [];
|
|
282
|
-
const name = typeReferenceName(unwrapped);
|
|
283
|
-
if (name === null)
|
|
284
|
-
return [];
|
|
285
|
-
const substitution = substitutions.get(name);
|
|
286
|
-
if (substitution !== undefined) {
|
|
287
|
-
return isUnappliedReferenceTo(substitution, name) ? [] : dictionaryValueTypes(substitution, environment, substitutions, resolvingAliases);
|
|
288
|
-
}
|
|
289
|
-
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
290
|
-
const wrapped = unwrapped.typeArguments?.params[0];
|
|
291
|
-
return wrapped === undefined ? [] : dictionaryValueTypes(wrapped, environment, substitutions, resolvingAliases);
|
|
292
|
-
}
|
|
293
|
-
if (name === "Record" && isBuiltIn(name, environment)) {
|
|
294
|
-
const value = unwrapped.typeArguments?.params[1] ?? null;
|
|
295
|
-
return value === null ? [] : [{ type: value, substitutions }];
|
|
296
|
-
}
|
|
297
|
-
if ((name === "Pick" || name === "Omit") && isBuiltIn(name, environment)) {
|
|
298
|
-
const source = unwrapped.typeArguments?.params[0];
|
|
299
|
-
return source === undefined ? [] : dictionaryValueTypes(source, environment, substitutions, resolvingAliases);
|
|
300
|
-
}
|
|
301
|
-
const alias = environment.aliases.get(name);
|
|
302
|
-
if (alias === undefined || resolvingAliases.has(name))
|
|
303
|
-
return [];
|
|
304
|
-
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
305
|
-
if (nextSubstitutions === null)
|
|
306
|
-
return [];
|
|
307
|
-
const nextResolving = new Set(resolvingAliases);
|
|
308
|
-
nextResolving.add(name);
|
|
309
|
-
return dictionaryValueTypes(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
310
|
-
}
|
|
311
|
-
function classifyUnsafeDictionaryValue(valueType, environment) {
|
|
312
|
-
const unsafeValue = unsafeDirectValue(valueType, environment, new Map, new Set);
|
|
313
|
-
return unsafeValue === null ? null : { kind: "unsafe-dictionary", unsafeValue };
|
|
314
|
-
}
|
|
315
|
-
function classifyUnsafeDictionary(type, environment) {
|
|
316
|
-
for (const valueType of dictionaryValueTypes(type, environment, new Map, new Set)) {
|
|
317
|
-
const unsafeValue = unsafeDirectValue(valueType.type, environment, valueType.substitutions, new Set);
|
|
318
|
-
if (unsafeValue !== null)
|
|
319
|
-
return { kind: "unsafe-dictionary", unsafeValue };
|
|
320
|
-
}
|
|
321
|
-
return null;
|
|
322
|
-
}
|
|
323
|
-
function resolvesToDictionary(type, environment, substitutions, resolvingAliases) {
|
|
324
|
-
return dictionaryValueTypes(type, environment, substitutions, resolvingAliases).length > 0;
|
|
325
|
-
}
|
|
326
|
-
function classifyWideningTarget(type, environment) {
|
|
327
|
-
const unwrapped = unwrapTransparentType(type);
|
|
328
|
-
if (unwrapped.type === "TSUnknownKeyword")
|
|
329
|
-
return { kind: "unknown" };
|
|
330
|
-
if (unwrapped.type === "TSObjectKeyword")
|
|
331
|
-
return { kind: "object" };
|
|
332
|
-
if (unwrapped.type === "TSTypeLiteral") {
|
|
333
|
-
return unwrapped.members.some((member) => member.type === "TSIndexSignature") ? { kind: "open dictionary" } : unwrapped.members.length > 0 ? { kind: "anonymous object" } : null;
|
|
334
|
-
}
|
|
335
|
-
if (unwrapped.type === "TSMappedType")
|
|
336
|
-
return { kind: "open dictionary" };
|
|
337
|
-
if (unwrapped.type !== "TSTypeReference")
|
|
338
|
-
return null;
|
|
339
|
-
const name = typeReferenceName(unwrapped);
|
|
340
|
-
if (name === null)
|
|
341
|
-
return null;
|
|
342
|
-
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
343
|
-
const wrapped = unwrapped.typeArguments?.params[0];
|
|
344
|
-
return wrapped === undefined ? null : classifyWideningTarget(wrapped, environment);
|
|
345
|
-
}
|
|
346
|
-
if (name === "Record" && isBuiltIn(name, environment))
|
|
347
|
-
return { kind: "open dictionary" };
|
|
348
|
-
const alias = environment.aliases.get(name);
|
|
349
|
-
if (alias === undefined)
|
|
350
|
-
return null;
|
|
351
|
-
if ((alias.typeParameters?.params.length ?? 0) > 0) {
|
|
352
|
-
const substitutions2 = aliasSubstitution(alias, unwrapped, new Map);
|
|
353
|
-
return substitutions2 !== null && resolvesToDictionary(alias.typeAnnotation, environment, substitutions2, new Set([name])) ? { kind: "generic container" } : null;
|
|
354
|
-
}
|
|
355
|
-
const substitutions = aliasSubstitution(alias, unwrapped, new Map);
|
|
356
|
-
if (substitutions === null)
|
|
357
|
-
return null;
|
|
358
|
-
const resolved = classifyAliasBroadTarget(alias.typeAnnotation, environment, substitutions, new Set([name]));
|
|
359
|
-
return resolved;
|
|
360
|
-
}
|
|
361
|
-
function isBroadMappedKey(type, environment, substitutions) {
|
|
362
|
-
const unwrapped = unwrapTransparentType(type);
|
|
363
|
-
if (unwrapped.type === "TSStringKeyword" || unwrapped.type === "TSNumberKeyword" || unwrapped.type === "TSSymbolKeyword") {
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
if (unwrapped.type === "TSUnionType") {
|
|
367
|
-
return unwrapped.types.every((member) => isBroadMappedKey(member, environment, substitutions));
|
|
368
|
-
}
|
|
369
|
-
if (unwrapped.type !== "TSTypeReference")
|
|
370
|
-
return false;
|
|
371
|
-
const name = typeReferenceName(unwrapped);
|
|
372
|
-
if (name === null)
|
|
373
|
-
return false;
|
|
374
|
-
const substitution = substitutions.get(name);
|
|
375
|
-
if (substitution !== undefined && !isUnappliedReferenceTo(substitution, name)) {
|
|
376
|
-
return isBroadMappedKey(substitution, environment, substitutions);
|
|
377
|
-
}
|
|
378
|
-
return name === "PropertyKey" && isBuiltIn(name, environment);
|
|
379
|
-
}
|
|
380
|
-
function classifyAliasBroadTarget(type, environment, substitutions, resolvingAliases) {
|
|
381
|
-
const unwrapped = unwrapTransparentType(type);
|
|
382
|
-
if (unwrapped.type === "TSUnknownKeyword")
|
|
383
|
-
return { kind: "unknown" };
|
|
384
|
-
if (unwrapped.type === "TSObjectKeyword")
|
|
385
|
-
return { kind: "object" };
|
|
386
|
-
if (unwrapped.type === "TSTypeLiteral") {
|
|
387
|
-
return unwrapped.members.some((member) => member.type === "TSIndexSignature") ? { kind: "open dictionary" } : null;
|
|
388
|
-
}
|
|
389
|
-
if (unwrapped.type === "TSMappedType") {
|
|
390
|
-
return isBroadMappedKey(unwrapped.constraint, environment, substitutions) ? { kind: "open dictionary" } : null;
|
|
391
|
-
}
|
|
392
|
-
if (unwrapped.type !== "TSTypeReference")
|
|
393
|
-
return null;
|
|
394
|
-
const name = typeReferenceName(unwrapped);
|
|
395
|
-
if (name === null)
|
|
396
|
-
return null;
|
|
397
|
-
const substitution = substitutions.get(name);
|
|
398
|
-
if (substitution !== undefined) {
|
|
399
|
-
return isUnappliedReferenceTo(substitution, name) ? null : classifyAliasBroadTarget(substitution, environment, substitutions, resolvingAliases);
|
|
400
|
-
}
|
|
401
|
-
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
402
|
-
const wrapped = unwrapped.typeArguments?.params[0];
|
|
403
|
-
return wrapped === undefined ? null : classifyAliasBroadTarget(wrapped, environment, substitutions, resolvingAliases);
|
|
404
|
-
}
|
|
405
|
-
if (name === "Record" && isBuiltIn(name, environment)) {
|
|
406
|
-
return { kind: "open dictionary" };
|
|
407
|
-
}
|
|
408
|
-
const alias = environment.aliases.get(name);
|
|
409
|
-
if (alias === undefined || resolvingAliases.has(name))
|
|
410
|
-
return null;
|
|
411
|
-
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
412
|
-
if (nextSubstitutions === null)
|
|
413
|
-
return null;
|
|
414
|
-
const nextResolving = new Set(resolvingAliases);
|
|
415
|
-
nextResolving.add(name);
|
|
416
|
-
return classifyAliasBroadTarget(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
417
|
-
}
|
|
418
|
-
function isKnownEvidenceExpression(expression) {
|
|
419
|
-
let current = expression;
|
|
420
|
-
while (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression" || current.type === "TSSatisfiesExpression") {
|
|
421
|
-
current = current.expression;
|
|
422
|
-
}
|
|
423
|
-
if (current.type === "ObjectExpression")
|
|
424
|
-
return true;
|
|
425
|
-
return current.type === "ArrayExpression" || current.type === "ArrowFunctionExpression" || current.type === "ClassExpression" || current.type === "FunctionExpression" || current.type === "NewExpression" || current.type === "Literal" || current.type === "TemplateLiteral" || current.type === "UnaryExpression";
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// src/rules/dillon-anti-slop/rules/no-known-value-widening.ts
|
|
429
|
-
function unwrapExpression(expression) {
|
|
430
|
-
let current = expression;
|
|
431
|
-
while (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression") {
|
|
432
|
-
current = current.expression;
|
|
433
|
-
}
|
|
434
|
-
return current;
|
|
435
|
-
}
|
|
436
|
-
function resolveVariable(sourceCode, identifier) {
|
|
437
|
-
let scope = sourceCode.getScope(identifier);
|
|
438
|
-
while (scope !== null) {
|
|
439
|
-
const variable = scope.set.get(identifier.name);
|
|
440
|
-
if (variable !== undefined)
|
|
441
|
-
return variable;
|
|
442
|
-
scope = scope.upper;
|
|
443
|
-
}
|
|
444
|
-
return null;
|
|
445
|
-
}
|
|
446
|
-
function variableDeclarator(variable) {
|
|
447
|
-
if (variable.defs.length !== 1)
|
|
448
|
-
return null;
|
|
449
|
-
const [definition] = variable.defs;
|
|
450
|
-
return definition?.type === "Variable" && definition.node.type === "VariableDeclarator" ? definition.node : null;
|
|
451
|
-
}
|
|
452
|
-
function isStableConstVariable(variable, declarator) {
|
|
453
|
-
return declarator.parent.type === "VariableDeclaration" && declarator.parent.kind === "const" && variable.references.every((reference) => reference.init || !reference.isWrite());
|
|
454
|
-
}
|
|
455
|
-
function hasKnownEvidence(sourceCode, expression, visitedVariables = new Set) {
|
|
456
|
-
if (isKnownEvidenceExpression(expression))
|
|
457
|
-
return true;
|
|
458
|
-
const unwrapped = unwrapExpression(expression);
|
|
459
|
-
if (unwrapped.type !== "Identifier")
|
|
460
|
-
return false;
|
|
461
|
-
const variable = resolveVariable(sourceCode, unwrapped);
|
|
462
|
-
if (variable === null || visitedVariables.has(variable))
|
|
463
|
-
return false;
|
|
464
|
-
const declarator = variableDeclarator(variable);
|
|
465
|
-
if (declarator === null || declarator.init === null || !isStableConstVariable(variable, declarator)) {
|
|
466
|
-
return false;
|
|
467
|
-
}
|
|
468
|
-
visitedVariables.add(variable);
|
|
469
|
-
return hasKnownEvidence(sourceCode, declarator.init, visitedVariables);
|
|
470
|
-
}
|
|
471
|
-
function annotationTarget(annotation, environment) {
|
|
472
|
-
return annotation === null || annotation === undefined ? null : classifyWideningTarget(annotation.typeAnnotation, environment);
|
|
473
|
-
}
|
|
474
|
-
function enclosingFunction(node) {
|
|
475
|
-
let current = node.parent;
|
|
476
|
-
while (current !== null && current.type !== "Program") {
|
|
477
|
-
if (current.type === "ArrowFunctionExpression" || current.type === "FunctionDeclaration" || current.type === "FunctionExpression") {
|
|
478
|
-
return current;
|
|
479
|
-
}
|
|
480
|
-
current = current.parent;
|
|
481
|
-
}
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
484
|
-
function sourceKeyName(sourceCode, key) {
|
|
485
|
-
if (key.type === "Identifier" || key.type === "PrivateIdentifier")
|
|
486
|
-
return key.name;
|
|
487
|
-
if (key.type === "Literal")
|
|
488
|
-
return String(key.value);
|
|
489
|
-
return sourceCode.getText(key);
|
|
490
|
-
}
|
|
491
|
-
function functionName(sourceCode, owner) {
|
|
492
|
-
if (owner === null)
|
|
493
|
-
return "anonymous function";
|
|
494
|
-
if (owner.id !== null)
|
|
495
|
-
return owner.id.name;
|
|
496
|
-
const parent = owner.parent;
|
|
497
|
-
if (parent.type === "VariableDeclarator" && parent.id.type === "Identifier")
|
|
498
|
-
return parent.id.name;
|
|
499
|
-
if (parent.type === "MethodDefinition")
|
|
500
|
-
return sourceKeyName(sourceCode, parent.key);
|
|
501
|
-
return "anonymous function";
|
|
502
|
-
}
|
|
503
|
-
function isEmptyObjectExpression2(expression) {
|
|
504
|
-
const unwrapped = unwrapExpression(expression);
|
|
505
|
-
return unwrapped.type === "ObjectExpression" && unwrapped.properties.length === 0;
|
|
506
|
-
}
|
|
507
|
-
function isDictionaryAccumulatorTarget(destination) {
|
|
508
|
-
return destination.kind === "open dictionary" || destination.kind === "generic container";
|
|
509
|
-
}
|
|
510
|
-
function hasParentAssertion(node) {
|
|
511
|
-
return node.parent?.type === "TSAsExpression" || node.parent?.type === "TSTypeAssertion";
|
|
512
|
-
}
|
|
513
|
-
var noKnownValueWideningRule = defineRule3({
|
|
514
|
-
meta: {
|
|
515
|
-
type: "problem",
|
|
516
|
-
docs: {
|
|
517
|
-
description: "Disallow syntactically established values from flowing into explicitly broad or anonymous target types that discard useful evidence."
|
|
518
|
-
},
|
|
519
|
-
messages: {
|
|
520
|
-
widening: "The explicit {{target}} type on {{subject}} discards known type evidence. Keep inference, validate with `satisfies`, or use a named owner contract."
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
createOnce(context) {
|
|
524
|
-
let environment = null;
|
|
525
|
-
const reportFlow = (expression, destination, subject) => {
|
|
526
|
-
if (destination === null)
|
|
527
|
-
return;
|
|
528
|
-
if (isDictionaryAccumulatorTarget(destination) && isEmptyObjectExpression2(expression)) {
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
if (!hasKnownEvidence(context.sourceCode, expression))
|
|
532
|
-
return;
|
|
533
|
-
context.report({
|
|
534
|
-
node: expression,
|
|
535
|
-
messageId: "widening",
|
|
536
|
-
data: { subject, target: destination.kind }
|
|
537
|
-
});
|
|
538
|
-
};
|
|
539
|
-
const targetFromAnnotation = (annotation) => environment === null ? null : annotationTarget(annotation, environment);
|
|
540
|
-
return {
|
|
541
|
-
Program(node) {
|
|
542
|
-
environment = createTypeEnvironment(node);
|
|
543
|
-
},
|
|
544
|
-
VariableDeclarator(node) {
|
|
545
|
-
if (node.init === null || node.id.type !== "Identifier")
|
|
546
|
-
return;
|
|
547
|
-
reportFlow(node.init, targetFromAnnotation(node.id.typeAnnotation), `binding \`${node.id.name}\``);
|
|
548
|
-
},
|
|
549
|
-
PropertyDefinition(node) {
|
|
550
|
-
if (node.value === null)
|
|
551
|
-
return;
|
|
552
|
-
reportFlow(node.value, targetFromAnnotation(node.typeAnnotation), `property \`${sourceKeyName(context.sourceCode, node.key)}\``);
|
|
553
|
-
},
|
|
554
|
-
AccessorProperty(node) {
|
|
555
|
-
if (node.value === null)
|
|
556
|
-
return;
|
|
557
|
-
reportFlow(node.value, targetFromAnnotation(node.typeAnnotation), `property \`${sourceKeyName(context.sourceCode, node.key)}\``);
|
|
558
|
-
},
|
|
559
|
-
AssignmentExpression(node) {
|
|
560
|
-
if (node.operator !== "=" || node.left.type !== "Identifier")
|
|
561
|
-
return;
|
|
562
|
-
const variable = resolveVariable(context.sourceCode, node.left);
|
|
563
|
-
if (variable === null)
|
|
564
|
-
return;
|
|
565
|
-
const declarator = variableDeclarator(variable);
|
|
566
|
-
if (declarator === null || declarator.id.type !== "Identifier")
|
|
567
|
-
return;
|
|
568
|
-
reportFlow(node.right, targetFromAnnotation(declarator.id.typeAnnotation), `binding \`${declarator.id.name}\``);
|
|
569
|
-
},
|
|
570
|
-
ReturnStatement(node) {
|
|
571
|
-
if (node.argument === null)
|
|
572
|
-
return;
|
|
573
|
-
const owner = enclosingFunction(node);
|
|
574
|
-
reportFlow(node.argument, targetFromAnnotation(owner?.returnType), `return value of \`${functionName(context.sourceCode, owner)}\``);
|
|
575
|
-
},
|
|
576
|
-
ArrowFunctionExpression(node) {
|
|
577
|
-
if (node.body.type === "BlockStatement")
|
|
578
|
-
return;
|
|
579
|
-
reportFlow(node.body, targetFromAnnotation(node.returnType), `return value of \`${functionName(context.sourceCode, node)}\``);
|
|
580
|
-
},
|
|
581
|
-
TSAsExpression(node) {
|
|
582
|
-
if (environment === null || hasParentAssertion(node))
|
|
583
|
-
return;
|
|
584
|
-
reportFlow(node.expression, classifyWideningTarget(node.typeAnnotation, environment), "assertion");
|
|
585
|
-
},
|
|
586
|
-
TSTypeAssertion(node) {
|
|
587
|
-
if (environment === null || hasParentAssertion(node))
|
|
588
|
-
return;
|
|
589
|
-
reportFlow(node.expression, classifyWideningTarget(node.typeAnnotation, environment), "assertion");
|
|
590
|
-
}
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
// src/rules/dillon-anti-slop/rules/no-module-mocking.ts
|
|
596
|
-
import { defineRule as defineRule4 } from "@oxlint/plugins";
|
|
597
|
-
|
|
598
|
-
// src/utils/helpers/ast.ts
|
|
599
|
-
var astVisitSkip = new Set(["parent", "type", "start", "end", "range", "loc", "tokens", "comments"]);
|
|
600
|
-
function astIsNode(value) {
|
|
601
|
-
return value !== null && typeof value === "object" && typeof Reflect.get(value, "type") === "string";
|
|
602
|
-
}
|
|
603
|
-
function astVisit(node, ancestors, callback) {
|
|
604
|
-
if (Array.isArray(node)) {
|
|
605
|
-
for (const child of node) {
|
|
606
|
-
astVisit(child, ancestors, callback);
|
|
607
|
-
}
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
if (!astIsNode(node)) {
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
callback(node, ancestors);
|
|
614
|
-
ancestors.push(node);
|
|
615
|
-
for (const key in node) {
|
|
616
|
-
if (Object.hasOwn(node, key) && !astVisitSkip.has(key)) {
|
|
617
|
-
astVisit(Reflect.get(node, key), ancestors, callback);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
ancestors.pop();
|
|
621
|
-
}
|
|
622
|
-
function astContainsJsx(node) {
|
|
623
|
-
let found = false;
|
|
624
|
-
astVisit(node, [], (candidate) => {
|
|
625
|
-
if (candidate.type === "JSXElement" || candidate.type === "JSXFragment") {
|
|
626
|
-
found = true;
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
return found;
|
|
630
|
-
}
|
|
631
|
-
function astNearestFunction(ancestors) {
|
|
632
|
-
return [...ancestors].reverse().find((node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression");
|
|
633
|
-
}
|
|
634
|
-
function astDirectReturnExpressions(node) {
|
|
635
|
-
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
636
|
-
return [node.body];
|
|
637
|
-
}
|
|
638
|
-
const expressions = [];
|
|
639
|
-
astVisit(node.body, [node], (candidate, ancestors) => {
|
|
640
|
-
if (candidate.type === "ReturnStatement" && candidate.argument && astNearestFunction(ancestors) === node) {
|
|
641
|
-
expressions.push(candidate.argument);
|
|
642
|
-
}
|
|
643
|
-
});
|
|
644
|
-
return expressions;
|
|
645
|
-
}
|
|
646
|
-
function astStaticMemberName(node) {
|
|
647
|
-
return !node.computed && node.property.type === "Identifier" ? node.property.name : undefined;
|
|
648
|
-
}
|
|
649
|
-
function astDottedName(node) {
|
|
650
|
-
if (node.type === "Identifier") {
|
|
651
|
-
return node.name;
|
|
652
|
-
}
|
|
653
|
-
if (node.type !== "MemberExpression") {
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
const object = astDottedName(node.object);
|
|
657
|
-
const property = astStaticMemberName(node);
|
|
658
|
-
return object && property ? `${object}.${property}` : undefined;
|
|
659
|
-
}
|
|
660
|
-
function astCallName(node) {
|
|
661
|
-
if (node.callee.type === "Identifier") {
|
|
662
|
-
return node.callee.name;
|
|
663
|
-
}
|
|
664
|
-
return node.callee.type === "MemberExpression" ? astStaticMemberName(node.callee) : undefined;
|
|
665
|
-
}
|
|
666
|
-
function astImportedCallAliases(program) {
|
|
667
|
-
const aliases = new Map;
|
|
668
|
-
for (const statement of program.body) {
|
|
669
|
-
if (statement.type !== "ImportDeclaration") {
|
|
670
|
-
continue;
|
|
671
|
-
}
|
|
672
|
-
for (const specifier of statement.specifiers) {
|
|
673
|
-
if (specifier.type !== "ImportSpecifier") {
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
|
|
677
|
-
if (imported !== specifier.local.name) {
|
|
678
|
-
aliases.set(specifier.local.name, imported);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
return aliases;
|
|
683
|
-
}
|
|
684
|
-
function astResolvedCallName(node, aliases) {
|
|
685
|
-
const name = astCallName(node);
|
|
686
|
-
return name ? aliases.get(name) ?? name : undefined;
|
|
687
|
-
}
|
|
688
|
-
// src/utils/helpers/comments.ts
|
|
689
|
-
var COMMENTS_DIRECTIVE = /^\s*(?:\/\s*<(?:reference\b|amd-[\w-]+\b)|@ts-(?:check|ignore|expect-error|nocheck)\b|(?:#|@)__(?:PURE|NO_SIDE_EFFECTS)__\b|@jsx(?:ImportSource|Runtime|Frag)?\b|@vite-ignore\b|(?:[#@]\s*)?sourceMappingURL=|(?:es|ox)lint-(?:disable|enable)|prettier-ignore|biome-ignore|[cv]8 ignore|istanbul ignore|webpack[A-Z])/;
|
|
690
|
-
var COMMENTS_DISABLE_NEXT_LINE = /^(?:eslint|oxlint)-disable-next-line\b/;
|
|
691
|
-
var COMMENTS_LEGAL_BLOCK = "\x00legal-block\x00";
|
|
692
|
-
function commentsNormalizedValue(comment) {
|
|
693
|
-
const value = (comment.type === "Block" ? comment.value.replace(/^\s*\* ?/gm, "") : comment.value).trimStart();
|
|
694
|
-
return comment.type === "Block" && comment.value.startsWith("!") ? COMMENTS_LEGAL_BLOCK + value : value;
|
|
695
|
-
}
|
|
696
|
-
function commentsIsDirective(value) {
|
|
697
|
-
return value.startsWith(COMMENTS_LEGAL_BLOCK) || COMMENTS_DIRECTIVE.test(value);
|
|
698
|
-
}
|
|
699
|
-
function commentsIsDisableNextLine(value) {
|
|
700
|
-
return COMMENTS_DISABLE_NEXT_LINE.test(value);
|
|
701
|
-
}
|
|
702
|
-
function commentsIsWhy(value) {
|
|
703
|
-
return value.startsWith("Why: ");
|
|
704
|
-
}
|
|
705
|
-
function commentsIsJsdoc(source, comment) {
|
|
706
|
-
return comment.type === "Block" && source.startsWith("/**", comment.range[0]);
|
|
707
|
-
}
|
|
708
|
-
// src/utils/helpers/trivial.ts
|
|
709
|
-
function trivialExpression(node) {
|
|
710
|
-
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
711
|
-
return node.body;
|
|
712
|
-
}
|
|
713
|
-
if (!node.body || node.body.type !== "BlockStatement")
|
|
714
|
-
return null;
|
|
715
|
-
const statements = node.body.body.filter((statement2) => statement2.type !== "EmptyStatement" && !(statement2.type === "ExpressionStatement" && statement2.directive));
|
|
716
|
-
if (statements.length !== 1)
|
|
717
|
-
return null;
|
|
718
|
-
const statement = statements[0];
|
|
719
|
-
if (statement.type === "ReturnStatement")
|
|
720
|
-
return statement.argument;
|
|
721
|
-
return statement.type === "ExpressionStatement" ? statement.expression : null;
|
|
722
|
-
}
|
|
723
|
-
function trivialUnwrap(expression) {
|
|
724
|
-
if (expression.type === "ParenthesizedExpression" || expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSTypeAssertion" || expression.type === "TSNonNullExpression" || expression.type === "ChainExpression") {
|
|
725
|
-
return trivialUnwrap(expression.expression);
|
|
726
|
-
}
|
|
727
|
-
return expression.type === "AwaitExpression" ? trivialUnwrap(expression.argument) : expression;
|
|
728
|
-
}
|
|
729
|
-
function trivialCallRoot(expression) {
|
|
730
|
-
if (expression.type === "Identifier")
|
|
731
|
-
return expression.name;
|
|
732
|
-
if (expression.type === "MemberExpression" && !expression.computed && !expression.optional) {
|
|
733
|
-
return trivialCallRoot(expression.object);
|
|
734
|
-
}
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
function trivialIsForwarder(node, expression) {
|
|
738
|
-
const value = trivialUnwrap(expression);
|
|
739
|
-
if (value.type === "Literal")
|
|
740
|
-
return !("regex" in value);
|
|
741
|
-
if (value.type === "TemplateLiteral")
|
|
742
|
-
return value.expressions.length === 0;
|
|
743
|
-
if (value.type === "Identifier")
|
|
744
|
-
return true;
|
|
745
|
-
if (value.type !== "CallExpression" || value.optional)
|
|
746
|
-
return false;
|
|
747
|
-
const root = trivialCallRoot(value.callee);
|
|
748
|
-
if (!root || node.params.some((param) => param.type !== "Identifier"))
|
|
749
|
-
return false;
|
|
750
|
-
const parameters = node.params.flatMap((param) => param.type === "Identifier" ? [param.name] : []);
|
|
751
|
-
const arguments_ = value.arguments;
|
|
752
|
-
const receiver = value.callee.type === "MemberExpression" ? root : undefined;
|
|
753
|
-
const forwarded = parameters.filter((name) => name !== receiver);
|
|
754
|
-
return arguments_.length === forwarded.length && arguments_.every((argument, index) => argument.type === "Identifier" && argument.name === forwarded[index]);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// src/utils/helpers/declarations.ts
|
|
758
|
-
function declarationsTopLevelUnexportedFunctions(program) {
|
|
759
|
-
return program.body.flatMap((statement) => {
|
|
760
|
-
if (statement.type === "FunctionDeclaration") {
|
|
761
|
-
return [{ name: statement.id?.name ?? "<anonymous>", node: statement }];
|
|
762
|
-
}
|
|
763
|
-
if (statement.type !== "VariableDeclaration") {
|
|
764
|
-
return [];
|
|
765
|
-
}
|
|
766
|
-
return statement.declarations.flatMap((item) => item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression" ? [{ name: item.id.type === "Identifier" ? item.id.name : "<anonymous>", node: item.init }] : []);
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
function declarationsFileLevelFunctionCandidates(program) {
|
|
770
|
-
const result = [];
|
|
771
|
-
for (const statement of program.body) {
|
|
772
|
-
const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
|
|
773
|
-
if (declaration?.type === "FunctionDeclaration") {
|
|
774
|
-
result.push({ name: declaration.id?.name ?? "<anonymous>", node: declaration });
|
|
775
|
-
continue;
|
|
776
|
-
}
|
|
777
|
-
if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") {
|
|
778
|
-
continue;
|
|
779
|
-
}
|
|
780
|
-
for (const item of declaration.declarations) {
|
|
781
|
-
if (item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression") {
|
|
782
|
-
result.push({
|
|
783
|
-
name: item.id.type === "Identifier" ? item.id.name : "<anonymous>",
|
|
784
|
-
node: declaration,
|
|
785
|
-
initializer: item.init
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
return result;
|
|
791
|
-
}
|
|
792
|
-
function declarationsIsObjectParam(node) {
|
|
793
|
-
if (node.type === "ObjectPattern") {
|
|
794
|
-
return true;
|
|
795
|
-
}
|
|
796
|
-
if (node.type === "TSParameterProperty") {
|
|
797
|
-
return declarationsIsObjectParam(node.parameter);
|
|
798
|
-
}
|
|
799
|
-
const annotation = node.typeAnnotation?.typeAnnotation;
|
|
800
|
-
return !annotation || annotation.type === "TSTypeReference" || annotation.type === "TSTypeLiteral" || annotation.type === "TSIntersectionType";
|
|
801
|
-
}
|
|
802
|
-
function declarationsFunctionName(node) {
|
|
803
|
-
if (node.type === "VariableDeclarator") {
|
|
804
|
-
return node.id.type === "Identifier" ? node.id.name : "<anonymous>";
|
|
805
|
-
}
|
|
806
|
-
return node.id?.name ?? "<anonymous>";
|
|
807
|
-
}
|
|
808
|
-
function declarationsIsRuntime(node) {
|
|
809
|
-
return node.type === "VariableDeclaration" || node.type === "FunctionDeclaration" || node.type === "ClassDeclaration" || node.type === "TSEnumDeclaration";
|
|
810
|
-
}
|
|
811
|
-
function declarationsRuntimeName(node) {
|
|
812
|
-
if (node.type === "VariableDeclaration") {
|
|
813
|
-
return node.declarations.map((item) => item.id.type === "Identifier" ? item.id.name : "<anonymous>").join(", ");
|
|
814
|
-
}
|
|
815
|
-
return node.id?.name ?? "<anonymous>";
|
|
816
|
-
}
|
|
817
|
-
function declarationsRuntimeKind(node) {
|
|
818
|
-
if (node.type === "ClassDeclaration") {
|
|
819
|
-
return "class";
|
|
820
|
-
}
|
|
821
|
-
if (node.type === "TSEnumDeclaration") {
|
|
822
|
-
return "enum";
|
|
823
|
-
}
|
|
824
|
-
if (node.type === "FunctionDeclaration") {
|
|
825
|
-
return "function";
|
|
826
|
-
}
|
|
827
|
-
return "variable";
|
|
828
|
-
}
|
|
829
|
-
function declarationsUnwrapExpression(node) {
|
|
830
|
-
if (node.type === "ParenthesizedExpression" || node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression") {
|
|
831
|
-
return declarationsUnwrapExpression(node.expression);
|
|
832
|
-
}
|
|
833
|
-
if (node.type === "ChainExpression") {
|
|
834
|
-
return declarationsUnwrapExpression(node.expression);
|
|
835
|
-
}
|
|
836
|
-
return node.type === "AwaitExpression" && node.argument ? declarationsUnwrapExpression(node.argument) : node;
|
|
837
|
-
}
|
|
838
|
-
function declarationsIsTrivialExpression(node) {
|
|
839
|
-
const value = declarationsUnwrapExpression(node);
|
|
840
|
-
if (value.type === "Identifier" || value.type === "ThisExpression" || value.type === "MetaProperty" || value.type.endsWith("Literal")) {
|
|
841
|
-
return true;
|
|
842
|
-
}
|
|
843
|
-
if (value.type === "TemplateLiteral") {
|
|
844
|
-
return value.expressions.length === 0;
|
|
845
|
-
}
|
|
846
|
-
if (value.type === "MemberExpression" || value.type === "CallExpression" || value.type === "NewExpression" || value.type === "TaggedTemplateExpression") {
|
|
847
|
-
return true;
|
|
848
|
-
}
|
|
849
|
-
return value.type === "LogicalExpression" && declarationsIsTrivialExpression(value.left) && declarationsIsTrivialExpression(value.right);
|
|
850
|
-
}
|
|
851
|
-
function declarationsIsTrivialFunction(node) {
|
|
852
|
-
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
853
|
-
return declarationsIsTrivialExpression(node.body);
|
|
854
|
-
}
|
|
855
|
-
if (!node.body || node.body.type !== "BlockStatement") {
|
|
856
|
-
return false;
|
|
857
|
-
}
|
|
858
|
-
const statements = node.body.body.filter((statement2) => statement2.type !== "EmptyStatement" && !(statement2.type === "ExpressionStatement" && statement2.directive));
|
|
859
|
-
if (statements.length === 0) {
|
|
860
|
-
return true;
|
|
861
|
-
}
|
|
862
|
-
if (statements.length > 1) {
|
|
863
|
-
return false;
|
|
864
|
-
}
|
|
865
|
-
const statement = statements[0];
|
|
866
|
-
if (statement.type === "ReturnStatement") {
|
|
867
|
-
return !statement.argument || declarationsIsTrivialExpression(statement.argument);
|
|
868
|
-
}
|
|
869
|
-
return statement.type === "ExpressionStatement" && declarationsIsTrivialExpression(statement.expression);
|
|
870
|
-
}
|
|
871
|
-
var declarationsKinds = ["function", "const", "let", "var", "class", "type", "interface", "enum"];
|
|
872
|
-
function declarationsNamedKind(node, ancestors) {
|
|
873
|
-
if (node.type === "FunctionDeclaration") {
|
|
874
|
-
return "function";
|
|
875
|
-
}
|
|
876
|
-
if (node.type === "ClassDeclaration") {
|
|
877
|
-
return "class";
|
|
878
|
-
}
|
|
879
|
-
if (node.type === "TSTypeAliasDeclaration") {
|
|
880
|
-
return "type";
|
|
881
|
-
}
|
|
882
|
-
if (node.type === "TSInterfaceDeclaration") {
|
|
883
|
-
return "interface";
|
|
884
|
-
}
|
|
885
|
-
if (node.type === "TSEnumDeclaration") {
|
|
886
|
-
return "enum";
|
|
887
|
-
}
|
|
888
|
-
if (node.type !== "VariableDeclarator") {
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
const declaration = ancestors.findLast((ancestor) => ancestor.type === "VariableDeclaration");
|
|
892
|
-
return declaration?.type === "VariableDeclaration" && (declaration.kind === "const" || declaration.kind === "let" || declaration.kind === "var") ? declaration.kind : undefined;
|
|
893
|
-
}
|
|
894
|
-
function declarationsCollectNamed(root, kinds) {
|
|
895
|
-
const allowed = kinds && kinds.length > 0 ? new Set(kinds) : null;
|
|
896
|
-
const result = [];
|
|
897
|
-
astVisit(root, [], (node, ancestors) => {
|
|
898
|
-
const kind = declarationsNamedKind(node, ancestors);
|
|
899
|
-
const name = node.type === "VariableDeclarator" && node.id.type === "Identifier" ? node.id.name : ("id" in node) && node.id && ("name" in node.id) ? node.id.name : undefined;
|
|
900
|
-
if (!kind || !name || allowed && !allowed.has(kind)) {
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
if (node.type === "VariableDeclarator" && ancestors.some((ancestor) => ancestor.type === "ForStatement" || ancestor.type === "ForInStatement" || ancestor.type === "ForOfStatement")) {
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
result.push({ name, node, kind });
|
|
907
|
-
});
|
|
908
|
-
return result;
|
|
909
|
-
}
|
|
910
|
-
function declarationsInsideForLoop(node) {
|
|
911
|
-
for (let current = node.parent;current; current = current.parent) {
|
|
912
|
-
if (current.type === "ForStatement" || current.type === "ForInStatement" || current.type === "ForOfStatement") {
|
|
913
|
-
return true;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
return false;
|
|
917
|
-
}
|
|
918
|
-
function declarationsNamedFromNode(node) {
|
|
919
|
-
if (node.type === "VariableDeclarator") {
|
|
920
|
-
const kind2 = declarationsNamedKind(node, [node.parent]);
|
|
921
|
-
return kind2 && node.id.type === "Identifier" && !declarationsInsideForLoop(node) ? { name: node.id.name, node, kind: kind2 } : undefined;
|
|
922
|
-
}
|
|
923
|
-
const kind = declarationsNamedKind(node, []);
|
|
924
|
-
const name = "id" in node && node.id && "name" in node.id ? node.id.name : undefined;
|
|
925
|
-
return kind && name ? { name, node, kind } : undefined;
|
|
926
|
-
}
|
|
927
|
-
function declarationsCollectFunctions(root) {
|
|
928
|
-
return declarationsCollectNamed(root).flatMap((item) => {
|
|
929
|
-
if (item.node.type === "FunctionDeclaration") {
|
|
930
|
-
return [{ name: item.name, node: item.node }];
|
|
931
|
-
}
|
|
932
|
-
if (item.node.type === "VariableDeclarator" && (item.node.init?.type === "FunctionExpression" || item.node.init?.type === "ArrowFunctionExpression")) {
|
|
933
|
-
return [{ name: item.name, node: item.node.init }];
|
|
934
|
-
}
|
|
935
|
-
return [];
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
function declarationsPublicServiceMethods(program) {
|
|
939
|
-
return program.body.flatMap((statement) => {
|
|
940
|
-
if (statement.type !== "ExportNamedDeclaration" || statement.declaration?.type !== "VariableDeclaration" || statement.declaration.kind !== "const")
|
|
941
|
-
return [];
|
|
942
|
-
return statement.declaration.declarations.flatMap((binding) => {
|
|
943
|
-
if (binding.id.type !== "Identifier" || !binding.init)
|
|
944
|
-
return [];
|
|
945
|
-
const value = trivialUnwrap(binding.init);
|
|
946
|
-
if (value.type !== "ObjectExpression" || value.properties.length === 0)
|
|
947
|
-
return [];
|
|
948
|
-
const methods = value.properties.flatMap((property) => property.type === "Property" && property.method && property.kind === "init" && !property.computed && property.key.type === "Identifier" && property.value.type === "FunctionExpression" ? [{ name: property.key.name, node: property.value, binding }] : []);
|
|
949
|
-
return methods.length === value.properties.length ? methods : [];
|
|
950
|
-
});
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
// src/utils/helpers/exports.ts
|
|
954
|
-
function moduleExportName(node) {
|
|
955
|
-
return node.type === "Identifier" ? node.name : node.value;
|
|
956
|
-
}
|
|
957
|
-
function declarationBindings(declaration) {
|
|
958
|
-
if (!declaration || declaration.type === "TSModuleDeclaration") {
|
|
959
|
-
return [];
|
|
960
|
-
}
|
|
961
|
-
if (declaration.type === "VariableDeclaration") {
|
|
962
|
-
return declaration.declarations.map((item) => ({
|
|
963
|
-
exportedName: item.id.type === "Identifier" ? item.id.name : "<anonymous>",
|
|
964
|
-
localName: item.id.type === "Identifier" ? item.id.name : undefined,
|
|
965
|
-
node: declaration,
|
|
966
|
-
declaration,
|
|
967
|
-
initializer: item.init,
|
|
968
|
-
typeOnly: false,
|
|
969
|
-
default: false,
|
|
970
|
-
reExport: false
|
|
971
|
-
}));
|
|
972
|
-
}
|
|
973
|
-
const name = declaration.id?.name ?? "<anonymous>";
|
|
974
|
-
return [
|
|
975
|
-
{
|
|
976
|
-
exportedName: name,
|
|
977
|
-
localName: declaration.id?.name,
|
|
978
|
-
node: declaration,
|
|
979
|
-
declaration,
|
|
980
|
-
typeOnly: declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration" || declaration.type === "TSDeclareFunction",
|
|
981
|
-
default: false,
|
|
982
|
-
reExport: false
|
|
983
|
-
}
|
|
984
|
-
];
|
|
985
|
-
}
|
|
986
|
-
function localBindings(program) {
|
|
987
|
-
const result = new Map;
|
|
988
|
-
for (const statement of program.body) {
|
|
989
|
-
if (statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration") {
|
|
990
|
-
continue;
|
|
991
|
-
}
|
|
992
|
-
if (statement.type.endsWith("Declaration")) {
|
|
993
|
-
for (const binding of declarationBindings(statement)) {
|
|
994
|
-
if (binding.localName) {
|
|
995
|
-
result.set(binding.localName, binding);
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
return result;
|
|
1001
|
-
}
|
|
1002
|
-
function exportsCollect(program) {
|
|
1003
|
-
const locals = localBindings(program);
|
|
1004
|
-
return program.body.flatMap((statement) => {
|
|
1005
|
-
if (statement.type === "ExportAllDeclaration") {
|
|
1006
|
-
return [
|
|
1007
|
-
{
|
|
1008
|
-
exportedName: "*",
|
|
1009
|
-
node: statement,
|
|
1010
|
-
typeOnly: statement.exportKind === "type",
|
|
1011
|
-
default: false,
|
|
1012
|
-
reExport: true
|
|
1013
|
-
}
|
|
1014
|
-
];
|
|
1015
|
-
}
|
|
1016
|
-
if (statement.type === "ExportDefaultDeclaration") {
|
|
1017
|
-
if (statement.declaration.type === "Identifier") {
|
|
1018
|
-
const local = locals.get(statement.declaration.name);
|
|
1019
|
-
return [
|
|
1020
|
-
{
|
|
1021
|
-
...local ?? {
|
|
1022
|
-
exportedName: "default",
|
|
1023
|
-
node: statement,
|
|
1024
|
-
typeOnly: false,
|
|
1025
|
-
default: true,
|
|
1026
|
-
reExport: false
|
|
1027
|
-
},
|
|
1028
|
-
exportedName: "default",
|
|
1029
|
-
localName: local?.localName ?? statement.declaration.name,
|
|
1030
|
-
default: true
|
|
1031
|
-
}
|
|
1032
|
-
];
|
|
1033
|
-
}
|
|
1034
|
-
if (statement.declaration.type.endsWith("Declaration")) {
|
|
1035
|
-
const bindings = declarationBindings(statement.declaration);
|
|
1036
|
-
return bindings.length > 0 ? bindings.map((binding) => ({ ...binding, exportedName: "default", default: true })) : [{ exportedName: "default", node: statement, typeOnly: false, default: true, reExport: false }];
|
|
1037
|
-
}
|
|
1038
|
-
return [{ exportedName: "default", node: statement, typeOnly: false, default: true, reExport: false }];
|
|
1039
|
-
}
|
|
1040
|
-
if (statement.type !== "ExportNamedDeclaration") {
|
|
1041
|
-
return [];
|
|
1042
|
-
}
|
|
1043
|
-
return [
|
|
1044
|
-
...declarationBindings(statement.declaration),
|
|
1045
|
-
...statement.specifiers.map((specifier) => {
|
|
1046
|
-
const localName = moduleExportName(specifier.local);
|
|
1047
|
-
const local = locals.get(localName);
|
|
1048
|
-
return {
|
|
1049
|
-
...local ?? {
|
|
1050
|
-
exportedName: moduleExportName(specifier.exported),
|
|
1051
|
-
node: specifier,
|
|
1052
|
-
typeOnly: false,
|
|
1053
|
-
default: false,
|
|
1054
|
-
reExport: Boolean(statement.source)
|
|
1055
|
-
},
|
|
1056
|
-
exportedName: moduleExportName(specifier.exported),
|
|
1057
|
-
localName,
|
|
1058
|
-
node: local?.node ?? specifier,
|
|
1059
|
-
typeOnly: specifier.exportKind === "type" || statement.exportKind === "type" || Boolean(local?.typeOnly),
|
|
1060
|
-
default: moduleExportName(specifier.exported) === "default",
|
|
1061
|
-
reExport: Boolean(statement.source)
|
|
1062
|
-
};
|
|
1063
|
-
})
|
|
1064
|
-
];
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
function exportsCollectFunctions(program) {
|
|
1068
|
-
return exportsCollect(program).flatMap((binding) => {
|
|
1069
|
-
const name = binding.localName ?? binding.exportedName;
|
|
1070
|
-
if (binding.declaration?.type === "FunctionDeclaration") {
|
|
1071
|
-
return [{ name, node: binding.declaration }];
|
|
1072
|
-
}
|
|
1073
|
-
return binding.initializer?.type === "FunctionExpression" || binding.initializer?.type === "ArrowFunctionExpression" ? [{ name, node: binding.initializer }] : [];
|
|
1074
|
-
});
|
|
1075
|
-
}
|
|
1076
|
-
// src/utils/helpers/naming.ts
|
|
1077
|
-
function namingPosixPath(filePath) {
|
|
1078
|
-
return filePath.replaceAll("\\", "/");
|
|
1079
|
-
}
|
|
1080
|
-
function namingFileBasename(filePath) {
|
|
1081
|
-
const normalized = namingPosixPath(filePath);
|
|
1082
|
-
const slash = normalized.lastIndexOf("/");
|
|
1083
|
-
return slash === -1 ? normalized : normalized.slice(slash + 1);
|
|
1084
|
-
}
|
|
1085
|
-
function namingDirSegments(filePath) {
|
|
1086
|
-
const parts = namingPosixPath(filePath).split("/").filter(Boolean);
|
|
1087
|
-
return parts.slice(0, -1);
|
|
1088
|
-
}
|
|
1089
|
-
function namingSegmentsAfter(filePath, after) {
|
|
1090
|
-
const dirs = namingDirSegments(filePath);
|
|
1091
|
-
const marker = after.split("/").filter(Boolean);
|
|
1092
|
-
if (marker.length === 0) {
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
for (let index = dirs.length - marker.length;index >= 0; index -= 1) {
|
|
1096
|
-
if (marker.every((part, offset) => dirs[index + offset] === part)) {
|
|
1097
|
-
return dirs.slice(index + marker.length);
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
function namingFolderPrefixes(folders, separator, singularize) {
|
|
1102
|
-
const joined = folders.join(separator);
|
|
1103
|
-
const last = folders.at(-1) ?? "";
|
|
1104
|
-
const prefixes = [joined];
|
|
1105
|
-
if (singularize === "trailing-s" && last.endsWith("s") && last.length > 1) {
|
|
1106
|
-
prefixes.push([...folders.slice(0, -1), last.slice(0, -1)].join(separator));
|
|
1107
|
-
}
|
|
1108
|
-
return prefixes.filter(Boolean);
|
|
1109
|
-
}
|
|
1110
|
-
function namingPascalCase(value) {
|
|
1111
|
-
return value.split(/[-_\s.]+/).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
|
|
1112
|
-
}
|
|
1113
|
-
function namingCamelCase(value) {
|
|
1114
|
-
const next = namingPascalCase(value);
|
|
1115
|
-
return `${next.charAt(0).toLowerCase()}${next.slice(1)}`;
|
|
1116
|
-
}
|
|
1117
|
-
function namingMatchTemplate(basename, template, placeholderPattern = "[a-z0-9-]+") {
|
|
1118
|
-
let pattern = "";
|
|
1119
|
-
let index = 0;
|
|
1120
|
-
while (index < template.length) {
|
|
1121
|
-
const char = template[index];
|
|
1122
|
-
if (char === "{") {
|
|
1123
|
-
const end = template.indexOf("}", index);
|
|
1124
|
-
if (end === -1) {
|
|
1125
|
-
return null;
|
|
1126
|
-
}
|
|
1127
|
-
const key = template.slice(index + 1, end);
|
|
1128
|
-
pattern += `(?<${key}>${placeholderPattern})`;
|
|
1129
|
-
index = end + 1;
|
|
1130
|
-
continue;
|
|
1131
|
-
}
|
|
1132
|
-
pattern += /[.+^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
|
|
1133
|
-
index += 1;
|
|
1134
|
-
}
|
|
1135
|
-
return new RegExp(`^${pattern}$`).exec(basename)?.groups ?? null;
|
|
1136
|
-
}
|
|
1137
|
-
var namingStemModes = ["before-first-dot", "full-basename"];
|
|
1138
|
-
function namingNormalized(value, normalize) {
|
|
1139
|
-
return normalize === "none" ? value : value.replaceAll(/[-_.]/g, "").toLowerCase();
|
|
1140
|
-
}
|
|
1141
|
-
function namingFileStem(basename, mode = "before-first-dot", trailingRoles = [], roleSeparators = [".", "-"]) {
|
|
1142
|
-
const stem = mode === "full-basename" ? basename : basename.split(".")[0] ?? "";
|
|
1143
|
-
const roles = [...new Set(trailingRoles.filter(Boolean))].sort((left, right) => right.length - left.length);
|
|
1144
|
-
const separators = [...new Set(roleSeparators.filter(Boolean))];
|
|
1145
|
-
if (roles.length === 0 || separators.length === 0) {
|
|
1146
|
-
return stem;
|
|
1147
|
-
}
|
|
1148
|
-
let remaining = stem;
|
|
1149
|
-
let changed = true;
|
|
1150
|
-
while (changed) {
|
|
1151
|
-
changed = false;
|
|
1152
|
-
for (const role of roles) {
|
|
1153
|
-
for (const separator of separators) {
|
|
1154
|
-
const suffix = `${separator}${role}`;
|
|
1155
|
-
if (remaining.length > suffix.length && remaining.endsWith(suffix)) {
|
|
1156
|
-
remaining = remaining.slice(0, -suffix.length);
|
|
1157
|
-
changed = true;
|
|
1158
|
-
break;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
if (changed) {
|
|
1162
|
-
break;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
return remaining;
|
|
1167
|
-
}
|
|
1168
|
-
function namingFilePrefixes(stem, normalize = "remove-separators", singularize = "none") {
|
|
1169
|
-
const prefixes = [namingNormalized(stem, normalize)];
|
|
1170
|
-
const parts = stem.split(/[-_]+/).filter(Boolean);
|
|
1171
|
-
const last = parts.at(-1) ?? "";
|
|
1172
|
-
if (singularize === "trailing-s" && last.endsWith("s") && last.length > 1) {
|
|
1173
|
-
prefixes.push(namingNormalized([...parts.slice(0, -1), last.slice(0, -1)].join("-"), normalize));
|
|
1174
|
-
}
|
|
1175
|
-
return [...new Set(prefixes.filter(Boolean))];
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
// src/utils/helpers/factories.ts
|
|
1179
|
-
function factoriesExpandTemplate(template, groups, camelCase = false) {
|
|
1180
|
-
return template.replaceAll(/\{([^}]+)\}/g, (placeholder, key) => {
|
|
1181
|
-
const group = groups[key.toLowerCase()];
|
|
1182
|
-
if (!group) {
|
|
1183
|
-
return placeholder;
|
|
1184
|
-
}
|
|
1185
|
-
if (/^[A-Z]/.test(key)) {
|
|
1186
|
-
return namingPascalCase(group);
|
|
1187
|
-
}
|
|
1188
|
-
return camelCase ? namingCamelCase(group) : group;
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
function factoriesCollectTopLevel(program, pattern) {
|
|
1192
|
-
return program.body.flatMap((statement) => {
|
|
1193
|
-
const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
|
|
1194
|
-
if (declaration?.type === "FunctionDeclaration") {
|
|
1195
|
-
const name = declaration.id?.name ?? "<anonymous>";
|
|
1196
|
-
return pattern.test(name) ? [{ name, node: declaration }] : [];
|
|
1197
|
-
}
|
|
1198
|
-
if (declaration?.type !== "VariableDeclaration") {
|
|
1199
|
-
return [];
|
|
1200
|
-
}
|
|
1201
|
-
return declaration.declarations.flatMap((item) => item.id.type === "Identifier" && pattern.test(item.id.name) && (item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression") ? [{ name: item.id.name, node: item.init }] : []);
|
|
1202
|
-
});
|
|
1203
|
-
}
|
|
1204
|
-
function factoriesDirectlyReturnedObjects(factory) {
|
|
1205
|
-
if (factory.node.type === "ArrowFunctionExpression" && factory.node.body.type === "ObjectExpression") {
|
|
1206
|
-
return [factory.node.body];
|
|
1207
|
-
}
|
|
1208
|
-
const result = [];
|
|
1209
|
-
astVisit(factory.node.body, [factory.node], (node, ancestors) => {
|
|
1210
|
-
if (node.type === "ReturnStatement" && node.argument?.type === "ObjectExpression" && astNearestFunction(ancestors) === factory.node) {
|
|
1211
|
-
result.push(node.argument);
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
return result;
|
|
1215
|
-
}
|
|
1216
|
-
function factoriesObjectPropertyName(property) {
|
|
1217
|
-
if (property.type === "SpreadElement" || property.computed) {
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
if (property.key.type === "Identifier") {
|
|
1221
|
-
return property.key.name;
|
|
1222
|
-
}
|
|
1223
|
-
return property.key.type === "Literal" && typeof property.key.value === "string" ? property.key.value : undefined;
|
|
1224
|
-
}
|
|
1225
|
-
// src/utils/helpers/guards.ts
|
|
1226
|
-
var guardsPrimitiveNames = new Set([
|
|
1227
|
-
"string",
|
|
1228
|
-
"number",
|
|
1229
|
-
"boolean",
|
|
1230
|
-
"object",
|
|
1231
|
-
"undefined",
|
|
1232
|
-
"bigint",
|
|
1233
|
-
"symbol",
|
|
1234
|
-
"function"
|
|
1235
|
-
]);
|
|
1236
|
-
function guardsValue(node, state) {
|
|
1237
|
-
const value = trivialUnwrap(node);
|
|
1238
|
-
if (value.type === "Identifier")
|
|
1239
|
-
return state.values.has(value.name);
|
|
1240
|
-
return value.type === "MemberExpression" && value.computed && value.object.type === "Identifier" && state.values.has(value.object.name) && value.property.type === "Identifier" && state.values.has(value.property.name);
|
|
1241
|
-
}
|
|
1242
|
-
function guardsFallback(node) {
|
|
1243
|
-
return node.type === "Literal" && !("regex" in node) || node.type === "Identifier" && node.name === "undefined" || node.type === "ArrayExpression" && node.elements.length === 0 || node.type === "ObjectExpression" && node.properties.length === 0;
|
|
1244
|
-
}
|
|
1245
|
-
function guardsPredicate(expression, state) {
|
|
1246
|
-
const node = trivialUnwrap(expression);
|
|
1247
|
-
if (node.type === "Identifier")
|
|
1248
|
-
return state.predicates.has(node.name);
|
|
1249
|
-
if (node.type === "UnaryExpression" && node.operator === "!")
|
|
1250
|
-
return guardsPredicate(node.argument, state);
|
|
1251
|
-
if (node.type === "LogicalExpression" && (node.operator === "&&" || node.operator === "||")) {
|
|
1252
|
-
return guardsPredicate(node.left, state) && guardsPredicate(node.right, state);
|
|
1253
|
-
}
|
|
1254
|
-
if (node.type === "BinaryExpression" && ["===", "!==", "==", "!="].includes(node.operator)) {
|
|
1255
|
-
for (const [left, right] of [
|
|
1256
|
-
[node.left, node.right],
|
|
1257
|
-
[node.right, node.left]
|
|
1258
|
-
]) {
|
|
1259
|
-
if (left.type === "UnaryExpression" && left.operator === "typeof" && right.type === "Literal" && typeof right.value === "string" && guardsPrimitiveNames.has(right.value) && guardsValue(left.argument, state)) {
|
|
1260
|
-
state.primitive = true;
|
|
1261
|
-
return true;
|
|
1262
|
-
}
|
|
1263
|
-
if (left.type !== "PrivateIdentifier" && guardsValue(left, state) && (right.type === "Literal" && right.value === null || right.type === "Identifier" && right.name === "undefined"))
|
|
1264
|
-
return true;
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
if (node.type === "CallExpression" && !node.optional && node.arguments.length === 1 && node.arguments[0].type !== "SpreadElement" && guardsValue(node.arguments[0], state) && node.callee.type === "MemberExpression" && !node.callee.computed && !node.callee.optional && node.callee.object.type === "Identifier" && node.callee.object.name === "Array" && node.callee.property.type === "Identifier" && node.callee.property.name === "isArray" && state.globalArray(node.callee.object)) {
|
|
1268
|
-
state.primitive = true;
|
|
1269
|
-
return true;
|
|
1270
|
-
}
|
|
1271
|
-
return false;
|
|
1272
|
-
}
|
|
1273
|
-
function guardsResult(node, state) {
|
|
1274
|
-
const value = trivialUnwrap(node);
|
|
1275
|
-
if (guardsValue(value, state) || guardsFallback(value) || guardsPredicate(value, state))
|
|
1276
|
-
return true;
|
|
1277
|
-
return value.type === "ConditionalExpression" && guardsPredicate(value.test, state) && guardsResult(value.consequent, state) && guardsResult(value.alternate, state);
|
|
1278
|
-
}
|
|
1279
|
-
function guardsStatement(node, state) {
|
|
1280
|
-
if (node.type === "EmptyStatement" || node.type === "ExpressionStatement" && Boolean(node.directive))
|
|
1281
|
-
return true;
|
|
1282
|
-
if (node.type === "ReturnStatement" && node.argument) {
|
|
1283
|
-
state.returned = true;
|
|
1284
|
-
return guardsResult(node.argument, state);
|
|
1285
|
-
}
|
|
1286
|
-
if (node.type === "BlockStatement")
|
|
1287
|
-
return node.body.every((statement) => guardsStatement(statement, state));
|
|
1288
|
-
if (node.type === "IfStatement") {
|
|
1289
|
-
return guardsPredicate(node.test, state) && guardsStatement(node.consequent, state) && (!node.alternate || guardsStatement(node.alternate, state));
|
|
1290
|
-
}
|
|
1291
|
-
if (node.type === "VariableDeclaration" && node.kind === "const") {
|
|
1292
|
-
return node.declarations.every((declaration) => {
|
|
1293
|
-
if (declaration.id.type !== "Identifier" || !declaration.init)
|
|
1294
|
-
return false;
|
|
1295
|
-
if (guardsValue(declaration.init, state)) {
|
|
1296
|
-
state.values.add(declaration.id.name);
|
|
1297
|
-
return true;
|
|
1298
|
-
}
|
|
1299
|
-
if (guardsPredicate(declaration.init, state)) {
|
|
1300
|
-
state.predicates.add(declaration.id.name);
|
|
1301
|
-
return true;
|
|
1302
|
-
}
|
|
1303
|
-
return guardsFallback(declaration.init);
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
return false;
|
|
1307
|
-
}
|
|
1308
|
-
function guardsIsGenericHelper(node, globalArray) {
|
|
1309
|
-
if (!node.body || node.params.length === 0 || node.params.some((param) => param.type !== "Identifier"))
|
|
1310
|
-
return false;
|
|
1311
|
-
const state = {
|
|
1312
|
-
values: new Set(node.params.flatMap((param) => param.type === "Identifier" ? [param.name] : [])),
|
|
1313
|
-
predicates: new Set,
|
|
1314
|
-
primitive: false,
|
|
1315
|
-
returned: false,
|
|
1316
|
-
globalArray
|
|
1317
|
-
};
|
|
1318
|
-
if (node.body.type !== "BlockStatement")
|
|
1319
|
-
return guardsResult(node.body, state) && state.primitive;
|
|
1320
|
-
return node.body.body.every((statement) => guardsStatement(statement, state)) && state.primitive && state.returned;
|
|
1321
|
-
}
|
|
1322
|
-
// src/utils/helpers/layout.ts
|
|
1323
|
-
var layoutIndentSchema = {
|
|
1324
|
-
anyOf: [
|
|
1325
|
-
{ type: "integer", minimum: 0 },
|
|
1326
|
-
{ type: "string", enum: ["tab"] }
|
|
1327
|
-
]
|
|
1328
|
-
};
|
|
1329
|
-
function layoutLineIndent(text, offset) {
|
|
1330
|
-
const lineStart = text.lastIndexOf(`
|
|
1331
|
-
`, offset - 1) + 1;
|
|
1332
|
-
return /^[\t ]*/.exec(text.slice(lineStart, offset))?.[0] ?? "";
|
|
1333
|
-
}
|
|
1334
|
-
function layoutIndentUnit(indent = 2) {
|
|
1335
|
-
return indent === "tab" ? "\t" : " ".repeat(indent);
|
|
1336
|
-
}
|
|
1337
|
-
function layoutNewline(text) {
|
|
1338
|
-
return text.includes(`\r
|
|
1339
|
-
`) ? `\r
|
|
1340
|
-
` : `
|
|
1341
|
-
`;
|
|
1342
|
-
}
|
|
1343
|
-
// src/utils/helpers/mocking.ts
|
|
1344
|
-
function mockingApiPatternsAssert(patterns) {
|
|
1345
|
-
if (patterns.some((pattern) => !/^(?:\*|(?:vi|jest|\*)\.(?:[A-Za-z_$][\w$]*|\*))$/.test(pattern)))
|
|
1346
|
-
throw new Error("Test mocking APIs must be `*` or `<vi|jest|*>.<method|*>`.");
|
|
1347
|
-
}
|
|
1348
|
-
// src/utils/helpers/options.ts
|
|
1349
|
-
function optionsFirst(context, fallback) {
|
|
1350
|
-
return context.options[0] ?? fallback;
|
|
1351
|
-
}
|
|
1352
|
-
var optionsPatternSchema = {
|
|
1353
|
-
anyOf: [{ type: "string" }, { type: "array", items: { type: "string" }, minItems: 1 }]
|
|
1354
|
-
};
|
|
1355
|
-
function optionsPatterns(pattern, flags) {
|
|
1356
|
-
return pattern === undefined ? [] : [pattern].flat().map((source) => new RegExp(source, flags));
|
|
1357
|
-
}
|
|
1358
|
-
function optionsOptionalPatterns(pattern, flags) {
|
|
1359
|
-
return optionsPatterns(pattern === undefined ? undefined : [pattern].flat().filter((source) => source !== ""), flags);
|
|
1360
|
-
}
|
|
1361
|
-
function optionsPatternLabel(pattern) {
|
|
1362
|
-
return pattern === undefined ? "" : [pattern].flat().join(" | ");
|
|
1363
|
-
}
|
|
1364
|
-
function optionsPatternsTest(patterns, value) {
|
|
1365
|
-
return patterns.some((pattern) => {
|
|
1366
|
-
pattern.lastIndex = 0;
|
|
1367
|
-
return pattern.test(value);
|
|
1368
|
-
});
|
|
1369
|
-
}
|
|
1370
|
-
// src/utils/helpers/orpc.ts
|
|
1371
|
-
function orpcFindChainCall(expression, method) {
|
|
1372
|
-
if (expression.type === "CallExpression") {
|
|
1373
|
-
if (expression.callee.type === "MemberExpression" && astStaticMemberName(expression.callee) === method) {
|
|
1374
|
-
return expression;
|
|
1375
|
-
}
|
|
1376
|
-
if (expression.callee.type === "MemberExpression") {
|
|
1377
|
-
return orpcFindChainCall(expression.callee.object, method);
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
if (expression.type === "MemberExpression") {
|
|
1381
|
-
return orpcFindChainCall(expression.object, method);
|
|
1382
|
-
}
|
|
1383
|
-
if (expression.type === "ChainExpression") {
|
|
1384
|
-
return orpcFindChainCall(expression.expression, method);
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
function orpcIsNamedOrComposedArgument(argument, composers) {
|
|
1388
|
-
if (argument?.type === "Identifier") {
|
|
1389
|
-
return true;
|
|
1390
|
-
}
|
|
1391
|
-
return argument?.type === "CallExpression" && argument.callee.type === "Identifier" && composers.has(argument.callee.name) && argument.arguments[0]?.type === "Identifier";
|
|
1392
|
-
}
|
|
1393
|
-
// src/utils/helpers/react-components.ts
|
|
1394
|
-
function reactComponentsExportCandidates(program) {
|
|
1395
|
-
return exportsCollect(program).map((binding) => ({
|
|
1396
|
-
...binding,
|
|
1397
|
-
name: binding.localName ?? binding.exportedName
|
|
1398
|
-
}));
|
|
1399
|
-
}
|
|
1400
|
-
function importedPropsWithChildren(program) {
|
|
1401
|
-
return new Set(program.body.flatMap((statement) => statement.type === "ImportDeclaration" && statement.source.value === "react" ? statement.specifiers.flatMap((specifier) => specifier.type === "ImportSpecifier" && specifier.imported.type === "Identifier" && specifier.imported.name === "PropsWithChildren" ? [specifier.local.name] : []) : []));
|
|
1402
|
-
}
|
|
1403
|
-
function containsNamedTypeReference(node, names) {
|
|
1404
|
-
let found = false;
|
|
1405
|
-
astVisit(node, [], (candidate) => {
|
|
1406
|
-
if (candidate.type === "TSTypeReference" && candidate.typeName.type === "Identifier" && names.has(candidate.typeName.name)) {
|
|
1407
|
-
found = true;
|
|
1408
|
-
}
|
|
1409
|
-
});
|
|
1410
|
-
return found;
|
|
1411
|
-
}
|
|
1412
|
-
function returnsReactChildren(program, node, expressions) {
|
|
1413
|
-
const returnedNames = new Set(expressions.flatMap((expression) => expression.type === "Identifier" ? [expression.name] : []));
|
|
1414
|
-
const propsWithChildren = importedPropsWithChildren(program);
|
|
1415
|
-
return node.params.some((param) => {
|
|
1416
|
-
if (param.type !== "ObjectPattern") {
|
|
1417
|
-
return false;
|
|
1418
|
-
}
|
|
1419
|
-
const returnsChild = param.properties.some((property) => property.type === "Property" && property.value.type === "Identifier" && returnedNames.has(property.value.name));
|
|
1420
|
-
return returnsChild && containsNamedTypeReference(param.typeAnnotation, propsWithChildren);
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
function reactComponentsFunctionReturnsOutput(program, node) {
|
|
1424
|
-
const expressions = astDirectReturnExpressions(node);
|
|
1425
|
-
return expressions.some((expression) => astContainsJsx(expression) || expression.type === "Literal" && expression.value === null) || returnsReactChildren(program, node, expressions);
|
|
1426
|
-
}
|
|
1427
|
-
function functionReturnsLocalComponent(program, node) {
|
|
1428
|
-
const componentNames = new Set;
|
|
1429
|
-
astVisit(node.body, [node], (candidate, ancestors) => {
|
|
1430
|
-
if (candidate.type === "FunctionDeclaration" && candidate !== node && candidate.id?.name && /^[A-Z]/.test(candidate.id.name) && astNearestFunction(ancestors) === node && reactComponentsFunctionReturnsOutput(program, candidate)) {
|
|
1431
|
-
componentNames.add(candidate.id.name);
|
|
1432
|
-
}
|
|
1433
|
-
});
|
|
1434
|
-
return astDirectReturnExpressions(node).some((expression) => expression.type === "Identifier" && componentNames.has(expression.name));
|
|
1435
|
-
}
|
|
1436
|
-
function componentFactoryNames(program) {
|
|
1437
|
-
return new Set(program.body.flatMap((statement) => statement.type === "FunctionDeclaration" && statement.id?.name && functionReturnsLocalComponent(program, statement) ? [statement.id.name] : []));
|
|
1438
|
-
}
|
|
1439
|
-
function isComponentInitializer(program, initializer) {
|
|
1440
|
-
if (!initializer) {
|
|
1441
|
-
return false;
|
|
1442
|
-
}
|
|
1443
|
-
if (initializer.type === "ArrowFunctionExpression" || initializer.type === "FunctionExpression") {
|
|
1444
|
-
return reactComponentsFunctionReturnsOutput(program, initializer);
|
|
1445
|
-
}
|
|
1446
|
-
if (initializer.type !== "CallExpression") {
|
|
1447
|
-
return false;
|
|
1448
|
-
}
|
|
1449
|
-
const name = astCallName(initializer);
|
|
1450
|
-
if (name === "forwardRef" || name === "memo") {
|
|
1451
|
-
return initializer.arguments.some((argument) => argument.type !== "SpreadElement" && isComponentInitializer(program, argument));
|
|
1452
|
-
}
|
|
1453
|
-
if (name === "lazy") {
|
|
1454
|
-
return initializer.arguments.some((argument) => argument.type === "ArrowFunctionExpression" || argument.type === "FunctionExpression");
|
|
1455
|
-
}
|
|
1456
|
-
return Boolean(name?.startsWith("create") && componentFactoryNames(program).has(name));
|
|
1457
|
-
}
|
|
1458
|
-
function reactComponentsIsLike(program, candidate) {
|
|
1459
|
-
if (!/^[A-Z]/.test(candidate.name)) {
|
|
1460
|
-
return false;
|
|
1461
|
-
}
|
|
1462
|
-
const declaration = candidate.declaration ?? candidate.node;
|
|
1463
|
-
if (declaration.type === "FunctionDeclaration") {
|
|
1464
|
-
return reactComponentsFunctionReturnsOutput(program, declaration);
|
|
1465
|
-
}
|
|
1466
|
-
return declaration.type === "VariableDeclaration" && isComponentInitializer(program, candidate.initializer);
|
|
1467
|
-
}
|
|
1468
|
-
function reactComponentsPascalFromBasename(basename) {
|
|
1469
|
-
return basename.replace(/\.(tsx?|jsx?)$/, "").split(/[-.]/).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
|
|
1470
|
-
}
|
|
1471
|
-
// src/utils/helpers/routes.ts
|
|
1472
|
-
function routesInspectFunction(context, item, bannedHooks, banIntrinsicJsx) {
|
|
1473
|
-
astVisit(item.node.body, [item.node], (node) => {
|
|
1474
|
-
if (banIntrinsicJsx && node.type === "JSXOpeningElement" && node.name.type === "JSXIdentifier" && /^[a-z]/.test(node.name.name)) {
|
|
1475
|
-
context.report({
|
|
1476
|
-
node,
|
|
1477
|
-
messageId: "hostJsx",
|
|
1478
|
-
data: { name: item.name }
|
|
1479
|
-
});
|
|
1480
|
-
}
|
|
1481
|
-
if (node.type === "CallExpression") {
|
|
1482
|
-
const name = astCallName(node);
|
|
1483
|
-
if (name && bannedHooks.has(name)) {
|
|
1484
|
-
context.report({
|
|
1485
|
-
node,
|
|
1486
|
-
messageId: "bannedHook",
|
|
1487
|
-
data: { hook: name, name: item.name }
|
|
1488
|
-
});
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
});
|
|
1492
|
-
}
|
|
1493
|
-
// src/utils/helpers/runtime.ts
|
|
1494
|
-
import { builtinModules } from "node:module";
|
|
1495
|
-
var runtimeServerGlobals = ["process", "Bun", "Deno", "Buffer", "__dirname", "__filename", "require"];
|
|
1496
|
-
var runtimeBrowserGlobals = [
|
|
1497
|
-
"window",
|
|
1498
|
-
"document",
|
|
1499
|
-
"navigator",
|
|
1500
|
-
"localStorage",
|
|
1501
|
-
"sessionStorage",
|
|
1502
|
-
"location",
|
|
1503
|
-
"history",
|
|
1504
|
-
"screen",
|
|
1505
|
-
"HTMLElement",
|
|
1506
|
-
"Element",
|
|
1507
|
-
"HTMLInputElement",
|
|
1508
|
-
"ResizeObserver",
|
|
1509
|
-
"MutationObserver",
|
|
1510
|
-
"IntersectionObserver",
|
|
1511
|
-
"FileReader",
|
|
1512
|
-
"matchMedia",
|
|
1513
|
-
"getComputedStyle",
|
|
1514
|
-
"requestAnimationFrame",
|
|
1515
|
-
"cancelAnimationFrame",
|
|
1516
|
-
"alert",
|
|
1517
|
-
"confirm",
|
|
1518
|
-
"prompt"
|
|
1519
|
-
];
|
|
1520
|
-
function runtimeMatchesImport(values, source) {
|
|
1521
|
-
return Boolean(values?.some((value) => source === value || source.startsWith(value + "/")));
|
|
1522
|
-
}
|
|
1523
|
-
function runtimeIsBackendImport(options, target, source) {
|
|
1524
|
-
return options.backend.some((path) => target.startsWith(path + "/")) || options.backendPackages.some((name) => source === name || source.startsWith(name + "/") && /\/(?:server|rpc)(?:\/|$)/.test(source.slice(name.length)));
|
|
1525
|
-
}
|
|
1526
|
-
function runtimeIsServerPlatformImport(source) {
|
|
1527
|
-
return builtinModules.includes(source) || source === "@orpc/server" || /^(?:node:|bun(?::|$)|cloudflare:)/.test(source) || /^react-dom\/server(?:\.|\/|$)/.test(source);
|
|
1528
|
-
}
|
|
1529
|
-
function runtimeMemberName(node) {
|
|
1530
|
-
if (node?.type !== "MemberExpression")
|
|
1531
|
-
return null;
|
|
1532
|
-
if (!node.computed && node.property.type === "Identifier")
|
|
1533
|
-
return node.property.name;
|
|
1534
|
-
return node.computed && node.property.type === "Literal" && typeof node.property.value === "string" ? node.property.value : null;
|
|
1535
|
-
}
|
|
1536
|
-
// src/utils/helpers/sanitizers.ts
|
|
1537
|
-
function sanitizersCallsConfigured(node, names) {
|
|
1538
|
-
let found = false;
|
|
1539
|
-
astVisit(node, [], (candidate) => {
|
|
1540
|
-
if (candidate.type === "CallExpression" && candidate.callee.type === "Identifier" && names.has(candidate.callee.name)) {
|
|
1541
|
-
found = true;
|
|
1542
|
-
}
|
|
1543
|
-
});
|
|
1544
|
-
return found;
|
|
1545
|
-
}
|
|
1546
|
-
function sanitizersConfiguredBindings(program, names) {
|
|
1547
|
-
const result = new Set;
|
|
1548
|
-
for (const statement of program.body) {
|
|
1549
|
-
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
1550
|
-
if (declaration?.type !== "VariableDeclaration") {
|
|
1551
|
-
continue;
|
|
1552
|
-
}
|
|
1553
|
-
for (const item of declaration.declarations) {
|
|
1554
|
-
if (item.id.type === "Identifier" && sanitizersCallsConfigured(item.init, names)) {
|
|
1555
|
-
result.add(item.id.name);
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
return result;
|
|
1560
|
-
}
|
|
1561
|
-
// src/utils/helpers/schemas.ts
|
|
1562
|
-
function schemasIsRuntimeSpecifier(specifier) {
|
|
1563
|
-
return specifier.type !== "ImportSpecifier" || specifier.importKind !== "type";
|
|
1564
|
-
}
|
|
1565
|
-
function schemasMemberRootName(node) {
|
|
1566
|
-
if (node.object.type === "Identifier") {
|
|
1567
|
-
return node.object.name;
|
|
1568
|
-
}
|
|
1569
|
-
return node.object.type === "MemberExpression" ? schemasMemberRootName(node.object) : undefined;
|
|
1570
|
-
}
|
|
1571
|
-
function schemasIsPackage(source, packages) {
|
|
1572
|
-
return packages.has(source) || [...packages].some((pkg) => source.startsWith(`${pkg}/`));
|
|
1573
|
-
}
|
|
1574
|
-
function schemasIsCall(node, namespaces) {
|
|
1575
|
-
return node.callee.type === "MemberExpression" && node.callee.object.type === "Identifier" && namespaces.has(node.callee.object.name);
|
|
1576
|
-
}
|
|
1577
|
-
function schemasRuntimeImportLocals(node) {
|
|
1578
|
-
return node.importKind === "type" ? [] : node.specifiers.flatMap((specifier) => schemasIsRuntimeSpecifier(specifier) ? [specifier.local.name] : []);
|
|
1579
|
-
}
|
|
1580
|
-
function schemasIsRuntimeImport(node, packages) {
|
|
1581
|
-
return schemasIsPackage(node.source.value, packages) && (node.specifiers.length === 0 ? node.importKind !== "type" : schemasRuntimeImportLocals(node).length > 0);
|
|
1582
|
-
}
|
|
1583
|
-
function schemasIsRuntimeReexport(node, packages) {
|
|
1584
|
-
if (!node.source || !schemasIsPackage(node.source.value, packages) || node.exportKind === "type") {
|
|
1585
|
-
return false;
|
|
1586
|
-
}
|
|
1587
|
-
return node.type === "ExportAllDeclaration" || node.specifiers.length === 0 || node.specifiers.some((specifier) => specifier.exportKind !== "type");
|
|
1588
|
-
}
|
|
1589
|
-
function schemasIsConstruction(node, constructors) {
|
|
1590
|
-
if (node.callee.type === "Identifier") {
|
|
1591
|
-
return constructors.has(node.callee.name);
|
|
1592
|
-
}
|
|
1593
|
-
if (node.callee.type !== "MemberExpression") {
|
|
1594
|
-
return false;
|
|
1595
|
-
}
|
|
1596
|
-
const root = schemasMemberRootName(node.callee);
|
|
1597
|
-
return Boolean(root && constructors.has(root));
|
|
1598
|
-
}
|
|
1599
|
-
function schemasElements(node) {
|
|
1600
|
-
return node.arguments.flatMap((argument) => argument.type === "ArrayExpression" ? argument.elements.filter((element) => element !== null) : [argument]);
|
|
1601
|
-
}
|
|
1602
|
-
function schemasIsAllowedElement(node, allowScalars, namespaces, structuralMethods) {
|
|
1603
|
-
if (node.type === "Identifier") {
|
|
1604
|
-
return true;
|
|
1605
|
-
}
|
|
1606
|
-
if (!allowScalars || node.type !== "CallExpression" || !schemasIsCall(node, namespaces)) {
|
|
1607
|
-
return false;
|
|
1608
|
-
}
|
|
1609
|
-
const name = node.callee.type === "MemberExpression" ? astStaticMemberName(node.callee) : undefined;
|
|
1610
|
-
return Boolean(name && !structuralMethods.has(name));
|
|
1611
|
-
}
|
|
1612
|
-
function schemasTypeOperator(node, namespaces, operators) {
|
|
1613
|
-
return node.typeName.type === "TSQualifiedName" && node.typeName.left.type === "Identifier" && namespaces.has(node.typeName.left.name) && operators.has(node.typeName.right.name) ? node.typeName.right.name : undefined;
|
|
1614
|
-
}
|
|
1615
|
-
// src/utils/helpers/boundaries.ts
|
|
1616
|
-
import { resolve, dirname } from "node:path";
|
|
1617
|
-
function boundaryResolveTarget(options, file, source) {
|
|
1618
|
-
const owner = [...options.web, ...options.backend].sort((a, b) => b.length - a.length).find((root) => file.startsWith(root + "/"));
|
|
1619
|
-
const web = owner && options.web.includes(owner) ? owner : undefined;
|
|
1620
|
-
const alias = Object.entries(options.aliases).sort(([a], [b]) => b.length - a.length).find(([prefix]) => source === prefix || source.startsWith(`${prefix}/`));
|
|
1621
|
-
return alias ? resolve(alias[1], source.slice(alias[0].length).replace(/^\//, "")).replaceAll("\\", "/") : source.startsWith(".") ? resolve(dirname(file), source).replaceAll("\\", "/") : source.startsWith("@/") && web ? `${web}/${source.slice(2)}` : source.startsWith("#/") && owner ? `${owner}/${source.slice(2)}` : source.startsWith("#services/") && owner ? `${owner}/services/${source.slice(10)}` : source;
|
|
1622
|
-
}
|
|
1623
|
-
// src/rules/dillon-anti-slop/rules/no-module-mocking.ts
|
|
1624
|
-
var moduleMockMethods = new Set(["doMock", "mock", "unstable_mockModule"]);
|
|
1625
|
-
var defaultDeny = [
|
|
1626
|
-
{ api: "*.mock" },
|
|
1627
|
-
{ api: "*.doMock" },
|
|
1628
|
-
{ api: "*.unstable_mockModule" }
|
|
1629
|
-
];
|
|
1630
|
-
function resolveVariable2(sourceCode, identifier) {
|
|
1631
|
-
let scope = sourceCode.getScope(identifier);
|
|
1632
|
-
while (scope !== null) {
|
|
1633
|
-
const variable = scope.set.get(identifier.name);
|
|
1634
|
-
if (variable !== undefined)
|
|
1635
|
-
return variable;
|
|
1636
|
-
scope = scope.upper;
|
|
1637
|
-
}
|
|
1638
|
-
return null;
|
|
1639
|
-
}
|
|
1640
|
-
function importedName(node) {
|
|
1641
|
-
if (node.type !== "ImportSpecifier")
|
|
1642
|
-
return null;
|
|
1643
|
-
return node.imported.type === "Identifier" ? node.imported.name : node.imported.value;
|
|
1644
|
-
}
|
|
1645
|
-
function testFramework(sourceCode, expression) {
|
|
1646
|
-
if (expression.type !== "Identifier")
|
|
1647
|
-
return null;
|
|
1648
|
-
const name = expression.name === "vi" || expression.name === "jest" ? expression.name : null;
|
|
1649
|
-
if (name !== null && sourceCode.isGlobalReference(expression))
|
|
1650
|
-
return name;
|
|
1651
|
-
const variable = resolveVariable2(sourceCode, expression);
|
|
1652
|
-
if (variable === null || variable.defs.length === 0)
|
|
1653
|
-
return name;
|
|
1654
|
-
for (const definition of variable.defs) {
|
|
1655
|
-
if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration") {
|
|
1656
|
-
continue;
|
|
1657
|
-
}
|
|
1658
|
-
const source = definition.parent.source.value;
|
|
1659
|
-
const imported = importedName(definition.node);
|
|
1660
|
-
if (source === "vitest" && imported === "vi")
|
|
1661
|
-
return "vi";
|
|
1662
|
-
if (source === "@jest/globals" && imported === "jest")
|
|
1663
|
-
return "jest";
|
|
1664
|
-
}
|
|
1665
|
-
return null;
|
|
1666
|
-
}
|
|
1667
|
-
function propertyName(key, computed) {
|
|
1668
|
-
if (key.type === "Literal" && typeof key.value === "string")
|
|
1669
|
-
return key.value;
|
|
1670
|
-
return !computed && key.type === "Identifier" ? key.name : null;
|
|
1671
|
-
}
|
|
1672
|
-
function calledApi(sourceCode, callee) {
|
|
1673
|
-
if (callee.type !== "MemberExpression")
|
|
1674
|
-
return null;
|
|
1675
|
-
const framework = testFramework(sourceCode, callee.object);
|
|
1676
|
-
if (framework === null)
|
|
1677
|
-
return null;
|
|
1678
|
-
const method = propertyName(callee.property, callee.computed);
|
|
1679
|
-
return method === null ? null : `${framework}.${method}`;
|
|
1680
|
-
}
|
|
1681
|
-
function destructuredApi(sourceCode, callee) {
|
|
1682
|
-
if (callee.type !== "Identifier")
|
|
1683
|
-
return null;
|
|
1684
|
-
const definition = resolveVariable2(sourceCode, callee)?.defs[0];
|
|
1685
|
-
if (definition?.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
1686
|
-
return null;
|
|
1687
|
-
const declarator = definition.node;
|
|
1688
|
-
if (declarator?.type !== "VariableDeclarator" || declarator.id.type !== "ObjectPattern" || declarator.init === null)
|
|
1689
|
-
return null;
|
|
1690
|
-
const framework = testFramework(sourceCode, declarator.init);
|
|
1691
|
-
if (framework === null)
|
|
1692
|
-
return null;
|
|
1693
|
-
for (const property of declarator.id.properties) {
|
|
1694
|
-
if (property.type !== "Property")
|
|
1695
|
-
continue;
|
|
1696
|
-
const binding = property.value.type === "AssignmentPattern" ? property.value.left : property.value;
|
|
1697
|
-
if (binding.type !== "Identifier" || binding.name !== callee.name)
|
|
1698
|
-
continue;
|
|
1699
|
-
const method = propertyName(property.key, property.computed);
|
|
1700
|
-
return method === null ? null : `${framework}.${method}`;
|
|
1701
|
-
}
|
|
1702
|
-
return null;
|
|
1703
|
-
}
|
|
1704
|
-
function apiParts(api) {
|
|
1705
|
-
const dot = api.indexOf(".");
|
|
1706
|
-
return [api.slice(0, dot), api.slice(dot + 1)];
|
|
1707
|
-
}
|
|
1708
|
-
function apiMatches(pattern, api) {
|
|
1709
|
-
if (pattern === "*")
|
|
1710
|
-
return true;
|
|
1711
|
-
const [patternFramework, patternMethod] = apiParts(pattern);
|
|
1712
|
-
const [framework, method] = apiParts(api);
|
|
1713
|
-
return (patternFramework === "*" || patternFramework === framework) && (patternMethod === "*" || patternMethod === method);
|
|
1714
|
-
}
|
|
1715
|
-
var noModuleMockingRule = defineRule4({
|
|
1716
|
-
meta: {
|
|
1717
|
-
type: "problem",
|
|
1718
|
-
docs: {
|
|
1719
|
-
description: "Disallow Vitest and Jest module mocking; tests must replace dependencies through real interfaces."
|
|
1720
|
-
},
|
|
1721
|
-
schema: [
|
|
1722
|
-
{
|
|
1723
|
-
type: "object",
|
|
1724
|
-
additionalProperties: false,
|
|
1725
|
-
properties: {
|
|
1726
|
-
deny: {
|
|
1727
|
-
type: "array",
|
|
1728
|
-
items: {
|
|
1729
|
-
anyOf: [
|
|
1730
|
-
{ type: "string" },
|
|
1731
|
-
{
|
|
1732
|
-
type: "object",
|
|
1733
|
-
additionalProperties: false,
|
|
1734
|
-
properties: {
|
|
1735
|
-
api: { type: "string" },
|
|
1736
|
-
message: { type: "string" }
|
|
1737
|
-
},
|
|
1738
|
-
required: ["api"]
|
|
1739
|
-
}
|
|
1740
|
-
]
|
|
1741
|
-
}
|
|
1742
|
-
},
|
|
1743
|
-
allow: { type: "array", items: { type: "string" } },
|
|
1744
|
-
checkDestructured: { type: "boolean" }
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
],
|
|
1748
|
-
messages: {
|
|
1749
|
-
moduleMock: "Replace module mocking with dependency injection through a real interface, service layer, or faithful test implementation.",
|
|
1750
|
-
testDouble: "Replace {{api}} with dependency injection through a real interface, service layer, or faithful test implementation.",
|
|
1751
|
-
custom: "{{message}}"
|
|
1752
|
-
}
|
|
1753
|
-
},
|
|
1754
|
-
createOnce(context) {
|
|
1755
|
-
let deny = [];
|
|
1756
|
-
let allow = [];
|
|
1757
|
-
let checkDestructured = false;
|
|
1758
|
-
return {
|
|
1759
|
-
before() {
|
|
1760
|
-
const options2 = optionsFirst(context, {});
|
|
1761
|
-
deny = (options2.deny ?? defaultDeny).map((entry) => typeof entry === "string" ? { api: entry } : entry);
|
|
1762
|
-
allow = options2.allow ?? [];
|
|
1763
|
-
checkDestructured = options2.checkDestructured ?? false;
|
|
1764
|
-
mockingApiPatternsAssert([...deny.map((entry) => entry.api), ...allow]);
|
|
1765
|
-
},
|
|
1766
|
-
CallExpression(node) {
|
|
1767
|
-
if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression")
|
|
1768
|
-
return;
|
|
1769
|
-
const api = calledApi(context.sourceCode, node.callee) ?? (checkDestructured ? destructuredApi(context.sourceCode, node.callee) : null);
|
|
1770
|
-
if (api === null)
|
|
1771
|
-
return;
|
|
1772
|
-
if (allow.some((pattern) => apiMatches(pattern, api)))
|
|
1773
|
-
return;
|
|
1774
|
-
const denied = deny.find((entry) => apiMatches(entry.api, api));
|
|
1775
|
-
if (denied === undefined)
|
|
1776
|
-
return;
|
|
1777
|
-
if (denied.message !== undefined) {
|
|
1778
|
-
context.report({
|
|
1779
|
-
node,
|
|
1780
|
-
messageId: "custom",
|
|
1781
|
-
data: { message: denied.message }
|
|
1782
|
-
});
|
|
1783
|
-
return;
|
|
1784
|
-
}
|
|
1785
|
-
context.report({
|
|
1786
|
-
node,
|
|
1787
|
-
messageId: moduleMockMethods.has(apiParts(api)[1]) ? "moduleMock" : "testDouble",
|
|
1788
|
-
data: { api }
|
|
1789
|
-
});
|
|
1790
|
-
}
|
|
1791
|
-
};
|
|
1792
|
-
}
|
|
1793
|
-
});
|
|
1794
|
-
|
|
1795
|
-
// src/rules/dillon-anti-slop/rules/no-object-parameters.ts
|
|
1796
|
-
import { defineRule as defineRule5 } from "@oxlint/plugins";
|
|
1797
|
-
|
|
1798
|
-
// src/rules/dillon-anti-slop/shared/lexical-type-parameters.ts
|
|
1799
|
-
function isNode(value) {
|
|
1800
|
-
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
1801
|
-
}
|
|
1802
|
-
function collectInferTypeParameterNames(node, visitorKeys, names) {
|
|
1803
|
-
if (node.type === "TSInferType")
|
|
1804
|
-
names.add(node.typeParameter.name.name);
|
|
1805
|
-
const record = node;
|
|
1806
|
-
for (const key of visitorKeys[node.type] ?? []) {
|
|
1807
|
-
const value = record[key];
|
|
1808
|
-
if (isNode(value)) {
|
|
1809
|
-
collectInferTypeParameterNames(value, visitorKeys, names);
|
|
1810
|
-
continue;
|
|
1811
|
-
}
|
|
1812
|
-
if (!Array.isArray(value))
|
|
1813
|
-
continue;
|
|
1814
|
-
for (const child of value) {
|
|
1815
|
-
if (isNode(child))
|
|
1816
|
-
collectInferTypeParameterNames(child, visitorKeys, names);
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
function lexicalTypeParameterNames(node, visitorKeys) {
|
|
1821
|
-
const names = new Set;
|
|
1822
|
-
let descendant = node;
|
|
1823
|
-
let current = node;
|
|
1824
|
-
while (current !== null && current.type !== "Program") {
|
|
1825
|
-
if ("typeParameters" in current) {
|
|
1826
|
-
for (const parameter of current.typeParameters?.params ?? []) {
|
|
1827
|
-
names.add(parameter.name.name);
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
if (current.type === "TSMappedType" && (descendant === current.nameType || descendant === current.typeAnnotation)) {
|
|
1831
|
-
names.add(current.key.name);
|
|
1832
|
-
}
|
|
1833
|
-
if (current.type === "TSConditionalType" && descendant === current.trueType) {
|
|
1834
|
-
collectInferTypeParameterNames(current.extendsType, visitorKeys, names);
|
|
1835
|
-
}
|
|
1836
|
-
descendant = current;
|
|
1837
|
-
current = current.parent;
|
|
1838
|
-
}
|
|
1839
|
-
return names;
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
// src/rules/dillon-anti-slop/rules/no-object-parameters.ts
|
|
1843
|
-
function parameterAnnotation(parameter) {
|
|
1844
|
-
if (parameter.type === "TSParameterProperty") {
|
|
1845
|
-
return parameterAnnotation(parameter.parameter);
|
|
1846
|
-
}
|
|
1847
|
-
if (parameter.type === "RestElement") {
|
|
1848
|
-
return parameter.typeAnnotation ?? parameterAnnotation(parameter.argument);
|
|
1849
|
-
}
|
|
1850
|
-
if (parameter.type === "AssignmentPattern") {
|
|
1851
|
-
return parameter.typeAnnotation ?? parameter.left.typeAnnotation;
|
|
1852
|
-
}
|
|
1853
|
-
return parameter.typeAnnotation;
|
|
1854
|
-
}
|
|
1855
|
-
function parameterName(parameter, sourceCode) {
|
|
1856
|
-
return parameter.type === "Identifier" ? parameter.name : sourceCode.getText(parameter).replace(/\s*:\s*object\s*$/u, "");
|
|
1857
|
-
}
|
|
1858
|
-
var noObjectParametersRule = defineRule5({
|
|
1859
|
-
meta: {
|
|
1860
|
-
type: "problem",
|
|
1861
|
-
docs: {
|
|
1862
|
-
description: "Disallow object function parameters; inputs must use an owner-provided type and be parsed at their boundary."
|
|
1863
|
-
},
|
|
1864
|
-
messages: {
|
|
1865
|
-
objectParameter: "Parameter `{{parameter}}` uses the broad `object` type. Accept a named owner type; parse external input at its boundary before calling this function."
|
|
1866
|
-
}
|
|
1867
|
-
},
|
|
1868
|
-
createOnce(context) {
|
|
1869
|
-
const aliases = new Map;
|
|
1870
|
-
const resolvesToObject = (type, shadowedAliases, visited = new Set) => {
|
|
1871
|
-
if (type.type === "TSObjectKeyword")
|
|
1872
|
-
return true;
|
|
1873
|
-
if (type.type === "TSParenthesizedType")
|
|
1874
|
-
return resolvesToObject(type.typeAnnotation, shadowedAliases, visited);
|
|
1875
|
-
if (type.type === "TSUnionType") {
|
|
1876
|
-
return type.types.some((member) => resolvesToObject(member, shadowedAliases, visited));
|
|
1877
|
-
}
|
|
1878
|
-
if (type.type !== "TSTypeReference" || type.typeName.type !== "Identifier" || type.typeArguments !== null && type.typeArguments !== undefined && type.typeArguments.params.length > 0 || visited.has(type.typeName.name) || shadowedAliases.has(type.typeName.name)) {
|
|
1879
|
-
return false;
|
|
1880
|
-
}
|
|
1881
|
-
const alias = aliases.get(type.typeName.name);
|
|
1882
|
-
if (alias === undefined)
|
|
1883
|
-
return false;
|
|
1884
|
-
const nextVisited = new Set(visited);
|
|
1885
|
-
nextVisited.add(type.typeName.name);
|
|
1886
|
-
return resolvesToObject(alias, shadowedAliases, nextVisited);
|
|
1887
|
-
};
|
|
1888
|
-
const checkParameters = (node) => {
|
|
1889
|
-
const shadowedAliases = lexicalTypeParameterNames(node, context.sourceCode.visitorKeys);
|
|
1890
|
-
for (const parameter of node.params) {
|
|
1891
|
-
const annotation = parameterAnnotation(parameter);
|
|
1892
|
-
if (annotation === null || annotation === undefined)
|
|
1893
|
-
continue;
|
|
1894
|
-
if (!resolvesToObject(annotation.typeAnnotation, shadowedAliases))
|
|
1895
|
-
continue;
|
|
1896
|
-
context.report({
|
|
1897
|
-
node: annotation.typeAnnotation,
|
|
1898
|
-
messageId: "objectParameter",
|
|
1899
|
-
data: { parameter: parameterName(parameter, context.sourceCode) }
|
|
1900
|
-
});
|
|
1901
|
-
}
|
|
1902
|
-
};
|
|
1903
|
-
return {
|
|
1904
|
-
Program(node) {
|
|
1905
|
-
aliases.clear();
|
|
1906
|
-
for (const statement of node.body) {
|
|
1907
|
-
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
1908
|
-
if (declaration?.type === "TSTypeAliasDeclaration" && (declaration.typeParameters === null || declaration.typeParameters === undefined)) {
|
|
1909
|
-
aliases.set(declaration.id.name, declaration.typeAnnotation);
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
},
|
|
1913
|
-
ArrowFunctionExpression: checkParameters,
|
|
1914
|
-
FunctionDeclaration: checkParameters,
|
|
1915
|
-
FunctionExpression: checkParameters,
|
|
1916
|
-
TSCallSignatureDeclaration: checkParameters,
|
|
1917
|
-
TSConstructSignatureDeclaration: checkParameters,
|
|
1918
|
-
TSConstructorType: checkParameters,
|
|
1919
|
-
TSDeclareFunction: checkParameters,
|
|
1920
|
-
TSEmptyBodyFunctionExpression: checkParameters,
|
|
1921
|
-
TSFunctionType: checkParameters,
|
|
1922
|
-
TSMethodSignature: checkParameters
|
|
1923
|
-
};
|
|
1924
|
-
}
|
|
1925
|
-
});
|
|
1926
|
-
|
|
1927
|
-
// src/rules/dillon-anti-slop/rules/no-reflect-apply.ts
|
|
1928
|
-
import { defineRule as defineRule6 } from "@oxlint/plugins";
|
|
1929
|
-
|
|
1930
|
-
// src/rules/dillon-anti-slop/shared/reflect-method.ts
|
|
1931
|
-
function resolveVariable3(sourceCode, identifier) {
|
|
1932
|
-
let scope = sourceCode.getScope(identifier);
|
|
1933
|
-
while (scope !== null) {
|
|
1934
|
-
const variable = scope.set.get(identifier.name);
|
|
1935
|
-
if (variable !== undefined)
|
|
1936
|
-
return variable;
|
|
1937
|
-
scope = scope.upper;
|
|
1938
|
-
}
|
|
1939
|
-
return null;
|
|
1940
|
-
}
|
|
1941
|
-
function isGlobalReflect(sourceCode, expression) {
|
|
1942
|
-
if (expression.type !== "Identifier" || expression.name !== "Reflect")
|
|
1943
|
-
return false;
|
|
1944
|
-
if (sourceCode.isGlobalReference(expression))
|
|
1945
|
-
return true;
|
|
1946
|
-
const variable = resolveVariable3(sourceCode, expression);
|
|
1947
|
-
return variable === null || variable.defs.length === 0;
|
|
1948
|
-
}
|
|
1949
|
-
function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
|
|
1950
|
-
if (!("property" in callee) || !("object" in callee) || !("computed" in callee))
|
|
1951
|
-
return false;
|
|
1952
|
-
if (!isGlobalReflect(sourceCode, callee.object))
|
|
1953
|
-
return false;
|
|
1954
|
-
const property = callee.property;
|
|
1955
|
-
return callee.computed ? property.type === "Literal" && property.value === methodName : property.type === "Identifier" && property.name === methodName;
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
// src/rules/dillon-anti-slop/rules/no-reflect-apply.ts
|
|
1959
|
-
var noReflectApplyRule = defineRule6({
|
|
1960
|
-
meta: {
|
|
1961
|
-
type: "problem",
|
|
1962
|
-
docs: {
|
|
1963
|
-
description: "Disallow Reflect.apply; call typed functions directly or model dynamic dispatch behind an interface."
|
|
1964
|
-
},
|
|
1965
|
-
messages: {
|
|
1966
|
-
reflectApply: "Replace `Reflect.apply` with a typed function call. Model dynamic dispatch behind a named interface."
|
|
1967
|
-
}
|
|
1968
|
-
},
|
|
1969
|
-
createOnce(context) {
|
|
1970
|
-
return {
|
|
1971
|
-
CallExpression(node) {
|
|
1972
|
-
if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression")
|
|
1973
|
-
return;
|
|
1974
|
-
if (isGlobalReflectMethodCall(context.sourceCode, node.callee, "apply")) {
|
|
1975
|
-
context.report({ node, messageId: "reflectApply" });
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
};
|
|
1979
|
-
}
|
|
1980
|
-
});
|
|
1981
|
-
|
|
1982
|
-
// src/rules/dillon-anti-slop/rules/no-reflect-get.ts
|
|
1983
|
-
import { defineRule as defineRule7 } from "@oxlint/plugins";
|
|
1984
|
-
var noReflectGetRule = defineRule7({
|
|
1985
|
-
meta: {
|
|
1986
|
-
type: "problem",
|
|
1987
|
-
docs: {
|
|
1988
|
-
description: "Disallow Reflect.get; use typed property access or parse dynamic input into a domain type."
|
|
1989
|
-
},
|
|
1990
|
-
messages: {
|
|
1991
|
-
reflectGet: "Replace `Reflect.get` with typed property access. Parse dynamic input into a named domain type before reading it."
|
|
1992
|
-
}
|
|
1993
|
-
},
|
|
1994
|
-
createOnce(context) {
|
|
1995
|
-
return {
|
|
1996
|
-
CallExpression(node) {
|
|
1997
|
-
if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression")
|
|
1998
|
-
return;
|
|
1999
|
-
if (isGlobalReflectMethodCall(context.sourceCode, node.callee, "get")) {
|
|
2000
|
-
context.report({ node, messageId: "reflectGet" });
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
};
|
|
2004
|
-
}
|
|
2005
|
-
});
|
|
2006
|
-
|
|
2007
|
-
// src/rules/dillon-anti-slop/rules/no-runtime-typeof.ts
|
|
2008
|
-
import { defineRule as defineRule8 } from "@oxlint/plugins";
|
|
2009
|
-
function isRuntimeFunction(node) {
|
|
2010
|
-
return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
|
|
2011
|
-
}
|
|
2012
|
-
function isInsideTypeGuard(node) {
|
|
2013
|
-
let current = node.parent;
|
|
2014
|
-
while (current !== null && current.type !== "Program") {
|
|
2015
|
-
if (isRuntimeFunction(current)) {
|
|
2016
|
-
return current.returnType?.typeAnnotation.type === "TSTypePredicate";
|
|
2017
|
-
}
|
|
2018
|
-
current = current.parent;
|
|
2019
|
-
}
|
|
2020
|
-
return false;
|
|
2021
|
-
}
|
|
2022
|
-
var noRuntimeTypeofRule = defineRule8({
|
|
2023
|
-
meta: {
|
|
2024
|
-
type: "problem",
|
|
2025
|
-
docs: {
|
|
2026
|
-
description: "Disallow runtime typeof checks; external values must be decoded into meaningful types at their I/O boundary."
|
|
2027
|
-
},
|
|
2028
|
-
messages: {
|
|
2029
|
-
runtimeTypeof: "A `typeof` check narrows a representation without establishing its contract. Parse input at its I/O boundary, then branch on the domain value."
|
|
2030
|
-
},
|
|
2031
|
-
schema: [
|
|
2032
|
-
{
|
|
2033
|
-
type: "object",
|
|
2034
|
-
properties: {
|
|
2035
|
-
allowInTypeGuards: { type: "boolean" }
|
|
2036
|
-
},
|
|
2037
|
-
additionalProperties: false
|
|
2038
|
-
}
|
|
2039
|
-
],
|
|
2040
|
-
defaultOptions: [{ allowInTypeGuards: false }]
|
|
2041
|
-
},
|
|
2042
|
-
createOnce(context) {
|
|
2043
|
-
return {
|
|
2044
|
-
UnaryExpression(node) {
|
|
2045
|
-
const option = context.options?.[0];
|
|
2046
|
-
const allowInTypeGuards = typeof option === "object" && option !== null && !Array.isArray(option) && option.allowInTypeGuards === true;
|
|
2047
|
-
if (node.operator === "typeof" && (!allowInTypeGuards || !isInsideTypeGuard(node))) {
|
|
2048
|
-
context.report({ node, messageId: "runtimeTypeof" });
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
|
-
});
|
|
2054
|
-
|
|
2055
|
-
// src/rules/dillon-anti-slop/rules/no-shape-in-symbol-names.ts
|
|
2056
|
-
import { defineRule as defineRule9 } from "@oxlint/plugins";
|
|
2057
|
-
var FORBIDDEN_SYMBOL_NAME = "shape";
|
|
2058
|
-
function containsForbiddenSymbolName(name) {
|
|
2059
|
-
return name.toLowerCase().includes(FORBIDDEN_SYMBOL_NAME);
|
|
2060
|
-
}
|
|
2061
|
-
var noForbiddenTermInSymbolNamesRule = defineRule9({
|
|
2062
|
-
meta: {
|
|
2063
|
-
type: "problem",
|
|
2064
|
-
docs: {
|
|
2065
|
-
description: 'Disallow the case-insensitive substring "shape" in JavaScript, TypeScript, private, and JSX symbol names.'
|
|
2066
|
-
},
|
|
2067
|
-
messages: {
|
|
2068
|
-
forbiddenSymbolName: 'Rename symbol "{{name}}" for its domain role; "shape" describes structure rather than ownership.'
|
|
2069
|
-
}
|
|
2070
|
-
},
|
|
2071
|
-
createOnce(context) {
|
|
2072
|
-
const reportForbiddenSymbolName = (node) => {
|
|
2073
|
-
if (!containsForbiddenSymbolName(node.name))
|
|
2074
|
-
return;
|
|
2075
|
-
context.report({
|
|
2076
|
-
node,
|
|
2077
|
-
messageId: "forbiddenSymbolName",
|
|
2078
|
-
data: { name: node.name }
|
|
2079
|
-
});
|
|
2080
|
-
};
|
|
2081
|
-
return {
|
|
2082
|
-
Identifier: reportForbiddenSymbolName,
|
|
2083
|
-
PrivateIdentifier: reportForbiddenSymbolName,
|
|
2084
|
-
JSXIdentifier: reportForbiddenSymbolName
|
|
2085
|
-
};
|
|
2086
|
-
}
|
|
2087
|
-
});
|
|
2088
|
-
|
|
2089
|
-
// src/rules/dillon-anti-slop/rules/no-unknown-parameters.ts
|
|
2090
|
-
import { defineRule as defineRule10 } from "@oxlint/plugins";
|
|
2091
|
-
function parameterAnnotation2(parameter) {
|
|
2092
|
-
if (parameter.type === "TSParameterProperty") {
|
|
2093
|
-
return parameterAnnotation2(parameter.parameter);
|
|
2094
|
-
}
|
|
2095
|
-
if (parameter.type === "RestElement") {
|
|
2096
|
-
return parameter.typeAnnotation ?? parameterAnnotation2(parameter.argument);
|
|
2097
|
-
}
|
|
2098
|
-
if (parameter.type === "AssignmentPattern") {
|
|
2099
|
-
return parameter.typeAnnotation ?? parameter.left.typeAnnotation;
|
|
2100
|
-
}
|
|
2101
|
-
return parameter.typeAnnotation;
|
|
2102
|
-
}
|
|
2103
|
-
function parameterName2(parameter, sourceText) {
|
|
2104
|
-
if (parameter.type === "TSParameterProperty") {
|
|
2105
|
-
return parameterName2(parameter.parameter, sourceText);
|
|
2106
|
-
}
|
|
2107
|
-
if (parameter.type === "AssignmentPattern") {
|
|
2108
|
-
return parameterName2(parameter.left, sourceText);
|
|
2109
|
-
}
|
|
2110
|
-
if (parameter.type === "RestElement") {
|
|
2111
|
-
return parameterName2(parameter.argument, sourceText);
|
|
2112
|
-
}
|
|
2113
|
-
return parameter.type === "Identifier" ? parameter.name : sourceText.replace(/\s*:\s*unknown\s*$/u, "");
|
|
2114
|
-
}
|
|
2115
|
-
var noUnknownParametersRule = defineRule10({
|
|
2116
|
-
meta: {
|
|
2117
|
-
type: "problem",
|
|
2118
|
-
docs: {
|
|
2119
|
-
description: "Disallow explicitly unknown function parameters except `error` and `cause`; decode unknown input at its I/O boundary instead."
|
|
2120
|
-
},
|
|
2121
|
-
messages: {
|
|
2122
|
-
unknownParameter: "Parameter `{{parameter}}` leaves input unparsed. Accept a named domain type; run the expected schema or parser at the I/O boundary before calling this function."
|
|
2123
|
-
}
|
|
2124
|
-
},
|
|
2125
|
-
createOnce(context) {
|
|
2126
|
-
const checkParameters = (node) => {
|
|
2127
|
-
for (const parameter of node.params) {
|
|
2128
|
-
const annotation = parameterAnnotation2(parameter);
|
|
2129
|
-
if (annotation?.typeAnnotation.type !== "TSUnknownKeyword")
|
|
2130
|
-
continue;
|
|
2131
|
-
const name = parameterName2(parameter, context.sourceCode.getText(parameter));
|
|
2132
|
-
if (name === "cause" || name === "error")
|
|
2133
|
-
continue;
|
|
2134
|
-
context.report({
|
|
2135
|
-
node: annotation.typeAnnotation,
|
|
2136
|
-
messageId: "unknownParameter",
|
|
2137
|
-
data: { parameter: name }
|
|
2138
|
-
});
|
|
2139
|
-
}
|
|
2140
|
-
};
|
|
2141
|
-
return {
|
|
2142
|
-
ArrowFunctionExpression: checkParameters,
|
|
2143
|
-
FunctionDeclaration: checkParameters,
|
|
2144
|
-
FunctionExpression: checkParameters,
|
|
2145
|
-
TSCallSignatureDeclaration: checkParameters,
|
|
2146
|
-
TSConstructSignatureDeclaration: checkParameters,
|
|
2147
|
-
TSConstructorType: checkParameters,
|
|
2148
|
-
TSDeclareFunction: checkParameters,
|
|
2149
|
-
TSEmptyBodyFunctionExpression: checkParameters,
|
|
2150
|
-
TSFunctionType: checkParameters,
|
|
2151
|
-
TSMethodSignature: checkParameters
|
|
2152
|
-
};
|
|
2153
|
-
}
|
|
2154
|
-
});
|
|
2155
|
-
|
|
2156
|
-
// src/rules/dillon-anti-slop/rules/no-unknown-returns.ts
|
|
2157
|
-
import { defineRule as defineRule11 } from "@oxlint/plugins";
|
|
2158
|
-
function referencedAliasName(type) {
|
|
2159
|
-
if (type.type === "TSParenthesizedType")
|
|
2160
|
-
return referencedAliasName(type.typeAnnotation);
|
|
2161
|
-
if (type.type !== "TSTypeReference" || type.typeName.type !== "Identifier")
|
|
2162
|
-
return null;
|
|
2163
|
-
return type.typeArguments === null || type.typeArguments === undefined || type.typeArguments.params.length === 0 ? type.typeName.name : null;
|
|
2164
|
-
}
|
|
2165
|
-
var noUnknownReturnsRule = defineRule11({
|
|
2166
|
-
meta: {
|
|
2167
|
-
type: "problem",
|
|
2168
|
-
docs: {
|
|
2169
|
-
description: "Disallow functions whose explicit return contract is unknown or Promise<unknown>."
|
|
2170
|
-
},
|
|
2171
|
-
messages: {
|
|
2172
|
-
unknownReturn: "This function exposes `unknown` to its caller. Parse the value at its boundary and return a named domain type."
|
|
2173
|
-
}
|
|
2174
|
-
},
|
|
2175
|
-
createOnce(context) {
|
|
2176
|
-
const aliases = new Map;
|
|
2177
|
-
const resolvesToUnknown = (type, shadowedAliases, visited = new Set) => {
|
|
2178
|
-
if (type.type === "TSUnknownKeyword")
|
|
2179
|
-
return true;
|
|
2180
|
-
if (type.type === "TSParenthesizedType") {
|
|
2181
|
-
return resolvesToUnknown(type.typeAnnotation, shadowedAliases, visited);
|
|
2182
|
-
}
|
|
2183
|
-
if (type.type === "TSUnionType") {
|
|
2184
|
-
return type.types.some((member) => resolvesToUnknown(member, shadowedAliases, visited));
|
|
2185
|
-
}
|
|
2186
|
-
if (type.type === "TSTypeReference" && type.typeName.type === "Identifier" && (type.typeName.name === "Promise" || type.typeName.name === "PromiseLike")) {
|
|
2187
|
-
const value = type.typeArguments?.params[0];
|
|
2188
|
-
return value !== undefined && resolvesToUnknown(value, shadowedAliases, visited);
|
|
2189
|
-
}
|
|
2190
|
-
const name = referencedAliasName(type);
|
|
2191
|
-
if (name === null || visited.has(name) || shadowedAliases.has(name))
|
|
2192
|
-
return false;
|
|
2193
|
-
const alias = aliases.get(name);
|
|
2194
|
-
if (alias === undefined || alias.typeParameters !== null && alias.typeParameters !== undefined) {
|
|
2195
|
-
return false;
|
|
2196
|
-
}
|
|
2197
|
-
const nextVisited = new Set(visited);
|
|
2198
|
-
nextVisited.add(name);
|
|
2199
|
-
return resolvesToUnknown(alias.typeAnnotation, shadowedAliases, nextVisited);
|
|
2200
|
-
};
|
|
2201
|
-
const checkReturnType = (node) => {
|
|
2202
|
-
const annotation = node.returnType;
|
|
2203
|
-
if (annotation === null || annotation === undefined)
|
|
2204
|
-
return;
|
|
2205
|
-
if (!resolvesToUnknown(annotation.typeAnnotation, lexicalTypeParameterNames(node, context.sourceCode.visitorKeys))) {
|
|
2206
|
-
return;
|
|
2207
|
-
}
|
|
2208
|
-
context.report({
|
|
2209
|
-
node: annotation.typeAnnotation,
|
|
2210
|
-
messageId: "unknownReturn"
|
|
2211
|
-
});
|
|
2212
|
-
};
|
|
2213
|
-
return {
|
|
2214
|
-
Program(node) {
|
|
2215
|
-
aliases.clear();
|
|
2216
|
-
for (const statement of node.body) {
|
|
2217
|
-
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
2218
|
-
if (declaration?.type === "TSTypeAliasDeclaration") {
|
|
2219
|
-
aliases.set(declaration.id.name, declaration);
|
|
2220
|
-
}
|
|
2221
|
-
}
|
|
2222
|
-
},
|
|
2223
|
-
ArrowFunctionExpression: checkReturnType,
|
|
2224
|
-
FunctionDeclaration: checkReturnType,
|
|
2225
|
-
FunctionExpression: checkReturnType,
|
|
2226
|
-
TSCallSignatureDeclaration: checkReturnType,
|
|
2227
|
-
TSConstructSignatureDeclaration: checkReturnType,
|
|
2228
|
-
TSConstructorType: checkReturnType,
|
|
2229
|
-
TSDeclareFunction: checkReturnType,
|
|
2230
|
-
TSEmptyBodyFunctionExpression: checkReturnType,
|
|
2231
|
-
TSFunctionType: checkReturnType,
|
|
2232
|
-
TSMethodSignature: checkReturnType
|
|
2233
|
-
};
|
|
2234
|
-
}
|
|
2235
|
-
});
|
|
2236
|
-
|
|
2237
|
-
// src/rules/dillon-anti-slop/rules/no-unknown-type-aliases.ts
|
|
2238
|
-
import { defineRule as defineRule12 } from "@oxlint/plugins";
|
|
2239
|
-
function referencedAliasName2(type) {
|
|
2240
|
-
if (type.type === "TSParenthesizedType")
|
|
2241
|
-
return referencedAliasName2(type.typeAnnotation);
|
|
2242
|
-
if (type.type !== "TSTypeReference" || type.typeName.type !== "Identifier")
|
|
2243
|
-
return null;
|
|
2244
|
-
return type.typeArguments === null || type.typeArguments === undefined || type.typeArguments.params.length === 0 ? type.typeName.name : null;
|
|
2245
|
-
}
|
|
2246
|
-
var noUnknownTypeAliasesRule = defineRule12({
|
|
2247
|
-
meta: {
|
|
2248
|
-
type: "problem",
|
|
2249
|
-
docs: {
|
|
2250
|
-
description: "Disallow type aliases whose resolved type is unknown; unknown must remain visible at an allowed boundary."
|
|
2251
|
-
},
|
|
2252
|
-
messages: {
|
|
2253
|
-
unknownAlias: "Type alias `{{alias}}` hides `unknown`. Keep `unknown` explicit at the parsing boundary or on an allowed `cause` field; otherwise use the parsed owner type."
|
|
2254
|
-
}
|
|
2255
|
-
},
|
|
2256
|
-
createOnce(context) {
|
|
2257
|
-
const aliases = new Map;
|
|
2258
|
-
const resolvesToUnknown = (type, visited = new Set) => {
|
|
2259
|
-
if (type.type === "TSUnknownKeyword")
|
|
2260
|
-
return true;
|
|
2261
|
-
if (type.type === "TSParenthesizedType")
|
|
2262
|
-
return resolvesToUnknown(type.typeAnnotation, visited);
|
|
2263
|
-
const name = referencedAliasName2(type);
|
|
2264
|
-
if (name === null || visited.has(name))
|
|
2265
|
-
return false;
|
|
2266
|
-
const alias = aliases.get(name);
|
|
2267
|
-
if (alias === undefined || alias.typeParameters !== null && alias.typeParameters !== undefined) {
|
|
2268
|
-
return false;
|
|
2269
|
-
}
|
|
2270
|
-
const nextVisited = new Set(visited);
|
|
2271
|
-
nextVisited.add(name);
|
|
2272
|
-
return resolvesToUnknown(alias.typeAnnotation, nextVisited);
|
|
2273
|
-
};
|
|
2274
|
-
return {
|
|
2275
|
-
Program(node) {
|
|
2276
|
-
aliases.clear();
|
|
2277
|
-
for (const statement of node.body) {
|
|
2278
|
-
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
2279
|
-
if (declaration?.type === "TSTypeAliasDeclaration") {
|
|
2280
|
-
aliases.set(declaration.id.name, declaration);
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
for (const alias of aliases.values()) {
|
|
2284
|
-
if (!resolvesToUnknown(alias.typeAnnotation, new Set([alias.id.name])))
|
|
2285
|
-
continue;
|
|
2286
|
-
context.report({
|
|
2287
|
-
node: alias.id,
|
|
2288
|
-
messageId: "unknownAlias",
|
|
2289
|
-
data: { alias: alias.id.name }
|
|
2290
|
-
});
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
};
|
|
2294
|
-
}
|
|
2295
|
-
});
|
|
2296
|
-
|
|
2297
|
-
// src/rules/dillon-anti-slop/rules/no-unsafe-dictionary-type.ts
|
|
2298
|
-
import { defineRule as defineRule13 } from "@oxlint/plugins";
|
|
2299
|
-
var typeNodeKinds = new Set([
|
|
2300
|
-
"JSDocNonNullableType",
|
|
2301
|
-
"JSDocNullableType",
|
|
2302
|
-
"JSDocUnknownType",
|
|
2303
|
-
"TSAnyKeyword",
|
|
2304
|
-
"TSArrayType",
|
|
2305
|
-
"TSBigIntKeyword",
|
|
2306
|
-
"TSBooleanKeyword",
|
|
2307
|
-
"TSConditionalType",
|
|
2308
|
-
"TSConstructorType",
|
|
2309
|
-
"TSFunctionType",
|
|
2310
|
-
"TSImportType",
|
|
2311
|
-
"TSIndexedAccessType",
|
|
2312
|
-
"TSInferType",
|
|
2313
|
-
"TSIntersectionType",
|
|
2314
|
-
"TSIntrinsicKeyword",
|
|
2315
|
-
"TSLiteralType",
|
|
2316
|
-
"TSMappedType",
|
|
2317
|
-
"TSNamedTupleMember",
|
|
2318
|
-
"TSNeverKeyword",
|
|
2319
|
-
"TSNullKeyword",
|
|
2320
|
-
"TSNumberKeyword",
|
|
2321
|
-
"TSObjectKeyword",
|
|
2322
|
-
"TSParenthesizedType",
|
|
2323
|
-
"TSStringKeyword",
|
|
2324
|
-
"TSSymbolKeyword",
|
|
2325
|
-
"TSTemplateLiteralType",
|
|
2326
|
-
"TSThisType",
|
|
2327
|
-
"TSTupleType",
|
|
2328
|
-
"TSTypeLiteral",
|
|
2329
|
-
"TSTypeOperator",
|
|
2330
|
-
"TSTypePredicate",
|
|
2331
|
-
"TSTypeQuery",
|
|
2332
|
-
"TSTypeReference",
|
|
2333
|
-
"TSUndefinedKeyword",
|
|
2334
|
-
"TSUnionType",
|
|
2335
|
-
"TSUnknownKeyword",
|
|
2336
|
-
"TSVoidKeyword"
|
|
2337
|
-
]);
|
|
2338
|
-
function isTypeNode(node) {
|
|
2339
|
-
return typeNodeKinds.has(node.type);
|
|
2340
|
-
}
|
|
2341
|
-
function typeReferenceName2(type) {
|
|
2342
|
-
return type.typeName.type === "Identifier" ? type.typeName.name : null;
|
|
2343
|
-
}
|
|
2344
|
-
function isInsideTypeAliasDeclaration(node) {
|
|
2345
|
-
let current = node.parent;
|
|
2346
|
-
while (current !== null && current.type !== "Program") {
|
|
2347
|
-
if (current.type === "TSTypeAliasDeclaration")
|
|
2348
|
-
return true;
|
|
2349
|
-
current = current.parent;
|
|
2350
|
-
}
|
|
2351
|
-
return false;
|
|
2352
|
-
}
|
|
2353
|
-
function isPlainAliasConsumerUse(node, environment) {
|
|
2354
|
-
if (node.type !== "TSTypeReference" || node.typeArguments?.params.length)
|
|
2355
|
-
return false;
|
|
2356
|
-
const name = typeReferenceName2(node);
|
|
2357
|
-
return name !== null && environment.aliases.has(name) && !isInsideTypeAliasDeclaration(node);
|
|
2358
|
-
}
|
|
2359
|
-
function shouldReportType(node, environment) {
|
|
2360
|
-
if (isPlainAliasConsumerUse(node, environment))
|
|
2361
|
-
return false;
|
|
2362
|
-
if (classifyUnsafeDictionary(node, environment) === null)
|
|
2363
|
-
return false;
|
|
2364
|
-
let current = node.parent;
|
|
2365
|
-
while (current !== null && current.type !== "Program") {
|
|
2366
|
-
if (isTypeNode(current) && classifyUnsafeDictionary(current, environment) !== null)
|
|
2367
|
-
return false;
|
|
2368
|
-
current = current.parent;
|
|
2369
|
-
}
|
|
2370
|
-
return true;
|
|
2371
|
-
}
|
|
2372
|
-
var noUnsafeDictionaryTypeRule = defineRule13({
|
|
2373
|
-
meta: {
|
|
2374
|
-
type: "problem",
|
|
2375
|
-
docs: {
|
|
2376
|
-
description: "Disallow object-dictionary contracts whose direct value type is unknown, any, object, {}, or a union/alias containing one of those escape hatches."
|
|
2377
|
-
},
|
|
2378
|
-
messages: {
|
|
2379
|
-
unsafeDictionary: "This dictionary's {{value}} value type gives callers no concrete value contract. Use an owner/schema-derived value type; parse external payloads before insertion."
|
|
2380
|
-
}
|
|
2381
|
-
},
|
|
2382
|
-
createOnce(context) {
|
|
2383
|
-
let environment = null;
|
|
2384
|
-
const report = (node, value) => {
|
|
2385
|
-
context.report({ node, messageId: "unsafeDictionary", data: { value } });
|
|
2386
|
-
};
|
|
2387
|
-
const reportIfUnsafe = (node) => {
|
|
2388
|
-
if (environment === null || !shouldReportType(node, environment))
|
|
2389
|
-
return;
|
|
2390
|
-
const unsafe = classifyUnsafeDictionary(node, environment);
|
|
2391
|
-
if (unsafe === null)
|
|
2392
|
-
return;
|
|
2393
|
-
report(node, unsafe.unsafeValue);
|
|
2394
|
-
};
|
|
2395
|
-
return {
|
|
2396
|
-
Program(node) {
|
|
2397
|
-
environment = createTypeEnvironment(node);
|
|
2398
|
-
},
|
|
2399
|
-
TSTypeReference: reportIfUnsafe,
|
|
2400
|
-
TSTypeLiteral: reportIfUnsafe,
|
|
2401
|
-
TSMappedType: reportIfUnsafe,
|
|
2402
|
-
TSIndexSignature(node) {
|
|
2403
|
-
if (environment === null || node.typeAnnotation === null || node.parent.type === "TSTypeLiteral")
|
|
2404
|
-
return;
|
|
2405
|
-
const unsafe = classifyUnsafeDictionaryValue(node.typeAnnotation.typeAnnotation, environment);
|
|
2406
|
-
if (unsafe !== null)
|
|
2407
|
-
report(node, unsafe.unsafeValue);
|
|
2408
|
-
}
|
|
2409
|
-
};
|
|
2410
|
-
}
|
|
2411
|
-
});
|
|
2412
|
-
|
|
2413
|
-
// src/rules/dillon-anti-slop/rules/no-widen-then-assert.ts
|
|
2414
|
-
import { defineRule as defineRule14 } from "@oxlint/plugins";
|
|
2415
|
-
var functionBoundaryTypes = new Set([
|
|
2416
|
-
"ArrowFunctionExpression",
|
|
2417
|
-
"FunctionDeclaration",
|
|
2418
|
-
"FunctionExpression",
|
|
2419
|
-
"TSDeclareFunction",
|
|
2420
|
-
"TSEmptyBodyFunctionExpression"
|
|
2421
|
-
]);
|
|
2422
|
-
function unwrapExpressionParentheses(expression) {
|
|
2423
|
-
let current = expression;
|
|
2424
|
-
while (current.type === "ParenthesizedExpression")
|
|
2425
|
-
current = current.expression;
|
|
2426
|
-
return current;
|
|
2427
|
-
}
|
|
2428
|
-
function unwrapTypeParentheses(type) {
|
|
2429
|
-
let current = type;
|
|
2430
|
-
while (current.type === "TSParenthesizedType")
|
|
2431
|
-
current = current.typeAnnotation;
|
|
2432
|
-
return current;
|
|
2433
|
-
}
|
|
2434
|
-
function typeReferenceName3(type) {
|
|
2435
|
-
return type.typeName.type === "Identifier" ? type.typeName.name : null;
|
|
2436
|
-
}
|
|
2437
|
-
function isUnknownOrAnyType(type) {
|
|
2438
|
-
const unwrapped = unwrapTypeParentheses(type);
|
|
2439
|
-
return unwrapped.type === "TSUnknownKeyword" || unwrapped.type === "TSAnyKeyword";
|
|
2440
|
-
}
|
|
2441
|
-
function isBroadRecordKeyType(type) {
|
|
2442
|
-
const unwrapped = unwrapTypeParentheses(type);
|
|
2443
|
-
if (unwrapped.type === "TSStringKeyword" || unwrapped.type === "TSNumberKeyword" || unwrapped.type === "TSSymbolKeyword") {
|
|
2444
|
-
return true;
|
|
2445
|
-
}
|
|
2446
|
-
if (unwrapped.type === "TSUnionType")
|
|
2447
|
-
return unwrapped.types.every(isBroadRecordKeyType);
|
|
2448
|
-
return unwrapped.type === "TSTypeReference" && typeReferenceName3(unwrapped) === "PropertyKey";
|
|
2449
|
-
}
|
|
2450
|
-
function isBroadRecordType(type) {
|
|
2451
|
-
const unwrapped = unwrapTypeParentheses(type);
|
|
2452
|
-
if (unwrapped.type === "TSTypeReference") {
|
|
2453
|
-
if (typeReferenceName3(unwrapped) === "Readonly") {
|
|
2454
|
-
const [inner] = unwrapped.typeArguments?.params ?? [];
|
|
2455
|
-
return inner !== undefined && isBroadRecordType(inner);
|
|
2456
|
-
}
|
|
2457
|
-
if (typeReferenceName3(unwrapped) !== "Record")
|
|
2458
|
-
return false;
|
|
2459
|
-
const parameters = unwrapped.typeArguments?.params ?? [];
|
|
2460
|
-
return parameters.length === 2 && parameters[0] !== undefined && parameters[1] !== undefined && isBroadRecordKeyType(parameters[0]) && isUnknownOrAnyType(parameters[1]);
|
|
2461
|
-
}
|
|
2462
|
-
if (unwrapped.type !== "TSTypeLiteral" || unwrapped.members.length !== 1)
|
|
2463
|
-
return false;
|
|
2464
|
-
const [member] = unwrapped.members;
|
|
2465
|
-
const [parameter] = member?.type === "TSIndexSignature" ? member.parameters : [];
|
|
2466
|
-
return member?.type === "TSIndexSignature" && member.parameters.length === 1 && parameter !== undefined && isBroadRecordKeyType(parameter.typeAnnotation.typeAnnotation) && isUnknownOrAnyType(member.typeAnnotation.typeAnnotation);
|
|
2467
|
-
}
|
|
2468
|
-
function broadTypeKind(type) {
|
|
2469
|
-
const unwrapped = unwrapTypeParentheses(type);
|
|
2470
|
-
if (unwrapped.type === "TSUnknownKeyword" || unwrapped.type === "TSAnyKeyword")
|
|
2471
|
-
return "top";
|
|
2472
|
-
if (unwrapped.type === "TSObjectKeyword")
|
|
2473
|
-
return "object";
|
|
2474
|
-
return isBroadRecordType(unwrapped) ? "record" : null;
|
|
2475
|
-
}
|
|
2476
|
-
function assertedExpression(node) {
|
|
2477
|
-
return unwrapExpressionParentheses(node.expression);
|
|
2478
|
-
}
|
|
2479
|
-
function assertionFromExpression(expression) {
|
|
2480
|
-
const unwrapped = unwrapExpressionParentheses(expression);
|
|
2481
|
-
return unwrapped.type === "TSAsExpression" || unwrapped.type === "TSTypeAssertion" ? unwrapped : null;
|
|
2482
|
-
}
|
|
2483
|
-
function normalizedTypeText(sourceText, type) {
|
|
2484
|
-
return sourceText.slice(type.start, type.end).replaceAll(/\s+/gu, "");
|
|
2485
|
-
}
|
|
2486
|
-
function typesHaveSameSyntax(sourceText, left, right) {
|
|
2487
|
-
return left !== null && normalizedTypeText(sourceText, unwrapTypeParentheses(left)) === normalizedTypeText(sourceText, unwrapTypeParentheses(right));
|
|
2488
|
-
}
|
|
2489
|
-
function isDefinitelyObjectType(type) {
|
|
2490
|
-
const unwrapped = unwrapTypeParentheses(type);
|
|
2491
|
-
switch (unwrapped.type) {
|
|
2492
|
-
case "TSArrayType":
|
|
2493
|
-
case "TSConstructorType":
|
|
2494
|
-
case "TSFunctionType":
|
|
2495
|
-
case "TSMappedType":
|
|
2496
|
-
case "TSObjectKeyword":
|
|
2497
|
-
case "TSTupleType":
|
|
2498
|
-
return true;
|
|
2499
|
-
case "TSTypeLiteral":
|
|
2500
|
-
return unwrapped.members.length > 0;
|
|
2501
|
-
case "TSIntersectionType":
|
|
2502
|
-
return unwrapped.types.every(isDefinitelyObjectType);
|
|
2503
|
-
case "TSTypeOperator":
|
|
2504
|
-
return unwrapped.operator === "readonly" && isDefinitelyObjectType(unwrapped.typeAnnotation);
|
|
2505
|
-
default:
|
|
2506
|
-
return false;
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
function isDefinitelyNarrowerRecordType(type) {
|
|
2510
|
-
const unwrapped = unwrapTypeParentheses(type);
|
|
2511
|
-
if (unwrapped.type === "TSTypeLiteral") {
|
|
2512
|
-
return unwrapped.members.some((member) => member.type !== "TSIndexSignature");
|
|
2513
|
-
}
|
|
2514
|
-
if (unwrapped.type !== "TSTypeReference")
|
|
2515
|
-
return false;
|
|
2516
|
-
if (typeReferenceName3(unwrapped) === "Readonly") {
|
|
2517
|
-
const [inner] = unwrapped.typeArguments?.params ?? [];
|
|
2518
|
-
return inner !== undefined && isDefinitelyNarrowerRecordType(inner);
|
|
2519
|
-
}
|
|
2520
|
-
if (typeReferenceName3(unwrapped) !== "Record")
|
|
2521
|
-
return false;
|
|
2522
|
-
const parameters = unwrapped.typeArguments?.params ?? [];
|
|
2523
|
-
return parameters.length === 2 && parameters[1] !== undefined && !isUnknownOrAnyType(parameters[1]);
|
|
2524
|
-
}
|
|
2525
|
-
function functionBoundary(node) {
|
|
2526
|
-
let current = node.parent;
|
|
2527
|
-
while (current !== null && current.type !== "Program") {
|
|
2528
|
-
if (functionBoundaryTypes.has(current.type))
|
|
2529
|
-
return current;
|
|
2530
|
-
current = current.parent;
|
|
2531
|
-
}
|
|
2532
|
-
return null;
|
|
2533
|
-
}
|
|
2534
|
-
function resolvedVariableForIdentifier(scopes, identifier) {
|
|
2535
|
-
for (const scope of scopes) {
|
|
2536
|
-
const reference = scope.references.find((candidate) => candidate.identifier.start === identifier.start && candidate.identifier.end === identifier.end);
|
|
2537
|
-
if (reference !== undefined)
|
|
2538
|
-
return reference.resolved;
|
|
2539
|
-
}
|
|
2540
|
-
return null;
|
|
2541
|
-
}
|
|
2542
|
-
function variableDeclarator2(variable) {
|
|
2543
|
-
for (const definition of variable.defs) {
|
|
2544
|
-
if (definition.type === "Variable" && definition.node.type === "VariableDeclarator") {
|
|
2545
|
-
return definition.node;
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
return null;
|
|
2549
|
-
}
|
|
2550
|
-
function knownValueEvidence(expression, scopes, boundary, visitedVariables) {
|
|
2551
|
-
const unwrapped = unwrapExpressionParentheses(expression);
|
|
2552
|
-
if (unwrapped.type === "TSAsExpression" || unwrapped.type === "TSTypeAssertion") {
|
|
2553
|
-
if (broadTypeKind(unwrapped.typeAnnotation) !== null)
|
|
2554
|
-
return null;
|
|
2555
|
-
return { type: unwrapped.typeAnnotation };
|
|
2556
|
-
}
|
|
2557
|
-
if (unwrapped.type === "Literal" || unwrapped.type === "TemplateLiteral") {
|
|
2558
|
-
return { type: null };
|
|
2559
|
-
}
|
|
2560
|
-
if (unwrapped.type === "ArrayExpression" || unwrapped.type === "ArrowFunctionExpression" || unwrapped.type === "ClassExpression" || unwrapped.type === "FunctionExpression" || unwrapped.type === "NewExpression" || unwrapped.type === "ObjectExpression") {
|
|
2561
|
-
return { type: null };
|
|
2562
|
-
}
|
|
2563
|
-
if (unwrapped.type !== "Identifier")
|
|
2564
|
-
return null;
|
|
2565
|
-
const variable = resolvedVariableForIdentifier(scopes, unwrapped);
|
|
2566
|
-
if (variable === null || visitedVariables.has(variable))
|
|
2567
|
-
return null;
|
|
2568
|
-
const annotatedIdentifier = variable.identifiers.find((identifier) => identifier.typeAnnotation !== null && identifier.typeAnnotation !== undefined);
|
|
2569
|
-
const annotation = annotatedIdentifier?.typeAnnotation?.typeAnnotation;
|
|
2570
|
-
if (annotation !== undefined && annotatedIdentifier !== undefined) {
|
|
2571
|
-
if (functionBoundary(annotatedIdentifier) !== boundary || broadTypeKind(annotation) !== null) {
|
|
2572
|
-
return null;
|
|
2573
|
-
}
|
|
2574
|
-
return { type: annotation };
|
|
2575
|
-
}
|
|
2576
|
-
const declarator = variableDeclarator2(variable);
|
|
2577
|
-
if (declarator === null || declarator.parent.type !== "VariableDeclaration" || declarator.parent.kind !== "const" || declarator.init === null || variable.references.some((reference) => reference.isWrite() && !reference.init) || functionBoundary(declarator) !== boundary) {
|
|
2578
|
-
return null;
|
|
2579
|
-
}
|
|
2580
|
-
return knownValueEvidence(declarator.init, scopes, boundary, new Set([...visitedVariables, variable]));
|
|
2581
|
-
}
|
|
2582
|
-
function widenedBinding(variable, scopes) {
|
|
2583
|
-
const declarator = variableDeclarator2(variable);
|
|
2584
|
-
if (declarator === null || declarator.parent.type !== "VariableDeclaration" || declarator.parent.kind !== "const" || declarator.id.type !== "Identifier" || declarator.init === null || variable.references.some((reference) => reference.isWrite() && !reference.init)) {
|
|
2585
|
-
return null;
|
|
2586
|
-
}
|
|
2587
|
-
const boundary = functionBoundary(declarator);
|
|
2588
|
-
const declaredType = declarator.id.typeAnnotation?.typeAnnotation;
|
|
2589
|
-
const initializerAssertion = assertionFromExpression(declarator.init);
|
|
2590
|
-
const initializerBroadKind = initializerAssertion === null ? null : broadTypeKind(initializerAssertion.typeAnnotation);
|
|
2591
|
-
const declaredBroadKind = declaredType === undefined ? null : broadTypeKind(declaredType);
|
|
2592
|
-
const broadKind = declaredBroadKind ?? initializerBroadKind;
|
|
2593
|
-
if (broadKind === null)
|
|
2594
|
-
return null;
|
|
2595
|
-
const originalExpression = initializerAssertion !== null && initializerBroadKind !== null ? assertedExpression(initializerAssertion) : declarator.init;
|
|
2596
|
-
const evidence = knownValueEvidence(originalExpression, scopes, boundary, new Set([variable]));
|
|
2597
|
-
return evidence === null ? null : { broadKind, evidence, declaredAt: declarator.end, boundary };
|
|
2598
|
-
}
|
|
2599
|
-
function assertionIsNarrower(sourceText, broadKind, evidence, assertedType) {
|
|
2600
|
-
if (broadTypeKind(assertedType) !== null)
|
|
2601
|
-
return false;
|
|
2602
|
-
if (broadKind === "top")
|
|
2603
|
-
return true;
|
|
2604
|
-
if (typesHaveSameSyntax(sourceText, evidence.type, assertedType))
|
|
2605
|
-
return true;
|
|
2606
|
-
if (broadKind === "object")
|
|
2607
|
-
return isDefinitelyObjectType(assertedType);
|
|
2608
|
-
return isDefinitelyNarrowerRecordType(assertedType);
|
|
2609
|
-
}
|
|
2610
|
-
var noWidenThenAssertRule = defineRule14({
|
|
2611
|
-
meta: {
|
|
2612
|
-
type: "problem",
|
|
2613
|
-
docs: {
|
|
2614
|
-
description: "Disallow local const flows that explicitly widen a known value before asserting the widened binding to a narrower type."
|
|
2615
|
-
},
|
|
2616
|
-
messages: {
|
|
2617
|
-
widenThenAssert: 'Binding "{{name}}" discards type evidence and later recreates it with an assertion. Keep the precise type from initialization through use; parse boundary input once.'
|
|
2618
|
-
}
|
|
2619
|
-
},
|
|
2620
|
-
createOnce(context) {
|
|
2621
|
-
let scopes = [];
|
|
2622
|
-
const checkAssertion = (node) => {
|
|
2623
|
-
const expression = assertedExpression(node);
|
|
2624
|
-
if (expression.type !== "Identifier")
|
|
2625
|
-
return;
|
|
2626
|
-
const variable = resolvedVariableForIdentifier(scopes, expression);
|
|
2627
|
-
if (variable === null)
|
|
2628
|
-
return;
|
|
2629
|
-
const widened = widenedBinding(variable, scopes);
|
|
2630
|
-
if (widened === null || node.start <= widened.declaredAt || functionBoundary(node) !== widened.boundary || !assertionIsNarrower(context.sourceCode.text, widened.broadKind, widened.evidence, node.typeAnnotation)) {
|
|
2631
|
-
return;
|
|
2632
|
-
}
|
|
2633
|
-
context.report({
|
|
2634
|
-
node,
|
|
2635
|
-
messageId: "widenThenAssert",
|
|
2636
|
-
data: { name: expression.name }
|
|
2637
|
-
});
|
|
2638
|
-
};
|
|
2639
|
-
return {
|
|
2640
|
-
Program() {
|
|
2641
|
-
scopes = context.sourceCode.scopeManager.scopes;
|
|
2642
|
-
},
|
|
2643
|
-
TSAsExpression: checkAssertion,
|
|
2644
|
-
TSTypeAssertion: checkAssertion
|
|
2645
|
-
};
|
|
2646
|
-
}
|
|
2647
|
-
});
|
|
2648
|
-
|
|
2649
|
-
// src/rules/dillon-anti-slop/rules/require-safety-comment-for-type-assertion.ts
|
|
2650
|
-
import { defineRule as defineRule15 } from "@oxlint/plugins";
|
|
2651
|
-
var commentOwnerKinds = new Set([
|
|
2652
|
-
"ExpressionStatement",
|
|
2653
|
-
"PropertyDefinition",
|
|
2654
|
-
"ReturnStatement",
|
|
2655
|
-
"ThrowStatement",
|
|
2656
|
-
"VariableDeclaration"
|
|
2657
|
-
]);
|
|
2658
|
-
function isConstAssertion2(node) {
|
|
2659
|
-
return node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const";
|
|
2660
|
-
}
|
|
2661
|
-
function hasSafetyComment(sourceCode, node) {
|
|
2662
|
-
let current = node;
|
|
2663
|
-
while (true) {
|
|
2664
|
-
if (sourceCode.getCommentsBefore(current).some((comment) => comment.end <= node.start && /\bSAFETY\s*:/u.test(comment.value))) {
|
|
2665
|
-
return true;
|
|
2666
|
-
}
|
|
2667
|
-
if (commentOwnerKinds.has(current.type) && current.parent.type !== "ExportNamedDeclaration" && current.parent.type !== "ExportDefaultDeclaration" || current.parent.type === "Program")
|
|
2668
|
-
return false;
|
|
2669
|
-
current = current.parent;
|
|
2670
|
-
}
|
|
2671
|
-
}
|
|
2672
|
-
var requireSafetyCommentForTypeAssertionRule = defineRule15({
|
|
2673
|
-
meta: {
|
|
2674
|
-
type: "problem",
|
|
2675
|
-
docs: {
|
|
2676
|
-
description: "Require a nearby SAFETY comment for every TypeScript type assertion except const assertions."
|
|
2677
|
-
},
|
|
2678
|
-
messages: {
|
|
2679
|
-
missingSafetyComment: "This type assertion has no `SAFETY:` justification. State the checked invariant immediately before the assertion or its containing statement."
|
|
2680
|
-
}
|
|
2681
|
-
},
|
|
2682
|
-
createOnce(context) {
|
|
2683
|
-
const checkAssertion = (node) => {
|
|
2684
|
-
if (isConstAssertion2(node) || hasSafetyComment(context.sourceCode, node))
|
|
2685
|
-
return;
|
|
2686
|
-
context.report({ node, messageId: "missingSafetyComment" });
|
|
2687
|
-
};
|
|
2688
|
-
return {
|
|
2689
|
-
TSAsExpression: checkAssertion,
|
|
2690
|
-
TSTypeAssertion: checkAssertion
|
|
2691
|
-
};
|
|
2692
|
-
}
|
|
2693
|
-
});
|
|
2694
|
-
|
|
2695
|
-
// src/rules/dillon-anti-slop/index.ts
|
|
2696
|
-
var dillonAntiSlopPlugin = eslintCompatPlugin({
|
|
2697
|
-
meta: { name: "dillon-anti-slop" },
|
|
2698
|
-
rules: {
|
|
2699
|
-
"no-chained-type-assertions": noChainedTypeAssertionsRule,
|
|
2700
|
-
"no-conditional-empty-object-spread": noConditionalEmptyObjectSpreadRule,
|
|
2701
|
-
"no-known-value-widening": noKnownValueWideningRule,
|
|
2702
|
-
"no-module-mocking": noModuleMockingRule,
|
|
2703
|
-
"no-object-parameters": noObjectParametersRule,
|
|
2704
|
-
"no-reflect-apply": noReflectApplyRule,
|
|
2705
|
-
"no-reflect-get": noReflectGetRule,
|
|
2706
|
-
"no-runtime-typeof": noRuntimeTypeofRule,
|
|
2707
|
-
"no-unsafe-dictionary-type": noUnsafeDictionaryTypeRule,
|
|
2708
|
-
"no-shape-in-symbol-names": noForbiddenTermInSymbolNamesRule,
|
|
2709
|
-
"no-unknown-parameters": noUnknownParametersRule,
|
|
2710
|
-
"no-unknown-returns": noUnknownReturnsRule,
|
|
2711
|
-
"no-unknown-type-aliases": noUnknownTypeAliasesRule,
|
|
2712
|
-
"no-widen-then-assert": noWidenThenAssertRule,
|
|
2713
|
-
"require-safety-comment-for-type-assertion": requireSafetyCommentForTypeAssertionRule
|
|
2714
|
-
}
|
|
2715
|
-
});
|
|
2716
|
-
var dillon_anti_slop_default = dillonAntiSlopPlugin;
|
|
2717
|
-
export {
|
|
2718
|
-
dillon_anti_slop_default as default
|
|
2719
|
-
};
|