@mkrz/oxlint-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.
@@ -0,0 +1,1957 @@
1
+ import { defineRule, eslintCompatPlugin } from "@oxlint/plugins";
2
+ import ignore from "ignore";
3
+ //#region src/plugin/rules/bindings.ts
4
+ function findVariable(scope, name) {
5
+ return scope?.set.get(name) ?? (scope?.upper ? findVariable(scope.upper, name) : void 0);
6
+ }
7
+ /** Resolve the declaration, including imports below their uses and shadowing. */
8
+ function importedBinding(sourceCode, identifier) {
9
+ const definition = findVariable(sourceCode.getScope(identifier), identifier.name)?.defs.find((entry) => entry.type === "ImportBinding");
10
+ if (definition?.parent?.type !== "ImportDeclaration") return null;
11
+ const specifier = definition.node;
12
+ return {
13
+ source: definition.parent.source.value,
14
+ name: specifier.type === "ImportSpecifier" ? specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value : null
15
+ };
16
+ }
17
+ /** An unresolved name or an environment global has no local declaration. */
18
+ function isGlobalIdentifier(sourceCode, identifier) {
19
+ return (findVariable(sourceCode.getScope(identifier), identifier.name)?.defs.length ?? 0) === 0;
20
+ }
21
+ /** Resolve a static member name, including bracket strings and templates. */
22
+ function memberName(node) {
23
+ if (!node.computed) return node.property.type === "Identifier" ? node.property.name : null;
24
+ return staticString(node.property);
25
+ }
26
+ function staticString(node) {
27
+ if (node.type === "Literal" && typeof node.value === "string") return node.value;
28
+ return node.type === "TemplateLiteral" && node.expressions.length === 0 ? node.quasis[0]?.value.cooked ?? null : null;
29
+ }
30
+ /** Follow direct imports and namespace/default-import members at the call site. */
31
+ function importedCall(sourceCode, callee) {
32
+ if (callee.type === "Identifier") return importedBinding(sourceCode, callee);
33
+ if (callee.type !== "MemberExpression" || callee.object.type !== "Identifier") return null;
34
+ const binding = importedBinding(sourceCode, callee.object);
35
+ if (binding === null || binding.name !== null && binding.name !== "default") return null;
36
+ return {
37
+ source: binding.source,
38
+ name: memberName(callee)
39
+ };
40
+ }
41
+ //#endregion
42
+ //#region src/plugin/rules/no-app-requires.ts
43
+ function isRequire(sourceCode, callee) {
44
+ if (callee.type === "Identifier") return callee.name === "require" && isGlobalIdentifier(sourceCode, callee);
45
+ return callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "module" && isGlobalIdentifier(sourceCode, callee.object) && memberName(callee) === "require";
46
+ }
47
+ function moduleName(argument) {
48
+ if (argument?.type === "Literal" && typeof argument.value === "string") return argument.value;
49
+ return argument?.type === "TemplateLiteral" && argument.expressions.length === 0 ? argument.quasis[0]?.value.cooked ?? null : null;
50
+ }
51
+ function isString(value) {
52
+ return typeof value === "string";
53
+ }
54
+ function stringPatterns(value) {
55
+ return Array.isArray(value) && value.every(isString);
56
+ }
57
+ /** CommonJS counterpart to the monorepo's native no-restricted-imports rule. */
58
+ const noAppRequiresRule = defineRule({
59
+ meta: {
60
+ type: "problem",
61
+ docs: {
62
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-app-requires",
63
+ description: "Disallow requiring application packages from shared packages."
64
+ },
65
+ schema: [{
66
+ type: "array",
67
+ items: { type: "string" }
68
+ }],
69
+ messages: { applicationImport: "Move shared code into a package and import it from there instead." }
70
+ },
71
+ createOnce(context) {
72
+ const state = { matcher: ignore({ ignorecase: true }) };
73
+ return {
74
+ before() {
75
+ const patterns = context.options[0];
76
+ if (patterns !== void 0 && !stringPatterns(patterns)) throw new Error("mkrz/no-app-requires expects an array of gitignore patterns.");
77
+ state.matcher = ignore({ ignorecase: true }).add(patterns ?? []);
78
+ },
79
+ CallExpression(node) {
80
+ if (!isRequire(context.sourceCode, node.callee)) return;
81
+ const name = moduleName(node.arguments[0]);
82
+ if (name !== null && ignore.isPathValid(name) && state.matcher.ignores(name)) context.report({
83
+ node,
84
+ messageId: "applicationImport"
85
+ });
86
+ }
87
+ };
88
+ }
89
+ });
90
+ //#endregion
91
+ //#region src/plugin/rules/no-let.ts
92
+ /** Require immutable variable bindings. */
93
+ const noLetRule = defineRule({
94
+ meta: {
95
+ type: "suggestion",
96
+ docs: {
97
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-let",
98
+ description: "Disallow variables declared with let."
99
+ },
100
+ messages: { mutableBinding: "Use `const` and derive a new value instead of reassigning a binding." }
101
+ },
102
+ createOnce(context) {
103
+ return { VariableDeclaration(node) {
104
+ if (node.kind === "let" && node.declare !== true) context.report({
105
+ node,
106
+ messageId: "mutableBinding"
107
+ });
108
+ } };
109
+ }
110
+ });
111
+ //#endregion
112
+ //#region src/plugin/rules/oxlint-directives.ts
113
+ const directivePattern = /^\s*(?:oxlint|eslint)-(?:disable(?:-(?:next-)?line)?|enable)(?=\s|$)/u;
114
+ const allowedPackageDirective = "oxlint-disable-next-line";
115
+ const ruleNamePattern = /^(?:@?[\w-]+\/)?[\w-]+$/u;
116
+ const reasonSeparatorPattern = /\s-{2,}\s/u;
117
+ /** Oxlint also honors eslint-flavored directives, so both count. */
118
+ function isOxlintDirective(value) {
119
+ return directivePattern.test(value);
120
+ }
121
+ function isAllowedPackageDirective(value) {
122
+ const directive = value.trim();
123
+ if (!directive.startsWith(allowedPackageDirective)) return false;
124
+ const remainder = directive.slice(24);
125
+ if (remainder !== "" && !/^\s/u.test(remainder)) return false;
126
+ const separator = reasonSeparatorPattern.exec(remainder);
127
+ if (separator === null) return false;
128
+ const rule = remainder.slice(0, separator.index).trim();
129
+ return remainder.slice(separator.index + separator[0].length).trim().length > 0 && ruleNamePattern.test(rule);
130
+ }
131
+ //#endregion
132
+ //#region src/plugin/rules/no-oxlint-disable.ts
133
+ /**
134
+ * Applications must fix violations rather than suppress them.
135
+ *
136
+ * Caveat: a file-level `oxlint-disable mkrz/no-oxlint-disable` suppresses
137
+ * this rule's own report and counts as "used", so
138
+ * reportUnusedDisableDirectives stays silent too. The bare `oxlint-disable`
139
+ * form is still caught by `unicorn/no-abusive-eslint-disable`. Lint rules
140
+ * cannot exempt themselves from suppression; catching the named form needs an
141
+ * out-of-band check (for example a CI grep for `oxlint-disable mkrz/`).
142
+ */
143
+ const noOxlintDisableRule = defineRule({
144
+ meta: {
145
+ type: "problem",
146
+ docs: {
147
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-oxlint-disable",
148
+ description: "Disallow Oxlint and ESLint disable directives."
149
+ },
150
+ messages: { forbiddenDirective: "Remove this Oxlint directive and fix the reported rule violation instead." }
151
+ },
152
+ createOnce(context) {
153
+ return { Program() {
154
+ for (const comment of context.sourceCode.getAllComments()) if (isOxlintDirective(comment.value)) context.report({
155
+ node: comment,
156
+ messageId: "forbiddenDirective"
157
+ });
158
+ } };
159
+ }
160
+ });
161
+ //#endregion
162
+ //#region src/plugin/rules/hook-calls.ts
163
+ /**
164
+ * Matches the TanStack query hooks and the wrapper convention built on them,
165
+ * such as `useUserQuery`. Names like `useQueryClient` do not match because
166
+ * they do not end in Query, Queries, or Mutation.
167
+ */
168
+ const queryHookPattern = /^use(?:[A-Z].*)?(?:Query|Queries|Mutation)$/u;
169
+ function isQueryHook(name) {
170
+ return queryHookPattern.test(name);
171
+ }
172
+ function calleeName(callee) {
173
+ if (callee.type === "Identifier") return callee.name;
174
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") return callee.property.name;
175
+ return null;
176
+ }
177
+ /** Casts and parentheses do not change what a call returns, so they must not hide it. */
178
+ function unwrapCast(expression) {
179
+ return expression.type === "ParenthesizedExpression" || expression.type === "TSAsExpression" || expression.type === "TSNonNullExpression" || expression.type === "TSSatisfiesExpression" ? unwrapCast(expression.expression) : expression;
180
+ }
181
+ function initializedByHook(node, predicate) {
182
+ if (node.id.type !== "ObjectPattern" || node.init == null) return false;
183
+ const init = unwrapCast(node.init);
184
+ return init.type === "CallExpression" && predicate(calleeName(init.callee) ?? "");
185
+ }
186
+ /**
187
+ * A zustand hook bound to one store, named after it: `useCartStore`. The bare
188
+ * `useStore` is not one; it takes the store as an argument and is recognised
189
+ * by its import from `zustand` instead. React's own subscription hook is not
190
+ * an application state store.
191
+ */
192
+ function isBoundStoreHook(name) {
193
+ return name !== "useSyncExternalStore" && /^use[A-Z]\w*Store$/u.test(name);
194
+ }
195
+ //#endregion
196
+ //#region src/plugin/rules/no-query-result-destructuring.ts
197
+ /** Keep query fields qualified by their result object as a project convention. */
198
+ const noQueryResultDestructuringRule = defineRule({
199
+ meta: {
200
+ type: "problem",
201
+ docs: {
202
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-query-result-destructuring",
203
+ description: "Disallow object destructuring of query results."
204
+ },
205
+ messages: { destructuredQuery: "Keep the query result as one variable so fields stay qualified, for example `query.data` and `query.isSuccess`." }
206
+ },
207
+ createOnce(context) {
208
+ return { VariableDeclarator(node) {
209
+ if (initializedByHook(node, isQueryHook)) context.report({
210
+ node: node.id,
211
+ messageId: "destructuredQuery"
212
+ });
213
+ } };
214
+ }
215
+ });
216
+ //#endregion
217
+ //#region src/plugin/rules/no-restricted-react-hooks.ts
218
+ const restrictedHooks = /* @__PURE__ */ new Set([
219
+ "useCallback",
220
+ "useEffect",
221
+ "useInsertionEffect",
222
+ "useLayoutEffect",
223
+ "useMemo",
224
+ "useReducer",
225
+ "useSyncExternalStore"
226
+ ]);
227
+ /** Reject React APIs that fight render-time derivation and the compiler. */
228
+ const noRestrictedReactHooksRule = defineRule({
229
+ meta: {
230
+ type: "problem",
231
+ docs: {
232
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-restricted-react-hooks",
233
+ description: "Disallow restricted React hooks."
234
+ },
235
+ messages: { restrictedHook: "Replace `{{hook}}` with an event handler, render-time derivation, useState, or a selector-based store; let React Compiler handle memoization." }
236
+ },
237
+ createOnce(context) {
238
+ return { CallExpression(node) {
239
+ const binding = importedCall(context.sourceCode, node.callee);
240
+ if (binding?.source === "react" && binding.name !== null && restrictedHooks.has(binding.name)) context.report({
241
+ node,
242
+ messageId: "restrictedHook",
243
+ data: { hook: binding.name }
244
+ });
245
+ } };
246
+ }
247
+ });
248
+ //#endregion
249
+ //#region src/plugin/rules/no-type-assertion.ts
250
+ function isConstAssertion$1(node) {
251
+ return node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const";
252
+ }
253
+ /**
254
+ * The first ancestor that is not parentheses or `!` around `node`. Both wrap
255
+ * a single expression, so climbing them keeps the operand relationship.
256
+ */
257
+ function outerNode(node) {
258
+ const { parent } = node;
259
+ return parent.type === "ParenthesizedExpression" || parent.type === "TSNonNullExpression" ? outerNode(parent) : parent;
260
+ }
261
+ /** True when this assertion is the operand of another assertion, which reports the whole chain. */
262
+ function isInnerAssertion(node) {
263
+ const outer = outerNode(node);
264
+ return outer.type === "TSAsExpression" || outer.type === "TSTypeAssertion";
265
+ }
266
+ /** Reject type assertions; unsafe data should be validated instead of asserted. */
267
+ const noTypeAssertionRule = defineRule({
268
+ meta: {
269
+ type: "problem",
270
+ docs: {
271
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-type-assertion",
272
+ description: "Disallow TypeScript type assertions except const assertions."
273
+ },
274
+ messages: { noTypeAssertion: "Avoid type assertions. Validate the value with a runtime schema library such as zod, arktype, or valibot, or narrow it with a type guard, so the type is proven instead of asserted." }
275
+ },
276
+ createOnce(context) {
277
+ const checkAssertion = (node) => {
278
+ if (isConstAssertion$1(node) || isInnerAssertion(node)) return;
279
+ context.report({
280
+ node,
281
+ messageId: "noTypeAssertion"
282
+ });
283
+ };
284
+ return {
285
+ TSAsExpression: checkAssertion,
286
+ TSTypeAssertion: checkAssertion
287
+ };
288
+ }
289
+ });
290
+ //#endregion
291
+ //#region src/plugin/rules/package-disable-policy.ts
292
+ /**
293
+ * Packages may suppress one named violation when they document why.
294
+ *
295
+ * Caveat: a blanket disable directive suppresses this rule's own report; see
296
+ * no-oxlint-disable for details.
297
+ */
298
+ const packageDisablePolicyRule = defineRule({
299
+ meta: {
300
+ type: "problem",
301
+ docs: {
302
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#package-disable-policy",
303
+ description: "Require narrow, explained Oxlint directives."
304
+ },
305
+ messages: { invalidDirective: "Use `oxlint-disable-next-line <rule> -- <reason>` for one named rule instead." }
306
+ },
307
+ createOnce(context) {
308
+ return { Program() {
309
+ for (const comment of context.sourceCode.getAllComments()) if (isOxlintDirective(comment.value) && !isAllowedPackageDirective(comment.value)) context.report({
310
+ node: comment,
311
+ messageId: "invalidDirective"
312
+ });
313
+ } };
314
+ }
315
+ });
316
+ //#endregion
317
+ //#region src/plugin/rules/require-store-selector.ts
318
+ /** Modules that export zustand's bare `useStore(store, selector)`. */
319
+ const zustandModules = /* @__PURE__ */ new Set(["zustand", "zustand/react"]);
320
+ function returnedExpression(body) {
321
+ if (body === null) return null;
322
+ if (body.type !== "BlockStatement") return body;
323
+ const [statement] = body.body;
324
+ return body.body.length === 1 && statement?.type === "ReturnStatement" ? statement.argument : null;
325
+ }
326
+ /** `(state) => state` subscribes to the whole store, the same as no selector. */
327
+ function isIdentitySelector(argument) {
328
+ if (argument.type !== "ArrowFunctionExpression" && argument.type !== "FunctionExpression" || argument.params.length !== 1) return false;
329
+ const [parameter] = argument.params;
330
+ const returned = returnedExpression(argument.body);
331
+ return parameter?.type === "Identifier" && returned?.type === "Identifier" && returned.name === parameter.name;
332
+ }
333
+ function isMissingSelector(sourceCode, selector) {
334
+ return selector === void 0 || selector.type === "Identifier" && selector.name === "undefined" && isGlobalIdentifier(sourceCode, selector) || selector.type === "UnaryExpression" && selector.operator === "void";
335
+ }
336
+ /**
337
+ * Require zustand subscriptions to select only the state they consume.
338
+ *
339
+ * Two call shapes are checked: a bound hook named after its store, such as
340
+ * `useCartStore(selector)`, and the bare `useStore(store, selector)` when it
341
+ * is imported from zustand. A `useStore` from any other module is a different
342
+ * library's hook and is left alone.
343
+ */
344
+ const requireStoreSelectorRule = defineRule({
345
+ meta: {
346
+ type: "problem",
347
+ docs: {
348
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#require-store-selector",
349
+ description: "Require a selector argument for zustand hooks."
350
+ },
351
+ messages: {
352
+ missingSelector: "Pass a selector such as `useFeatureStore((state) => state.value)` instead.",
353
+ identitySelector: "Select one value such as `(state) => state.value`; returning the whole state subscribes to every change."
354
+ }
355
+ },
356
+ createOnce(context) {
357
+ return { CallExpression(node) {
358
+ const binding = importedCall(context.sourceCode, node.callee);
359
+ const bare = binding?.name === "useStore" && zustandModules.has(binding.source);
360
+ const name = calleeName(node.callee);
361
+ if (!bare && (name === null || !isBoundStoreHook(name))) return;
362
+ const selector = node.arguments[bare ? 1 : 0];
363
+ if (isMissingSelector(context.sourceCode, selector)) context.report({
364
+ node,
365
+ messageId: "missingSelector"
366
+ });
367
+ else if (selector !== void 0 && isIdentitySelector(selector)) context.report({
368
+ node: selector,
369
+ messageId: "identitySelector"
370
+ });
371
+ } };
372
+ }
373
+ });
374
+ //#endregion
375
+ //#region src/plugin/rules/vendor/anti-slop/no-chained-type-assertions.ts
376
+ function isTypeAssertionExpression(node) {
377
+ return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
378
+ }
379
+ function unwrapParenthesizedExpression(expression) {
380
+ let current = expression;
381
+ while (current.type === "ParenthesizedExpression" || current.type === "TSNonNullExpression") current = current.expression;
382
+ return current;
383
+ }
384
+ function isConstAssertion(node) {
385
+ const { typeAnnotation } = node;
386
+ return typeAnnotation.type === "TSTypeReference" && typeAnnotation.typeName.type === "Identifier" && typeAnnotation.typeName.name === "const";
387
+ }
388
+ function isOutermostAssertionInChain(node) {
389
+ let current = node;
390
+ let parent = node.parent;
391
+ while ((parent.type === "ParenthesizedExpression" || parent.type === "TSNonNullExpression") && parent.expression === current) {
392
+ current = parent;
393
+ parent = parent.parent;
394
+ }
395
+ return !isTypeAssertionExpression(parent) || parent.expression !== current;
396
+ }
397
+ function isForbiddenAssertionChain(node) {
398
+ let assertionCount = 0;
399
+ let hasNonConstAssertion = false;
400
+ let current = node;
401
+ while (isTypeAssertionExpression(current)) {
402
+ assertionCount += 1;
403
+ hasNonConstAssertion ||= !isConstAssertion(current);
404
+ current = unwrapParenthesizedExpression(current.expression);
405
+ }
406
+ return assertionCount > 1 && hasNonConstAssertion;
407
+ }
408
+ /** Disallow nested TypeScript type assertions, while permitting chains made only of const assertions. */
409
+ const noChainedTypeAssertionsRule = defineRule({
410
+ meta: {
411
+ type: "problem",
412
+ docs: {
413
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-chained-type-assertions",
414
+ description: "Disallow chained TypeScript as and angle-bracket assertions, including parenthesized chains."
415
+ },
416
+ messages: { chained: "This assertion chain discards type evidence. Keep the original precise type, or parse untrusted input at its boundary before narrowing it." }
417
+ },
418
+ createOnce(context) {
419
+ const checkTypeAssertion = (node) => {
420
+ if (!isOutermostAssertionInChain(node) || !isForbiddenAssertionChain(node)) return;
421
+ context.report({
422
+ node,
423
+ messageId: "chained"
424
+ });
425
+ };
426
+ return {
427
+ TSAsExpression: checkTypeAssertion,
428
+ TSTypeAssertion: checkTypeAssertion
429
+ };
430
+ }
431
+ });
432
+ //#endregion
433
+ //#region src/plugin/rules/vendor/anti-slop/dictionary-types.ts
434
+ const BUILT_INS = /* @__PURE__ */ new Set([
435
+ "Record",
436
+ "Readonly",
437
+ "Partial",
438
+ "Required",
439
+ "Pick",
440
+ "Omit",
441
+ "PropertyKey",
442
+ "NonNullable",
443
+ "Promise",
444
+ "PromiseLike"
445
+ ]);
446
+ const TRANSPARENT_WRAPPERS = /* @__PURE__ */ new Set([
447
+ "Readonly",
448
+ "Partial",
449
+ "Required",
450
+ "NonNullable"
451
+ ]);
452
+ /** Marks a name bound by an enclosing type parameter; it never resolves to a module alias. */
453
+ const OPAQUE = Symbol("opaque type parameter");
454
+ /** Substitutions that hide module aliases shadowed by lexical type parameters. */
455
+ function shadowedSubstitutions(shadowed) {
456
+ return new Map([...shadowed].map((name) => [name, OPAQUE]));
457
+ }
458
+ function substitutionFor(substitutions, name) {
459
+ const substitution = substitutions.get(name);
460
+ if (substitution === void 0) return;
461
+ if (substitution === OPAQUE || isUnappliedReferenceTo(substitution, name)) return "opaque";
462
+ return substitution;
463
+ }
464
+ function declaredStatement(statement) {
465
+ return statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration ?? null : statement;
466
+ }
467
+ function createTypeEnvironment(program) {
468
+ const aliases = /* @__PURE__ */ new Map();
469
+ const interfaces = /* @__PURE__ */ new Map();
470
+ const shadowedBuiltIns = /* @__PURE__ */ new Set();
471
+ for (const statement of program.body) {
472
+ const declaration = declaredStatement(statement);
473
+ if (declaration?.type === "ImportDeclaration") {
474
+ for (const specifier of declaration.specifiers) if (BUILT_INS.has(specifier.local.name)) shadowedBuiltIns.add(specifier.local.name);
475
+ continue;
476
+ }
477
+ if (declaration?.type === "TSTypeAliasDeclaration") {
478
+ if (aliases.get(declaration.id.name) === void 0) aliases.set(declaration.id.name, declaration);
479
+ else shadowedBuiltIns.add(declaration.id.name);
480
+ if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
481
+ continue;
482
+ }
483
+ if (declaration?.type === "TSInterfaceDeclaration") {
484
+ const declarations = interfaces.get(declaration.id.name) ?? [];
485
+ declarations.push(declaration);
486
+ interfaces.set(declaration.id.name, declarations);
487
+ if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
488
+ continue;
489
+ }
490
+ if (declaration?.type === "TSEnumDeclaration") {
491
+ if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
492
+ continue;
493
+ }
494
+ if ((declaration?.type === "ClassDeclaration" || declaration?.type === "FunctionDeclaration") && declaration.id !== null) {
495
+ if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
496
+ }
497
+ }
498
+ return {
499
+ aliases,
500
+ interfaces,
501
+ shadowedBuiltIns
502
+ };
503
+ }
504
+ function typeReferenceName$2(type) {
505
+ return type.typeName.type === "Identifier" ? type.typeName.name : null;
506
+ }
507
+ function isBuiltIn(name, environment) {
508
+ return BUILT_INS.has(name) && !environment.shadowedBuiltIns.has(name);
509
+ }
510
+ function isUnappliedReferenceTo(type, name) {
511
+ const unwrapped = unwrapTransparentType(type);
512
+ return unwrapped.type === "TSTypeReference" && typeReferenceName$2(unwrapped) === name && (unwrapped.typeArguments === null || unwrapped.typeArguments === void 0 || unwrapped.typeArguments.params.length === 0);
513
+ }
514
+ function unwrapTransparentType(type) {
515
+ let current = type;
516
+ while (current.type === "TSParenthesizedType" || current.type === "TSTypeOperator" && current.operator === "readonly") current = current.typeAnnotation;
517
+ return current;
518
+ }
519
+ function isNeverType(type) {
520
+ return unwrapTransparentType(type).type === "TSNeverKeyword";
521
+ }
522
+ function isEffectivelyEmptyMember(member) {
523
+ return member.type === "TSPropertySignature" && member.optional && member.typeAnnotation !== null && member.typeAnnotation !== void 0 && isNeverType(member.typeAnnotation.typeAnnotation);
524
+ }
525
+ function isEffectivelyEmptyTypeLiteral(type) {
526
+ return type.members.length === 0 || type.members.every(isEffectivelyEmptyMember);
527
+ }
528
+ function isEffectivelyEmptyInterface(declarations) {
529
+ if (declarations.length !== 1) return false;
530
+ const [type] = declarations;
531
+ return type?.extends.length === 0 && (type.body.body.length === 0 || type.body.body.every(isEffectivelyEmptyMember));
532
+ }
533
+ function resolvedSubstitutionArgument(type, base, resolving = /* @__PURE__ */ new Set()) {
534
+ const unwrapped = unwrapTransparentType(type);
535
+ if (unwrapped.type !== "TSTypeReference") return type;
536
+ const name = typeReferenceName$2(unwrapped);
537
+ if (name === null || resolving.has(name)) return type;
538
+ const substitution = substitutionFor(base, name);
539
+ if (substitution === void 0 || substitution === "opaque") return type;
540
+ const nextResolving = new Set(resolving);
541
+ nextResolving.add(name);
542
+ return resolvedSubstitutionArgument(substitution, base, nextResolving);
543
+ }
544
+ function aliasSubstitution(alias, type, base) {
545
+ const parameters = alias.typeParameters?.params ?? [];
546
+ const typeArguments = type.typeArguments?.params ?? [];
547
+ const next = new Map(base);
548
+ for (const [index, parameter] of parameters.entries()) {
549
+ const argument = typeArguments[index] ?? parameter.default;
550
+ if (argument === null || argument === void 0) return null;
551
+ next.set(parameter.name.name, resolvedSubstitutionArgument(argument, next));
552
+ }
553
+ return next;
554
+ }
555
+ function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
556
+ const unwrapped = unwrapTransparentType(type);
557
+ if (unwrapped.type === "TSUnknownKeyword") return "unknown";
558
+ if (unwrapped.type === "TSAnyKeyword") return "any";
559
+ if (unwrapped.type === "TSObjectKeyword") return "object";
560
+ if (unwrapped.type === "TSTypeLiteral" && isEffectivelyEmptyTypeLiteral(unwrapped)) return "empty-object";
561
+ if (unwrapped.type === "TSUnionType") return unwrapped.types.some((member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases) !== null) ? "union" : null;
562
+ if (unwrapped.type === "TSIntersectionType") {
563
+ const unsafeMembers = unwrapped.types.map((member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases));
564
+ if (unsafeMembers.includes("any")) return "any";
565
+ const [first] = unsafeMembers;
566
+ return first !== void 0 && unsafeMembers.every((member) => member !== null) ? first : null;
567
+ }
568
+ if (unwrapped.type !== "TSTypeReference") return null;
569
+ const name = typeReferenceName$2(unwrapped);
570
+ if (name === null) return null;
571
+ const substitution = substitutionFor(substitutions, name);
572
+ if (substitution !== void 0) return substitution === "opaque" ? null : unsafeDirectValue(substitution, environment, substitutions, resolvingAliases);
573
+ if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
574
+ const wrapped = unwrapped.typeArguments?.params[0];
575
+ return wrapped === void 0 ? null : unsafeDirectValue(wrapped, environment, substitutions, resolvingAliases);
576
+ }
577
+ const interfaceDeclarations = environment.interfaces.get(name);
578
+ if (interfaceDeclarations !== void 0) return isEffectivelyEmptyInterface(interfaceDeclarations) ? "empty-object" : null;
579
+ const alias = environment.aliases.get(name);
580
+ if (alias === void 0 || resolvingAliases.has(name)) return null;
581
+ const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
582
+ if (nextSubstitutions === null) return null;
583
+ const nextResolving = new Set(resolvingAliases);
584
+ nextResolving.add(name);
585
+ return unsafeDirectValue(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
586
+ }
587
+ function dictionaryValueTypes(type, environment, substitutions, resolvingAliases) {
588
+ const unwrapped = unwrapTransparentType(type);
589
+ if (unwrapped.type === "TSTypeLiteral") return unwrapped.members.flatMap((member) => member.type === "TSIndexSignature" && member.typeAnnotation !== null ? [{
590
+ type: member.typeAnnotation.typeAnnotation,
591
+ substitutions
592
+ }] : []);
593
+ if (unwrapped.type === "TSMappedType") return unwrapped.typeAnnotation === null || !isBroadMappedKey(unwrapped.constraint, environment, substitutions) ? [] : [{
594
+ type: unwrapped.typeAnnotation,
595
+ substitutions
596
+ }];
597
+ if (unwrapped.type !== "TSTypeReference") return [];
598
+ const name = typeReferenceName$2(unwrapped);
599
+ if (name === null) return [];
600
+ const substitution = substitutionFor(substitutions, name);
601
+ if (substitution !== void 0) return substitution === "opaque" ? [] : dictionaryValueTypes(substitution, environment, substitutions, resolvingAliases);
602
+ if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
603
+ const wrapped = unwrapped.typeArguments?.params[0];
604
+ return wrapped === void 0 ? [] : dictionaryValueTypes(wrapped, environment, substitutions, resolvingAliases);
605
+ }
606
+ if (name === "Record" && isBuiltIn(name, environment)) {
607
+ const value = unwrapped.typeArguments?.params[1] ?? null;
608
+ return value === null ? [] : [{
609
+ type: value,
610
+ substitutions
611
+ }];
612
+ }
613
+ if ((name === "Pick" || name === "Omit") && isBuiltIn(name, environment)) {
614
+ const source = unwrapped.typeArguments?.params[0];
615
+ return source === void 0 ? [] : dictionaryValueTypes(source, environment, substitutions, resolvingAliases);
616
+ }
617
+ const alias = environment.aliases.get(name);
618
+ if (alias === void 0 || resolvingAliases.has(name)) return [];
619
+ const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
620
+ if (nextSubstitutions === null) return [];
621
+ const nextResolving = new Set(resolvingAliases);
622
+ nextResolving.add(name);
623
+ return dictionaryValueTypes(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
624
+ }
625
+ function classifyUnsafeDictionaryValue(valueType, environment, shadowed = /* @__PURE__ */ new Set()) {
626
+ const unsafeValue = unsafeDirectValue(valueType, environment, shadowedSubstitutions(shadowed), /* @__PURE__ */ new Set());
627
+ return unsafeValue === null ? null : {
628
+ kind: "unsafe-dictionary",
629
+ unsafeValue
630
+ };
631
+ }
632
+ function classifyUnsafeDictionary(type, environment, shadowed = /* @__PURE__ */ new Set()) {
633
+ for (const valueType of dictionaryValueTypes(type, environment, shadowedSubstitutions(shadowed), /* @__PURE__ */ new Set())) {
634
+ const unsafeValue = unsafeDirectValue(valueType.type, environment, valueType.substitutions, /* @__PURE__ */ new Set());
635
+ if (unsafeValue !== null) return {
636
+ kind: "unsafe-dictionary",
637
+ unsafeValue
638
+ };
639
+ }
640
+ return null;
641
+ }
642
+ function resolvesToDictionary(type, environment, substitutions, resolvingAliases) {
643
+ return dictionaryValueTypes(type, environment, substitutions, resolvingAliases).length > 0;
644
+ }
645
+ function classifyWideningTarget(type, environment, shadowed = /* @__PURE__ */ new Set()) {
646
+ const shadowedMap = shadowedSubstitutions(shadowed);
647
+ const unwrapped = unwrapTransparentType(type);
648
+ if (unwrapped.type === "TSUnknownKeyword") return { kind: "unknown" };
649
+ if (unwrapped.type === "TSObjectKeyword") return { kind: "object" };
650
+ if (unwrapped.type === "TSTypeLiteral") return unwrapped.members.some((member) => member.type === "TSIndexSignature") ? { kind: "open dictionary" } : unwrapped.members.length > 0 ? { kind: "anonymous object" } : null;
651
+ if (unwrapped.type === "TSMappedType") return isBroadMappedKey(unwrapped.constraint, environment, shadowedMap) ? { kind: "open dictionary" } : { kind: "anonymous object" };
652
+ if (unwrapped.type !== "TSTypeReference") return null;
653
+ const name = typeReferenceName$2(unwrapped);
654
+ if (name === null || shadowed.has(name)) return null;
655
+ if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
656
+ const wrapped = unwrapped.typeArguments?.params[0];
657
+ return wrapped === void 0 ? null : classifyWideningTarget(wrapped, environment, shadowed);
658
+ }
659
+ if (name === "Record" && isBuiltIn(name, environment)) return { kind: "open dictionary" };
660
+ const alias = environment.aliases.get(name);
661
+ if (alias === void 0) return null;
662
+ if ((alias.typeParameters?.params.length ?? 0) > 0) {
663
+ const substitutions = aliasSubstitution(alias, unwrapped, shadowedMap);
664
+ return substitutions !== null && resolvesToDictionary(alias.typeAnnotation, environment, substitutions, /* @__PURE__ */ new Set([name])) ? { kind: "generic container" } : null;
665
+ }
666
+ const substitutions = aliasSubstitution(alias, unwrapped, shadowedMap);
667
+ if (substitutions === null) return null;
668
+ return classifyAliasBroadTarget(alias.typeAnnotation, environment, substitutions, /* @__PURE__ */ new Set([name]));
669
+ }
670
+ function isBroadMappedKey(type, environment, substitutions, resolvingAliases = /* @__PURE__ */ new Set()) {
671
+ const unwrapped = unwrapTransparentType(type);
672
+ if (unwrapped.type === "TSStringKeyword" || unwrapped.type === "TSNumberKeyword" || unwrapped.type === "TSSymbolKeyword") return true;
673
+ if (unwrapped.type === "TSUnionType") return unwrapped.types.every((member) => isBroadMappedKey(member, environment, substitutions, resolvingAliases));
674
+ if (unwrapped.type !== "TSTypeReference") return false;
675
+ const name = typeReferenceName$2(unwrapped);
676
+ if (name === null) return false;
677
+ const substitution = substitutionFor(substitutions, name);
678
+ if (substitution !== void 0) return substitution !== "opaque" && isBroadMappedKey(substitution, environment, substitutions, resolvingAliases);
679
+ if (name === "PropertyKey" && isBuiltIn(name, environment)) return true;
680
+ const alias = environment.aliases.get(name);
681
+ if (alias === void 0 || resolvingAliases.has(name)) return false;
682
+ const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
683
+ if (nextSubstitutions === null) return false;
684
+ const nextResolving = new Set(resolvingAliases);
685
+ nextResolving.add(name);
686
+ return isBroadMappedKey(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
687
+ }
688
+ function classifyAliasBroadTarget(type, environment, substitutions, resolvingAliases) {
689
+ const unwrapped = unwrapTransparentType(type);
690
+ if (unwrapped.type === "TSUnknownKeyword") return { kind: "unknown" };
691
+ if (unwrapped.type === "TSObjectKeyword") return { kind: "object" };
692
+ if (unwrapped.type === "TSTypeLiteral") return unwrapped.members.some((member) => member.type === "TSIndexSignature") ? { kind: "open dictionary" } : null;
693
+ if (unwrapped.type === "TSMappedType") return isBroadMappedKey(unwrapped.constraint, environment, substitutions) ? { kind: "open dictionary" } : null;
694
+ if (unwrapped.type !== "TSTypeReference") return null;
695
+ const name = typeReferenceName$2(unwrapped);
696
+ if (name === null) return null;
697
+ const substitution = substitutionFor(substitutions, name);
698
+ if (substitution !== void 0) return substitution === "opaque" ? null : classifyAliasBroadTarget(substitution, environment, substitutions, resolvingAliases);
699
+ if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
700
+ const wrapped = unwrapped.typeArguments?.params[0];
701
+ return wrapped === void 0 ? null : classifyAliasBroadTarget(wrapped, environment, substitutions, resolvingAliases);
702
+ }
703
+ if (name === "Record" && isBuiltIn(name, environment)) return { kind: "open dictionary" };
704
+ const alias = environment.aliases.get(name);
705
+ if (alias === void 0 || resolvingAliases.has(name)) return null;
706
+ const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
707
+ if (nextSubstitutions === null) return null;
708
+ const nextResolving = new Set(resolvingAliases);
709
+ nextResolving.add(name);
710
+ return classifyAliasBroadTarget(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
711
+ }
712
+ function isKnownEvidenceExpression(expression) {
713
+ let current = expression;
714
+ while (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression" || current.type === "TSSatisfiesExpression") current = current.expression;
715
+ if (current.type === "ObjectExpression") return true;
716
+ return current.type === "ArrayExpression" || current.type === "ArrowFunctionExpression" || current.type === "ClassExpression" || current.type === "FunctionExpression" || current.type === "NewExpression" || current.type === "Literal" || current.type === "TemplateLiteral" || current.type === "UnaryExpression";
717
+ }
718
+ //#endregion
719
+ //#region src/plugin/rules/vendor/anti-slop/lexical-type-parameters.ts
720
+ function isNode(value) {
721
+ return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
722
+ }
723
+ function collectInferTypeParameterNames(node, visitorKeys, names) {
724
+ if (node.type === "TSInferType") names.add(node.typeParameter.name.name);
725
+ const keys = new Set(visitorKeys[node.type] ?? []);
726
+ for (const [key, value] of Object.entries(node)) {
727
+ if (!keys.has(key)) continue;
728
+ if (isNode(value)) {
729
+ collectInferTypeParameterNames(value, visitorKeys, names);
730
+ continue;
731
+ }
732
+ if (!Array.isArray(value)) continue;
733
+ for (const child of value) if (isNode(child)) collectInferTypeParameterNames(child, visitorKeys, names);
734
+ }
735
+ }
736
+ /** Local aliases, interfaces, classes, and enums shadow module-level aliases. */
737
+ function collectLocalTypeDeclarationNames(statements, names) {
738
+ for (const statement of statements) {
739
+ const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
740
+ if (declaration?.type === "TSTypeAliasDeclaration" || declaration?.type === "TSInterfaceDeclaration" || declaration?.type === "ClassDeclaration" || declaration?.type === "TSEnumDeclaration") {
741
+ if (declaration.id !== null) names.add(declaration.id.name);
742
+ }
743
+ }
744
+ }
745
+ /** Collect type binders that are in scope at a node and can shadow module aliases. */
746
+ function lexicalTypeParameterNames(node, visitorKeys) {
747
+ const names = /* @__PURE__ */ new Set();
748
+ let descendant = node;
749
+ let current = node;
750
+ while (current !== null && current.type !== "Program") {
751
+ if ("typeParameters" in current) for (const parameter of current.typeParameters?.params ?? []) names.add(parameter.name.name);
752
+ if (current.type === "BlockStatement" || current.type === "StaticBlock" || current.type === "TSModuleBlock") collectLocalTypeDeclarationNames(current.body, names);
753
+ if (current.type === "TSMappedType" && (descendant === current.nameType || descendant === current.typeAnnotation)) names.add(current.key.name);
754
+ if (current.type === "TSConditionalType" && descendant === current.trueType) collectInferTypeParameterNames(current.extendsType, visitorKeys, names);
755
+ descendant = current;
756
+ current = current.parent;
757
+ }
758
+ return names;
759
+ }
760
+ //#endregion
761
+ //#region src/plugin/rules/vendor/anti-slop/no-known-value-widening.ts
762
+ function unwrapExpression(expression) {
763
+ let current = expression;
764
+ while (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression") current = current.expression;
765
+ return current;
766
+ }
767
+ function resolveVariable$2(sourceCode, identifier) {
768
+ let scope = sourceCode.getScope(identifier);
769
+ while (scope !== null) {
770
+ const variable = scope.set.get(identifier.name);
771
+ if (variable !== void 0) return variable;
772
+ scope = scope.upper;
773
+ }
774
+ return null;
775
+ }
776
+ function variableDeclarator$1(variable) {
777
+ if (variable.defs.length !== 1) return null;
778
+ const [definition] = variable.defs;
779
+ return definition?.type === "Variable" && definition.node.type === "VariableDeclarator" ? definition.node : null;
780
+ }
781
+ function isStableConstVariable(variable, declarator) {
782
+ return declarator.parent.type === "VariableDeclaration" && declarator.parent.kind === "const" && variable.references.every((reference) => reference.init || !reference.isWrite());
783
+ }
784
+ function hasKnownEvidence(sourceCode, expression, visitedVariables = /* @__PURE__ */ new Set()) {
785
+ if (isKnownEvidenceExpression(expression)) return true;
786
+ const unwrapped = unwrapExpression(expression);
787
+ if (unwrapped.type !== "Identifier") return false;
788
+ const variable = resolveVariable$2(sourceCode, unwrapped);
789
+ if (variable === null || visitedVariables.has(variable)) return false;
790
+ const declarator = variableDeclarator$1(variable);
791
+ if (declarator === null || declarator.init === null || !isStableConstVariable(variable, declarator)) return false;
792
+ visitedVariables.add(variable);
793
+ return hasKnownEvidence(sourceCode, declarator.init, visitedVariables);
794
+ }
795
+ function typeTarget(sourceCode, type, environment) {
796
+ return classifyWideningTarget(type, environment, lexicalTypeParameterNames(type, sourceCode.visitorKeys));
797
+ }
798
+ function annotationTarget(sourceCode, annotation, environment) {
799
+ return annotation === null || annotation === void 0 ? null : typeTarget(sourceCode, annotation.typeAnnotation, environment);
800
+ }
801
+ function enclosingFunction(node) {
802
+ let current = node.parent;
803
+ while (current !== null && current.type !== "Program") {
804
+ if (current.type === "ArrowFunctionExpression" || current.type === "FunctionDeclaration" || current.type === "FunctionExpression") return current;
805
+ current = current.parent;
806
+ }
807
+ return null;
808
+ }
809
+ function sourceKeyName(sourceCode, key) {
810
+ if (key.type === "Identifier" || key.type === "PrivateIdentifier") return key.name;
811
+ if (key.type === "Literal") return String(key.value);
812
+ return sourceCode.getText(key);
813
+ }
814
+ function functionName(sourceCode, owner) {
815
+ if (owner === null) return "anonymous function";
816
+ if (owner.id !== null) return owner.id.name;
817
+ const parent = owner.parent;
818
+ if (parent.type === "VariableDeclarator" && parent.id.type === "Identifier") return parent.id.name;
819
+ if (parent.type === "MethodDefinition") return sourceKeyName(sourceCode, parent.key);
820
+ return "anonymous function";
821
+ }
822
+ function isEmptyObjectExpression(expression) {
823
+ const unwrapped = unwrapExpression(expression);
824
+ return unwrapped.type === "ObjectExpression" && unwrapped.properties.length === 0;
825
+ }
826
+ function isDictionaryAccumulatorTarget(destination) {
827
+ return destination.kind === "open dictionary" || destination.kind === "generic container";
828
+ }
829
+ function hasParentAssertion(node) {
830
+ let parent = node.parent;
831
+ while (parent?.type === "TSNonNullExpression" || parent?.type === "ParenthesizedExpression") parent = parent.parent;
832
+ return parent?.type === "TSAsExpression" || parent?.type === "TSTypeAssertion";
833
+ }
834
+ /** Detect sound syntactic cases where a known value is explicitly widened and loses evidence. */
835
+ const noKnownValueWideningRule = defineRule({
836
+ meta: {
837
+ type: "problem",
838
+ docs: {
839
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-known-value-widening",
840
+ description: "Disallow syntactically established values from flowing into explicitly broad or anonymous target types that discard useful evidence."
841
+ },
842
+ messages: { widening: "The explicit {{target}} type on {{subject}} discards known type evidence. Keep inference, validate with `satisfies`, or use a named owner contract." }
843
+ },
844
+ createOnce(context) {
845
+ let environment = null;
846
+ const reportFlow = (expression, destination, subject) => {
847
+ if (destination === null) return;
848
+ if (isDictionaryAccumulatorTarget(destination) && isEmptyObjectExpression(expression)) return;
849
+ if (!hasKnownEvidence(context.sourceCode, expression)) return;
850
+ context.report({
851
+ node: expression,
852
+ messageId: "widening",
853
+ data: {
854
+ subject,
855
+ target: destination.kind
856
+ }
857
+ });
858
+ };
859
+ const targetFromAnnotation = (annotation) => environment === null ? null : annotationTarget(context.sourceCode, annotation, environment);
860
+ return {
861
+ Program(node) {
862
+ environment = createTypeEnvironment(node);
863
+ },
864
+ VariableDeclarator(node) {
865
+ if (node.init === null || node.id.type !== "Identifier") return;
866
+ reportFlow(node.init, targetFromAnnotation(node.id.typeAnnotation), `binding \`${node.id.name}\``);
867
+ },
868
+ PropertyDefinition(node) {
869
+ if (node.value === null) return;
870
+ reportFlow(node.value, targetFromAnnotation(node.typeAnnotation), `property \`${sourceKeyName(context.sourceCode, node.key)}\``);
871
+ },
872
+ AccessorProperty(node) {
873
+ if (node.value === null) return;
874
+ reportFlow(node.value, targetFromAnnotation(node.typeAnnotation), `property \`${sourceKeyName(context.sourceCode, node.key)}\``);
875
+ },
876
+ AssignmentExpression(node) {
877
+ if (node.operator !== "=" || node.left.type !== "Identifier") return;
878
+ const variable = resolveVariable$2(context.sourceCode, node.left);
879
+ if (variable === null) return;
880
+ const declarator = variableDeclarator$1(variable);
881
+ if (declarator === null || declarator.id.type !== "Identifier") return;
882
+ reportFlow(node.right, targetFromAnnotation(declarator.id.typeAnnotation), `binding \`${declarator.id.name}\``);
883
+ },
884
+ ReturnStatement(node) {
885
+ if (node.argument === null) return;
886
+ const owner = enclosingFunction(node);
887
+ reportFlow(node.argument, targetFromAnnotation(owner?.returnType), `return value of \`${functionName(context.sourceCode, owner)}\``);
888
+ },
889
+ ArrowFunctionExpression(node) {
890
+ if (node.body.type === "BlockStatement") return;
891
+ reportFlow(node.body, targetFromAnnotation(node.returnType), `return value of \`${functionName(context.sourceCode, node)}\``);
892
+ },
893
+ TSAsExpression(node) {
894
+ if (environment === null || hasParentAssertion(node)) return;
895
+ reportFlow(node.expression, typeTarget(context.sourceCode, node.typeAnnotation, environment), "assertion");
896
+ },
897
+ TSTypeAssertion(node) {
898
+ if (environment === null || hasParentAssertion(node)) return;
899
+ reportFlow(node.expression, typeTarget(context.sourceCode, node.typeAnnotation, environment), "assertion");
900
+ }
901
+ };
902
+ }
903
+ });
904
+ //#endregion
905
+ //#region src/plugin/rules/vendor/anti-slop/reflect-method.ts
906
+ function resolveVariable$1(sourceCode, identifier) {
907
+ let scope = sourceCode.getScope(identifier);
908
+ while (scope !== null) {
909
+ const variable = scope.set.get(identifier.name);
910
+ if (variable !== void 0) return variable;
911
+ scope = scope.upper;
912
+ }
913
+ return null;
914
+ }
915
+ function isGlobalReflect(sourceCode, expression) {
916
+ if (expression.type !== "Identifier" || expression.name !== "Reflect") return false;
917
+ if (sourceCode.isGlobalReference(expression)) return true;
918
+ const variable = resolveVariable$1(sourceCode, expression);
919
+ return variable === null || variable.defs.length === 0;
920
+ }
921
+ /** Reports whether a call target names one method on the global Reflect object. */
922
+ function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
923
+ if (!("property" in callee) || !("object" in callee) || !("computed" in callee)) return false;
924
+ if (!isGlobalReflect(sourceCode, callee.object)) return false;
925
+ return staticPropertyName(callee.property, callee.computed) === methodName;
926
+ }
927
+ /** Resolves a member name from an identifier, string literal, or expression-free template. */
928
+ function staticPropertyName(property, computed) {
929
+ if (!computed) return property.type === "Identifier" ? property.name : null;
930
+ if (property.type === "Literal" && typeof property.value === "string") return property.value;
931
+ if (property.type === "TemplateLiteral" && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? null;
932
+ return null;
933
+ }
934
+ //#endregion
935
+ //#region src/plugin/rules/vendor/anti-slop/no-module-mocking.ts
936
+ const moduleMockMethods = /* @__PURE__ */ new Set([
937
+ "doMock",
938
+ "mock",
939
+ "unstable_mockModule"
940
+ ]);
941
+ function resolveVariable(sourceCode, identifier) {
942
+ let scope = sourceCode.getScope(identifier);
943
+ while (scope !== null) {
944
+ const variable = scope.set.get(identifier.name);
945
+ if (variable !== void 0) return variable;
946
+ scope = scope.upper;
947
+ }
948
+ return null;
949
+ }
950
+ function importedName(node) {
951
+ if (node.type !== "ImportSpecifier") return null;
952
+ return node.imported.type === "Identifier" ? node.imported.name : node.imported.value;
953
+ }
954
+ function isTestFrameworkObject(sourceCode, expression) {
955
+ if (expression.type !== "Identifier") return false;
956
+ if ((expression.name === "vi" || expression.name === "jest") && sourceCode.isGlobalReference(expression)) return true;
957
+ const variable = resolveVariable(sourceCode, expression);
958
+ if (variable === null || variable.defs.length === 0) return expression.name === "vi" || expression.name === "jest";
959
+ return variable.defs.some((definition) => {
960
+ if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration") return false;
961
+ const source = definition.parent.source.value;
962
+ const name = importedName(definition.node);
963
+ return source === "vitest" && name === "vi" || source === "@jest/globals" && name === "jest";
964
+ });
965
+ }
966
+ function moduleMockCall(sourceCode, callee) {
967
+ if (!("property" in callee) || !("object" in callee) || !("computed" in callee)) return false;
968
+ if (!isTestFrameworkObject(sourceCode, callee.object)) return false;
969
+ const method = staticPropertyName(callee.property, callee.computed);
970
+ return method !== null && moduleMockMethods.has(method);
971
+ }
972
+ /** Ban test framework module mocking in favor of real dependency seams. */
973
+ const noModuleMockingRule = defineRule({
974
+ meta: {
975
+ type: "problem",
976
+ docs: {
977
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-module-mocking",
978
+ description: "Disallow Vitest and Jest module mocking; tests must replace dependencies through real interfaces."
979
+ },
980
+ messages: { moduleMock: "Replace module mocking with dependency injection through a real interface, service layer, or faithful test implementation." }
981
+ },
982
+ createOnce(context) {
983
+ return { CallExpression(node) {
984
+ if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression") return;
985
+ if (moduleMockCall(context.sourceCode, node.callee)) context.report({
986
+ node,
987
+ messageId: "moduleMock"
988
+ });
989
+ } };
990
+ }
991
+ });
992
+ //#endregion
993
+ //#region src/plugin/rules/vendor/anti-slop/no-reflect-apply.ts
994
+ /** Ban Reflect.apply, which bypasses ordinary typed function calls. */
995
+ const noReflectApplyRule = defineRule({
996
+ meta: {
997
+ type: "problem",
998
+ docs: {
999
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-reflect-apply",
1000
+ description: "Disallow Reflect.apply; call typed functions directly or model dynamic dispatch behind an interface."
1001
+ },
1002
+ messages: { reflectApply: "Replace `Reflect.apply` with a typed function call. Model dynamic dispatch behind a named interface." }
1003
+ },
1004
+ createOnce(context) {
1005
+ return { CallExpression(node) {
1006
+ if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression") return;
1007
+ if (isGlobalReflectMethodCall(context.sourceCode, node.callee, "apply")) context.report({
1008
+ node,
1009
+ messageId: "reflectApply"
1010
+ });
1011
+ } };
1012
+ }
1013
+ });
1014
+ //#endregion
1015
+ //#region src/plugin/rules/vendor/anti-slop/no-reflect-get.ts
1016
+ /** Ban Reflect.get, which bypasses ordinary property access and useful type evidence. */
1017
+ const noReflectGetRule = defineRule({
1018
+ meta: {
1019
+ type: "problem",
1020
+ docs: {
1021
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-reflect-get",
1022
+ description: "Disallow Reflect.get; use typed property access or parse dynamic input into a domain type."
1023
+ },
1024
+ messages: { reflectGet: "Replace `Reflect.get` with typed property access. Parse dynamic input into a named domain type before reading it." }
1025
+ },
1026
+ createOnce(context) {
1027
+ return { CallExpression(node) {
1028
+ if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression") return;
1029
+ if (isGlobalReflectMethodCall(context.sourceCode, node.callee, "get")) context.report({
1030
+ node,
1031
+ messageId: "reflectGet"
1032
+ });
1033
+ } };
1034
+ }
1035
+ });
1036
+ //#endregion
1037
+ //#region src/plugin/rules/vendor/anti-slop/no-unknown-parameters.ts
1038
+ function parameterAnnotation(parameter) {
1039
+ if (parameter.type === "TSParameterProperty") return parameterAnnotation(parameter.parameter);
1040
+ if (parameter.type === "RestElement") return parameter.typeAnnotation ?? parameterAnnotation(parameter.argument);
1041
+ if (parameter.type === "AssignmentPattern") return parameter.typeAnnotation ?? parameter.left.typeAnnotation;
1042
+ return parameter.typeAnnotation;
1043
+ }
1044
+ /** The parameter a type guard narrows; only that parameter may be unknown. */
1045
+ function guardedParameterName(node) {
1046
+ const annotation = node.returnType?.typeAnnotation;
1047
+ if (annotation?.type !== "TSTypePredicate") return null;
1048
+ return annotation.parameterName.type === "Identifier" ? annotation.parameterName.name : "this";
1049
+ }
1050
+ function parameterName(parameter, sourceText) {
1051
+ if (parameter.type === "TSParameterProperty") return parameterName(parameter.parameter, sourceText);
1052
+ if (parameter.type === "AssignmentPattern") return parameterName(parameter.left, sourceText);
1053
+ if (parameter.type === "RestElement") return parameterName(parameter.argument, sourceText);
1054
+ return parameter.type === "Identifier" ? parameter.name : sourceText.replace(/\s*:\s*unknown\s*$/u, "");
1055
+ }
1056
+ /** Disallow unknown inputs except type guards and `cause`. */
1057
+ const noUnknownParametersRule = defineRule({
1058
+ meta: {
1059
+ type: "problem",
1060
+ docs: {
1061
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-unknown-parameters",
1062
+ description: "Disallow explicitly unknown function parameters except `cause` and type guards."
1063
+ },
1064
+ messages: { unknownParameter: "Parameter `{{parameter}}` leaves input unparsed. Accept a named domain type; run the expected schema or parser at the I/O boundary before calling this function." }
1065
+ },
1066
+ createOnce(context) {
1067
+ const checkParameters = (node) => {
1068
+ const guarded = guardedParameterName(node);
1069
+ for (const parameter of node.params) {
1070
+ const annotation = parameterAnnotation(parameter);
1071
+ if (annotation?.typeAnnotation.type !== "TSUnknownKeyword") continue;
1072
+ const name = parameterName(parameter, context.sourceCode.getText(parameter));
1073
+ if (name === "cause" || name === "this" || name === guarded) continue;
1074
+ context.report({
1075
+ node: annotation.typeAnnotation,
1076
+ messageId: "unknownParameter",
1077
+ data: { parameter: name }
1078
+ });
1079
+ }
1080
+ };
1081
+ return {
1082
+ ArrowFunctionExpression: checkParameters,
1083
+ FunctionDeclaration: checkParameters,
1084
+ FunctionExpression: checkParameters,
1085
+ TSCallSignatureDeclaration: checkParameters,
1086
+ TSConstructSignatureDeclaration: checkParameters,
1087
+ TSConstructorType: checkParameters,
1088
+ TSDeclareFunction: checkParameters,
1089
+ TSEmptyBodyFunctionExpression: checkParameters,
1090
+ TSFunctionType: checkParameters,
1091
+ TSMethodSignature: checkParameters
1092
+ };
1093
+ }
1094
+ });
1095
+ //#endregion
1096
+ //#region src/plugin/rules/vendor/anti-slop/no-unknown-returns.ts
1097
+ function referencedAliasName$1(type) {
1098
+ if (type.type === "TSParenthesizedType") return referencedAliasName$1(type.typeAnnotation);
1099
+ if (type.type !== "TSTypeReference" || type.typeName.type !== "Identifier") return null;
1100
+ return type.typeArguments === null || type.typeArguments === void 0 || type.typeArguments.params.length === 0 ? type.typeName.name : null;
1101
+ }
1102
+ /** Ban function contracts that return unknown instead of a parsed domain type. */
1103
+ const noUnknownReturnsRule = defineRule({
1104
+ meta: {
1105
+ type: "problem",
1106
+ docs: {
1107
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-unknown-returns",
1108
+ description: "Disallow functions whose explicit return contract is unknown or Promise<unknown>."
1109
+ },
1110
+ messages: { unknownReturn: "This function exposes `unknown` to its caller. Parse the value at its boundary and return a named domain type." }
1111
+ },
1112
+ createOnce(context) {
1113
+ const aliases = /* @__PURE__ */ new Map();
1114
+ const shadowedBuiltIns = /* @__PURE__ */ new Set();
1115
+ const resolvesToUnknown = (type, shadowedAliases, visited = /* @__PURE__ */ new Set()) => {
1116
+ if (type.type === "TSUnknownKeyword") return true;
1117
+ if (type.type === "TSParenthesizedType") return resolvesToUnknown(type.typeAnnotation, shadowedAliases, visited);
1118
+ if (type.type === "TSUnionType") return type.types.some((member) => resolvesToUnknown(member, shadowedAliases, visited));
1119
+ if (type.type === "TSTypeReference" && type.typeName.type === "Identifier" && (type.typeName.name === "Promise" || type.typeName.name === "PromiseLike") && !shadowedAliases.has(type.typeName.name) && !shadowedBuiltIns.has(type.typeName.name)) {
1120
+ const value = type.typeArguments?.params[0];
1121
+ return value !== void 0 && resolvesToUnknown(value, shadowedAliases, visited);
1122
+ }
1123
+ const name = referencedAliasName$1(type);
1124
+ if (name === null || visited.has(name) || shadowedAliases.has(name)) return false;
1125
+ const alias = aliases.get(name);
1126
+ if (alias === void 0 || alias.typeParameters !== null && alias.typeParameters !== void 0) return false;
1127
+ const nextVisited = new Set(visited);
1128
+ nextVisited.add(name);
1129
+ return resolvesToUnknown(alias.typeAnnotation, shadowedAliases, nextVisited);
1130
+ };
1131
+ const checkReturnType = (node) => {
1132
+ const annotation = node.returnType;
1133
+ if (annotation === null || annotation === void 0) return;
1134
+ if (!resolvesToUnknown(annotation.typeAnnotation, lexicalTypeParameterNames(node, context.sourceCode.visitorKeys))) return;
1135
+ context.report({
1136
+ node: annotation.typeAnnotation,
1137
+ messageId: "unknownReturn"
1138
+ });
1139
+ };
1140
+ return {
1141
+ Program(node) {
1142
+ aliases.clear();
1143
+ shadowedBuiltIns.clear();
1144
+ for (const name of createTypeEnvironment(node).shadowedBuiltIns) shadowedBuiltIns.add(name);
1145
+ for (const statement of node.body) {
1146
+ const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
1147
+ if (declaration?.type === "TSTypeAliasDeclaration") aliases.set(declaration.id.name, declaration);
1148
+ }
1149
+ },
1150
+ ArrowFunctionExpression: checkReturnType,
1151
+ FunctionDeclaration: checkReturnType,
1152
+ FunctionExpression: checkReturnType,
1153
+ TSCallSignatureDeclaration: checkReturnType,
1154
+ TSConstructSignatureDeclaration: checkReturnType,
1155
+ TSConstructorType: checkReturnType,
1156
+ TSDeclareFunction: checkReturnType,
1157
+ TSEmptyBodyFunctionExpression: checkReturnType,
1158
+ TSFunctionType: checkReturnType,
1159
+ TSMethodSignature: checkReturnType
1160
+ };
1161
+ }
1162
+ });
1163
+ //#endregion
1164
+ //#region src/plugin/rules/vendor/anti-slop/no-unknown-type-aliases.ts
1165
+ function referencedAliasName(type) {
1166
+ if (type.type === "TSParenthesizedType") return referencedAliasName(type.typeAnnotation);
1167
+ if (type.type !== "TSTypeReference" || type.typeName.type !== "Identifier") return null;
1168
+ return type.typeArguments === null || type.typeArguments === void 0 || type.typeArguments.params.length === 0 ? type.typeName.name : null;
1169
+ }
1170
+ /** Ban named aliases that merely conceal TypeScript's unknown top type. */
1171
+ const noUnknownTypeAliasesRule = defineRule({
1172
+ meta: {
1173
+ type: "problem",
1174
+ docs: {
1175
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-unknown-type-aliases",
1176
+ description: "Disallow type aliases whose resolved type is unknown; unknown must remain visible at an allowed boundary."
1177
+ },
1178
+ messages: { unknownAlias: "Type alias `{{alias}}` hides `unknown`. Keep `unknown` explicit at the parsing boundary or on an allowed `cause` field; otherwise use the parsed owner type." }
1179
+ },
1180
+ createOnce(context) {
1181
+ const aliases = /* @__PURE__ */ new Map();
1182
+ const resolvesToUnknown = (type, visited = /* @__PURE__ */ new Set()) => {
1183
+ if (type.type === "TSUnknownKeyword") return true;
1184
+ if (type.type === "TSParenthesizedType") return resolvesToUnknown(type.typeAnnotation, visited);
1185
+ if (type.type === "TSUnionType") return type.types.some((member) => resolvesToUnknown(member, visited));
1186
+ const name = referencedAliasName(type);
1187
+ if (name === null || visited.has(name)) return false;
1188
+ if (lexicalTypeParameterNames(type, context.sourceCode.visitorKeys).has(name)) return false;
1189
+ const alias = aliases.get(name);
1190
+ if (alias === void 0 || alias.typeParameters !== null && alias.typeParameters !== void 0) return false;
1191
+ const nextVisited = new Set(visited);
1192
+ nextVisited.add(name);
1193
+ return resolvesToUnknown(alias.typeAnnotation, nextVisited);
1194
+ };
1195
+ return { Program(node) {
1196
+ aliases.clear();
1197
+ for (const statement of node.body) {
1198
+ const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
1199
+ if (declaration?.type === "TSTypeAliasDeclaration") aliases.set(declaration.id.name, declaration);
1200
+ }
1201
+ for (const alias of aliases.values()) {
1202
+ if (!resolvesToUnknown(alias.typeAnnotation, /* @__PURE__ */ new Set([alias.id.name]))) continue;
1203
+ context.report({
1204
+ node: alias.id,
1205
+ messageId: "unknownAlias",
1206
+ data: { alias: alias.id.name }
1207
+ });
1208
+ }
1209
+ } };
1210
+ }
1211
+ });
1212
+ //#endregion
1213
+ //#region src/plugin/rules/vendor/anti-slop/no-unsafe-dictionary-type.ts
1214
+ const typeNodeKinds = /* @__PURE__ */ new Set([
1215
+ "JSDocNonNullableType",
1216
+ "JSDocNullableType",
1217
+ "JSDocUnknownType",
1218
+ "TSAnyKeyword",
1219
+ "TSArrayType",
1220
+ "TSBigIntKeyword",
1221
+ "TSBooleanKeyword",
1222
+ "TSConditionalType",
1223
+ "TSConstructorType",
1224
+ "TSFunctionType",
1225
+ "TSImportType",
1226
+ "TSIndexedAccessType",
1227
+ "TSInferType",
1228
+ "TSIntersectionType",
1229
+ "TSIntrinsicKeyword",
1230
+ "TSLiteralType",
1231
+ "TSMappedType",
1232
+ "TSNamedTupleMember",
1233
+ "TSNeverKeyword",
1234
+ "TSNullKeyword",
1235
+ "TSNumberKeyword",
1236
+ "TSObjectKeyword",
1237
+ "TSParenthesizedType",
1238
+ "TSStringKeyword",
1239
+ "TSSymbolKeyword",
1240
+ "TSTemplateLiteralType",
1241
+ "TSThisType",
1242
+ "TSTupleType",
1243
+ "TSTypeLiteral",
1244
+ "TSTypeOperator",
1245
+ "TSTypePredicate",
1246
+ "TSTypeQuery",
1247
+ "TSTypeReference",
1248
+ "TSUndefinedKeyword",
1249
+ "TSUnionType",
1250
+ "TSUnknownKeyword",
1251
+ "TSVoidKeyword"
1252
+ ]);
1253
+ function isTypeNode(node) {
1254
+ return typeNodeKinds.has(node.type);
1255
+ }
1256
+ function typeReferenceName$1(type) {
1257
+ return type.typeName.type === "Identifier" ? type.typeName.name : null;
1258
+ }
1259
+ function isInsideTypeAliasDeclaration(node) {
1260
+ let current = node.parent;
1261
+ while (current !== null && current.type !== "Program") {
1262
+ if (current.type === "TSTypeAliasDeclaration") return true;
1263
+ current = current.parent;
1264
+ }
1265
+ return false;
1266
+ }
1267
+ function isPlainAliasConsumerUse(node, environment) {
1268
+ if (node.type !== "TSTypeReference" || node.typeArguments?.params.length) return false;
1269
+ const name = typeReferenceName$1(node);
1270
+ return name !== null && environment.aliases.has(name) && !isInsideTypeAliasDeclaration(node);
1271
+ }
1272
+ function shouldReportType(node, environment, shadowed) {
1273
+ if (isPlainAliasConsumerUse(node, environment)) return false;
1274
+ if (classifyUnsafeDictionary(node, environment, shadowed) === null) return false;
1275
+ let child = node;
1276
+ let current = node.parent;
1277
+ while (current !== null && current.type !== "Program") {
1278
+ if (current.type === "TSTypeOperator" && current.operator === "keyof") return false;
1279
+ if (current.type === "TSIndexedAccessType" && current.objectType === child) return false;
1280
+ if (isTypeNode(current) && classifyUnsafeDictionary(current, environment, shadowed) !== null) return false;
1281
+ child = current;
1282
+ current = current.parent;
1283
+ }
1284
+ return true;
1285
+ }
1286
+ /** Disallow object-dictionary contracts whose direct value type is an unsafe escape hatch. */
1287
+ const noUnsafeDictionaryTypeRule = defineRule({
1288
+ meta: {
1289
+ type: "problem",
1290
+ docs: {
1291
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-unsafe-dictionary-type",
1292
+ description: "Disallow object-dictionary contracts whose direct value type is unknown, any, object, {}, or a union/alias containing one of those escape hatches."
1293
+ },
1294
+ messages: { unsafeDictionary: "This dictionary's {{value}} value type gives callers no concrete value contract. Use an owner/schema-derived value type; parse external payloads before insertion." }
1295
+ },
1296
+ createOnce(context) {
1297
+ let environment = null;
1298
+ const report = (node, value) => {
1299
+ context.report({
1300
+ node,
1301
+ messageId: "unsafeDictionary",
1302
+ data: { value }
1303
+ });
1304
+ };
1305
+ const shadowedNames = (node) => lexicalTypeParameterNames(node, context.sourceCode.visitorKeys);
1306
+ const reportIfUnsafe = (node) => {
1307
+ if (environment === null) return;
1308
+ const shadowed = shadowedNames(node);
1309
+ if (!shouldReportType(node, environment, shadowed)) return;
1310
+ const unsafe = classifyUnsafeDictionary(node, environment, shadowed);
1311
+ if (unsafe === null) return;
1312
+ report(node, unsafe.unsafeValue);
1313
+ };
1314
+ return {
1315
+ Program(node) {
1316
+ environment = createTypeEnvironment(node);
1317
+ },
1318
+ TSTypeReference: reportIfUnsafe,
1319
+ TSTypeLiteral: reportIfUnsafe,
1320
+ TSMappedType: reportIfUnsafe,
1321
+ TSIndexSignature(node) {
1322
+ if (environment === null || node.typeAnnotation === null || node.parent.type === "TSTypeLiteral") return;
1323
+ const unsafe = classifyUnsafeDictionaryValue(node.typeAnnotation.typeAnnotation, environment, shadowedNames(node));
1324
+ if (unsafe !== null) report(node, unsafe.unsafeValue);
1325
+ }
1326
+ };
1327
+ }
1328
+ });
1329
+ //#endregion
1330
+ //#region src/plugin/rules/vendor/anti-slop/no-widen-then-assert.ts
1331
+ const functionBoundaryTypes = /* @__PURE__ */ new Set([
1332
+ "ArrowFunctionExpression",
1333
+ "FunctionDeclaration",
1334
+ "FunctionExpression",
1335
+ "TSDeclareFunction",
1336
+ "TSEmptyBodyFunctionExpression"
1337
+ ]);
1338
+ function unwrapExpressionParentheses(expression) {
1339
+ let current = expression;
1340
+ while (current.type === "ParenthesizedExpression") current = current.expression;
1341
+ return current;
1342
+ }
1343
+ function unwrapTypeParentheses(type) {
1344
+ let current = type;
1345
+ while (current.type === "TSParenthesizedType") current = current.typeAnnotation;
1346
+ return current;
1347
+ }
1348
+ function typeReferenceName(type) {
1349
+ return type.typeName.type === "Identifier" ? type.typeName.name : null;
1350
+ }
1351
+ function isUnknownOrAnyType(type) {
1352
+ const unwrapped = unwrapTypeParentheses(type);
1353
+ return unwrapped.type === "TSUnknownKeyword" || unwrapped.type === "TSAnyKeyword";
1354
+ }
1355
+ function isBroadRecordKeyType(type) {
1356
+ const unwrapped = unwrapTypeParentheses(type);
1357
+ if (unwrapped.type === "TSStringKeyword" || unwrapped.type === "TSNumberKeyword" || unwrapped.type === "TSSymbolKeyword") return true;
1358
+ if (unwrapped.type === "TSUnionType") return unwrapped.types.every(isBroadRecordKeyType);
1359
+ return unwrapped.type === "TSTypeReference" && typeReferenceName(unwrapped) === "PropertyKey";
1360
+ }
1361
+ function isBroadRecordType(type) {
1362
+ const unwrapped = unwrapTypeParentheses(type);
1363
+ if (unwrapped.type === "TSTypeReference") {
1364
+ if (typeReferenceName(unwrapped) === "Readonly") {
1365
+ const [inner] = unwrapped.typeArguments?.params ?? [];
1366
+ return inner !== void 0 && isBroadRecordType(inner);
1367
+ }
1368
+ if (typeReferenceName(unwrapped) !== "Record") return false;
1369
+ const parameters = unwrapped.typeArguments?.params ?? [];
1370
+ return parameters.length === 2 && parameters[0] !== void 0 && parameters[1] !== void 0 && isBroadRecordKeyType(parameters[0]) && isUnknownOrAnyType(parameters[1]);
1371
+ }
1372
+ if (unwrapped.type !== "TSTypeLiteral" || unwrapped.members.length !== 1) return false;
1373
+ const [member] = unwrapped.members;
1374
+ const [parameter] = member?.type === "TSIndexSignature" ? member.parameters : [];
1375
+ return member?.type === "TSIndexSignature" && member.parameters.length === 1 && parameter !== void 0 && isBroadRecordKeyType(parameter.typeAnnotation.typeAnnotation) && isUnknownOrAnyType(member.typeAnnotation.typeAnnotation);
1376
+ }
1377
+ function broadTypeKind(type) {
1378
+ const unwrapped = unwrapTypeParentheses(type);
1379
+ if (unwrapped.type === "TSUnknownKeyword" || unwrapped.type === "TSAnyKeyword") return "top";
1380
+ if (unwrapped.type === "TSObjectKeyword") return "object";
1381
+ return isBroadRecordType(unwrapped) ? "record" : null;
1382
+ }
1383
+ function assertedExpression(node) {
1384
+ let current = unwrapExpressionParentheses(node.expression);
1385
+ while (current.type === "TSNonNullExpression") current = unwrapExpressionParentheses(current.expression);
1386
+ return current;
1387
+ }
1388
+ function assertionFromExpression(expression) {
1389
+ const unwrapped = unwrapExpressionParentheses(expression);
1390
+ return unwrapped.type === "TSAsExpression" || unwrapped.type === "TSTypeAssertion" ? unwrapped : null;
1391
+ }
1392
+ function normalizedTypeText(sourceText, type) {
1393
+ return sourceText.slice(type.start, type.end).replaceAll(/\s+/gu, "");
1394
+ }
1395
+ function typesHaveSameSyntax(sourceText, left, right) {
1396
+ return left !== null && normalizedTypeText(sourceText, unwrapTypeParentheses(left)) === normalizedTypeText(sourceText, unwrapTypeParentheses(right));
1397
+ }
1398
+ function isDefinitelyObjectType(type) {
1399
+ const unwrapped = unwrapTypeParentheses(type);
1400
+ switch (unwrapped.type) {
1401
+ case "TSArrayType":
1402
+ case "TSConstructorType":
1403
+ case "TSFunctionType":
1404
+ case "TSMappedType":
1405
+ case "TSObjectKeyword":
1406
+ case "TSTupleType": return true;
1407
+ case "TSTypeLiteral": return unwrapped.members.length > 0;
1408
+ case "TSIntersectionType": return unwrapped.types.every(isDefinitelyObjectType);
1409
+ case "TSTypeOperator": return unwrapped.operator === "readonly" && isDefinitelyObjectType(unwrapped.typeAnnotation);
1410
+ default: return false;
1411
+ }
1412
+ }
1413
+ function isDefinitelyNarrowerRecordType(type) {
1414
+ const unwrapped = unwrapTypeParentheses(type);
1415
+ if (unwrapped.type === "TSTypeLiteral") return unwrapped.members.some((member) => member.type !== "TSIndexSignature");
1416
+ if (unwrapped.type !== "TSTypeReference") return false;
1417
+ if (typeReferenceName(unwrapped) === "Readonly") {
1418
+ const [inner] = unwrapped.typeArguments?.params ?? [];
1419
+ return inner !== void 0 && isDefinitelyNarrowerRecordType(inner);
1420
+ }
1421
+ if (typeReferenceName(unwrapped) !== "Record") return false;
1422
+ const parameters = unwrapped.typeArguments?.params ?? [];
1423
+ return parameters.length === 2 && parameters[1] !== void 0 && !isUnknownOrAnyType(parameters[1]);
1424
+ }
1425
+ function functionBoundary(node) {
1426
+ let current = node.parent;
1427
+ while (current !== null && current.type !== "Program") {
1428
+ if (functionBoundaryTypes.has(current.type)) return current;
1429
+ current = current.parent;
1430
+ }
1431
+ return null;
1432
+ }
1433
+ function resolvedVariableForIdentifier(scopes, identifier) {
1434
+ for (const scope of scopes) {
1435
+ const reference = scope.references.find((candidate) => candidate.identifier.start === identifier.start && candidate.identifier.end === identifier.end);
1436
+ if (reference !== void 0) return reference.resolved;
1437
+ }
1438
+ return null;
1439
+ }
1440
+ function variableDeclarator(variable) {
1441
+ for (const definition of variable.defs) if (definition.type === "Variable" && definition.node.type === "VariableDeclarator") return definition.node;
1442
+ return null;
1443
+ }
1444
+ function knownValueEvidence(expression, scopes, boundary, visitedVariables) {
1445
+ const unwrapped = unwrapExpressionParentheses(expression);
1446
+ if (unwrapped.type === "TSSatisfiesExpression") return knownValueEvidence(unwrapped.expression, scopes, boundary, visitedVariables);
1447
+ if (unwrapped.type === "TSAsExpression" || unwrapped.type === "TSTypeAssertion") {
1448
+ if (broadTypeKind(unwrapped.typeAnnotation) !== null) return null;
1449
+ return { type: unwrapped.typeAnnotation };
1450
+ }
1451
+ if (unwrapped.type === "Literal" || unwrapped.type === "TemplateLiteral") return { type: null };
1452
+ if (unwrapped.type === "ArrayExpression" || unwrapped.type === "ArrowFunctionExpression" || unwrapped.type === "ClassExpression" || unwrapped.type === "FunctionExpression" || unwrapped.type === "NewExpression" || unwrapped.type === "ObjectExpression") return { type: null };
1453
+ if (unwrapped.type !== "Identifier") return null;
1454
+ const variable = resolvedVariableForIdentifier(scopes, unwrapped);
1455
+ if (variable === null || visitedVariables.has(variable)) return null;
1456
+ const annotatedIdentifier = variable.identifiers.find((identifier) => identifier.typeAnnotation !== null && identifier.typeAnnotation !== void 0);
1457
+ const annotation = annotatedIdentifier?.typeAnnotation?.typeAnnotation;
1458
+ if (annotation !== void 0 && annotatedIdentifier !== void 0) {
1459
+ if (functionBoundary(annotatedIdentifier) !== boundary || broadTypeKind(annotation) !== null) return null;
1460
+ return { type: annotation };
1461
+ }
1462
+ const declarator = variableDeclarator(variable);
1463
+ if (declarator === null || declarator.parent.type !== "VariableDeclaration" || declarator.parent.kind !== "const" || declarator.init === null || variable.references.some((reference) => reference.isWrite() && !reference.init) || functionBoundary(declarator) !== boundary) return null;
1464
+ return knownValueEvidence(declarator.init, scopes, boundary, /* @__PURE__ */ new Set([...visitedVariables, variable]));
1465
+ }
1466
+ function widenedBinding(variable, scopes) {
1467
+ const declarator = variableDeclarator(variable);
1468
+ if (declarator === null || declarator.parent.type !== "VariableDeclaration" || declarator.parent.kind !== "const" || declarator.id.type !== "Identifier" || declarator.init === null || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
1469
+ const boundary = functionBoundary(declarator);
1470
+ const declaredType = declarator.id.typeAnnotation?.typeAnnotation;
1471
+ const initializerAssertion = assertionFromExpression(declarator.init);
1472
+ const initializerBroadKind = initializerAssertion === null ? null : broadTypeKind(initializerAssertion.typeAnnotation);
1473
+ const broadKind = (declaredType === void 0 ? null : broadTypeKind(declaredType)) ?? initializerBroadKind;
1474
+ if (broadKind === null) return null;
1475
+ const evidence = knownValueEvidence(initializerAssertion !== null && initializerBroadKind !== null ? assertedExpression(initializerAssertion) : declarator.init, scopes, boundary, /* @__PURE__ */ new Set([variable]));
1476
+ return evidence === null ? null : {
1477
+ broadKind,
1478
+ evidence,
1479
+ declaredAt: declarator.end,
1480
+ boundary
1481
+ };
1482
+ }
1483
+ function assertionIsNarrower(sourceText, broadKind, evidence, assertedType) {
1484
+ if (broadTypeKind(assertedType) !== null) return false;
1485
+ if (broadKind === "top") return true;
1486
+ if (typesHaveSameSyntax(sourceText, evidence.type, assertedType)) return true;
1487
+ if (broadKind === "object") return isDefinitelyObjectType(assertedType);
1488
+ return isDefinitelyNarrowerRecordType(assertedType);
1489
+ }
1490
+ /** Detect immutable local bindings that erase a known type and are later asserted back to a narrower type. */
1491
+ const noWidenThenAssertRule = defineRule({
1492
+ meta: {
1493
+ type: "problem",
1494
+ docs: {
1495
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#no-widen-then-assert",
1496
+ description: "Disallow local const flows that explicitly widen a known value before asserting the widened binding to a narrower type."
1497
+ },
1498
+ messages: { widenThenAssert: "Binding \"{{name}}\" discards type evidence and later recreates it with an assertion. Keep the precise type from initialization through use; parse boundary input once." }
1499
+ },
1500
+ createOnce(context) {
1501
+ let scopes = [];
1502
+ const checkAssertion = (node) => {
1503
+ const expression = assertedExpression(node);
1504
+ if (expression.type !== "Identifier") return;
1505
+ const variable = resolvedVariableForIdentifier(scopes, expression);
1506
+ if (variable === null) return;
1507
+ const widened = widenedBinding(variable, scopes);
1508
+ if (widened === null || node.start <= widened.declaredAt || functionBoundary(node) !== widened.boundary || !assertionIsNarrower(context.sourceCode.text, widened.broadKind, widened.evidence, node.typeAnnotation)) return;
1509
+ context.report({
1510
+ node,
1511
+ messageId: "widenThenAssert",
1512
+ data: { name: expression.name }
1513
+ });
1514
+ };
1515
+ return {
1516
+ Program() {
1517
+ scopes = context.sourceCode.scopeManager.scopes;
1518
+ },
1519
+ TSAsExpression: checkAssertion,
1520
+ TSTypeAssertion: checkAssertion
1521
+ };
1522
+ }
1523
+ });
1524
+ //#endregion
1525
+ //#region src/plugin/rules/vendor/stylistic/padding-line-between-statements.ts
1526
+ const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/u;
1527
+ const LT = "[\\r\\n\\u2028\\u2029]";
1528
+ const PADDING_LINE_SEQUENCE = new RegExp(String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`, "u");
1529
+ const paddingTypes = /* @__PURE__ */ new Set([
1530
+ "always",
1531
+ "any",
1532
+ "never"
1533
+ ]);
1534
+ const lineModes = /* @__PURE__ */ new Set([
1535
+ "any",
1536
+ "multiline",
1537
+ "singleline"
1538
+ ]);
1539
+ const statementContainers = /* @__PURE__ */ new Set([
1540
+ "BlockStatement",
1541
+ "Program",
1542
+ "StaticBlock",
1543
+ "SwitchCase",
1544
+ "SwitchStatement",
1545
+ "TSInterfaceBody",
1546
+ "TSModuleBlock",
1547
+ "TSTypeLiteral"
1548
+ ]);
1549
+ function isPlainObject(value) {
1550
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1551
+ }
1552
+ function isSelectorOption(value) {
1553
+ return isPlainObject(value) && "selector" in value && typeof value.selector === "string" && (!("lineMode" in value) || value.lineMode === void 0 || typeof value.lineMode === "string" && lineModes.has(value.lineMode));
1554
+ }
1555
+ function isStatementMatcher(value) {
1556
+ return typeof value === "string" && statementTypes.has(value) || isSelectorOption(value);
1557
+ }
1558
+ function isStatementOption(value) {
1559
+ return Array.isArray(value) ? value.length > 0 && value.every(isStatementMatcher) : isStatementMatcher(value);
1560
+ }
1561
+ function isPaddingOption(value) {
1562
+ return isPlainObject(value) && "blankLine" in value && typeof value.blankLine === "string" && paddingTypes.has(value.blankLine) && "prev" in value && isStatementOption(value.prev) && "next" in value && isStatementOption(value.next);
1563
+ }
1564
+ function isSingleLine(node) {
1565
+ return node.loc.start.line === node.loc.end.line;
1566
+ }
1567
+ function isTokenOnSameLine(left, right) {
1568
+ return left.loc.end.line === right.loc.start.line;
1569
+ }
1570
+ function isSemicolonToken(token) {
1571
+ return token.type === "Punctuator" && token.value === ";";
1572
+ }
1573
+ function isNotSemicolonToken(token) {
1574
+ return !isSemicolonToken(token);
1575
+ }
1576
+ function isClosingBraceToken(token) {
1577
+ return token.type === "Punctuator" && token.value === "}";
1578
+ }
1579
+ function isFunction(node) {
1580
+ return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
1581
+ }
1582
+ function skipChainExpression(node) {
1583
+ return node.type === "ChainExpression" ? node.expression : node;
1584
+ }
1585
+ function isParenthesized(node, sourceCode) {
1586
+ const previousToken = sourceCode.getTokenBefore(node);
1587
+ const nextToken = sourceCode.getTokenAfter(node);
1588
+ return previousToken !== null && nextToken !== null && previousToken.type === "Punctuator" && previousToken.value === "(" && previousToken.range[1] <= node.range[0] && nextToken.type === "Punctuator" && nextToken.value === ")" && nextToken.range[0] >= node.range[1];
1589
+ }
1590
+ function isTopLevelExpressionStatement(node) {
1591
+ if (node.type !== "ExpressionStatement") return false;
1592
+ const { parent } = node;
1593
+ return parent.type === "Program" || parent.type === "BlockStatement" && isFunction(parent.parent);
1594
+ }
1595
+ function isIIFEStatement(node) {
1596
+ if (node.type !== "ExpressionStatement") return false;
1597
+ let expression = skipChainExpression(node.expression);
1598
+ if (expression.type === "UnaryExpression") expression = skipChainExpression(expression.argument);
1599
+ if (expression.type !== "CallExpression") return false;
1600
+ let callee = expression.callee;
1601
+ while (callee.type === "SequenceExpression") {
1602
+ callee = callee.expressions[callee.expressions.length - 1] ?? callee;
1603
+ if (callee.type === "SequenceExpression") break;
1604
+ }
1605
+ return isFunction(callee);
1606
+ }
1607
+ function isCJSRequire(node) {
1608
+ if (node.type !== "VariableDeclaration") return false;
1609
+ const init = node.declarations[0]?.init;
1610
+ if (init === null || init === void 0) return false;
1611
+ let call = init;
1612
+ while (call.type === "MemberExpression") call = call.object;
1613
+ return call.type === "CallExpression" && call.callee.type === "Identifier" && call.callee.name === "require";
1614
+ }
1615
+ function isBlockLikeStatement(node, sourceCode) {
1616
+ if (node.type === "DoWhileStatement" && node.body.type === "BlockStatement") return true;
1617
+ if (isIIFEStatement(node)) return true;
1618
+ const lastToken = sourceCode.getLastToken(node, isNotSemicolonToken);
1619
+ const belongingNode = lastToken !== null && isClosingBraceToken(lastToken) ? sourceCode.getNodeByRangeIndex(lastToken.range[0]) : null;
1620
+ return belongingNode !== null && (belongingNode.type === "BlockStatement" || belongingNode.type === "SwitchStatement");
1621
+ }
1622
+ function isDirective(node, sourceCode) {
1623
+ return isTopLevelExpressionStatement(node) && node.expression.type === "Literal" && typeof node.expression.value === "string" && !isParenthesized(node.expression, sourceCode);
1624
+ }
1625
+ function isDirectivePrologue(node, sourceCode) {
1626
+ if (!isDirective(node, sourceCode)) return false;
1627
+ const { parent } = node;
1628
+ if (parent.type !== "Program" && parent.type !== "BlockStatement") return false;
1629
+ for (const sibling of parent.body) {
1630
+ if (sibling === node) break;
1631
+ if (!isDirective(sibling, sourceCode)) return false;
1632
+ }
1633
+ return true;
1634
+ }
1635
+ function isCJSExport(node) {
1636
+ if (node.type !== "ExpressionStatement") return false;
1637
+ const { expression } = node;
1638
+ if (expression.type !== "AssignmentExpression") return false;
1639
+ let left = expression.left;
1640
+ if (left.type !== "MemberExpression") return false;
1641
+ while (left.object.type === "MemberExpression") left = left.object;
1642
+ return left.object.type === "Identifier" && (left.object.name === "exports" || left.object.name === "module" && left.property.type === "Identifier" && left.property.name === "exports");
1643
+ }
1644
+ function isExpression(node, sourceCode) {
1645
+ return node.type === "ExpressionStatement" && !isDirectivePrologue(node, sourceCode);
1646
+ }
1647
+ function newKeywordTester(types, keyword) {
1648
+ return (node, sourceCode) => types.includes(node.type) && sourceCode.getFirstToken(node)?.value === keyword;
1649
+ }
1650
+ function newNodeTypeTester(type) {
1651
+ return (node) => node.type === type;
1652
+ }
1653
+ const maybeMultilineStatementEntries = [
1654
+ ["block-like", isBlockLikeStatement],
1655
+ ["const", newKeywordTester(["VariableDeclaration"], "const")],
1656
+ ["export", newKeywordTester([
1657
+ "ExportAllDeclaration",
1658
+ "ExportDefaultDeclaration",
1659
+ "ExportNamedDeclaration"
1660
+ ], "export")],
1661
+ ["expression", isExpression],
1662
+ ["let", newKeywordTester(["VariableDeclaration"], "let")],
1663
+ ["return", newKeywordTester(["ReturnStatement"], "return")],
1664
+ ["type", newKeywordTester(["TSTypeAliasDeclaration"], "type")],
1665
+ ["using", (node) => node.type === "VariableDeclaration" && (node.kind === "using" || node.kind === "await using")],
1666
+ ["var", newKeywordTester(["VariableDeclaration"], "var")]
1667
+ ];
1668
+ const statementEntries = [
1669
+ ["*", () => true],
1670
+ ["block", newNodeTypeTester("BlockStatement")],
1671
+ ["break", newKeywordTester(["BreakStatement"], "break")],
1672
+ ["case", newKeywordTester(["SwitchCase"], "case")],
1673
+ ["cjs-export", (node, sourceCode) => node.type === "ExpressionStatement" && node.expression.type === "AssignmentExpression" && CJS_EXPORT.test(sourceCode.getText(node.expression.left))],
1674
+ ["cjs-import", (node, sourceCode) => {
1675
+ if (node.type !== "VariableDeclaration") return false;
1676
+ const init = node.declarations[0]?.init;
1677
+ return init !== null && init !== void 0 && sourceCode.getText(init).startsWith("require(");
1678
+ }],
1679
+ ["class", newKeywordTester(["ClassDeclaration"], "class")],
1680
+ ["continue", newKeywordTester(["ContinueStatement"], "continue")],
1681
+ ["debugger", newKeywordTester(["DebuggerStatement"], "debugger")],
1682
+ ["default", newKeywordTester(["SwitchCase", "ExportDefaultDeclaration"], "default")],
1683
+ ["directive", isDirectivePrologue],
1684
+ ["do", newKeywordTester(["DoWhileStatement"], "do")],
1685
+ ["empty", newNodeTypeTester("EmptyStatement")],
1686
+ ["enum", newKeywordTester(["TSEnumDeclaration"], "enum")],
1687
+ ["exports", isCJSExport],
1688
+ ["for", newKeywordTester([
1689
+ "ForStatement",
1690
+ "ForInStatement",
1691
+ "ForOfStatement"
1692
+ ], "for")],
1693
+ ["function", newNodeTypeTester("FunctionDeclaration")],
1694
+ ["function-overload", newNodeTypeTester("TSDeclareFunction")],
1695
+ ["if", newKeywordTester(["IfStatement"], "if")],
1696
+ ["iife", isIIFEStatement],
1697
+ ["import", newKeywordTester(["ImportDeclaration"], "import")],
1698
+ ["interface", newKeywordTester(["TSInterfaceDeclaration"], "interface")],
1699
+ ["require", isCJSRequire],
1700
+ ["switch", newKeywordTester(["SwitchStatement"], "switch")],
1701
+ ["throw", newKeywordTester(["ThrowStatement"], "throw")],
1702
+ ["try", newKeywordTester(["TryStatement"], "try")],
1703
+ ["ts-method", newNodeTypeTester("TSMethodSignature")],
1704
+ ["while", newKeywordTester(["WhileStatement", "DoWhileStatement"], "while")],
1705
+ ["with", newKeywordTester(["WithStatement"], "with")],
1706
+ ...maybeMultilineStatementEntries.flatMap(([key, test]) => [
1707
+ [key, test],
1708
+ [`singleline-${key}`, (node, sourceCode) => test(node, sourceCode) && isSingleLine(node)],
1709
+ [`multiline-${key}`, (node, sourceCode) => test(node, sourceCode) && !isSingleLine(node)]
1710
+ ])
1711
+ ];
1712
+ const statementTypes = new Map(statementEntries);
1713
+ const statementTypeNames = [...statementTypes.keys()];
1714
+ /** Nodes that take part in padding checks when they are direct children of a container. */
1715
+ function isStatementLike(node) {
1716
+ return node.type.endsWith("Statement") || node.type.endsWith("Declaration") || node.type === "SwitchCase" || node.type === "TSDeclareFunction" || node.type === "TSMethodSignature";
1717
+ }
1718
+ function containerChildren(node) {
1719
+ switch (node.type) {
1720
+ case "BlockStatement":
1721
+ case "Program":
1722
+ case "StaticBlock":
1723
+ case "TSInterfaceBody":
1724
+ case "TSModuleBlock": return node.body;
1725
+ case "SwitchCase": return node.consequent;
1726
+ case "SwitchStatement": return node.cases;
1727
+ case "TSTypeLiteral": return node.members;
1728
+ default: return [];
1729
+ }
1730
+ }
1731
+ function getActualLastToken(node, sourceCode) {
1732
+ const semiToken = sourceCode.getLastToken(node);
1733
+ if (semiToken === null) throw new Error("padding-line-between-statements: statement has no tokens");
1734
+ const prevToken = sourceCode.getTokenBefore(semiToken);
1735
+ const nextToken = sourceCode.getTokenAfter(semiToken);
1736
+ return prevToken !== null && nextToken !== null && prevToken.range[0] >= node.range[0] && isSemicolonToken(semiToken) && !isTokenOnSameLine(prevToken, semiToken) && isTokenOnSameLine(semiToken, nextToken) ? prevToken : semiToken;
1737
+ }
1738
+ function getPaddingLineSequences(prevNode, nextNode, sourceCode) {
1739
+ const pairs = [];
1740
+ let prevToken = getActualLastToken(prevNode, sourceCode);
1741
+ if (nextNode.loc.start.line - prevToken.loc.end.line < 2) return pairs;
1742
+ do {
1743
+ const token = sourceCode.getTokenAfter(prevToken, { includeComments: true });
1744
+ if (token === null) break;
1745
+ if (token.loc.start.line - prevToken.loc.end.line >= 2) pairs.push([prevToken, token]);
1746
+ prevToken = token;
1747
+ } while (prevToken.range[0] < nextNode.range[0]);
1748
+ return pairs;
1749
+ }
1750
+ function getReportLoc(node, sourceCode) {
1751
+ if (isSingleLine(node)) return node.loc;
1752
+ const { line } = node.loc.start;
1753
+ return {
1754
+ start: node.loc.start,
1755
+ end: {
1756
+ line,
1757
+ column: sourceCode.lines[line - 1]?.length ?? 0
1758
+ }
1759
+ };
1760
+ }
1761
+ function replacerToRemovePaddingLines(_match, trailingSpaces, indentSpaces) {
1762
+ return trailingSpaces + indentSpaces;
1763
+ }
1764
+ function matchesSelector(node, option) {
1765
+ if (option.selector !== "*" && option.selector !== node.type) return false;
1766
+ if (option.lineMode === "singleline") return isSingleLine(node);
1767
+ if (option.lineMode === "multiline") return !isSingleLine(node);
1768
+ return true;
1769
+ }
1770
+ function matches(node, option, sourceCode) {
1771
+ let statement = node;
1772
+ while (statement.type === "LabeledStatement") statement = statement.body;
1773
+ if (typeof option === "string") return statementTypes.get(option)?.(statement, sourceCode) ?? false;
1774
+ if (isSelectorOption(option)) return matchesSelector(statement, option);
1775
+ return option.some((item) => matches(statement, item, sourceCode));
1776
+ }
1777
+ function paddingTypeFor(options, prevNode, nextNode, sourceCode) {
1778
+ for (let index = options.length - 1; index >= 0; index -= 1) {
1779
+ const option = options[index];
1780
+ if (option !== void 0 && matches(prevNode, option.prev, sourceCode) && matches(nextNode, option.next, sourceCode)) return option.blankLine;
1781
+ }
1782
+ return "any";
1783
+ }
1784
+ /** Require or disallow padding lines between statements. */
1785
+ const paddingLineBetweenStatementsRule = defineRule({
1786
+ meta: {
1787
+ type: "layout",
1788
+ docs: {
1789
+ url: "https://github.com/mkrz/oxlint-config/blob/main/docs/rules.md#padding-line-between-statements",
1790
+ description: "Require or disallow padding lines between statements."
1791
+ },
1792
+ fixable: "whitespace",
1793
+ schema: {
1794
+ $defs: {
1795
+ paddingType: {
1796
+ type: "string",
1797
+ enum: [
1798
+ "any",
1799
+ "never",
1800
+ "always"
1801
+ ]
1802
+ },
1803
+ statementType: {
1804
+ type: "string",
1805
+ enum: statementTypeNames
1806
+ },
1807
+ selectorOption: {
1808
+ type: "object",
1809
+ properties: {
1810
+ selector: { type: "string" },
1811
+ lineMode: {
1812
+ type: "string",
1813
+ enum: [
1814
+ "any",
1815
+ "singleline",
1816
+ "multiline"
1817
+ ]
1818
+ }
1819
+ },
1820
+ required: ["selector"],
1821
+ additionalProperties: false
1822
+ },
1823
+ statementMatcher: { anyOf: [{ $ref: "#/$defs/statementType" }, { $ref: "#/$defs/selectorOption" }] },
1824
+ statementOption: { anyOf: [{ $ref: "#/$defs/statementMatcher" }, {
1825
+ type: "array",
1826
+ items: { $ref: "#/$defs/statementMatcher" },
1827
+ minItems: 1,
1828
+ uniqueItems: true,
1829
+ additionalItems: false
1830
+ }] }
1831
+ },
1832
+ type: "array",
1833
+ additionalItems: false,
1834
+ items: {
1835
+ type: "object",
1836
+ properties: {
1837
+ blankLine: { $ref: "#/$defs/paddingType" },
1838
+ prev: { $ref: "#/$defs/statementOption" },
1839
+ next: { $ref: "#/$defs/statementOption" }
1840
+ },
1841
+ additionalProperties: false,
1842
+ required: [
1843
+ "blankLine",
1844
+ "prev",
1845
+ "next"
1846
+ ]
1847
+ }
1848
+ },
1849
+ messages: {
1850
+ expectedBlankLine: "Expected blank line before this statement.",
1851
+ unexpectedBlankLine: "Unexpected blank line before this statement."
1852
+ }
1853
+ },
1854
+ createOnce(context) {
1855
+ const options = [];
1856
+ const verifyNever = (nextNode, paddingLines) => {
1857
+ if (paddingLines.length === 0) return;
1858
+ context.report({
1859
+ node: nextNode,
1860
+ messageId: "unexpectedBlankLine",
1861
+ loc: getReportLoc(nextNode, context.sourceCode),
1862
+ fix(fixer) {
1863
+ const [pair] = paddingLines;
1864
+ if (paddingLines.length >= 2 || pair === void 0) return null;
1865
+ const [prevToken, nextToken] = pair;
1866
+ const start = prevToken.range[1];
1867
+ const end = nextToken.range[0];
1868
+ const text = context.sourceCode.text.slice(start, end).replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines);
1869
+ return fixer.replaceTextRange([start, end], text);
1870
+ }
1871
+ });
1872
+ };
1873
+ const verifyAlways = (prevNode, nextNode, paddingLines) => {
1874
+ if (paddingLines.length > 0) return;
1875
+ context.report({
1876
+ node: nextNode,
1877
+ messageId: "expectedBlankLine",
1878
+ loc: getReportLoc(nextNode, context.sourceCode),
1879
+ fix(fixer) {
1880
+ let prevToken = getActualLastToken(prevNode, context.sourceCode);
1881
+ const nextToken = context.sourceCode.getFirstTokenBetween(prevToken, nextNode, {
1882
+ includeComments: true,
1883
+ filter(token) {
1884
+ if (isTokenOnSameLine(prevToken, token)) {
1885
+ prevToken = token;
1886
+ return false;
1887
+ }
1888
+ return true;
1889
+ }
1890
+ }) ?? nextNode;
1891
+ const newline = /\r\n|[\r\n\u2028\u2029]/u.exec(context.sourceCode.text)?.[0] ?? "\n";
1892
+ const insertText = isTokenOnSameLine(prevToken, nextToken) ? newline + newline : newline;
1893
+ return fixer.insertTextAfter(prevToken, insertText);
1894
+ }
1895
+ });
1896
+ };
1897
+ const verifyPair = (prevNode, nextNode) => {
1898
+ const paddingType = paddingTypeFor(options, prevNode, nextNode, context.sourceCode);
1899
+ if (paddingType === "any") return;
1900
+ const paddingLines = getPaddingLineSequences(prevNode, nextNode, context.sourceCode);
1901
+ if (paddingType === "never") verifyNever(nextNode, paddingLines);
1902
+ else verifyAlways(prevNode, nextNode, paddingLines);
1903
+ };
1904
+ const verifyContainer = (node) => {
1905
+ if (!statementContainers.has(node.type)) return;
1906
+ let prevNode = null;
1907
+ for (const child of containerChildren(node)) {
1908
+ if (!isStatementLike(child)) continue;
1909
+ if (prevNode !== null) verifyPair(prevNode, child);
1910
+ prevNode = child;
1911
+ }
1912
+ };
1913
+ return {
1914
+ before() {
1915
+ options.length = 0;
1916
+ for (const option of context.options) if (isPaddingOption(option)) options.push(option);
1917
+ return options.length > 0;
1918
+ },
1919
+ BlockStatement: verifyContainer,
1920
+ Program: verifyContainer,
1921
+ StaticBlock: verifyContainer,
1922
+ SwitchCase: verifyContainer,
1923
+ SwitchStatement: verifyContainer,
1924
+ TSInterfaceBody: verifyContainer,
1925
+ TSModuleBlock: verifyContainer,
1926
+ TSTypeLiteral: verifyContainer
1927
+ };
1928
+ }
1929
+ });
1930
+ //#endregion
1931
+ //#region src/plugin/index.ts
1932
+ const plugin = eslintCompatPlugin({
1933
+ meta: { name: "@mkrz/oxlint-config" },
1934
+ rules: {
1935
+ "no-app-requires": noAppRequiresRule,
1936
+ "no-chained-type-assertions": noChainedTypeAssertionsRule,
1937
+ "no-known-value-widening": noKnownValueWideningRule,
1938
+ "no-let": noLetRule,
1939
+ "no-module-mocking": noModuleMockingRule,
1940
+ "no-oxlint-disable": noOxlintDisableRule,
1941
+ "no-query-result-destructuring": noQueryResultDestructuringRule,
1942
+ "no-reflect-apply": noReflectApplyRule,
1943
+ "no-reflect-get": noReflectGetRule,
1944
+ "no-restricted-react-hooks": noRestrictedReactHooksRule,
1945
+ "no-type-assertion": noTypeAssertionRule,
1946
+ "no-unknown-parameters": noUnknownParametersRule,
1947
+ "no-unknown-returns": noUnknownReturnsRule,
1948
+ "no-unknown-type-aliases": noUnknownTypeAliasesRule,
1949
+ "no-unsafe-dictionary-type": noUnsafeDictionaryTypeRule,
1950
+ "no-widen-then-assert": noWidenThenAssertRule,
1951
+ "package-disable-policy": packageDisablePolicyRule,
1952
+ "padding-line-between-statements": paddingLineBetweenStatementsRule,
1953
+ "require-store-selector": requireStoreSelectorRule
1954
+ }
1955
+ });
1956
+ //#endregion
1957
+ export { plugin as default };