@jameswyse/oxc-config 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -0
  3. package/REFERENCE.md +134 -0
  4. package/dist/format.d.ts +5 -0
  5. package/dist/format.js +47 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +97 -0
  8. package/dist/lint.d.ts +33 -0
  9. package/dist/lint.js +493 -0
  10. package/dist/rules/companion-has-owner.d.ts +1 -0
  11. package/dist/rules/companion-has-owner.js +74 -0
  12. package/dist/rules/electron-no-raw-ipc-renderer-exposure.d.ts +1 -0
  13. package/dist/rules/electron-no-raw-ipc-renderer-exposure.js +127 -0
  14. package/dist/rules/electron-no-remote-module.d.ts +1 -0
  15. package/dist/rules/electron-no-remote-module.js +112 -0
  16. package/dist/rules/electron-secure-web-preferences.d.ts +1 -0
  17. package/dist/rules/electron-secure-web-preferences.js +108 -0
  18. package/dist/rules/explicit-api-states.d.ts +1 -0
  19. package/dist/rules/explicit-api-states.js +143 -0
  20. package/dist/rules/import-boundaries.d.ts +1 -0
  21. package/dist/rules/import-boundaries.js +99 -0
  22. package/dist/rules/no-ambient-clock.d.ts +1 -0
  23. package/dist/rules/no-ambient-clock.js +35 -0
  24. package/dist/rules/no-ambient-random.d.ts +1 -0
  25. package/dist/rules/no-ambient-random.js +71 -0
  26. package/dist/rules/no-comments.d.ts +1 -0
  27. package/dist/rules/no-comments.js +64 -0
  28. package/dist/rules/no-deep-ternary.d.ts +1 -0
  29. package/dist/rules/no-deep-ternary.js +32 -0
  30. package/dist/rules/no-edit-proof-assertions.d.ts +1 -0
  31. package/dist/rules/no-edit-proof-assertions.js +120 -0
  32. package/dist/rules/no-env-access.d.ts +1 -0
  33. package/dist/rules/no-env-access.js +35 -0
  34. package/dist/rules/no-fixed-test-delay.d.ts +1 -0
  35. package/dist/rules/no-fixed-test-delay.js +130 -0
  36. package/dist/rules/no-forwarding-module.d.ts +1 -0
  37. package/dist/rules/no-forwarding-module.js +158 -0
  38. package/dist/rules/no-generic-file-suffix.d.ts +1 -0
  39. package/dist/rules/no-generic-file-suffix.js +74 -0
  40. package/dist/rules/no-pass-through-function.d.ts +1 -0
  41. package/dist/rules/no-pass-through-function.js +106 -0
  42. package/dist/rules/no-swallowed-errors.d.ts +1 -0
  43. package/dist/rules/no-swallowed-errors.js +168 -0
  44. package/dist/rules/no-test-timeout-overrides.d.ts +1 -0
  45. package/dist/rules/no-test-timeout-overrides.js +105 -0
  46. package/dist/rules/no-vacuous-test-assertions.d.ts +1 -0
  47. package/dist/rules/no-vacuous-test-assertions.js +71 -0
  48. package/dist/rules/server-action-guard.d.ts +1 -0
  49. package/dist/rules/server-action-guard.js +109 -0
  50. package/dist/rules/statement-spacing.d.ts +1 -0
  51. package/dist/rules/statement-spacing.js +29 -0
  52. package/dist/rules/type-guard-name.d.ts +1 -0
  53. package/dist/rules/type-guard-name.js +59 -0
  54. package/dist/shared/index.d.ts +26 -0
  55. package/dist/shared/index.js +260 -0
  56. package/dist/shared/rule-options.d.ts +2 -0
  57. package/dist/shared/rule-options.js +8 -0
  58. package/dist/vendor/anti-slop/effect/rules/no-manual-effect-error-tag.d.ts +1 -0
  59. package/dist/vendor/anti-slop/effect/rules/no-manual-effect-error-tag.js +35 -0
  60. package/dist/vendor/anti-slop/effect/rules/no-service-constructor-imports.d.ts +1 -0
  61. package/dist/vendor/anti-slop/effect/rules/no-service-constructor-imports.js +48 -0
  62. package/dist/vendor/anti-slop/effect/shared/tagged-values.d.ts +9 -0
  63. package/dist/vendor/anti-slop/effect/shared/tagged-values.js +65 -0
  64. package/dist/vendor/anti-slop/rules/no-array-filter-map.d.ts +1 -0
  65. package/dist/vendor/anti-slop/rules/no-array-filter-map.js +40 -0
  66. package/dist/vendor/anti-slop/rules/no-chained-type-assertions.d.ts +1 -0
  67. package/dist/vendor/anti-slop/rules/no-chained-type-assertions.js +58 -0
  68. package/dist/vendor/anti-slop/rules/no-conditional-empty-object-spread.d.ts +1 -0
  69. package/dist/vendor/anti-slop/rules/no-conditional-empty-object-spread.js +41 -0
  70. package/dist/vendor/anti-slop/rules/no-known-value-widening.d.ts +1 -0
  71. package/dist/vendor/anti-slop/rules/no-known-value-widening.js +333 -0
  72. package/dist/vendor/anti-slop/rules/no-module-mocking.d.ts +1 -0
  73. package/dist/vendor/anti-slop/rules/no-module-mocking.js +74 -0
  74. package/dist/vendor/anti-slop/rules/no-object-parameters.d.ts +1 -0
  75. package/dist/vendor/anti-slop/rules/no-object-parameters.js +59 -0
  76. package/dist/vendor/anti-slop/rules/no-reduce-accumulator-copy.d.ts +1 -0
  77. package/dist/vendor/anti-slop/rules/no-reduce-accumulator-copy.js +124 -0
  78. package/dist/vendor/anti-slop/rules/no-reflect-apply.d.ts +1 -0
  79. package/dist/vendor/anti-slop/rules/no-reflect-apply.js +26 -0
  80. package/dist/vendor/anti-slop/rules/no-reflect-get.d.ts +1 -0
  81. package/dist/vendor/anti-slop/rules/no-reflect-get.js +26 -0
  82. package/dist/vendor/anti-slop/rules/no-runtime-typeof.d.ts +1 -0
  83. package/dist/vendor/anti-slop/rules/no-runtime-typeof.js +63 -0
  84. package/dist/vendor/anti-slop/rules/no-shape-in-symbol-names.d.ts +1 -0
  85. package/dist/vendor/anti-slop/rules/no-shape-in-symbol-names.js +37 -0
  86. package/dist/vendor/anti-slop/rules/no-unknown-returns.d.ts +1 -0
  87. package/dist/vendor/anti-slop/rules/no-unknown-returns.js +61 -0
  88. package/dist/vendor/anti-slop/rules/no-unknown-type-aliases.d.ts +1 -0
  89. package/dist/vendor/anti-slop/rules/no-unknown-type-aliases.js +38 -0
  90. package/dist/vendor/anti-slop/rules/no-unsafe-dictionary-type.d.ts +1 -0
  91. package/dist/vendor/anti-slop/rules/no-unsafe-dictionary-type.js +143 -0
  92. package/dist/vendor/anti-slop/rules/no-widen-then-assert.d.ts +1 -0
  93. package/dist/vendor/anti-slop/rules/no-widen-then-assert.js +288 -0
  94. package/dist/vendor/anti-slop/shared/array-method.d.ts +10 -0
  95. package/dist/vendor/anti-slop/shared/array-method.js +95 -0
  96. package/dist/vendor/anti-slop/shared/dictionary-types.d.ts +23 -0
  97. package/dist/vendor/anti-slop/shared/dictionary-types.js +408 -0
  98. package/dist/vendor/anti-slop/shared/function-parameters.d.ts +6 -0
  99. package/dist/vendor/anti-slop/shared/function-parameters.js +41 -0
  100. package/dist/vendor/anti-slop/shared/lexical-type-parameters.d.ts +4 -0
  101. package/dist/vendor/anti-slop/shared/lexical-type-parameters.js +32 -0
  102. package/dist/vendor/anti-slop/shared/reflect-method.d.ts +3 -0
  103. package/dist/vendor/anti-slop/shared/reflect-method.js +24 -0
  104. package/dist/vendor/anti-slop/shared/scope.d.ts +3 -0
  105. package/dist/vendor/anti-slop/shared/scope.js +12 -0
  106. package/dist/vendor/anti-slop/shared/type-alias-resolution.d.ts +21 -0
  107. package/dist/vendor/anti-slop/shared/type-alias-resolution.js +147 -0
  108. package/dist/vendor/eslint-stylistic/padding-line-ast.d.ts +14 -0
  109. package/dist/vendor/eslint-stylistic/padding-line-ast.js +19 -0
  110. package/dist/vendor/eslint-stylistic/padding-line-between-statements.d.ts +91 -0
  111. package/dist/vendor/eslint-stylistic/padding-line-between-statements.js +493 -0
  112. package/package.json +89 -0
  113. package/src/vendor/anti-slop/LICENSE +21 -0
  114. package/src/vendor/eslint-stylistic/LICENSE +22 -0
  115. package/tsconfig.base.json +10 -0
