@sarj/eslint-plugin 15.17.8 → 15.17.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1825 -1690
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +689 -554
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2406,7 +2406,7 @@ var no_duplicate_lifecycle_refresh_listeners_default = createRule({
|
|
|
2406
2406
|
VariableDeclarator(node) {
|
|
2407
2407
|
if (node.id.type !== AST_NODE_TYPES9.Identifier) return;
|
|
2408
2408
|
const variable = ASTUtils3.findVariable(context.sourceCode.getScope(node.id), node.id.name);
|
|
2409
|
-
if (variable === null) return;
|
|
2409
|
+
if (variable === null || variable.references.some((reference) => reference.isWrite() && !reference.init)) return;
|
|
2410
2410
|
if (node.init?.type === AST_NODE_TYPES9.ArrowFunctionExpression || node.init?.type === AST_NODE_TYPES9.FunctionExpression) {
|
|
2411
2411
|
functionCallbacks.set(node.init, variable);
|
|
2412
2412
|
}
|
|
@@ -2417,7 +2417,7 @@ var no_duplicate_lifecycle_refresh_listeners_default = createRule({
|
|
|
2417
2417
|
FunctionDeclaration(node) {
|
|
2418
2418
|
if (node.id === null) return;
|
|
2419
2419
|
const variable = ASTUtils3.findVariable(context.sourceCode.getScope(node.id), node.id.name);
|
|
2420
|
-
if (variable !== null) functionCallbacks.set(node, variable);
|
|
2420
|
+
if (variable !== null && !variable.references.some((reference) => reference.isWrite() && !reference.init)) functionCallbacks.set(node, variable);
|
|
2421
2421
|
},
|
|
2422
2422
|
CallExpression(node) {
|
|
2423
2423
|
const item = registration(context.sourceCode, node);
|
|
@@ -2455,8 +2455,51 @@ var no_duplicate_lifecycle_refresh_listeners_default = createRule({
|
|
|
2455
2455
|
}
|
|
2456
2456
|
});
|
|
2457
2457
|
|
|
2458
|
+
// src/rules/_exported-next-config-property.ts
|
|
2459
|
+
import { ASTUtils as ASTUtils4 } from "@typescript-eslint/utils";
|
|
2460
|
+
function unwrap(node) {
|
|
2461
|
+
while (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression") node = node.expression;
|
|
2462
|
+
return node;
|
|
2463
|
+
}
|
|
2464
|
+
function exportedNextConfigProperty(sourceCode, path) {
|
|
2465
|
+
const resolve2 = (input, seen = /* @__PURE__ */ new Set()) => {
|
|
2466
|
+
const node = unwrap(input);
|
|
2467
|
+
if (node.type !== "Identifier") return node;
|
|
2468
|
+
if (seen.has(node)) return null;
|
|
2469
|
+
seen.add(node);
|
|
2470
|
+
const binding = ASTUtils4.findVariable(sourceCode.getScope(node), node.name);
|
|
2471
|
+
const definition = binding?.defs.length === 1 ? binding.defs[0] : void 0;
|
|
2472
|
+
if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some((reference) => reference.identifier !== node && reference.init !== true)) return null;
|
|
2473
|
+
return resolve2(definition.node.init, seen);
|
|
2474
|
+
};
|
|
2475
|
+
let exported = null;
|
|
2476
|
+
for (const statement of sourceCode.ast.body) {
|
|
2477
|
+
if (statement.type === "ExportDefaultDeclaration") exported = statement.declaration;
|
|
2478
|
+
if (statement.type !== "ExpressionStatement" || statement.expression.type !== "AssignmentExpression" || statement.expression.operator !== "=" || sourceCode.ast.body.length !== 1) continue;
|
|
2479
|
+
const assignment = statement.expression;
|
|
2480
|
+
const left = assignment.left;
|
|
2481
|
+
if (left.type !== "MemberExpression" || left.computed || left.object.type !== "Identifier" || left.object.name !== "module" || left.property.type !== "Identifier" || left.property.name !== "exports") continue;
|
|
2482
|
+
const binding = ASTUtils4.findVariable(sourceCode.getScope(left.object), "module");
|
|
2483
|
+
if (binding === null || binding.defs.length === 0) exported = assignment.right;
|
|
2484
|
+
}
|
|
2485
|
+
if (exported === null) return null;
|
|
2486
|
+
let current = resolve2(exported);
|
|
2487
|
+
let selected = null;
|
|
2488
|
+
for (const name of path) {
|
|
2489
|
+
if (current?.type !== "ObjectExpression" || current.properties.some((property) => property.type !== "Property" || property.computed || property.kind !== "init")) return null;
|
|
2490
|
+
selected = null;
|
|
2491
|
+
for (const property of current.properties) {
|
|
2492
|
+
if (property.type !== "Property") continue;
|
|
2493
|
+
const key = property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? property.key.value : null;
|
|
2494
|
+
if (key === name) selected = property;
|
|
2495
|
+
}
|
|
2496
|
+
if (selected === null) return null;
|
|
2497
|
+
current = resolve2(selected.value);
|
|
2498
|
+
}
|
|
2499
|
+
return selected;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2458
2502
|
// src/rules/no-dangerously-allow-svg.ts
|
|
2459
|
-
import "@typescript-eslint/utils";
|
|
2460
2503
|
var NEXT_CONFIG_RE = /(?:^|\/)next\.config\.[cm]?[jt]s$/;
|
|
2461
2504
|
var NO_DANGEROUSLY_ALLOW_SVG_DOCUMENTATION = {
|
|
2462
2505
|
summary: "Next.js image configuration enables unsanitized SVG rendering",
|
|
@@ -2464,7 +2507,7 @@ var NO_DANGEROUSLY_ALLOW_SVG_DOCUMENTATION = {
|
|
|
2464
2507
|
remediation: "Keep dangerouslyAllowSVG disabled. If SVG delivery is unavoidable, use a separately reviewed asset path with restrictive Content-Disposition and Content-Security-Policy headers.",
|
|
2465
2508
|
category: "security",
|
|
2466
2509
|
limitations: [
|
|
2467
|
-
"Only a literal true
|
|
2510
|
+
"Only a literal true in the effective images property of a directly exported object, unescaped const alias, or isolated module.exports object is reported. Wrappers, factories, spreads, computed keys and mutations are not inferred."
|
|
2468
2511
|
],
|
|
2469
2512
|
examples: [
|
|
2470
2513
|
{
|
|
@@ -2487,11 +2530,6 @@ var NO_DANGEROUSLY_ALLOW_SVG_DOCUMENTATION = {
|
|
|
2487
2530
|
}
|
|
2488
2531
|
]
|
|
2489
2532
|
};
|
|
2490
|
-
function propertyName(node) {
|
|
2491
|
-
if (!node.computed && node.key.type === "Identifier") return node.key.name;
|
|
2492
|
-
if (node.key.type === "Literal" && typeof node.key.value === "string") return node.key.value;
|
|
2493
|
-
return null;
|
|
2494
|
-
}
|
|
2495
2533
|
var no_dangerously_allow_svg_default = createRule({
|
|
2496
2534
|
name: "no-dangerously-allow-svg",
|
|
2497
2535
|
documentation: NO_DANGEROUSLY_ALLOW_SVG_DOCUMENTATION,
|
|
@@ -2507,8 +2545,9 @@ var no_dangerously_allow_svg_default = createRule({
|
|
|
2507
2545
|
create(context) {
|
|
2508
2546
|
if (!NEXT_CONFIG_RE.test(context.filename.replaceAll("\\", "/"))) return {};
|
|
2509
2547
|
return {
|
|
2510
|
-
|
|
2511
|
-
|
|
2548
|
+
"Program:exit"() {
|
|
2549
|
+
const node = exportedNextConfigProperty(context.sourceCode, ["images", "dangerouslyAllowSVG"]);
|
|
2550
|
+
if (node !== null && node.value.type === "Literal" && node.value.value === true) {
|
|
2512
2551
|
context.report({ node, messageId: "noDangerouslyAllowSvg" });
|
|
2513
2552
|
}
|
|
2514
2553
|
}
|
|
@@ -3117,7 +3156,7 @@ function subtreeMatches(stmt, predicate, descendIntoFunctions = false) {
|
|
|
3117
3156
|
visit(stmt);
|
|
3118
3157
|
return found;
|
|
3119
3158
|
}
|
|
3120
|
-
function
|
|
3159
|
+
function unwrap2(expr) {
|
|
3121
3160
|
let current = expr;
|
|
3122
3161
|
while (current.type === AST_NODE_TYPES12.ChainExpression || current.type === AST_NODE_TYPES12.TSNonNullExpression) {
|
|
3123
3162
|
current = current.expression;
|
|
@@ -3145,7 +3184,7 @@ var hasThrowingCallOrNew = (node) => subtreeMatches(
|
|
|
3145
3184
|
(n) => n.type === AST_NODE_TYPES12.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES12.NewExpression && !isPureNew(n)
|
|
3146
3185
|
);
|
|
3147
3186
|
function isBareCallStatement(stmt) {
|
|
3148
|
-
return stmt.type === AST_NODE_TYPES12.ExpressionStatement &&
|
|
3187
|
+
return stmt.type === AST_NODE_TYPES12.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES12.CallExpression;
|
|
3149
3188
|
}
|
|
3150
3189
|
function isSimpleCatchFinallyOrchestration(node) {
|
|
3151
3190
|
const handler = node.handler;
|
|
@@ -3248,8 +3287,8 @@ function handlerEndsByHandingOff(handler) {
|
|
|
3248
3287
|
return last.type === AST_NODE_TYPES12.ExpressionStatement && unwrapAwait(last.expression).type === AST_NODE_TYPES12.CallExpression;
|
|
3249
3288
|
}
|
|
3250
3289
|
function unwrapAwait(expr) {
|
|
3251
|
-
const inner =
|
|
3252
|
-
return inner.type === AST_NODE_TYPES12.AwaitExpression ?
|
|
3290
|
+
const inner = unwrap2(expr);
|
|
3291
|
+
return inner.type === AST_NODE_TYPES12.AwaitExpression ? unwrap2(inner.argument) : inner;
|
|
3253
3292
|
}
|
|
3254
3293
|
function handlerMentionsCaughtBinding(handler) {
|
|
3255
3294
|
const names = caughtBindingNames(handler);
|
|
@@ -3574,7 +3613,7 @@ var NO_HAND_ROLLED_SPINNER_DOCUMENTATION = {
|
|
|
3574
3613
|
rationale: "One-off loading indicators duplicate a shared primitive and let accessibility and styling diverge.",
|
|
3575
3614
|
remediation: "Render the design-system Spinner component instead.",
|
|
3576
3615
|
category: "maintainability",
|
|
3577
|
-
limitations: ["Only static className values on div and span elements are inspected;
|
|
3616
|
+
limitations: ["Only effective static className values on div and span elements are inspected; a later spread makes the value unknown. Tests, stories, generated files, and the design-system implementation are excluded."],
|
|
3578
3617
|
examples: [
|
|
3579
3618
|
{ id: "design-system-spinner", title: "Use the shared spinner", outcome: "no-match", files: [{ path: "src/loading-state.tsx", source: '<Spinner className="size-4" />' }], focusPath: "src/loading-state.tsx", expectedCount: 0, public: true },
|
|
3580
3619
|
{ id: "border-ring-spinner", title: "Do not rebuild a spinner", outcome: "match", files: [{ path: "src/loading-state.tsx", source: '<div className="size-4 animate-spin rounded-full border-2 border-t-transparent" />' }], focusPath: "src/loading-state.tsx", expectedCount: 1, public: true }
|
|
@@ -3636,10 +3675,10 @@ var no_hand_rolled_spinner_default = createRule({
|
|
|
3636
3675
|
if (node.name.type !== AST_NODE_TYPES14.JSXIdentifier || node.name.name !== "div" && node.name.name !== "span") {
|
|
3637
3676
|
return;
|
|
3638
3677
|
}
|
|
3639
|
-
const classNameAttribute = node.attributes.find(
|
|
3640
|
-
(attribute) => attribute.type === AST_NODE_TYPES14.JSXAttribute && attribute.name.type === AST_NODE_TYPES14.JSXIdentifier && attribute.name.name === "className"
|
|
3678
|
+
const classNameAttribute = node.attributes.toReversed().find(
|
|
3679
|
+
(attribute) => attribute.type === AST_NODE_TYPES14.JSXSpreadAttribute || attribute.type === AST_NODE_TYPES14.JSXAttribute && attribute.name.type === AST_NODE_TYPES14.JSXIdentifier && attribute.name.name === "className"
|
|
3641
3680
|
);
|
|
3642
|
-
if (classNameAttribute
|
|
3681
|
+
if (classNameAttribute?.type !== AST_NODE_TYPES14.JSXAttribute) return;
|
|
3643
3682
|
const className = staticClassName(classNameAttribute);
|
|
3644
3683
|
if (className === null) return;
|
|
3645
3684
|
const classes = className.split(/\s+/u);
|
|
@@ -3882,15 +3921,15 @@ var no_insecure_random_id_default = createRule({
|
|
|
3882
3921
|
});
|
|
3883
3922
|
|
|
3884
3923
|
// src/rules/no-json-stringify-error.ts
|
|
3885
|
-
import "@typescript-eslint/utils";
|
|
3924
|
+
import { ASTUtils as ASTUtils5 } from "@typescript-eslint/utils";
|
|
3886
3925
|
var NO_JSON_STRINGIFY_ERROR_DOCUMENTATION = {
|
|
3887
|
-
summary: "
|
|
3926
|
+
summary: "Avoid generic JSON serialization that can omit native Error details.",
|
|
3888
3927
|
rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
|
|
3889
3928
|
remediation: "Serialize explicit error fields or use an error-aware serializer.",
|
|
3890
3929
|
category: "correctness",
|
|
3891
|
-
limitations: ["The rule uses local catch
|
|
3930
|
+
limitations: ["The rule uses stable local catch bindings and unshadowed built-in constructors, not runtime type information. Custom replacers are left to their serializer contract; a catch value is not guaranteed to be an Error."],
|
|
3892
3931
|
examples: [
|
|
3893
|
-
{ id: "explicit-error-message", title: "
|
|
3932
|
+
{ id: "explicit-error-message", title: "Narrow an unknown catch value before selecting fields", outcome: "no-match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err instanceof Error ? err.message : String(err) }); }" }], focusPath: "src/report.ts", expectedCount: 0, public: true },
|
|
3894
3933
|
{ id: "stringified-error", title: "Do not stringify an Error object", outcome: "match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err }); }" }], focusPath: "src/report.ts", expectedCount: 1, public: true }
|
|
3895
3934
|
]
|
|
3896
3935
|
};
|
|
@@ -3922,34 +3961,17 @@ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
|
3922
3961
|
"URIError"
|
|
3923
3962
|
]);
|
|
3924
3963
|
function identifierIsProvenError(identifier, scope) {
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
while (current !== null && !current.set.has(identifier.name)) {
|
|
3928
|
-
current = current.upper;
|
|
3929
|
-
}
|
|
3930
|
-
const variable = current?.set.get(identifier.name);
|
|
3931
|
-
if (variable === void 0 || variable.defs.length !== 1) return false;
|
|
3964
|
+
const variable = ASTUtils5.findVariable(scope, identifier.name);
|
|
3965
|
+
if (variable === null || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
|
|
3932
3966
|
const definition = variable.defs[0];
|
|
3967
|
+
if (definition?.type === "CatchClause") return true;
|
|
3933
3968
|
if (definition?.type !== "Variable") return false;
|
|
3934
3969
|
const initializer = definition.node.init;
|
|
3935
|
-
return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) &&
|
|
3936
|
-
(reference) => !reference.isWrite() || reference.init === true
|
|
3937
|
-
);
|
|
3970
|
+
return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && isGlobalIdentifier(initializer.callee.name, scope);
|
|
3938
3971
|
}
|
|
3939
|
-
function
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
const variable = current.set.get(name);
|
|
3943
|
-
if (variable) {
|
|
3944
|
-
for (const def of variable.defs) {
|
|
3945
|
-
if (def.type === "CatchClause") {
|
|
3946
|
-
return true;
|
|
3947
|
-
}
|
|
3948
|
-
}
|
|
3949
|
-
}
|
|
3950
|
-
current = current.upper;
|
|
3951
|
-
}
|
|
3952
|
-
return false;
|
|
3972
|
+
function isGlobalIdentifier(name, scope) {
|
|
3973
|
+
const binding = ASTUtils5.findVariable(scope, name);
|
|
3974
|
+
return binding === null || binding.defs.length === 0;
|
|
3953
3975
|
}
|
|
3954
3976
|
function positiveErrorSubject(test) {
|
|
3955
3977
|
return instanceofErrorSubject(test) ?? typeGuardSubject(test);
|
|
@@ -4057,7 +4079,7 @@ function expressionSuggestsError(expression, scope) {
|
|
|
4057
4079
|
return identifierIsProvenError(expression, scope);
|
|
4058
4080
|
}
|
|
4059
4081
|
if (expression.type === "NewExpression" && expression.callee.type === "Identifier") {
|
|
4060
|
-
return BUILTIN_ERROR_CONSTRUCTORS.has(expression.callee.name);
|
|
4082
|
+
return BUILTIN_ERROR_CONSTRUCTORS.has(expression.callee.name) && isGlobalIdentifier(expression.callee.name, scope);
|
|
4061
4083
|
}
|
|
4062
4084
|
return expression.type === "MemberExpression" && memberSuggestsError(expression, scope);
|
|
4063
4085
|
}
|
|
@@ -4080,11 +4102,11 @@ var no_json_stringify_error_default = createRule({
|
|
|
4080
4102
|
meta: {
|
|
4081
4103
|
type: "problem",
|
|
4082
4104
|
docs: {
|
|
4083
|
-
description: "
|
|
4105
|
+
description: "Avoid generic JSON serialization that can omit native Error details."
|
|
4084
4106
|
},
|
|
4085
4107
|
schema: [],
|
|
4086
4108
|
messages: {
|
|
4087
|
-
noJsonStringifyError: "`JSON.stringify`
|
|
4109
|
+
noJsonStringifyError: "Generic `JSON.stringify` can omit non-enumerable Error details such as message and stack. Serialize explicit fields or use an error-aware serializer."
|
|
4088
4110
|
}
|
|
4089
4111
|
},
|
|
4090
4112
|
defaultOptions: [],
|
|
@@ -4099,6 +4121,9 @@ var no_json_stringify_error_default = createRule({
|
|
|
4099
4121
|
return;
|
|
4100
4122
|
}
|
|
4101
4123
|
const scope = context.sourceCode.getScope(firstArg);
|
|
4124
|
+
if (!isGlobalIdentifier("JSON", scope)) return;
|
|
4125
|
+
const replacer = node.arguments[1];
|
|
4126
|
+
if (replacer !== void 0 && !(replacer.type === "Literal" && replacer.value === null) && !(replacer.type === "Identifier" && replacer.name === "undefined" && isGlobalIdentifier("undefined", scope))) return;
|
|
4102
4127
|
const unsafeValue = directLiteralValues(firstArg).find(
|
|
4103
4128
|
(value) => expressionSuggestsError(value, scope) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
|
|
4104
4129
|
);
|
|
@@ -4409,105 +4434,9 @@ import * as ts from "typescript";
|
|
|
4409
4434
|
import {
|
|
4410
4435
|
AST_NODE_TYPES as AST_NODE_TYPES16
|
|
4411
4436
|
} from "@typescript-eslint/utils";
|
|
4412
|
-
function symbolAt(services, checker, node) {
|
|
4413
|
-
return checker.getSymbolAtLocation(services.esTreeNodeToTSNodeMap.get(node));
|
|
4414
|
-
}
|
|
4415
|
-
function sameSymbol(left, right) {
|
|
4416
|
-
return left !== void 0 && right !== void 0 && left === right;
|
|
4417
|
-
}
|
|
4418
|
-
function enclosingClass(node) {
|
|
4419
|
-
let current = node.parent;
|
|
4420
|
-
while (current !== void 0) {
|
|
4421
|
-
if (current.type === AST_NODE_TYPES16.ClassDeclaration || current.type === AST_NODE_TYPES16.ClassExpression) {
|
|
4422
|
-
return current;
|
|
4423
|
-
}
|
|
4424
|
-
current = current.parent;
|
|
4425
|
-
}
|
|
4426
|
-
return null;
|
|
4427
|
-
}
|
|
4428
4437
|
function memberName2(member) {
|
|
4429
4438
|
return !member.computed && member.key.type === AST_NODE_TYPES16.Identifier ? member.key.name : null;
|
|
4430
4439
|
}
|
|
4431
|
-
function privateMemberFixes(context, services, owner, members, removePrivateKeyword) {
|
|
4432
|
-
const first = members[0];
|
|
4433
|
-
const name = first === void 0 ? null : memberName2(first);
|
|
4434
|
-
if (first === void 0 || name === null || members.some((member) => member.static || member.decorators.length > 0)) {
|
|
4435
|
-
return void 0;
|
|
4436
|
-
}
|
|
4437
|
-
if (owner.body.body.some((member) => {
|
|
4438
|
-
if (member.type !== AST_NODE_TYPES16.MethodDefinition && member.type !== AST_NODE_TYPES16.PropertyDefinition && member.type !== AST_NODE_TYPES16.AccessorProperty) return false;
|
|
4439
|
-
return member.key.type === AST_NODE_TYPES16.PrivateIdentifier && member.key.name === name;
|
|
4440
|
-
})) return void 0;
|
|
4441
|
-
const selectedMembers = new Set(members);
|
|
4442
|
-
if (owner.body.body.some((member) => {
|
|
4443
|
-
if (member.type !== AST_NODE_TYPES16.MethodDefinition && member.type !== AST_NODE_TYPES16.PropertyDefinition && member.type !== AST_NODE_TYPES16.AccessorProperty) return false;
|
|
4444
|
-
return memberName2(member) === name && !selectedMembers.has(member);
|
|
4445
|
-
})) return void 0;
|
|
4446
|
-
const checker = services.program.getTypeChecker();
|
|
4447
|
-
const symbols = members.map((member) => symbolAt(services, checker, member.key)).filter(
|
|
4448
|
-
(symbol) => symbol !== void 0
|
|
4449
|
-
);
|
|
4450
|
-
if (symbols.length === 0) return void 0;
|
|
4451
|
-
const references = [];
|
|
4452
|
-
let unsafe = false;
|
|
4453
|
-
walk(context.sourceCode.ast, context.sourceCode.visitorKeys, (node) => {
|
|
4454
|
-
if (node.type === AST_NODE_TYPES16.Literal && node.value === name) {
|
|
4455
|
-
unsafe = true;
|
|
4456
|
-
return;
|
|
4457
|
-
}
|
|
4458
|
-
if (node.type !== AST_NODE_TYPES16.MemberExpression) return;
|
|
4459
|
-
const propertyName8 = node.property.type === AST_NODE_TYPES16.Identifier || node.property.type === AST_NODE_TYPES16.PrivateIdentifier ? node.property.name : node.property.type === AST_NODE_TYPES16.Literal && typeof node.property.value === "string" ? node.property.value : null;
|
|
4460
|
-
if (propertyName8 !== name) return;
|
|
4461
|
-
const propertySymbol = symbolAt(services, checker, node.property);
|
|
4462
|
-
if (node.computed || node.property.type !== AST_NODE_TYPES16.Identifier || node.object.type !== AST_NODE_TYPES16.ThisExpression || enclosingClass(node) !== owner || !symbols.some((symbol) => sameSymbol(symbol, propertySymbol))) {
|
|
4463
|
-
unsafe = true;
|
|
4464
|
-
return;
|
|
4465
|
-
}
|
|
4466
|
-
references.push(node);
|
|
4467
|
-
});
|
|
4468
|
-
if (unsafe) return void 0;
|
|
4469
|
-
const privateKeywordRanges = /* @__PURE__ */ new Map();
|
|
4470
|
-
if (removePrivateKeyword) {
|
|
4471
|
-
const comments = context.sourceCode.getAllComments();
|
|
4472
|
-
for (const member of members) {
|
|
4473
|
-
const keyword = context.sourceCode.getTokens(member).find((token) => token.value === "private");
|
|
4474
|
-
const next = keyword === void 0 ? void 0 : context.sourceCode.getTokenAfter(keyword);
|
|
4475
|
-
if (keyword === void 0 || next === null || next === void 0) return void 0;
|
|
4476
|
-
if (comments.some((comment) => comment.range[0] >= keyword.range[1] && comment.range[1] <= next.range[0])) {
|
|
4477
|
-
return void 0;
|
|
4478
|
-
}
|
|
4479
|
-
privateKeywordRanges.set(member, [keyword.range[0], next.range[0]]);
|
|
4480
|
-
}
|
|
4481
|
-
}
|
|
4482
|
-
return (fixer) => {
|
|
4483
|
-
const fixes = [];
|
|
4484
|
-
for (const member of members) {
|
|
4485
|
-
fixes.push(fixer.replaceText(member.key, `#${name}`));
|
|
4486
|
-
if (removePrivateKeyword) {
|
|
4487
|
-
const range = privateKeywordRanges.get(member);
|
|
4488
|
-
if (range === void 0) return [];
|
|
4489
|
-
fixes.push(fixer.removeRange(range));
|
|
4490
|
-
}
|
|
4491
|
-
}
|
|
4492
|
-
for (const reference of references) fixes.push(fixer.replaceText(reference.property, `#${name}`));
|
|
4493
|
-
return fixes;
|
|
4494
|
-
};
|
|
4495
|
-
}
|
|
4496
|
-
function walk(node, visitorKeys, visit) {
|
|
4497
|
-
visit(node);
|
|
4498
|
-
for (const key of visitorKeys[node.type] ?? []) {
|
|
4499
|
-
const child = node[key];
|
|
4500
|
-
if (Array.isArray(child)) {
|
|
4501
|
-
for (const item of child) {
|
|
4502
|
-
if (typeof item === "object" && item !== null && "type" in item) {
|
|
4503
|
-
walk(item, visitorKeys, visit);
|
|
4504
|
-
}
|
|
4505
|
-
}
|
|
4506
|
-
} else if (typeof child === "object" && child !== null && "type" in child) {
|
|
4507
|
-
walk(child, visitorKeys, visit);
|
|
4508
|
-
}
|
|
4509
|
-
}
|
|
4510
|
-
}
|
|
4511
4440
|
function convertibleMemberName(member) {
|
|
4512
4441
|
if (member.type !== AST_NODE_TYPES16.MethodDefinition && member.type !== AST_NODE_TYPES16.PropertyDefinition && member.type !== AST_NODE_TYPES16.AccessorProperty) return null;
|
|
4513
4442
|
return memberName2(member);
|
|
@@ -4620,7 +4549,7 @@ var interface_contract_members_private_default = createRule({
|
|
|
4620
4549
|
});
|
|
4621
4550
|
|
|
4622
4551
|
// src/rules/no-log-only-catch.ts
|
|
4623
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES18, ASTUtils as
|
|
4552
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES18, ASTUtils as ASTUtils6 } from "@typescript-eslint/utils";
|
|
4624
4553
|
|
|
4625
4554
|
// src/rules/_logging.ts
|
|
4626
4555
|
import "@typescript-eslint/utils";
|
|
@@ -4792,7 +4721,7 @@ function seededFallbackHandled(tryStatement, scope) {
|
|
|
4792
4721
|
if (previous.declarations.length !== 1 || declarator === void 0) return false;
|
|
4793
4722
|
if (declarator.id.type !== AST_NODE_TYPES18.Identifier) return false;
|
|
4794
4723
|
if (declarator.init == null || !isSeedValue(declarator.init)) return false;
|
|
4795
|
-
const variable =
|
|
4724
|
+
const variable = ASTUtils6.findVariable(scope, declarator.id.name);
|
|
4796
4725
|
if (variable === null) return false;
|
|
4797
4726
|
const [tryStart, tryEnd] = tryStatement.block.range;
|
|
4798
4727
|
let writtenInTry = false;
|
|
@@ -4897,7 +4826,7 @@ var no_log_only_catch_default = createRule({
|
|
|
4897
4826
|
});
|
|
4898
4827
|
|
|
4899
4828
|
// src/rules/no-bare-return-from-test-catch.ts
|
|
4900
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES19, ASTUtils as
|
|
4829
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES19, ASTUtils as ASTUtils7 } from "@typescript-eslint/utils";
|
|
4901
4830
|
var NO_BARE_RETURN_FROM_TEST_CATCH_DOCUMENTATION = {
|
|
4902
4831
|
summary: "Disallow a bare return from a test catch block when it skips a later assertion.",
|
|
4903
4832
|
rationale: "The caught failure turns into a passing test without executing the assertion that follows it.",
|
|
@@ -4922,7 +4851,7 @@ function staticMemberName2(node) {
|
|
|
4922
4851
|
return null;
|
|
4923
4852
|
}
|
|
4924
4853
|
function importedName3(identifier, context, modules) {
|
|
4925
|
-
const variable =
|
|
4854
|
+
const variable = ASTUtils7.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
4926
4855
|
if (variable === null || variable.defs.length === 0) return identifier.name;
|
|
4927
4856
|
for (const definition of variable.defs) {
|
|
4928
4857
|
if (definition.node.type !== AST_NODE_TYPES19.ImportSpecifier) continue;
|
|
@@ -5062,7 +4991,7 @@ var ADAPTER_BASENAME_RE = /(?:^|[-_.])adapters?(?:[-_.]|$)/i;
|
|
|
5062
4991
|
var API_BOUNDARY_IMPORT_RE = /(?:^|[/_.-])(?:api|client|sdk|contract|generated)(?:$|[/_.-])/i;
|
|
5063
4992
|
var SNAKE_CASE_RE = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/;
|
|
5064
4993
|
var LOWER_CAMEL_CASE_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
5065
|
-
function
|
|
4994
|
+
function propertyName(node) {
|
|
5066
4995
|
return node.type === AST_NODE_TYPES20.Identifier ? node.name : null;
|
|
5067
4996
|
}
|
|
5068
4997
|
function memberName3(node) {
|
|
@@ -5112,7 +5041,7 @@ var no_bespoke_api_case_conversion_default = createRule({
|
|
|
5112
5041
|
return {
|
|
5113
5042
|
Property(node) {
|
|
5114
5043
|
if (node.computed || node.method || node.shorthand) return;
|
|
5115
|
-
const key =
|
|
5044
|
+
const key = propertyName(node.key);
|
|
5116
5045
|
const value = memberName3(node.value);
|
|
5117
5046
|
if (key === null || value === null || !isDirectCaseTranslation(key, value)) return;
|
|
5118
5047
|
const wireName = SNAKE_CASE_RE.test(key) ? key : value;
|
|
@@ -5306,15 +5235,15 @@ var no_vague_suppression_description_default = createRule({
|
|
|
5306
5235
|
});
|
|
5307
5236
|
|
|
5308
5237
|
// src/rules/no-generic-single-export-module.ts
|
|
5309
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES22, ASTUtils as
|
|
5238
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES22, ASTUtils as ASTUtils8 } from "@typescript-eslint/utils";
|
|
5310
5239
|
var NO_GENERIC_SINGLE_EXPORT_MODULE_DOCUMENTATION = {
|
|
5311
5240
|
summary: "Disallow generic module stems when one runtime export already names the responsibility.",
|
|
5312
5241
|
rationale: "A generic filename hides the sole exported responsibility and makes navigation less descriptive.",
|
|
5313
5242
|
remediation: "Choose a responsibility-bearing module name or colocate the export with its domain.",
|
|
5314
5243
|
category: "maintainability",
|
|
5315
|
-
limitations: ["Only
|
|
5244
|
+
limitations: ["Only the fixed generic-stem vocabulary with exactly one public runtime export is checked; exported destructuring patterns are excluded rather than undercounted."],
|
|
5316
5245
|
examples: [
|
|
5317
|
-
{ id: "responsibility-named-module", title: "Name the module after its export", outcome: "no-match", files: [{ path: "src/order
|
|
5246
|
+
{ id: "responsibility-named-module", title: "Name the module after its export", outcome: "no-match", files: [{ path: "src/parse-order.ts", source: "export function parseOrder() { return {}; }" }], focusPath: "src/parse-order.ts", expectedCount: 0, public: true },
|
|
5318
5247
|
{ id: "generic-module-name", title: "Do not hide one export in a generic module", outcome: "match", files: [{ path: "src/utils.ts", source: "export function parseOrder() { return {}; }" }], focusPath: "src/utils.ts", expectedCount: 1, public: true }
|
|
5319
5248
|
]
|
|
5320
5249
|
};
|
|
@@ -5375,6 +5304,7 @@ function runtimeExports(program) {
|
|
|
5375
5304
|
}
|
|
5376
5305
|
if (statement.declaration !== null) {
|
|
5377
5306
|
const declaration = statement.declaration;
|
|
5307
|
+
if (declaration.type === AST_NODE_TYPES22.VariableDeclaration && declaration.declarations.some((item) => item.id.type !== AST_NODE_TYPES22.Identifier)) ambiguous = true;
|
|
5378
5308
|
exports.push(...declaredNames(declaration).map((name) => ({ key: name, name, node: declaration })));
|
|
5379
5309
|
}
|
|
5380
5310
|
for (const specifier of statement.specifiers) {
|
|
@@ -5431,8 +5361,8 @@ function typeOnlyBindings(program) {
|
|
|
5431
5361
|
}
|
|
5432
5362
|
return new Set([...names].filter((name) => !runtimeNames.has(name)));
|
|
5433
5363
|
}
|
|
5434
|
-
function
|
|
5435
|
-
const variable =
|
|
5364
|
+
function isGlobalIdentifier2(context, node) {
|
|
5365
|
+
const variable = ASTUtils8.findVariable(context.sourceCode.getScope(node), node.name);
|
|
5436
5366
|
return variable === null || variable.defs.length === 0;
|
|
5437
5367
|
}
|
|
5438
5368
|
function isConventionalFrameworkUtility(filename, exported) {
|
|
@@ -5463,11 +5393,11 @@ var no_generic_single_export_module_default = createRule({
|
|
|
5463
5393
|
return {
|
|
5464
5394
|
CallExpression(node) {
|
|
5465
5395
|
const first = node.arguments[0];
|
|
5466
|
-
if (first?.type === AST_NODE_TYPES22.Identifier && first.name === "exports" &&
|
|
5396
|
+
if (first?.type === AST_NODE_TYPES22.Identifier && first.name === "exports" && isGlobalIdentifier2(context, first) && node.callee.type === AST_NODE_TYPES22.MemberExpression && node.callee.object.type === AST_NODE_TYPES22.Identifier && node.callee.object.name === "Object" && isGlobalIdentifier2(context, node.callee.object) && memberPropertyName(node.callee) !== null && CJS_OBJECT_EXPORT_METHODS.has(memberPropertyName(node.callee))) hasCommonJsExport = true;
|
|
5467
5397
|
},
|
|
5468
5398
|
MemberExpression(node) {
|
|
5469
|
-
if (node.object.type === AST_NODE_TYPES22.Identifier && node.object.name === "exports" &&
|
|
5470
|
-
if (node.object.type === AST_NODE_TYPES22.Identifier && node.object.name === "module" &&
|
|
5399
|
+
if (node.object.type === AST_NODE_TYPES22.Identifier && node.object.name === "exports" && isGlobalIdentifier2(context, node.object)) hasCommonJsExport = true;
|
|
5400
|
+
if (node.object.type === AST_NODE_TYPES22.Identifier && node.object.name === "module" && isGlobalIdentifier2(context, node.object) && memberPropertyName(node) === "exports") hasCommonJsExport = true;
|
|
5471
5401
|
},
|
|
5472
5402
|
"Program:exit"(program) {
|
|
5473
5403
|
if (hasCommonJsExport) return;
|
|
@@ -5533,7 +5463,7 @@ var no_offset_pagination_default = createRule({
|
|
|
5533
5463
|
});
|
|
5534
5464
|
|
|
5535
5465
|
// src/rules/no-positional-tuple-return.ts
|
|
5536
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES23 } from "@typescript-eslint/utils";
|
|
5466
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES23, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
|
|
5537
5467
|
var NO_POSITIONAL_TUPLE_RETURN_DOCUMENTATION = {
|
|
5538
5468
|
summary: "Disallow returning a multi-field tuple from a named function; return a named object so call sites cannot mismatch slots.",
|
|
5539
5469
|
rationale: "Tuple fields are identified only by position, so reordering can preserve types while changing meaning.",
|
|
@@ -5563,7 +5493,7 @@ function tupleReturnType(node, aliases, resolving = /* @__PURE__ */ new Set()) {
|
|
|
5563
5493
|
return argument === void 0 ? null : tupleReturnType(argument, aliases, resolving);
|
|
5564
5494
|
}
|
|
5565
5495
|
if (node.type === AST_NODE_TYPES23.TSTypeReference && node.typeName.type === AST_NODE_TYPES23.Identifier && !resolving.has(node.typeName.name)) {
|
|
5566
|
-
const target = aliases.get(node.typeName
|
|
5496
|
+
const target = aliases.get(node.typeName);
|
|
5567
5497
|
if (target !== void 0) return tupleReturnType(target, aliases, /* @__PURE__ */ new Set([...resolving, node.typeName.name]));
|
|
5568
5498
|
}
|
|
5569
5499
|
if (node.type === AST_NODE_TYPES23.TSTypeOperator && node.operator === "readonly") {
|
|
@@ -5655,20 +5585,27 @@ function exportedTypeNames(program) {
|
|
|
5655
5585
|
}
|
|
5656
5586
|
return names;
|
|
5657
5587
|
}
|
|
5658
|
-
function typeAliases(
|
|
5588
|
+
function typeAliases(sourceCode) {
|
|
5659
5589
|
const aliases = /* @__PURE__ */ new Map();
|
|
5660
|
-
for (const statement of
|
|
5590
|
+
for (const statement of sourceCode.ast.body) {
|
|
5661
5591
|
const declaration = statement.type === AST_NODE_TYPES23.ExportNamedDeclaration ? statement.declaration : statement;
|
|
5662
5592
|
if (declaration?.type === AST_NODE_TYPES23.TSTypeAliasDeclaration) {
|
|
5663
|
-
aliases.set(declaration.id.name, declaration
|
|
5593
|
+
aliases.set(declaration.id.name, declaration);
|
|
5664
5594
|
}
|
|
5665
5595
|
}
|
|
5666
|
-
return
|
|
5596
|
+
return {
|
|
5597
|
+
get(identifier) {
|
|
5598
|
+
const declaration = aliases.get(identifier.name);
|
|
5599
|
+
if (declaration === void 0) return void 0;
|
|
5600
|
+
const binding = ASTUtils9.findVariable(sourceCode.getScope(identifier), identifier.name);
|
|
5601
|
+
return binding?.defs.length === 1 && binding.defs[0]?.node === declaration ? declaration.typeAnnotation : void 0;
|
|
5602
|
+
}
|
|
5603
|
+
};
|
|
5667
5604
|
}
|
|
5668
5605
|
function callableReturnType(node, aliases, resolving = /* @__PURE__ */ new Set()) {
|
|
5669
5606
|
if (node.type === AST_NODE_TYPES23.TSFunctionType) return node.returnType?.typeAnnotation ?? null;
|
|
5670
5607
|
if (node.type === AST_NODE_TYPES23.TSTypeReference && node.typeName.type === AST_NODE_TYPES23.Identifier && !resolving.has(node.typeName.name)) {
|
|
5671
|
-
const target = aliases.get(node.typeName
|
|
5608
|
+
const target = aliases.get(node.typeName);
|
|
5672
5609
|
if (target !== void 0) {
|
|
5673
5610
|
return callableReturnType(target, aliases, /* @__PURE__ */ new Set([...resolving, node.typeName.name]));
|
|
5674
5611
|
}
|
|
@@ -5797,7 +5734,7 @@ var no_positional_tuple_return_default = createRule({
|
|
|
5797
5734
|
context.sourceCode.ast,
|
|
5798
5735
|
exportedTypeNames(context.sourceCode.ast)
|
|
5799
5736
|
);
|
|
5800
|
-
const aliases = typeAliases(context.sourceCode
|
|
5737
|
+
const aliases = typeAliases(context.sourceCode);
|
|
5801
5738
|
const reportedFunctions = /* @__PURE__ */ new WeakSet();
|
|
5802
5739
|
const functionStack = [];
|
|
5803
5740
|
const report2 = (annotation, name) => {
|
|
@@ -5905,7 +5842,6 @@ var no_positional_tuple_return_default = createRule({
|
|
|
5905
5842
|
});
|
|
5906
5843
|
|
|
5907
5844
|
// src/rules/no-production-browser-source-maps.ts
|
|
5908
|
-
import "@typescript-eslint/utils";
|
|
5909
5845
|
var NEXT_CONFIG_RE2 = /(?:^|\/)next\.config\.[cm]?[jt]s$/;
|
|
5910
5846
|
var NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION = {
|
|
5911
5847
|
summary: "Next.js production browser source maps expose application source",
|
|
@@ -5913,7 +5849,7 @@ var NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION = {
|
|
|
5913
5849
|
remediation: "Leave productionBrowserSourceMaps disabled and upload private source maps directly to the error-monitoring service during the build.",
|
|
5914
5850
|
category: "security",
|
|
5915
5851
|
limitations: [
|
|
5916
|
-
"Only a literal true
|
|
5852
|
+
"Only a literal true in the effective property of a directly exported object, unescaped const alias, or isolated module.exports object is reported. Wrappers, factories, spreads, computed keys and mutations are not inferred."
|
|
5917
5853
|
],
|
|
5918
5854
|
examples: [
|
|
5919
5855
|
{
|
|
@@ -5936,11 +5872,6 @@ var NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION = {
|
|
|
5936
5872
|
}
|
|
5937
5873
|
]
|
|
5938
5874
|
};
|
|
5939
|
-
function propertyName3(node) {
|
|
5940
|
-
if (!node.computed && node.key.type === "Identifier") return node.key.name;
|
|
5941
|
-
if (node.key.type === "Literal" && typeof node.key.value === "string") return node.key.value;
|
|
5942
|
-
return null;
|
|
5943
|
-
}
|
|
5944
5875
|
var no_production_browser_source_maps_default = createRule({
|
|
5945
5876
|
name: "no-production-browser-source-maps",
|
|
5946
5877
|
documentation: NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION,
|
|
@@ -5956,8 +5887,9 @@ var no_production_browser_source_maps_default = createRule({
|
|
|
5956
5887
|
create(context) {
|
|
5957
5888
|
if (!NEXT_CONFIG_RE2.test(context.filename.replaceAll("\\", "/"))) return {};
|
|
5958
5889
|
return {
|
|
5959
|
-
|
|
5960
|
-
|
|
5890
|
+
"Program:exit"() {
|
|
5891
|
+
const node = exportedNextConfigProperty(context.sourceCode, ["productionBrowserSourceMaps"]);
|
|
5892
|
+
if (node !== null && node.value.type === "Literal" && node.value.value === true) {
|
|
5961
5893
|
context.report({ node, messageId: "noProductionBrowserSourceMaps" });
|
|
5962
5894
|
}
|
|
5963
5895
|
}
|
|
@@ -5966,13 +5898,13 @@ var no_production_browser_source_maps_default = createRule({
|
|
|
5966
5898
|
});
|
|
5967
5899
|
|
|
5968
5900
|
// src/rules/no-raw-env.ts
|
|
5969
|
-
import "@typescript-eslint/utils";
|
|
5901
|
+
import { ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
|
|
5970
5902
|
var NO_RAW_ENV_DOCUMENTATION = {
|
|
5971
5903
|
summary: "Disallow direct `process.env` and `import.meta.env` reads outside validated boundaries.",
|
|
5972
5904
|
rationale: "Raw environment reads are untyped and defer invalid configuration failures until use.",
|
|
5973
5905
|
remediation: "Validate environment values at startup and import the typed configuration object.",
|
|
5974
5906
|
category: "correctness",
|
|
5975
|
-
limitations: ["Host markers, assignment targets, tests, scripts, build config, and
|
|
5907
|
+
limitations: ["Host markers, assignment targets, tests, scripts, build config, and recognized validation-boundary files are policy exemptions. A validation call does not prove every export is validated; assignment-target exemptions also include compound writes that read the previous value."],
|
|
5976
5908
|
examples: [
|
|
5977
5909
|
{ id: "validated-environment", title: "Read validated configuration", outcome: "no-match", files: [{ path: "src/database.ts", source: "import { env } from './env.js'; const url = env.DATABASE_URL;" }], focusPath: "src/database.ts", expectedCount: 0, public: true },
|
|
5978
5910
|
{ id: "raw-environment-read", title: "Do not read raw configuration", outcome: "match", files: [{ path: "src/database.ts", source: "const url = process.env.DATABASE_URL;" }], focusPath: "src/database.ts", expectedCount: 1, public: true }
|
|
@@ -5980,10 +5912,6 @@ var NO_RAW_ENV_DOCUMENTATION = {
|
|
|
5980
5912
|
};
|
|
5981
5913
|
var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
|
|
5982
5914
|
var ENV_BOUNDARY_FILE_RE = /(^|[\\/])(?:env|client-env|server-env|client-settings|server-settings)\.[cm]?[jt]sx?$/;
|
|
5983
|
-
var ENV_VALIDATION_MARKER_RE = /\bcreateEnv\s*\(|\bz\.object\s*\(|\.(?:safeParse|parse)\s*\(/;
|
|
5984
|
-
function isValidatedEnvBoundary(filename, sourceText) {
|
|
5985
|
-
return ENV_BOUNDARY_FILE_RE.test(filename.replaceAll("\\", "/")) && ENV_VALIDATION_MARKER_RE.test(sourceText);
|
|
5986
|
-
}
|
|
5987
5915
|
function isProcessEnv(node) {
|
|
5988
5916
|
return !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
|
|
5989
5917
|
}
|
|
@@ -6038,30 +5966,43 @@ var no_raw_env_default = createRule({
|
|
|
6038
5966
|
defaultOptions: [],
|
|
6039
5967
|
create(context) {
|
|
6040
5968
|
const filename = context.filename;
|
|
6041
|
-
if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/"))
|
|
5969
|
+
if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/"))) {
|
|
6042
5970
|
return {};
|
|
6043
5971
|
}
|
|
5972
|
+
const reads = [];
|
|
5973
|
+
const boundaryFile = ENV_BOUNDARY_FILE_RE.test(filename.replaceAll("\\", "/"));
|
|
5974
|
+
let hasValidationCall = false;
|
|
6044
5975
|
return {
|
|
5976
|
+
CallExpression(node) {
|
|
5977
|
+
if (!boundaryFile) return;
|
|
5978
|
+
const callee = node.callee;
|
|
5979
|
+
if (callee.type === "Identifier" && callee.name === "createEnv" || callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier" && (["parse", "safeParse"].includes(callee.property.name) || callee.property.name === "object" && callee.object.type === "Identifier" && callee.object.name === "z")) hasValidationCall = true;
|
|
5980
|
+
},
|
|
6045
5981
|
MemberExpression(node) {
|
|
5982
|
+
if (isProcessEnv(node) && node.object.type === "Identifier") {
|
|
5983
|
+
const binding = ASTUtils10.findVariable(context.sourceCode.getScope(node), node.object.name);
|
|
5984
|
+
if (binding !== null && binding.defs.length > 0 && !binding.defs.every((definition) => definition.type === "ImportBinding" && definition.parent.type === "ImportDeclaration" && ["node:process", "process"].includes(definition.parent.source.value) && ["ImportDefaultSpecifier", "ImportNamespaceSpecifier"].includes(definition.node.type))) return;
|
|
5985
|
+
}
|
|
6046
5986
|
if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isExemptVariableAccess(node) && !isWriteTarget(node) && !isWholeEnvSpread(node)) {
|
|
6047
|
-
|
|
6048
|
-
node,
|
|
6049
|
-
messageId: "noRawEnv"
|
|
6050
|
-
});
|
|
5987
|
+
reads.push(node);
|
|
6051
5988
|
}
|
|
5989
|
+
},
|
|
5990
|
+
"Program:exit"() {
|
|
5991
|
+
if (boundaryFile && hasValidationCall) return;
|
|
5992
|
+
for (const node of reads) context.report({ node, messageId: "noRawEnv" });
|
|
6052
5993
|
}
|
|
6053
5994
|
};
|
|
6054
5995
|
}
|
|
6055
5996
|
});
|
|
6056
5997
|
|
|
6057
5998
|
// src/rules/no-raw-fetch-outside-clients.ts
|
|
6058
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES24, ASTUtils as
|
|
5999
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES24, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
|
|
6059
6000
|
var NO_RAW_FETCH_OUTSIDE_CLIENTS_DOCUMENTATION = {
|
|
6060
6001
|
summary: "Disallow calling the global `fetch` outside the client layer; route outbound HTTP through a client module that owns retry, timeout and status handling.",
|
|
6061
6002
|
rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
|
|
6062
6003
|
remediation: "Move the request into a client module and call that abstraction from application code.",
|
|
6063
6004
|
category: "architecture",
|
|
6064
|
-
limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded. Configure the same literal Next.js basePath here and on prefer-server-actions so one rule owns each internal mutation."],
|
|
6005
|
+
limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded. Configure the same literal Next.js basePath here and on prefer-server-actions so one rule owns each internal mutation.", "Effect and pre-signed-transfer exemptions use recognized syntax and naming conventions, not complete React or URL provenance. Those conservative exclusions are recall limitations, not evidence that every excluded request satisfies transport policy."],
|
|
6065
6006
|
examples: [
|
|
6066
6007
|
{ id: "client-call", title: "Use a client abstraction", outcome: "no-match", files: [{ path: "src/routes/handler.ts", source: "const response = await billingClient.getInvoice(id);" }], focusPath: "src/routes/handler.ts", expectedCount: 0, public: true },
|
|
6067
6008
|
{ id: "raw-fetch", title: "Do not call global fetch here", outcome: "match", files: [{ path: "src/routes/handler.ts", source: "const response = await fetch('/api/invoices');" }], focusPath: "src/routes/handler.ts", expectedCount: 1, public: true }
|
|
@@ -6243,7 +6184,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6243
6184
|
internalApiPrefixes.push(`${options.basePath}/api`);
|
|
6244
6185
|
}
|
|
6245
6186
|
function resolvesToGlobal(identifier) {
|
|
6246
|
-
const variable =
|
|
6187
|
+
const variable = ASTUtils11.findVariable(
|
|
6247
6188
|
context.sourceCode.getScope(identifier),
|
|
6248
6189
|
identifier.name
|
|
6249
6190
|
);
|
|
@@ -6252,17 +6193,18 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6252
6193
|
function resolveNode2(node) {
|
|
6253
6194
|
if (node === void 0) return null;
|
|
6254
6195
|
if (node.type !== AST_NODE_TYPES24.Identifier) return node;
|
|
6255
|
-
const variable =
|
|
6196
|
+
const variable = ASTUtils11.findVariable(
|
|
6256
6197
|
context.sourceCode.getScope(node),
|
|
6257
6198
|
node.name
|
|
6258
6199
|
);
|
|
6259
6200
|
if (variable?.defs.length !== 1) return node;
|
|
6260
6201
|
const definition = variable.defs[0];
|
|
6261
|
-
return definition?.type === "Variable" && definition.node.init !== null ? definition.node.init : node;
|
|
6202
|
+
return definition?.type === "Variable" && definition.parent.kind === "const" && definition.node.init !== null && !variable.references.some((reference) => reference.isWrite() && reference.init !== true) && !(definition.node.init.type === AST_NODE_TYPES24.ObjectExpression && variable.references.some((reference) => reference.identifier !== node && reference.init !== true)) ? definition.node.init : node;
|
|
6262
6203
|
}
|
|
6263
6204
|
function propertyValue(node, name) {
|
|
6264
6205
|
if (node?.type !== AST_NODE_TYPES24.ObjectExpression) return null;
|
|
6265
|
-
|
|
6206
|
+
if (node.properties.some((property) => property.type !== AST_NODE_TYPES24.Property || property.computed)) return null;
|
|
6207
|
+
for (const property of [...node.properties].reverse()) {
|
|
6266
6208
|
if (property.type !== AST_NODE_TYPES24.Property || property.computed) continue;
|
|
6267
6209
|
const key = property.key;
|
|
6268
6210
|
const keyName = key.type === AST_NODE_TYPES24.Identifier ? key.name : key.type === AST_NODE_TYPES24.Literal && typeof key.value === "string" ? key.value : null;
|
|
@@ -6330,13 +6272,13 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6330
6272
|
});
|
|
6331
6273
|
|
|
6332
6274
|
// src/rules/no-restricted-library-load.ts
|
|
6333
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES25, ASTUtils as
|
|
6275
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES25, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
|
|
6334
6276
|
var NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION = {
|
|
6335
|
-
summary: "Apply
|
|
6336
|
-
rationale: "
|
|
6337
|
-
remediation: "
|
|
6277
|
+
summary: "Apply configured library restrictions to literal runtime loads and CommonJS resolution references.",
|
|
6278
|
+
rationale: "Dynamic imports, CommonJS loads, and package resolution checks can bypass library restrictions enforced for static imports.",
|
|
6279
|
+
remediation: "Use the configured replacement for the runtime dependency reference; resolution checks do not themselves load a module.",
|
|
6338
6280
|
category: "architecture",
|
|
6339
|
-
limitations: ["Only literal dynamic imports, unshadowed CommonJS loads, and TypeScript import-equals declarations are checked."],
|
|
6281
|
+
limitations: ["Only literal dynamic imports, unshadowed CommonJS loads/resolution calls, and runtime TypeScript import-equals declarations are checked; erased type imports are excluded. A configured restriction list is required."],
|
|
6340
6282
|
examples: [
|
|
6341
6283
|
{ id: "static-import", title: "Static imports remain the static-import rule's responsibility", outcome: "no-match", files: [{ path: "src/client.ts", source: "import axios from 'axios';" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
|
|
6342
6284
|
{ id: "runtime-load", title: "Do not load a restricted library at runtime", outcome: "match", files: [{ path: "src/client.ts", source: "const client = require('axios');" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
|
|
@@ -6354,7 +6296,7 @@ var no_restricted_library_load_default = createRule({
|
|
|
6354
6296
|
meta: {
|
|
6355
6297
|
type: "problem",
|
|
6356
6298
|
docs: {
|
|
6357
|
-
description:
|
|
6299
|
+
description: NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION.summary
|
|
6358
6300
|
},
|
|
6359
6301
|
schema: [
|
|
6360
6302
|
{
|
|
@@ -6380,7 +6322,7 @@ var no_restricted_library_load_default = createRule({
|
|
|
6380
6322
|
}
|
|
6381
6323
|
],
|
|
6382
6324
|
messages: {
|
|
6383
|
-
restrictedLibraryLoad: "{{id}}: Replace runtime
|
|
6325
|
+
restrictedLibraryLoad: "{{id}}: Replace this runtime dependency reference to {{module}} with {{replacement}}.{{note}}"
|
|
6384
6326
|
}
|
|
6385
6327
|
},
|
|
6386
6328
|
defaultOptions: [{ libraries: [] }],
|
|
@@ -6403,7 +6345,7 @@ var no_restricted_library_load_default = createRule({
|
|
|
6403
6345
|
});
|
|
6404
6346
|
}
|
|
6405
6347
|
function isUnshadowedRequire(node) {
|
|
6406
|
-
const variable =
|
|
6348
|
+
const variable = ASTUtils12.findVariable(
|
|
6407
6349
|
context.sourceCode.getScope(node),
|
|
6408
6350
|
node.name
|
|
6409
6351
|
);
|
|
@@ -6426,6 +6368,7 @@ var no_restricted_library_load_default = createRule({
|
|
|
6426
6368
|
if (source !== null) report2(node.arguments[0], source);
|
|
6427
6369
|
},
|
|
6428
6370
|
TSImportEqualsDeclaration(node) {
|
|
6371
|
+
if (node.importKind === "type") return;
|
|
6429
6372
|
if (node.moduleReference.type !== AST_NODE_TYPES25.TSExternalModuleReference) return;
|
|
6430
6373
|
const source = literalModule(node.moduleReference.expression);
|
|
6431
6374
|
if (source !== null) report2(node.moduleReference.expression, source);
|
|
@@ -6435,7 +6378,7 @@ var no_restricted_library_load_default = createRule({
|
|
|
6435
6378
|
});
|
|
6436
6379
|
|
|
6437
6380
|
// src/rules/no-router-refresh-polling.ts
|
|
6438
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES26, ASTUtils as
|
|
6381
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES26, ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
|
|
6439
6382
|
var NO_ROUTER_REFRESH_POLLING_DOCUMENTATION = {
|
|
6440
6383
|
summary: "Do not poll by calling a Next.js router's refresh method from a timer.",
|
|
6441
6384
|
rationale: "A route refresh refetches and rerenders the whole route on every tick instead of loading the named resource that changed.",
|
|
@@ -6443,8 +6386,8 @@ var NO_ROUTER_REFRESH_POLLING_DOCUMENTATION = {
|
|
|
6443
6386
|
category: "performance",
|
|
6444
6387
|
limitations: ["Only router bindings created from next/navigation useRouter and direct setInterval or window.setInterval callbacks are inspected; generated and test files are excluded."],
|
|
6445
6388
|
examples: [
|
|
6446
|
-
{ id: "poll-named-action", title: "Poll a named
|
|
6447
|
-
{ id: "poll-router-refresh", title: "Do not poll the whole route", outcome: "match", files: [{ path: "src/status.tsx", source: 'import { useRouter } from "next/navigation"; const router = useRouter(); setInterval(() => router.refresh(), POLLING_INTERVAL_MS);' }], focusPath: "src/status.tsx", expectedCount: 1, public: true }
|
|
6389
|
+
{ id: "poll-named-action", title: "Poll a named resource", outcome: "no-match", files: [{ path: "src/status.tsx", source: '"use client"; import { useEffect } from "react"; function Status() { useEffect(() => { const timer = setInterval(() => fetchStatus(), POLLING_INTERVAL_MS); return () => clearInterval(timer); }, []); return null; }' }], focusPath: "src/status.tsx", expectedCount: 0, public: true },
|
|
6390
|
+
{ id: "poll-router-refresh", title: "Do not poll the whole route", outcome: "match", files: [{ path: "src/status.tsx", source: '"use client"; import { useEffect } from "react"; import { useRouter } from "next/navigation"; function Status() { const router = useRouter(); useEffect(() => { const timer = setInterval(() => router.refresh(), POLLING_INTERVAL_MS); return () => clearInterval(timer); }, [router]); return null; }' }], focusPath: "src/status.tsx", expectedCount: 1, public: true }
|
|
6448
6391
|
]
|
|
6449
6392
|
};
|
|
6450
6393
|
function importedName4(node) {
|
|
@@ -6454,6 +6397,7 @@ function enclosingIntervalCallback(sourceCode, node) {
|
|
|
6454
6397
|
const ancestors = sourceCode.getAncestors(node);
|
|
6455
6398
|
for (let index = ancestors.length - 1; index >= 0; index -= 1) {
|
|
6456
6399
|
const ancestor = ancestors[index];
|
|
6400
|
+
if (ancestor?.type === AST_NODE_TYPES26.FunctionDeclaration) return null;
|
|
6457
6401
|
if (ancestor?.type !== AST_NODE_TYPES26.ArrowFunctionExpression && ancestor?.type !== AST_NODE_TYPES26.FunctionExpression) continue;
|
|
6458
6402
|
const parent = ancestor.parent;
|
|
6459
6403
|
return parent.type === AST_NODE_TYPES26.CallExpression && parent.arguments[0] === ancestor && isIntervalCallee(sourceCode, parent.callee) ? ancestor : null;
|
|
@@ -6464,7 +6408,7 @@ function isIntervalCallee(sourceCode, node) {
|
|
|
6464
6408
|
return node.type === AST_NODE_TYPES26.Identifier && node.name === "setInterval" && isUnshadowedGlobal2(sourceCode, node) || node.type === AST_NODE_TYPES26.MemberExpression && !node.computed && node.object.type === AST_NODE_TYPES26.Identifier && (node.object.name === "window" || node.object.name === "globalThis") && isUnshadowedGlobal2(sourceCode, node.object) && node.property.type === AST_NODE_TYPES26.Identifier && node.property.name === "setInterval";
|
|
6465
6409
|
}
|
|
6466
6410
|
function isUnshadowedGlobal2(sourceCode, node) {
|
|
6467
|
-
const variable =
|
|
6411
|
+
const variable = ASTUtils13.findVariable(sourceCode.getScope(node), node.name);
|
|
6468
6412
|
return variable === null || variable.defs.length === 0;
|
|
6469
6413
|
}
|
|
6470
6414
|
var no_router_refresh_polling_default = createRule({
|
|
@@ -6487,25 +6431,25 @@ var no_router_refresh_polling_default = createRule({
|
|
|
6487
6431
|
if (node.source.value !== "next/navigation") return;
|
|
6488
6432
|
for (const specifier of node.specifiers) {
|
|
6489
6433
|
if (specifier.type === AST_NODE_TYPES26.ImportSpecifier && importedName4(specifier) === "useRouter") {
|
|
6490
|
-
const variable =
|
|
6434
|
+
const variable = ASTUtils13.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
|
|
6491
6435
|
if (variable !== null) routerHooks.add(variable);
|
|
6492
6436
|
}
|
|
6493
6437
|
}
|
|
6494
6438
|
},
|
|
6495
6439
|
VariableDeclarator(node) {
|
|
6496
6440
|
if (node.id.type === AST_NODE_TYPES26.Identifier && node.init?.type === AST_NODE_TYPES26.CallExpression && node.init.callee.type === AST_NODE_TYPES26.Identifier) {
|
|
6497
|
-
const hook =
|
|
6498
|
-
const router =
|
|
6441
|
+
const hook = ASTUtils13.findVariable(context.sourceCode.getScope(node.init.callee), node.init.callee.name);
|
|
6442
|
+
const router = ASTUtils13.findVariable(context.sourceCode.getScope(node.id), node.id.name);
|
|
6499
6443
|
if (hook !== null && router !== null && routerHooks.has(hook)) routers.add(router);
|
|
6500
6444
|
}
|
|
6501
6445
|
},
|
|
6502
6446
|
CallExpression(node) {
|
|
6503
6447
|
if (node.callee.type !== AST_NODE_TYPES26.MemberExpression || node.callee.computed || node.callee.object.type !== AST_NODE_TYPES26.Identifier || node.callee.property.type !== AST_NODE_TYPES26.Identifier || node.callee.property.name !== "refresh") return;
|
|
6504
|
-
const router =
|
|
6448
|
+
const router = ASTUtils13.findVariable(
|
|
6505
6449
|
context.sourceCode.getScope(node.callee.object),
|
|
6506
6450
|
node.callee.object.name
|
|
6507
6451
|
);
|
|
6508
|
-
if (router === null || !routers.has(router)) return;
|
|
6452
|
+
if (router === null || !routers.has(router) || router.references.some((reference) => reference.isWrite() && reference.init !== true)) return;
|
|
6509
6453
|
const callback = enclosingIntervalCallback(context.sourceCode, node);
|
|
6510
6454
|
if (callback !== null && !reportedCallbacks.has(callback)) {
|
|
6511
6455
|
reportedCallbacks.add(callback);
|
|
@@ -7191,11 +7135,11 @@ function isAuthSecretName(identifier) {
|
|
|
7191
7135
|
var NO_SECRET_IN_LOG_DOCUMENTATION = {
|
|
7192
7136
|
summary: "Disallow passing a secret-named value or a raw request/response blob to a logging call; both leak to log sinks. Redact or omit.",
|
|
7193
7137
|
rationale: "Logs are widely retained and distributed, so credentials and raw bodies can become durable data leaks.",
|
|
7194
|
-
remediation: "Omit the value or
|
|
7138
|
+
remediation: "Omit the value, log allowlisted non-sensitive context, or use an approved redactor; truncation alone is not a safety guarantee.",
|
|
7195
7139
|
category: "security",
|
|
7196
|
-
limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers."],
|
|
7140
|
+
limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers. Name-based exemptions are policy heuristics, not proof that a value is safely redacted."],
|
|
7197
7141
|
examples: [
|
|
7198
|
-
{ id: "redacted-secret", title: "Log
|
|
7142
|
+
{ id: "redacted-secret", title: "Log non-sensitive context instead of the secret", outcome: "no-match", files: [{ path: "src/auth.ts", source: "logger.info('auth', { requestId });" }], focusPath: "src/auth.ts", expectedCount: 0, public: true },
|
|
7199
7143
|
{ id: "logged-secret", title: "Do not send a secret to logs", outcome: "match", files: [{ path: "src/auth.ts", source: "logger.error('auth failed', { token });" }], focusPath: "src/auth.ts", expectedCount: 1, public: true }
|
|
7200
7144
|
]
|
|
7201
7145
|
};
|
|
@@ -7247,13 +7191,14 @@ var LOG_INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
|
|
|
7247
7191
|
var REDACTION_RE = /prefix|suffix|redact|mask|hash|hint|_len|length/i;
|
|
7248
7192
|
var WHOLE_TOKEN_REDACTION_MARKERS = /* @__PURE__ */ new Set(["tag"]);
|
|
7249
7193
|
function isSecretKeyword(name) {
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7194
|
+
return !hasRedactionMarker(name) && isSecretName(name, LOG_INNOCUOUS_WORDS);
|
|
7195
|
+
}
|
|
7196
|
+
function hasRedactionMarker(name) {
|
|
7197
|
+
return REDACTION_RE.test(name) || tokenize(name).some((tok) => WHOLE_TOKEN_REDACTION_MARKERS.has(tok));
|
|
7198
|
+
}
|
|
7199
|
+
function valueName(node) {
|
|
7200
|
+
if (node.type === "Identifier") return node.name;
|
|
7201
|
+
return node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier" ? node.property.name : null;
|
|
7257
7202
|
}
|
|
7258
7203
|
function isRawSecretValue(prop) {
|
|
7259
7204
|
if (prop.shorthand) {
|
|
@@ -7337,8 +7282,8 @@ var no_secret_in_log_default = createRule({
|
|
|
7337
7282
|
}
|
|
7338
7283
|
],
|
|
7339
7284
|
messages: {
|
|
7340
|
-
noSecretInLog: "Secret `{{name}}` passed to a logging call
|
|
7341
|
-
noRawBodyInLog: "Raw `{{name}}` passed to a logging call. Request/response
|
|
7285
|
+
noSecretInLog: "Secret-like `{{name}}` passed to a logging call. Omit it or use an approved redactor; a prefix can expose an entire short secret.",
|
|
7286
|
+
noRawBodyInLog: "Raw `{{name}}` passed to a logging call. Request/response bodies can contain personal data or credentials. Log allowlisted non-sensitive context or use an approved redactor."
|
|
7342
7287
|
}
|
|
7343
7288
|
},
|
|
7344
7289
|
defaultOptions: [{}],
|
|
@@ -7346,7 +7291,7 @@ var no_secret_in_log_default = createRule({
|
|
|
7346
7291
|
const matcher = createLogMatcher(loggingOptions);
|
|
7347
7292
|
const blobArmApplies = !isTestFile(context.filename);
|
|
7348
7293
|
function reportSecretArgument(arg) {
|
|
7349
|
-
const name = arg
|
|
7294
|
+
const name = valueName(arg);
|
|
7350
7295
|
if (name === null || !isSecretKeyword(name)) {
|
|
7351
7296
|
return false;
|
|
7352
7297
|
}
|
|
@@ -7355,10 +7300,13 @@ var no_secret_in_log_default = createRule({
|
|
|
7355
7300
|
}
|
|
7356
7301
|
function reportSecretProperty(prop) {
|
|
7357
7302
|
const keyName = propertyKeyName2(prop);
|
|
7358
|
-
|
|
7303
|
+
const value = valueName(prop.value);
|
|
7304
|
+
if (value !== null && hasRedactionMarker(value)) return false;
|
|
7305
|
+
const name = value !== null && isSecretKeyword(value) ? value : keyName;
|
|
7306
|
+
if (name === null || !isSecretKeyword(name) || !isRawSecretValue(prop)) {
|
|
7359
7307
|
return false;
|
|
7360
7308
|
}
|
|
7361
|
-
context.report({ node: prop, messageId: "noSecretInLog", data: { name
|
|
7309
|
+
context.report({ node: prop, messageId: "noSecretInLog", data: { name } });
|
|
7362
7310
|
return true;
|
|
7363
7311
|
}
|
|
7364
7312
|
function reportRawBlob(node, value) {
|
|
@@ -7555,13 +7503,13 @@ var no_select_star_default = createRule({
|
|
|
7555
7503
|
});
|
|
7556
7504
|
|
|
7557
7505
|
// src/rules/no-sentinel-return-on-catch.ts
|
|
7558
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES30 } from "@typescript-eslint/utils";
|
|
7506
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES30, ASTUtils as ASTUtils14 } from "@typescript-eslint/utils";
|
|
7559
7507
|
var NO_SENTINEL_RETURN_ON_CATCH_DOCUMENTATION = {
|
|
7560
7508
|
summary: "Disallow swallowing a caught error by returning an empty sentinel unless the error is handled or the sentinel is part of the function contract.",
|
|
7561
7509
|
rationale: "An unreported fallback makes operational failure indistinguishable from a legitimate empty result.",
|
|
7562
7510
|
remediation: "Rethrow, report the error before returning, or model expected absence with an explicit predicate, safe-parse, or result contract.",
|
|
7563
7511
|
category: "correctness",
|
|
7564
|
-
limitations: ["Recognized predicate, safe-parse, normal-path sentinel, deliberate parse, generated-client, and configured logging patterns are excluded."],
|
|
7512
|
+
limitations: ["Recognized predicate, safe-parse, normal-path sentinel, deliberate parse, generated-client, and configured logging patterns are excluded. Locally shadowed undefined bindings are not treated as sentinels; recognized handling patterns are not a proof that every control-flow path handles the error."],
|
|
7565
7513
|
examples: [
|
|
7566
7514
|
{ id: "reported-fallback", title: "Report an error before returning a fallback", outcome: "no-match", files: [{ path: "src/load.ts", source: "function load() { try { return read(); } catch (error) { logger.warn('load failed', error); return null; } }" }], focusPath: "src/load.ts", expectedCount: 0, public: true },
|
|
7567
7515
|
{ id: "silent-fallback", title: "Do not turn an unreported error into absence", outcome: "match", files: [{ path: "src/load.ts", source: "function load() { try { return read(); } catch { return null; } }" }], focusPath: "src/load.ts", expectedCount: 1, public: true }
|
|
@@ -7976,6 +7924,8 @@ var no_sentinel_return_on_catch_default = createRule({
|
|
|
7976
7924
|
if (!isSentinelArgument(last.argument)) {
|
|
7977
7925
|
return;
|
|
7978
7926
|
}
|
|
7927
|
+
const returned = unwrapSentinelExpression(last.argument);
|
|
7928
|
+
if (returned?.type === AST_NODE_TYPES30.Identifier && returned.name === "undefined" && (ASTUtils14.findVariable(context.sourceCode.getScope(returned), returned.name)?.defs.length ?? 0) > 0) return;
|
|
7979
7929
|
if (containsThrow(node.body)) {
|
|
7980
7930
|
return;
|
|
7981
7931
|
}
|
|
@@ -8425,12 +8375,13 @@ var no_storage_in_stateless_modules_default = createRule({
|
|
|
8425
8375
|
// src/rules/no-string-concat-in-loop.ts
|
|
8426
8376
|
import "@typescript-eslint/utils";
|
|
8427
8377
|
var NO_STRING_CONCAT_IN_LOOP_DOCUMENTATION = {
|
|
8428
|
-
summary: "
|
|
8378
|
+
summary: "Prefer collecting string fragments over repeatedly accumulating a growing string inside a loop.",
|
|
8429
8379
|
rationale: "Repeatedly rebuilding a growing string can copy all prior content on each iteration, making total work grow quadratically.",
|
|
8430
|
-
remediation: "
|
|
8380
|
+
remediation: "Consider collecting fragments and joining once; preserve intermediate observations and coercion timing, and measure hot paths.",
|
|
8431
8381
|
category: "performance",
|
|
8432
8382
|
limitations: [
|
|
8433
|
-
"Only local identifiers initialized with a string or template literal and accumulated in a loop body are inspected."
|
|
8383
|
+
"Only local identifiers initialized with a string or template literal and accumulated in a loop body are inspected.",
|
|
8384
|
+
"Deferred function bodies are excluded except recognized direct forEach callbacks. Syntax does not establish engine-specific string allocation complexity."
|
|
8434
8385
|
],
|
|
8435
8386
|
examples: [
|
|
8436
8387
|
{
|
|
@@ -8548,6 +8499,7 @@ function enclosingLoop(node) {
|
|
|
8548
8499
|
if ((parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression") && parent.parent.type === "CallExpression" && parent.parent.arguments[0] === parent && parent.parent.callee.type === "MemberExpression" && !parent.parent.callee.computed && parent.parent.callee.property.type === "Identifier" && parent.parent.callee.property.name === "forEach") {
|
|
8549
8500
|
return parent.parent;
|
|
8550
8501
|
}
|
|
8502
|
+
if (parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") return null;
|
|
8551
8503
|
if (LOOP_NODE_TYPES.has(parent.type)) {
|
|
8552
8504
|
const loop = parent;
|
|
8553
8505
|
if (loop.body === child) {
|
|
@@ -8559,6 +8511,19 @@ function enclosingLoop(node) {
|
|
|
8559
8511
|
}
|
|
8560
8512
|
return null;
|
|
8561
8513
|
}
|
|
8514
|
+
function immediatelyExitsLoop(node, loop) {
|
|
8515
|
+
if (!LOOP_NODE_TYPES.has(loop.type) || node.parent.type !== "ExpressionStatement") return false;
|
|
8516
|
+
const statement = node.parent;
|
|
8517
|
+
const block = statement.parent;
|
|
8518
|
+
if (block.type !== "BlockStatement") return false;
|
|
8519
|
+
const next = block.body[block.body.indexOf(statement) + 1];
|
|
8520
|
+
if (next?.type !== "BreakStatement" && next?.type !== "ReturnStatement" && next?.type !== "ThrowStatement") return false;
|
|
8521
|
+
if (next.type === "BreakStatement" && next.label !== null) return false;
|
|
8522
|
+
for (let current = block; current !== void 0 && current !== loop; current = current.parent) {
|
|
8523
|
+
if (current.type === "TryStatement" || next.type === "BreakStatement" && current.type === "SwitchStatement") return false;
|
|
8524
|
+
}
|
|
8525
|
+
return true;
|
|
8526
|
+
}
|
|
8562
8527
|
function isSmallStaticForLoop(node) {
|
|
8563
8528
|
if (node.type !== "ForStatement" || node.init?.type !== "VariableDeclaration" || node.init.declarations.length !== 1 || node.test?.type !== "BinaryExpression" || node.test.operator !== "<" && node.test.operator !== "<=" || node.update?.type !== "UpdateExpression" || node.update.operator !== "++") {
|
|
8564
8529
|
return false;
|
|
@@ -8597,12 +8562,12 @@ var no_string_concat_in_loop_default = createRule({
|
|
|
8597
8562
|
meta: {
|
|
8598
8563
|
type: "suggestion",
|
|
8599
8564
|
docs: {
|
|
8600
|
-
description:
|
|
8565
|
+
description: NO_STRING_CONCAT_IN_LOOP_DOCUMENTATION.summary
|
|
8601
8566
|
},
|
|
8602
8567
|
schema: [],
|
|
8603
8568
|
messages: {
|
|
8604
|
-
noStringConcatInLoop:
|
|
8605
|
-
noStringReduce: "
|
|
8569
|
+
noStringConcatInLoop: "This loop repeatedly accumulates a growing string. Consider collecting fragments and joining once; preserve coercion timing and intermediate reads, and measure performance-sensitive paths.",
|
|
8570
|
+
noStringReduce: "This reduce repeatedly accumulates a growing string. Consider mapping fragments and joining once if coercion timing and intermediate observations are unchanged."
|
|
8606
8571
|
}
|
|
8607
8572
|
},
|
|
8608
8573
|
defaultOptions: [],
|
|
@@ -8629,6 +8594,7 @@ var no_string_concat_in_loop_default = createRule({
|
|
|
8629
8594
|
if (loop === null) {
|
|
8630
8595
|
return;
|
|
8631
8596
|
}
|
|
8597
|
+
if (immediatelyExitsLoop(node, loop)) return;
|
|
8632
8598
|
if (isSmallStaticForLoop(loop)) {
|
|
8633
8599
|
return;
|
|
8634
8600
|
}
|
|
@@ -8829,9 +8795,48 @@ var RESULT_FILLER = /* @__PURE__ */ new Set([
|
|
|
8829
8795
|
"the",
|
|
8830
8796
|
"value"
|
|
8831
8797
|
]);
|
|
8832
|
-
function
|
|
8833
|
-
|
|
8834
|
-
return
|
|
8798
|
+
function parameterTarget(parameter) {
|
|
8799
|
+
if (parameter.type === "TSParameterProperty") return parameterTarget(parameter.parameter);
|
|
8800
|
+
return parameter.type === "AssignmentPattern" ? parameter.left : parameter;
|
|
8801
|
+
}
|
|
8802
|
+
function documentedSignature(sourceCode, comment) {
|
|
8803
|
+
const before = sourceCode.getTokenBefore(comment);
|
|
8804
|
+
if (before?.loc.end.line === comment.loc.start.line) return null;
|
|
8805
|
+
const token = sourceCode.getTokenAfter(comment);
|
|
8806
|
+
if (token === null || token.loc.start.line !== comment.loc.end.line + 1) return null;
|
|
8807
|
+
let node = sourceCode.getNodeByRangeIndex(token.range[0]);
|
|
8808
|
+
while (node !== null && node.type !== "Program" && node.type !== "BlockStatement" && node.type !== "ClassBody") {
|
|
8809
|
+
const signature = functionSignature(node);
|
|
8810
|
+
if (signature !== null) {
|
|
8811
|
+
return signature.returnType !== void 0 && signature.params.every((parameter) => {
|
|
8812
|
+
const target = parameterTarget(parameter);
|
|
8813
|
+
return "typeAnnotation" in target && target.typeAnnotation != null;
|
|
8814
|
+
}) ? signature : null;
|
|
8815
|
+
}
|
|
8816
|
+
node = node.parent ?? null;
|
|
8817
|
+
}
|
|
8818
|
+
return null;
|
|
8819
|
+
}
|
|
8820
|
+
function functionSignature(node) {
|
|
8821
|
+
switch (node.type) {
|
|
8822
|
+
case "ExportNamedDeclaration":
|
|
8823
|
+
case "ExportDefaultDeclaration":
|
|
8824
|
+
return node.declaration === null ? null : functionSignature(node.declaration);
|
|
8825
|
+
case "FunctionDeclaration":
|
|
8826
|
+
case "FunctionExpression":
|
|
8827
|
+
case "ArrowFunctionExpression":
|
|
8828
|
+
case "TSDeclareFunction":
|
|
8829
|
+
case "TSMethodSignature":
|
|
8830
|
+
return node;
|
|
8831
|
+
case "MethodDefinition":
|
|
8832
|
+
return node.value;
|
|
8833
|
+
case "VariableDeclaration": {
|
|
8834
|
+
const init = node.declarations.length === 1 ? node.declarations[0]?.init : null;
|
|
8835
|
+
return init?.type === "ArrowFunctionExpression" || init?.type === "FunctionExpression" ? init : null;
|
|
8836
|
+
}
|
|
8837
|
+
default:
|
|
8838
|
+
return null;
|
|
8839
|
+
}
|
|
8835
8840
|
}
|
|
8836
8841
|
function typedTags(text) {
|
|
8837
8842
|
const tags = [];
|
|
@@ -8845,26 +8850,37 @@ function typedTags(text) {
|
|
|
8845
8850
|
}
|
|
8846
8851
|
}
|
|
8847
8852
|
return tags.map(({ kind, payload }) => {
|
|
8848
|
-
|
|
8853
|
+
const typeMatch = /^\{([^}\n]+)\}\s*/u.exec(payload);
|
|
8854
|
+
const explicitType = typeMatch?.[1]?.trim() ?? null;
|
|
8855
|
+
let rest = payload.slice(typeMatch?.[0].length ?? 0).trim();
|
|
8849
8856
|
if (!PARAM_TAGS2.has(kind)) {
|
|
8850
|
-
return { kind, name: null, description: rest.replace(/^-\s
|
|
8857
|
+
return { kind, name: null, description: rest.replace(/^-\s+/u, "").trim(), explicitType };
|
|
8851
8858
|
}
|
|
8852
|
-
const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s
|
|
8853
|
-
|
|
8854
|
-
|
|
8859
|
+
const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s+|\s+)?(.*)$/u.exec(rest);
|
|
8860
|
+
const rawName = match?.[1] ?? "";
|
|
8861
|
+
if (match === null || !/^[A-Za-z_$][\w$]*$/u.test(rawName)) return { kind, name: null, description: rest, explicitType };
|
|
8855
8862
|
rest = (match[2] ?? "").trim();
|
|
8856
|
-
return { kind, name: rawName, description: rest };
|
|
8863
|
+
return { kind, name: rawName, description: rest, explicitType };
|
|
8857
8864
|
});
|
|
8858
8865
|
}
|
|
8859
|
-
function isVacuousTag(tag) {
|
|
8866
|
+
function isVacuousTag(tag, signature, sourceCode) {
|
|
8867
|
+
let annotation = signature.returnType;
|
|
8868
|
+
if (PARAM_TAGS2.has(tag.kind)) {
|
|
8869
|
+
const parameter = signature.params.map(parameterTarget).find((node) => node.type === "Identifier" && node.name === tag.name);
|
|
8870
|
+
if (parameter?.type !== "Identifier") return false;
|
|
8871
|
+
annotation = parameter.typeAnnotation;
|
|
8872
|
+
}
|
|
8873
|
+
if (annotation === void 0) return false;
|
|
8874
|
+
if (tag.explicitType !== null && (!/^(?:string|number|boolean|bigint|symbol|unknown|never|void|null|undefined)$/u.test(tag.explicitType) || sourceCode.getText(annotation.typeAnnotation) !== tag.explicitType)) return false;
|
|
8875
|
+
if (/[^\p{L}\p{M}\s.,]/u.test(tag.description)) return false;
|
|
8860
8876
|
const description = words(tag.description).map(canonicalWord);
|
|
8861
|
-
if (description.length === 0) return
|
|
8877
|
+
if (description.length === 0) return tag.description.length === 0;
|
|
8862
8878
|
if (tag.name === null) return description.every((word) => RESULT_FILLER.has(word));
|
|
8863
8879
|
const nameWords2 = new Set(words(tag.name).map(canonicalWord));
|
|
8864
8880
|
return description.every((word) => PARAMETER_FILLER.has(word) || nameWords2.has(word));
|
|
8865
8881
|
}
|
|
8866
8882
|
function words(text) {
|
|
8867
|
-
return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[
|
|
8883
|
+
return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[\p{L}\p{M}][\p{L}\p{M}\p{N}]*/gu) ?? [];
|
|
8868
8884
|
}
|
|
8869
8885
|
function canonicalWord(word) {
|
|
8870
8886
|
if (["identifier", "identifiers", "ids"].includes(word)) return "id";
|
|
@@ -8876,7 +8892,7 @@ var NO_TYPED_DOC_SECTIONS_DOCUMENTATION = {
|
|
|
8876
8892
|
rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
|
|
8877
8893
|
remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
|
|
8878
8894
|
category: "maintainability",
|
|
8879
|
-
limitations: ["Description-free or name-restating
|
|
8895
|
+
limitations: ["Description-free or name-restating tags require the adjacent explicitly typed signature and a corresponding parameter name. Optional/defaulted or nested parameter tags and unproven explicit JSDoc types are preserved."],
|
|
8880
8896
|
examples: [
|
|
8881
8897
|
{
|
|
8882
8898
|
id: "behavioral-documentation",
|
|
@@ -8914,7 +8930,8 @@ var no_typed_doc_sections_default = createRule({
|
|
|
8914
8930
|
return {
|
|
8915
8931
|
Program() {
|
|
8916
8932
|
for (const group of proseGroups(context.filename, context.sourceCode, true)) {
|
|
8917
|
-
|
|
8933
|
+
const signature = documentedSignature(context.sourceCode, group.comment);
|
|
8934
|
+
if (group.hasTypedTags && signature !== null && typedTags(group.text).some((tag) => isVacuousTag(tag, signature, context.sourceCode))) {
|
|
8918
8935
|
context.report({ node: group.comment, messageId: "typedSection" });
|
|
8919
8936
|
}
|
|
8920
8937
|
}
|
|
@@ -8924,7 +8941,7 @@ var no_typed_doc_sections_default = createRule({
|
|
|
8924
8941
|
});
|
|
8925
8942
|
|
|
8926
8943
|
// src/rules/no-trailing-value-narration.ts
|
|
8927
|
-
import "@typescript-eslint/utils";
|
|
8944
|
+
import { AST_TOKEN_TYPES as AST_TOKEN_TYPES2 } from "@typescript-eslint/utils";
|
|
8928
8945
|
var NO_TRAILING_VALUE_NARRATION_DOCUMENTATION = {
|
|
8929
8946
|
summary: "Flag a trailing comment that repeats the line's numeric value only to name its unit.",
|
|
8930
8947
|
rationale: "A repeated value can disagree with the expression after either the code or comment changes.",
|
|
@@ -8932,7 +8949,7 @@ var NO_TRAILING_VALUE_NARRATION_DOCUMENTATION = {
|
|
|
8932
8949
|
category: "maintainability",
|
|
8933
8950
|
autofix: "suggestion",
|
|
8934
8951
|
aliases: ["trailing-value-narration"],
|
|
8935
|
-
limitations: ["Only
|
|
8952
|
+
limitations: ["Only attached declaration, property, or assignment values containing numeric tokens and comments with recognized unit words are inspected. Deletion requires a numeric literal and the same unit on its owner. Constraints, additional prose, unknown expressions on unit-bearing owners, and cross-unit annotations are preserved; conversions are not evaluated."],
|
|
8936
8953
|
examples: [
|
|
8937
8954
|
{
|
|
8938
8955
|
id: "explain-constraint",
|
|
@@ -8955,7 +8972,7 @@ var NO_TRAILING_VALUE_NARRATION_DOCUMENTATION = {
|
|
|
8955
8972
|
]
|
|
8956
8973
|
};
|
|
8957
8974
|
var NUMBER_RE = /(?<![\w.])(\d+(?:\.\d+)?)(?![\w.])/g;
|
|
8958
|
-
var WORD_RE3 = /[
|
|
8975
|
+
var WORD_RE3 = /[\p{L}\p{M}]+(?:'[\p{L}\p{M}]+)?|\d+(?:\.\d+)?/gu;
|
|
8959
8976
|
var UNIT_WORDS = /* @__PURE__ */ new Set([
|
|
8960
8977
|
"bytes",
|
|
8961
8978
|
"characters",
|
|
@@ -9017,9 +9034,9 @@ var STOPWORDS3 = /* @__PURE__ */ new Set([
|
|
|
9017
9034
|
]);
|
|
9018
9035
|
var DIRECTIVE_RE5 = /^\s*(?:eslint\b|eslint-|sarj-noqa\b|@ts-|prettier|biome-|c8\b|v8\b|istanbul\b|todo\b|fixme\b|hack\b|xxx\b)/i;
|
|
9019
9036
|
var UNIT_NAME_SUFFIX_RE = /(?:_(?:NS|US|MS|S|SEC|SECS|SECOND|SECONDS|MIN|MINS|MINUTE|MINUTES|HOUR|HOURS|DAY|DAYS|BYTE|BYTES|KB|MB|GB|HZ|KHZ|MHZ|PX)|(?:Ns|Us|Ms|Sec|Secs|Second|Seconds|Min|Mins|Minute|Minutes|Hour|Hours|Day|Days|Byte|Bytes|Kb|Mb|Gb|Hz|Khz|Mhz|Px))$/u;
|
|
9020
|
-
function narratesValue(body2, code) {
|
|
9037
|
+
function narratesValue(body2, code, codeNumbers) {
|
|
9021
9038
|
if (body2.length === 0 || DIRECTIVE_RE5.test(body2) || hasExternalReference(body2)) return false;
|
|
9022
|
-
|
|
9039
|
+
if (/[^\p{L}\p{M}\p{N}\s.,:()_]/u.test(body2)) return false;
|
|
9023
9040
|
if (codeNumbers.size === 0) return false;
|
|
9024
9041
|
const words2 = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
|
|
9025
9042
|
if (words2.length === 0) return false;
|
|
@@ -9039,8 +9056,36 @@ function narratesValue(body2, code) {
|
|
|
9039
9056
|
function numbersIn(text) {
|
|
9040
9057
|
return new Set(text.match(NUMBER_RE) ?? []);
|
|
9041
9058
|
}
|
|
9042
|
-
function
|
|
9043
|
-
|
|
9059
|
+
function canonicalUnit(word) {
|
|
9060
|
+
switch (word) {
|
|
9061
|
+
case "milliseconds":
|
|
9062
|
+
return "ms";
|
|
9063
|
+
case "sec":
|
|
9064
|
+
case "secs":
|
|
9065
|
+
case "second":
|
|
9066
|
+
case "seconds":
|
|
9067
|
+
return "s";
|
|
9068
|
+
case "mins":
|
|
9069
|
+
case "minute":
|
|
9070
|
+
case "minutes":
|
|
9071
|
+
return "min";
|
|
9072
|
+
case "hr":
|
|
9073
|
+
case "hrs":
|
|
9074
|
+
case "hours":
|
|
9075
|
+
return "hour";
|
|
9076
|
+
case "days":
|
|
9077
|
+
return "day";
|
|
9078
|
+
case "bytes":
|
|
9079
|
+
return "byte";
|
|
9080
|
+
default:
|
|
9081
|
+
return word;
|
|
9082
|
+
}
|
|
9083
|
+
}
|
|
9084
|
+
function identifierUnit(node) {
|
|
9085
|
+
if (node.type === "MemberExpression" && !node.computed) return identifierUnit(node.property);
|
|
9086
|
+
if (node.type !== "Identifier") return null;
|
|
9087
|
+
const suffix = UNIT_NAME_SUFFIX_RE.exec(node.name)?.[0];
|
|
9088
|
+
return suffix === void 0 ? null : canonicalUnit(suffix.replace(/^_/u, "").toLowerCase());
|
|
9044
9089
|
}
|
|
9045
9090
|
var no_trailing_value_narration_default = createRule({
|
|
9046
9091
|
name: "no-trailing-value-narration",
|
|
@@ -9054,7 +9099,7 @@ var no_trailing_value_narration_default = createRule({
|
|
|
9054
9099
|
schema: [],
|
|
9055
9100
|
messages: {
|
|
9056
9101
|
deleteNarration: "Trailing comment restates the literal and the identifier already names its unit \u2014 delete the comment so it cannot drift.",
|
|
9057
|
-
narratesValue: "
|
|
9102
|
+
narratesValue: "Consider putting the unit in the name if this comment only narrates the value; keep conversion details and constraints.",
|
|
9058
9103
|
removeNarration: "Delete the redundant trailing narration."
|
|
9059
9104
|
}
|
|
9060
9105
|
},
|
|
@@ -9079,15 +9124,46 @@ var no_trailing_value_narration_default = createRule({
|
|
|
9079
9124
|
}
|
|
9080
9125
|
return false;
|
|
9081
9126
|
}
|
|
9127
|
+
function attachedValue(comment) {
|
|
9128
|
+
let token = sourceCode.getTokenBefore(comment);
|
|
9129
|
+
if (token?.value === ";" || token?.value === ",") token = sourceCode.getTokenBefore(token);
|
|
9130
|
+
if (token === null) return null;
|
|
9131
|
+
let node = sourceCode.getNodeByRangeIndex(token.range[0]);
|
|
9132
|
+
while (node !== null && node.type !== "Program") {
|
|
9133
|
+
if (node.range[1] <= comment.range[0]) {
|
|
9134
|
+
if (node.type === "VariableDeclarator" && node.id.type === "Identifier" && node.init !== null) {
|
|
9135
|
+
return { name: node.id, value: node.init };
|
|
9136
|
+
}
|
|
9137
|
+
if (node.type === "Property" && !node.computed && node.kind === "init" && node.parent.type === "ObjectExpression") {
|
|
9138
|
+
return { name: node.key, value: node.value };
|
|
9139
|
+
}
|
|
9140
|
+
if (node.type === "PropertyDefinition" && !node.computed && node.value !== null) {
|
|
9141
|
+
return { name: node.key, value: node.value };
|
|
9142
|
+
}
|
|
9143
|
+
if (node.type === "AssignmentExpression" && node.operator === "=") {
|
|
9144
|
+
if (node.left.type !== "Identifier" && (node.left.type !== "MemberExpression" || node.left.computed)) return null;
|
|
9145
|
+
return { name: node.left, value: node.right };
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
node = node.parent ?? null;
|
|
9149
|
+
}
|
|
9150
|
+
return null;
|
|
9151
|
+
}
|
|
9082
9152
|
return {
|
|
9083
9153
|
Program() {
|
|
9084
9154
|
for (const comment of sourceCode.getAllComments()) {
|
|
9085
9155
|
if (!isTrailing(comment) || isInsideBrackets(comment)) continue;
|
|
9086
|
-
const
|
|
9087
|
-
|
|
9156
|
+
const attached = attachedValue(comment);
|
|
9157
|
+
if (attached === null) continue;
|
|
9158
|
+
const code = `${sourceCode.getText(attached.name)} ${sourceCode.getText(attached.value)}`;
|
|
9159
|
+
const codeNumbers = new Set(sourceCode.getTokens(attached.value).filter((token) => token.type === AST_TOKEN_TYPES2.Numeric).flatMap((token) => [...numbersIn(token.value)]));
|
|
9088
9160
|
const body2 = comment.value.replace(/^\*+/, "").replace(/\*+$/, "").trim();
|
|
9089
|
-
if (narratesValue(body2, code)) {
|
|
9090
|
-
const
|
|
9161
|
+
if (narratesValue(body2, code, codeNumbers)) {
|
|
9162
|
+
const namedUnit = identifierUnit(attached.name);
|
|
9163
|
+
if (namedUnit !== null && (attached.value.type !== "Literal" || typeof attached.value.value !== "number")) continue;
|
|
9164
|
+
const units = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase()).filter((word) => UNIT_WORDS.has(word));
|
|
9165
|
+
if (namedUnit !== null && !units.every((word) => canonicalUnit(word) === namedUnit)) continue;
|
|
9166
|
+
const canDelete = namedUnit !== null;
|
|
9091
9167
|
const removal = canDelete ? trailingCommentRemovalRange(sourceCode.text, comment) : null;
|
|
9092
9168
|
context.report({
|
|
9093
9169
|
node: comment,
|
|
@@ -9107,10 +9183,10 @@ var no_trailing_value_narration_default = createRule({
|
|
|
9107
9183
|
});
|
|
9108
9184
|
|
|
9109
9185
|
// src/rules/no-declaration-comment-wall.ts
|
|
9110
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES36, AST_TOKEN_TYPES as
|
|
9186
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES36, AST_TOKEN_TYPES as AST_TOKEN_TYPES4 } from "@typescript-eslint/utils";
|
|
9111
9187
|
|
|
9112
9188
|
// src/rules/_comment-wall.ts
|
|
9113
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES35, AST_TOKEN_TYPES as
|
|
9189
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES35, AST_TOKEN_TYPES as AST_TOKEN_TYPES3 } from "@typescript-eslint/utils";
|
|
9114
9190
|
var WALL_DEFAULTS = {
|
|
9115
9191
|
// Below three rows "a wall" is not a fair description of what the reader sees.
|
|
9116
9192
|
minCommentedMembers: 3,
|
|
@@ -9176,7 +9252,7 @@ function commentBody(comment) {
|
|
|
9176
9252
|
return comment.value.replace(/^\*+/, "").replace(/^[ \t]*\*[ \t]?/gm, "").trim();
|
|
9177
9253
|
}
|
|
9178
9254
|
function hasJsDocTag(comment) {
|
|
9179
|
-
return comment.type ===
|
|
9255
|
+
return comment.type === AST_TOKEN_TYPES3.Block && comment.value.startsWith("*") && /(?:^|\s)@[A-Za-z][\w-]*\b/u.test(commentBody(comment));
|
|
9180
9256
|
}
|
|
9181
9257
|
function carriesValue(body2) {
|
|
9182
9258
|
return isProtected(body2) || VALUE_TAG_RE2.test(body2) || DEFAULT_RE.test(body2) || DIGIT_RE.test(body2) || UNIT_WORD_RE.test(body2) || EXAMPLE_RE.test(body2) || BANNER_RE.test(body2) || NON_ASCII_LETTER_RE2.test(body2);
|
|
@@ -9312,7 +9388,7 @@ var no_declaration_comment_wall_default = createRule({
|
|
|
9312
9388
|
const before = sourceCode.getTokenBefore(lead, { includeComments: false });
|
|
9313
9389
|
if (before === null || before.loc.end.line < lead.loc.start.line) {
|
|
9314
9390
|
const previousLine = endingOn.get(lead.loc.start.line - 1);
|
|
9315
|
-
if (lead.type ===
|
|
9391
|
+
if (lead.type === AST_TOKEN_TYPES4.Line && previousLine?.type === AST_TOKEN_TYPES4.Line && previousLine.loc.start.column === lead.loc.start.column) {
|
|
9316
9392
|
return void 0;
|
|
9317
9393
|
}
|
|
9318
9394
|
return lead;
|
|
@@ -9382,10 +9458,10 @@ var no_declaration_comment_wall_default = createRule({
|
|
|
9382
9458
|
import { AST_NODE_TYPES as AST_NODE_TYPES37 } from "@typescript-eslint/utils";
|
|
9383
9459
|
var NO_UNION_IN_COMMENT_DOCUMENTATION = {
|
|
9384
9460
|
summary: "Flag a comment that lists a `string` field's allowed values instead of the type listing them.",
|
|
9385
|
-
rationale: "A
|
|
9386
|
-
remediation: "
|
|
9387
|
-
category: "
|
|
9388
|
-
limitations: ["Only bare quoted-value lists attached to
|
|
9461
|
+
rationale: "A broad string annotation does not express a closed set documented beside it.",
|
|
9462
|
+
remediation: "If the list is exhaustive, express it as a string-literal union; keep examples and runtime constraints documented separately.",
|
|
9463
|
+
category: "maintainability",
|
|
9464
|
+
limitations: ["Only bare quoted-value lists directly attached to explicitly annotated string declarations are inspected. Unknown schema builders and runtime validation are not inferred."],
|
|
9389
9465
|
examples: [
|
|
9390
9466
|
{
|
|
9391
9467
|
id: "literal-union",
|
|
@@ -9412,16 +9488,6 @@ var LITERAL = String.raw`(?:'[^'\n]*'|"[^"\n]*"|\`[^\`\n]*\`)`;
|
|
|
9412
9488
|
var LEAD_IN_RE2 = /^(?:one of|either|values?|allowed(?: values)?|options?|possible(?: values)?)\s*[:=-]?\s*/i;
|
|
9413
9489
|
var UNION_BODY_RE = new RegExp(String.raw`^${LITERAL}(?:\s*[|,/]\s*${LITERAL})+\.?$`);
|
|
9414
9490
|
var LITERAL_G = new RegExp(LITERAL, "g");
|
|
9415
|
-
var STRING_BUILDERS = /* @__PURE__ */ new Set([
|
|
9416
|
-
"char",
|
|
9417
|
-
"citext",
|
|
9418
|
-
"longtext",
|
|
9419
|
-
"mediumtext",
|
|
9420
|
-
"string",
|
|
9421
|
-
"text",
|
|
9422
|
-
"tinytext",
|
|
9423
|
-
"varchar"
|
|
9424
|
-
]);
|
|
9425
9491
|
function isBareString(node) {
|
|
9426
9492
|
if (node === void 0) return false;
|
|
9427
9493
|
switch (node.type) {
|
|
@@ -9446,12 +9512,6 @@ function targetOf(node) {
|
|
|
9446
9512
|
if (name === null || !isBareString(node.typeAnnotation?.typeAnnotation)) return null;
|
|
9447
9513
|
return { node, name };
|
|
9448
9514
|
}
|
|
9449
|
-
case AST_NODE_TYPES37.Property: {
|
|
9450
|
-
const name = node.computed || node.shorthand ? null : nameOf(node.key);
|
|
9451
|
-
const callee = rootCallee(node.value);
|
|
9452
|
-
if (name === null || callee === null || !STRING_BUILDERS.has(callee)) return null;
|
|
9453
|
-
return { node, name };
|
|
9454
|
-
}
|
|
9455
9515
|
case AST_NODE_TYPES37.VariableDeclarator: {
|
|
9456
9516
|
if (node.id.type !== AST_NODE_TYPES37.Identifier) return null;
|
|
9457
9517
|
if (!isBareString(node.id.typeAnnotation?.typeAnnotation)) return null;
|
|
@@ -9461,24 +9521,6 @@ function targetOf(node) {
|
|
|
9461
9521
|
return null;
|
|
9462
9522
|
}
|
|
9463
9523
|
}
|
|
9464
|
-
function rootCallee(node) {
|
|
9465
|
-
let current = node;
|
|
9466
|
-
for (let hops = 0; current != null && hops < 12; hops += 1) {
|
|
9467
|
-
switch (current.type) {
|
|
9468
|
-
case AST_NODE_TYPES37.CallExpression:
|
|
9469
|
-
current = current.callee;
|
|
9470
|
-
break;
|
|
9471
|
-
case AST_NODE_TYPES37.MemberExpression:
|
|
9472
|
-
current = current.object;
|
|
9473
|
-
break;
|
|
9474
|
-
case AST_NODE_TYPES37.Identifier:
|
|
9475
|
-
return current.name;
|
|
9476
|
-
default:
|
|
9477
|
-
return null;
|
|
9478
|
-
}
|
|
9479
|
-
}
|
|
9480
|
-
return null;
|
|
9481
|
-
}
|
|
9482
9524
|
function nameOf(key) {
|
|
9483
9525
|
if (key.type === AST_NODE_TYPES37.Identifier) return key.name;
|
|
9484
9526
|
if (key.type === AST_NODE_TYPES37.Literal && typeof key.value === "string") return key.value;
|
|
@@ -9503,7 +9545,7 @@ var no_union_in_comment_default = createRule({
|
|
|
9503
9545
|
},
|
|
9504
9546
|
schema: [],
|
|
9505
9547
|
messages: {
|
|
9506
|
-
unionInComment: '
|
|
9548
|
+
unionInComment: 'The annotation for `{{name}}` accepts arbitrary strings. If this list is exhaustive, express it as a string-literal union ("{{first}}" | \u2026); retain separate runtime constraints or examples.'
|
|
9507
9549
|
}
|
|
9508
9550
|
},
|
|
9509
9551
|
defaultOptions: [],
|
|
@@ -9528,7 +9570,11 @@ var no_union_in_comment_default = createRule({
|
|
|
9528
9570
|
}
|
|
9529
9571
|
for (let node = anchor; node != null && node.type !== AST_NODE_TYPES37.Program; node = node.parent) {
|
|
9530
9572
|
const target = targetOf(node);
|
|
9531
|
-
if (target !== null)
|
|
9573
|
+
if (target !== null) {
|
|
9574
|
+
const follows = target.node.range[1] <= comment.range[0] && target.node.loc.end.line === comment.loc.start.line;
|
|
9575
|
+
const precedes = comment.range[1] <= target.node.range[0] && comment.loc.end.line + 1 === target.node.loc.start.line;
|
|
9576
|
+
return follows || precedes ? target : null;
|
|
9577
|
+
}
|
|
9532
9578
|
}
|
|
9533
9579
|
return null;
|
|
9534
9580
|
}
|
|
@@ -9541,8 +9587,6 @@ var no_union_in_comment_default = createRule({
|
|
|
9541
9587
|
if (literals === null) continue;
|
|
9542
9588
|
const target = annotated(comment);
|
|
9543
9589
|
if (target === null) continue;
|
|
9544
|
-
const declaration = sourceCode.getText(target.node);
|
|
9545
|
-
if (literals.every((literal) => declaration.includes(literal))) continue;
|
|
9546
9590
|
context.report({
|
|
9547
9591
|
node: comment,
|
|
9548
9592
|
messageId: "unionInComment",
|
|
@@ -9555,13 +9599,14 @@ var no_union_in_comment_default = createRule({
|
|
|
9555
9599
|
});
|
|
9556
9600
|
|
|
9557
9601
|
// src/rules/no-type-member-comment-wall.ts
|
|
9558
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES38, AST_TOKEN_TYPES as
|
|
9602
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES38, AST_TOKEN_TYPES as AST_TOKEN_TYPES5 } from "@typescript-eslint/utils";
|
|
9603
|
+
var BEHAVIORAL_RELATION_RE = /\b(?:not|no|never|only|must|shall|should|may|can|could|will|would|required|optional|if|unless|when|before|after|until|while|without|instead|true|false|null|undefined)\b|[<>=!]/iu;
|
|
9559
9604
|
var NO_TYPE_MEMBER_COMMENT_WALL_DOCUMENTATION = {
|
|
9560
9605
|
summary: "Flag an object type whose member comments mostly re-spell the members' own names and types.",
|
|
9561
9606
|
rationale: "Repetitive member comments add scanning cost while hiding the comments that describe facts absent from the type.",
|
|
9562
9607
|
remediation: "Delete comments that restate member names or types and keep comments that add constraints or behavior.",
|
|
9563
9608
|
category: "maintainability",
|
|
9564
|
-
limitations: ["Only interface and type-literal bodies meeting the configured comment-count and restatement-ratio thresholds are reported."],
|
|
9609
|
+
limitations: ["Only interface and type-literal bodies meeting the configured comment-count and restatement-ratio thresholds are reported. Negation, requirements, conditional relations, and fixed-value contracts are not counted as restatements. The novel-word threshold is a review heuristic, not proof that a comment contains no useful contract."],
|
|
9565
9610
|
examples: [
|
|
9566
9611
|
{
|
|
9567
9612
|
id: "uncommented-members",
|
|
@@ -9596,7 +9641,7 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
9596
9641
|
},
|
|
9597
9642
|
schema: [WALL_SCHEMA],
|
|
9598
9643
|
messages: {
|
|
9599
|
-
commentWall: "{{restated}} of this type's {{commented}} member comments
|
|
9644
|
+
commentWall: "{{restated}} of this type's {{commented}} member comments appear to repeat names and types \u2014 review them for removal or clearer naming. Keep constraints and rationale."
|
|
9600
9645
|
}
|
|
9601
9646
|
},
|
|
9602
9647
|
defaultOptions: [WALL_DEFAULTS],
|
|
@@ -9620,7 +9665,7 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
9620
9665
|
const before = sourceCode.getTokenBefore(lead, { includeComments: false });
|
|
9621
9666
|
if (before === null || before.loc.end.line < lead.loc.start.line) {
|
|
9622
9667
|
const previousLine = endingOn.get(lead.loc.start.line - 1);
|
|
9623
|
-
if (lead.type ===
|
|
9668
|
+
if (lead.type === AST_TOKEN_TYPES5.Line && previousLine?.type === AST_TOKEN_TYPES5.Line && previousLine.loc.start.column === lead.loc.start.column) {
|
|
9624
9669
|
return void 0;
|
|
9625
9670
|
}
|
|
9626
9671
|
return lead;
|
|
@@ -9654,7 +9699,7 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
9654
9699
|
claimed.add(comment);
|
|
9655
9700
|
commented += 1;
|
|
9656
9701
|
const body2 = commentBody(comment);
|
|
9657
|
-
if (body2.length === 0 || hasJsDocTag(comment) || carriesValue(body2) || isTagsOnly(body2)) {
|
|
9702
|
+
if (body2.length === 0 || hasJsDocTag(comment) || carriesValue(body2) || isTagsOnly(body2) || BEHAVIORAL_RELATION_RE.test(body2)) {
|
|
9658
9703
|
continue;
|
|
9659
9704
|
}
|
|
9660
9705
|
if (novelWords(body2, knownTokens(sourceCode.getText(member))) <= options.maxNovelWords) {
|
|
@@ -9684,9 +9729,9 @@ import { AST_NODE_TYPES as AST_NODE_TYPES39 } from "@typescript-eslint/utils";
|
|
|
9684
9729
|
var NO_UNNECESSARY_USE_CLIENT_DOCUMENTATION = {
|
|
9685
9730
|
summary: "Flag `'use client'` files with no hooks or event handlers \u2014 they could be RSC.",
|
|
9686
9731
|
rationale: "An unnecessary client boundary sends the component and its transitive dependencies to the browser without using client-only behavior.",
|
|
9687
|
-
remediation: "
|
|
9732
|
+
remediation: "Review whether the directive can be removed after checking transitive client requirements and intended export boundaries; local syntax alone does not prove server compatibility.",
|
|
9688
9733
|
category: "performance",
|
|
9689
|
-
limitations: ["Client need is inferred from recognized hooks, handlers, browser globals, exports, classes, and known client-only imports."],
|
|
9734
|
+
limitations: ["Client need is inferred from recognized hooks, handlers, browser globals, exports, classes, and known client-only imports. Unknown side-effect imports preserve the boundary; arbitrary transitive runtime requirements are not inspected."],
|
|
9690
9735
|
examples: [
|
|
9691
9736
|
{
|
|
9692
9737
|
id: "interactive-component",
|
|
@@ -9766,7 +9811,7 @@ var subtreeReadsImportedBinding = (node, imported) => {
|
|
|
9766
9811
|
return false;
|
|
9767
9812
|
};
|
|
9768
9813
|
var isUseClientDirective = (node) => {
|
|
9769
|
-
return node.type === AST_NODE_TYPES39.ExpressionStatement && node.
|
|
9814
|
+
return node.type === AST_NODE_TYPES39.ExpressionStatement && node.directive === "use client";
|
|
9770
9815
|
};
|
|
9771
9816
|
var isGlobalReference = (node, context) => {
|
|
9772
9817
|
if (!BROWSER_GLOBALS.has(node.name)) return false;
|
|
@@ -9832,7 +9877,7 @@ var no_unnecessary_use_client_default = createRule({
|
|
|
9832
9877
|
return {
|
|
9833
9878
|
Program(node) {
|
|
9834
9879
|
for (const stmt of node.body) {
|
|
9835
|
-
if (stmt.type !== AST_NODE_TYPES39.ExpressionStatement) break;
|
|
9880
|
+
if (stmt.type !== AST_NODE_TYPES39.ExpressionStatement || stmt.directive === void 0) break;
|
|
9836
9881
|
if (isUseClientDirective(stmt)) {
|
|
9837
9882
|
directiveNode = stmt;
|
|
9838
9883
|
break;
|
|
@@ -9853,6 +9898,7 @@ var no_unnecessary_use_client_default = createRule({
|
|
|
9853
9898
|
if (directiveNode === null) return;
|
|
9854
9899
|
if (typeof node.source.value !== "string") return;
|
|
9855
9900
|
const source = node.source.value;
|
|
9901
|
+
if (node.importKind !== "type" && node.specifiers.length === 0) hasClientIndicator = true;
|
|
9856
9902
|
if (CLIENT_ONLY_PACKAGES_REGEX.test(source) || CLIENT_REQUIRED_MODULES.has(source)) {
|
|
9857
9903
|
hasClientIndicator = true;
|
|
9858
9904
|
}
|
|
@@ -9922,7 +9968,7 @@ var no_unnecessary_use_client_default = createRule({
|
|
|
9922
9968
|
// src/rules/no-unsafe-mock-casting.ts
|
|
9923
9969
|
import {
|
|
9924
9970
|
AST_NODE_TYPES as AST_NODE_TYPES40,
|
|
9925
|
-
ASTUtils as
|
|
9971
|
+
ASTUtils as ASTUtils15
|
|
9926
9972
|
} from "@typescript-eslint/utils";
|
|
9927
9973
|
var MOCK_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
9928
9974
|
"Mock",
|
|
@@ -9988,7 +10034,7 @@ var no_unsafe_mock_casting_default = createRule({
|
|
|
9988
10034
|
const directBindings = /* @__PURE__ */ new Set();
|
|
9989
10035
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
9990
10036
|
function resolve2(identifier) {
|
|
9991
|
-
return
|
|
10037
|
+
return ASTUtils15.findVariable(
|
|
9992
10038
|
context.sourceCode.getScope(identifier),
|
|
9993
10039
|
identifier.name
|
|
9994
10040
|
);
|
|
@@ -10038,7 +10084,7 @@ var no_unsafe_mock_casting_default = createRule({
|
|
|
10038
10084
|
import {
|
|
10039
10085
|
ESLintUtils as ESLintUtils3,
|
|
10040
10086
|
AST_NODE_TYPES as AST_NODE_TYPES41,
|
|
10041
|
-
ASTUtils as
|
|
10087
|
+
ASTUtils as ASTUtils16
|
|
10042
10088
|
} from "@typescript-eslint/utils";
|
|
10043
10089
|
import * as ts2 from "typescript";
|
|
10044
10090
|
var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
|
|
@@ -10084,9 +10130,9 @@ function isIgnoredFile(filename, sourceText) {
|
|
|
10084
10130
|
function isZodModule2(source) {
|
|
10085
10131
|
return /(^|[/@-])zod([/-]|$)/.test(source);
|
|
10086
10132
|
}
|
|
10087
|
-
function
|
|
10133
|
+
function unwrap3(node) {
|
|
10088
10134
|
if (node.type === AST_NODE_TYPES41.TSAsExpression || node.type === AST_NODE_TYPES41.TSSatisfiesExpression) {
|
|
10089
|
-
return
|
|
10135
|
+
return unwrap3(node.expression);
|
|
10090
10136
|
}
|
|
10091
10137
|
return node;
|
|
10092
10138
|
}
|
|
@@ -10148,7 +10194,7 @@ var no_zod_native_enum_default = createRule({
|
|
|
10148
10194
|
const zodImportedBindings = /* @__PURE__ */ new Map();
|
|
10149
10195
|
const zodNamespaceBindings = /* @__PURE__ */ new Set();
|
|
10150
10196
|
function resolvedBinding(identifier) {
|
|
10151
|
-
return
|
|
10197
|
+
return ASTUtils16.findVariable(
|
|
10152
10198
|
sourceCode.getScope(identifier),
|
|
10153
10199
|
identifier.name
|
|
10154
10200
|
);
|
|
@@ -10198,7 +10244,7 @@ var no_zod_native_enum_default = createRule({
|
|
|
10198
10244
|
if (argument === void 0 || argument.type === AST_NODE_TYPES41.SpreadElement) {
|
|
10199
10245
|
return;
|
|
10200
10246
|
}
|
|
10201
|
-
const arg =
|
|
10247
|
+
const arg = unwrap3(argument);
|
|
10202
10248
|
if (arg.type !== AST_NODE_TYPES41.Identifier) return;
|
|
10203
10249
|
const isEnum = resolvesToLocalEnum(arg, sourceCode.getScope(arg)) || services !== null && resolvesToImportedEnum(arg, services);
|
|
10204
10250
|
if (isEnum) {
|
|
@@ -10214,7 +10260,7 @@ var no_zod_native_enum_default = createRule({
|
|
|
10214
10260
|
});
|
|
10215
10261
|
|
|
10216
10262
|
// src/rules/test-loops-over-literal-cases.ts
|
|
10217
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES42, ASTUtils as
|
|
10263
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES42, ASTUtils as ASTUtils17 } from "@typescript-eslint/utils";
|
|
10218
10264
|
var TEST_LOOPS_OVER_LITERAL_CASES_DOCUMENTATION = {
|
|
10219
10265
|
summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
|
|
10220
10266
|
rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
|
|
@@ -10377,7 +10423,7 @@ var test_loops_over_literal_cases_default = createRule({
|
|
|
10377
10423
|
return {};
|
|
10378
10424
|
}
|
|
10379
10425
|
const isFrameworkIdentifier = (identifier, modules) => {
|
|
10380
|
-
const variable =
|
|
10426
|
+
const variable = ASTUtils17.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
10381
10427
|
if (variable === null || variable.defs.length === 0) return true;
|
|
10382
10428
|
return variable.defs.some((definition) => {
|
|
10383
10429
|
let current = definition.node;
|
|
@@ -10511,17 +10557,18 @@ var test_phase_label_comment_default = createRule({
|
|
|
10511
10557
|
// src/rules/prefer-constant-time-secret-compare.ts
|
|
10512
10558
|
import { AST_NODE_TYPES as AST_NODE_TYPES44 } from "@typescript-eslint/utils";
|
|
10513
10559
|
var PREFER_CONSTANT_TIME_SECRET_COMPARE_DOCUMENTATION = {
|
|
10514
|
-
summary: "
|
|
10515
|
-
rationale: "Ordinary equality
|
|
10560
|
+
summary: "Prefer a supported constant-time comparison primitive for secret-like values.",
|
|
10561
|
+
rationale: "Ordinary equality offers no constant-time guarantee for comparing secrets.",
|
|
10516
10562
|
remediation: "Compare equal-length cryptographic digests with a constant-time comparison primitive.",
|
|
10517
10563
|
category: "security",
|
|
10518
|
-
limitations: ["
|
|
10564
|
+
limitations: ["This is name-based analysis, not proof of runtime sensitivity. Ambiguous token names require an authentication or cryptographic qualifier; test files and public sentinel comparisons are excluded."],
|
|
10519
10565
|
examples: [
|
|
10520
|
-
{ id: "constant-time-compare", title: "Use a constant-time comparison", outcome: "no-match", files: [{ path: "src/auth.ts", source: "if (await constantTimeEqual(
|
|
10521
|
-
{ id: "secret-equality", title: "Do not compare
|
|
10566
|
+
{ id: "constant-time-compare", title: "Use a constant-time comparison", outcome: "no-match", files: [{ path: "src/auth.ts", source: "if (await constantTimeEqual(presentedAccessToken, expectedAccessToken)) { allow(); }" }], focusPath: "src/auth.ts", expectedCount: 0, public: true },
|
|
10567
|
+
{ id: "secret-equality", title: "Do not compare authentication tokens with equality", outcome: "match", files: [{ path: "src/auth.ts", source: "if (presentedAccessToken === expectedAccessToken) { allow(); }" }], focusPath: "src/auth.ts", expectedCount: 1, public: true }
|
|
10522
10568
|
]
|
|
10523
10569
|
};
|
|
10524
10570
|
var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
|
|
10571
|
+
var AUTH_TOKEN_QUALIFIERS = /* @__PURE__ */ new Set(["access", "refresh", "session", "admin", "csrf", "xsrf", "auth", "authentication", "signing", "api"]);
|
|
10525
10572
|
var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
|
|
10526
10573
|
var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
|
|
10527
10574
|
var SENTINEL_PREFIX_RE = /^(skip|sentinel|empty|none|missing|unset|placeholder|dummy|fake|example|noop)[A-Z]/;
|
|
@@ -10559,7 +10606,9 @@ function isSecretOperand(node) {
|
|
|
10559
10606
|
return node.expressions.some((expression) => isSecretOperand(expression));
|
|
10560
10607
|
}
|
|
10561
10608
|
const name = operandName(node);
|
|
10562
|
-
|
|
10609
|
+
if (name === null || !isAuthSecretName(name)) return false;
|
|
10610
|
+
const words2 = tokenize(name);
|
|
10611
|
+
return !words2.includes("token") || words2.some((word) => AUTH_TOKEN_QUALIFIERS.has(word) || word !== "token" && SECRET_WORDS.has(word));
|
|
10563
10612
|
}
|
|
10564
10613
|
function secretNameOf(node) {
|
|
10565
10614
|
if (node.type === AST_NODE_TYPES44.TemplateLiteral) {
|
|
@@ -10579,11 +10628,11 @@ var prefer_constant_time_secret_compare_default = createRule({
|
|
|
10579
10628
|
meta: {
|
|
10580
10629
|
type: "problem",
|
|
10581
10630
|
docs: {
|
|
10582
|
-
description: "
|
|
10631
|
+
description: "Prefer a supported constant-time comparison primitive for secret-like values."
|
|
10583
10632
|
},
|
|
10584
10633
|
schema: [],
|
|
10585
10634
|
messages: {
|
|
10586
|
-
preferConstantTimeSecretCompare: "`{{operator}}` on secret `{{name}}`
|
|
10635
|
+
preferConstantTimeSecretCompare: "`{{operator}}` on secret-like `{{name}}` is not guaranteed constant-time. Use a constant-time comparison primitive supported by the target runtime and handle its input-length requirements."
|
|
10587
10636
|
}
|
|
10588
10637
|
},
|
|
10589
10638
|
defaultOptions: [],
|
|
@@ -10622,13 +10671,12 @@ import {
|
|
|
10622
10671
|
var PREFER_ECMASCRIPT_PRIVATE_MEMBERS_DOCUMENTATION = {
|
|
10623
10672
|
summary: "Prefer ECMAScript `#private` class members over TypeScript-only `private` members.",
|
|
10624
10673
|
rationale: "ECMAScript private names enforce encapsulation at runtime instead of erasing the boundary during compilation.",
|
|
10625
|
-
remediation: "
|
|
10674
|
+
remediation: "Review reflection, instance escape and framework contracts before replacing TypeScript privacy with ECMAScript private names and updating references.",
|
|
10626
10675
|
category: "maintainability",
|
|
10627
|
-
autofix: "
|
|
10676
|
+
autofix: "none",
|
|
10628
10677
|
limitations: [
|
|
10629
10678
|
"Ambient, abstract, computed, decorated, override, parameter-property, and generated declarations are excluded.",
|
|
10630
|
-
"
|
|
10631
|
-
"Overloads, modifier-adjacent comments, reflection, and any potentially cross-file or escaping class remain report-only."
|
|
10679
|
+
"Migration is report-only: type information cannot prove that instances or constructors never escape through this, or that reflection and framework serialization do not observe ordinary private properties."
|
|
10632
10680
|
],
|
|
10633
10681
|
references: ["https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_elements"],
|
|
10634
10682
|
examples: [
|
|
@@ -10648,19 +10696,11 @@ var PREFER_ECMASCRIPT_PRIVATE_MEMBERS_DOCUMENTATION = {
|
|
|
10648
10696
|
files: [{ path: "src/vault.ts", source: "class Vault { private read() { return 1; } open() { return this.read(); } }" }],
|
|
10649
10697
|
focusPath: "src/vault.ts",
|
|
10650
10698
|
expectedCount: 1,
|
|
10651
|
-
public: true
|
|
10652
|
-
fixedFiles: [{ path: "src/vault.ts", source: "class Vault { #read() { return 1; } open() { return this.#read(); } }" }]
|
|
10699
|
+
public: true
|
|
10653
10700
|
}
|
|
10654
10701
|
]
|
|
10655
10702
|
};
|
|
10656
|
-
function reportClass2(context,
|
|
10657
|
-
const parent = owner.parent;
|
|
10658
|
-
const directlyExported = parent.type === AST_NODE_TYPES45.ExportNamedDeclaration || parent.type === AST_NODE_TYPES45.ExportDefaultDeclaration;
|
|
10659
|
-
const locallyClosed = !directlyExported && owner.decorators.length === 0 && owner.type === AST_NODE_TYPES45.ClassDeclaration && context.sourceCode.getDeclaredVariables(owner).every(
|
|
10660
|
-
(variable) => variable.references.every(
|
|
10661
|
-
(reference) => reference.identifier.range[0] >= owner.range[0] && reference.identifier.range[1] <= owner.range[1]
|
|
10662
|
-
)
|
|
10663
|
-
);
|
|
10703
|
+
function reportClass2(context, owner) {
|
|
10664
10704
|
const groups = /* @__PURE__ */ new Map();
|
|
10665
10705
|
for (const member of owner.body.body) {
|
|
10666
10706
|
if (!isConvertible(member)) continue;
|
|
@@ -10673,18 +10713,10 @@ function reportClass2(context, services, owner) {
|
|
|
10673
10713
|
for (const [name, members] of groups) {
|
|
10674
10714
|
const first = members[0];
|
|
10675
10715
|
if (first === void 0) continue;
|
|
10676
|
-
const fix = locallyClosed ? privateMemberFixes(
|
|
10677
|
-
context,
|
|
10678
|
-
services,
|
|
10679
|
-
owner,
|
|
10680
|
-
members,
|
|
10681
|
-
true
|
|
10682
|
-
) : void 0;
|
|
10683
10716
|
context.report({
|
|
10684
10717
|
node: first.key,
|
|
10685
10718
|
messageId: "preferEcmascriptPrivate",
|
|
10686
|
-
data: { name }
|
|
10687
|
-
...fix === void 0 ? {} : { fix }
|
|
10719
|
+
data: { name }
|
|
10688
10720
|
});
|
|
10689
10721
|
}
|
|
10690
10722
|
}
|
|
@@ -10698,7 +10730,6 @@ var prefer_ecmascript_private_members_default = createRule({
|
|
|
10698
10730
|
meta: {
|
|
10699
10731
|
type: "suggestion",
|
|
10700
10732
|
docs: { description: "Prefer ECMAScript `#private` class members over TypeScript-only `private` members." },
|
|
10701
|
-
fixable: "code",
|
|
10702
10733
|
schema: [],
|
|
10703
10734
|
messages: {
|
|
10704
10735
|
preferEcmascriptPrivate: "TypeScript `private {{name}}` is erased at runtime; use the ECMAScript private name `#{{name}}`."
|
|
@@ -10715,8 +10746,8 @@ var prefer_ecmascript_private_members_default = createRule({
|
|
|
10715
10746
|
}
|
|
10716
10747
|
if (services === null) return {};
|
|
10717
10748
|
return {
|
|
10718
|
-
ClassDeclaration: (node) => reportClass2(context,
|
|
10719
|
-
ClassExpression: (node) => reportClass2(context,
|
|
10749
|
+
ClassDeclaration: (node) => reportClass2(context, node),
|
|
10750
|
+
ClassExpression: (node) => reportClass2(context, node)
|
|
10720
10751
|
};
|
|
10721
10752
|
}
|
|
10722
10753
|
});
|
|
@@ -10726,10 +10757,10 @@ import "@typescript-eslint/utils";
|
|
|
10726
10757
|
import { AST_NODE_TYPES as AST_NODE_TYPES46 } from "@typescript-eslint/utils";
|
|
10727
10758
|
var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
|
|
10728
10759
|
summary: "Flag flat result objects with a required positive boolean status and optional success/failure payloads.",
|
|
10729
|
-
rationale: "
|
|
10730
|
-
remediation: "
|
|
10760
|
+
rationale: "When success and failure are mutually exclusive outcomes, a boolean plus optional branch data permits contradictory and incomplete states.",
|
|
10761
|
+
remediation: "If the outcomes are mutually exclusive, represent each branch as a discriminated union member with its required payload.",
|
|
10731
10762
|
category: "correctness",
|
|
10732
|
-
limitations: ["Only local object shapes with recognized
|
|
10763
|
+
limitations: ["Only local object shapes with recognized non-computed status and payload names are inspected. Names do not prove that partial-success outcomes are forbidden; review the domain before changing its representation."],
|
|
10733
10764
|
examples: [
|
|
10734
10765
|
{ id: "explicit-result-branches", title: "Use explicit result branches", outcome: "no-match", files: [{ path: "src/result.ts", source: "type Result = { ok: true; data: string } | { ok: false; error: string };" }], focusPath: "src/result.ts", expectedCount: 0, public: true },
|
|
10735
10766
|
{ id: "optional-result-payloads", title: "Do not make both result payloads optional", outcome: "match", files: [{ path: "src/result.ts", source: "type Result = { ok: boolean; data?: string; error?: string };" }], focusPath: "src/result.ts", expectedCount: 1, public: true }
|
|
@@ -10793,7 +10824,7 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
|
|
|
10793
10824
|
return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && (hasSuccessPayload || !hasUnrecognizedMember);
|
|
10794
10825
|
}
|
|
10795
10826
|
function getMemberName(member) {
|
|
10796
|
-
if (member.type !== AST_NODE_TYPES46.TSPropertySignature) {
|
|
10827
|
+
if (member.type !== AST_NODE_TYPES46.TSPropertySignature || member.computed) {
|
|
10797
10828
|
return null;
|
|
10798
10829
|
}
|
|
10799
10830
|
const { key } = member;
|
|
@@ -10829,7 +10860,7 @@ var prefer_discriminated_union_default = createRule({
|
|
|
10829
10860
|
},
|
|
10830
10861
|
schema: [],
|
|
10831
10862
|
messages: {
|
|
10832
|
-
preferDiscriminatedUnion: "This object type
|
|
10863
|
+
preferDiscriminatedUnion: "This object type combines a boolean status with optional payloads. If success and failure are mutually exclusive, consider a discriminated union such as `{ ok: true; data: T } | { ok: false; error: E }`."
|
|
10833
10864
|
}
|
|
10834
10865
|
},
|
|
10835
10866
|
defaultOptions: [],
|
|
@@ -10869,13 +10900,14 @@ var prefer_discriminated_union_default = createRule({
|
|
|
10869
10900
|
});
|
|
10870
10901
|
|
|
10871
10902
|
// src/rules/prefer-input-group-search.ts
|
|
10872
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES47 } from "@typescript-eslint/utils";
|
|
10903
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES47, ASTUtils as ASTUtils18 } from "@typescript-eslint/utils";
|
|
10873
10904
|
var PREFER_INPUT_GROUP_SEARCH_DOCUMENTATION = {
|
|
10874
10905
|
summary: "Require search icons and shared Input controls in the same visual wrapper to use InputGroup.",
|
|
10875
10906
|
rationale: "The shared compound control provides consistent spacing, focus behavior, and accessible composition.",
|
|
10876
10907
|
remediation: "Compose the search icon and field with InputGroup, InputGroupAddon, and InputGroupInput.",
|
|
10877
10908
|
category: "style",
|
|
10878
10909
|
limitations: [
|
|
10910
|
+
"Opposite branches of the same conditional expression and icons with explicit interaction handlers are excluded; arbitrary component behavior is not inferred.",
|
|
10879
10911
|
"Only Search and Input bindings imported from the recognized shared modules are paired.",
|
|
10880
10912
|
"The file must import InputGroup, proving that the repository has adopted that optional primitive."
|
|
10881
10913
|
],
|
|
@@ -10927,10 +10959,26 @@ function nearestEligibleCommonAncestor(search, input, inputGroupNames) {
|
|
|
10927
10959
|
return null;
|
|
10928
10960
|
}
|
|
10929
10961
|
function isActionIcon(search, wrapper) {
|
|
10962
|
+
if (hasInteraction(search.node)) return true;
|
|
10930
10963
|
return jsxAncestors(search).some((ancestor) => {
|
|
10931
10964
|
if (ancestor === wrapper) return false;
|
|
10932
10965
|
const name = elementName(ancestor.openingElement);
|
|
10933
|
-
return name === "a" || name === "button";
|
|
10966
|
+
return name === "a" || name === "button" || hasInteraction(ancestor.openingElement);
|
|
10967
|
+
});
|
|
10968
|
+
}
|
|
10969
|
+
function hasInteraction(node) {
|
|
10970
|
+
return node.attributes.some(
|
|
10971
|
+
(attribute) => attribute.type === AST_NODE_TYPES47.JSXAttribute && attribute.name.type === AST_NODE_TYPES47.JSXIdentifier && /^(?:on[A-Z]|href$)/u.test(attribute.name.name)
|
|
10972
|
+
);
|
|
10973
|
+
}
|
|
10974
|
+
function mutuallyExclusive(left, right) {
|
|
10975
|
+
return left.ancestors.some((ancestor, index) => {
|
|
10976
|
+
if (ancestor.type !== AST_NODE_TYPES47.ConditionalExpression) return false;
|
|
10977
|
+
const otherIndex = right.ancestors.indexOf(ancestor);
|
|
10978
|
+
if (otherIndex < 0) return false;
|
|
10979
|
+
const leftBranch = left.ancestors[index + 1];
|
|
10980
|
+
const rightBranch = right.ancestors[otherIndex + 1];
|
|
10981
|
+
return leftBranch === ancestor.consequent && rightBranch === ancestor.alternate || leftBranch === ancestor.alternate && rightBranch === ancestor.consequent;
|
|
10934
10982
|
});
|
|
10935
10983
|
}
|
|
10936
10984
|
var prefer_input_group_search_default = createRule({
|
|
@@ -10955,6 +11003,7 @@ var prefer_input_group_search_default = createRule({
|
|
|
10955
11003
|
const searches = [];
|
|
10956
11004
|
return {
|
|
10957
11005
|
ImportDeclaration(node) {
|
|
11006
|
+
if (node.importKind === "type") return;
|
|
10958
11007
|
const source = String(node.source.value);
|
|
10959
11008
|
if (source === "lucide-react") {
|
|
10960
11009
|
for (const exported of SEARCH_EXPORTS) {
|
|
@@ -10975,6 +11024,8 @@ var prefer_input_group_search_default = createRule({
|
|
|
10975
11024
|
JSXOpeningElement(node) {
|
|
10976
11025
|
const name = elementName(node);
|
|
10977
11026
|
if (name === null) return;
|
|
11027
|
+
const binding = ASTUtils18.findVariable(context.sourceCode.getScope(node), name);
|
|
11028
|
+
if (binding?.defs.length !== 1 || binding.defs[0]?.node.type !== AST_NODE_TYPES47.ImportSpecifier || binding.defs[0].node.importKind === "type") return;
|
|
10978
11029
|
const occurrence = {
|
|
10979
11030
|
ancestors: context.sourceCode.getAncestors(node),
|
|
10980
11031
|
node
|
|
@@ -10988,6 +11039,7 @@ var prefer_input_group_search_default = createRule({
|
|
|
10988
11039
|
for (const search of searches) {
|
|
10989
11040
|
if (isWithinInputGroup(search, inputGroupNames)) continue;
|
|
10990
11041
|
for (const input of inputs) {
|
|
11042
|
+
if (mutuallyExclusive(search, input)) continue;
|
|
10991
11043
|
if (isWithinInputGroup(input, inputGroupNames)) continue;
|
|
10992
11044
|
const wrapper = nearestEligibleCommonAncestor(
|
|
10993
11045
|
search,
|
|
@@ -11012,7 +11064,7 @@ var prefer_input_group_search_default = createRule({
|
|
|
11012
11064
|
|
|
11013
11065
|
// src/rules/prefer-millisecond-control-duration-schema.ts
|
|
11014
11066
|
import {
|
|
11015
|
-
ASTUtils as
|
|
11067
|
+
ASTUtils as ASTUtils19,
|
|
11016
11068
|
AST_NODE_TYPES as AST_NODE_TYPES48
|
|
11017
11069
|
} from "@typescript-eslint/utils";
|
|
11018
11070
|
var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
|
|
@@ -11081,7 +11133,7 @@ var prefer_millisecond_control_duration_schema_default = createRule({
|
|
|
11081
11133
|
const zodNamespaces = /* @__PURE__ */ new Set();
|
|
11082
11134
|
const objectFactories = /* @__PURE__ */ new Set();
|
|
11083
11135
|
function binding(identifier) {
|
|
11084
|
-
return
|
|
11136
|
+
return ASTUtils19.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
11085
11137
|
}
|
|
11086
11138
|
function record(target, identifier) {
|
|
11087
11139
|
const variable = binding(identifier);
|
|
@@ -11128,7 +11180,7 @@ var prefer_millisecond_control_duration_schema_default = createRule({
|
|
|
11128
11180
|
});
|
|
11129
11181
|
|
|
11130
11182
|
// src/rules/prefer-immutable-module-constant.ts
|
|
11131
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES49, ASTUtils as
|
|
11183
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES49, ASTUtils as ASTUtils20 } from "@typescript-eslint/utils";
|
|
11132
11184
|
var PREFER_IMMUTABLE_MODULE_CONSTANT_DOCUMENTATION = {
|
|
11133
11185
|
summary: "Require module-level constant collections to expose readonly state.",
|
|
11134
11186
|
rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
|
|
@@ -11281,7 +11333,7 @@ var prefer_immutable_module_constant_default = createRule({
|
|
|
11281
11333
|
create(context) {
|
|
11282
11334
|
const sourceCode = context.sourceCode;
|
|
11283
11335
|
const isUnshadowedGlobal3 = (identifier) => {
|
|
11284
|
-
const variable =
|
|
11336
|
+
const variable = ASTUtils20.findVariable(sourceCode.getScope(identifier), identifier.name);
|
|
11285
11337
|
return variable === null || variable.defs.length === 0;
|
|
11286
11338
|
};
|
|
11287
11339
|
if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
|
|
@@ -11381,6 +11433,7 @@ var PREFER_SHADCN_PRIMITIVES_DOCUMENTATION = {
|
|
|
11381
11433
|
remediation: "Replace the raw visible control with the corresponding shared shadcn component.",
|
|
11382
11434
|
category: "style",
|
|
11383
11435
|
limitations: [
|
|
11436
|
+
"Native multiple selects and controls with possibly enabled hidden attributes are excluded. Unknown JSX spreads can hide controls; visual equivalence is not inferred.",
|
|
11384
11437
|
"Hidden and file inputs, unassociated labels, and non-control semantic elements are excluded.",
|
|
11385
11438
|
"Tests and the shared components/ui primitive implementation tree are excluded.",
|
|
11386
11439
|
"Package-local project detection is opt-in and fails closed unless components.json, one unambiguous tsconfig/jsconfig alias, the exact primitive module, and its expected export all exist."
|
|
@@ -11668,6 +11721,7 @@ function isStaticallyAssociatedLabel(node) {
|
|
|
11668
11721
|
return node.parent.type === AST_NODE_TYPES50.JSXElement && containsLabelableElement(node.parent);
|
|
11669
11722
|
}
|
|
11670
11723
|
function replacementFor(node, element) {
|
|
11724
|
+
if (element === "select" && mayHaveBooleanAttribute(node, "multiple")) return null;
|
|
11671
11725
|
if (element !== "input") return RAW_PRIMITIVES[element];
|
|
11672
11726
|
const typeAttribute = effectiveAttribute(node, "type");
|
|
11673
11727
|
if (typeAttribute.kind === "unknown") return null;
|
|
@@ -11682,6 +11736,14 @@ function replacementFor(node, element) {
|
|
|
11682
11736
|
if (AMBIGUOUS_INPUT_TYPES.has(inputType)) return null;
|
|
11683
11737
|
return RAW_PRIMITIVES.input;
|
|
11684
11738
|
}
|
|
11739
|
+
function mayHaveBooleanAttribute(node, name) {
|
|
11740
|
+
for (const attribute of node.attributes.toReversed()) {
|
|
11741
|
+
if (attribute.type === AST_NODE_TYPES50.JSXSpreadAttribute) return true;
|
|
11742
|
+
if (attribute.name.type !== AST_NODE_TYPES50.JSXIdentifier || attribute.name.name !== name) continue;
|
|
11743
|
+
return !(attribute.value?.type === AST_NODE_TYPES50.JSXExpressionContainer && attribute.value.expression.type === AST_NODE_TYPES50.Literal && attribute.value.expression.value === false);
|
|
11744
|
+
}
|
|
11745
|
+
return false;
|
|
11746
|
+
}
|
|
11685
11747
|
var prefer_shadcn_primitives_default = createRule({
|
|
11686
11748
|
name: "prefer-shadcn-primitives",
|
|
11687
11749
|
documentation: PREFER_SHADCN_PRIMITIVES_DOCUMENTATION,
|
|
@@ -11727,6 +11789,9 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
11727
11789
|
JSXOpeningElement(node) {
|
|
11728
11790
|
const element = rawElementName(node);
|
|
11729
11791
|
if (element === null) return;
|
|
11792
|
+
if (mayHaveBooleanAttribute(node, "hidden") || context.sourceCode.getAncestors(node).some(
|
|
11793
|
+
(ancestor) => ancestor.type === AST_NODE_TYPES50.JSXElement && mayHaveBooleanAttribute(ancestor.openingElement, "hidden")
|
|
11794
|
+
)) return;
|
|
11730
11795
|
if (element === "label" && !isStaticallyAssociatedLabel(node)) return;
|
|
11731
11796
|
const replacement = replacementFor(node, element);
|
|
11732
11797
|
if (replacement === null) return;
|
|
@@ -11812,9 +11877,9 @@ function isIgnoredFile2(filename, sourceText) {
|
|
|
11812
11877
|
function isLocalFixtureFile(filename) {
|
|
11813
11878
|
return isTestFile(filename) || isStoryFile(filename);
|
|
11814
11879
|
}
|
|
11815
|
-
function
|
|
11880
|
+
function unwrap4(node) {
|
|
11816
11881
|
if (node.type === AST_NODE_TYPES51.TSAsExpression || node.type === AST_NODE_TYPES51.TSSatisfiesExpression || node.type === AST_NODE_TYPES51.TSNonNullExpression) {
|
|
11817
|
-
return
|
|
11882
|
+
return unwrap4(node.expression);
|
|
11818
11883
|
}
|
|
11819
11884
|
return node;
|
|
11820
11885
|
}
|
|
@@ -11826,7 +11891,7 @@ function isLiteralOnly(node, depth) {
|
|
|
11826
11891
|
if (depth > MAX_LITERAL_DEPTH) {
|
|
11827
11892
|
return false;
|
|
11828
11893
|
}
|
|
11829
|
-
const inner =
|
|
11894
|
+
const inner = unwrap4(node);
|
|
11830
11895
|
switch (inner.type) {
|
|
11831
11896
|
case AST_NODE_TYPES51.Literal: {
|
|
11832
11897
|
return !(isRegexLiteral(inner) && HAS_STATEFUL_FLAG_RE.test(inner.regex.flags));
|
|
@@ -11883,7 +11948,7 @@ function classify(init, checkRegex) {
|
|
|
11883
11948
|
if (node.arguments.length !== 1 || arg === void 0 || arg.type === AST_NODE_TYPES51.SpreadElement) {
|
|
11884
11949
|
return null;
|
|
11885
11950
|
}
|
|
11886
|
-
const entries =
|
|
11951
|
+
const entries = unwrap4(arg);
|
|
11887
11952
|
if (entries.type !== AST_NODE_TYPES51.ArrayExpression) {
|
|
11888
11953
|
return null;
|
|
11889
11954
|
}
|
|
@@ -11892,9 +11957,9 @@ function classify(init, checkRegex) {
|
|
|
11892
11957
|
return null;
|
|
11893
11958
|
}
|
|
11894
11959
|
function unwrapObjectFreeze(node) {
|
|
11895
|
-
const inner =
|
|
11960
|
+
const inner = unwrap4(node);
|
|
11896
11961
|
if (inner.type === AST_NODE_TYPES51.CallExpression && inner.callee.type === AST_NODE_TYPES51.MemberExpression && !inner.callee.computed && inner.callee.object.type === AST_NODE_TYPES51.Identifier && inner.callee.object.name === "Object" && inner.callee.property.type === AST_NODE_TYPES51.Identifier && inner.callee.property.name === "freeze" && inner.arguments.length === 1 && inner.arguments[0] !== void 0 && inner.arguments[0].type !== AST_NODE_TYPES51.SpreadElement) {
|
|
11897
|
-
return
|
|
11962
|
+
return unwrap4(inner.arguments[0]);
|
|
11898
11963
|
}
|
|
11899
11964
|
return inner;
|
|
11900
11965
|
}
|
|
@@ -12427,7 +12492,7 @@ var prefer_module_level_schema_default = createRule({
|
|
|
12427
12492
|
// src/rules/prefer-module-level-refined-schema.ts
|
|
12428
12493
|
import {
|
|
12429
12494
|
AST_NODE_TYPES as AST_NODE_TYPES53,
|
|
12430
|
-
ASTUtils as
|
|
12495
|
+
ASTUtils as ASTUtils21
|
|
12431
12496
|
} from "@typescript-eslint/utils";
|
|
12432
12497
|
var BENCHMARK_PATH_RE = /(^|[/\\])(?:benchmarks?|bench)[/\\]/;
|
|
12433
12498
|
var FACTORIES = /* @__PURE__ */ new Set([
|
|
@@ -12698,7 +12763,7 @@ var prefer_module_level_refined_schema_default = createRule({
|
|
|
12698
12763
|
return {};
|
|
12699
12764
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
12700
12765
|
function resolvedBinding(identifier) {
|
|
12701
|
-
return
|
|
12766
|
+
return ASTUtils21.findVariable(
|
|
12702
12767
|
context.sourceCode.getScope(identifier),
|
|
12703
12768
|
identifier.name
|
|
12704
12769
|
);
|
|
@@ -12799,7 +12864,7 @@ var prefer_module_level_refined_schema_default = createRule({
|
|
|
12799
12864
|
// src/rules/prefer-multi-value-zod-literal.ts
|
|
12800
12865
|
import {
|
|
12801
12866
|
AST_NODE_TYPES as AST_NODE_TYPES54,
|
|
12802
|
-
ASTUtils as
|
|
12867
|
+
ASTUtils as ASTUtils22
|
|
12803
12868
|
} from "@typescript-eslint/utils";
|
|
12804
12869
|
var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
12805
12870
|
summary: "Use the Zod 4 multi-value literal API instead of a union of literal schemas.",
|
|
@@ -12846,7 +12911,7 @@ function isStaticPrimitive(node, context) {
|
|
|
12846
12911
|
if (node.type === AST_NODE_TYPES54.TemplateLiteral && node.expressions.length === 0)
|
|
12847
12912
|
return true;
|
|
12848
12913
|
if (node.type === AST_NODE_TYPES54.Identifier && node.name === "undefined") {
|
|
12849
|
-
const binding =
|
|
12914
|
+
const binding = ASTUtils22.findVariable(
|
|
12850
12915
|
context.sourceCode.getScope(node),
|
|
12851
12916
|
node.name
|
|
12852
12917
|
);
|
|
@@ -12883,7 +12948,7 @@ var prefer_multi_value_zod_literal_default = createRule({
|
|
|
12883
12948
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
12884
12949
|
const zod4Bindings = /* @__PURE__ */ new Set();
|
|
12885
12950
|
function resolvedBinding(identifier) {
|
|
12886
|
-
return
|
|
12951
|
+
return ASTUtils22.findVariable(
|
|
12887
12952
|
context.sourceCode.getScope(identifier),
|
|
12888
12953
|
identifier.name
|
|
12889
12954
|
);
|
|
@@ -12954,7 +13019,7 @@ function isLiteralUnion(node) {
|
|
|
12954
13019
|
function exportedContract(node) {
|
|
12955
13020
|
let current = node;
|
|
12956
13021
|
while (current !== void 0) {
|
|
12957
|
-
if (current.type === AST_NODE_TYPES55.
|
|
13022
|
+
if (current.type === AST_NODE_TYPES55.TSTypeAliasDeclaration || current.type === AST_NODE_TYPES55.TSInterfaceDeclaration) return current.parent.type === AST_NODE_TYPES55.ExportNamedDeclaration;
|
|
12958
13023
|
if (current.type === AST_NODE_TYPES55.Program) return false;
|
|
12959
13024
|
current = current.parent ?? void 0;
|
|
12960
13025
|
}
|
|
@@ -13001,10 +13066,10 @@ var PREFER_NAMED_COMPLEX_RETURN_TYPE_DOCUMENTATION = {
|
|
|
13001
13066
|
{ id: "inline-result", title: "Do not inline a multi-state result", outcome: "match", files: [{ path: "src/queue.ts", source: "export function claim(): { state: 'idle' } | { state: 'waiting'; retryAt: number } | { state: 'claimed'; id: string } { return { state: 'idle' }; }" }], focusPath: "src/queue.ts", expectedCount: 1, public: true }
|
|
13002
13067
|
]
|
|
13003
13068
|
};
|
|
13004
|
-
function
|
|
13069
|
+
function unwrap5(node) {
|
|
13005
13070
|
if (node.type === AST_NODE_TYPES56.TSTypeReference && node.typeArguments?.params.length === 1) {
|
|
13006
13071
|
const [inner] = node.typeArguments.params;
|
|
13007
|
-
if (inner !== void 0) return
|
|
13072
|
+
if (inner !== void 0) return unwrap5(inner);
|
|
13008
13073
|
}
|
|
13009
13074
|
return node;
|
|
13010
13075
|
}
|
|
@@ -13015,10 +13080,10 @@ function report(context, node) {
|
|
|
13015
13080
|
}
|
|
13016
13081
|
}
|
|
13017
13082
|
function isComplex(node) {
|
|
13018
|
-
const type =
|
|
13083
|
+
const type = unwrap5(node);
|
|
13019
13084
|
if (type.type === AST_NODE_TYPES56.TSTypeLiteral) return type.members.length >= 3;
|
|
13020
13085
|
if (type.type !== AST_NODE_TYPES56.TSUnionType || type.types.length < 3) return false;
|
|
13021
|
-
return type.types.every((member) =>
|
|
13086
|
+
return type.types.every((member) => unwrap5(member).type === AST_NODE_TYPES56.TSTypeLiteral);
|
|
13022
13087
|
}
|
|
13023
13088
|
var prefer_named_complex_return_type_default = createRule({
|
|
13024
13089
|
name: "prefer-named-complex-return-type",
|
|
@@ -13045,14 +13110,14 @@ var prefer_named_complex_return_type_default = createRule({
|
|
|
13045
13110
|
});
|
|
13046
13111
|
|
|
13047
13112
|
// src/rules/prefer-native-random-uuid.ts
|
|
13048
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as
|
|
13113
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils23 } from "@typescript-eslint/utils";
|
|
13049
13114
|
var PREFER_NATIVE_RANDOM_UUID_DOCUMENTATION = {
|
|
13050
13115
|
summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
|
|
13051
13116
|
rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
|
|
13052
13117
|
remediation: "Call `globalThis.crypto.randomUUID()` and remove the unused `uuid` v4 import when possible.",
|
|
13053
13118
|
category: "maintainability",
|
|
13054
13119
|
autofix: "suggestion",
|
|
13055
|
-
limitations: ["Only resolved zero-argument UUID v4 calls are reported; customized and other UUID versions are excluded."],
|
|
13120
|
+
limitations: ["Only resolved zero-argument UUID v4 calls are reported; customized and other UUID versions are excluded. Suggestions require unshadowed globalThis and comment-free calls; verify native randomUUID availability in the deployment runtime."],
|
|
13056
13121
|
examples: [
|
|
13057
13122
|
{ id: "native-random-uuid", title: "Use the platform UUID generator", outcome: "no-match", files: [{ path: "src/id.ts", source: "const id = globalThis.crypto.randomUUID();" }], focusPath: "src/id.ts", expectedCount: 0, public: true },
|
|
13058
13123
|
{ id: "uuid-v4-package", title: "Do not call uuid v4 without options", outcome: "match", files: [{ path: "src/id.ts", source: "import { v4 } from 'uuid'; const id = v4();" }], focusPath: "src/id.ts", expectedCount: 1, public: true }
|
|
@@ -13072,7 +13137,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
13072
13137
|
hasSuggestions: true,
|
|
13073
13138
|
schema: [],
|
|
13074
13139
|
messages: {
|
|
13075
|
-
preferNative: "
|
|
13140
|
+
preferNative: "Where supported by the deployment runtime, prefer native `globalThis.crypto.randomUUID()` over the `uuid` package for UUID v4.",
|
|
13076
13141
|
replaceWithNative: "Replace this UUID v4 call with the native implementation."
|
|
13077
13142
|
}
|
|
13078
13143
|
},
|
|
@@ -13081,22 +13146,24 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
13081
13146
|
const directBindings = /* @__PURE__ */ new Set();
|
|
13082
13147
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
13083
13148
|
function resolve2(identifier) {
|
|
13084
|
-
return
|
|
13149
|
+
return ASTUtils23.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
13085
13150
|
}
|
|
13086
13151
|
function record(identifier, destination) {
|
|
13087
13152
|
const variable = resolve2(identifier);
|
|
13088
13153
|
if (variable !== null) destination.add(variable);
|
|
13089
13154
|
}
|
|
13090
13155
|
function report2(node) {
|
|
13156
|
+
const globalBinding = ASTUtils23.findVariable(context.sourceCode.getScope(node), "globalThis");
|
|
13157
|
+
const canSuggest = (globalBinding?.defs.length ?? 0) === 0 && context.sourceCode.getCommentsInside(node).length === 0;
|
|
13091
13158
|
context.report({
|
|
13092
13159
|
node,
|
|
13093
13160
|
messageId: "preferNative",
|
|
13094
|
-
suggest: [
|
|
13161
|
+
suggest: canSuggest ? [
|
|
13095
13162
|
{
|
|
13096
13163
|
messageId: "replaceWithNative",
|
|
13097
13164
|
fix: (fixer) => fixer.replaceText(node, "globalThis.crypto.randomUUID()")
|
|
13098
13165
|
}
|
|
13099
|
-
]
|
|
13166
|
+
] : []
|
|
13100
13167
|
});
|
|
13101
13168
|
}
|
|
13102
13169
|
return {
|
|
@@ -13144,7 +13211,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
13144
13211
|
});
|
|
13145
13212
|
|
|
13146
13213
|
// src/rules/prefer-node-crypto-hash.ts
|
|
13147
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as
|
|
13214
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils24 } from "@typescript-eslint/utils";
|
|
13148
13215
|
var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
|
|
13149
13216
|
summary: "Prefer the modern one-shot node:crypto hash API when streaming state is unnecessary.",
|
|
13150
13217
|
rationale: "A createHash-update-digest chain allocates mutable streaming state for a single in-memory value; Node's built-in hash function expresses the one-shot operation directly and can use its optimized fast path.",
|
|
@@ -13184,7 +13251,7 @@ function isUnshadowedBuiltinIdentifier(identifier, resolve2) {
|
|
|
13184
13251
|
const variable = resolve2(identifier);
|
|
13185
13252
|
return variable === null || variable.defs.length === 0;
|
|
13186
13253
|
}
|
|
13187
|
-
function
|
|
13254
|
+
function propertyName2(node) {
|
|
13188
13255
|
if (!node.computed && node.key.type === AST_NODE_TYPES58.Identifier) return node.key.name;
|
|
13189
13256
|
if (node.key.type === AST_NODE_TYPES58.Literal && typeof node.key.value === "string") {
|
|
13190
13257
|
return node.key.value;
|
|
@@ -13200,7 +13267,7 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
13200
13267
|
const directBindings = /* @__PURE__ */ new Set();
|
|
13201
13268
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
13202
13269
|
function resolve2(identifier) {
|
|
13203
|
-
return
|
|
13270
|
+
return ASTUtils24.findVariable(
|
|
13204
13271
|
context.sourceCode.getScope(identifier),
|
|
13205
13272
|
identifier.name
|
|
13206
13273
|
);
|
|
@@ -13230,7 +13297,7 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
13230
13297
|
}
|
|
13231
13298
|
if (node.id.type !== AST_NODE_TYPES58.ObjectPattern) return;
|
|
13232
13299
|
for (const property of node.id.properties) {
|
|
13233
|
-
if (property.type === AST_NODE_TYPES58.Property &&
|
|
13300
|
+
if (property.type === AST_NODE_TYPES58.Property && propertyName2(property) === "createHash" && property.value.type === AST_NODE_TYPES58.Identifier) {
|
|
13234
13301
|
record(property.value, directBindings);
|
|
13235
13302
|
}
|
|
13236
13303
|
}
|
|
@@ -13312,7 +13379,7 @@ function isFsLoader(node) {
|
|
|
13312
13379
|
function isFsSpecifier(node) {
|
|
13313
13380
|
return node.type === AST_NODE_TYPES59.Literal && (node.value === "node:fs" || node.value === "fs");
|
|
13314
13381
|
}
|
|
13315
|
-
function
|
|
13382
|
+
function propertyName3(node) {
|
|
13316
13383
|
if (!node.computed && node.key.type === AST_NODE_TYPES59.Identifier) return node.key.name;
|
|
13317
13384
|
if (node.key.type === AST_NODE_TYPES59.Literal && typeof node.key.value === "string") return node.key.value;
|
|
13318
13385
|
return null;
|
|
@@ -13363,7 +13430,7 @@ var prefer_node_fs_promises_default = createRule({
|
|
|
13363
13430
|
if (node.id.type !== AST_NODE_TYPES59.ObjectPattern) return;
|
|
13364
13431
|
const synchronousImports = node.id.properties.flatMap((property) => {
|
|
13365
13432
|
if (property.type !== AST_NODE_TYPES59.Property) return [];
|
|
13366
|
-
const name =
|
|
13433
|
+
const name = propertyName3(property);
|
|
13367
13434
|
return name?.endsWith("Sync") === true ? [name] : [];
|
|
13368
13435
|
});
|
|
13369
13436
|
if (synchronousImports.length > 0) {
|
|
@@ -13387,7 +13454,7 @@ var prefer_node_fs_promises_default = createRule({
|
|
|
13387
13454
|
});
|
|
13388
13455
|
|
|
13389
13456
|
// src/rules/prefer-non-nullable-collection.ts
|
|
13390
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as
|
|
13457
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils25 } from "@typescript-eslint/utils";
|
|
13391
13458
|
var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
|
|
13392
13459
|
summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
|
|
13393
13460
|
rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
|
|
@@ -13400,7 +13467,7 @@ var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
|
|
|
13400
13467
|
]
|
|
13401
13468
|
};
|
|
13402
13469
|
var ARRAY_TYPE_NAMES = /* @__PURE__ */ new Set(["Array", "ReadonlyArray"]);
|
|
13403
|
-
function
|
|
13470
|
+
function propertyName4(node) {
|
|
13404
13471
|
const key = node.key;
|
|
13405
13472
|
if (node.computed) return null;
|
|
13406
13473
|
if (key.type === AST_NODE_TYPES60.Identifier) return key.name;
|
|
@@ -13413,7 +13480,7 @@ function isArrayType(node) {
|
|
|
13413
13480
|
}
|
|
13414
13481
|
function nullableProperty(node) {
|
|
13415
13482
|
if (node.optional) return null;
|
|
13416
|
-
const name =
|
|
13483
|
+
const name = propertyName4(node);
|
|
13417
13484
|
const annotation = node.typeAnnotation?.typeAnnotation;
|
|
13418
13485
|
if (name === null || annotation?.type !== AST_NODE_TYPES60.TSUnionType) return null;
|
|
13419
13486
|
const concrete = annotation.types.filter(
|
|
@@ -13517,14 +13584,14 @@ function directlyCoalesced(node) {
|
|
|
13517
13584
|
return parent?.type === AST_NODE_TYPES60.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
|
|
13518
13585
|
}
|
|
13519
13586
|
function identifierIsOnlyCoalesced(context, binding, fn) {
|
|
13520
|
-
const variable =
|
|
13587
|
+
const variable = ASTUtils25.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
13521
13588
|
if (variable === null || variable.references.length === 0) return false;
|
|
13522
13589
|
return variable.references.every(
|
|
13523
13590
|
(reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
|
|
13524
13591
|
);
|
|
13525
13592
|
}
|
|
13526
13593
|
function memberIsOnlyCoalesced(context, object, property, fn) {
|
|
13527
|
-
const variable =
|
|
13594
|
+
const variable = ASTUtils25.findVariable(context.sourceCode.getScope(object), object.name);
|
|
13528
13595
|
if (variable === null) return false;
|
|
13529
13596
|
const accesses = variable.references.flatMap((reference) => {
|
|
13530
13597
|
if (!belongsToFunction(reference.identifier, fn)) return [null];
|
|
@@ -13623,7 +13690,7 @@ var prefer_non_nullable_collection_default = createRule({
|
|
|
13623
13690
|
context.report({
|
|
13624
13691
|
node,
|
|
13625
13692
|
messageId: "preferNonNullableCollection",
|
|
13626
|
-
data: { name:
|
|
13693
|
+
data: { name: propertyName4(node) ?? "collection" }
|
|
13627
13694
|
});
|
|
13628
13695
|
}
|
|
13629
13696
|
}
|
|
@@ -13634,7 +13701,7 @@ var prefer_non_nullable_collection_default = createRule({
|
|
|
13634
13701
|
// src/rules/prefer-nullish-filter-predicate.ts
|
|
13635
13702
|
import {
|
|
13636
13703
|
AST_NODE_TYPES as AST_NODE_TYPES61,
|
|
13637
|
-
ASTUtils as
|
|
13704
|
+
ASTUtils as ASTUtils26,
|
|
13638
13705
|
ESLintUtils as ESLintUtils5
|
|
13639
13706
|
} from "@typescript-eslint/utils";
|
|
13640
13707
|
import ts3 from "typescript";
|
|
@@ -13680,7 +13747,7 @@ var PREFER_NULLISH_FILTER_PREDICATE_DOCUMENTATION = {
|
|
|
13680
13747
|
]
|
|
13681
13748
|
};
|
|
13682
13749
|
function isUnshadowedBoolean(node, context) {
|
|
13683
|
-
const variable =
|
|
13750
|
+
const variable = ASTUtils26.findVariable(context.sourceCode.getScope(node), node.name);
|
|
13684
13751
|
return variable === null || variable.defs.length === 0;
|
|
13685
13752
|
}
|
|
13686
13753
|
function isBuiltinArrayFilter(node, services) {
|
|
@@ -13739,7 +13806,7 @@ function isProvablyTruthy(type, checker) {
|
|
|
13739
13806
|
}
|
|
13740
13807
|
function availableParameterName(node, context) {
|
|
13741
13808
|
for (const name of ["value", "item", "element", "candidate"]) {
|
|
13742
|
-
if (
|
|
13809
|
+
if (ASTUtils26.findVariable(context.sourceCode.getScope(node), name) === null) return name;
|
|
13743
13810
|
}
|
|
13744
13811
|
return null;
|
|
13745
13812
|
}
|
|
@@ -13793,7 +13860,7 @@ var prefer_nullish_filter_predicate_default = createRule({
|
|
|
13793
13860
|
|
|
13794
13861
|
// src/rules/prefer-await-in-async-return.ts
|
|
13795
13862
|
import {
|
|
13796
|
-
ASTUtils as
|
|
13863
|
+
ASTUtils as ASTUtils27,
|
|
13797
13864
|
ESLintUtils as ESLintUtils6,
|
|
13798
13865
|
AST_NODE_TYPES as AST_NODE_TYPES62
|
|
13799
13866
|
} from "@typescript-eslint/utils";
|
|
@@ -13907,13 +13974,13 @@ var prefer_await_in_async_return_default = createRule({
|
|
|
13907
13974
|
if (services === null) return {};
|
|
13908
13975
|
const frameworkLoaders = /* @__PURE__ */ new Set();
|
|
13909
13976
|
const rememberFrameworkLoader = (identifier) => {
|
|
13910
|
-
const variable =
|
|
13977
|
+
const variable = ASTUtils27.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
13911
13978
|
if (variable !== null) frameworkLoaders.add(variable);
|
|
13912
13979
|
};
|
|
13913
13980
|
const isFrameworkLoaderCallback = (owner) => {
|
|
13914
13981
|
const parent = owner.parent;
|
|
13915
13982
|
if (parent.type !== AST_NODE_TYPES62.CallExpression || parent.arguments[0] !== owner || parent.callee.type !== AST_NODE_TYPES62.Identifier) return false;
|
|
13916
|
-
const variable =
|
|
13983
|
+
const variable = ASTUtils27.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
|
|
13917
13984
|
return variable !== null && frameworkLoaders.has(variable);
|
|
13918
13985
|
};
|
|
13919
13986
|
return {
|
|
@@ -13955,7 +14022,7 @@ var PREFER_SCHEMA_FOR_API_PAYLOAD_DOCUMENTATION = {
|
|
|
13955
14022
|
{ id: "unvalidated-payload", title: "Do not trust response JSON directly", outcome: "match", files: [{ path: "src/client.ts", source: "async function load(response) { const body = await response.json(); return body.id; }" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
|
|
13956
14023
|
]
|
|
13957
14024
|
};
|
|
13958
|
-
var
|
|
14025
|
+
var unwrap6 = (node) => {
|
|
13959
14026
|
let current = node;
|
|
13960
14027
|
while (current !== null && current !== void 0) {
|
|
13961
14028
|
if (current.type === AST_NODE_TYPES63.TSAsExpression || current.type === AST_NODE_TYPES63.TSTypeAssertion || current.type === AST_NODE_TYPES63.TSNonNullExpression || current.type === AST_NODE_TYPES63.TSSatisfiesExpression) {
|
|
@@ -13974,23 +14041,23 @@ var PROMISE_CHAIN_METHODS = /* @__PURE__ */ new Set([
|
|
|
13974
14041
|
"finally"
|
|
13975
14042
|
]);
|
|
13976
14043
|
var isSchemaParseReference = (node) => {
|
|
13977
|
-
const inner =
|
|
14044
|
+
const inner = unwrap6(node);
|
|
13978
14045
|
return inner !== null && inner.type === AST_NODE_TYPES63.MemberExpression && !inner.computed && inner.property.type === AST_NODE_TYPES63.Identifier && (inner.property.name === "parse" || inner.property.name === "safeParse");
|
|
13979
14046
|
};
|
|
13980
14047
|
var isRawPayloadSource = (node, isKnownLocalText) => {
|
|
13981
|
-
let current =
|
|
14048
|
+
let current = unwrap6(node);
|
|
13982
14049
|
if (current === null) return false;
|
|
13983
14050
|
if (current.type === AST_NODE_TYPES63.AwaitExpression) {
|
|
13984
|
-
current =
|
|
14051
|
+
current = unwrap6(current.argument);
|
|
13985
14052
|
}
|
|
13986
14053
|
if (current === null || current.type !== AST_NODE_TYPES63.CallExpression) {
|
|
13987
14054
|
return false;
|
|
13988
14055
|
}
|
|
13989
|
-
const callee =
|
|
14056
|
+
const callee = unwrap6(current.callee);
|
|
13990
14057
|
if (callee === null || callee.type !== AST_NODE_TYPES63.MemberExpression) {
|
|
13991
14058
|
return false;
|
|
13992
14059
|
}
|
|
13993
|
-
const property =
|
|
14060
|
+
const property = unwrap6(callee.property);
|
|
13994
14061
|
if (property === null || property.type !== AST_NODE_TYPES63.Identifier) {
|
|
13995
14062
|
return false;
|
|
13996
14063
|
}
|
|
@@ -14000,17 +14067,17 @@ var isRawPayloadSource = (node, isKnownLocalText) => {
|
|
|
14000
14067
|
if (PROMISE_CHAIN_METHODS.has(property.name)) {
|
|
14001
14068
|
return !current.arguments.some(isSchemaParseReference) && isRawPayloadSource(callee.object);
|
|
14002
14069
|
}
|
|
14003
|
-
const object =
|
|
14070
|
+
const object = unwrap6(callee.object);
|
|
14004
14071
|
return property.name === "parse" && object !== null && object.type === AST_NODE_TYPES63.Identifier && object.name === "JSON" && !isLocalFileRead(current.arguments[0]) && isKnownLocalText?.(current.arguments[0]) !== true;
|
|
14005
14072
|
};
|
|
14006
14073
|
var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
|
|
14007
14074
|
var isDirectLocalFileRead = (node) => {
|
|
14008
|
-
let current =
|
|
14075
|
+
let current = unwrap6(node);
|
|
14009
14076
|
if (current?.type === AST_NODE_TYPES63.AwaitExpression) {
|
|
14010
|
-
current =
|
|
14077
|
+
current = unwrap6(current.argument);
|
|
14011
14078
|
}
|
|
14012
14079
|
if (current?.type !== AST_NODE_TYPES63.CallExpression) return false;
|
|
14013
|
-
const callee =
|
|
14080
|
+
const callee = unwrap6(current.callee);
|
|
14014
14081
|
const name = callee?.type === AST_NODE_TYPES63.Identifier ? callee.name : callee?.type === AST_NODE_TYPES63.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES63.Identifier ? callee.property.name : null;
|
|
14015
14082
|
return name !== null && FILE_READ_RE.test(name);
|
|
14016
14083
|
};
|
|
@@ -14250,7 +14317,7 @@ var isGuardTestPosition = (node) => {
|
|
|
14250
14317
|
return false;
|
|
14251
14318
|
};
|
|
14252
14319
|
var unvalidatedVariableRef = (node, scope, tracked) => {
|
|
14253
|
-
const unwrapped =
|
|
14320
|
+
const unwrapped = unwrap6(node);
|
|
14254
14321
|
if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES63.Identifier) {
|
|
14255
14322
|
return null;
|
|
14256
14323
|
}
|
|
@@ -14279,7 +14346,7 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
14279
14346
|
const aliasGroups = /* @__PURE__ */ new Map();
|
|
14280
14347
|
const localFileTextVariables = /* @__PURE__ */ new Set();
|
|
14281
14348
|
const localFileTextRef = (node, scope) => {
|
|
14282
|
-
const unwrapped =
|
|
14349
|
+
const unwrapped = unwrap6(node);
|
|
14283
14350
|
if (unwrapped?.type !== AST_NODE_TYPES63.Identifier) return null;
|
|
14284
14351
|
const variable = findVariable2(scope, unwrapped.name);
|
|
14285
14352
|
return variable !== null && localFileTextVariables.has(variable) ? variable : null;
|
|
@@ -14415,7 +14482,7 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
14415
14482
|
const scope = context.sourceCode.getScope(node);
|
|
14416
14483
|
for (const arg of node.arguments) {
|
|
14417
14484
|
if (arg.type === AST_NODE_TYPES63.SpreadElement) continue;
|
|
14418
|
-
const unwrapped =
|
|
14485
|
+
const unwrapped = unwrap6(arg);
|
|
14419
14486
|
if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES63.Identifier) {
|
|
14420
14487
|
continue;
|
|
14421
14488
|
}
|
|
@@ -14427,7 +14494,7 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
14427
14494
|
if (isInsideAssertion(node)) return;
|
|
14428
14495
|
if (isValidationRead(node)) return;
|
|
14429
14496
|
const scope = context.sourceCode.getScope(node);
|
|
14430
|
-
const obj =
|
|
14497
|
+
const obj = unwrap6(node.object);
|
|
14431
14498
|
if (isRawPayloadSource(
|
|
14432
14499
|
obj,
|
|
14433
14500
|
(candidate2) => localFileTextRef(candidate2, scope) !== null
|
|
@@ -14539,6 +14606,7 @@ var PREFER_SWITCH_FOR_REPEATED_EQUALITY_DOCUMENTATION = {
|
|
|
14539
14606
|
category: "maintainability",
|
|
14540
14607
|
limitations: [
|
|
14541
14608
|
"Only direct if/else-if chains with at least three strict-equality tests are reported.",
|
|
14609
|
+
"The discriminant must be a bare identifier; calls, getters, indexed reads and other repeatedly evaluated expressions are excluded. Review case-value effects, selector mutation, branch scoping, and break/continue targets when converting manually; this is not an equivalence proof.",
|
|
14542
14610
|
"Case values may be literals, enum-like member references, or upper-case named constants; dynamic expressions are excluded.",
|
|
14543
14611
|
"The rule deliberately ignores compound predicates, loose equality, ranges, and chains that compare different discriminants."
|
|
14544
14612
|
],
|
|
@@ -14552,7 +14620,8 @@ function discriminantText(sourceCode, test) {
|
|
|
14552
14620
|
const leftIsCase = isCaseValue(test.left);
|
|
14553
14621
|
const rightIsCase = isCaseValue(test.right);
|
|
14554
14622
|
if (leftIsCase === rightIsCase) return null;
|
|
14555
|
-
|
|
14623
|
+
const discriminant = leftIsCase ? test.right : test.left;
|
|
14624
|
+
return discriminant.type === AST_NODE_TYPES65.Identifier ? sourceCode.getText(discriminant) : null;
|
|
14556
14625
|
}
|
|
14557
14626
|
function isCaseValue(node) {
|
|
14558
14627
|
if (node.type === AST_NODE_TYPES65.Literal) return true;
|
|
@@ -14632,6 +14701,7 @@ var PREFER_SEMANTIC_COLORS_DOCUMENTATION = {
|
|
|
14632
14701
|
remediation: "Replace raw palette and literal colors with the closest semantic design-system token or CSS variable.",
|
|
14633
14702
|
category: "style",
|
|
14634
14703
|
limitations: [
|
|
14704
|
+
"Class-composition helper objects use literal keys as class fragments; cva/tv configuration objects retain value traversal. Computed keys are not resolved. URL payloads are not color literals.",
|
|
14635
14705
|
"Email, PDF, video-rendering, print-only, icon artwork, masks, gradients, stories, and explicitly configured non-token projects have targeted exclusions.",
|
|
14636
14706
|
"Opaque-foreground checks are opt-in and require both a same-variant semantic background class and its package-local declared foreground token."
|
|
14637
14707
|
],
|
|
@@ -15036,7 +15106,7 @@ var prefer_semantic_colors_default = createRule({
|
|
|
15036
15106
|
});
|
|
15037
15107
|
}
|
|
15038
15108
|
};
|
|
15039
|
-
const checkClassNode = (node) => {
|
|
15109
|
+
const checkClassNode = (node, objectKeys = false) => {
|
|
15040
15110
|
if (node === null) return;
|
|
15041
15111
|
switch (node.type) {
|
|
15042
15112
|
case AST_NODE_TYPES66.Literal:
|
|
@@ -15047,27 +15117,35 @@ var prefer_semantic_colors_default = createRule({
|
|
|
15047
15117
|
break;
|
|
15048
15118
|
case AST_NODE_TYPES66.ArrayExpression:
|
|
15049
15119
|
for (const element of node.elements) {
|
|
15050
|
-
if (element !== null && element.type !== AST_NODE_TYPES66.SpreadElement) checkClassNode(element);
|
|
15120
|
+
if (element !== null && element.type !== AST_NODE_TYPES66.SpreadElement) checkClassNode(element, objectKeys);
|
|
15051
15121
|
}
|
|
15052
15122
|
break;
|
|
15053
15123
|
case AST_NODE_TYPES66.ObjectExpression:
|
|
15054
15124
|
for (const property of node.properties) {
|
|
15055
|
-
if (property.type
|
|
15125
|
+
if (property.type !== AST_NODE_TYPES66.Property) continue;
|
|
15126
|
+
if (objectKeys) {
|
|
15127
|
+
if (property.value.type === AST_NODE_TYPES66.Literal && !property.value.value && !("regex" in property.value)) continue;
|
|
15128
|
+
if (!property.computed && property.key.type === AST_NODE_TYPES66.Literal) {
|
|
15129
|
+
checkClassNode(property.key);
|
|
15130
|
+
}
|
|
15131
|
+
} else {
|
|
15132
|
+
checkClassNode(property.value);
|
|
15133
|
+
}
|
|
15056
15134
|
}
|
|
15057
15135
|
break;
|
|
15058
15136
|
case AST_NODE_TYPES66.ConditionalExpression:
|
|
15059
|
-
checkClassNode(node.consequent);
|
|
15060
|
-
checkClassNode(node.alternate);
|
|
15137
|
+
checkClassNode(node.consequent, objectKeys);
|
|
15138
|
+
checkClassNode(node.alternate, objectKeys);
|
|
15061
15139
|
break;
|
|
15062
15140
|
case AST_NODE_TYPES66.LogicalExpression:
|
|
15063
|
-
checkClassNode(node.right);
|
|
15141
|
+
checkClassNode(node.right, objectKeys);
|
|
15064
15142
|
break;
|
|
15065
15143
|
default:
|
|
15066
15144
|
break;
|
|
15067
15145
|
}
|
|
15068
15146
|
};
|
|
15069
15147
|
const checkColorValueNode = (node) => {
|
|
15070
|
-
if (node.type === AST_NODE_TYPES66.Literal && typeof node.value === "string" && RAW_COLOR_VALUE_RE.test(node.value) && !CSS_VAR_REFERENCE_RE.test(node.value)) {
|
|
15148
|
+
if (node.type === AST_NODE_TYPES66.Literal && typeof node.value === "string" && RAW_COLOR_VALUE_RE.test(node.value.replace(/url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/giu, "")) && !CSS_VAR_REFERENCE_RE.test(node.value)) {
|
|
15071
15149
|
report2(node, "inlineColor", { value: node.value });
|
|
15072
15150
|
}
|
|
15073
15151
|
};
|
|
@@ -15087,7 +15165,9 @@ var prefer_semantic_colors_default = createRule({
|
|
|
15087
15165
|
}
|
|
15088
15166
|
if (node.callee.type === AST_NODE_TYPES66.Identifier && CLASS_FNS.has(node.callee.name)) {
|
|
15089
15167
|
for (const arg of node.arguments) {
|
|
15090
|
-
if (arg.type !== AST_NODE_TYPES66.SpreadElement)
|
|
15168
|
+
if (arg.type !== AST_NODE_TYPES66.SpreadElement) {
|
|
15169
|
+
checkClassNode(arg, node.callee.name !== "cva" && node.callee.name !== "tv");
|
|
15170
|
+
}
|
|
15091
15171
|
}
|
|
15092
15172
|
}
|
|
15093
15173
|
},
|
|
@@ -15131,13 +15211,13 @@ var prefer_semantic_colors_default = createRule({
|
|
|
15131
15211
|
});
|
|
15132
15212
|
|
|
15133
15213
|
// src/rules/prefer-server-actions.ts
|
|
15134
|
-
import "@typescript-eslint/utils";
|
|
15214
|
+
import { ASTUtils as ASTUtils28 } from "@typescript-eslint/utils";
|
|
15135
15215
|
var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
|
|
15136
15216
|
summary: "Prefer Next.js Server Actions over same-origin API mutations.",
|
|
15137
|
-
rationale: "Server Actions
|
|
15138
|
-
remediation: "
|
|
15217
|
+
rationale: "Server Actions can remove a hand-written internal API wrapper while retaining typed application calls. Client invocations still cross a network and serialization boundary.",
|
|
15218
|
+
remediation: "Consider a Server Action for application-owned mutations; preserve authorization, input validation and any public API consumers.",
|
|
15139
15219
|
category: "architecture",
|
|
15140
|
-
limitations: ["Only statically recognizable /api/ mutations
|
|
15220
|
+
limitations: ["Only statically recognizable /api/ mutations through global fetch or proven Axios imports/instances in use-client modules are reported. Custom wrapper provenance, mutated configuration and unknown option overrides are not inferred; server boundaries and route handlers are excluded."],
|
|
15141
15221
|
examples: [
|
|
15142
15222
|
{ id: "server-action-call", title: "Call a Server Action", outcome: "no-match", files: [{ path: "app/tasks/page.tsx", source: "import { createTask } from './actions'; await createTask(input);" }], focusPath: "app/tasks/page.tsx", expectedCount: 0, public: true },
|
|
15143
15223
|
{ id: "api-mutation", title: "Do not mutate through an API route", outcome: "match", files: [{ path: "app/tasks/page.tsx", source: "'use client'; await fetch('/api/tasks', { method: 'POST', body });" }], focusPath: "app/tasks/page.tsx", expectedCount: 1, public: true }
|
|
@@ -15163,21 +15243,39 @@ function resolvesToGlobalFetch(context, identifier) {
|
|
|
15163
15243
|
function resolveNode(node, context) {
|
|
15164
15244
|
if (!node) return null;
|
|
15165
15245
|
if (node.type !== "Identifier") return node;
|
|
15166
|
-
|
|
15167
|
-
|
|
15168
|
-
|
|
15169
|
-
|
|
15170
|
-
|
|
15171
|
-
|
|
15172
|
-
|
|
15173
|
-
|
|
15174
|
-
|
|
15175
|
-
|
|
15176
|
-
|
|
15177
|
-
|
|
15178
|
-
|
|
15179
|
-
|
|
15180
|
-
|
|
15246
|
+
const variable = ASTUtils28.findVariable(getScope(context, node), node.name);
|
|
15247
|
+
const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
|
|
15248
|
+
if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || variable?.references.some((reference) => reference.isWrite() && reference.init !== true)) return node;
|
|
15249
|
+
if (definition.node.init.type === "ObjectExpression" && variable?.references.some((reference) => reference.identifier !== node && reference.init !== true)) return node;
|
|
15250
|
+
return definition.node.init;
|
|
15251
|
+
}
|
|
15252
|
+
function isAxiosClient(node, context, seen = /* @__PURE__ */ new Set()) {
|
|
15253
|
+
if (node.type !== "Identifier" || seen.has(node)) return false;
|
|
15254
|
+
seen.add(node);
|
|
15255
|
+
const variable = ASTUtils28.findVariable(getScope(context, node), node.name);
|
|
15256
|
+
const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
|
|
15257
|
+
if (definition === void 0 || variable?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
|
|
15258
|
+
if (variable?.references.some((reference) => {
|
|
15259
|
+
if (reference.init === true) return false;
|
|
15260
|
+
const identifier = reference.identifier;
|
|
15261
|
+
const parent = identifier.parent;
|
|
15262
|
+
if (parent.type === "CallExpression" && parent.callee === identifier) return false;
|
|
15263
|
+
return parent.type !== "MemberExpression" || parent.object !== identifier || parent.computed || parent.parent.type !== "CallExpression" || parent.parent.callee !== parent;
|
|
15264
|
+
})) return false;
|
|
15265
|
+
if (definition.type === "ImportBinding") {
|
|
15266
|
+
const declaration = definition.parent;
|
|
15267
|
+
return declaration.type === "ImportDeclaration" && declaration.source.value === "axios" && declaration.importKind !== "type" && (definition.node.type === "ImportDefaultSpecifier" || definition.node.type === "ImportSpecifier" && definition.node.importKind !== "type" && (definition.node.imported.type === "Identifier" ? definition.node.imported.name : definition.node.imported.value) === "default");
|
|
15268
|
+
}
|
|
15269
|
+
if (definition.type !== "Variable" || definition.parent.kind !== "const") return false;
|
|
15270
|
+
const init = definition.node.init;
|
|
15271
|
+
return init?.type === "CallExpression" && init.arguments.length <= 1 && hasLocalAxiosOptions(init.arguments[0], context) && init.callee.type === "MemberExpression" && !init.callee.computed && init.callee.property.type === "Identifier" && init.callee.property.name === "create" && isAxiosClient(init.callee.object, context, seen);
|
|
15272
|
+
}
|
|
15273
|
+
function hasLocalAxiosOptions(node, context) {
|
|
15274
|
+
if (node === void 0) return true;
|
|
15275
|
+
const options = resolveNode(node, context);
|
|
15276
|
+
return options?.type === "ObjectExpression" && options.properties.every(
|
|
15277
|
+
(property) => property.type === "Property" && !property.computed && property.kind === "init" && !["baseURL", "adapter"].includes(property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : "baseURL")
|
|
15278
|
+
);
|
|
15181
15279
|
}
|
|
15182
15280
|
function isApiUrl(node, context, apiPrefixes) {
|
|
15183
15281
|
const resolved = resolveNode(node, context);
|
|
@@ -15238,7 +15336,8 @@ function isFunctionArgument(node, context) {
|
|
|
15238
15336
|
}
|
|
15239
15337
|
function getPropertyNode(objNode, propName2) {
|
|
15240
15338
|
if (!objNode || objNode.type !== "ObjectExpression") return null;
|
|
15241
|
-
|
|
15339
|
+
if (objNode.properties.some((property) => property.type === "SpreadElement" || property.computed)) return null;
|
|
15340
|
+
for (const prop of [...objNode.properties].reverse()) {
|
|
15242
15341
|
if (prop.type !== "Property") continue;
|
|
15243
15342
|
let keyName = null;
|
|
15244
15343
|
if (prop.key.type === "Identifier" && !prop.computed) {
|
|
@@ -15276,7 +15375,7 @@ var prefer_server_actions_default = createRule({
|
|
|
15276
15375
|
}
|
|
15277
15376
|
],
|
|
15278
15377
|
messages: {
|
|
15279
|
-
preferServerAction: "
|
|
15378
|
+
preferServerAction: "This client mutation targets a same-origin API route. Consider a Server Action to remove the hand-written API wrapper; retain authorization and validation, since the call still crosses a network boundary."
|
|
15280
15379
|
}
|
|
15281
15380
|
},
|
|
15282
15381
|
defaultOptions: [{}],
|
|
@@ -15320,9 +15419,11 @@ var prefer_server_actions_default = createRule({
|
|
|
15320
15419
|
}
|
|
15321
15420
|
}
|
|
15322
15421
|
}
|
|
15323
|
-
} else if (node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && !node.callee.computed) {
|
|
15422
|
+
} else if (node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && !node.callee.computed && isAxiosClient(node.callee.object, context)) {
|
|
15324
15423
|
const methodName2 = node.callee.property.name.toLowerCase();
|
|
15325
15424
|
if (AXIOS_MUTATION_METHODS.has(methodName2)) {
|
|
15425
|
+
const config = node.arguments[methodName2 === "delete" ? 1 : 2];
|
|
15426
|
+
if (!hasLocalAxiosOptions(config, context)) return;
|
|
15326
15427
|
const urlArg = node.arguments[0];
|
|
15327
15428
|
const hasHandlerArg = node.arguments.some(
|
|
15328
15429
|
(arg) => arg.type !== "SpreadElement" && isFunctionArgument(arg, context)
|
|
@@ -15331,11 +15432,12 @@ var prefer_server_actions_default = createRule({
|
|
|
15331
15432
|
isMutation = true;
|
|
15332
15433
|
}
|
|
15333
15434
|
}
|
|
15334
|
-
} else if (node.callee.type === "Identifier" && (node.callee
|
|
15435
|
+
} else if (node.callee.type === "Identifier" && isAxiosClient(node.callee, context)) {
|
|
15335
15436
|
const firstArg = node.arguments[0];
|
|
15336
15437
|
if (firstArg && firstArg.type !== "SpreadElement") {
|
|
15337
15438
|
const configArg = resolveNode(firstArg, context);
|
|
15338
15439
|
if (configArg && configArg.type === "ObjectExpression") {
|
|
15440
|
+
if (!hasLocalAxiosOptions(firstArg, context)) return;
|
|
15339
15441
|
const urlNode = getPropertyNode(configArg, "url");
|
|
15340
15442
|
const methodNode = getPropertyNode(configArg, "method");
|
|
15341
15443
|
if (urlNode && isApiUrl(urlNode, context, apiPrefixes) && methodNode && isMutationMethod(methodNode, context)) {
|
|
@@ -15619,7 +15721,7 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15619
15721
|
});
|
|
15620
15722
|
|
|
15621
15723
|
// src/rules/repeated-static-call-cases.ts
|
|
15622
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as
|
|
15724
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils29 } from "@typescript-eslint/utils";
|
|
15623
15725
|
var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
|
|
15624
15726
|
summary: "Report three or more consecutive literal call assertions that should be independently named test cases.",
|
|
15625
15727
|
rationale: "Copy-pasted cases obscure the input table and stop later cases from being reported after the first failure.",
|
|
@@ -15645,7 +15747,7 @@ function staticMemberName5(node) {
|
|
|
15645
15747
|
return null;
|
|
15646
15748
|
}
|
|
15647
15749
|
function importedName6(identifier, context, modules) {
|
|
15648
|
-
const variable =
|
|
15750
|
+
const variable = ASTUtils29.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
15649
15751
|
if (variable === null || variable.defs.length === 0) return identifier.name;
|
|
15650
15752
|
for (const definition of variable.defs) {
|
|
15651
15753
|
if (definition.node.type !== AST_NODE_TYPES68.ImportSpecifier) continue;
|
|
@@ -15808,6 +15910,11 @@ var PREFER_ZOD_INFER_DOCUMENTATION = {
|
|
|
15808
15910
|
rationale: "A derived type stays synchronized when the runtime schema changes.",
|
|
15809
15911
|
remediation: "Replace the hand-written twin with `z.infer<typeof Schema>`.",
|
|
15810
15912
|
category: "correctness",
|
|
15913
|
+
limitations: [
|
|
15914
|
+
"Only module-level const schemas and module-level type declarations are paired; local declarations are excluded rather than matched by spelling across scopes.",
|
|
15915
|
+
"By default every field must positively agree; collection, nested-object and referenced-schema equivalence is not inferred from outer syntax alone.",
|
|
15916
|
+
"This is a bounded syntactic comparison, not general type equivalence. Review schema input versus output, interface augmentation, and separately evolving domain contracts before replacing a declaration."
|
|
15917
|
+
],
|
|
15811
15918
|
examples: [
|
|
15812
15919
|
{ id: "inferred-type", title: "Infer the schema type", outcome: "no-match", files: [{ path: "src/user.ts", source: 'import { z } from "zod"; const UserSchema = z.object({ id: z.string() }); type User = z.infer<typeof UserSchema>;' }], focusPath: "src/user.ts", expectedCount: 0, public: true },
|
|
15813
15920
|
{ id: "handwritten-twin", title: "Do not duplicate the schema shape", outcome: "match", files: [{ path: "src/user.ts", source: 'import { z } from "zod"; const UserSchema = z.object({ id: z.string() }); interface User { id: string }' }], focusPath: "src/user.ts", expectedCount: 1, public: true }
|
|
@@ -15987,6 +16094,10 @@ function sameDomain(left, right) {
|
|
|
15987
16094
|
function isExportedDeclaration(node) {
|
|
15988
16095
|
return node.parent?.type === AST_NODE_TYPES69.ExportNamedDeclaration;
|
|
15989
16096
|
}
|
|
16097
|
+
function isModuleLevelDeclaration(node) {
|
|
16098
|
+
const parent = node.parent;
|
|
16099
|
+
return parent?.type === AST_NODE_TYPES69.Program || parent?.type === AST_NODE_TYPES69.ExportNamedDeclaration && parent.parent.type === AST_NODE_TYPES69.Program;
|
|
16100
|
+
}
|
|
15990
16101
|
function isModuleLevelConst(node) {
|
|
15991
16102
|
const declaration = node.parent;
|
|
15992
16103
|
if (declaration.type !== AST_NODE_TYPES69.VariableDeclaration || declaration.kind !== "const") {
|
|
@@ -16037,6 +16148,9 @@ function leafAgrees(field, annotation) {
|
|
|
16037
16148
|
return annotationDomain !== null && sameDomain(field.domain, annotationDomain);
|
|
16038
16149
|
}
|
|
16039
16150
|
const { leaf } = field;
|
|
16151
|
+
if (leaf !== null && ["array", "tuple", "object", "strictObject", "looseObject", "record", "map", "set", "promise", "intersection"].includes(leaf)) {
|
|
16152
|
+
return false;
|
|
16153
|
+
}
|
|
16040
16154
|
if (leaf === null || annotation === null) {
|
|
16041
16155
|
return null;
|
|
16042
16156
|
}
|
|
@@ -16223,11 +16337,11 @@ var prefer_zod_infer_default = createRule({
|
|
|
16223
16337
|
continue;
|
|
16224
16338
|
}
|
|
16225
16339
|
const key = member.key;
|
|
16226
|
-
const
|
|
16227
|
-
if (
|
|
16340
|
+
const propertyName6 = key.type === AST_NODE_TYPES69.Identifier ? key.name : key.type === AST_NODE_TYPES69.Literal && typeof key.value === "string" ? key.value : null;
|
|
16341
|
+
if (propertyName6 === null) {
|
|
16228
16342
|
continue;
|
|
16229
16343
|
}
|
|
16230
|
-
const propertyTokens = nameTokens(
|
|
16344
|
+
const propertyTokens = nameTokens(propertyName6);
|
|
16231
16345
|
if (propertyTokens.length < 2) {
|
|
16232
16346
|
continue;
|
|
16233
16347
|
}
|
|
@@ -16242,7 +16356,7 @@ var prefer_zod_infer_default = createRule({
|
|
|
16242
16356
|
node: annotation,
|
|
16243
16357
|
owner,
|
|
16244
16358
|
ownerName,
|
|
16245
|
-
propertyName:
|
|
16359
|
+
propertyName: propertyName6,
|
|
16246
16360
|
propertyTokens
|
|
16247
16361
|
});
|
|
16248
16362
|
}
|
|
@@ -16329,7 +16443,6 @@ var prefer_zod_infer_default = createRule({
|
|
|
16329
16443
|
if (fields.size !== members.size) {
|
|
16330
16444
|
return false;
|
|
16331
16445
|
}
|
|
16332
|
-
let agreements = 0;
|
|
16333
16446
|
for (const [name, field] of fields) {
|
|
16334
16447
|
const member = members.get(name);
|
|
16335
16448
|
if (member === void 0) {
|
|
@@ -16348,14 +16461,11 @@ var prefer_zod_infer_default = createRule({
|
|
|
16348
16461
|
return false;
|
|
16349
16462
|
}
|
|
16350
16463
|
const agrees = leafAgrees(field, member.annotation);
|
|
16351
|
-
if (agrees
|
|
16464
|
+
if (agrees !== true) {
|
|
16352
16465
|
return false;
|
|
16353
16466
|
}
|
|
16354
|
-
if (agrees === true) {
|
|
16355
|
-
agreements += 1;
|
|
16356
|
-
}
|
|
16357
16467
|
}
|
|
16358
|
-
return
|
|
16468
|
+
return true;
|
|
16359
16469
|
}
|
|
16360
16470
|
return {
|
|
16361
16471
|
Program(node) {
|
|
@@ -16369,7 +16479,7 @@ var prefer_zod_infer_default = createRule({
|
|
|
16369
16479
|
recordZodImport(node);
|
|
16370
16480
|
},
|
|
16371
16481
|
VariableDeclarator(node) {
|
|
16372
|
-
if (node.id.type !== AST_NODE_TYPES69.Identifier || node.init == null) {
|
|
16482
|
+
if (node.id.type !== AST_NODE_TYPES69.Identifier || node.init == null || !isModuleLevelConst(node)) {
|
|
16373
16483
|
return;
|
|
16374
16484
|
}
|
|
16375
16485
|
const fields = schemaFields(node.init);
|
|
@@ -16402,6 +16512,7 @@ var prefer_zod_infer_default = createRule({
|
|
|
16402
16512
|
}
|
|
16403
16513
|
},
|
|
16404
16514
|
TSInterfaceDeclaration(node) {
|
|
16515
|
+
if (!isModuleLevelDeclaration(node)) return;
|
|
16405
16516
|
if (node.typeParameters !== void 0 || (node.extends?.length ?? 0) > 0) {
|
|
16406
16517
|
return;
|
|
16407
16518
|
}
|
|
@@ -16417,6 +16528,7 @@ var prefer_zod_infer_default = createRule({
|
|
|
16417
16528
|
);
|
|
16418
16529
|
},
|
|
16419
16530
|
TSTypeAliasDeclaration(node) {
|
|
16531
|
+
if (!isModuleLevelDeclaration(node)) return;
|
|
16420
16532
|
const schemaName = inferredSchemaName(node.typeAnnotation);
|
|
16421
16533
|
if (schemaName !== null) {
|
|
16422
16534
|
inferredAliases.push({
|
|
@@ -16534,6 +16646,7 @@ var REQUIRE_ASSERT_NEVER_DOCUMENTATION = {
|
|
|
16534
16646
|
rationale: "An empty default silently accepts new union members instead of making the compiler identify the missing case.",
|
|
16535
16647
|
remediation: "Call `assertNever` with the discriminant in the exhaustive switch default.",
|
|
16536
16648
|
category: "correctness",
|
|
16649
|
+
limitations: ["Requires type information and singleton case values covering the current union. The helper must accept never to provide a compile-time check; its spelling alone is not a proof of that contract. Review the desired runtime behavior for unexpected external values."],
|
|
16537
16650
|
examples: [
|
|
16538
16651
|
{ id: "assert-never-default", title: "Make the default exhaustive", outcome: "no-match", files: [{ path: "src/render.ts", source: "declare const kind: 'a' | 'b';\nswitch (kind) { case 'a': break; case 'b': break; default: assertNever(kind); }" }], focusPath: "src/render.ts", expectedCount: 0, public: true },
|
|
16539
16652
|
{ id: "empty-default", title: "Do not leave an exhaustive default empty", outcome: "match", files: [{ path: "src/render.ts", source: "declare const kind: 'a' | 'b';\nswitch (kind) { case 'a': break; case 'b': break; default: }" }], focusPath: "src/render.ts", expectedCount: 1, public: true }
|
|
@@ -16590,11 +16703,9 @@ function isExhaustiveFiniteSwitch(node, services) {
|
|
|
16590
16703
|
if (caseNode.test === null) continue;
|
|
16591
16704
|
const test = services.esTreeNodeToTSNodeMap.get(caseNode.test);
|
|
16592
16705
|
const testType = checker.getTypeAtLocation(test);
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
if (key !== null) handled.add(key);
|
|
16597
|
-
}
|
|
16706
|
+
if (testType.isUnion()) continue;
|
|
16707
|
+
const key = finiteTypeKey(testType, checker);
|
|
16708
|
+
if (key !== null) handled.add(key);
|
|
16598
16709
|
}
|
|
16599
16710
|
return [...expected].every((key) => handled.has(key));
|
|
16600
16711
|
}
|
|
@@ -16646,12 +16757,13 @@ var require_assert_never_default = createRule({
|
|
|
16646
16757
|
});
|
|
16647
16758
|
|
|
16648
16759
|
// src/rules/require-fetch-timeout.ts
|
|
16649
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as
|
|
16760
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils30 } from "@typescript-eslint/utils";
|
|
16650
16761
|
var REQUIRE_FETCH_TIMEOUT_DOCUMENTATION = {
|
|
16651
|
-
summary: "Require an abort
|
|
16762
|
+
summary: "Require an explicit abort signal on locally analyzable global fetch calls.",
|
|
16652
16763
|
rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
|
|
16653
16764
|
remediation: "Pass an abort signal, such as `AbortSignal.timeout(ms)`, in the fetch init.",
|
|
16654
16765
|
category: "correctness",
|
|
16766
|
+
limitations: ["Signal presence establishes an explicit cancellation path, not a guaranteed timeout. Forwarded Request objects can carry an existing signal."],
|
|
16655
16767
|
examples: [
|
|
16656
16768
|
{ id: "bounded-fetch", title: "Bound the request", outcome: "no-match", files: [{ path: "src/client.ts", source: "await fetch(url, { signal: AbortSignal.timeout(5000) });" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
|
|
16657
16769
|
{ id: "unbounded-fetch", title: "Do not leave fetch unbounded", outcome: "match", files: [{ path: "src/client.ts", source: "await fetch('https://api.example.com/items');" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
|
|
@@ -16697,7 +16809,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
16697
16809
|
meta: {
|
|
16698
16810
|
type: "problem",
|
|
16699
16811
|
docs: {
|
|
16700
|
-
description: "Require an abort
|
|
16812
|
+
description: "Require an explicit abort signal on locally analyzable global fetch calls."
|
|
16701
16813
|
},
|
|
16702
16814
|
schema: [
|
|
16703
16815
|
{
|
|
@@ -16713,7 +16825,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
16713
16825
|
}
|
|
16714
16826
|
],
|
|
16715
16827
|
messages: {
|
|
16716
|
-
missingSignal: "This `fetch()` has no abort
|
|
16828
|
+
missingSignal: "This `fetch()` has no explicit abort signal. Pass `AbortSignal.timeout(ms)` for a deadline, or an owner-managed signal for cancellation."
|
|
16717
16829
|
}
|
|
16718
16830
|
},
|
|
16719
16831
|
defaultOptions: [{}],
|
|
@@ -16727,7 +16839,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
16727
16839
|
}
|
|
16728
16840
|
function resolvesToGlobal(identifier) {
|
|
16729
16841
|
const scope = context.sourceCode.getScope(identifier);
|
|
16730
|
-
const variable =
|
|
16842
|
+
const variable = ASTUtils30.findVariable(scope, identifier.name);
|
|
16731
16843
|
return variable === null || variable.defs.length === 0;
|
|
16732
16844
|
}
|
|
16733
16845
|
function isGlobalFetchCall2(callee) {
|
|
@@ -16737,7 +16849,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
16737
16849
|
return callee.type === AST_NODE_TYPES71.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES71.Identifier && callee.property.name === "fetch" && callee.object.type === AST_NODE_TYPES71.Identifier && GLOBAL_OBJECTS2.has(callee.object.name) && resolvesToGlobal(callee.object);
|
|
16738
16850
|
}
|
|
16739
16851
|
function localConstInitProvablyLacksSignal(identifier) {
|
|
16740
|
-
const variable =
|
|
16852
|
+
const variable = ASTUtils30.findVariable(
|
|
16741
16853
|
context.sourceCode.getScope(identifier),
|
|
16742
16854
|
identifier.name
|
|
16743
16855
|
);
|
|
@@ -16756,13 +16868,23 @@ var require_fetch_timeout_default = createRule({
|
|
|
16756
16868
|
}
|
|
16757
16869
|
return true;
|
|
16758
16870
|
}
|
|
16871
|
+
function isForwardedRequest(argument) {
|
|
16872
|
+
let value = argument;
|
|
16873
|
+
if (value.type === AST_NODE_TYPES71.Identifier) {
|
|
16874
|
+
const binding = ASTUtils30.findVariable(context.sourceCode.getScope(value), value.name);
|
|
16875
|
+
const definition = binding?.defs.length === 1 ? binding.defs[0] : void 0;
|
|
16876
|
+
if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
|
|
16877
|
+
value = definition.node.init;
|
|
16878
|
+
}
|
|
16879
|
+
return value.type === AST_NODE_TYPES71.NewExpression && value.callee.type === AST_NODE_TYPES71.Identifier && value.callee.name === "Request" && resolvesToGlobal(value.callee);
|
|
16880
|
+
}
|
|
16759
16881
|
return {
|
|
16760
16882
|
CallExpression(node) {
|
|
16761
16883
|
if (!isGlobalFetchCall2(node.callee)) {
|
|
16762
16884
|
return;
|
|
16763
16885
|
}
|
|
16764
16886
|
const [first, init] = node.arguments;
|
|
16765
|
-
if (node.arguments.length === 1 &&
|
|
16887
|
+
if (first !== void 0 && (node.arguments.length === 1 && !isInlineUrl(first, resolvesToGlobal) || isForwardedRequest(first))) {
|
|
16766
16888
|
return;
|
|
16767
16889
|
}
|
|
16768
16890
|
if (init === void 0 || initProvablyLacksSignal(init) || init.type === AST_NODE_TYPES71.Identifier && localConstInitProvablyLacksSignal(init)) {
|
|
@@ -17238,15 +17360,14 @@ var publicMethodNames = (body2, functionAliases) => {
|
|
|
17238
17360
|
if (member.static || member.accessibility === "private" || member.accessibility === "protected") continue;
|
|
17239
17361
|
if (member.key.type === AST_NODE_TYPES73.PrivateIdentifier) continue;
|
|
17240
17362
|
if (member.value?.type !== AST_NODE_TYPES73.ArrowFunctionExpression && member.value?.type !== AST_NODE_TYPES73.FunctionExpression && member.typeAnnotation?.typeAnnotation.type !== AST_NODE_TYPES73.TSFunctionType && !(member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES73.TSTypeReference && member.typeAnnotation.typeAnnotation.typeName.type === AST_NODE_TYPES73.Identifier && functionAliases.has(member.typeAnnotation.typeAnnotation.typeName.name))) continue;
|
|
17241
|
-
names.push(member
|
|
17363
|
+
names.push(declaredMemberName(member) ?? "\u2026");
|
|
17242
17364
|
continue;
|
|
17243
17365
|
}
|
|
17244
17366
|
if (member.type !== AST_NODE_TYPES73.MethodDefinition) continue;
|
|
17245
17367
|
if (member.kind !== "method" || member.static) continue;
|
|
17246
17368
|
if (member.accessibility === "private" || member.accessibility === "protected") continue;
|
|
17247
17369
|
if (member.key.type === AST_NODE_TYPES73.PrivateIdentifier) continue;
|
|
17248
|
-
|
|
17249
|
-
else names.push("\u2026");
|
|
17370
|
+
names.push(declaredMemberName(member) ?? "\u2026");
|
|
17250
17371
|
}
|
|
17251
17372
|
return names;
|
|
17252
17373
|
};
|
|
@@ -17287,6 +17408,11 @@ function localClassAbstractness(program) {
|
|
|
17287
17408
|
}
|
|
17288
17409
|
return classes;
|
|
17289
17410
|
}
|
|
17411
|
+
function declaredMemberName(member) {
|
|
17412
|
+
if (!member.computed && member.key.type === AST_NODE_TYPES73.Identifier) return member.key.name;
|
|
17413
|
+
if (member.key.type === AST_NODE_TYPES73.Literal && typeof member.key.value === "string") return member.key.value;
|
|
17414
|
+
return null;
|
|
17415
|
+
}
|
|
17290
17416
|
function localInterfaceSurfaces(program) {
|
|
17291
17417
|
const interfaces = /* @__PURE__ */ new Map();
|
|
17292
17418
|
const parents = /* @__PURE__ */ new Map();
|
|
@@ -17309,14 +17435,15 @@ function localInterfaceSurfaces(program) {
|
|
|
17309
17435
|
if (part.type !== AST_NODE_TYPES73.TSTypeLiteral) continue;
|
|
17310
17436
|
for (const member of part.members) {
|
|
17311
17437
|
if (member.type !== AST_NODE_TYPES73.TSMethodSignature && member.type !== AST_NODE_TYPES73.TSPropertySignature) continue;
|
|
17312
|
-
|
|
17438
|
+
const name = declaredMemberName(member);
|
|
17439
|
+
if (name === null) continue;
|
|
17313
17440
|
if (member.type === AST_NODE_TYPES73.TSMethodSignature) {
|
|
17314
|
-
callables2.add(
|
|
17441
|
+
callables2.add(name);
|
|
17315
17442
|
continue;
|
|
17316
17443
|
}
|
|
17317
17444
|
if (member.type !== AST_NODE_TYPES73.TSPropertySignature) continue;
|
|
17318
17445
|
const annotation = member.typeAnnotation?.typeAnnotation;
|
|
17319
|
-
if (annotation?.type === AST_NODE_TYPES73.TSFunctionType || annotation?.type === AST_NODE_TYPES73.TSTypeReference && annotation.typeName.type === AST_NODE_TYPES73.Identifier && functionAliases.has(annotation.typeName.name)) callables2.add(
|
|
17446
|
+
if (annotation?.type === AST_NODE_TYPES73.TSFunctionType || annotation?.type === AST_NODE_TYPES73.TSTypeReference && annotation.typeName.type === AST_NODE_TYPES73.Identifier && functionAliases.has(annotation.typeName.name)) callables2.add(name);
|
|
17320
17447
|
}
|
|
17321
17448
|
}
|
|
17322
17449
|
interfaces.set(declaration.id.name, callables2);
|
|
@@ -17327,8 +17454,9 @@ function localInterfaceSurfaces(program) {
|
|
|
17327
17454
|
const callables = interfaces.get(declaration.id.name) ?? /* @__PURE__ */ new Set();
|
|
17328
17455
|
for (const member of declaration.body.body) {
|
|
17329
17456
|
if (member.type !== AST_NODE_TYPES73.TSMethodSignature && member.type !== AST_NODE_TYPES73.TSPropertySignature) continue;
|
|
17330
|
-
|
|
17331
|
-
if (
|
|
17457
|
+
const name = declaredMemberName(member);
|
|
17458
|
+
if (name === null) continue;
|
|
17459
|
+
if (member.type === AST_NODE_TYPES73.TSMethodSignature || member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES73.TSFunctionType || member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES73.TSTypeReference && member.typeAnnotation.typeAnnotation.typeName.type === AST_NODE_TYPES73.Identifier && functionAliases.has(member.typeAnnotation.typeAnnotation.typeName.name)) callables.add(name);
|
|
17332
17460
|
}
|
|
17333
17461
|
interfaces.set(declaration.id.name, callables);
|
|
17334
17462
|
parents.set(
|
|
@@ -17775,7 +17903,7 @@ function isStaticValue(node) {
|
|
|
17775
17903
|
}
|
|
17776
17904
|
return false;
|
|
17777
17905
|
}
|
|
17778
|
-
function
|
|
17906
|
+
function propertyName5(property) {
|
|
17779
17907
|
if (property.computed) return null;
|
|
17780
17908
|
if (property.key.type === AST_NODE_TYPES75.Identifier) return property.key.name;
|
|
17781
17909
|
return typeof property.key.value === "string" ? property.key.value : null;
|
|
@@ -17812,7 +17940,7 @@ var require_static_next_matcher_default = createRule({
|
|
|
17812
17940
|
continue;
|
|
17813
17941
|
}
|
|
17814
17942
|
for (const property of config.properties) {
|
|
17815
|
-
if (property.type !== AST_NODE_TYPES75.Property ||
|
|
17943
|
+
if (property.type !== AST_NODE_TYPES75.Property || propertyName5(property) !== "matcher" || property.value.type === AST_NODE_TYPES75.AssignmentPattern) {
|
|
17816
17944
|
continue;
|
|
17817
17945
|
}
|
|
17818
17946
|
if (!isStaticValue(property.value)) {
|
|
@@ -17826,21 +17954,21 @@ var require_static_next_matcher_default = createRule({
|
|
|
17826
17954
|
});
|
|
17827
17955
|
|
|
17828
17956
|
// src/rules/require-use-form-default-values.ts
|
|
17829
|
-
import { ASTUtils as
|
|
17957
|
+
import { ASTUtils as ASTUtils31 } from "@typescript-eslint/utils";
|
|
17830
17958
|
var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
|
|
17831
|
-
summary: "react-hook-form useForm call without
|
|
17959
|
+
summary: "react-hook-form useForm call without explicit initial or reactive values",
|
|
17832
17960
|
rationale: "Without an explicit initial value, fields can change from uncontrolled to controlled as data arrives, reset behavior becomes ambiguous, and the form's initial shape no longer documents the values users can edit.",
|
|
17833
|
-
remediation: "
|
|
17961
|
+
remediation: "Provide defaultValues for initial state, or values when reactive external state owns initialization; choose schema-appropriate values for controlled fields.",
|
|
17834
17962
|
category: "correctness",
|
|
17835
17963
|
limitations: [
|
|
17836
|
-
"Only direct calls to a scope-resolved useForm value imported from react-hook-form are checked; wrapper hooks and computed option objects are intentionally not inferred."
|
|
17964
|
+
"Only direct calls to a scope-resolved useForm value imported from react-hook-form are checked; reactive values are accepted, while wrapper hooks, spreads and computed option objects are intentionally not inferred."
|
|
17837
17965
|
],
|
|
17838
17966
|
examples: [
|
|
17839
17967
|
{
|
|
17840
17968
|
id: "form-with-initial-values",
|
|
17841
17969
|
title: "Give the form an explicit initial shape",
|
|
17842
17970
|
outcome: "no-match",
|
|
17843
|
-
files: [{ path: "profile-form.tsx", source: "import { useForm } from 'react-hook-form'
|
|
17971
|
+
files: [{ path: "profile-form.tsx", source: "'use client'; import { useForm } from 'react-hook-form'; function ProfileForm() { const form = useForm({ defaultValues: { name: '' } }); return <input {...form.register('name')} />; }" }],
|
|
17844
17972
|
focusPath: "profile-form.tsx",
|
|
17845
17973
|
expectedCount: 0,
|
|
17846
17974
|
public: true
|
|
@@ -17849,16 +17977,16 @@ var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
|
|
|
17849
17977
|
id: "form-without-initial-values",
|
|
17850
17978
|
title: "Do not leave form initialization implicit",
|
|
17851
17979
|
outcome: "match",
|
|
17852
|
-
files: [{ path: "profile-form.tsx", source: "import { useForm } from 'react-hook-form'
|
|
17980
|
+
files: [{ path: "profile-form.tsx", source: "'use client'; import { useForm } from 'react-hook-form'; function ProfileForm() { const form = useForm({ mode: 'onChange' }); return <input {...form.register('name')} />; }" }],
|
|
17853
17981
|
focusPath: "profile-form.tsx",
|
|
17854
17982
|
expectedCount: 1,
|
|
17855
17983
|
public: true
|
|
17856
17984
|
}
|
|
17857
17985
|
]
|
|
17858
17986
|
};
|
|
17859
|
-
function
|
|
17987
|
+
function hasInitializationOrUnknownOptions(options) {
|
|
17860
17988
|
return options?.type === "ObjectExpression" && options.properties.some(
|
|
17861
|
-
(property) => property.type === "
|
|
17989
|
+
(property) => property.type === "SpreadElement" || property.computed || (property.key.type === "Identifier" && ["defaultValues", "values"].includes(property.key.name) || property.key.type === "Literal" && ["defaultValues", "values"].includes(String(property.key.value)))
|
|
17862
17990
|
);
|
|
17863
17991
|
}
|
|
17864
17992
|
var require_use_form_default_values_default = createRule({
|
|
@@ -17869,7 +17997,7 @@ var require_use_form_default_values_default = createRule({
|
|
|
17869
17997
|
docs: { description: REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION.summary },
|
|
17870
17998
|
schema: [],
|
|
17871
17999
|
messages: {
|
|
17872
|
-
requireUseFormDefaultValues: "
|
|
18000
|
+
requireUseFormDefaultValues: "Provide defaultValues or reactive values to useForm so controlled fields have an explicit initial shape."
|
|
17873
18001
|
}
|
|
17874
18002
|
},
|
|
17875
18003
|
defaultOptions: [],
|
|
@@ -17880,15 +18008,15 @@ var require_use_form_default_values_default = createRule({
|
|
|
17880
18008
|
if (node.source.value !== "react-hook-form") return;
|
|
17881
18009
|
for (const specifier of node.specifiers) {
|
|
17882
18010
|
if (specifier.type !== "ImportSpecifier" || (specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value) !== "useForm") continue;
|
|
17883
|
-
const variable =
|
|
18011
|
+
const variable = ASTUtils31.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
|
|
17884
18012
|
if (variable) importedHooks.add(variable);
|
|
17885
18013
|
}
|
|
17886
18014
|
},
|
|
17887
18015
|
CallExpression(node) {
|
|
17888
18016
|
if (node.callee.type !== "Identifier") return;
|
|
17889
|
-
const variable =
|
|
18017
|
+
const variable = ASTUtils31.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
|
|
17890
18018
|
const options = node.arguments[0];
|
|
17891
|
-
if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" ||
|
|
18019
|
+
if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" || hasInitializationOrUnknownOptions(options)) return;
|
|
17892
18020
|
context.report({ node, messageId: "requireUseFormDefaultValues" });
|
|
17893
18021
|
}
|
|
17894
18022
|
};
|
|
@@ -17901,10 +18029,10 @@ var ACTION_MODULE_RE = /(?:^|\/)app\/.*\/(?:actions|[^/]+-actions)\.[cm]?[jt]s$/
|
|
|
17901
18029
|
var REQUIRE_USE_SERVER_IN_ACTIONS_FILE_DOCUMENTATION = {
|
|
17902
18030
|
summary: "route action module missing the use server directive",
|
|
17903
18031
|
rationale: "An exported async function is not callable as a Server Action merely because its file is named actions.ts. Without the module directive, a client import can fail or pull server-only implementation details across the client boundary.",
|
|
17904
|
-
remediation: "
|
|
18032
|
+
remediation: "Use a leading module directive for an action-only module, or retain a function-level directive for an inline Server Action. Do not turn mixed non-action exports into a Server Action module.",
|
|
17905
18033
|
category: "correctness",
|
|
17906
18034
|
limitations: [
|
|
17907
|
-
"Only
|
|
18035
|
+
"Only direct named exports of async declarations or initialized functions in actions.ts or *-actions.ts below an app directory are checked. Detached/default exports and other naming schemes are not inferred; functions with their own directive are accepted."
|
|
17908
18036
|
],
|
|
17909
18037
|
examples: [
|
|
17910
18038
|
{
|
|
@@ -17929,9 +18057,10 @@ var REQUIRE_USE_SERVER_IN_ACTIONS_FILE_DOCUMENTATION = {
|
|
|
17929
18057
|
};
|
|
17930
18058
|
function isExportedAsyncFunction(node) {
|
|
17931
18059
|
const declaration = node.declaration;
|
|
17932
|
-
|
|
18060
|
+
const unmarked = (fn) => fn.async && !(fn.body?.type === "BlockStatement" && fn.body.body.some((statement) => statement.type === "ExpressionStatement" && statement.directive === "use server"));
|
|
18061
|
+
if (declaration?.type === "FunctionDeclaration") return unmarked(declaration);
|
|
17933
18062
|
return declaration?.type === "VariableDeclaration" && declaration.declarations.some(
|
|
17934
|
-
(item) => item.init?.type === "ArrowFunctionExpression" || item.init?.type === "FunctionExpression" ? item.init
|
|
18063
|
+
(item) => item.init?.type === "ArrowFunctionExpression" || item.init?.type === "FunctionExpression" ? unmarked(item.init) : false
|
|
17935
18064
|
);
|
|
17936
18065
|
}
|
|
17937
18066
|
var require_use_server_in_actions_file_default = createRule({
|
|
@@ -17964,7 +18093,7 @@ var require_use_server_in_actions_file_default = createRule({
|
|
|
17964
18093
|
// src/rules/require-zod-form-validation.ts
|
|
17965
18094
|
import {
|
|
17966
18095
|
AST_NODE_TYPES as AST_NODE_TYPES76,
|
|
17967
|
-
ASTUtils as
|
|
18096
|
+
ASTUtils as ASTUtils32
|
|
17968
18097
|
} from "@typescript-eslint/utils";
|
|
17969
18098
|
var REQUIRE_ZOD_FORM_VALIDATION_DOCUMENTATION = {
|
|
17970
18099
|
summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
|
|
@@ -18032,7 +18161,7 @@ var require_zod_form_validation_default = createRule({
|
|
|
18032
18161
|
return {};
|
|
18033
18162
|
}
|
|
18034
18163
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
18035
|
-
const resolvedBinding = (identifier) =>
|
|
18164
|
+
const resolvedBinding = (identifier) => ASTUtils32.findVariable(
|
|
18036
18165
|
context.sourceCode.getScope(identifier),
|
|
18037
18166
|
identifier.name
|
|
18038
18167
|
);
|
|
@@ -18329,7 +18458,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
18329
18458
|
});
|
|
18330
18459
|
|
|
18331
18460
|
// src/rules/stepdown.ts
|
|
18332
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as
|
|
18461
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils33 } from "@typescript-eslint/utils";
|
|
18333
18462
|
var STEPDOWN_DOCUMENTATION = {
|
|
18334
18463
|
summary: "Place a private helper below its sole direct same-scope caller.",
|
|
18335
18464
|
rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
|
|
@@ -18534,7 +18663,7 @@ function methodName(node) {
|
|
|
18534
18663
|
return !node.computed && node.key.type === AST_NODE_TYPES77.Identifier ? node.key.name : null;
|
|
18535
18664
|
}
|
|
18536
18665
|
function referencedMethod(context, node, classVariables) {
|
|
18537
|
-
const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ?
|
|
18666
|
+
const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils33.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
|
|
18538
18667
|
const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
|
|
18539
18668
|
if (node.object.type !== AST_NODE_TYPES77.ThisExpression && !isClassReference) return null;
|
|
18540
18669
|
if (node.property.type === AST_NODE_TYPES77.PrivateIdentifier) return `#${node.property.name}`;
|
|
@@ -18546,15 +18675,15 @@ function referencedPropertyName(node) {
|
|
|
18546
18675
|
if (!node.computed && node.property.type === AST_NODE_TYPES77.Identifier) return node.property.name;
|
|
18547
18676
|
return node.computed && node.property.type === AST_NODE_TYPES77.Literal && typeof node.property.value === "string" ? node.property.value : null;
|
|
18548
18677
|
}
|
|
18549
|
-
function
|
|
18678
|
+
function walk(node, visitorKeys, visit, nestedFunction = false) {
|
|
18550
18679
|
visit(node, nestedFunction);
|
|
18551
18680
|
const nested = nestedFunction || isFunction(node);
|
|
18552
18681
|
for (const key of visitorKeys[node.type] ?? []) {
|
|
18553
18682
|
const child = node[key];
|
|
18554
18683
|
if (Array.isArray(child)) {
|
|
18555
|
-
for (const item of child) if (typeof item === "object" && item !== null && "type" in item)
|
|
18684
|
+
for (const item of child) if (typeof item === "object" && item !== null && "type" in item) walk(item, visitorKeys, visit, nested);
|
|
18556
18685
|
} else if (typeof child === "object" && child !== null && "type" in child) {
|
|
18557
|
-
|
|
18686
|
+
walk(child, visitorKeys, visit, nested);
|
|
18558
18687
|
}
|
|
18559
18688
|
}
|
|
18560
18689
|
}
|
|
@@ -18587,11 +18716,11 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
18587
18716
|
const pinned = /* @__PURE__ */ new Set();
|
|
18588
18717
|
const classVariables = /* @__PURE__ */ new Set();
|
|
18589
18718
|
if (node.id !== null) {
|
|
18590
|
-
const internal =
|
|
18719
|
+
const internal = ASTUtils33.findVariable(context.sourceCode.getScope(node), node.id.name);
|
|
18591
18720
|
if (internal !== null) classVariables.add(internal);
|
|
18592
18721
|
}
|
|
18593
18722
|
if (node.type === AST_NODE_TYPES77.ClassExpression && node.parent.type === AST_NODE_TYPES77.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES77.Identifier) {
|
|
18594
|
-
const outer =
|
|
18723
|
+
const outer = ASTUtils33.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
|
|
18595
18724
|
if (outer !== null) classVariables.add(outer);
|
|
18596
18725
|
}
|
|
18597
18726
|
for (const method of methods) {
|
|
@@ -18602,7 +18731,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
18602
18731
|
const parameterDecoratorNodes = /* @__PURE__ */ new Set();
|
|
18603
18732
|
for (const parameter of method.value.params) {
|
|
18604
18733
|
for (const decorator of parameter.decorators) {
|
|
18605
|
-
|
|
18734
|
+
walk(decorator, context.sourceCode.visitorKeys, (current) => parameterDecoratorNodes.add(current));
|
|
18606
18735
|
}
|
|
18607
18736
|
}
|
|
18608
18737
|
const thisValue = (value) => {
|
|
@@ -18627,17 +18756,17 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
18627
18756
|
return;
|
|
18628
18757
|
}
|
|
18629
18758
|
if (binding.type !== AST_NODE_TYPES77.Identifier) return;
|
|
18630
|
-
const variable =
|
|
18759
|
+
const variable = ASTUtils33.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
18631
18760
|
if (variable !== null) {
|
|
18632
18761
|
methodClassVariables.add(variable);
|
|
18633
18762
|
methodAliases.add(variable);
|
|
18634
18763
|
}
|
|
18635
18764
|
};
|
|
18636
18765
|
for (const parameter of method.value.params) {
|
|
18637
|
-
|
|
18766
|
+
walk(parameter, context.sourceCode.visitorKeys, collectAlias);
|
|
18638
18767
|
}
|
|
18639
18768
|
for (const statement of method.value.body.body) {
|
|
18640
|
-
|
|
18769
|
+
walk(statement, context.sourceCode.visitorKeys, collectAlias);
|
|
18641
18770
|
}
|
|
18642
18771
|
const visitCall = (current, nestedFunction) => {
|
|
18643
18772
|
if (current.type === AST_NODE_TYPES77.VariableDeclarator && current.id.type === AST_NODE_TYPES77.ObjectPattern && thisValue(current.init)) {
|
|
@@ -18657,7 +18786,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
18657
18786
|
return;
|
|
18658
18787
|
}
|
|
18659
18788
|
if (!privateNames.has(target)) return;
|
|
18660
|
-
const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ?
|
|
18789
|
+
const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils33.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
|
|
18661
18790
|
if (objectVariable !== null && methodAliases.has(objectVariable)) {
|
|
18662
18791
|
pinned.add(target);
|
|
18663
18792
|
return;
|
|
@@ -18671,19 +18800,19 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
18671
18800
|
calls.set(caller, callees);
|
|
18672
18801
|
};
|
|
18673
18802
|
for (const decorator of method.decorators) {
|
|
18674
|
-
|
|
18803
|
+
walk(decorator, context.sourceCode.visitorKeys, visitCall, true);
|
|
18675
18804
|
}
|
|
18676
|
-
if (method.computed)
|
|
18805
|
+
if (method.computed) walk(method.key, context.sourceCode.visitorKeys, visitCall, true);
|
|
18677
18806
|
for (const parameter of method.value.params) {
|
|
18678
|
-
|
|
18807
|
+
walk(parameter, context.sourceCode.visitorKeys, visitCall);
|
|
18679
18808
|
}
|
|
18680
18809
|
for (const statement of method.value.body.body) {
|
|
18681
|
-
|
|
18810
|
+
walk(statement, context.sourceCode.visitorKeys, visitCall);
|
|
18682
18811
|
}
|
|
18683
18812
|
}
|
|
18684
18813
|
for (const member of node.body.body) {
|
|
18685
18814
|
if (member.type === AST_NODE_TYPES77.MethodDefinition || member.type === AST_NODE_TYPES77.TSAbstractMethodDefinition) continue;
|
|
18686
|
-
|
|
18815
|
+
walk(member, context.sourceCode.visitorKeys, (current) => {
|
|
18687
18816
|
if (current.type !== AST_NODE_TYPES77.MemberExpression) return;
|
|
18688
18817
|
const target = referencedMethod(context, current, classVariables);
|
|
18689
18818
|
const possibleTarget = target ?? referencedPropertyName(current);
|
|
@@ -18773,7 +18902,7 @@ var stepdown_default = createRule({
|
|
|
18773
18902
|
"Program:exit": (program) => {
|
|
18774
18903
|
moduleScope(context, program);
|
|
18775
18904
|
const computedReferenceNames = /* @__PURE__ */ new Set();
|
|
18776
|
-
|
|
18905
|
+
walk(program, context.sourceCode.visitorKeys, (node) => {
|
|
18777
18906
|
if (node.type === AST_NODE_TYPES77.MemberExpression && node.computed && node.property.type === AST_NODE_TYPES77.Literal && typeof node.property.value === "string") computedReferenceNames.add(node.property.value);
|
|
18778
18907
|
});
|
|
18779
18908
|
for (const node of classes) classScope(context, node, computedReferenceNames);
|
|
@@ -18856,14 +18985,14 @@ function staticMemberName7(node) {
|
|
|
18856
18985
|
if (node.computed && node.property.type === AST_NODE_TYPES78.Literal && typeof node.property.value === "string") return node.property.value;
|
|
18857
18986
|
return null;
|
|
18858
18987
|
}
|
|
18859
|
-
function
|
|
18860
|
-
if (node.type === AST_NODE_TYPES78.AwaitExpression) return
|
|
18861
|
-
if (node.type === AST_NODE_TYPES78.ChainExpression) return
|
|
18862
|
-
if (node.type === AST_NODE_TYPES78.TSAsExpression || node.type === AST_NODE_TYPES78.TSNonNullExpression || node.type === AST_NODE_TYPES78.TSTypeAssertion) return
|
|
18988
|
+
function unwrap7(node) {
|
|
18989
|
+
if (node.type === AST_NODE_TYPES78.AwaitExpression) return unwrap7(node.argument);
|
|
18990
|
+
if (node.type === AST_NODE_TYPES78.ChainExpression) return unwrap7(node.expression);
|
|
18991
|
+
if (node.type === AST_NODE_TYPES78.TSAsExpression || node.type === AST_NODE_TYPES78.TSNonNullExpression || node.type === AST_NODE_TYPES78.TSTypeAssertion) return unwrap7(node.expression);
|
|
18863
18992
|
return node;
|
|
18864
18993
|
}
|
|
18865
18994
|
function stringValue(node) {
|
|
18866
|
-
const current =
|
|
18995
|
+
const current = unwrap7(node);
|
|
18867
18996
|
if (current.type === AST_NODE_TYPES78.Literal && typeof current.value === "string") return current.value;
|
|
18868
18997
|
if (current.type === AST_NODE_TYPES78.TemplateLiteral && current.expressions.length === 0) return current.quasis[0]?.value.cooked ?? null;
|
|
18869
18998
|
return null;
|
|
@@ -18872,7 +19001,7 @@ function importSource(node) {
|
|
|
18872
19001
|
return typeof node.source.value === "string" ? node.source.value : null;
|
|
18873
19002
|
}
|
|
18874
19003
|
function requireSource(node) {
|
|
18875
|
-
const current =
|
|
19004
|
+
const current = unwrap7(node);
|
|
18876
19005
|
if (current.type !== AST_NODE_TYPES78.CallExpression || current.callee.type !== AST_NODE_TYPES78.Identifier || current.callee.name !== "require" || current.arguments.length !== 1 || current.arguments[0]?.type === AST_NODE_TYPES78.SpreadElement) return null;
|
|
18877
19006
|
return stringValue(current.arguments[0]);
|
|
18878
19007
|
}
|
|
@@ -18910,7 +19039,7 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
18910
19039
|
return /* @__PURE__ */ new Set();
|
|
18911
19040
|
};
|
|
18912
19041
|
const sourcePath = (node) => {
|
|
18913
|
-
const current =
|
|
19042
|
+
const current = unwrap7(node);
|
|
18914
19043
|
const value = stringValue(current);
|
|
18915
19044
|
if (value !== null) return sourceSuffixRe.test(value);
|
|
18916
19045
|
if (current.type === AST_NODE_TYPES78.Identifier) return visible("paths", current.name);
|
|
@@ -18925,37 +19054,37 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
18925
19054
|
return false;
|
|
18926
19055
|
};
|
|
18927
19056
|
const rawRead = (node) => {
|
|
18928
|
-
const current =
|
|
19057
|
+
const current = unwrap7(node);
|
|
18929
19058
|
if (current.type !== AST_NODE_TYPES78.CallExpression || current.arguments.length === 0) return false;
|
|
18930
|
-
const callee =
|
|
19059
|
+
const callee = unwrap7(current.callee);
|
|
18931
19060
|
if (callee.type === AST_NODE_TYPES78.Identifier) {
|
|
18932
19061
|
return visible("fsReaders", callee.name) && sourcePath(current.arguments[0]);
|
|
18933
19062
|
}
|
|
18934
19063
|
if (callee.type !== AST_NODE_TYPES78.MemberExpression) return false;
|
|
18935
19064
|
const name2 = staticMemberName7(callee);
|
|
18936
|
-
const object =
|
|
19065
|
+
const object = unwrap7(callee.object);
|
|
18937
19066
|
return name2 !== null && FS_READERS.has(name2) && object.type === AST_NODE_TYPES78.Identifier && visible("fsObjects", object.name) && sourcePath(current.arguments[0]);
|
|
18938
19067
|
};
|
|
18939
19068
|
const rawOrigins = (node) => {
|
|
18940
|
-
const current =
|
|
19069
|
+
const current = unwrap7(node);
|
|
18941
19070
|
if (current.type === AST_NODE_TYPES78.Identifier) return visibleRawOrigins(current.name);
|
|
18942
19071
|
if (rawRead(current)) return /* @__PURE__ */ new Set([`${current.range[0]}:${current.range[1]}`]);
|
|
18943
19072
|
if (current.type === AST_NODE_TYPES78.BinaryExpression && current.operator === "+") return /* @__PURE__ */ new Set([...rawOrigins(current.left), ...rawOrigins(current.right)]);
|
|
18944
19073
|
if (current.type === AST_NODE_TYPES78.MemberExpression && staticMemberName7(current) === "length") return rawOrigins(current.object);
|
|
18945
19074
|
if (current.type !== AST_NODE_TYPES78.CallExpression) return /* @__PURE__ */ new Set();
|
|
18946
|
-
const callee =
|
|
19075
|
+
const callee = unwrap7(current.callee);
|
|
18947
19076
|
if (callee.type !== AST_NODE_TYPES78.MemberExpression) return /* @__PURE__ */ new Set();
|
|
18948
19077
|
const name2 = staticMemberName7(callee);
|
|
18949
19078
|
return name2 !== null && TEXT_TRANSFORMS.has(name2) ? rawOrigins(callee.object) : /* @__PURE__ */ new Set();
|
|
18950
19079
|
};
|
|
18951
19080
|
const evidenceOrigins = (node) => {
|
|
18952
|
-
const current =
|
|
19081
|
+
const current = unwrap7(node);
|
|
18953
19082
|
const direct = rawOrigins(current);
|
|
18954
19083
|
if (direct.size > 0) return direct;
|
|
18955
19084
|
if (current.type === AST_NODE_TYPES78.BinaryExpression || current.type === AST_NODE_TYPES78.LogicalExpression) return /* @__PURE__ */ new Set([...evidenceOrigins(current.left), ...evidenceOrigins(current.right)]);
|
|
18956
19085
|
if (current.type === AST_NODE_TYPES78.UnaryExpression) return evidenceOrigins(current.argument);
|
|
18957
19086
|
if (current.type !== AST_NODE_TYPES78.CallExpression) return /* @__PURE__ */ new Set();
|
|
18958
|
-
const callee =
|
|
19087
|
+
const callee = unwrap7(current.callee);
|
|
18959
19088
|
if (callee.type !== AST_NODE_TYPES78.MemberExpression) return /* @__PURE__ */ new Set();
|
|
18960
19089
|
const name2 = staticMemberName7(callee);
|
|
18961
19090
|
if (name2 !== null && TEXT_PREDICATES.has(name2)) return rawOrigins(callee.object);
|
|
@@ -18963,15 +19092,15 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
18963
19092
|
return /* @__PURE__ */ new Set();
|
|
18964
19093
|
};
|
|
18965
19094
|
const rawAssertionOrigins = (node) => {
|
|
18966
|
-
const callee =
|
|
19095
|
+
const callee = unwrap7(node.callee);
|
|
18967
19096
|
if (callee.type === AST_NODE_TYPES78.Identifier && callee.name === "assert") {
|
|
18968
19097
|
return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
18969
19098
|
}
|
|
18970
19099
|
if (callee.type !== AST_NODE_TYPES78.MemberExpression) return /* @__PURE__ */ new Set();
|
|
18971
19100
|
const matcher = staticMemberName7(callee);
|
|
18972
19101
|
if (matcher === null) return /* @__PURE__ */ new Set();
|
|
18973
|
-
let receiver =
|
|
18974
|
-
while (receiver.type === AST_NODE_TYPES78.MemberExpression && EXPECT_MODIFIERS2.has(staticMemberName7(receiver) ?? "")) receiver =
|
|
19102
|
+
let receiver = unwrap7(callee.object);
|
|
19103
|
+
while (receiver.type === AST_NODE_TYPES78.MemberExpression && EXPECT_MODIFIERS2.has(staticMemberName7(receiver) ?? "")) receiver = unwrap7(receiver.object);
|
|
18975
19104
|
if (receiver.type === AST_NODE_TYPES78.CallExpression && receiver.callee.type === AST_NODE_TYPES78.Identifier && receiver.callee.name === "expect") {
|
|
18976
19105
|
if (!EXPECT_MATCHERS.has(matcher)) return /* @__PURE__ */ new Set();
|
|
18977
19106
|
return new Set([...receiver.arguments, ...node.arguments].flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
@@ -18980,7 +19109,7 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
18980
19109
|
return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
18981
19110
|
};
|
|
18982
19111
|
const rawRegexExtractionOrigins = (node) => {
|
|
18983
|
-
const callee =
|
|
19112
|
+
const callee = unwrap7(node.callee);
|
|
18984
19113
|
if (callee.type !== AST_NODE_TYPES78.MemberExpression || staticMemberName7(callee) !== "matchAll" || node.arguments.length !== 1) return /* @__PURE__ */ new Set();
|
|
18985
19114
|
const argument = node.arguments[0];
|
|
18986
19115
|
if (argument?.type !== AST_NODE_TYPES78.Literal || !(argument.value instanceof RegExp)) return /* @__PURE__ */ new Set();
|
|
@@ -19003,11 +19132,11 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
19003
19132
|
}
|
|
19004
19133
|
};
|
|
19005
19134
|
const sourceCollection = (node) => {
|
|
19006
|
-
const current =
|
|
19135
|
+
const current = unwrap7(node);
|
|
19007
19136
|
return current.type === AST_NODE_TYPES78.ArrayExpression && current.elements.length > 0 && current.elements.every((element) => element !== null && element.type !== AST_NODE_TYPES78.SpreadElement && sourcePath(element));
|
|
19008
19137
|
};
|
|
19009
19138
|
const declaredNames2 = (node) => {
|
|
19010
|
-
const current =
|
|
19139
|
+
const current = unwrap7(node);
|
|
19011
19140
|
if (current.type === AST_NODE_TYPES78.Identifier) return [current.name];
|
|
19012
19141
|
if (current.type === AST_NODE_TYPES78.AssignmentPattern) return declaredNames2(current.left);
|
|
19013
19142
|
if (current.type === AST_NODE_TYPES78.RestElement) return declaredNames2(current.argument);
|
|
@@ -19059,7 +19188,7 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
19059
19188
|
if (node.left.type === AST_NODE_TYPES78.Identifier) declare(node.left.name, { path: sourcePath(node.right), rawOrigins: rawOrigins(node.right) });
|
|
19060
19189
|
},
|
|
19061
19190
|
ForOfStatement(node) {
|
|
19062
|
-
const right =
|
|
19191
|
+
const right = unwrap7(node.right);
|
|
19063
19192
|
const collection = right.type === AST_NODE_TYPES78.Identifier && visible("collections", right.name);
|
|
19064
19193
|
const left = node.left.type === AST_NODE_TYPES78.VariableDeclaration ? node.left.declarations[0]?.id : node.left;
|
|
19065
19194
|
if (collection && left?.type === AST_NODE_TYPES78.Identifier) declare(left.name, { path: true });
|
|
@@ -19092,7 +19221,8 @@ var SOLE_EXPORT_MATCHES_FILENAME_DOCUMENTATION = {
|
|
|
19092
19221
|
category: "maintainability",
|
|
19093
19222
|
limitations: [
|
|
19094
19223
|
"Framework entrypoints, generic stems covered by no-generic-single-export-module, tests, generated files, anonymous defaults, CommonJS, and re-exports are excluded.",
|
|
19095
|
-
"The rule compares the primary filename stem and preserves conventional suffixes such as .server or .worker."
|
|
19224
|
+
"The rule compares the primary filename stem and preserves a single private underscore prefix and conventional suffixes such as .server or .worker.",
|
|
19225
|
+
"Exported destructuring patterns are excluded rather than undercounted as public exports."
|
|
19096
19226
|
],
|
|
19097
19227
|
examples: [
|
|
19098
19228
|
{ id: "matching-class", title: "Match a class and module", outcome: "no-match", files: [{ path: "src/artifact-store.ts", source: "export class ArtifactStore {}" }], focusPath: "src/artifact-store.ts", expectedCount: 0, public: true },
|
|
@@ -19148,7 +19278,7 @@ var sole_export_matches_filename_default = createRule({
|
|
|
19148
19278
|
create(context) {
|
|
19149
19279
|
const fileStem = stem3(context.filename);
|
|
19150
19280
|
const normalizedFilename = context.filename.replaceAll("\\", "/");
|
|
19151
|
-
if (EXCLUDED_STEMS.has(fileStem) || normalizedFilename.includes("/pages/") || context.filename.endsWith(".d.ts") || isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
|
|
19281
|
+
if (EXCLUDED_STEMS.has(fileStem) || /(?:^|\/)app\/(?:.*\/)?(?:global-)?error\.[jt]sx?$/u.test(normalizedFilename) || normalizedFilename.includes("/pages/") || context.filename.endsWith(".d.ts") || isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
|
|
19152
19282
|
return {
|
|
19153
19283
|
"Program:exit"(program) {
|
|
19154
19284
|
const exports = [];
|
|
@@ -19167,6 +19297,7 @@ var sole_export_matches_filename_default = createRule({
|
|
|
19167
19297
|
publicExports.add(declaration.id.name);
|
|
19168
19298
|
}
|
|
19169
19299
|
if (declaration?.type === AST_NODE_TYPES79.VariableDeclaration) {
|
|
19300
|
+
if (declaration.declarations.some((item) => item.id.type !== AST_NODE_TYPES79.Identifier)) return;
|
|
19170
19301
|
for (const item of declaration.declarations) {
|
|
19171
19302
|
if (item.id.type === AST_NODE_TYPES79.Identifier) publicExports.add(item.id.name);
|
|
19172
19303
|
}
|
|
@@ -19189,8 +19320,12 @@ var sole_export_matches_filename_default = createRule({
|
|
|
19189
19320
|
if (unique.size !== 1 || publicExports.size !== 1) return;
|
|
19190
19321
|
const only = [...unique.values()][0];
|
|
19191
19322
|
if (only === void 0) return;
|
|
19192
|
-
|
|
19193
|
-
if (
|
|
19323
|
+
if (only.name === "onRouterTransitionStart" && /(?:^|\/)instrumentation-client\.[jt]s$/u.test(normalizedFilename)) return;
|
|
19324
|
+
if (only.name === "collections" && /(?:^|\/)src\/content\.config\.(?:ts|js|mjs)$/u.test(normalizedFilename) && program.body.some((statement) => statement.type === AST_NODE_TYPES79.ImportDeclaration && statement.source.value === "astro:content")) return;
|
|
19325
|
+
const exportedStem = kebabCase(only.name);
|
|
19326
|
+
if (exportedStem === "") return;
|
|
19327
|
+
const expected = `${fileStem.startsWith("_") ? "_" : ""}${exportedStem}`;
|
|
19328
|
+
if (expected === fileStem.toLowerCase()) return;
|
|
19194
19329
|
context.report({ node: only.node, messageId: "matchSoleExport", data: { exported: only.name, expected } });
|
|
19195
19330
|
}
|
|
19196
19331
|
};
|
|
@@ -19238,7 +19373,7 @@ var iac_source_coupled_test_default = createSourceCoupledRule(
|
|
|
19238
19373
|
// src/rules/require-pascal-case-zod-schema-name.ts
|
|
19239
19374
|
import {
|
|
19240
19375
|
AST_NODE_TYPES as AST_NODE_TYPES80,
|
|
19241
|
-
ASTUtils as
|
|
19376
|
+
ASTUtils as ASTUtils34
|
|
19242
19377
|
} from "@typescript-eslint/utils";
|
|
19243
19378
|
var REQUIRE_PASCAL_CASE_ZOD_SCHEMA_NAME_DOCUMENTATION = {
|
|
19244
19379
|
summary: "Require confirmed module-level Zod schema contracts to use PascalCase with a `Schema` suffix.",
|
|
@@ -19439,7 +19574,7 @@ var require_pascal_case_zod_schema_name_default = createRule({
|
|
|
19439
19574
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
19440
19575
|
const schemaBindings = /* @__PURE__ */ new Set();
|
|
19441
19576
|
function resolvedBinding(identifier) {
|
|
19442
|
-
return
|
|
19577
|
+
return ASTUtils34.findVariable(
|
|
19443
19578
|
context.sourceCode.getScope(identifier),
|
|
19444
19579
|
identifier.name
|
|
19445
19580
|
);
|
|
@@ -19684,7 +19819,7 @@ var RULES = {
|
|
|
19684
19819
|
};
|
|
19685
19820
|
var meta = {
|
|
19686
19821
|
name: "@sarj/eslint-plugin",
|
|
19687
|
-
version: "15.17.
|
|
19822
|
+
version: "15.17.10"
|
|
19688
19823
|
};
|
|
19689
19824
|
var APPLICATION_ONLY_RULES = [];
|
|
19690
19825
|
var LIBRARY_IMPORT_POLICY = ["error", {
|