ai-deslop 0.2.0 → 0.2.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.
Files changed (95) hide show
  1. package/README.md +7 -4
  2. package/dist/index.d.ts +18 -0
  3. package/dist/index.js +82 -0
  4. package/dist/rules/no-array-filter-map.d.ts +2 -0
  5. package/dist/rules/no-array-filter-map.js +30 -0
  6. package/dist/rules/no-call-only-assertions.d.ts +2 -0
  7. package/dist/rules/no-call-only-assertions.js +87 -0
  8. package/dist/rules/no-chained-type-assertions.d.ts +2 -0
  9. package/dist/rules/no-chained-type-assertions.js +60 -0
  10. package/dist/rules/no-conditional-empty-object-spread.d.ts +2 -0
  11. package/dist/rules/no-conditional-empty-object-spread.js +40 -0
  12. package/dist/rules/no-known-value-widening.d.ts +2 -0
  13. package/dist/rules/no-known-value-widening.js +300 -0
  14. package/dist/rules/no-module-mocking.d.ts +2 -0
  15. package/dist/rules/no-module-mocking.js +69 -0
  16. package/dist/rules/no-object-parameters.d.ts +2 -0
  17. package/dist/rules/no-object-parameters.js +56 -0
  18. package/dist/rules/no-pass-through-type-alias.d.ts +2 -0
  19. package/dist/rules/no-pass-through-type-alias.js +41 -0
  20. package/dist/rules/no-reduce-accumulator-copy.d.ts +2 -0
  21. package/dist/rules/no-reduce-accumulator-copy.js +99 -0
  22. package/dist/rules/no-reflect-apply.d.ts +2 -0
  23. package/dist/rules/no-reflect-apply.js +25 -0
  24. package/dist/rules/no-reflect-get.d.ts +2 -0
  25. package/dist/rules/no-reflect-get.js +25 -0
  26. package/dist/rules/no-runtime-typeof.d.ts +2 -0
  27. package/dist/rules/no-runtime-typeof.js +64 -0
  28. package/dist/rules/no-shape-in-symbol-names.d.ts +2 -0
  29. package/dist/rules/no-shape-in-symbol-names.js +40 -0
  30. package/dist/rules/no-unknown-parameters.d.ts +2 -0
  31. package/dist/rules/no-unknown-parameters.js +51 -0
  32. package/dist/rules/no-unknown-returns.d.ts +2 -0
  33. package/dist/rules/no-unknown-returns.js +58 -0
  34. package/dist/rules/no-unknown-type-aliases.d.ts +2 -0
  35. package/dist/rules/no-unknown-type-aliases.js +40 -0
  36. package/dist/rules/no-unsafe-dictionary-type.d.ts +2 -0
  37. package/dist/rules/no-unsafe-dictionary-type.js +134 -0
  38. package/dist/rules/no-widen-then-assert.d.ts +2 -0
  39. package/dist/rules/no-widen-then-assert.js +270 -0
  40. package/dist/rules/require-safety-comment-for-type-assertion.d.ts +2 -0
  41. package/dist/rules/require-safety-comment-for-type-assertion.js +100 -0
  42. package/dist/shared/array-method.d.ts +12 -0
  43. package/dist/shared/array-method.js +84 -0
  44. package/dist/shared/dictionary-types.d.ts +20 -0
  45. package/dist/shared/dictionary-types.js +377 -0
  46. package/dist/shared/function-parameters.d.ts +8 -0
  47. package/dist/shared/function-parameters.js +40 -0
  48. package/dist/shared/lexical-type-parameters.d.ts +5 -0
  49. package/dist/shared/lexical-type-parameters.js +47 -0
  50. package/dist/shared/reflect-method.d.ts +3 -0
  51. package/dist/shared/reflect-method.js +20 -0
  52. package/dist/shared/scope.d.ts +3 -0
  53. package/dist/shared/scope.js +11 -0
  54. package/dist/shared/type-alias-resolution.d.ts +23 -0
  55. package/dist/shared/type-alias-resolution.js +162 -0
  56. package/dist/tailwind/candidates.d.ts +14 -0
  57. package/dist/tailwind/candidates.js +24 -0
  58. package/dist/tailwind/design-system.d.ts +3 -0
  59. package/dist/tailwind/design-system.js +41 -0
  60. package/dist/tailwind/rule.d.ts +3 -0
  61. package/dist/tailwind/rule.js +133 -0
  62. package/dist/tailwind/worker.d.ts +1 -0
  63. package/dist/tailwind/worker.js +13 -0
  64. package/package.json +13 -4
  65. package/src/index.ts +0 -87
  66. package/src/rules/no-array-filter-map.ts +0 -28
  67. package/src/rules/no-call-only-assertions.ts +0 -94
  68. package/src/rules/no-chained-type-assertions.ts +0 -77
  69. package/src/rules/no-conditional-empty-object-spread.ts +0 -49
  70. package/src/rules/no-known-value-widening.ts +0 -415
  71. package/src/rules/no-module-mocking.ts +0 -80
  72. package/src/rules/no-object-parameters.ts +0 -83
  73. package/src/rules/no-pass-through-type-alias.ts +0 -52
  74. package/src/rules/no-reduce-accumulator-copy.ts +0 -109
  75. package/src/rules/no-reflect-apply.ts +0 -28
  76. package/src/rules/no-reflect-get.ts +0 -28
  77. package/src/rules/no-runtime-typeof.ts +0 -77
  78. package/src/rules/no-shape-in-symbol-names.ts +0 -46
  79. package/src/rules/no-unknown-parameters.ts +0 -69
  80. package/src/rules/no-unknown-returns.ts +0 -82
  81. package/src/rules/no-unknown-type-aliases.ts +0 -54
  82. package/src/rules/no-unsafe-dictionary-type.ts +0 -154
  83. package/src/rules/no-widen-then-assert.ts +0 -366
  84. package/src/rules/require-safety-comment-for-type-assertion.ts +0 -131
  85. package/src/shared/array-method.ts +0 -94
  86. package/src/shared/dictionary-types.ts +0 -515
  87. package/src/shared/function-parameters.ts +0 -49
  88. package/src/shared/lexical-type-parameters.ts +0 -61
  89. package/src/shared/reflect-method.ts +0 -24
  90. package/src/shared/scope.ts +0 -15
  91. package/src/shared/type-alias-resolution.ts +0 -250
  92. package/src/tailwind/candidates.ts +0 -38
  93. package/src/tailwind/design-system.ts +0 -59
  94. package/src/tailwind/rule.ts +0 -126
  95. package/src/tailwind/worker.ts +0 -38