@@ -0,0 +1,29 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import createPaddingLineRule from "../vendor/eslint-stylistic/padding-line-between-statements.js";
3
+ const multilineDeclaration = {
4
+ selector: ':matches(VariableDeclaration, ExportNamedDeclaration[declaration.type="VariableDeclaration"])',
5
+ lineMode: "multiline",
6
+ };
7
+ const typeAlias = {
8
+ selector: ':matches(TSTypeAliasDeclaration, ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"])',
9
+ };
10
+ const paddingRule = createPaddingLineRule([
11
+ { blankLine: "always", prev: "multiline-block-like", next: "*" },
12
+ { blankLine: "always", prev: "*", next: "multiline-block-like" },
13
+ { blankLine: "always", prev: "*", next: "return" },
14
+ { blankLine: "always", prev: "import", next: "*" },
15
+ { blankLine: "any", prev: "import", next: "import" },
16
+ { blankLine: "always", prev: "*", next: multilineDeclaration },
17
+ { blankLine: "always", prev: multilineDeclaration, next: "*" },
18
+ { blankLine: "any", prev: multilineDeclaration, next: typeAlias },
19
+ ]);
20
+ export const statementSpacing = defineRule({
21
+ ...paddingRule,
22
+ meta: {
23
+ ...paddingRule.meta,
24
+ docs: {
25
+ description: "Separate imports, multiline blocks, multiline declarations and returns from neighbouring statements.",
26
+ },
27
+ schema: [],
28
+ },
29
+ });
@@ -0,0 +1 @@
1
+ export declare const typeGuardName: import("@oxlint/plugins").Rule;
@@ -0,0 +1,59 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import { propertyName } from "../shared/index.js";
3
+ function predicateName(node) {
4
+ const owner = node.parent.parent;
5
+ if (!owner) {
6
+ return null;
7
+ }
8
+ if (owner.type === "TSMethodSignature") {
9
+ return owner.computed ? null : { node: owner.key, name: propertyName(owner) };
10
+ }
11
+ const parent = owner.type === "TSFunctionType" && owner.parent.type === "TSTypeAnnotation"
12
+ ? owner.parent.parent
13
+ : owner.parent;
14
+ if (parent?.type === "VariableDeclarator" && parent.id.type === "Identifier") {
15
+ return { node: parent.id, name: parent.id.name };
16
+ }
17
+ if (parent?.type === "Identifier" &&
18
+ parent.parent.type === "VariableDeclarator" &&
19
+ parent.parent.id === parent) {
20
+ return { node: parent, name: parent.name };
21
+ }
22
+ if (parent?.type === "Property" ||
23
+ parent?.type === "MethodDefinition" ||
24
+ parent?.type === "PropertyDefinition" ||
25
+ parent?.type === "TSPropertySignature") {
26
+ return parent.computed ? null : { node: parent.key, name: propertyName(parent) };
27
+ }
28
+ if ("id" in owner && owner.id?.type === "Identifier") {
29
+ return { node: owner.id, name: owner.id.name };
30
+ }
31
+ return null;
32
+ }
33
+ export const typeGuardName = defineRule({
34
+ meta: {
35
+ type: "suggestion",
36
+ docs: { description: "Name type predicates with an is or has prefix." },
37
+ schema: [],
38
+ messages: {
39
+ name: "Name the type predicate '{{name}}' with an is or has prefix, such as isValue or hasValue.",
40
+ },
41
+ },
42
+ create(context) {
43
+ const reported = new WeakSet();
44
+ return {
45
+ TSTypePredicate(node) {
46
+ if (node.asserts) {
47
+ return;
48
+ }
49
+ const named = predicateName(node);
50
+ if (named?.name &&
51
+ !reported.has(named.node) &&
52
+ !/^(?:is|has)(?:\p{Lu}|\p{N}|_)/u.test(named.name)) {
53
+ reported.add(named.node);
54
+ context.report({ node: named.node, messageId: "name", data: { name: named.name } });
55
+ }
56
+ },
57
+ };
58
+ },
59
+ });
@@ -0,0 +1,26 @@
1
+ import type { ESTree, SourceCode, Variable } from "@oxlint/plugins";
2
+ export type ApiReference = {
3
+ readonly module: string;
4
+ readonly name: string;
5
+ };
6
+ export type TestContext = {
7
+ readonly callback: FunctionNode;
8
+ readonly api: ApiReference;
9
+ };
10
+ type FunctionNode = ESTree.ArrowFunctionExpression | ESTree.Function;
11
+ type VisitorKeys = SourceCode["visitorKeys"];
12
+ export declare const testModules: Set<string>;
13
+ export declare const testFunctions: Set<string>;
14
+ export declare function isNode(value: unknown): value is ESTree.Node;
15
+ export declare function childNodes(node: ESTree.Node, visitorKeys: VisitorKeys): ESTree.Node[];
16
+ export declare function variableFor(sourceCode: SourceCode, node: ESTree.Node): Variable | null;
17
+ export declare function isGlobal(sourceCode: SourceCode, identifier: ESTree.Node | null | undefined, name: string): boolean;
18
+ export declare function propertyName(node: ESTree.Node): string | null;
19
+ export declare function isPlaywright(module: string): boolean;
20
+ export declare function apiReference(sourceCode: SourceCode, node: ESTree.Node, seen?: Set<Variable>): ApiReference | null;
21
+ export declare function testContext(sourceCode: SourceCode, node: ESTree.Node): TestContext | null;
22
+ export declare function projectPath(cwd: string, file: string): string;
23
+ export declare function matchesAny(path: string, patterns?: readonly string[]): boolean;
24
+ export declare function isGlobalValue(sourceCode: SourceCode, node: ESTree.Node | null | undefined, name: string): boolean;
25
+ export declare function isInjectionSeam(source: ESTree.Node): boolean;
26
+ export {};
@@ -0,0 +1,260 @@
1
+ import { matchesGlob, relative, sep } from "node:path";
2
+ export const testModules = new Set([
3
+ "vitest",
4
+ "@effect/vitest",
5
+ "@jest/globals",
6
+ "@playwright/test",
7
+ "playwright/test",
8
+ "node:test",
9
+ ]);
10
+ const defaultImports = new Map([
11
+ ["@playwright/test", "test"],
12
+ ["playwright/test", "test"],
13
+ ["node:test", "test"],
14
+ ["node:timers/promises", "*"],
15
+ ["timers/promises", "*"],
16
+ ]);
17
+ export const testFunctions = new Set([
18
+ "test",
19
+ "it",
20
+ "beforeEach",
21
+ "afterEach",
22
+ "beforeAll",
23
+ "afterAll",
24
+ ]);
25
+ const testModifiers = new Set([
26
+ "only",
27
+ "skip",
28
+ "todo",
29
+ "concurrent",
30
+ "sequential",
31
+ "serial",
32
+ "fails",
33
+ ]);
34
+ const testFactories = new Set(["each", "for", "runIf", "skipIf"]);
35
+ const effectModifiers = new Set(["effect", "live", "scoped"]);
36
+ export function isNode(value) {
37
+ return (typeof value === "object" && value !== null && "type" in value && typeof value.type === "string");
38
+ }
39
+ export function childNodes(node, visitorKeys) {
40
+ const children = [];
41
+ for (const key of visitorKeys[node.type] ?? []) {
42
+ const value = Object.getOwnPropertyDescriptor(node, key)?.value;
43
+ const items = Array.isArray(value) ? value : [value];
44
+ for (const item of items) {
45
+ if (isNode(item)) {
46
+ children.push(item);
47
+ }
48
+ }
49
+ }
50
+ return children;
51
+ }
52
+ export function variableFor(sourceCode, node) {
53
+ if (node.type !== "Identifier" &&
54
+ node.type !== "PrivateIdentifier" &&
55
+ node.type !== "JSXIdentifier") {
56
+ return null;
57
+ }
58
+ let scope = sourceCode.getScope(node);
59
+ while (scope !== null) {
60
+ const variable = scope.set.get(node.name);
61
+ if (variable !== undefined) {
62
+ return variable;
63
+ }
64
+ scope = scope.upper;
65
+ }
66
+ return null;
67
+ }
68
+ export function isGlobal(sourceCode, identifier, name) {
69
+ return (identifier?.type === "Identifier" &&
70
+ identifier.name === name &&
71
+ !variableFor(sourceCode, identifier)?.defs.length);
72
+ }
73
+ function memberKey(node) {
74
+ if ("property" in node) {
75
+ return node.property;
76
+ }
77
+ return "key" in node ? node.key : null;
78
+ }
79
+ export function propertyName(node) {
80
+ if ("computed" in node && node.computed) {
81
+ return null;
82
+ }
83
+ const key = memberKey(node);
84
+ if (key?.type === "Identifier" || key?.type === "PrivateIdentifier") {
85
+ return key.name;
86
+ }
87
+ return key?.type === "Literal" && typeof key.value === "string" ? key.value : null;
88
+ }
89
+ export function isPlaywright(module) {
90
+ return module === "@playwright/test" || module === "playwright/test";
91
+ }
92
+ export function apiReference(sourceCode, node, seen = new Set()) {
93
+ if (node.type === "Identifier") {
94
+ const variable = variableFor(sourceCode, node);
95
+ if (!variable?.defs.length) {
96
+ return testFunctions.has(node.name) || node.name === "expect"
97
+ ? { module: "global", name: node.name }
98
+ : null;
99
+ }
100
+ const [definition] = variable.defs;
101
+ if (seen.has(variable) || definition === undefined || variable.defs.length !== 1) {
102
+ return null;
103
+ }
104
+ seen.add(variable);
105
+ const specifier = definition.node;
106
+ const declaration = definition.parent;
107
+ if (definition.type === "ImportBinding" && declaration?.type === "ImportDeclaration") {
108
+ const module = declaration.source.value;
109
+ if (specifier.type === "ImportDefaultSpecifier" ||
110
+ (specifier.type === "ImportSpecifier" &&
111
+ specifier.imported.type === "Identifier" &&
112
+ specifier.imported.name === "default")) {
113
+ const name = defaultImports.get(module);
114
+ return name ? { module, name } : null;
115
+ }
116
+ if (specifier.type === "ImportNamespaceSpecifier") {
117
+ return { module, name: "*" };
118
+ }
119
+ if (specifier.type === "ImportSpecifier") {
120
+ return {
121
+ module,
122
+ name: specifier.imported.type === "Identifier"
123
+ ? specifier.imported.name
124
+ : specifier.imported.value,
125
+ };
126
+ }
127
+ return null;
128
+ }
129
+ if (definition.type === "Variable" &&
130
+ declaration?.type === "VariableDeclaration" &&
131
+ declaration.kind === "const" &&
132
+ specifier.type === "VariableDeclarator" &&
133
+ specifier.id.type === "Identifier" &&
134
+ specifier.init) {
135
+ return apiReference(sourceCode, specifier.init, seen);
136
+ }
137
+ return null;
138
+ }
139
+ if (node.type === "MemberExpression") {
140
+ const object = apiReference(sourceCode, node.object, seen);
141
+ const name = propertyName(node);
142
+ if (!object || !name) {
143
+ return null;
144
+ }
145
+ if (object.name === "*") {
146
+ return { module: object.module, name };
147
+ }
148
+ if (testFunctions.has(object.name) && (testModifiers.has(name) || testFactories.has(name))) {
149
+ return object;
150
+ }
151
+ if (object.module === "@effect/vitest" &&
152
+ ["test", "it"].includes(object.name) &&
153
+ effectModifiers.has(name)) {
154
+ return object;
155
+ }
156
+ if (object.name === "test" && testFunctions.has(name)) {
157
+ return { module: object.module, name };
158
+ }
159
+ return null;
160
+ }
161
+ if (node.type === "CallExpression" && node.callee.type === "MemberExpression") {
162
+ const name = propertyName(node.callee);
163
+ const object = apiReference(sourceCode, node.callee.object, seen);
164
+ if (object && name !== null && testFunctions.has(object.name) && testFactories.has(name)) {
165
+ return object;
166
+ }
167
+ if (object &&
168
+ ["test", "it"].includes(object.name) &&
169
+ (isPlaywright(object.module) || ["vitest", "@effect/vitest"].includes(object.module)) &&
170
+ name === "extend") {
171
+ return object;
172
+ }
173
+ }
174
+ if (node.type === "TaggedTemplateExpression" &&
175
+ node.tag.type === "MemberExpression" &&
176
+ propertyName(node.tag) === "each") {
177
+ return apiReference(sourceCode, node.tag.object, seen);
178
+ }
179
+ return null;
180
+ }
181
+ export function testContext(sourceCode, node) {
182
+ for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
183
+ if (ancestor.type !== "ArrowFunctionExpression" && ancestor.type !== "FunctionExpression") {
184
+ continue;
185
+ }
186
+ const call = ancestor.parent;
187
+ if (call.type !== "CallExpression" || !call.arguments.includes(ancestor)) {
188
+ continue;
189
+ }
190
+ const api = apiReference(sourceCode, call.callee);
191
+ if (api &&
192
+ testFunctions.has(api.name) &&
193
+ (testModules.has(api.module) || api.module === "global")) {
194
+ return { callback: ancestor, api };
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+ export function projectPath(cwd, file) {
200
+ return relative(cwd, file).split(sep).join("/");
201
+ }
202
+ export function matchesAny(path, patterns = []) {
203
+ return patterns.some((pattern) => matchesGlob(path, pattern));
204
+ }
205
+ const globalObjects = ["globalThis", "global", "window", "self"];
206
+ function isFunctionNode(node) {
207
+ return (node.type === "ArrowFunctionExpression" ||
208
+ node.type === "FunctionExpression" ||
209
+ node.type === "FunctionDeclaration");
210
+ }
211
+ export function isGlobalValue(sourceCode, node, name) {
212
+ if (isGlobal(sourceCode, node, name)) {
213
+ return true;
214
+ }
215
+ return (node?.type === "MemberExpression" &&
216
+ propertyName(node) === name &&
217
+ globalObjects.some((object) => isGlobal(sourceCode, node.object, object)));
218
+ }
219
+ function returns(callback, value) {
220
+ const body = callback.body;
221
+ if (body === value) {
222
+ return true;
223
+ }
224
+ if (body?.type !== "BlockStatement") {
225
+ return false;
226
+ }
227
+ const [statement] = body.body;
228
+ return (body.body.length === 1 && statement?.type === "ReturnStatement" && statement.argument === value);
229
+ }
230
+ function isProvider(callback, value) {
231
+ const holder = callback.parent;
232
+ if (holder.type === "AssignmentPattern") {
233
+ return holder.right === callback;
234
+ }
235
+ if (!returns(callback, value)) {
236
+ return false;
237
+ }
238
+ return ((holder.type === "Property" && holder.value === callback) ||
239
+ (holder.type === "LogicalExpression" &&
240
+ holder.right === callback &&
241
+ ["??", "||"].includes(holder.operator)));
242
+ }
243
+ export function isInjectionSeam(source) {
244
+ const parent = source.parent;
245
+ if (source.type !== "NewExpression" && source.type !== "CallExpression") {
246
+ return parent?.type === "CallExpression" && parent.callee === source
247
+ ? isInjectionSeam(parent)
248
+ : true;
249
+ }
250
+ let child = source;
251
+ for (let ancestor = parent; ancestor; child = ancestor, ancestor = ancestor.parent) {
252
+ if (ancestor.type === "AssignmentPattern") {
253
+ return ancestor.right === child;
254
+ }
255
+ if (isFunctionNode(ancestor)) {
256
+ return isProvider(ancestor, source);
257
+ }
258
+ }
259
+ return false;
260
+ }
@@ -0,0 +1,2 @@
1
+ import type { Options } from "@oxlint/plugins";
2
+ export declare function optionStrings(options: Readonly<Options>, key: string): string[];
@@ -0,0 +1,8 @@
1
+ export function optionStrings(options, key) {
2
+ const [option] = options;
3
+ if (typeof option !== "object" || option === null || Array.isArray(option)) {
4
+ return [];
5
+ }
6
+ const value = option[key];
7
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
8
+ }
@@ -0,0 +1 @@
1
+ export declare const noManualEffectErrorTag: import("@oxlint/plugins").Rule;
@@ -0,0 +1,35 @@
1
+ // Adapted from dmmulroy/anti-slop
2
+ import { defineRule } from "@oxlint/plugins";
3
+ import { isInsideBroadEffectHandler, isReasonTagMember, isTagMember, tagMemberFromComparison, } from "../shared/tagged-values.js";
4
+ export const noManualEffectErrorTag = defineRule({
5
+ meta: {
6
+ type: "problem",
7
+ docs: {
8
+ description: "Use Effect tagged error handlers instead of manually branching on `_tag` in a catch handler.",
9
+ },
10
+ messages: {
11
+ tag: "Use Effect.catchTag or Effect.catchTags instead of manually discriminating a tagged error in a broad Effect catch handler.",
12
+ reason: "Use Effect.catchReason or Effect.catchReasons instead of manually discriminating a tagged `reason` in a broad Effect catch handler.",
13
+ },
14
+ },
15
+ createOnce(context) {
16
+ return {
17
+ BinaryExpression(node) {
18
+ const tagMember = tagMemberFromComparison(node);
19
+ if (tagMember === void 0 || !isInsideBroadEffectHandler(node)) {
20
+ return;
21
+ }
22
+ context.report({ node, messageId: isReasonTagMember(tagMember) ? "reason" : "tag" });
23
+ },
24
+ SwitchStatement(node) {
25
+ if (!isTagMember(node.discriminant) || !isInsideBroadEffectHandler(node)) {
26
+ return;
27
+ }
28
+ context.report({
29
+ node,
30
+ messageId: isReasonTagMember(node.discriminant) ? "reason" : "tag",
31
+ });
32
+ },
33
+ };
34
+ },
35
+ });
@@ -0,0 +1 @@
1
+ export declare const noServiceConstructorImports: import("@oxlint/plugins").Rule;
@@ -0,0 +1,48 @@
1
+ // Adapted from dmmulroy/anti-slop
2
+ import { defineRule } from "@oxlint/plugins";
3
+ const SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
4
+ const TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
5
+ function isProjectLocalImport(source) {
6
+ return source.startsWith("./") || source.startsWith("../");
7
+ }
8
+ function getImportedName(specifier) {
9
+ if (specifier.imported.type === "Identifier") {
10
+ return specifier.imported.name;
11
+ }
12
+ return specifier.imported.value;
13
+ }
14
+ export const noServiceConstructorImports = defineRule({
15
+ meta: {
16
+ type: "problem",
17
+ docs: {
18
+ description: "Disallow project-local make<CapabilityName> imports outside test and spec files.",
19
+ },
20
+ messages: {
21
+ 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.',
22
+ },
23
+ },
24
+ create(context) {
25
+ const isTestFile = TEST_FILE.test(context.filename.replaceAll("\\", "/"));
26
+ return {
27
+ ImportDeclaration(node) {
28
+ if (isTestFile || !isProjectLocalImport(node.source.value)) {
29
+ return;
30
+ }
31
+ for (const specifier of node.specifiers) {
32
+ if (specifier.type !== "ImportSpecifier") {
33
+ continue;
34
+ }
35
+ const importedName = getImportedName(specifier);
36
+ if (!SERVICE_CONSTRUCTOR_NAME.test(importedName)) {
37
+ continue;
38
+ }
39
+ context.report({
40
+ node: specifier,
41
+ messageId: "serviceConstructorImport",
42
+ data: { name: importedName },
43
+ });
44
+ }
45
+ },
46
+ };
47
+ },
48
+ });
@@ -0,0 +1,9 @@
1
+ import type { ESTree } from "@oxlint/plugins";
2
+ declare const isStringLiteral: (node: ESTree.Node | null | undefined) => node is ESTree.StringLiteral;
3
+ declare const isTagMember: (node: ESTree.Node | null | undefined) => node is ESTree.MemberExpression;
4
+ declare const tagMemberFromComparison: (node: ESTree.BinaryExpression) => ESTree.MemberExpression | undefined;
5
+ declare const isInsideBroadEffectHandler: (node: ESTree.Node) => boolean;
6
+ declare const isReasonTagMember: (node: ESTree.MemberExpression) => boolean;
7
+ declare const propertyName: (property: ESTree.ObjectProperty) => string | undefined;
8
+ declare const isMatchPatternObject: (node: ESTree.Node) => boolean;
9
+ export { isInsideBroadEffectHandler, isMatchPatternObject, isReasonTagMember, isStringLiteral, isTagMember, propertyName, tagMemberFromComparison, };
@@ -0,0 +1,65 @@
1
+ const equalityOperators = new Set(["==", "===", "!=", "!=="]);
2
+ const broadEffectCatchMethods = new Set(["catch", "catchAll", "catchIf"]);
3
+ const isStringLiteral = (node) => node?.type === "Literal" && typeof node.value === "string";
4
+ const isTagMember = (node) => node?.type === "MemberExpression" &&
5
+ ((!node.computed && node.property.type === "Identifier" && node.property.name === "_tag") ||
6
+ (node.computed && isStringLiteral(node.property) && node.property.value === "_tag"));
7
+ const tagMemberFromComparison = (node) => {
8
+ if (!equalityOperators.has(node.operator)) {
9
+ return void 0;
10
+ }
11
+ if (isTagMember(node.left) && isStringLiteral(node.right)) {
12
+ return node.left;
13
+ }
14
+ if (isTagMember(node.right) && isStringLiteral(node.left)) {
15
+ return node.right;
16
+ }
17
+ return void 0;
18
+ };
19
+ const isBroadEffectCatchCall = (node) => node?.type === "CallExpression" &&
20
+ node.callee.type === "MemberExpression" &&
21
+ node.callee.object.type === "Identifier" &&
22
+ node.callee.object.name === "Effect" &&
23
+ !node.callee.computed &&
24
+ node.callee.property.type === "Identifier" &&
25
+ broadEffectCatchMethods.has(node.callee.property.name);
26
+ const isInsideBroadEffectHandler = (node) => {
27
+ let current = node.parent;
28
+ while (current !== null) {
29
+ if (current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") {
30
+ return isBroadEffectCatchCall(current.parent) && current.parent.arguments.includes(current);
31
+ }
32
+ current = current.parent;
33
+ }
34
+ return false;
35
+ };
36
+ const isReasonTagMember = (node) => node.object.type === "MemberExpression" &&
37
+ ((!node.object.computed &&
38
+ node.object.property.type === "Identifier" &&
39
+ node.object.property.name === "reason") ||
40
+ (node.object.computed &&
41
+ isStringLiteral(node.object.property) &&
42
+ node.object.property.value === "reason"));
43
+ const propertyName = (property) => {
44
+ if (!property.computed && property.key.type === "Identifier") {
45
+ return property.key.name;
46
+ }
47
+ if (property.key.type === "Literal" && typeof property.key.value === "string") {
48
+ return property.key.value;
49
+ }
50
+ return void 0;
51
+ };
52
+ const isMatchPatternObject = (node) => {
53
+ const call = node.parent;
54
+ if (call?.type !== "CallExpression" || !call.arguments.some((argument) => argument === node)) {
55
+ return false;
56
+ }
57
+ const callee = call.callee;
58
+ return (callee.type === "MemberExpression" &&
59
+ callee.object.type === "Identifier" &&
60
+ callee.object.name === "Match" &&
61
+ !callee.computed &&
62
+ callee.property.type === "Identifier" &&
63
+ (callee.property.name === "when" || callee.property.name === "not"));
64
+ };
65
+ export { isInsideBroadEffectHandler, isMatchPatternObject, isReasonTagMember, isStringLiteral, isTagMember, propertyName, tagMemberFromComparison, };
@@ -0,0 +1 @@
1
+ export declare const noArrayFilterMap: import("@oxlint/plugins").Rule;
@@ -0,0 +1,40 @@
1
+ // Adapted from dmmulroy/anti-slop
2
+ import { defineRule } from "@oxlint/plugins";
3
+ import { arrayMethodTarget, isKnownArrayExpression, unwrapArrayExpression, } from "../shared/array-method.js";
4
+ export const noArrayFilterMap = defineRule({
5
+ meta: {
6
+ type: "suggestion",
7
+ docs: {
8
+ description: "Disallow adjacent array filter/map passes in favor of lazy iterator helpers or a single transformation.",
9
+ },
10
+ messages: {
11
+ 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.",
12
+ },
13
+ },
14
+ createOnce(context) {
15
+ return {
16
+ CallExpression(node) {
17
+ const outer = arrayMethodTarget(node.callee);
18
+ if (outer === null || (outer.name !== "map" && outer.name !== "filter")) {
19
+ return;
20
+ }
21
+ const innerCall = unwrapArrayExpression(outer.object);
22
+ if (innerCall.type !== "CallExpression") {
23
+ return;
24
+ }
25
+ const inner = arrayMethodTarget(innerCall.callee);
26
+ if (inner === null || inner.name !== (outer.name === "map" ? "filter" : "map")) {
27
+ return;
28
+ }
29
+ if (!isKnownArrayExpression(context.sourceCode, inner.object)) {
30
+ return;
31
+ }
32
+ context.report({
33
+ node,
34
+ messageId: "arrayFilterMap",
35
+ data: { first: inner.name, second: outer.name },
36
+ });
37
+ },
38
+ };
39
+ },
40
+ });
@@ -0,0 +1 @@
1
+ export declare const noChainedTypeAssertions: import("@oxlint/plugins").Rule;
@@ -0,0 +1,58 @@
1
+ // Adapted from dmmulroy/anti-slop
2
+ import { defineRule } from "@oxlint/plugins";
3
+ function isTypeAssertionExpression(node) {
4
+ return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
5
+ }
6
+ function unwrapParenthesizedExpression(expression) {
7
+ let current = expression;
8
+ while (current.type === "ParenthesizedExpression") {
9
+ current = current.expression;
10
+ }
11
+ return current;
12
+ }
13
+ function isConstAssertion(node) {
14
+ const { typeAnnotation } = node;
15
+ return (typeAnnotation.type === "TSTypeReference" &&
16
+ typeAnnotation.typeName.type === "Identifier" &&
17
+ typeAnnotation.typeName.name === "const");
18
+ }
19
+ function isOutermostAssertionInChain(node) {
20
+ let current = node;
21
+ let parent = node.parent;
22
+ while (parent.type === "ParenthesizedExpression" && parent.expression === current) {
23
+ current = parent;
24
+ parent = parent.parent;
25
+ }
26
+ return !isTypeAssertionExpression(parent) || parent.expression !== current;
27
+ }
28
+ function isForbiddenAssertionChain(node) {
29
+ let assertionCount = 0;
30
+ let hasNonConstAssertion = false;
31
+ let current = node;
32
+ while (isTypeAssertionExpression(current)) {
33
+ assertionCount += 1;
34
+ hasNonConstAssertion ||= !isConstAssertion(current);
35
+ current = unwrapParenthesizedExpression(current.expression);
36
+ }
37
+ return assertionCount > 1 && hasNonConstAssertion;
38
+ }
39
+ export const noChainedTypeAssertions = 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
+ }
54
+ context.report({ node, messageId: "chained" });
55
+ };
56
+ return { TSAsExpression: checkTypeAssertion, TSTypeAssertion: checkTypeAssertion };
57
+ },
58
+ });
@@ -0,0 +1 @@
1
+ export declare const noConditionalEmptyObjectSpread: import("@oxlint/plugins").Rule;