@kamaal111/oxlint-plugin-anti-slop 0.0.2 → 0.0.3
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/README.md +50 -1
- package/dist/effect/index.d.ts +4 -0
- package/dist/effect/index.d.ts.map +1 -0
- package/{src/effect/index.ts → dist/effect/index.js} +6 -8
- package/dist/effect/index.js.map +1 -0
- package/dist/effect/rules/no-service-constructor-imports.d.ts +3 -0
- package/dist/effect/rules/no-service-constructor-imports.d.ts.map +1 -0
- package/dist/effect/rules/no-service-constructor-imports.js +45 -0
- package/dist/effect/rules/no-service-constructor-imports.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/no-chained-type-assertions.d.ts +3 -0
- package/dist/rules/no-chained-type-assertions.d.ts.map +1 -0
- package/dist/rules/no-chained-type-assertions.js +61 -0
- package/dist/rules/no-chained-type-assertions.js.map +1 -0
- package/dist/rules/no-conditional-empty-object-spread.d.ts +3 -0
- package/dist/rules/no-conditional-empty-object-spread.d.ts.map +1 -0
- package/dist/rules/no-conditional-empty-object-spread.js +41 -0
- package/dist/rules/no-conditional-empty-object-spread.js.map +1 -0
- package/dist/rules/no-known-value-widening.d.ts +3 -0
- package/dist/rules/no-known-value-widening.d.ts.map +1 -0
- package/dist/rules/no-known-value-widening.js +184 -0
- package/dist/rules/no-known-value-widening.js.map +1 -0
- package/dist/rules/no-module-mocking.d.ts +3 -0
- package/dist/rules/no-module-mocking.d.ts.map +1 -0
- package/dist/rules/no-module-mocking.js +79 -0
- package/dist/rules/no-module-mocking.js.map +1 -0
- package/dist/rules/no-object-parameters.d.ts +3 -0
- package/dist/rules/no-object-parameters.d.ts.map +1 -0
- package/dist/rules/no-object-parameters.js +96 -0
- package/dist/rules/no-object-parameters.js.map +1 -0
- package/dist/rules/no-reflect-apply.d.ts +3 -0
- package/dist/rules/no-reflect-apply.d.ts.map +1 -0
- package/dist/rules/no-reflect-apply.js +26 -0
- package/dist/rules/no-reflect-apply.js.map +1 -0
- package/dist/rules/no-reflect-get.d.ts +3 -0
- package/dist/rules/no-reflect-get.d.ts.map +1 -0
- package/dist/rules/no-reflect-get.js +26 -0
- package/dist/rules/no-reflect-get.js.map +1 -0
- package/dist/rules/no-runtime-typeof.d.ts +3 -0
- package/dist/rules/no-runtime-typeof.d.ts.map +1 -0
- package/dist/rules/no-runtime-typeof.js +54 -0
- package/dist/rules/no-runtime-typeof.js.map +1 -0
- package/dist/rules/no-shape-in-symbol-names.d.ts +3 -0
- package/dist/rules/no-shape-in-symbol-names.d.ts.map +1 -0
- package/dist/rules/no-shape-in-symbol-names.js +34 -0
- package/dist/rules/no-shape-in-symbol-names.js.map +1 -0
- package/dist/rules/no-unknown-parameters.d.ts +3 -0
- package/dist/rules/no-unknown-parameters.d.ts.map +1 -0
- package/dist/rules/no-unknown-parameters.js +69 -0
- package/dist/rules/no-unknown-parameters.js.map +1 -0
- package/dist/rules/no-unknown-returns.d.ts +3 -0
- package/dist/rules/no-unknown-returns.d.ts.map +1 -0
- package/dist/rules/no-unknown-returns.js +86 -0
- package/dist/rules/no-unknown-returns.js.map +1 -0
- package/dist/rules/no-unknown-type-aliases.d.ts +3 -0
- package/dist/rules/no-unknown-type-aliases.d.ts.map +1 -0
- package/dist/rules/no-unknown-type-aliases.js +65 -0
- package/dist/rules/no-unknown-type-aliases.js.map +1 -0
- package/dist/rules/no-unsafe-dictionary-type.d.ts +3 -0
- package/dist/rules/no-unsafe-dictionary-type.d.ts.map +1 -0
- package/dist/rules/no-unsafe-dictionary-type.js +119 -0
- package/dist/rules/no-unsafe-dictionary-type.js.map +1 -0
- package/dist/rules/no-widen-then-assert.d.ts +3 -0
- package/dist/rules/no-widen-then-assert.d.ts.map +1 -0
- package/dist/rules/no-widen-then-assert.js +271 -0
- package/dist/rules/no-widen-then-assert.js.map +1 -0
- package/dist/rules/require-safety-comment-for-type-assertion.d.ts +3 -0
- package/dist/rules/require-safety-comment-for-type-assertion.d.ts.map +1 -0
- package/dist/rules/require-safety-comment-for-type-assertion.js +50 -0
- package/dist/rules/require-safety-comment-for-type-assertion.js.map +1 -0
- package/dist/shared/dictionary-types.d.ts +21 -0
- package/dist/shared/dictionary-types.d.ts.map +1 -0
- package/dist/shared/dictionary-types.js +389 -0
- package/dist/shared/dictionary-types.js.map +1 -0
- package/dist/shared/lexical-type-parameters.d.ts +6 -0
- package/dist/shared/lexical-type-parameters.d.ts.map +1 -0
- package/dist/shared/lexical-type-parameters.js +48 -0
- package/dist/shared/lexical-type-parameters.js.map +1 -0
- package/dist/shared/reflect-method.d.ts +4 -0
- package/dist/shared/reflect-method.d.ts.map +1 -0
- package/dist/shared/reflect-method.js +30 -0
- package/dist/shared/reflect-method.js.map +1 -0
- package/package.json +16 -4
- package/src/effect/rules/no-service-constructor-imports.test.ts +0 -56
- package/src/effect/rules/no-service-constructor-imports.ts +0 -52
- package/src/index.ts +0 -41
- package/src/rules/no-chained-type-assertions.ts +0 -77
- package/src/rules/no-conditional-empty-object-spread.test.ts +0 -32
- package/src/rules/no-conditional-empty-object-spread.ts +0 -49
- package/src/rules/no-known-value-widening.test.ts +0 -114
- package/src/rules/no-known-value-widening.ts +0 -247
- package/src/rules/no-module-mocking.test.ts +0 -28
- package/src/rules/no-module-mocking.ts +0 -91
- package/src/rules/no-object-parameters.test.ts +0 -32
- package/src/rules/no-object-parameters.ts +0 -126
- package/src/rules/no-reflect-apply.test.ts +0 -19
- package/src/rules/no-reflect-apply.ts +0 -28
- package/src/rules/no-reflect-get.test.ts +0 -20
- package/src/rules/no-reflect-get.ts +0 -28
- package/src/rules/no-runtime-typeof.test.ts +0 -42
- package/src/rules/no-runtime-typeof.ts +0 -67
- package/src/rules/no-shape-in-symbol-names.ts +0 -39
- package/src/rules/no-unknown-parameters.ts +0 -83
- package/src/rules/no-unknown-returns.test.ts +0 -33
- package/src/rules/no-unknown-returns.ts +0 -115
- package/src/rules/no-unknown-type-aliases.test.ts +0 -18
- package/src/rules/no-unknown-type-aliases.ts +0 -70
- package/src/rules/no-unsafe-dictionary-type.test.ts +0 -103
- package/src/rules/no-unsafe-dictionary-type.ts +0 -134
- package/src/rules/no-widen-then-assert.test.ts +0 -19
- package/src/rules/no-widen-then-assert.ts +0 -366
- package/src/rules/require-safety-comment-for-type-assertion.test.ts +0 -29
- package/src/rules/require-safety-comment-for-type-assertion.ts +0 -62
- package/src/shared/dictionary-types.ts +0 -502
- package/src/shared/lexical-type-parameters.ts +0 -61
- package/src/shared/reflect-method.ts +0 -35
package/README.md
CHANGED
|
@@ -4,7 +4,56 @@
|
|
|
4
4
|
|
|
5
5
|
Opinionated Oxlint rules that reject low-evidence and low-signal TypeScript and JavaScript patterns.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Published as `@kamaal111/oxlint-plugin-anti-slop` for repositories that want to track upstream releases as a normal npm dependency. If you'd rather read, tweak, and own the rules directly, vendor the source instead — the bundled agent skill handles the initial copy and configuration.
|
|
8
|
+
|
|
9
|
+
## Install as an npm dependency
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --save-dev @kamaal111/oxlint-plugin-anti-slop
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Register it in `oxlint.config.ts`:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { defineConfig } from "oxlint";
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
jsPlugins: [
|
|
22
|
+
{ name: "anti-slop", specifier: "@kamaal111/oxlint-plugin-anti-slop" },
|
|
23
|
+
],
|
|
24
|
+
rules: {
|
|
25
|
+
"anti-slop/no-chained-type-assertions": "error",
|
|
26
|
+
"anti-slop/no-conditional-empty-object-spread": "error",
|
|
27
|
+
"anti-slop/no-known-value-widening": "error",
|
|
28
|
+
"anti-slop/no-module-mocking": "error",
|
|
29
|
+
"anti-slop/no-object-parameters": "error",
|
|
30
|
+
"anti-slop/no-reflect-apply": "error",
|
|
31
|
+
"anti-slop/no-reflect-get": "error",
|
|
32
|
+
"anti-slop/no-runtime-typeof": "error",
|
|
33
|
+
"anti-slop/no-shape-in-symbol-names": "error",
|
|
34
|
+
"anti-slop/no-unknown-parameters": "error",
|
|
35
|
+
"anti-slop/no-unknown-returns": "error",
|
|
36
|
+
"anti-slop/no-unknown-type-aliases": "error",
|
|
37
|
+
"anti-slop/no-unsafe-dictionary-type": "error",
|
|
38
|
+
"anti-slop/no-widen-then-assert": "error",
|
|
39
|
+
"anti-slop/require-safety-comment-for-type-assertion": "error"
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The optional Effect rules live at the `@kamaal111/oxlint-plugin-anti-slop/effect` subpath:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
export default defineConfig({
|
|
48
|
+
jsPlugins: [
|
|
49
|
+
{ name: "anti-slop", specifier: "@kamaal111/oxlint-plugin-anti-slop" },
|
|
50
|
+
{ name: "anti-slop-effect", specifier: "@kamaal111/oxlint-plugin-anti-slop/effect" }
|
|
51
|
+
],
|
|
52
|
+
rules: {
|
|
53
|
+
"anti-slop-effect/no-service-constructor-imports": "error"
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
```
|
|
8
57
|
|
|
9
58
|
## Install with an agent skill
|
|
10
59
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/effect/index.ts"],"names":[],"mappings":"AAIA,qEAAqE;AACrE,QAAA,MAAM,oBAAoB,kCAKxB,CAAC;eAEY,oBAAoB"}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
2
|
-
|
|
3
|
-
import { noServiceConstructorImportsRule } from "./rules/no-service-constructor-imports.ts";
|
|
4
|
-
|
|
2
|
+
import { noServiceConstructorImportsRule } from "./rules/no-service-constructor-imports.js";
|
|
5
3
|
/** Opt-in Oxlint rules for Effect service and Layer architecture. */
|
|
6
4
|
const antiSlopEffectPlugin = eslintCompatPlugin({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
meta: { name: "anti-slop-effect" },
|
|
6
|
+
rules: {
|
|
7
|
+
"no-service-constructor-imports": noServiceConstructorImportsRule,
|
|
8
|
+
},
|
|
11
9
|
});
|
|
12
|
-
|
|
13
10
|
export default antiSlopEffectPlugin;
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/effect/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,EAAE,+BAA+B,EAAE,MAAM,2CAA2C,CAAC;AAE5F,qEAAqE;AACrE,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;IAC/C,IAAI,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE;IAClC,KAAK,EAAE;QACN,gCAAgC,EAAE,+BAA+B;KACjE;CACD,CAAC,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-service-constructor-imports.d.ts","sourceRoot":"","sources":["../../../src/effect/rules/no-service-constructor-imports.ts"],"names":[],"mappings":"AAgBA,oGAAoG;AACpG,eAAO,MAAM,+BAA+B,gCAkC1C,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
const SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
|
|
3
|
+
const TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
|
|
4
|
+
function isProjectLocalImport(source) {
|
|
5
|
+
return source.startsWith("./") || source.startsWith("../");
|
|
6
|
+
}
|
|
7
|
+
function getImportedName(specifier) {
|
|
8
|
+
if (specifier.imported.type === "Identifier")
|
|
9
|
+
return specifier.imported.name;
|
|
10
|
+
return specifier.imported.value;
|
|
11
|
+
}
|
|
12
|
+
/** Keep dependency-bearing Effect service constructors local to their owning capability modules. */
|
|
13
|
+
export const noServiceConstructorImportsRule = defineRule({
|
|
14
|
+
meta: {
|
|
15
|
+
type: "problem",
|
|
16
|
+
docs: {
|
|
17
|
+
description: "Disallow project-local make<CapabilityName> imports outside test and spec files.",
|
|
18
|
+
},
|
|
19
|
+
messages: {
|
|
20
|
+
serviceConstructorImport: 'Do not import Effect service constructor "{{name}}" into runtime code. Import the owning Layer, yield the contextual service, and allow its requirements to propagate to the composition root.',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
create(context) {
|
|
24
|
+
const isTestFile = TEST_FILE.test(context.filename.replaceAll("\\", "/"));
|
|
25
|
+
return {
|
|
26
|
+
ImportDeclaration(node) {
|
|
27
|
+
if (isTestFile || !isProjectLocalImport(node.source.value))
|
|
28
|
+
return;
|
|
29
|
+
for (const specifier of node.specifiers) {
|
|
30
|
+
if (specifier.type !== "ImportSpecifier")
|
|
31
|
+
continue;
|
|
32
|
+
const importedName = getImportedName(specifier);
|
|
33
|
+
if (!SERVICE_CONSTRUCTOR_NAME.test(importedName))
|
|
34
|
+
continue;
|
|
35
|
+
context.report({
|
|
36
|
+
node: specifier,
|
|
37
|
+
messageId: "serviceConstructorImport",
|
|
38
|
+
data: { name: importedName },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=no-service-constructor-imports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-service-constructor-imports.js","sourceRoot":"","sources":["../../../src/effect/rules/no-service-constructor-imports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,MAAM,wBAAwB,GAAG,aAAa,CAAC;AAC/C,MAAM,SAAS,GAAG,iCAAiC,CAAC;AAEpD,SAAS,oBAAoB,CAAC,MAAc;IAC3C,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,eAAe,CAAC,SAAiC;IACzD,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC7E,OAAO,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;AACjC,CAAC;AAED,oGAAoG;AACpG,MAAM,CAAC,MAAM,+BAA+B,GAAG,UAAU,CAAC;IACzD,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EACV,kFAAkF;SACnF;QACD,QAAQ,EAAE;YACT,wBAAwB,EACvB,gMAAgM;SACjM;KACD;IACD,MAAM,CAAC,OAAO;QACb,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QAE1E,OAAO;YACN,iBAAiB,CAAC,IAAI;gBACrB,IAAI,UAAU,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;oBAAE,OAAO;gBAEnE,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;oBACzC,IAAI,SAAS,CAAC,IAAI,KAAK,iBAAiB;wBAAE,SAAS;oBAEnD,MAAM,YAAY,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;oBAChD,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,YAAY,CAAC;wBAAE,SAAS;oBAE3D,OAAO,CAAC,MAAM,CAAC;wBACd,IAAI,EAAE,SAAS;wBACf,SAAS,EAAE,0BAA0B;wBACrC,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;qBAC5B,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,4FAA4F;AAC5F,QAAA,MAAM,cAAc,kCAmBlB,CAAC;eAEY,cAAc"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
2
|
+
import { noChainedTypeAssertionsRule } from "./rules/no-chained-type-assertions.js";
|
|
3
|
+
import { noConditionalEmptyObjectSpreadRule } from "./rules/no-conditional-empty-object-spread.js";
|
|
4
|
+
import { noKnownValueWideningRule } from "./rules/no-known-value-widening.js";
|
|
5
|
+
import { noModuleMockingRule } from "./rules/no-module-mocking.js";
|
|
6
|
+
import { noObjectParametersRule } from "./rules/no-object-parameters.js";
|
|
7
|
+
import { noReflectApplyRule } from "./rules/no-reflect-apply.js";
|
|
8
|
+
import { noReflectGetRule } from "./rules/no-reflect-get.js";
|
|
9
|
+
import { noRuntimeTypeofRule } from "./rules/no-runtime-typeof.js";
|
|
10
|
+
import { noForbiddenTermInSymbolNamesRule } from "./rules/no-shape-in-symbol-names.js";
|
|
11
|
+
import { noUnknownParametersRule } from "./rules/no-unknown-parameters.js";
|
|
12
|
+
import { noUnknownReturnsRule } from "./rules/no-unknown-returns.js";
|
|
13
|
+
import { noUnknownTypeAliasesRule } from "./rules/no-unknown-type-aliases.js";
|
|
14
|
+
import { noUnsafeDictionaryTypeRule } from "./rules/no-unsafe-dictionary-type.js";
|
|
15
|
+
import { noWidenThenAssertRule } from "./rules/no-widen-then-assert.js";
|
|
16
|
+
import { requireSafetyCommentForTypeAssertionRule } from "./rules/require-safety-comment-for-type-assertion.js";
|
|
17
|
+
/** Generic Oxlint rules that reject low-evidence and low-signal implementation patterns. */
|
|
18
|
+
const antiSlopPlugin = eslintCompatPlugin({
|
|
19
|
+
meta: { name: "anti-slop" },
|
|
20
|
+
rules: {
|
|
21
|
+
"no-chained-type-assertions": noChainedTypeAssertionsRule,
|
|
22
|
+
"no-conditional-empty-object-spread": noConditionalEmptyObjectSpreadRule,
|
|
23
|
+
"no-known-value-widening": noKnownValueWideningRule,
|
|
24
|
+
"no-module-mocking": noModuleMockingRule,
|
|
25
|
+
"no-object-parameters": noObjectParametersRule,
|
|
26
|
+
"no-reflect-apply": noReflectApplyRule,
|
|
27
|
+
"no-reflect-get": noReflectGetRule,
|
|
28
|
+
"no-runtime-typeof": noRuntimeTypeofRule,
|
|
29
|
+
"no-unsafe-dictionary-type": noUnsafeDictionaryTypeRule,
|
|
30
|
+
"no-shape-in-symbol-names": noForbiddenTermInSymbolNamesRule,
|
|
31
|
+
"no-unknown-parameters": noUnknownParametersRule,
|
|
32
|
+
"no-unknown-returns": noUnknownReturnsRule,
|
|
33
|
+
"no-unknown-type-aliases": noUnknownTypeAliasesRule,
|
|
34
|
+
"no-widen-then-assert": noWidenThenAssertRule,
|
|
35
|
+
"require-safety-comment-for-type-assertion": requireSafetyCommentForTypeAssertionRule,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
export default antiSlopPlugin;
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAC;AACpF,OAAO,EAAE,kCAAkC,EAAE,MAAM,+CAA+C,CAAC;AACnG,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,gCAAgC,EAAE,MAAM,qCAAqC,CAAC;AACvF,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAClF,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,wCAAwC,EAAE,MAAM,sDAAsD,CAAC;AAEhH,4FAA4F;AAC5F,MAAM,cAAc,GAAG,kBAAkB,CAAC;IACzC,IAAI,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;IAC3B,KAAK,EAAE;QACN,4BAA4B,EAAE,2BAA2B;QACzD,oCAAoC,EAAE,kCAAkC;QACxE,yBAAyB,EAAE,wBAAwB;QACnD,mBAAmB,EAAE,mBAAmB;QACxC,sBAAsB,EAAE,sBAAsB;QAC9C,kBAAkB,EAAE,kBAAkB;QACtC,gBAAgB,EAAE,gBAAgB;QAClC,mBAAmB,EAAE,mBAAmB;QACxC,2BAA2B,EAAE,0BAA0B;QACvD,0BAA0B,EAAE,gCAAgC;QAC5D,uBAAuB,EAAE,uBAAuB;QAChD,oBAAoB,EAAE,oBAAoB;QAC1C,yBAAyB,EAAE,wBAAwB;QACnD,sBAAsB,EAAE,qBAAqB;QAC7C,2CAA2C,EAAE,wCAAwC;KACrF;CACD,CAAC,CAAC;AAEH,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-chained-type-assertions.d.ts","sourceRoot":"","sources":["../../src/rules/no-chained-type-assertions.ts"],"names":[],"mappings":"AAoDA,yGAAyG;AACzG,eAAO,MAAM,2BAA2B,gCAuBtC,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
function isTypeAssertionExpression(node) {
|
|
3
|
+
return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
|
|
4
|
+
}
|
|
5
|
+
function unwrapParenthesizedExpression(expression) {
|
|
6
|
+
let current = expression;
|
|
7
|
+
while (current.type === "ParenthesizedExpression") {
|
|
8
|
+
current = current.expression;
|
|
9
|
+
}
|
|
10
|
+
return current;
|
|
11
|
+
}
|
|
12
|
+
function isConstAssertion(node) {
|
|
13
|
+
const { typeAnnotation } = node;
|
|
14
|
+
return (typeAnnotation.type === "TSTypeReference" &&
|
|
15
|
+
typeAnnotation.typeName.type === "Identifier" &&
|
|
16
|
+
typeAnnotation.typeName.name === "const");
|
|
17
|
+
}
|
|
18
|
+
function isOutermostAssertionInChain(node) {
|
|
19
|
+
let current = node;
|
|
20
|
+
let parent = node.parent;
|
|
21
|
+
while (parent.type === "ParenthesizedExpression" && parent.expression === current) {
|
|
22
|
+
current = parent;
|
|
23
|
+
parent = parent.parent;
|
|
24
|
+
}
|
|
25
|
+
return !isTypeAssertionExpression(parent) || parent.expression !== current;
|
|
26
|
+
}
|
|
27
|
+
function isForbiddenAssertionChain(node) {
|
|
28
|
+
let assertionCount = 0;
|
|
29
|
+
let hasNonConstAssertion = false;
|
|
30
|
+
let current = node;
|
|
31
|
+
while (isTypeAssertionExpression(current)) {
|
|
32
|
+
assertionCount += 1;
|
|
33
|
+
hasNonConstAssertion ||= !isConstAssertion(current);
|
|
34
|
+
current = unwrapParenthesizedExpression(current.expression);
|
|
35
|
+
}
|
|
36
|
+
return assertionCount > 1 && hasNonConstAssertion;
|
|
37
|
+
}
|
|
38
|
+
/** Disallow nested TypeScript type assertions, while permitting chains made only of const assertions. */
|
|
39
|
+
export const noChainedTypeAssertionsRule = defineRule({
|
|
40
|
+
meta: {
|
|
41
|
+
type: "problem",
|
|
42
|
+
docs: {
|
|
43
|
+
description: "Disallow chained TypeScript as and angle-bracket assertions, including parenthesized chains.",
|
|
44
|
+
},
|
|
45
|
+
messages: {
|
|
46
|
+
chained: "This assertion chain discards type evidence. Keep the original precise type, or parse untrusted input at its boundary before narrowing it.",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
createOnce(context) {
|
|
50
|
+
const checkTypeAssertion = (node) => {
|
|
51
|
+
if (!isOutermostAssertionInChain(node) || !isForbiddenAssertionChain(node))
|
|
52
|
+
return;
|
|
53
|
+
context.report({ node, messageId: "chained" });
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
TSAsExpression: checkTypeAssertion,
|
|
57
|
+
TSTypeAssertion: checkTypeAssertion,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=no-chained-type-assertions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-chained-type-assertions.js","sourceRoot":"","sources":["../../src/rules/no-chained-type-assertions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAK7C,SAAS,yBAAyB,CAAC,IAAiB;IAClD,OAAO,IAAI,CAAC,IAAI,KAAK,gBAAgB,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,CAAC;AAC3E,CAAC;AAED,SAAS,6BAA6B,CAAC,UAA6B;IAClE,IAAI,OAAO,GAAG,UAAU,CAAC;IACzB,OAAO,OAAO,CAAC,IAAI,KAAK,yBAAyB,EAAE,CAAC;QAClD,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAC/B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAA6B;IACrD,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAChC,OAAO,CACL,cAAc,CAAC,IAAI,KAAK,iBAAiB;QACzC,cAAc,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;QAC7C,cAAc,CAAC,QAAQ,CAAC,IAAI,KAAK,OAAO,CACzC,CAAC;AACJ,CAAC;AAED,SAAS,2BAA2B,CAAC,IAA6B;IAChE,IAAI,OAAO,GAAsB,IAAI,CAAC;IACtC,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAEzB,OAAO,MAAM,CAAC,IAAI,KAAK,yBAAyB,IAAI,MAAM,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;QAClF,OAAO,GAAG,MAAM,CAAC;QACjB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IACzB,CAAC;IAED,OAAO,CAAC,yBAAyB,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,OAAO,CAAC;AAC7E,CAAC;AAED,SAAS,yBAAyB,CAAC,IAA6B;IAC9D,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,oBAAoB,GAAG,KAAK,CAAC;IACjC,IAAI,OAAO,GAAsB,IAAI,CAAC;IAEtC,OAAO,yBAAyB,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1C,cAAc,IAAI,CAAC,CAAC;QACpB,oBAAoB,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACpD,OAAO,GAAG,6BAA6B,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,cAAc,GAAG,CAAC,IAAI,oBAAoB,CAAC;AACpD,CAAC;AAED,yGAAyG;AACzG,MAAM,CAAC,MAAM,2BAA2B,GAAG,UAAU,CAAC;IACpD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EACT,8FAA8F;SACjG;QACD,QAAQ,EAAE;YACR,OAAO,EACL,4IAA4I;SAC/I;KACF;IACD,UAAU,CAAC,OAAO;QAChB,MAAM,kBAAkB,GAAG,CAAC,IAA6B,EAAE,EAAE;YAC3D,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC;gBAAE,OAAO;YACnF,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;QACjD,CAAC,CAAC;QAEF,OAAO;YACL,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,kBAAkB;SACpC,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-conditional-empty-object-spread.d.ts","sourceRoot":"","sources":["../../src/rules/no-conditional-empty-object-spread.ts"],"names":[],"mappings":"AAwBA,sFAAsF;AACtF,eAAO,MAAM,kCAAkC,gCAuB7C,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
function unwrapParentheses(node) {
|
|
3
|
+
let current = node;
|
|
4
|
+
while (current.type === "ParenthesizedExpression") {
|
|
5
|
+
current = current.expression;
|
|
6
|
+
}
|
|
7
|
+
return current;
|
|
8
|
+
}
|
|
9
|
+
function isEmptyObjectExpression(node) {
|
|
10
|
+
return node.type === "ObjectExpression" && node.properties.length === 0;
|
|
11
|
+
}
|
|
12
|
+
function isConditionalEmptyObjectSpread(node) {
|
|
13
|
+
const conditional = unwrapParentheses(node);
|
|
14
|
+
return (conditional.type === "ConditionalExpression" &&
|
|
15
|
+
(isEmptyObjectExpression(conditional.consequent) ||
|
|
16
|
+
isEmptyObjectExpression(conditional.alternate)));
|
|
17
|
+
}
|
|
18
|
+
/** Ban conditional empty-object spreads without changing their omission semantics. */
|
|
19
|
+
export const noConditionalEmptyObjectSpreadRule = defineRule({
|
|
20
|
+
meta: {
|
|
21
|
+
type: "suggestion",
|
|
22
|
+
docs: {
|
|
23
|
+
description: "Disallow object spreads that conditionally spread an empty object to omit fields.",
|
|
24
|
+
},
|
|
25
|
+
messages: {
|
|
26
|
+
avoid: "This conditional spread hides property omission behind an empty object. Build the object in separate statements and add the property only when present.",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
createOnce(context) {
|
|
30
|
+
return {
|
|
31
|
+
SpreadElement(node) {
|
|
32
|
+
if (node.parent.type !== "ObjectExpression")
|
|
33
|
+
return;
|
|
34
|
+
if (isConditionalEmptyObjectSpread(node.argument)) {
|
|
35
|
+
context.report({ node, messageId: "avoid" });
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=no-conditional-empty-object-spread.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-conditional-empty-object-spread.js","sourceRoot":"","sources":["../../src/rules/no-conditional-empty-object-spread.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C,SAAS,iBAAiB,CAAC,IAAuB;IAChD,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,OAAO,OAAO,CAAC,IAAI,KAAK,yBAAyB,EAAE,CAAC;QAClD,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAC/B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,uBAAuB,CAAC,IAAuB;IACtD,OAAO,IAAI,CAAC,IAAI,KAAK,kBAAkB,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,8BAA8B,CAAC,IAAuB;IAC7D,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC5C,OAAO,CACL,WAAW,CAAC,IAAI,KAAK,uBAAuB;QAC5C,CAAC,uBAAuB,CAAC,WAAW,CAAC,UAAU,CAAC;YAC9C,uBAAuB,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAClD,CAAC;AACJ,CAAC;AAED,sFAAsF;AACtF,MAAM,CAAC,MAAM,kCAAkC,GAAG,UAAU,CAAC;IAC3D,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EACT,mFAAmF;SACtF;QACD,QAAQ,EAAE;YACR,KAAK,EACH,yJAAyJ;SAC5J;KACF;IACD,UAAU,CAAC,OAAO;QAChB,OAAO;YACL,aAAa,CAAC,IAAI;gBAChB,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,kBAAkB;oBAAE,OAAO;gBAEpD,IAAI,8BAA8B,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAClD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC/C,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-known-value-widening.d.ts","sourceRoot":"","sources":["../../src/rules/no-known-value-widening.ts"],"names":[],"mappings":"AAoIA,iGAAiG;AACjG,eAAO,MAAM,wBAAwB,gCAiHnC,CAAC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
import { classifyWideningTarget, createTypeEnvironment, isKnownEvidenceExpression, } from "../shared/dictionary-types.js";
|
|
3
|
+
function unwrapExpression(expression) {
|
|
4
|
+
let current = expression;
|
|
5
|
+
while (current.type === "ParenthesizedExpression" ||
|
|
6
|
+
current.type === "TSAsExpression" ||
|
|
7
|
+
current.type === "TSSatisfiesExpression" ||
|
|
8
|
+
current.type === "TSTypeAssertion" ||
|
|
9
|
+
current.type === "TSNonNullExpression") {
|
|
10
|
+
current = current.expression;
|
|
11
|
+
}
|
|
12
|
+
return current;
|
|
13
|
+
}
|
|
14
|
+
function resolveVariable(sourceCode, identifier) {
|
|
15
|
+
let scope = sourceCode.getScope(identifier);
|
|
16
|
+
while (scope !== null) {
|
|
17
|
+
const variable = scope.set.get(identifier.name);
|
|
18
|
+
if (variable !== undefined)
|
|
19
|
+
return variable;
|
|
20
|
+
scope = scope.upper;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function variableDeclarator(variable) {
|
|
25
|
+
if (variable.defs.length !== 1)
|
|
26
|
+
return null;
|
|
27
|
+
const [definition] = variable.defs;
|
|
28
|
+
return definition?.type === "Variable" && definition.node.type === "VariableDeclarator"
|
|
29
|
+
? definition.node
|
|
30
|
+
: null;
|
|
31
|
+
}
|
|
32
|
+
function isStableConstVariable(variable, declarator) {
|
|
33
|
+
return (declarator.parent.type === "VariableDeclaration" &&
|
|
34
|
+
declarator.parent.kind === "const" &&
|
|
35
|
+
variable.references.every((reference) => reference.init || !reference.isWrite()));
|
|
36
|
+
}
|
|
37
|
+
function hasKnownEvidence(sourceCode, expression, visitedVariables = new Set()) {
|
|
38
|
+
if (isKnownEvidenceExpression(expression))
|
|
39
|
+
return true;
|
|
40
|
+
const unwrapped = unwrapExpression(expression);
|
|
41
|
+
if (unwrapped.type !== "Identifier")
|
|
42
|
+
return false;
|
|
43
|
+
const variable = resolveVariable(sourceCode, unwrapped);
|
|
44
|
+
if (variable === null || visitedVariables.has(variable))
|
|
45
|
+
return false;
|
|
46
|
+
const declarator = variableDeclarator(variable);
|
|
47
|
+
if (declarator === null ||
|
|
48
|
+
declarator.init === null ||
|
|
49
|
+
!isStableConstVariable(variable, declarator)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
visitedVariables.add(variable);
|
|
53
|
+
return hasKnownEvidence(sourceCode, declarator.init, visitedVariables);
|
|
54
|
+
}
|
|
55
|
+
function annotationTarget(annotation, environment) {
|
|
56
|
+
return annotation === null || annotation === undefined
|
|
57
|
+
? null
|
|
58
|
+
: classifyWideningTarget(annotation.typeAnnotation, environment);
|
|
59
|
+
}
|
|
60
|
+
function enclosingFunction(node) {
|
|
61
|
+
let current = node.parent;
|
|
62
|
+
while (current !== null && current.type !== "Program") {
|
|
63
|
+
if (current.type === "ArrowFunctionExpression" ||
|
|
64
|
+
current.type === "FunctionDeclaration" ||
|
|
65
|
+
current.type === "FunctionExpression") {
|
|
66
|
+
return current;
|
|
67
|
+
}
|
|
68
|
+
current = current.parent;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function sourceKeyName(sourceCode, key) {
|
|
73
|
+
if (key.type === "Identifier" || key.type === "PrivateIdentifier")
|
|
74
|
+
return key.name;
|
|
75
|
+
if (key.type === "Literal")
|
|
76
|
+
return String(key.value);
|
|
77
|
+
return sourceCode.getText(key);
|
|
78
|
+
}
|
|
79
|
+
function functionName(sourceCode, owner) {
|
|
80
|
+
if (owner === null)
|
|
81
|
+
return "anonymous function";
|
|
82
|
+
if (owner.id !== null)
|
|
83
|
+
return owner.id.name;
|
|
84
|
+
const parent = owner.parent;
|
|
85
|
+
if (parent.type === "VariableDeclarator" && parent.id.type === "Identifier")
|
|
86
|
+
return parent.id.name;
|
|
87
|
+
if (parent.type === "MethodDefinition")
|
|
88
|
+
return sourceKeyName(sourceCode, parent.key);
|
|
89
|
+
return "anonymous function";
|
|
90
|
+
}
|
|
91
|
+
function isEmptyObjectExpression(expression) {
|
|
92
|
+
const unwrapped = unwrapExpression(expression);
|
|
93
|
+
return unwrapped.type === "ObjectExpression" && unwrapped.properties.length === 0;
|
|
94
|
+
}
|
|
95
|
+
function isDictionaryAccumulatorTarget(destination) {
|
|
96
|
+
return destination.kind === "open dictionary" || destination.kind === "generic container";
|
|
97
|
+
}
|
|
98
|
+
function hasParentAssertion(node) {
|
|
99
|
+
return node.parent?.type === "TSAsExpression" || node.parent?.type === "TSTypeAssertion";
|
|
100
|
+
}
|
|
101
|
+
/** Detect sound syntactic cases where a known value is explicitly widened and loses evidence. */
|
|
102
|
+
export const noKnownValueWideningRule = defineRule({
|
|
103
|
+
meta: {
|
|
104
|
+
type: "problem",
|
|
105
|
+
docs: {
|
|
106
|
+
description: "Disallow syntactically established values from flowing into explicitly broad or anonymous target types that discard useful evidence.",
|
|
107
|
+
},
|
|
108
|
+
messages: {
|
|
109
|
+
widening: "The explicit {{target}} type on {{subject}} discards known type evidence. Keep inference, validate with `satisfies`, or use a named owner contract.",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
createOnce(context) {
|
|
113
|
+
let environment = null;
|
|
114
|
+
const reportFlow = (expression, destination, subject) => {
|
|
115
|
+
if (destination === null)
|
|
116
|
+
return;
|
|
117
|
+
if (isDictionaryAccumulatorTarget(destination) &&
|
|
118
|
+
isEmptyObjectExpression(expression)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!hasKnownEvidence(context.sourceCode, expression))
|
|
122
|
+
return;
|
|
123
|
+
context.report({
|
|
124
|
+
node: expression,
|
|
125
|
+
messageId: "widening",
|
|
126
|
+
data: { subject, target: destination.kind },
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
const targetFromAnnotation = (annotation) => environment === null ? null : annotationTarget(annotation, environment);
|
|
130
|
+
return {
|
|
131
|
+
Program(node) {
|
|
132
|
+
environment = createTypeEnvironment(node);
|
|
133
|
+
},
|
|
134
|
+
VariableDeclarator(node) {
|
|
135
|
+
if (node.init === null || node.id.type !== "Identifier")
|
|
136
|
+
return;
|
|
137
|
+
reportFlow(node.init, targetFromAnnotation(node.id.typeAnnotation), `binding \`${node.id.name}\``);
|
|
138
|
+
},
|
|
139
|
+
PropertyDefinition(node) {
|
|
140
|
+
if (node.value === null)
|
|
141
|
+
return;
|
|
142
|
+
reportFlow(node.value, targetFromAnnotation(node.typeAnnotation), `property \`${sourceKeyName(context.sourceCode, node.key)}\``);
|
|
143
|
+
},
|
|
144
|
+
AccessorProperty(node) {
|
|
145
|
+
if (node.value === null)
|
|
146
|
+
return;
|
|
147
|
+
reportFlow(node.value, targetFromAnnotation(node.typeAnnotation), `property \`${sourceKeyName(context.sourceCode, node.key)}\``);
|
|
148
|
+
},
|
|
149
|
+
AssignmentExpression(node) {
|
|
150
|
+
if (node.operator !== "=" || node.left.type !== "Identifier")
|
|
151
|
+
return;
|
|
152
|
+
const variable = resolveVariable(context.sourceCode, node.left);
|
|
153
|
+
if (variable === null)
|
|
154
|
+
return;
|
|
155
|
+
const declarator = variableDeclarator(variable);
|
|
156
|
+
if (declarator === null || declarator.id.type !== "Identifier")
|
|
157
|
+
return;
|
|
158
|
+
reportFlow(node.right, targetFromAnnotation(declarator.id.typeAnnotation), `binding \`${declarator.id.name}\``);
|
|
159
|
+
},
|
|
160
|
+
ReturnStatement(node) {
|
|
161
|
+
if (node.argument === null)
|
|
162
|
+
return;
|
|
163
|
+
const owner = enclosingFunction(node);
|
|
164
|
+
reportFlow(node.argument, targetFromAnnotation(owner?.returnType), `return value of \`${functionName(context.sourceCode, owner)}\``);
|
|
165
|
+
},
|
|
166
|
+
ArrowFunctionExpression(node) {
|
|
167
|
+
if (node.body.type === "BlockStatement")
|
|
168
|
+
return;
|
|
169
|
+
reportFlow(node.body, targetFromAnnotation(node.returnType), `return value of \`${functionName(context.sourceCode, node)}\``);
|
|
170
|
+
},
|
|
171
|
+
TSAsExpression(node) {
|
|
172
|
+
if (environment === null || hasParentAssertion(node))
|
|
173
|
+
return;
|
|
174
|
+
reportFlow(node.expression, classifyWideningTarget(node.typeAnnotation, environment), "assertion");
|
|
175
|
+
},
|
|
176
|
+
TSTypeAssertion(node) {
|
|
177
|
+
if (environment === null || hasParentAssertion(node))
|
|
178
|
+
return;
|
|
179
|
+
reportFlow(node.expression, classifyWideningTarget(node.typeAnnotation, environment), "assertion");
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
//# sourceMappingURL=no-known-value-widening.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-known-value-widening.js","sourceRoot":"","sources":["../../src/rules/no-known-value-widening.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-module-mocking.d.ts","sourceRoot":"","sources":["../../src/rules/no-module-mocking.ts"],"names":[],"mappings":"AAmEA,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,gCAsB9B,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { defineRule } from "@oxlint/plugins";
|
|
2
|
+
const moduleMockMethods = new Set(["doMock", "mock", "unstable_mockModule"]);
|
|
3
|
+
function resolveVariable(sourceCode, identifier) {
|
|
4
|
+
let scope = sourceCode.getScope(identifier);
|
|
5
|
+
while (scope !== null) {
|
|
6
|
+
const variable = scope.set.get(identifier.name);
|
|
7
|
+
if (variable !== undefined)
|
|
8
|
+
return variable;
|
|
9
|
+
scope = scope.upper;
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function importedName(node) {
|
|
14
|
+
if (node.type !== "ImportSpecifier")
|
|
15
|
+
return null;
|
|
16
|
+
return node.imported.type === "Identifier" ? node.imported.name : node.imported.value;
|
|
17
|
+
}
|
|
18
|
+
function isTestFrameworkObject(sourceCode, expression) {
|
|
19
|
+
if (expression.type !== "Identifier")
|
|
20
|
+
return false;
|
|
21
|
+
if ((expression.name === "vi" || expression.name === "jest") &&
|
|
22
|
+
sourceCode.isGlobalReference(expression)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
const variable = resolveVariable(sourceCode, expression);
|
|
26
|
+
if (variable === null || variable.defs.length === 0) {
|
|
27
|
+
return expression.name === "vi" || expression.name === "jest";
|
|
28
|
+
}
|
|
29
|
+
return variable.defs.some((definition) => {
|
|
30
|
+
if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration") {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const source = definition.parent.source.value;
|
|
34
|
+
const name = importedName(definition.node);
|
|
35
|
+
return (source === "vitest" && name === "vi") || (source === "@jest/globals" && name === "jest");
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function moduleMockCall(sourceCode, callee) {
|
|
39
|
+
if (!("property" in callee) || !("object" in callee) || !("computed" in callee))
|
|
40
|
+
return false;
|
|
41
|
+
if (!isTestFrameworkObject(sourceCode, callee.object))
|
|
42
|
+
return false;
|
|
43
|
+
const property = callee.property;
|
|
44
|
+
const method = callee.computed
|
|
45
|
+
? property.type === "Literal" &&
|
|
46
|
+
(property.value === "doMock" ||
|
|
47
|
+
property.value === "mock" ||
|
|
48
|
+
property.value === "unstable_mockModule")
|
|
49
|
+
? property.value
|
|
50
|
+
: null
|
|
51
|
+
: property.type === "Identifier"
|
|
52
|
+
? property.name
|
|
53
|
+
: null;
|
|
54
|
+
return method !== null && moduleMockMethods.has(method);
|
|
55
|
+
}
|
|
56
|
+
/** Ban test framework module mocking in favor of real dependency seams. */
|
|
57
|
+
export const noModuleMockingRule = defineRule({
|
|
58
|
+
meta: {
|
|
59
|
+
type: "problem",
|
|
60
|
+
docs: {
|
|
61
|
+
description: "Disallow Vitest and Jest module mocking; tests must replace dependencies through real interfaces.",
|
|
62
|
+
},
|
|
63
|
+
messages: {
|
|
64
|
+
moduleMock: "Replace module mocking with dependency injection through a real interface, service layer, or faithful test implementation.",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
createOnce(context) {
|
|
68
|
+
return {
|
|
69
|
+
CallExpression(node) {
|
|
70
|
+
if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression")
|
|
71
|
+
return;
|
|
72
|
+
if (moduleMockCall(context.sourceCode, node.callee)) {
|
|
73
|
+
context.report({ node, messageId: "moduleMock" });
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=no-module-mocking.js.map
|