package/README.md CHANGED
@@ -11,7 +11,7 @@ npm install --save-dev ai-deslop
11
11
  # @oxlint/plugins comes along as a runtime dependency — keep it in sync with your oxlint version
12
12
  ```
13
13
 
14
- Requires a runtime with native TypeScript plugin support: Bun, Deno, or Node.js >= 22.18 / 20.19.
14
+ Ships compiled JavaScript — works with any oxlint JS-plugin runtime (Bun, Deno, or Node.js).
15
15
 
16
16
  ## Usage
17
17
 
@@ -19,10 +19,10 @@ Register the plugin in `oxlint.config.ts` and enable every rule as an error via
19
19
 
20
20
  ```ts
21
21
  import { defineConfig } from "oxlint";
22
- import deslopPlugin, { configs as deslopConfigs } from "ai-deslop";
22
+ import { configs as deslopConfigs } from "ai-deslop";
23
23
 
24
24
  export default defineConfig({
25
- jsPlugins: [deslopPlugin],
25
+ jsPlugins: ["ai-deslop"],
26
26
  options: {
27
27
  ...deslopConfigs.recommended.options,
28
28
  },
@@ -108,8 +108,11 @@ The rules use Oxlint's ESTree and lexical-scope APIs rather than a TypeScript ty
108
108
  Enforces canonical Tailwind CSS class spellings with `--fix` support (`mt-[16px]` → `mt-4`). Same source of truth as the Tailwind language server. Excluded from `recommended` because it requires a `cssPath` option — add it manually:
109
109
 
110
110
  ```ts
111
+ import { defineConfig } from "oxlint";
112
+ import { configs as deslopConfigs } from "ai-deslop";
113
+
111
114
  export default defineConfig({
112
- jsPlugins: [deslopPlugin],
115
+ jsPlugins: ["ai-deslop"],
113
116
  options: {
114
117
  ...deslopConfigs.recommended.options,
115
118
  },
@@ -0,0 +1,18 @@
1
+ import { eslintCompatPlugin } from "@oxlint/plugins";
2
+ /** Every `deslop/*` rule, plus selected standard rules, each set to `"error"`. Spread into the `rules` field of an oxlint config. */
3
+ export declare const recommendedRules: Record<string, "error">;
4
+ /** ESLint flat-config style configs. `recommended` enables every `deslop/*` rule as `"error"` (except opt-in rules like `canonical-class-names`), plus standard `typescript/*` rules. The `typescript/*` rules require type-aware linting (`options.typeAware`), so `recommended` sets it. */
5
+ export declare const configs: {
6
+ recommended: {
7
+ name: string;
8
+ options: {
9
+ typeAware: boolean;
10
+ };
11
+ rules: Record<string, "error">;
12
+ };
13
+ };
14
+ /** Generic Oxlint rules that reject low-evidence and low-signal implementation patterns. */
15
+ declare const deslopPlugin: ReturnType<typeof eslintCompatPlugin> & {
16
+ configs: typeof configs;
17
+ };
18
+ export default deslopPlugin;
package/dist/index.js ADDED
@@ -0,0 +1,82 @@
1
+ import { eslintCompatPlugin } from "@oxlint/plugins";
2
+ import { noArrayFilterMapRule } from "./rules/no-array-filter-map.js";
3
+ import { noReduceAccumulatorCopyRule } from "./rules/no-reduce-accumulator-copy.js";
4
+ import { noChainedTypeAssertionsRule } from "./rules/no-chained-type-assertions.js";
5
+ import { noConditionalEmptyObjectSpreadRule } from "./rules/no-conditional-empty-object-spread.js";
6
+ import { noKnownValueWideningRule } from "./rules/no-known-value-widening.js";
7
+ import { noModuleMockingRule } from "./rules/no-module-mocking.js";
8
+ import { noObjectParametersRule } from "./rules/no-object-parameters.js";
9
+ import { noReflectApplyRule } from "./rules/no-reflect-apply.js";
10
+ import { noReflectGetRule } from "./rules/no-reflect-get.js";
11
+ import { noRuntimeTypeofRule } from "./rules/no-runtime-typeof.js";
12
+ import { noForbiddenTermInSymbolNamesRule } from "./rules/no-shape-in-symbol-names.js";
13
+ import { noUnknownParametersRule } from "./rules/no-unknown-parameters.js";
14
+ import { noUnknownReturnsRule } from "./rules/no-unknown-returns.js";
15
+ import { noUnknownTypeAliasesRule } from "./rules/no-unknown-type-aliases.js";
16
+ import { noUnsafeDictionaryTypeRule } from "./rules/no-unsafe-dictionary-type.js";
17
+ import { noWidenThenAssertRule } from "./rules/no-widen-then-assert.js";
18
+ import { requireSafetyCommentForTypeAssertionRule } from "./rules/require-safety-comment-for-type-assertion.js";
19
+ import { noCallOnlyAssertionsRule } from "./rules/no-call-only-assertions.js";
20
+ import { noPassThroughTypeAliasRule } from "./rules/no-pass-through-type-alias.js";
21
+ import { canonicalClassNames } from "./tailwind/rule.js";
22
+ /** Every `deslop/*` rule, plus selected standard rules, each set to `"error"`. Spread into the `rules` field of an oxlint config. */
23
+ export const recommendedRules = {
24
+ "deslop/no-array-filter-map": "error",
25
+ "deslop/no-reduce-accumulator-copy": "error",
26
+ "deslop/no-chained-type-assertions": "error",
27
+ "deslop/no-conditional-empty-object-spread": "error",
28
+ "deslop/no-known-value-widening": "error",
29
+ "deslop/no-module-mocking": "error",
30
+ "deslop/no-object-parameters": "error",
31
+ "deslop/no-reflect-apply": "error",
32
+ "deslop/no-reflect-get": "error",
33
+ "deslop/no-runtime-typeof": "error",
34
+ "deslop/no-shape-in-symbol-names": "error",
35
+ "deslop/no-unknown-parameters": "error",
36
+ "deslop/no-unknown-returns": "error",
37
+ "deslop/no-unknown-type-aliases": "error",
38
+ "deslop/no-unsafe-dictionary-type": "error",
39
+ "deslop/no-widen-then-assert": "error",
40
+ "deslop/require-safety-comment-for-type-assertion": "error",
41
+ "deslop/no-call-only-assertions": "error",
42
+ "deslop/no-pass-through-type-alias": "error",
43
+ "typescript/no-deprecated": "error",
44
+ "typescript/no-floating-promises": "error",
45
+ };
46
+ /** ESLint flat-config style configs. `recommended` enables every `deslop/*` rule as `"error"` (except opt-in rules like `canonical-class-names`), plus standard `typescript/*` rules. The `typescript/*` rules require type-aware linting (`options.typeAware`), so `recommended` sets it. */
47
+ export const configs = {
48
+ recommended: {
49
+ name: "deslop/recommended",
50
+ options: { typeAware: true },
51
+ rules: recommendedRules,
52
+ },
53
+ };
54
+ /** Generic Oxlint rules that reject low-evidence and low-signal implementation patterns. */
55
+ const deslopPlugin = eslintCompatPlugin({
56
+ meta: { name: "deslop" },
57
+ rules: {
58
+ "no-array-filter-map": noArrayFilterMapRule,
59
+ "no-reduce-accumulator-copy": noReduceAccumulatorCopyRule,
60
+ "no-chained-type-assertions": noChainedTypeAssertionsRule,
61
+ "no-conditional-empty-object-spread": noConditionalEmptyObjectSpreadRule,
62
+ "no-known-value-widening": noKnownValueWideningRule,
63
+ "no-module-mocking": noModuleMockingRule,
64
+ "no-object-parameters": noObjectParametersRule,
65
+ "no-reflect-apply": noReflectApplyRule,
66
+ "no-reflect-get": noReflectGetRule,
67
+ "no-runtime-typeof": noRuntimeTypeofRule,
68
+ "no-shape-in-symbol-names": noForbiddenTermInSymbolNamesRule,
69
+ "no-unknown-parameters": noUnknownParametersRule,
70
+ "no-unknown-returns": noUnknownReturnsRule,
71
+ "no-unknown-type-aliases": noUnknownTypeAliasesRule,
72
+ "no-unsafe-dictionary-type": noUnsafeDictionaryTypeRule,
73
+ "no-widen-then-assert": noWidenThenAssertRule,
74
+ "require-safety-comment-for-type-assertion": requireSafetyCommentForTypeAssertionRule,
75
+ "no-call-only-assertions": noCallOnlyAssertionsRule,
76
+ "no-pass-through-type-alias": noPassThroughTypeAliasRule,
77
+ // Opt-in: NOT in `recommendedRules`. Requires a `cssPath` option, so users add it manually.
78
+ "canonical-class-names": canonicalClassNames,
79
+ },
80
+ });
81
+ deslopPlugin.configs = configs;
82
+ export default deslopPlugin;
@@ -0,0 +1,2 @@
1
+ /** Reject eager array filter/map pipelines; lazy iterator helpers remain allowed. */
2
+ export declare const noArrayFilterMapRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,30 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import { arrayMethodTarget, isKnownArrayExpression, unwrapArrayExpression } from "../shared/array-method.js";
3
+ /** Reject eager array filter/map pipelines; lazy iterator helpers remain allowed. */
4
+ export const noArrayFilterMapRule = defineRule({
5
+ meta: {
6
+ type: "suggestion",
7
+ docs: { description: "Disallow adjacent array filter/map passes in favor of lazy iterator helpers or a single transformation." },
8
+ messages: {
9
+ arrayFilterMap: "Avoid consecutive array `{{first}}` and `{{second}}` passes. Prefer `.values().{{first}}(...).{{second}}(...).toArray()` where iterator helpers are supported, or a single `flatMap`/mutating reducer. Preserve callback ordering, indexes, and filtering semantics.",
10
+ },
11
+ },
12
+ createOnce(context) {
13
+ return {
14
+ CallExpression(node) {
15
+ const outer = arrayMethodTarget(node.callee);
16
+ if (outer === null || (outer.name !== "map" && outer.name !== "filter"))
17
+ return;
18
+ const innerCall = unwrapArrayExpression(outer.object);
19
+ if (innerCall.type !== "CallExpression")
20
+ return;
21
+ const inner = arrayMethodTarget(innerCall.callee);
22
+ if (inner === null || inner.name !== (outer.name === "map" ? "filter" : "map"))
23
+ return;
24
+ if (!isKnownArrayExpression(context.sourceCode, inner.object))
25
+ return;
26
+ context.report({ node, messageId: "arrayFilterMap", data: { first: inner.name, second: outer.name } });
27
+ },
28
+ };
29
+ },
30
+ });
@@ -0,0 +1,2 @@
1
+ /** Tests must assert on behavior, not only that a mock was called. */
2
+ export declare const noCallOnlyAssertionsRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,87 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ // Tests whose every assertion is a "was it called" mock check verify orchestration, not behavior.
3
+ // `toHaveBeenCalledWith` is allowed because it asserts on real data.
4
+ const CALL_ONLY_MATCHERS = new Set(["toHaveBeenCalled", "toHaveBeenCalledTimes", "toHaveBeenCalledOnce"]);
5
+ const TEST_NAMES = new Set(["it", "test"]);
6
+ const MODIFIERS = new Set(["not", "resolves", "rejects"]);
7
+ function rootIdentifierName(node) {
8
+ let current = node;
9
+ while (true) {
10
+ if (current.type === "Identifier")
11
+ return current.name;
12
+ if (current.type === "MemberExpression") {
13
+ current = current.object;
14
+ }
15
+ else if (current.type === "CallExpression" || current.type === "TaggedTemplateExpression") {
16
+ current = current.type === "CallExpression" ? current.callee : current.tag;
17
+ }
18
+ else {
19
+ return undefined;
20
+ }
21
+ }
22
+ }
23
+ function isTestCall(node) {
24
+ const name = rootIdentifierName(node.callee);
25
+ if (name === undefined || !TEST_NAMES.has(name))
26
+ return false;
27
+ const callback = node.arguments.at(-1);
28
+ return callback?.type === "FunctionExpression" || callback?.type === "ArrowFunctionExpression";
29
+ }
30
+ function matcherName(node) {
31
+ if (node.callee.type !== "MemberExpression" || node.callee.computed)
32
+ return undefined;
33
+ if (node.callee.property.type !== "Identifier")
34
+ return undefined;
35
+ let object = node.callee.object;
36
+ while (object.type === "MemberExpression" &&
37
+ !object.computed &&
38
+ object.property.type === "Identifier" &&
39
+ MODIFIERS.has(object.property.name)) {
40
+ object = object.object;
41
+ }
42
+ const isExpectCall = object.type === "CallExpression" && rootIdentifierName(object.callee) === "expect";
43
+ return isExpectCall ? node.callee.property.name : undefined;
44
+ }
45
+ /** Tests must assert on behavior, not only that a mock was called. */
46
+ export const noCallOnlyAssertionsRule = defineRule({
47
+ meta: {
48
+ type: "suggestion",
49
+ docs: { description: "Tests must assert on behavior, not only that a mock was called" },
50
+ messages: {
51
+ callOnlyAssertions: "Every assertion in this test only checks that a mock was called. First check whether this is a valuable test; remove it if not. Otherwise, assert on returned values, state, or call arguments instead.",
52
+ },
53
+ schema: [],
54
+ },
55
+ createOnce(context) {
56
+ let tests = [];
57
+ return {
58
+ before() {
59
+ tests = [];
60
+ },
61
+ CallExpression(node) {
62
+ if (isTestCall(node)) {
63
+ tests.push({ node, total: 0, callOnly: 0 });
64
+ return;
65
+ }
66
+ const test = tests.at(-1);
67
+ if (!test)
68
+ return;
69
+ const matcher = matcherName(node);
70
+ if (!matcher)
71
+ return;
72
+ test.total += 1;
73
+ if (CALL_ONLY_MATCHERS.has(matcher))
74
+ test.callOnly += 1;
75
+ },
76
+ "CallExpression:exit"(node) {
77
+ const test = tests.at(-1);
78
+ if (test?.node !== node)
79
+ return;
80
+ tests.pop();
81
+ if (test.total > 0 && test.total === test.callOnly) {
82
+ context.report({ messageId: "callOnlyAssertions", node: node.callee });
83
+ }
84
+ },
85
+ };
86
+ },
87
+ });
@@ -0,0 +1,2 @@
1
+ /** Disallow nested TypeScript type assertions, while permitting chains made only of const assertions. */
2
+ export declare const noChainedTypeAssertionsRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,60 @@
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
+ });
@@ -0,0 +1,2 @@
1
+ /** Ban conditional empty-object spreads without changing their omission semantics. */
2
+ export declare const noConditionalEmptyObjectSpreadRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,40 @@
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
+ });
@@ -0,0 +1,2 @@
1
+ /** Detect sound syntactic cases where a known value is explicitly widened and loses evidence. */
2
+ export declare const noKnownValueWideningRule: import("@oxlint/plugins").Rule;