@sarj/eslint-plugin 15.17.7 → 15.17.9
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 +1733 -1598
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +584 -449
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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
|
);
|
|
@@ -4456,8 +4481,8 @@ function privateMemberFixes(context, services, owner, members, removePrivateKeyw
|
|
|
4456
4481
|
return;
|
|
4457
4482
|
}
|
|
4458
4483
|
if (node.type !== AST_NODE_TYPES16.MemberExpression) return;
|
|
4459
|
-
const
|
|
4460
|
-
if (
|
|
4484
|
+
const propertyName6 = 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;
|
|
4485
|
+
if (propertyName6 !== name) return;
|
|
4461
4486
|
const propertySymbol = symbolAt(services, checker, node.property);
|
|
4462
4487
|
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
4488
|
unsafe = true;
|
|
@@ -4620,7 +4645,7 @@ var interface_contract_members_private_default = createRule({
|
|
|
4620
4645
|
});
|
|
4621
4646
|
|
|
4622
4647
|
// src/rules/no-log-only-catch.ts
|
|
4623
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES18, ASTUtils as
|
|
4648
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES18, ASTUtils as ASTUtils6 } from "@typescript-eslint/utils";
|
|
4624
4649
|
|
|
4625
4650
|
// src/rules/_logging.ts
|
|
4626
4651
|
import "@typescript-eslint/utils";
|
|
@@ -4792,7 +4817,7 @@ function seededFallbackHandled(tryStatement, scope) {
|
|
|
4792
4817
|
if (previous.declarations.length !== 1 || declarator === void 0) return false;
|
|
4793
4818
|
if (declarator.id.type !== AST_NODE_TYPES18.Identifier) return false;
|
|
4794
4819
|
if (declarator.init == null || !isSeedValue(declarator.init)) return false;
|
|
4795
|
-
const variable =
|
|
4820
|
+
const variable = ASTUtils6.findVariable(scope, declarator.id.name);
|
|
4796
4821
|
if (variable === null) return false;
|
|
4797
4822
|
const [tryStart, tryEnd] = tryStatement.block.range;
|
|
4798
4823
|
let writtenInTry = false;
|
|
@@ -4897,7 +4922,7 @@ var no_log_only_catch_default = createRule({
|
|
|
4897
4922
|
});
|
|
4898
4923
|
|
|
4899
4924
|
// src/rules/no-bare-return-from-test-catch.ts
|
|
4900
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES19, ASTUtils as
|
|
4925
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES19, ASTUtils as ASTUtils7 } from "@typescript-eslint/utils";
|
|
4901
4926
|
var NO_BARE_RETURN_FROM_TEST_CATCH_DOCUMENTATION = {
|
|
4902
4927
|
summary: "Disallow a bare return from a test catch block when it skips a later assertion.",
|
|
4903
4928
|
rationale: "The caught failure turns into a passing test without executing the assertion that follows it.",
|
|
@@ -4922,7 +4947,7 @@ function staticMemberName2(node) {
|
|
|
4922
4947
|
return null;
|
|
4923
4948
|
}
|
|
4924
4949
|
function importedName3(identifier, context, modules) {
|
|
4925
|
-
const variable =
|
|
4950
|
+
const variable = ASTUtils7.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
4926
4951
|
if (variable === null || variable.defs.length === 0) return identifier.name;
|
|
4927
4952
|
for (const definition of variable.defs) {
|
|
4928
4953
|
if (definition.node.type !== AST_NODE_TYPES19.ImportSpecifier) continue;
|
|
@@ -5062,7 +5087,7 @@ var ADAPTER_BASENAME_RE = /(?:^|[-_.])adapters?(?:[-_.]|$)/i;
|
|
|
5062
5087
|
var API_BOUNDARY_IMPORT_RE = /(?:^|[/_.-])(?:api|client|sdk|contract|generated)(?:$|[/_.-])/i;
|
|
5063
5088
|
var SNAKE_CASE_RE = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/;
|
|
5064
5089
|
var LOWER_CAMEL_CASE_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
5065
|
-
function
|
|
5090
|
+
function propertyName(node) {
|
|
5066
5091
|
return node.type === AST_NODE_TYPES20.Identifier ? node.name : null;
|
|
5067
5092
|
}
|
|
5068
5093
|
function memberName3(node) {
|
|
@@ -5112,7 +5137,7 @@ var no_bespoke_api_case_conversion_default = createRule({
|
|
|
5112
5137
|
return {
|
|
5113
5138
|
Property(node) {
|
|
5114
5139
|
if (node.computed || node.method || node.shorthand) return;
|
|
5115
|
-
const key =
|
|
5140
|
+
const key = propertyName(node.key);
|
|
5116
5141
|
const value = memberName3(node.value);
|
|
5117
5142
|
if (key === null || value === null || !isDirectCaseTranslation(key, value)) return;
|
|
5118
5143
|
const wireName = SNAKE_CASE_RE.test(key) ? key : value;
|
|
@@ -5306,7 +5331,7 @@ var no_vague_suppression_description_default = createRule({
|
|
|
5306
5331
|
});
|
|
5307
5332
|
|
|
5308
5333
|
// src/rules/no-generic-single-export-module.ts
|
|
5309
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES22, ASTUtils as
|
|
5334
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES22, ASTUtils as ASTUtils8 } from "@typescript-eslint/utils";
|
|
5310
5335
|
var NO_GENERIC_SINGLE_EXPORT_MODULE_DOCUMENTATION = {
|
|
5311
5336
|
summary: "Disallow generic module stems when one runtime export already names the responsibility.",
|
|
5312
5337
|
rationale: "A generic filename hides the sole exported responsibility and makes navigation less descriptive.",
|
|
@@ -5431,8 +5456,8 @@ function typeOnlyBindings(program) {
|
|
|
5431
5456
|
}
|
|
5432
5457
|
return new Set([...names].filter((name) => !runtimeNames.has(name)));
|
|
5433
5458
|
}
|
|
5434
|
-
function
|
|
5435
|
-
const variable =
|
|
5459
|
+
function isGlobalIdentifier2(context, node) {
|
|
5460
|
+
const variable = ASTUtils8.findVariable(context.sourceCode.getScope(node), node.name);
|
|
5436
5461
|
return variable === null || variable.defs.length === 0;
|
|
5437
5462
|
}
|
|
5438
5463
|
function isConventionalFrameworkUtility(filename, exported) {
|
|
@@ -5463,11 +5488,11 @@ var no_generic_single_export_module_default = createRule({
|
|
|
5463
5488
|
return {
|
|
5464
5489
|
CallExpression(node) {
|
|
5465
5490
|
const first = node.arguments[0];
|
|
5466
|
-
if (first?.type === AST_NODE_TYPES22.Identifier && first.name === "exports" &&
|
|
5491
|
+
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
5492
|
},
|
|
5468
5493
|
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" &&
|
|
5494
|
+
if (node.object.type === AST_NODE_TYPES22.Identifier && node.object.name === "exports" && isGlobalIdentifier2(context, node.object)) hasCommonJsExport = true;
|
|
5495
|
+
if (node.object.type === AST_NODE_TYPES22.Identifier && node.object.name === "module" && isGlobalIdentifier2(context, node.object) && memberPropertyName(node) === "exports") hasCommonJsExport = true;
|
|
5471
5496
|
},
|
|
5472
5497
|
"Program:exit"(program) {
|
|
5473
5498
|
if (hasCommonJsExport) return;
|
|
@@ -5905,7 +5930,6 @@ var no_positional_tuple_return_default = createRule({
|
|
|
5905
5930
|
});
|
|
5906
5931
|
|
|
5907
5932
|
// src/rules/no-production-browser-source-maps.ts
|
|
5908
|
-
import "@typescript-eslint/utils";
|
|
5909
5933
|
var NEXT_CONFIG_RE2 = /(?:^|\/)next\.config\.[cm]?[jt]s$/;
|
|
5910
5934
|
var NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION = {
|
|
5911
5935
|
summary: "Next.js production browser source maps expose application source",
|
|
@@ -5913,7 +5937,7 @@ var NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION = {
|
|
|
5913
5937
|
remediation: "Leave productionBrowserSourceMaps disabled and upload private source maps directly to the error-monitoring service during the build.",
|
|
5914
5938
|
category: "security",
|
|
5915
5939
|
limitations: [
|
|
5916
|
-
"Only a literal true
|
|
5940
|
+
"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
5941
|
],
|
|
5918
5942
|
examples: [
|
|
5919
5943
|
{
|
|
@@ -5936,11 +5960,6 @@ var NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION = {
|
|
|
5936
5960
|
}
|
|
5937
5961
|
]
|
|
5938
5962
|
};
|
|
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
5963
|
var no_production_browser_source_maps_default = createRule({
|
|
5945
5964
|
name: "no-production-browser-source-maps",
|
|
5946
5965
|
documentation: NO_PRODUCTION_BROWSER_SOURCE_MAPS_DOCUMENTATION,
|
|
@@ -5956,8 +5975,9 @@ var no_production_browser_source_maps_default = createRule({
|
|
|
5956
5975
|
create(context) {
|
|
5957
5976
|
if (!NEXT_CONFIG_RE2.test(context.filename.replaceAll("\\", "/"))) return {};
|
|
5958
5977
|
return {
|
|
5959
|
-
|
|
5960
|
-
|
|
5978
|
+
"Program:exit"() {
|
|
5979
|
+
const node = exportedNextConfigProperty(context.sourceCode, ["productionBrowserSourceMaps"]);
|
|
5980
|
+
if (node !== null && node.value.type === "Literal" && node.value.value === true) {
|
|
5961
5981
|
context.report({ node, messageId: "noProductionBrowserSourceMaps" });
|
|
5962
5982
|
}
|
|
5963
5983
|
}
|
|
@@ -5966,13 +5986,13 @@ var no_production_browser_source_maps_default = createRule({
|
|
|
5966
5986
|
});
|
|
5967
5987
|
|
|
5968
5988
|
// src/rules/no-raw-env.ts
|
|
5969
|
-
import "@typescript-eslint/utils";
|
|
5989
|
+
import { ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
|
|
5970
5990
|
var NO_RAW_ENV_DOCUMENTATION = {
|
|
5971
5991
|
summary: "Disallow direct `process.env` and `import.meta.env` reads outside validated boundaries.",
|
|
5972
5992
|
rationale: "Raw environment reads are untyped and defer invalid configuration failures until use.",
|
|
5973
5993
|
remediation: "Validate environment values at startup and import the typed configuration object.",
|
|
5974
5994
|
category: "correctness",
|
|
5975
|
-
limitations: ["Host markers, assignment targets, tests, scripts, build config, and
|
|
5995
|
+
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
5996
|
examples: [
|
|
5977
5997
|
{ 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
5998
|
{ 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 +6000,6 @@ var NO_RAW_ENV_DOCUMENTATION = {
|
|
|
5980
6000
|
};
|
|
5981
6001
|
var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
|
|
5982
6002
|
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
6003
|
function isProcessEnv(node) {
|
|
5988
6004
|
return !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
|
|
5989
6005
|
}
|
|
@@ -6038,30 +6054,43 @@ var no_raw_env_default = createRule({
|
|
|
6038
6054
|
defaultOptions: [],
|
|
6039
6055
|
create(context) {
|
|
6040
6056
|
const filename = context.filename;
|
|
6041
|
-
if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/"))
|
|
6057
|
+
if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/"))) {
|
|
6042
6058
|
return {};
|
|
6043
6059
|
}
|
|
6060
|
+
const reads = [];
|
|
6061
|
+
const boundaryFile = ENV_BOUNDARY_FILE_RE.test(filename.replaceAll("\\", "/"));
|
|
6062
|
+
let hasValidationCall = false;
|
|
6044
6063
|
return {
|
|
6064
|
+
CallExpression(node) {
|
|
6065
|
+
if (!boundaryFile) return;
|
|
6066
|
+
const callee = node.callee;
|
|
6067
|
+
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;
|
|
6068
|
+
},
|
|
6045
6069
|
MemberExpression(node) {
|
|
6070
|
+
if (isProcessEnv(node) && node.object.type === "Identifier") {
|
|
6071
|
+
const binding = ASTUtils9.findVariable(context.sourceCode.getScope(node), node.object.name);
|
|
6072
|
+
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;
|
|
6073
|
+
}
|
|
6046
6074
|
if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isExemptVariableAccess(node) && !isWriteTarget(node) && !isWholeEnvSpread(node)) {
|
|
6047
|
-
|
|
6048
|
-
node,
|
|
6049
|
-
messageId: "noRawEnv"
|
|
6050
|
-
});
|
|
6075
|
+
reads.push(node);
|
|
6051
6076
|
}
|
|
6077
|
+
},
|
|
6078
|
+
"Program:exit"() {
|
|
6079
|
+
if (boundaryFile && hasValidationCall) return;
|
|
6080
|
+
for (const node of reads) context.report({ node, messageId: "noRawEnv" });
|
|
6052
6081
|
}
|
|
6053
6082
|
};
|
|
6054
6083
|
}
|
|
6055
6084
|
});
|
|
6056
6085
|
|
|
6057
6086
|
// src/rules/no-raw-fetch-outside-clients.ts
|
|
6058
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES24, ASTUtils as
|
|
6087
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES24, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
|
|
6059
6088
|
var NO_RAW_FETCH_OUTSIDE_CLIENTS_DOCUMENTATION = {
|
|
6060
6089
|
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
6090
|
rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
|
|
6062
6091
|
remediation: "Move the request into a client module and call that abstraction from application code.",
|
|
6063
6092
|
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."],
|
|
6093
|
+
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
6094
|
examples: [
|
|
6066
6095
|
{ 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
6096
|
{ 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 +6272,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6243
6272
|
internalApiPrefixes.push(`${options.basePath}/api`);
|
|
6244
6273
|
}
|
|
6245
6274
|
function resolvesToGlobal(identifier) {
|
|
6246
|
-
const variable =
|
|
6275
|
+
const variable = ASTUtils10.findVariable(
|
|
6247
6276
|
context.sourceCode.getScope(identifier),
|
|
6248
6277
|
identifier.name
|
|
6249
6278
|
);
|
|
@@ -6252,17 +6281,18 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6252
6281
|
function resolveNode2(node) {
|
|
6253
6282
|
if (node === void 0) return null;
|
|
6254
6283
|
if (node.type !== AST_NODE_TYPES24.Identifier) return node;
|
|
6255
|
-
const variable =
|
|
6284
|
+
const variable = ASTUtils10.findVariable(
|
|
6256
6285
|
context.sourceCode.getScope(node),
|
|
6257
6286
|
node.name
|
|
6258
6287
|
);
|
|
6259
6288
|
if (variable?.defs.length !== 1) return node;
|
|
6260
6289
|
const definition = variable.defs[0];
|
|
6261
|
-
return definition?.type === "Variable" && definition.node.init !== null ? definition.node.init : node;
|
|
6290
|
+
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
6291
|
}
|
|
6263
6292
|
function propertyValue(node, name) {
|
|
6264
6293
|
if (node?.type !== AST_NODE_TYPES24.ObjectExpression) return null;
|
|
6265
|
-
|
|
6294
|
+
if (node.properties.some((property) => property.type !== AST_NODE_TYPES24.Property || property.computed)) return null;
|
|
6295
|
+
for (const property of [...node.properties].reverse()) {
|
|
6266
6296
|
if (property.type !== AST_NODE_TYPES24.Property || property.computed) continue;
|
|
6267
6297
|
const key = property.key;
|
|
6268
6298
|
const keyName = key.type === AST_NODE_TYPES24.Identifier ? key.name : key.type === AST_NODE_TYPES24.Literal && typeof key.value === "string" ? key.value : null;
|
|
@@ -6330,7 +6360,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
6330
6360
|
});
|
|
6331
6361
|
|
|
6332
6362
|
// src/rules/no-restricted-library-load.ts
|
|
6333
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES25, ASTUtils as
|
|
6363
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES25, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
|
|
6334
6364
|
var NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION = {
|
|
6335
6365
|
summary: "Apply a configured library-replacement policy to literal dynamic imports, CommonJS loads, and TypeScript import-equals declarations.",
|
|
6336
6366
|
rationale: "Runtime module loads can bypass the replacement policy enforced for static imports.",
|
|
@@ -6403,7 +6433,7 @@ var no_restricted_library_load_default = createRule({
|
|
|
6403
6433
|
});
|
|
6404
6434
|
}
|
|
6405
6435
|
function isUnshadowedRequire(node) {
|
|
6406
|
-
const variable =
|
|
6436
|
+
const variable = ASTUtils11.findVariable(
|
|
6407
6437
|
context.sourceCode.getScope(node),
|
|
6408
6438
|
node.name
|
|
6409
6439
|
);
|
|
@@ -6435,7 +6465,7 @@ var no_restricted_library_load_default = createRule({
|
|
|
6435
6465
|
});
|
|
6436
6466
|
|
|
6437
6467
|
// src/rules/no-router-refresh-polling.ts
|
|
6438
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES26, ASTUtils as
|
|
6468
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES26, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
|
|
6439
6469
|
var NO_ROUTER_REFRESH_POLLING_DOCUMENTATION = {
|
|
6440
6470
|
summary: "Do not poll by calling a Next.js router's refresh method from a timer.",
|
|
6441
6471
|
rationale: "A route refresh refetches and rerenders the whole route on every tick instead of loading the named resource that changed.",
|
|
@@ -6443,8 +6473,8 @@ var NO_ROUTER_REFRESH_POLLING_DOCUMENTATION = {
|
|
|
6443
6473
|
category: "performance",
|
|
6444
6474
|
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
6475
|
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 }
|
|
6476
|
+
{ 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 },
|
|
6477
|
+
{ 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
6478
|
]
|
|
6449
6479
|
};
|
|
6450
6480
|
function importedName4(node) {
|
|
@@ -6454,6 +6484,7 @@ function enclosingIntervalCallback(sourceCode, node) {
|
|
|
6454
6484
|
const ancestors = sourceCode.getAncestors(node);
|
|
6455
6485
|
for (let index = ancestors.length - 1; index >= 0; index -= 1) {
|
|
6456
6486
|
const ancestor = ancestors[index];
|
|
6487
|
+
if (ancestor?.type === AST_NODE_TYPES26.FunctionDeclaration) return null;
|
|
6457
6488
|
if (ancestor?.type !== AST_NODE_TYPES26.ArrowFunctionExpression && ancestor?.type !== AST_NODE_TYPES26.FunctionExpression) continue;
|
|
6458
6489
|
const parent = ancestor.parent;
|
|
6459
6490
|
return parent.type === AST_NODE_TYPES26.CallExpression && parent.arguments[0] === ancestor && isIntervalCallee(sourceCode, parent.callee) ? ancestor : null;
|
|
@@ -6464,7 +6495,7 @@ function isIntervalCallee(sourceCode, node) {
|
|
|
6464
6495
|
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
6496
|
}
|
|
6466
6497
|
function isUnshadowedGlobal2(sourceCode, node) {
|
|
6467
|
-
const variable =
|
|
6498
|
+
const variable = ASTUtils12.findVariable(sourceCode.getScope(node), node.name);
|
|
6468
6499
|
return variable === null || variable.defs.length === 0;
|
|
6469
6500
|
}
|
|
6470
6501
|
var no_router_refresh_polling_default = createRule({
|
|
@@ -6487,25 +6518,25 @@ var no_router_refresh_polling_default = createRule({
|
|
|
6487
6518
|
if (node.source.value !== "next/navigation") return;
|
|
6488
6519
|
for (const specifier of node.specifiers) {
|
|
6489
6520
|
if (specifier.type === AST_NODE_TYPES26.ImportSpecifier && importedName4(specifier) === "useRouter") {
|
|
6490
|
-
const variable =
|
|
6521
|
+
const variable = ASTUtils12.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
|
|
6491
6522
|
if (variable !== null) routerHooks.add(variable);
|
|
6492
6523
|
}
|
|
6493
6524
|
}
|
|
6494
6525
|
},
|
|
6495
6526
|
VariableDeclarator(node) {
|
|
6496
6527
|
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 =
|
|
6528
|
+
const hook = ASTUtils12.findVariable(context.sourceCode.getScope(node.init.callee), node.init.callee.name);
|
|
6529
|
+
const router = ASTUtils12.findVariable(context.sourceCode.getScope(node.id), node.id.name);
|
|
6499
6530
|
if (hook !== null && router !== null && routerHooks.has(hook)) routers.add(router);
|
|
6500
6531
|
}
|
|
6501
6532
|
},
|
|
6502
6533
|
CallExpression(node) {
|
|
6503
6534
|
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 =
|
|
6535
|
+
const router = ASTUtils12.findVariable(
|
|
6505
6536
|
context.sourceCode.getScope(node.callee.object),
|
|
6506
6537
|
node.callee.object.name
|
|
6507
6538
|
);
|
|
6508
|
-
if (router === null || !routers.has(router)) return;
|
|
6539
|
+
if (router === null || !routers.has(router) || router.references.some((reference) => reference.isWrite() && reference.init !== true)) return;
|
|
6509
6540
|
const callback = enclosingIntervalCallback(context.sourceCode, node);
|
|
6510
6541
|
if (callback !== null && !reportedCallbacks.has(callback)) {
|
|
6511
6542
|
reportedCallbacks.add(callback);
|
|
@@ -7191,11 +7222,11 @@ function isAuthSecretName(identifier) {
|
|
|
7191
7222
|
var NO_SECRET_IN_LOG_DOCUMENTATION = {
|
|
7192
7223
|
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
7224
|
rationale: "Logs are widely retained and distributed, so credentials and raw bodies can become durable data leaks.",
|
|
7194
|
-
remediation: "Omit the value or
|
|
7225
|
+
remediation: "Omit the value, log allowlisted non-sensitive context, or use an approved redactor; truncation alone is not a safety guarantee.",
|
|
7195
7226
|
category: "security",
|
|
7196
|
-
limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers."],
|
|
7227
|
+
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
7228
|
examples: [
|
|
7198
|
-
{ id: "redacted-secret", title: "Log
|
|
7229
|
+
{ 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
7230
|
{ 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
7231
|
]
|
|
7201
7232
|
};
|
|
@@ -7247,13 +7278,14 @@ var LOG_INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
|
|
|
7247
7278
|
var REDACTION_RE = /prefix|suffix|redact|mask|hash|hint|_len|length/i;
|
|
7248
7279
|
var WHOLE_TOKEN_REDACTION_MARKERS = /* @__PURE__ */ new Set(["tag"]);
|
|
7249
7280
|
function isSecretKeyword(name) {
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7281
|
+
return !hasRedactionMarker(name) && isSecretName(name, LOG_INNOCUOUS_WORDS);
|
|
7282
|
+
}
|
|
7283
|
+
function hasRedactionMarker(name) {
|
|
7284
|
+
return REDACTION_RE.test(name) || tokenize(name).some((tok) => WHOLE_TOKEN_REDACTION_MARKERS.has(tok));
|
|
7285
|
+
}
|
|
7286
|
+
function valueName(node) {
|
|
7287
|
+
if (node.type === "Identifier") return node.name;
|
|
7288
|
+
return node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier" ? node.property.name : null;
|
|
7257
7289
|
}
|
|
7258
7290
|
function isRawSecretValue(prop) {
|
|
7259
7291
|
if (prop.shorthand) {
|
|
@@ -7337,8 +7369,8 @@ var no_secret_in_log_default = createRule({
|
|
|
7337
7369
|
}
|
|
7338
7370
|
],
|
|
7339
7371
|
messages: {
|
|
7340
|
-
noSecretInLog: "Secret `{{name}}` passed to a logging call
|
|
7341
|
-
noRawBodyInLog: "Raw `{{name}}` passed to a logging call. Request/response
|
|
7372
|
+
noSecretInLog: "Secret-like `{{name}}` passed to a logging call. Omit it or use an approved redactor; a prefix can expose an entire short secret.",
|
|
7373
|
+
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
7374
|
}
|
|
7343
7375
|
},
|
|
7344
7376
|
defaultOptions: [{}],
|
|
@@ -7346,7 +7378,7 @@ var no_secret_in_log_default = createRule({
|
|
|
7346
7378
|
const matcher = createLogMatcher(loggingOptions);
|
|
7347
7379
|
const blobArmApplies = !isTestFile(context.filename);
|
|
7348
7380
|
function reportSecretArgument(arg) {
|
|
7349
|
-
const name = arg
|
|
7381
|
+
const name = valueName(arg);
|
|
7350
7382
|
if (name === null || !isSecretKeyword(name)) {
|
|
7351
7383
|
return false;
|
|
7352
7384
|
}
|
|
@@ -7355,10 +7387,13 @@ var no_secret_in_log_default = createRule({
|
|
|
7355
7387
|
}
|
|
7356
7388
|
function reportSecretProperty(prop) {
|
|
7357
7389
|
const keyName = propertyKeyName2(prop);
|
|
7358
|
-
|
|
7390
|
+
const value = valueName(prop.value);
|
|
7391
|
+
if (value !== null && hasRedactionMarker(value)) return false;
|
|
7392
|
+
const name = value !== null && isSecretKeyword(value) ? value : keyName;
|
|
7393
|
+
if (name === null || !isSecretKeyword(name) || !isRawSecretValue(prop)) {
|
|
7359
7394
|
return false;
|
|
7360
7395
|
}
|
|
7361
|
-
context.report({ node: prop, messageId: "noSecretInLog", data: { name
|
|
7396
|
+
context.report({ node: prop, messageId: "noSecretInLog", data: { name } });
|
|
7362
7397
|
return true;
|
|
7363
7398
|
}
|
|
7364
7399
|
function reportRawBlob(node, value) {
|
|
@@ -8829,9 +8864,48 @@ var RESULT_FILLER = /* @__PURE__ */ new Set([
|
|
|
8829
8864
|
"the",
|
|
8830
8865
|
"value"
|
|
8831
8866
|
]);
|
|
8832
|
-
function
|
|
8833
|
-
|
|
8834
|
-
return
|
|
8867
|
+
function parameterTarget(parameter) {
|
|
8868
|
+
if (parameter.type === "TSParameterProperty") return parameterTarget(parameter.parameter);
|
|
8869
|
+
return parameter.type === "AssignmentPattern" ? parameter.left : parameter;
|
|
8870
|
+
}
|
|
8871
|
+
function documentedSignature(sourceCode, comment) {
|
|
8872
|
+
const before = sourceCode.getTokenBefore(comment);
|
|
8873
|
+
if (before?.loc.end.line === comment.loc.start.line) return null;
|
|
8874
|
+
const token = sourceCode.getTokenAfter(comment);
|
|
8875
|
+
if (token === null || token.loc.start.line !== comment.loc.end.line + 1) return null;
|
|
8876
|
+
let node = sourceCode.getNodeByRangeIndex(token.range[0]);
|
|
8877
|
+
while (node !== null && node.type !== "Program" && node.type !== "BlockStatement" && node.type !== "ClassBody") {
|
|
8878
|
+
const signature = functionSignature(node);
|
|
8879
|
+
if (signature !== null) {
|
|
8880
|
+
return signature.returnType !== void 0 && signature.params.every((parameter) => {
|
|
8881
|
+
const target = parameterTarget(parameter);
|
|
8882
|
+
return "typeAnnotation" in target && target.typeAnnotation != null;
|
|
8883
|
+
}) ? signature : null;
|
|
8884
|
+
}
|
|
8885
|
+
node = node.parent ?? null;
|
|
8886
|
+
}
|
|
8887
|
+
return null;
|
|
8888
|
+
}
|
|
8889
|
+
function functionSignature(node) {
|
|
8890
|
+
switch (node.type) {
|
|
8891
|
+
case "ExportNamedDeclaration":
|
|
8892
|
+
case "ExportDefaultDeclaration":
|
|
8893
|
+
return node.declaration === null ? null : functionSignature(node.declaration);
|
|
8894
|
+
case "FunctionDeclaration":
|
|
8895
|
+
case "FunctionExpression":
|
|
8896
|
+
case "ArrowFunctionExpression":
|
|
8897
|
+
case "TSDeclareFunction":
|
|
8898
|
+
case "TSMethodSignature":
|
|
8899
|
+
return node;
|
|
8900
|
+
case "MethodDefinition":
|
|
8901
|
+
return node.value;
|
|
8902
|
+
case "VariableDeclaration": {
|
|
8903
|
+
const init = node.declarations.length === 1 ? node.declarations[0]?.init : null;
|
|
8904
|
+
return init?.type === "ArrowFunctionExpression" || init?.type === "FunctionExpression" ? init : null;
|
|
8905
|
+
}
|
|
8906
|
+
default:
|
|
8907
|
+
return null;
|
|
8908
|
+
}
|
|
8835
8909
|
}
|
|
8836
8910
|
function typedTags(text) {
|
|
8837
8911
|
const tags = [];
|
|
@@ -8845,26 +8919,37 @@ function typedTags(text) {
|
|
|
8845
8919
|
}
|
|
8846
8920
|
}
|
|
8847
8921
|
return tags.map(({ kind, payload }) => {
|
|
8848
|
-
|
|
8922
|
+
const typeMatch = /^\{([^}\n]+)\}\s*/u.exec(payload);
|
|
8923
|
+
const explicitType = typeMatch?.[1]?.trim() ?? null;
|
|
8924
|
+
let rest = payload.slice(typeMatch?.[0].length ?? 0).trim();
|
|
8849
8925
|
if (!PARAM_TAGS2.has(kind)) {
|
|
8850
|
-
return { kind, name: null, description: rest.replace(/^-\s
|
|
8926
|
+
return { kind, name: null, description: rest.replace(/^-\s+/u, "").trim(), explicitType };
|
|
8851
8927
|
}
|
|
8852
|
-
const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s
|
|
8853
|
-
|
|
8854
|
-
|
|
8928
|
+
const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s+|\s+)?(.*)$/u.exec(rest);
|
|
8929
|
+
const rawName = match?.[1] ?? "";
|
|
8930
|
+
if (match === null || !/^[A-Za-z_$][\w$]*$/u.test(rawName)) return { kind, name: null, description: rest, explicitType };
|
|
8855
8931
|
rest = (match[2] ?? "").trim();
|
|
8856
|
-
return { kind, name: rawName, description: rest };
|
|
8932
|
+
return { kind, name: rawName, description: rest, explicitType };
|
|
8857
8933
|
});
|
|
8858
8934
|
}
|
|
8859
|
-
function isVacuousTag(tag) {
|
|
8935
|
+
function isVacuousTag(tag, signature, sourceCode) {
|
|
8936
|
+
let annotation = signature.returnType;
|
|
8937
|
+
if (PARAM_TAGS2.has(tag.kind)) {
|
|
8938
|
+
const parameter = signature.params.map(parameterTarget).find((node) => node.type === "Identifier" && node.name === tag.name);
|
|
8939
|
+
if (parameter?.type !== "Identifier") return false;
|
|
8940
|
+
annotation = parameter.typeAnnotation;
|
|
8941
|
+
}
|
|
8942
|
+
if (annotation === void 0) return false;
|
|
8943
|
+
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;
|
|
8944
|
+
if (/[^\p{L}\p{M}\s.,]/u.test(tag.description)) return false;
|
|
8860
8945
|
const description = words(tag.description).map(canonicalWord);
|
|
8861
|
-
if (description.length === 0) return
|
|
8946
|
+
if (description.length === 0) return tag.description.length === 0;
|
|
8862
8947
|
if (tag.name === null) return description.every((word) => RESULT_FILLER.has(word));
|
|
8863
8948
|
const nameWords2 = new Set(words(tag.name).map(canonicalWord));
|
|
8864
8949
|
return description.every((word) => PARAMETER_FILLER.has(word) || nameWords2.has(word));
|
|
8865
8950
|
}
|
|
8866
8951
|
function words(text) {
|
|
8867
|
-
return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[
|
|
8952
|
+
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
8953
|
}
|
|
8869
8954
|
function canonicalWord(word) {
|
|
8870
8955
|
if (["identifier", "identifiers", "ids"].includes(word)) return "id";
|
|
@@ -8876,7 +8961,7 @@ var NO_TYPED_DOC_SECTIONS_DOCUMENTATION = {
|
|
|
8876
8961
|
rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
|
|
8877
8962
|
remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
|
|
8878
8963
|
category: "maintainability",
|
|
8879
|
-
limitations: ["Description-free or name-restating
|
|
8964
|
+
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
8965
|
examples: [
|
|
8881
8966
|
{
|
|
8882
8967
|
id: "behavioral-documentation",
|
|
@@ -8914,7 +8999,8 @@ var no_typed_doc_sections_default = createRule({
|
|
|
8914
8999
|
return {
|
|
8915
9000
|
Program() {
|
|
8916
9001
|
for (const group of proseGroups(context.filename, context.sourceCode, true)) {
|
|
8917
|
-
|
|
9002
|
+
const signature = documentedSignature(context.sourceCode, group.comment);
|
|
9003
|
+
if (group.hasTypedTags && signature !== null && typedTags(group.text).some((tag) => isVacuousTag(tag, signature, context.sourceCode))) {
|
|
8918
9004
|
context.report({ node: group.comment, messageId: "typedSection" });
|
|
8919
9005
|
}
|
|
8920
9006
|
}
|
|
@@ -8924,7 +9010,7 @@ var no_typed_doc_sections_default = createRule({
|
|
|
8924
9010
|
});
|
|
8925
9011
|
|
|
8926
9012
|
// src/rules/no-trailing-value-narration.ts
|
|
8927
|
-
import "@typescript-eslint/utils";
|
|
9013
|
+
import { AST_TOKEN_TYPES as AST_TOKEN_TYPES2 } from "@typescript-eslint/utils";
|
|
8928
9014
|
var NO_TRAILING_VALUE_NARRATION_DOCUMENTATION = {
|
|
8929
9015
|
summary: "Flag a trailing comment that repeats the line's numeric value only to name its unit.",
|
|
8930
9016
|
rationale: "A repeated value can disagree with the expression after either the code or comment changes.",
|
|
@@ -8932,7 +9018,7 @@ var NO_TRAILING_VALUE_NARRATION_DOCUMENTATION = {
|
|
|
8932
9018
|
category: "maintainability",
|
|
8933
9019
|
autofix: "suggestion",
|
|
8934
9020
|
aliases: ["trailing-value-narration"],
|
|
8935
|
-
limitations: ["Only
|
|
9021
|
+
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
9022
|
examples: [
|
|
8937
9023
|
{
|
|
8938
9024
|
id: "explain-constraint",
|
|
@@ -8955,7 +9041,7 @@ var NO_TRAILING_VALUE_NARRATION_DOCUMENTATION = {
|
|
|
8955
9041
|
]
|
|
8956
9042
|
};
|
|
8957
9043
|
var NUMBER_RE = /(?<![\w.])(\d+(?:\.\d+)?)(?![\w.])/g;
|
|
8958
|
-
var WORD_RE3 = /[
|
|
9044
|
+
var WORD_RE3 = /[\p{L}\p{M}]+(?:'[\p{L}\p{M}]+)?|\d+(?:\.\d+)?/gu;
|
|
8959
9045
|
var UNIT_WORDS = /* @__PURE__ */ new Set([
|
|
8960
9046
|
"bytes",
|
|
8961
9047
|
"characters",
|
|
@@ -9017,9 +9103,9 @@ var STOPWORDS3 = /* @__PURE__ */ new Set([
|
|
|
9017
9103
|
]);
|
|
9018
9104
|
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
9105
|
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) {
|
|
9106
|
+
function narratesValue(body2, code, codeNumbers) {
|
|
9021
9107
|
if (body2.length === 0 || DIRECTIVE_RE5.test(body2) || hasExternalReference(body2)) return false;
|
|
9022
|
-
|
|
9108
|
+
if (/[^\p{L}\p{M}\p{N}\s.,:()_]/u.test(body2)) return false;
|
|
9023
9109
|
if (codeNumbers.size === 0) return false;
|
|
9024
9110
|
const words2 = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
|
|
9025
9111
|
if (words2.length === 0) return false;
|
|
@@ -9039,8 +9125,36 @@ function narratesValue(body2, code) {
|
|
|
9039
9125
|
function numbersIn(text) {
|
|
9040
9126
|
return new Set(text.match(NUMBER_RE) ?? []);
|
|
9041
9127
|
}
|
|
9042
|
-
function
|
|
9043
|
-
|
|
9128
|
+
function canonicalUnit(word) {
|
|
9129
|
+
switch (word) {
|
|
9130
|
+
case "milliseconds":
|
|
9131
|
+
return "ms";
|
|
9132
|
+
case "sec":
|
|
9133
|
+
case "secs":
|
|
9134
|
+
case "second":
|
|
9135
|
+
case "seconds":
|
|
9136
|
+
return "s";
|
|
9137
|
+
case "mins":
|
|
9138
|
+
case "minute":
|
|
9139
|
+
case "minutes":
|
|
9140
|
+
return "min";
|
|
9141
|
+
case "hr":
|
|
9142
|
+
case "hrs":
|
|
9143
|
+
case "hours":
|
|
9144
|
+
return "hour";
|
|
9145
|
+
case "days":
|
|
9146
|
+
return "day";
|
|
9147
|
+
case "bytes":
|
|
9148
|
+
return "byte";
|
|
9149
|
+
default:
|
|
9150
|
+
return word;
|
|
9151
|
+
}
|
|
9152
|
+
}
|
|
9153
|
+
function identifierUnit(node) {
|
|
9154
|
+
if (node.type === "MemberExpression" && !node.computed) return identifierUnit(node.property);
|
|
9155
|
+
if (node.type !== "Identifier") return null;
|
|
9156
|
+
const suffix = UNIT_NAME_SUFFIX_RE.exec(node.name)?.[0];
|
|
9157
|
+
return suffix === void 0 ? null : canonicalUnit(suffix.replace(/^_/u, "").toLowerCase());
|
|
9044
9158
|
}
|
|
9045
9159
|
var no_trailing_value_narration_default = createRule({
|
|
9046
9160
|
name: "no-trailing-value-narration",
|
|
@@ -9054,7 +9168,7 @@ var no_trailing_value_narration_default = createRule({
|
|
|
9054
9168
|
schema: [],
|
|
9055
9169
|
messages: {
|
|
9056
9170
|
deleteNarration: "Trailing comment restates the literal and the identifier already names its unit \u2014 delete the comment so it cannot drift.",
|
|
9057
|
-
narratesValue: "
|
|
9171
|
+
narratesValue: "Consider putting the unit in the name if this comment only narrates the value; keep conversion details and constraints.",
|
|
9058
9172
|
removeNarration: "Delete the redundant trailing narration."
|
|
9059
9173
|
}
|
|
9060
9174
|
},
|
|
@@ -9079,15 +9193,46 @@ var no_trailing_value_narration_default = createRule({
|
|
|
9079
9193
|
}
|
|
9080
9194
|
return false;
|
|
9081
9195
|
}
|
|
9196
|
+
function attachedValue(comment) {
|
|
9197
|
+
let token = sourceCode.getTokenBefore(comment);
|
|
9198
|
+
if (token?.value === ";" || token?.value === ",") token = sourceCode.getTokenBefore(token);
|
|
9199
|
+
if (token === null) return null;
|
|
9200
|
+
let node = sourceCode.getNodeByRangeIndex(token.range[0]);
|
|
9201
|
+
while (node !== null && node.type !== "Program") {
|
|
9202
|
+
if (node.range[1] <= comment.range[0]) {
|
|
9203
|
+
if (node.type === "VariableDeclarator" && node.id.type === "Identifier" && node.init !== null) {
|
|
9204
|
+
return { name: node.id, value: node.init };
|
|
9205
|
+
}
|
|
9206
|
+
if (node.type === "Property" && !node.computed && node.kind === "init" && node.parent.type === "ObjectExpression") {
|
|
9207
|
+
return { name: node.key, value: node.value };
|
|
9208
|
+
}
|
|
9209
|
+
if (node.type === "PropertyDefinition" && !node.computed && node.value !== null) {
|
|
9210
|
+
return { name: node.key, value: node.value };
|
|
9211
|
+
}
|
|
9212
|
+
if (node.type === "AssignmentExpression" && node.operator === "=") {
|
|
9213
|
+
if (node.left.type !== "Identifier" && (node.left.type !== "MemberExpression" || node.left.computed)) return null;
|
|
9214
|
+
return { name: node.left, value: node.right };
|
|
9215
|
+
}
|
|
9216
|
+
}
|
|
9217
|
+
node = node.parent ?? null;
|
|
9218
|
+
}
|
|
9219
|
+
return null;
|
|
9220
|
+
}
|
|
9082
9221
|
return {
|
|
9083
9222
|
Program() {
|
|
9084
9223
|
for (const comment of sourceCode.getAllComments()) {
|
|
9085
9224
|
if (!isTrailing(comment) || isInsideBrackets(comment)) continue;
|
|
9086
|
-
const
|
|
9087
|
-
|
|
9225
|
+
const attached = attachedValue(comment);
|
|
9226
|
+
if (attached === null) continue;
|
|
9227
|
+
const code = `${sourceCode.getText(attached.name)} ${sourceCode.getText(attached.value)}`;
|
|
9228
|
+
const codeNumbers = new Set(sourceCode.getTokens(attached.value).filter((token) => token.type === AST_TOKEN_TYPES2.Numeric).flatMap((token) => [...numbersIn(token.value)]));
|
|
9088
9229
|
const body2 = comment.value.replace(/^\*+/, "").replace(/\*+$/, "").trim();
|
|
9089
|
-
if (narratesValue(body2, code)) {
|
|
9090
|
-
const
|
|
9230
|
+
if (narratesValue(body2, code, codeNumbers)) {
|
|
9231
|
+
const namedUnit = identifierUnit(attached.name);
|
|
9232
|
+
if (namedUnit !== null && (attached.value.type !== "Literal" || typeof attached.value.value !== "number")) continue;
|
|
9233
|
+
const units = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase()).filter((word) => UNIT_WORDS.has(word));
|
|
9234
|
+
if (namedUnit !== null && !units.every((word) => canonicalUnit(word) === namedUnit)) continue;
|
|
9235
|
+
const canDelete = namedUnit !== null;
|
|
9091
9236
|
const removal = canDelete ? trailingCommentRemovalRange(sourceCode.text, comment) : null;
|
|
9092
9237
|
context.report({
|
|
9093
9238
|
node: comment,
|
|
@@ -9107,10 +9252,10 @@ var no_trailing_value_narration_default = createRule({
|
|
|
9107
9252
|
});
|
|
9108
9253
|
|
|
9109
9254
|
// src/rules/no-declaration-comment-wall.ts
|
|
9110
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES36, AST_TOKEN_TYPES as
|
|
9255
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES36, AST_TOKEN_TYPES as AST_TOKEN_TYPES4 } from "@typescript-eslint/utils";
|
|
9111
9256
|
|
|
9112
9257
|
// src/rules/_comment-wall.ts
|
|
9113
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES35, AST_TOKEN_TYPES as
|
|
9258
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES35, AST_TOKEN_TYPES as AST_TOKEN_TYPES3 } from "@typescript-eslint/utils";
|
|
9114
9259
|
var WALL_DEFAULTS = {
|
|
9115
9260
|
// Below three rows "a wall" is not a fair description of what the reader sees.
|
|
9116
9261
|
minCommentedMembers: 3,
|
|
@@ -9176,7 +9321,7 @@ function commentBody(comment) {
|
|
|
9176
9321
|
return comment.value.replace(/^\*+/, "").replace(/^[ \t]*\*[ \t]?/gm, "").trim();
|
|
9177
9322
|
}
|
|
9178
9323
|
function hasJsDocTag(comment) {
|
|
9179
|
-
return comment.type ===
|
|
9324
|
+
return comment.type === AST_TOKEN_TYPES3.Block && comment.value.startsWith("*") && /(?:^|\s)@[A-Za-z][\w-]*\b/u.test(commentBody(comment));
|
|
9180
9325
|
}
|
|
9181
9326
|
function carriesValue(body2) {
|
|
9182
9327
|
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 +9457,7 @@ var no_declaration_comment_wall_default = createRule({
|
|
|
9312
9457
|
const before = sourceCode.getTokenBefore(lead, { includeComments: false });
|
|
9313
9458
|
if (before === null || before.loc.end.line < lead.loc.start.line) {
|
|
9314
9459
|
const previousLine = endingOn.get(lead.loc.start.line - 1);
|
|
9315
|
-
if (lead.type ===
|
|
9460
|
+
if (lead.type === AST_TOKEN_TYPES4.Line && previousLine?.type === AST_TOKEN_TYPES4.Line && previousLine.loc.start.column === lead.loc.start.column) {
|
|
9316
9461
|
return void 0;
|
|
9317
9462
|
}
|
|
9318
9463
|
return lead;
|
|
@@ -9382,10 +9527,10 @@ var no_declaration_comment_wall_default = createRule({
|
|
|
9382
9527
|
import { AST_NODE_TYPES as AST_NODE_TYPES37 } from "@typescript-eslint/utils";
|
|
9383
9528
|
var NO_UNION_IN_COMMENT_DOCUMENTATION = {
|
|
9384
9529
|
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
|
|
9530
|
+
rationale: "A broad string annotation does not express a closed set documented beside it.",
|
|
9531
|
+
remediation: "If the list is exhaustive, express it as a string-literal union; keep examples and runtime constraints documented separately.",
|
|
9532
|
+
category: "maintainability",
|
|
9533
|
+
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
9534
|
examples: [
|
|
9390
9535
|
{
|
|
9391
9536
|
id: "literal-union",
|
|
@@ -9412,16 +9557,6 @@ var LITERAL = String.raw`(?:'[^'\n]*'|"[^"\n]*"|\`[^\`\n]*\`)`;
|
|
|
9412
9557
|
var LEAD_IN_RE2 = /^(?:one of|either|values?|allowed(?: values)?|options?|possible(?: values)?)\s*[:=-]?\s*/i;
|
|
9413
9558
|
var UNION_BODY_RE = new RegExp(String.raw`^${LITERAL}(?:\s*[|,/]\s*${LITERAL})+\.?$`);
|
|
9414
9559
|
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
9560
|
function isBareString(node) {
|
|
9426
9561
|
if (node === void 0) return false;
|
|
9427
9562
|
switch (node.type) {
|
|
@@ -9446,12 +9581,6 @@ function targetOf(node) {
|
|
|
9446
9581
|
if (name === null || !isBareString(node.typeAnnotation?.typeAnnotation)) return null;
|
|
9447
9582
|
return { node, name };
|
|
9448
9583
|
}
|
|
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
9584
|
case AST_NODE_TYPES37.VariableDeclarator: {
|
|
9456
9585
|
if (node.id.type !== AST_NODE_TYPES37.Identifier) return null;
|
|
9457
9586
|
if (!isBareString(node.id.typeAnnotation?.typeAnnotation)) return null;
|
|
@@ -9461,24 +9590,6 @@ function targetOf(node) {
|
|
|
9461
9590
|
return null;
|
|
9462
9591
|
}
|
|
9463
9592
|
}
|
|
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
9593
|
function nameOf(key) {
|
|
9483
9594
|
if (key.type === AST_NODE_TYPES37.Identifier) return key.name;
|
|
9484
9595
|
if (key.type === AST_NODE_TYPES37.Literal && typeof key.value === "string") return key.value;
|
|
@@ -9503,7 +9614,7 @@ var no_union_in_comment_default = createRule({
|
|
|
9503
9614
|
},
|
|
9504
9615
|
schema: [],
|
|
9505
9616
|
messages: {
|
|
9506
|
-
unionInComment: '
|
|
9617
|
+
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
9618
|
}
|
|
9508
9619
|
},
|
|
9509
9620
|
defaultOptions: [],
|
|
@@ -9528,7 +9639,11 @@ var no_union_in_comment_default = createRule({
|
|
|
9528
9639
|
}
|
|
9529
9640
|
for (let node = anchor; node != null && node.type !== AST_NODE_TYPES37.Program; node = node.parent) {
|
|
9530
9641
|
const target = targetOf(node);
|
|
9531
|
-
if (target !== null)
|
|
9642
|
+
if (target !== null) {
|
|
9643
|
+
const follows = target.node.range[1] <= comment.range[0] && target.node.loc.end.line === comment.loc.start.line;
|
|
9644
|
+
const precedes = comment.range[1] <= target.node.range[0] && comment.loc.end.line + 1 === target.node.loc.start.line;
|
|
9645
|
+
return follows || precedes ? target : null;
|
|
9646
|
+
}
|
|
9532
9647
|
}
|
|
9533
9648
|
return null;
|
|
9534
9649
|
}
|
|
@@ -9541,8 +9656,6 @@ var no_union_in_comment_default = createRule({
|
|
|
9541
9656
|
if (literals === null) continue;
|
|
9542
9657
|
const target = annotated(comment);
|
|
9543
9658
|
if (target === null) continue;
|
|
9544
|
-
const declaration = sourceCode.getText(target.node);
|
|
9545
|
-
if (literals.every((literal) => declaration.includes(literal))) continue;
|
|
9546
9659
|
context.report({
|
|
9547
9660
|
node: comment,
|
|
9548
9661
|
messageId: "unionInComment",
|
|
@@ -9555,13 +9668,14 @@ var no_union_in_comment_default = createRule({
|
|
|
9555
9668
|
});
|
|
9556
9669
|
|
|
9557
9670
|
// src/rules/no-type-member-comment-wall.ts
|
|
9558
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES38, AST_TOKEN_TYPES as
|
|
9671
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES38, AST_TOKEN_TYPES as AST_TOKEN_TYPES5 } from "@typescript-eslint/utils";
|
|
9672
|
+
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
9673
|
var NO_TYPE_MEMBER_COMMENT_WALL_DOCUMENTATION = {
|
|
9560
9674
|
summary: "Flag an object type whose member comments mostly re-spell the members' own names and types.",
|
|
9561
9675
|
rationale: "Repetitive member comments add scanning cost while hiding the comments that describe facts absent from the type.",
|
|
9562
9676
|
remediation: "Delete comments that restate member names or types and keep comments that add constraints or behavior.",
|
|
9563
9677
|
category: "maintainability",
|
|
9564
|
-
limitations: ["Only interface and type-literal bodies meeting the configured comment-count and restatement-ratio thresholds are reported."],
|
|
9678
|
+
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
9679
|
examples: [
|
|
9566
9680
|
{
|
|
9567
9681
|
id: "uncommented-members",
|
|
@@ -9596,7 +9710,7 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
9596
9710
|
},
|
|
9597
9711
|
schema: [WALL_SCHEMA],
|
|
9598
9712
|
messages: {
|
|
9599
|
-
commentWall: "{{restated}} of this type's {{commented}} member comments
|
|
9713
|
+
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
9714
|
}
|
|
9601
9715
|
},
|
|
9602
9716
|
defaultOptions: [WALL_DEFAULTS],
|
|
@@ -9620,7 +9734,7 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
9620
9734
|
const before = sourceCode.getTokenBefore(lead, { includeComments: false });
|
|
9621
9735
|
if (before === null || before.loc.end.line < lead.loc.start.line) {
|
|
9622
9736
|
const previousLine = endingOn.get(lead.loc.start.line - 1);
|
|
9623
|
-
if (lead.type ===
|
|
9737
|
+
if (lead.type === AST_TOKEN_TYPES5.Line && previousLine?.type === AST_TOKEN_TYPES5.Line && previousLine.loc.start.column === lead.loc.start.column) {
|
|
9624
9738
|
return void 0;
|
|
9625
9739
|
}
|
|
9626
9740
|
return lead;
|
|
@@ -9654,7 +9768,7 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
9654
9768
|
claimed.add(comment);
|
|
9655
9769
|
commented += 1;
|
|
9656
9770
|
const body2 = commentBody(comment);
|
|
9657
|
-
if (body2.length === 0 || hasJsDocTag(comment) || carriesValue(body2) || isTagsOnly(body2)) {
|
|
9771
|
+
if (body2.length === 0 || hasJsDocTag(comment) || carriesValue(body2) || isTagsOnly(body2) || BEHAVIORAL_RELATION_RE.test(body2)) {
|
|
9658
9772
|
continue;
|
|
9659
9773
|
}
|
|
9660
9774
|
if (novelWords(body2, knownTokens(sourceCode.getText(member))) <= options.maxNovelWords) {
|
|
@@ -9684,9 +9798,9 @@ import { AST_NODE_TYPES as AST_NODE_TYPES39 } from "@typescript-eslint/utils";
|
|
|
9684
9798
|
var NO_UNNECESSARY_USE_CLIENT_DOCUMENTATION = {
|
|
9685
9799
|
summary: "Flag `'use client'` files with no hooks or event handlers \u2014 they could be RSC.",
|
|
9686
9800
|
rationale: "An unnecessary client boundary sends the component and its transitive dependencies to the browser without using client-only behavior.",
|
|
9687
|
-
remediation: "
|
|
9801
|
+
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
9802
|
category: "performance",
|
|
9689
|
-
limitations: ["Client need is inferred from recognized hooks, handlers, browser globals, exports, classes, and known client-only imports."],
|
|
9803
|
+
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
9804
|
examples: [
|
|
9691
9805
|
{
|
|
9692
9806
|
id: "interactive-component",
|
|
@@ -9766,7 +9880,7 @@ var subtreeReadsImportedBinding = (node, imported) => {
|
|
|
9766
9880
|
return false;
|
|
9767
9881
|
};
|
|
9768
9882
|
var isUseClientDirective = (node) => {
|
|
9769
|
-
return node.type === AST_NODE_TYPES39.ExpressionStatement && node.
|
|
9883
|
+
return node.type === AST_NODE_TYPES39.ExpressionStatement && node.directive === "use client";
|
|
9770
9884
|
};
|
|
9771
9885
|
var isGlobalReference = (node, context) => {
|
|
9772
9886
|
if (!BROWSER_GLOBALS.has(node.name)) return false;
|
|
@@ -9832,7 +9946,7 @@ var no_unnecessary_use_client_default = createRule({
|
|
|
9832
9946
|
return {
|
|
9833
9947
|
Program(node) {
|
|
9834
9948
|
for (const stmt of node.body) {
|
|
9835
|
-
if (stmt.type !== AST_NODE_TYPES39.ExpressionStatement) break;
|
|
9949
|
+
if (stmt.type !== AST_NODE_TYPES39.ExpressionStatement || stmt.directive === void 0) break;
|
|
9836
9950
|
if (isUseClientDirective(stmt)) {
|
|
9837
9951
|
directiveNode = stmt;
|
|
9838
9952
|
break;
|
|
@@ -9853,6 +9967,7 @@ var no_unnecessary_use_client_default = createRule({
|
|
|
9853
9967
|
if (directiveNode === null) return;
|
|
9854
9968
|
if (typeof node.source.value !== "string") return;
|
|
9855
9969
|
const source = node.source.value;
|
|
9970
|
+
if (node.importKind !== "type" && node.specifiers.length === 0) hasClientIndicator = true;
|
|
9856
9971
|
if (CLIENT_ONLY_PACKAGES_REGEX.test(source) || CLIENT_REQUIRED_MODULES.has(source)) {
|
|
9857
9972
|
hasClientIndicator = true;
|
|
9858
9973
|
}
|
|
@@ -9922,7 +10037,7 @@ var no_unnecessary_use_client_default = createRule({
|
|
|
9922
10037
|
// src/rules/no-unsafe-mock-casting.ts
|
|
9923
10038
|
import {
|
|
9924
10039
|
AST_NODE_TYPES as AST_NODE_TYPES40,
|
|
9925
|
-
ASTUtils as
|
|
10040
|
+
ASTUtils as ASTUtils13
|
|
9926
10041
|
} from "@typescript-eslint/utils";
|
|
9927
10042
|
var MOCK_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
9928
10043
|
"Mock",
|
|
@@ -9988,7 +10103,7 @@ var no_unsafe_mock_casting_default = createRule({
|
|
|
9988
10103
|
const directBindings = /* @__PURE__ */ new Set();
|
|
9989
10104
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
9990
10105
|
function resolve2(identifier) {
|
|
9991
|
-
return
|
|
10106
|
+
return ASTUtils13.findVariable(
|
|
9992
10107
|
context.sourceCode.getScope(identifier),
|
|
9993
10108
|
identifier.name
|
|
9994
10109
|
);
|
|
@@ -10038,7 +10153,7 @@ var no_unsafe_mock_casting_default = createRule({
|
|
|
10038
10153
|
import {
|
|
10039
10154
|
ESLintUtils as ESLintUtils3,
|
|
10040
10155
|
AST_NODE_TYPES as AST_NODE_TYPES41,
|
|
10041
|
-
ASTUtils as
|
|
10156
|
+
ASTUtils as ASTUtils14
|
|
10042
10157
|
} from "@typescript-eslint/utils";
|
|
10043
10158
|
import * as ts2 from "typescript";
|
|
10044
10159
|
var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
|
|
@@ -10046,8 +10161,8 @@ var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
|
|
|
10046
10161
|
rationale: "Wrapping a TypeScript enum preserves its emitted runtime object and duplicates the schema's value definition across two constructs.",
|
|
10047
10162
|
remediation: "Pass string literals directly to `z.enum` and derive the TypeScript type with `z.infer`.",
|
|
10048
10163
|
category: "maintainability",
|
|
10049
|
-
autofix: "
|
|
10050
|
-
limitations: ["
|
|
10164
|
+
autofix: "none",
|
|
10165
|
+
limitations: ["Migration is manual: replacing an enum-like object with a value array changes the public schema.enum keys and can affect consumers."],
|
|
10051
10166
|
examples: [
|
|
10052
10167
|
{
|
|
10053
10168
|
id: "zod-literal-enum",
|
|
@@ -10065,8 +10180,7 @@ var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
|
|
|
10065
10180
|
files: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.nativeEnum({ Active: "active", Inactive: "inactive" });' }],
|
|
10066
10181
|
focusPath: "src/status.ts",
|
|
10067
10182
|
expectedCount: 1,
|
|
10068
|
-
public: true
|
|
10069
|
-
fixedFiles: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.enum(["active", "inactive"]);' }]
|
|
10183
|
+
public: true
|
|
10070
10184
|
}
|
|
10071
10185
|
]
|
|
10072
10186
|
};
|
|
@@ -10085,32 +10199,12 @@ function isIgnoredFile(filename, sourceText) {
|
|
|
10085
10199
|
function isZodModule2(source) {
|
|
10086
10200
|
return /(^|[/@-])zod([/-]|$)/.test(source);
|
|
10087
10201
|
}
|
|
10088
|
-
function
|
|
10202
|
+
function unwrap3(node) {
|
|
10089
10203
|
if (node.type === AST_NODE_TYPES41.TSAsExpression || node.type === AST_NODE_TYPES41.TSSatisfiesExpression) {
|
|
10090
|
-
return
|
|
10204
|
+
return unwrap3(node.expression);
|
|
10091
10205
|
}
|
|
10092
10206
|
return node;
|
|
10093
10207
|
}
|
|
10094
|
-
function stringValueTexts(node, sourceCode) {
|
|
10095
|
-
const texts = [];
|
|
10096
|
-
for (const prop of node.properties) {
|
|
10097
|
-
if (prop.type !== AST_NODE_TYPES41.Property) {
|
|
10098
|
-
return null;
|
|
10099
|
-
}
|
|
10100
|
-
if (prop.computed || prop.shorthand || prop.method || prop.kind !== "init") {
|
|
10101
|
-
return null;
|
|
10102
|
-
}
|
|
10103
|
-
const value = prop.value;
|
|
10104
|
-
if (value.type !== AST_NODE_TYPES41.Literal || typeof value.value !== "string") {
|
|
10105
|
-
return null;
|
|
10106
|
-
}
|
|
10107
|
-
const text = sourceCode.getText(value);
|
|
10108
|
-
if (!texts.includes(text)) {
|
|
10109
|
-
texts.push(text);
|
|
10110
|
-
}
|
|
10111
|
-
}
|
|
10112
|
-
return texts.length > 0 ? texts : null;
|
|
10113
|
-
}
|
|
10114
10208
|
function resolvesToLocalEnum(node, scope) {
|
|
10115
10209
|
let current = scope;
|
|
10116
10210
|
while (current !== null) {
|
|
@@ -10142,7 +10236,6 @@ var no_zod_native_enum_default = createRule({
|
|
|
10142
10236
|
documentation: NO_ZOD_NATIVE_ENUM_DOCUMENTATION,
|
|
10143
10237
|
meta: {
|
|
10144
10238
|
type: "suggestion",
|
|
10145
|
-
fixable: "code",
|
|
10146
10239
|
docs: {
|
|
10147
10240
|
description: 'Disallow `z.nativeEnum()` (and `z.enum()` over a TypeScript enum); use `z.enum(["a", "b"])` with a string-literal union instead.'
|
|
10148
10241
|
},
|
|
@@ -10170,7 +10263,7 @@ var no_zod_native_enum_default = createRule({
|
|
|
10170
10263
|
const zodImportedBindings = /* @__PURE__ */ new Map();
|
|
10171
10264
|
const zodNamespaceBindings = /* @__PURE__ */ new Set();
|
|
10172
10265
|
function resolvedBinding(identifier) {
|
|
10173
|
-
return
|
|
10266
|
+
return ASTUtils14.findVariable(
|
|
10174
10267
|
sourceCode.getScope(identifier),
|
|
10175
10268
|
identifier.name
|
|
10176
10269
|
);
|
|
@@ -10187,30 +10280,6 @@ var no_zod_native_enum_default = createRule({
|
|
|
10187
10280
|
}
|
|
10188
10281
|
return false;
|
|
10189
10282
|
}
|
|
10190
|
-
function buildFix(node) {
|
|
10191
|
-
const callee = node.callee;
|
|
10192
|
-
if (callee.type !== AST_NODE_TYPES41.MemberExpression || callee.property.type !== AST_NODE_TYPES41.Identifier) {
|
|
10193
|
-
return null;
|
|
10194
|
-
}
|
|
10195
|
-
const arg = node.arguments[0];
|
|
10196
|
-
if (arg === void 0 || node.arguments.length !== 1 || arg.type === AST_NODE_TYPES41.SpreadElement) {
|
|
10197
|
-
return null;
|
|
10198
|
-
}
|
|
10199
|
-
const inner = unwrap2(arg);
|
|
10200
|
-
if (inner.type !== AST_NODE_TYPES41.ObjectExpression) {
|
|
10201
|
-
return null;
|
|
10202
|
-
}
|
|
10203
|
-
const values = stringValueTexts(inner, sourceCode);
|
|
10204
|
-
if (values === null) {
|
|
10205
|
-
return null;
|
|
10206
|
-
}
|
|
10207
|
-
const property = callee.property;
|
|
10208
|
-
const replacementArg = `[${values.join(", ")}]`;
|
|
10209
|
-
return (fixer) => [
|
|
10210
|
-
fixer.replaceText(property, "enum"),
|
|
10211
|
-
fixer.replaceText(arg, replacementArg)
|
|
10212
|
-
];
|
|
10213
|
-
}
|
|
10214
10283
|
return {
|
|
10215
10284
|
ImportDeclaration(node) {
|
|
10216
10285
|
if (!isZodModule2(node.source.value)) {
|
|
@@ -10231,11 +10300,9 @@ var no_zod_native_enum_default = createRule({
|
|
|
10231
10300
|
},
|
|
10232
10301
|
CallExpression(node) {
|
|
10233
10302
|
if (isZodMemberCall(node, "nativeEnum")) {
|
|
10234
|
-
const fix = buildFix(node);
|
|
10235
10303
|
context.report({
|
|
10236
10304
|
node,
|
|
10237
|
-
messageId: "nativeEnum"
|
|
10238
|
-
...fix === null ? {} : { fix }
|
|
10305
|
+
messageId: "nativeEnum"
|
|
10239
10306
|
});
|
|
10240
10307
|
return;
|
|
10241
10308
|
}
|
|
@@ -10246,7 +10313,7 @@ var no_zod_native_enum_default = createRule({
|
|
|
10246
10313
|
if (argument === void 0 || argument.type === AST_NODE_TYPES41.SpreadElement) {
|
|
10247
10314
|
return;
|
|
10248
10315
|
}
|
|
10249
|
-
const arg =
|
|
10316
|
+
const arg = unwrap3(argument);
|
|
10250
10317
|
if (arg.type !== AST_NODE_TYPES41.Identifier) return;
|
|
10251
10318
|
const isEnum = resolvesToLocalEnum(arg, sourceCode.getScope(arg)) || services !== null && resolvesToImportedEnum(arg, services);
|
|
10252
10319
|
if (isEnum) {
|
|
@@ -10262,7 +10329,7 @@ var no_zod_native_enum_default = createRule({
|
|
|
10262
10329
|
});
|
|
10263
10330
|
|
|
10264
10331
|
// src/rules/test-loops-over-literal-cases.ts
|
|
10265
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES42, ASTUtils as
|
|
10332
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES42, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
|
|
10266
10333
|
var TEST_LOOPS_OVER_LITERAL_CASES_DOCUMENTATION = {
|
|
10267
10334
|
summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
|
|
10268
10335
|
rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
|
|
@@ -10425,7 +10492,7 @@ var test_loops_over_literal_cases_default = createRule({
|
|
|
10425
10492
|
return {};
|
|
10426
10493
|
}
|
|
10427
10494
|
const isFrameworkIdentifier = (identifier, modules) => {
|
|
10428
|
-
const variable =
|
|
10495
|
+
const variable = ASTUtils15.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
10429
10496
|
if (variable === null || variable.defs.length === 0) return true;
|
|
10430
10497
|
return variable.defs.some((definition) => {
|
|
10431
10498
|
let current = definition.node;
|
|
@@ -10559,17 +10626,18 @@ var test_phase_label_comment_default = createRule({
|
|
|
10559
10626
|
// src/rules/prefer-constant-time-secret-compare.ts
|
|
10560
10627
|
import { AST_NODE_TYPES as AST_NODE_TYPES44 } from "@typescript-eslint/utils";
|
|
10561
10628
|
var PREFER_CONSTANT_TIME_SECRET_COMPARE_DOCUMENTATION = {
|
|
10562
|
-
summary: "
|
|
10563
|
-
rationale: "Ordinary equality
|
|
10629
|
+
summary: "Prefer a supported constant-time comparison primitive for secret-like values.",
|
|
10630
|
+
rationale: "Ordinary equality offers no constant-time guarantee for comparing secrets.",
|
|
10564
10631
|
remediation: "Compare equal-length cryptographic digests with a constant-time comparison primitive.",
|
|
10565
10632
|
category: "security",
|
|
10566
|
-
limitations: ["
|
|
10633
|
+
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."],
|
|
10567
10634
|
examples: [
|
|
10568
|
-
{ id: "constant-time-compare", title: "Use a constant-time comparison", outcome: "no-match", files: [{ path: "src/auth.ts", source: "if (await constantTimeEqual(
|
|
10569
|
-
{ id: "secret-equality", title: "Do not compare
|
|
10635
|
+
{ 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 },
|
|
10636
|
+
{ 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 }
|
|
10570
10637
|
]
|
|
10571
10638
|
};
|
|
10572
10639
|
var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
|
|
10640
|
+
var AUTH_TOKEN_QUALIFIERS = /* @__PURE__ */ new Set(["access", "refresh", "session", "admin", "csrf", "xsrf", "auth", "authentication", "signing", "api"]);
|
|
10573
10641
|
var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
|
|
10574
10642
|
var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
|
|
10575
10643
|
var SENTINEL_PREFIX_RE = /^(skip|sentinel|empty|none|missing|unset|placeholder|dummy|fake|example|noop)[A-Z]/;
|
|
@@ -10607,7 +10675,9 @@ function isSecretOperand(node) {
|
|
|
10607
10675
|
return node.expressions.some((expression) => isSecretOperand(expression));
|
|
10608
10676
|
}
|
|
10609
10677
|
const name = operandName(node);
|
|
10610
|
-
|
|
10678
|
+
if (name === null || !isAuthSecretName(name)) return false;
|
|
10679
|
+
const words2 = tokenize(name);
|
|
10680
|
+
return !words2.includes("token") || words2.some((word) => AUTH_TOKEN_QUALIFIERS.has(word) || word !== "token" && SECRET_WORDS.has(word));
|
|
10611
10681
|
}
|
|
10612
10682
|
function secretNameOf(node) {
|
|
10613
10683
|
if (node.type === AST_NODE_TYPES44.TemplateLiteral) {
|
|
@@ -10627,11 +10697,11 @@ var prefer_constant_time_secret_compare_default = createRule({
|
|
|
10627
10697
|
meta: {
|
|
10628
10698
|
type: "problem",
|
|
10629
10699
|
docs: {
|
|
10630
|
-
description: "
|
|
10700
|
+
description: "Prefer a supported constant-time comparison primitive for secret-like values."
|
|
10631
10701
|
},
|
|
10632
10702
|
schema: [],
|
|
10633
10703
|
messages: {
|
|
10634
|
-
preferConstantTimeSecretCompare: "`{{operator}}` on secret `{{name}}`
|
|
10704
|
+
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."
|
|
10635
10705
|
}
|
|
10636
10706
|
},
|
|
10637
10707
|
defaultOptions: [],
|
|
@@ -10917,13 +10987,14 @@ var prefer_discriminated_union_default = createRule({
|
|
|
10917
10987
|
});
|
|
10918
10988
|
|
|
10919
10989
|
// src/rules/prefer-input-group-search.ts
|
|
10920
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES47 } from "@typescript-eslint/utils";
|
|
10990
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES47, ASTUtils as ASTUtils16 } from "@typescript-eslint/utils";
|
|
10921
10991
|
var PREFER_INPUT_GROUP_SEARCH_DOCUMENTATION = {
|
|
10922
10992
|
summary: "Require search icons and shared Input controls in the same visual wrapper to use InputGroup.",
|
|
10923
10993
|
rationale: "The shared compound control provides consistent spacing, focus behavior, and accessible composition.",
|
|
10924
10994
|
remediation: "Compose the search icon and field with InputGroup, InputGroupAddon, and InputGroupInput.",
|
|
10925
10995
|
category: "style",
|
|
10926
10996
|
limitations: [
|
|
10997
|
+
"Opposite branches of the same conditional expression and icons with explicit interaction handlers are excluded; arbitrary component behavior is not inferred.",
|
|
10927
10998
|
"Only Search and Input bindings imported from the recognized shared modules are paired.",
|
|
10928
10999
|
"The file must import InputGroup, proving that the repository has adopted that optional primitive."
|
|
10929
11000
|
],
|
|
@@ -10975,10 +11046,26 @@ function nearestEligibleCommonAncestor(search, input, inputGroupNames) {
|
|
|
10975
11046
|
return null;
|
|
10976
11047
|
}
|
|
10977
11048
|
function isActionIcon(search, wrapper) {
|
|
11049
|
+
if (hasInteraction(search.node)) return true;
|
|
10978
11050
|
return jsxAncestors(search).some((ancestor) => {
|
|
10979
11051
|
if (ancestor === wrapper) return false;
|
|
10980
11052
|
const name = elementName(ancestor.openingElement);
|
|
10981
|
-
return name === "a" || name === "button";
|
|
11053
|
+
return name === "a" || name === "button" || hasInteraction(ancestor.openingElement);
|
|
11054
|
+
});
|
|
11055
|
+
}
|
|
11056
|
+
function hasInteraction(node) {
|
|
11057
|
+
return node.attributes.some(
|
|
11058
|
+
(attribute) => attribute.type === AST_NODE_TYPES47.JSXAttribute && attribute.name.type === AST_NODE_TYPES47.JSXIdentifier && /^(?:on[A-Z]|href$)/u.test(attribute.name.name)
|
|
11059
|
+
);
|
|
11060
|
+
}
|
|
11061
|
+
function mutuallyExclusive(left, right) {
|
|
11062
|
+
return left.ancestors.some((ancestor, index) => {
|
|
11063
|
+
if (ancestor.type !== AST_NODE_TYPES47.ConditionalExpression) return false;
|
|
11064
|
+
const otherIndex = right.ancestors.indexOf(ancestor);
|
|
11065
|
+
if (otherIndex < 0) return false;
|
|
11066
|
+
const leftBranch = left.ancestors[index + 1];
|
|
11067
|
+
const rightBranch = right.ancestors[otherIndex + 1];
|
|
11068
|
+
return leftBranch === ancestor.consequent && rightBranch === ancestor.alternate || leftBranch === ancestor.alternate && rightBranch === ancestor.consequent;
|
|
10982
11069
|
});
|
|
10983
11070
|
}
|
|
10984
11071
|
var prefer_input_group_search_default = createRule({
|
|
@@ -11003,6 +11090,7 @@ var prefer_input_group_search_default = createRule({
|
|
|
11003
11090
|
const searches = [];
|
|
11004
11091
|
return {
|
|
11005
11092
|
ImportDeclaration(node) {
|
|
11093
|
+
if (node.importKind === "type") return;
|
|
11006
11094
|
const source = String(node.source.value);
|
|
11007
11095
|
if (source === "lucide-react") {
|
|
11008
11096
|
for (const exported of SEARCH_EXPORTS) {
|
|
@@ -11023,6 +11111,8 @@ var prefer_input_group_search_default = createRule({
|
|
|
11023
11111
|
JSXOpeningElement(node) {
|
|
11024
11112
|
const name = elementName(node);
|
|
11025
11113
|
if (name === null) return;
|
|
11114
|
+
const binding = ASTUtils16.findVariable(context.sourceCode.getScope(node), name);
|
|
11115
|
+
if (binding?.defs.length !== 1 || binding.defs[0]?.node.type !== AST_NODE_TYPES47.ImportSpecifier || binding.defs[0].node.importKind === "type") return;
|
|
11026
11116
|
const occurrence = {
|
|
11027
11117
|
ancestors: context.sourceCode.getAncestors(node),
|
|
11028
11118
|
node
|
|
@@ -11036,6 +11126,7 @@ var prefer_input_group_search_default = createRule({
|
|
|
11036
11126
|
for (const search of searches) {
|
|
11037
11127
|
if (isWithinInputGroup(search, inputGroupNames)) continue;
|
|
11038
11128
|
for (const input of inputs) {
|
|
11129
|
+
if (mutuallyExclusive(search, input)) continue;
|
|
11039
11130
|
if (isWithinInputGroup(input, inputGroupNames)) continue;
|
|
11040
11131
|
const wrapper = nearestEligibleCommonAncestor(
|
|
11041
11132
|
search,
|
|
@@ -11060,7 +11151,7 @@ var prefer_input_group_search_default = createRule({
|
|
|
11060
11151
|
|
|
11061
11152
|
// src/rules/prefer-millisecond-control-duration-schema.ts
|
|
11062
11153
|
import {
|
|
11063
|
-
ASTUtils as
|
|
11154
|
+
ASTUtils as ASTUtils17,
|
|
11064
11155
|
AST_NODE_TYPES as AST_NODE_TYPES48
|
|
11065
11156
|
} from "@typescript-eslint/utils";
|
|
11066
11157
|
var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
|
|
@@ -11129,7 +11220,7 @@ var prefer_millisecond_control_duration_schema_default = createRule({
|
|
|
11129
11220
|
const zodNamespaces = /* @__PURE__ */ new Set();
|
|
11130
11221
|
const objectFactories = /* @__PURE__ */ new Set();
|
|
11131
11222
|
function binding(identifier) {
|
|
11132
|
-
return
|
|
11223
|
+
return ASTUtils17.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
11133
11224
|
}
|
|
11134
11225
|
function record(target, identifier) {
|
|
11135
11226
|
const variable = binding(identifier);
|
|
@@ -11176,7 +11267,7 @@ var prefer_millisecond_control_duration_schema_default = createRule({
|
|
|
11176
11267
|
});
|
|
11177
11268
|
|
|
11178
11269
|
// src/rules/prefer-immutable-module-constant.ts
|
|
11179
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES49, ASTUtils as
|
|
11270
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES49, ASTUtils as ASTUtils18 } from "@typescript-eslint/utils";
|
|
11180
11271
|
var PREFER_IMMUTABLE_MODULE_CONSTANT_DOCUMENTATION = {
|
|
11181
11272
|
summary: "Require module-level constant collections to expose readonly state.",
|
|
11182
11273
|
rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
|
|
@@ -11329,7 +11420,7 @@ var prefer_immutable_module_constant_default = createRule({
|
|
|
11329
11420
|
create(context) {
|
|
11330
11421
|
const sourceCode = context.sourceCode;
|
|
11331
11422
|
const isUnshadowedGlobal3 = (identifier) => {
|
|
11332
|
-
const variable =
|
|
11423
|
+
const variable = ASTUtils18.findVariable(sourceCode.getScope(identifier), identifier.name);
|
|
11333
11424
|
return variable === null || variable.defs.length === 0;
|
|
11334
11425
|
};
|
|
11335
11426
|
if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
|
|
@@ -11429,6 +11520,7 @@ var PREFER_SHADCN_PRIMITIVES_DOCUMENTATION = {
|
|
|
11429
11520
|
remediation: "Replace the raw visible control with the corresponding shared shadcn component.",
|
|
11430
11521
|
category: "style",
|
|
11431
11522
|
limitations: [
|
|
11523
|
+
"Native multiple selects and controls with possibly enabled hidden attributes are excluded. Unknown JSX spreads can hide controls; visual equivalence is not inferred.",
|
|
11432
11524
|
"Hidden and file inputs, unassociated labels, and non-control semantic elements are excluded.",
|
|
11433
11525
|
"Tests and the shared components/ui primitive implementation tree are excluded.",
|
|
11434
11526
|
"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."
|
|
@@ -11716,6 +11808,7 @@ function isStaticallyAssociatedLabel(node) {
|
|
|
11716
11808
|
return node.parent.type === AST_NODE_TYPES50.JSXElement && containsLabelableElement(node.parent);
|
|
11717
11809
|
}
|
|
11718
11810
|
function replacementFor(node, element) {
|
|
11811
|
+
if (element === "select" && mayHaveBooleanAttribute(node, "multiple")) return null;
|
|
11719
11812
|
if (element !== "input") return RAW_PRIMITIVES[element];
|
|
11720
11813
|
const typeAttribute = effectiveAttribute(node, "type");
|
|
11721
11814
|
if (typeAttribute.kind === "unknown") return null;
|
|
@@ -11730,6 +11823,14 @@ function replacementFor(node, element) {
|
|
|
11730
11823
|
if (AMBIGUOUS_INPUT_TYPES.has(inputType)) return null;
|
|
11731
11824
|
return RAW_PRIMITIVES.input;
|
|
11732
11825
|
}
|
|
11826
|
+
function mayHaveBooleanAttribute(node, name) {
|
|
11827
|
+
for (const attribute of node.attributes.toReversed()) {
|
|
11828
|
+
if (attribute.type === AST_NODE_TYPES50.JSXSpreadAttribute) return true;
|
|
11829
|
+
if (attribute.name.type !== AST_NODE_TYPES50.JSXIdentifier || attribute.name.name !== name) continue;
|
|
11830
|
+
return !(attribute.value?.type === AST_NODE_TYPES50.JSXExpressionContainer && attribute.value.expression.type === AST_NODE_TYPES50.Literal && attribute.value.expression.value === false);
|
|
11831
|
+
}
|
|
11832
|
+
return false;
|
|
11833
|
+
}
|
|
11733
11834
|
var prefer_shadcn_primitives_default = createRule({
|
|
11734
11835
|
name: "prefer-shadcn-primitives",
|
|
11735
11836
|
documentation: PREFER_SHADCN_PRIMITIVES_DOCUMENTATION,
|
|
@@ -11775,6 +11876,9 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
11775
11876
|
JSXOpeningElement(node) {
|
|
11776
11877
|
const element = rawElementName(node);
|
|
11777
11878
|
if (element === null) return;
|
|
11879
|
+
if (mayHaveBooleanAttribute(node, "hidden") || context.sourceCode.getAncestors(node).some(
|
|
11880
|
+
(ancestor) => ancestor.type === AST_NODE_TYPES50.JSXElement && mayHaveBooleanAttribute(ancestor.openingElement, "hidden")
|
|
11881
|
+
)) return;
|
|
11778
11882
|
if (element === "label" && !isStaticallyAssociatedLabel(node)) return;
|
|
11779
11883
|
const replacement = replacementFor(node, element);
|
|
11780
11884
|
if (replacement === null) return;
|
|
@@ -11860,9 +11964,9 @@ function isIgnoredFile2(filename, sourceText) {
|
|
|
11860
11964
|
function isLocalFixtureFile(filename) {
|
|
11861
11965
|
return isTestFile(filename) || isStoryFile(filename);
|
|
11862
11966
|
}
|
|
11863
|
-
function
|
|
11967
|
+
function unwrap4(node) {
|
|
11864
11968
|
if (node.type === AST_NODE_TYPES51.TSAsExpression || node.type === AST_NODE_TYPES51.TSSatisfiesExpression || node.type === AST_NODE_TYPES51.TSNonNullExpression) {
|
|
11865
|
-
return
|
|
11969
|
+
return unwrap4(node.expression);
|
|
11866
11970
|
}
|
|
11867
11971
|
return node;
|
|
11868
11972
|
}
|
|
@@ -11874,7 +11978,7 @@ function isLiteralOnly(node, depth) {
|
|
|
11874
11978
|
if (depth > MAX_LITERAL_DEPTH) {
|
|
11875
11979
|
return false;
|
|
11876
11980
|
}
|
|
11877
|
-
const inner =
|
|
11981
|
+
const inner = unwrap4(node);
|
|
11878
11982
|
switch (inner.type) {
|
|
11879
11983
|
case AST_NODE_TYPES51.Literal: {
|
|
11880
11984
|
return !(isRegexLiteral(inner) && HAS_STATEFUL_FLAG_RE.test(inner.regex.flags));
|
|
@@ -11931,7 +12035,7 @@ function classify(init, checkRegex) {
|
|
|
11931
12035
|
if (node.arguments.length !== 1 || arg === void 0 || arg.type === AST_NODE_TYPES51.SpreadElement) {
|
|
11932
12036
|
return null;
|
|
11933
12037
|
}
|
|
11934
|
-
const entries =
|
|
12038
|
+
const entries = unwrap4(arg);
|
|
11935
12039
|
if (entries.type !== AST_NODE_TYPES51.ArrayExpression) {
|
|
11936
12040
|
return null;
|
|
11937
12041
|
}
|
|
@@ -11940,9 +12044,9 @@ function classify(init, checkRegex) {
|
|
|
11940
12044
|
return null;
|
|
11941
12045
|
}
|
|
11942
12046
|
function unwrapObjectFreeze(node) {
|
|
11943
|
-
const inner =
|
|
12047
|
+
const inner = unwrap4(node);
|
|
11944
12048
|
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) {
|
|
11945
|
-
return
|
|
12049
|
+
return unwrap4(inner.arguments[0]);
|
|
11946
12050
|
}
|
|
11947
12051
|
return inner;
|
|
11948
12052
|
}
|
|
@@ -12475,7 +12579,7 @@ var prefer_module_level_schema_default = createRule({
|
|
|
12475
12579
|
// src/rules/prefer-module-level-refined-schema.ts
|
|
12476
12580
|
import {
|
|
12477
12581
|
AST_NODE_TYPES as AST_NODE_TYPES53,
|
|
12478
|
-
ASTUtils as
|
|
12582
|
+
ASTUtils as ASTUtils19
|
|
12479
12583
|
} from "@typescript-eslint/utils";
|
|
12480
12584
|
var BENCHMARK_PATH_RE = /(^|[/\\])(?:benchmarks?|bench)[/\\]/;
|
|
12481
12585
|
var FACTORIES = /* @__PURE__ */ new Set([
|
|
@@ -12746,7 +12850,7 @@ var prefer_module_level_refined_schema_default = createRule({
|
|
|
12746
12850
|
return {};
|
|
12747
12851
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
12748
12852
|
function resolvedBinding(identifier) {
|
|
12749
|
-
return
|
|
12853
|
+
return ASTUtils19.findVariable(
|
|
12750
12854
|
context.sourceCode.getScope(identifier),
|
|
12751
12855
|
identifier.name
|
|
12752
12856
|
);
|
|
@@ -12847,18 +12951,18 @@ var prefer_module_level_refined_schema_default = createRule({
|
|
|
12847
12951
|
// src/rules/prefer-multi-value-zod-literal.ts
|
|
12848
12952
|
import {
|
|
12849
12953
|
AST_NODE_TYPES as AST_NODE_TYPES54,
|
|
12850
|
-
ASTUtils as
|
|
12954
|
+
ASTUtils as ASTUtils20
|
|
12851
12955
|
} from "@typescript-eslint/utils";
|
|
12852
12956
|
var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
12853
12957
|
summary: "Use the Zod 4 multi-value literal API instead of a union of literal schemas.",
|
|
12854
12958
|
rationale: "One multi-value literal expresses the same closed value domain without repeated schema wrappers.",
|
|
12855
12959
|
remediation: "Replace the union with z.literal([value1, value2, ...]).",
|
|
12856
12960
|
category: "maintainability",
|
|
12857
|
-
autofix: "
|
|
12961
|
+
autofix: "none",
|
|
12858
12962
|
limitations: [
|
|
12859
12963
|
"Bare zod imports are analyzed only when the rule option explicitly declares zodMajorVersion: 4; explicit zod/v4 entrypoints are self-declaring.",
|
|
12860
12964
|
"All-string domains are left to zod/prefer-enum-over-literal-union.",
|
|
12861
|
-
"
|
|
12965
|
+
"Migration is manual: ZodLiteral and ZodUnion expose different introspection APIs and validation error shapes even when they accept the same values."
|
|
12862
12966
|
],
|
|
12863
12967
|
examples: [
|
|
12864
12968
|
{
|
|
@@ -12881,10 +12985,6 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
|
12881
12985
|
path: "src/schema.ts",
|
|
12882
12986
|
source: "import { z } from 'zod'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
|
|
12883
12987
|
}],
|
|
12884
|
-
fixedFiles: [{
|
|
12885
|
-
path: "src/schema.ts",
|
|
12886
|
-
source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
|
|
12887
|
-
}],
|
|
12888
12988
|
focusPath: "src/schema.ts",
|
|
12889
12989
|
expectedCount: 1,
|
|
12890
12990
|
public: true
|
|
@@ -12898,7 +12998,7 @@ function isStaticPrimitive(node, context) {
|
|
|
12898
12998
|
if (node.type === AST_NODE_TYPES54.TemplateLiteral && node.expressions.length === 0)
|
|
12899
12999
|
return true;
|
|
12900
13000
|
if (node.type === AST_NODE_TYPES54.Identifier && node.name === "undefined") {
|
|
12901
|
-
const binding =
|
|
13001
|
+
const binding = ASTUtils20.findVariable(
|
|
12902
13002
|
context.sourceCode.getScope(node),
|
|
12903
13003
|
node.name
|
|
12904
13004
|
);
|
|
@@ -12914,7 +13014,6 @@ var prefer_multi_value_zod_literal_default = createRule({
|
|
|
12914
13014
|
documentation: PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION,
|
|
12915
13015
|
meta: {
|
|
12916
13016
|
type: "suggestion",
|
|
12917
|
-
fixable: "code",
|
|
12918
13017
|
docs: {
|
|
12919
13018
|
description: "Use the Zod 4 multi-value literal API instead of a union of literal schemas."
|
|
12920
13019
|
},
|
|
@@ -12936,7 +13035,7 @@ var prefer_multi_value_zod_literal_default = createRule({
|
|
|
12936
13035
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
12937
13036
|
const zod4Bindings = /* @__PURE__ */ new Set();
|
|
12938
13037
|
function resolvedBinding(identifier) {
|
|
12939
|
-
return
|
|
13038
|
+
return ASTUtils20.findVariable(
|
|
12940
13039
|
context.sourceCode.getScope(identifier),
|
|
12941
13040
|
identifier.name
|
|
12942
13041
|
);
|
|
@@ -12978,15 +13077,10 @@ var prefer_multi_value_zod_literal_default = createRule({
|
|
|
12978
13077
|
}
|
|
12979
13078
|
if (values.every(isStaticString)) return;
|
|
12980
13079
|
const namespace = node.callee.object.name;
|
|
12981
|
-
const hasComments = context.sourceCode.getCommentsInside(node).length > 0;
|
|
12982
13080
|
context.report({
|
|
12983
13081
|
node,
|
|
12984
13082
|
messageId: "useMultiValueLiteral",
|
|
12985
|
-
data: { zod: namespace }
|
|
12986
|
-
fix: hasComments ? null : (fixer) => fixer.replaceText(
|
|
12987
|
-
node,
|
|
12988
|
-
`${namespace}.literal([${values.map((value) => context.sourceCode.getText(value)).join(", ")}])`
|
|
12989
|
-
)
|
|
13083
|
+
data: { zod: namespace }
|
|
12990
13084
|
});
|
|
12991
13085
|
}
|
|
12992
13086
|
};
|
|
@@ -13059,10 +13153,10 @@ var PREFER_NAMED_COMPLEX_RETURN_TYPE_DOCUMENTATION = {
|
|
|
13059
13153
|
{ 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 }
|
|
13060
13154
|
]
|
|
13061
13155
|
};
|
|
13062
|
-
function
|
|
13156
|
+
function unwrap5(node) {
|
|
13063
13157
|
if (node.type === AST_NODE_TYPES56.TSTypeReference && node.typeArguments?.params.length === 1) {
|
|
13064
13158
|
const [inner] = node.typeArguments.params;
|
|
13065
|
-
if (inner !== void 0) return
|
|
13159
|
+
if (inner !== void 0) return unwrap5(inner);
|
|
13066
13160
|
}
|
|
13067
13161
|
return node;
|
|
13068
13162
|
}
|
|
@@ -13073,10 +13167,10 @@ function report(context, node) {
|
|
|
13073
13167
|
}
|
|
13074
13168
|
}
|
|
13075
13169
|
function isComplex(node) {
|
|
13076
|
-
const type =
|
|
13170
|
+
const type = unwrap5(node);
|
|
13077
13171
|
if (type.type === AST_NODE_TYPES56.TSTypeLiteral) return type.members.length >= 3;
|
|
13078
13172
|
if (type.type !== AST_NODE_TYPES56.TSUnionType || type.types.length < 3) return false;
|
|
13079
|
-
return type.types.every((member) =>
|
|
13173
|
+
return type.types.every((member) => unwrap5(member).type === AST_NODE_TYPES56.TSTypeLiteral);
|
|
13080
13174
|
}
|
|
13081
13175
|
var prefer_named_complex_return_type_default = createRule({
|
|
13082
13176
|
name: "prefer-named-complex-return-type",
|
|
@@ -13103,7 +13197,7 @@ var prefer_named_complex_return_type_default = createRule({
|
|
|
13103
13197
|
});
|
|
13104
13198
|
|
|
13105
13199
|
// src/rules/prefer-native-random-uuid.ts
|
|
13106
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as
|
|
13200
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils21 } from "@typescript-eslint/utils";
|
|
13107
13201
|
var PREFER_NATIVE_RANDOM_UUID_DOCUMENTATION = {
|
|
13108
13202
|
summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
|
|
13109
13203
|
rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
|
|
@@ -13139,7 +13233,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
13139
13233
|
const directBindings = /* @__PURE__ */ new Set();
|
|
13140
13234
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
13141
13235
|
function resolve2(identifier) {
|
|
13142
|
-
return
|
|
13236
|
+
return ASTUtils21.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
13143
13237
|
}
|
|
13144
13238
|
function record(identifier, destination) {
|
|
13145
13239
|
const variable = resolve2(identifier);
|
|
@@ -13202,7 +13296,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
13202
13296
|
});
|
|
13203
13297
|
|
|
13204
13298
|
// src/rules/prefer-node-crypto-hash.ts
|
|
13205
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as
|
|
13299
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils22 } from "@typescript-eslint/utils";
|
|
13206
13300
|
var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
|
|
13207
13301
|
summary: "Prefer the modern one-shot node:crypto hash API when streaming state is unnecessary.",
|
|
13208
13302
|
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.",
|
|
@@ -13242,7 +13336,7 @@ function isUnshadowedBuiltinIdentifier(identifier, resolve2) {
|
|
|
13242
13336
|
const variable = resolve2(identifier);
|
|
13243
13337
|
return variable === null || variable.defs.length === 0;
|
|
13244
13338
|
}
|
|
13245
|
-
function
|
|
13339
|
+
function propertyName2(node) {
|
|
13246
13340
|
if (!node.computed && node.key.type === AST_NODE_TYPES58.Identifier) return node.key.name;
|
|
13247
13341
|
if (node.key.type === AST_NODE_TYPES58.Literal && typeof node.key.value === "string") {
|
|
13248
13342
|
return node.key.value;
|
|
@@ -13258,7 +13352,7 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
13258
13352
|
const directBindings = /* @__PURE__ */ new Set();
|
|
13259
13353
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
13260
13354
|
function resolve2(identifier) {
|
|
13261
|
-
return
|
|
13355
|
+
return ASTUtils22.findVariable(
|
|
13262
13356
|
context.sourceCode.getScope(identifier),
|
|
13263
13357
|
identifier.name
|
|
13264
13358
|
);
|
|
@@ -13288,7 +13382,7 @@ var prefer_node_crypto_hash_default = createRule({
|
|
|
13288
13382
|
}
|
|
13289
13383
|
if (node.id.type !== AST_NODE_TYPES58.ObjectPattern) return;
|
|
13290
13384
|
for (const property of node.id.properties) {
|
|
13291
|
-
if (property.type === AST_NODE_TYPES58.Property &&
|
|
13385
|
+
if (property.type === AST_NODE_TYPES58.Property && propertyName2(property) === "createHash" && property.value.type === AST_NODE_TYPES58.Identifier) {
|
|
13292
13386
|
record(property.value, directBindings);
|
|
13293
13387
|
}
|
|
13294
13388
|
}
|
|
@@ -13370,7 +13464,7 @@ function isFsLoader(node) {
|
|
|
13370
13464
|
function isFsSpecifier(node) {
|
|
13371
13465
|
return node.type === AST_NODE_TYPES59.Literal && (node.value === "node:fs" || node.value === "fs");
|
|
13372
13466
|
}
|
|
13373
|
-
function
|
|
13467
|
+
function propertyName3(node) {
|
|
13374
13468
|
if (!node.computed && node.key.type === AST_NODE_TYPES59.Identifier) return node.key.name;
|
|
13375
13469
|
if (node.key.type === AST_NODE_TYPES59.Literal && typeof node.key.value === "string") return node.key.value;
|
|
13376
13470
|
return null;
|
|
@@ -13421,7 +13515,7 @@ var prefer_node_fs_promises_default = createRule({
|
|
|
13421
13515
|
if (node.id.type !== AST_NODE_TYPES59.ObjectPattern) return;
|
|
13422
13516
|
const synchronousImports = node.id.properties.flatMap((property) => {
|
|
13423
13517
|
if (property.type !== AST_NODE_TYPES59.Property) return [];
|
|
13424
|
-
const name =
|
|
13518
|
+
const name = propertyName3(property);
|
|
13425
13519
|
return name?.endsWith("Sync") === true ? [name] : [];
|
|
13426
13520
|
});
|
|
13427
13521
|
if (synchronousImports.length > 0) {
|
|
@@ -13445,7 +13539,7 @@ var prefer_node_fs_promises_default = createRule({
|
|
|
13445
13539
|
});
|
|
13446
13540
|
|
|
13447
13541
|
// src/rules/prefer-non-nullable-collection.ts
|
|
13448
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as
|
|
13542
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils23 } from "@typescript-eslint/utils";
|
|
13449
13543
|
var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
|
|
13450
13544
|
summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
|
|
13451
13545
|
rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
|
|
@@ -13458,7 +13552,7 @@ var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
|
|
|
13458
13552
|
]
|
|
13459
13553
|
};
|
|
13460
13554
|
var ARRAY_TYPE_NAMES = /* @__PURE__ */ new Set(["Array", "ReadonlyArray"]);
|
|
13461
|
-
function
|
|
13555
|
+
function propertyName4(node) {
|
|
13462
13556
|
const key = node.key;
|
|
13463
13557
|
if (node.computed) return null;
|
|
13464
13558
|
if (key.type === AST_NODE_TYPES60.Identifier) return key.name;
|
|
@@ -13471,7 +13565,7 @@ function isArrayType(node) {
|
|
|
13471
13565
|
}
|
|
13472
13566
|
function nullableProperty(node) {
|
|
13473
13567
|
if (node.optional) return null;
|
|
13474
|
-
const name =
|
|
13568
|
+
const name = propertyName4(node);
|
|
13475
13569
|
const annotation = node.typeAnnotation?.typeAnnotation;
|
|
13476
13570
|
if (name === null || annotation?.type !== AST_NODE_TYPES60.TSUnionType) return null;
|
|
13477
13571
|
const concrete = annotation.types.filter(
|
|
@@ -13575,14 +13669,14 @@ function directlyCoalesced(node) {
|
|
|
13575
13669
|
return parent?.type === AST_NODE_TYPES60.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
|
|
13576
13670
|
}
|
|
13577
13671
|
function identifierIsOnlyCoalesced(context, binding, fn) {
|
|
13578
|
-
const variable =
|
|
13672
|
+
const variable = ASTUtils23.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
13579
13673
|
if (variable === null || variable.references.length === 0) return false;
|
|
13580
13674
|
return variable.references.every(
|
|
13581
13675
|
(reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
|
|
13582
13676
|
);
|
|
13583
13677
|
}
|
|
13584
13678
|
function memberIsOnlyCoalesced(context, object, property, fn) {
|
|
13585
|
-
const variable =
|
|
13679
|
+
const variable = ASTUtils23.findVariable(context.sourceCode.getScope(object), object.name);
|
|
13586
13680
|
if (variable === null) return false;
|
|
13587
13681
|
const accesses = variable.references.flatMap((reference) => {
|
|
13588
13682
|
if (!belongsToFunction(reference.identifier, fn)) return [null];
|
|
@@ -13681,7 +13775,7 @@ var prefer_non_nullable_collection_default = createRule({
|
|
|
13681
13775
|
context.report({
|
|
13682
13776
|
node,
|
|
13683
13777
|
messageId: "preferNonNullableCollection",
|
|
13684
|
-
data: { name:
|
|
13778
|
+
data: { name: propertyName4(node) ?? "collection" }
|
|
13685
13779
|
});
|
|
13686
13780
|
}
|
|
13687
13781
|
}
|
|
@@ -13692,7 +13786,7 @@ var prefer_non_nullable_collection_default = createRule({
|
|
|
13692
13786
|
// src/rules/prefer-nullish-filter-predicate.ts
|
|
13693
13787
|
import {
|
|
13694
13788
|
AST_NODE_TYPES as AST_NODE_TYPES61,
|
|
13695
|
-
ASTUtils as
|
|
13789
|
+
ASTUtils as ASTUtils24,
|
|
13696
13790
|
ESLintUtils as ESLintUtils5
|
|
13697
13791
|
} from "@typescript-eslint/utils";
|
|
13698
13792
|
import ts3 from "typescript";
|
|
@@ -13738,7 +13832,7 @@ var PREFER_NULLISH_FILTER_PREDICATE_DOCUMENTATION = {
|
|
|
13738
13832
|
]
|
|
13739
13833
|
};
|
|
13740
13834
|
function isUnshadowedBoolean(node, context) {
|
|
13741
|
-
const variable =
|
|
13835
|
+
const variable = ASTUtils24.findVariable(context.sourceCode.getScope(node), node.name);
|
|
13742
13836
|
return variable === null || variable.defs.length === 0;
|
|
13743
13837
|
}
|
|
13744
13838
|
function isBuiltinArrayFilter(node, services) {
|
|
@@ -13797,7 +13891,7 @@ function isProvablyTruthy(type, checker) {
|
|
|
13797
13891
|
}
|
|
13798
13892
|
function availableParameterName(node, context) {
|
|
13799
13893
|
for (const name of ["value", "item", "element", "candidate"]) {
|
|
13800
|
-
if (
|
|
13894
|
+
if (ASTUtils24.findVariable(context.sourceCode.getScope(node), name) === null) return name;
|
|
13801
13895
|
}
|
|
13802
13896
|
return null;
|
|
13803
13897
|
}
|
|
@@ -13851,7 +13945,7 @@ var prefer_nullish_filter_predicate_default = createRule({
|
|
|
13851
13945
|
|
|
13852
13946
|
// src/rules/prefer-await-in-async-return.ts
|
|
13853
13947
|
import {
|
|
13854
|
-
ASTUtils as
|
|
13948
|
+
ASTUtils as ASTUtils25,
|
|
13855
13949
|
ESLintUtils as ESLintUtils6,
|
|
13856
13950
|
AST_NODE_TYPES as AST_NODE_TYPES62
|
|
13857
13951
|
} from "@typescript-eslint/utils";
|
|
@@ -13965,13 +14059,13 @@ var prefer_await_in_async_return_default = createRule({
|
|
|
13965
14059
|
if (services === null) return {};
|
|
13966
14060
|
const frameworkLoaders = /* @__PURE__ */ new Set();
|
|
13967
14061
|
const rememberFrameworkLoader = (identifier) => {
|
|
13968
|
-
const variable =
|
|
14062
|
+
const variable = ASTUtils25.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
13969
14063
|
if (variable !== null) frameworkLoaders.add(variable);
|
|
13970
14064
|
};
|
|
13971
14065
|
const isFrameworkLoaderCallback = (owner) => {
|
|
13972
14066
|
const parent = owner.parent;
|
|
13973
14067
|
if (parent.type !== AST_NODE_TYPES62.CallExpression || parent.arguments[0] !== owner || parent.callee.type !== AST_NODE_TYPES62.Identifier) return false;
|
|
13974
|
-
const variable =
|
|
14068
|
+
const variable = ASTUtils25.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
|
|
13975
14069
|
return variable !== null && frameworkLoaders.has(variable);
|
|
13976
14070
|
};
|
|
13977
14071
|
return {
|
|
@@ -14013,7 +14107,7 @@ var PREFER_SCHEMA_FOR_API_PAYLOAD_DOCUMENTATION = {
|
|
|
14013
14107
|
{ 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 }
|
|
14014
14108
|
]
|
|
14015
14109
|
};
|
|
14016
|
-
var
|
|
14110
|
+
var unwrap6 = (node) => {
|
|
14017
14111
|
let current = node;
|
|
14018
14112
|
while (current !== null && current !== void 0) {
|
|
14019
14113
|
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) {
|
|
@@ -14032,23 +14126,23 @@ var PROMISE_CHAIN_METHODS = /* @__PURE__ */ new Set([
|
|
|
14032
14126
|
"finally"
|
|
14033
14127
|
]);
|
|
14034
14128
|
var isSchemaParseReference = (node) => {
|
|
14035
|
-
const inner =
|
|
14129
|
+
const inner = unwrap6(node);
|
|
14036
14130
|
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");
|
|
14037
14131
|
};
|
|
14038
14132
|
var isRawPayloadSource = (node, isKnownLocalText) => {
|
|
14039
|
-
let current =
|
|
14133
|
+
let current = unwrap6(node);
|
|
14040
14134
|
if (current === null) return false;
|
|
14041
14135
|
if (current.type === AST_NODE_TYPES63.AwaitExpression) {
|
|
14042
|
-
current =
|
|
14136
|
+
current = unwrap6(current.argument);
|
|
14043
14137
|
}
|
|
14044
14138
|
if (current === null || current.type !== AST_NODE_TYPES63.CallExpression) {
|
|
14045
14139
|
return false;
|
|
14046
14140
|
}
|
|
14047
|
-
const callee =
|
|
14141
|
+
const callee = unwrap6(current.callee);
|
|
14048
14142
|
if (callee === null || callee.type !== AST_NODE_TYPES63.MemberExpression) {
|
|
14049
14143
|
return false;
|
|
14050
14144
|
}
|
|
14051
|
-
const property =
|
|
14145
|
+
const property = unwrap6(callee.property);
|
|
14052
14146
|
if (property === null || property.type !== AST_NODE_TYPES63.Identifier) {
|
|
14053
14147
|
return false;
|
|
14054
14148
|
}
|
|
@@ -14058,17 +14152,17 @@ var isRawPayloadSource = (node, isKnownLocalText) => {
|
|
|
14058
14152
|
if (PROMISE_CHAIN_METHODS.has(property.name)) {
|
|
14059
14153
|
return !current.arguments.some(isSchemaParseReference) && isRawPayloadSource(callee.object);
|
|
14060
14154
|
}
|
|
14061
|
-
const object =
|
|
14155
|
+
const object = unwrap6(callee.object);
|
|
14062
14156
|
return property.name === "parse" && object !== null && object.type === AST_NODE_TYPES63.Identifier && object.name === "JSON" && !isLocalFileRead(current.arguments[0]) && isKnownLocalText?.(current.arguments[0]) !== true;
|
|
14063
14157
|
};
|
|
14064
14158
|
var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
|
|
14065
14159
|
var isDirectLocalFileRead = (node) => {
|
|
14066
|
-
let current =
|
|
14160
|
+
let current = unwrap6(node);
|
|
14067
14161
|
if (current?.type === AST_NODE_TYPES63.AwaitExpression) {
|
|
14068
|
-
current =
|
|
14162
|
+
current = unwrap6(current.argument);
|
|
14069
14163
|
}
|
|
14070
14164
|
if (current?.type !== AST_NODE_TYPES63.CallExpression) return false;
|
|
14071
|
-
const callee =
|
|
14165
|
+
const callee = unwrap6(current.callee);
|
|
14072
14166
|
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;
|
|
14073
14167
|
return name !== null && FILE_READ_RE.test(name);
|
|
14074
14168
|
};
|
|
@@ -14308,7 +14402,7 @@ var isGuardTestPosition = (node) => {
|
|
|
14308
14402
|
return false;
|
|
14309
14403
|
};
|
|
14310
14404
|
var unvalidatedVariableRef = (node, scope, tracked) => {
|
|
14311
|
-
const unwrapped =
|
|
14405
|
+
const unwrapped = unwrap6(node);
|
|
14312
14406
|
if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES63.Identifier) {
|
|
14313
14407
|
return null;
|
|
14314
14408
|
}
|
|
@@ -14337,7 +14431,7 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
14337
14431
|
const aliasGroups = /* @__PURE__ */ new Map();
|
|
14338
14432
|
const localFileTextVariables = /* @__PURE__ */ new Set();
|
|
14339
14433
|
const localFileTextRef = (node, scope) => {
|
|
14340
|
-
const unwrapped =
|
|
14434
|
+
const unwrapped = unwrap6(node);
|
|
14341
14435
|
if (unwrapped?.type !== AST_NODE_TYPES63.Identifier) return null;
|
|
14342
14436
|
const variable = findVariable2(scope, unwrapped.name);
|
|
14343
14437
|
return variable !== null && localFileTextVariables.has(variable) ? variable : null;
|
|
@@ -14473,7 +14567,7 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
14473
14567
|
const scope = context.sourceCode.getScope(node);
|
|
14474
14568
|
for (const arg of node.arguments) {
|
|
14475
14569
|
if (arg.type === AST_NODE_TYPES63.SpreadElement) continue;
|
|
14476
|
-
const unwrapped =
|
|
14570
|
+
const unwrapped = unwrap6(arg);
|
|
14477
14571
|
if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES63.Identifier) {
|
|
14478
14572
|
continue;
|
|
14479
14573
|
}
|
|
@@ -14485,7 +14579,7 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
14485
14579
|
if (isInsideAssertion(node)) return;
|
|
14486
14580
|
if (isValidationRead(node)) return;
|
|
14487
14581
|
const scope = context.sourceCode.getScope(node);
|
|
14488
|
-
const obj =
|
|
14582
|
+
const obj = unwrap6(node.object);
|
|
14489
14583
|
if (isRawPayloadSource(
|
|
14490
14584
|
obj,
|
|
14491
14585
|
(candidate2) => localFileTextRef(candidate2, scope) !== null
|
|
@@ -14690,6 +14784,7 @@ var PREFER_SEMANTIC_COLORS_DOCUMENTATION = {
|
|
|
14690
14784
|
remediation: "Replace raw palette and literal colors with the closest semantic design-system token or CSS variable.",
|
|
14691
14785
|
category: "style",
|
|
14692
14786
|
limitations: [
|
|
14787
|
+
"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.",
|
|
14693
14788
|
"Email, PDF, video-rendering, print-only, icon artwork, masks, gradients, stories, and explicitly configured non-token projects have targeted exclusions.",
|
|
14694
14789
|
"Opaque-foreground checks are opt-in and require both a same-variant semantic background class and its package-local declared foreground token."
|
|
14695
14790
|
],
|
|
@@ -15094,7 +15189,7 @@ var prefer_semantic_colors_default = createRule({
|
|
|
15094
15189
|
});
|
|
15095
15190
|
}
|
|
15096
15191
|
};
|
|
15097
|
-
const checkClassNode = (node) => {
|
|
15192
|
+
const checkClassNode = (node, objectKeys = false) => {
|
|
15098
15193
|
if (node === null) return;
|
|
15099
15194
|
switch (node.type) {
|
|
15100
15195
|
case AST_NODE_TYPES66.Literal:
|
|
@@ -15105,27 +15200,35 @@ var prefer_semantic_colors_default = createRule({
|
|
|
15105
15200
|
break;
|
|
15106
15201
|
case AST_NODE_TYPES66.ArrayExpression:
|
|
15107
15202
|
for (const element of node.elements) {
|
|
15108
|
-
if (element !== null && element.type !== AST_NODE_TYPES66.SpreadElement) checkClassNode(element);
|
|
15203
|
+
if (element !== null && element.type !== AST_NODE_TYPES66.SpreadElement) checkClassNode(element, objectKeys);
|
|
15109
15204
|
}
|
|
15110
15205
|
break;
|
|
15111
15206
|
case AST_NODE_TYPES66.ObjectExpression:
|
|
15112
15207
|
for (const property of node.properties) {
|
|
15113
|
-
if (property.type
|
|
15208
|
+
if (property.type !== AST_NODE_TYPES66.Property) continue;
|
|
15209
|
+
if (objectKeys) {
|
|
15210
|
+
if (property.value.type === AST_NODE_TYPES66.Literal && !property.value.value && !("regex" in property.value)) continue;
|
|
15211
|
+
if (!property.computed && property.key.type === AST_NODE_TYPES66.Literal) {
|
|
15212
|
+
checkClassNode(property.key);
|
|
15213
|
+
}
|
|
15214
|
+
} else {
|
|
15215
|
+
checkClassNode(property.value);
|
|
15216
|
+
}
|
|
15114
15217
|
}
|
|
15115
15218
|
break;
|
|
15116
15219
|
case AST_NODE_TYPES66.ConditionalExpression:
|
|
15117
|
-
checkClassNode(node.consequent);
|
|
15118
|
-
checkClassNode(node.alternate);
|
|
15220
|
+
checkClassNode(node.consequent, objectKeys);
|
|
15221
|
+
checkClassNode(node.alternate, objectKeys);
|
|
15119
15222
|
break;
|
|
15120
15223
|
case AST_NODE_TYPES66.LogicalExpression:
|
|
15121
|
-
checkClassNode(node.right);
|
|
15224
|
+
checkClassNode(node.right, objectKeys);
|
|
15122
15225
|
break;
|
|
15123
15226
|
default:
|
|
15124
15227
|
break;
|
|
15125
15228
|
}
|
|
15126
15229
|
};
|
|
15127
15230
|
const checkColorValueNode = (node) => {
|
|
15128
|
-
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)) {
|
|
15231
|
+
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)) {
|
|
15129
15232
|
report2(node, "inlineColor", { value: node.value });
|
|
15130
15233
|
}
|
|
15131
15234
|
};
|
|
@@ -15145,7 +15248,9 @@ var prefer_semantic_colors_default = createRule({
|
|
|
15145
15248
|
}
|
|
15146
15249
|
if (node.callee.type === AST_NODE_TYPES66.Identifier && CLASS_FNS.has(node.callee.name)) {
|
|
15147
15250
|
for (const arg of node.arguments) {
|
|
15148
|
-
if (arg.type !== AST_NODE_TYPES66.SpreadElement)
|
|
15251
|
+
if (arg.type !== AST_NODE_TYPES66.SpreadElement) {
|
|
15252
|
+
checkClassNode(arg, node.callee.name !== "cva" && node.callee.name !== "tv");
|
|
15253
|
+
}
|
|
15149
15254
|
}
|
|
15150
15255
|
}
|
|
15151
15256
|
},
|
|
@@ -15189,13 +15294,13 @@ var prefer_semantic_colors_default = createRule({
|
|
|
15189
15294
|
});
|
|
15190
15295
|
|
|
15191
15296
|
// src/rules/prefer-server-actions.ts
|
|
15192
|
-
import "@typescript-eslint/utils";
|
|
15297
|
+
import { ASTUtils as ASTUtils26 } from "@typescript-eslint/utils";
|
|
15193
15298
|
var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
|
|
15194
15299
|
summary: "Prefer Next.js Server Actions over same-origin API mutations.",
|
|
15195
|
-
rationale: "Server Actions
|
|
15196
|
-
remediation: "
|
|
15300
|
+
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.",
|
|
15301
|
+
remediation: "Consider a Server Action for application-owned mutations; preserve authorization, input validation and any public API consumers.",
|
|
15197
15302
|
category: "architecture",
|
|
15198
|
-
limitations: ["Only statically recognizable /api/ mutations
|
|
15303
|
+
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."],
|
|
15199
15304
|
examples: [
|
|
15200
15305
|
{ 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 },
|
|
15201
15306
|
{ 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 }
|
|
@@ -15221,21 +15326,39 @@ function resolvesToGlobalFetch(context, identifier) {
|
|
|
15221
15326
|
function resolveNode(node, context) {
|
|
15222
15327
|
if (!node) return null;
|
|
15223
15328
|
if (node.type !== "Identifier") return node;
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15232
|
-
|
|
15233
|
-
|
|
15234
|
-
|
|
15235
|
-
|
|
15236
|
-
|
|
15237
|
-
|
|
15238
|
-
|
|
15329
|
+
const variable = ASTUtils26.findVariable(getScope(context, node), node.name);
|
|
15330
|
+
const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
|
|
15331
|
+
if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || variable?.references.some((reference) => reference.isWrite() && reference.init !== true)) return node;
|
|
15332
|
+
if (definition.node.init.type === "ObjectExpression" && variable?.references.some((reference) => reference.identifier !== node && reference.init !== true)) return node;
|
|
15333
|
+
return definition.node.init;
|
|
15334
|
+
}
|
|
15335
|
+
function isAxiosClient(node, context, seen = /* @__PURE__ */ new Set()) {
|
|
15336
|
+
if (node.type !== "Identifier" || seen.has(node)) return false;
|
|
15337
|
+
seen.add(node);
|
|
15338
|
+
const variable = ASTUtils26.findVariable(getScope(context, node), node.name);
|
|
15339
|
+
const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
|
|
15340
|
+
if (definition === void 0 || variable?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
|
|
15341
|
+
if (variable?.references.some((reference) => {
|
|
15342
|
+
if (reference.init === true) return false;
|
|
15343
|
+
const identifier = reference.identifier;
|
|
15344
|
+
const parent = identifier.parent;
|
|
15345
|
+
if (parent.type === "CallExpression" && parent.callee === identifier) return false;
|
|
15346
|
+
return parent.type !== "MemberExpression" || parent.object !== identifier || parent.computed || parent.parent.type !== "CallExpression" || parent.parent.callee !== parent;
|
|
15347
|
+
})) return false;
|
|
15348
|
+
if (definition.type === "ImportBinding") {
|
|
15349
|
+
const declaration = definition.parent;
|
|
15350
|
+
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");
|
|
15351
|
+
}
|
|
15352
|
+
if (definition.type !== "Variable" || definition.parent.kind !== "const") return false;
|
|
15353
|
+
const init = definition.node.init;
|
|
15354
|
+
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);
|
|
15355
|
+
}
|
|
15356
|
+
function hasLocalAxiosOptions(node, context) {
|
|
15357
|
+
if (node === void 0) return true;
|
|
15358
|
+
const options = resolveNode(node, context);
|
|
15359
|
+
return options?.type === "ObjectExpression" && options.properties.every(
|
|
15360
|
+
(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")
|
|
15361
|
+
);
|
|
15239
15362
|
}
|
|
15240
15363
|
function isApiUrl(node, context, apiPrefixes) {
|
|
15241
15364
|
const resolved = resolveNode(node, context);
|
|
@@ -15296,7 +15419,8 @@ function isFunctionArgument(node, context) {
|
|
|
15296
15419
|
}
|
|
15297
15420
|
function getPropertyNode(objNode, propName2) {
|
|
15298
15421
|
if (!objNode || objNode.type !== "ObjectExpression") return null;
|
|
15299
|
-
|
|
15422
|
+
if (objNode.properties.some((property) => property.type === "SpreadElement" || property.computed)) return null;
|
|
15423
|
+
for (const prop of [...objNode.properties].reverse()) {
|
|
15300
15424
|
if (prop.type !== "Property") continue;
|
|
15301
15425
|
let keyName = null;
|
|
15302
15426
|
if (prop.key.type === "Identifier" && !prop.computed) {
|
|
@@ -15334,7 +15458,7 @@ var prefer_server_actions_default = createRule({
|
|
|
15334
15458
|
}
|
|
15335
15459
|
],
|
|
15336
15460
|
messages: {
|
|
15337
|
-
preferServerAction: "
|
|
15461
|
+
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."
|
|
15338
15462
|
}
|
|
15339
15463
|
},
|
|
15340
15464
|
defaultOptions: [{}],
|
|
@@ -15378,9 +15502,11 @@ var prefer_server_actions_default = createRule({
|
|
|
15378
15502
|
}
|
|
15379
15503
|
}
|
|
15380
15504
|
}
|
|
15381
|
-
} else if (node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && !node.callee.computed) {
|
|
15505
|
+
} else if (node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && !node.callee.computed && isAxiosClient(node.callee.object, context)) {
|
|
15382
15506
|
const methodName2 = node.callee.property.name.toLowerCase();
|
|
15383
15507
|
if (AXIOS_MUTATION_METHODS.has(methodName2)) {
|
|
15508
|
+
const config = node.arguments[methodName2 === "delete" ? 1 : 2];
|
|
15509
|
+
if (!hasLocalAxiosOptions(config, context)) return;
|
|
15384
15510
|
const urlArg = node.arguments[0];
|
|
15385
15511
|
const hasHandlerArg = node.arguments.some(
|
|
15386
15512
|
(arg) => arg.type !== "SpreadElement" && isFunctionArgument(arg, context)
|
|
@@ -15389,11 +15515,12 @@ var prefer_server_actions_default = createRule({
|
|
|
15389
15515
|
isMutation = true;
|
|
15390
15516
|
}
|
|
15391
15517
|
}
|
|
15392
|
-
} else if (node.callee.type === "Identifier" && (node.callee
|
|
15518
|
+
} else if (node.callee.type === "Identifier" && isAxiosClient(node.callee, context)) {
|
|
15393
15519
|
const firstArg = node.arguments[0];
|
|
15394
15520
|
if (firstArg && firstArg.type !== "SpreadElement") {
|
|
15395
15521
|
const configArg = resolveNode(firstArg, context);
|
|
15396
15522
|
if (configArg && configArg.type === "ObjectExpression") {
|
|
15523
|
+
if (!hasLocalAxiosOptions(firstArg, context)) return;
|
|
15397
15524
|
const urlNode = getPropertyNode(configArg, "url");
|
|
15398
15525
|
const methodNode = getPropertyNode(configArg, "method");
|
|
15399
15526
|
if (urlNode && isApiUrl(urlNode, context, apiPrefixes) && methodNode && isMutationMethod(methodNode, context)) {
|
|
@@ -15425,13 +15552,14 @@ var MIN_RUN_LENGTH = 2;
|
|
|
15425
15552
|
var PREFER_WHOLE_OBJECT_ASSERTION_DOCUMENTATION = {
|
|
15426
15553
|
summary: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together.",
|
|
15427
15554
|
rationale: "One whole-object assertion presents related expectations together and produces a complete structural diff.",
|
|
15428
|
-
remediation: "
|
|
15555
|
+
remediation: "Consider one `toMatchObject` assertion for ordinary data objects. Preserve missing-property checks, identity, and getter or proxy behavior when deciding whether to combine assertions.",
|
|
15429
15556
|
category: "testing",
|
|
15430
15557
|
aliases: ["strict-test-assertions"],
|
|
15431
|
-
autofix: "
|
|
15558
|
+
autofix: "none",
|
|
15559
|
+
limitations: ["No automatic rewrite: whole-object matching can require previously absent properties and change observable getter or proxy reads."],
|
|
15432
15560
|
examples: [
|
|
15433
15561
|
{ id: "whole-object", title: "Assert the object once", outcome: "no-match", files: [{ path: "src/user.test.ts", source: "expect(user).toMatchObject({ id: 1, name: 'Ada' });" }], focusPath: "src/user.test.ts", expectedCount: 0, public: true },
|
|
15434
|
-
{ id: "member-run", title: "
|
|
15562
|
+
{ id: "member-run", title: "Consider grouping related data properties", outcome: "match", files: [{ path: "src/user.test.ts", source: "expect(user.id).toBe(1);\nexpect(user.name).toBe('Ada');" }], focusPath: "src/user.test.ts", expectedCount: 1, public: true }
|
|
15435
15563
|
]
|
|
15436
15564
|
};
|
|
15437
15565
|
function literalText(node, getText) {
|
|
@@ -15487,9 +15615,8 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15487
15615
|
docs: {
|
|
15488
15616
|
description: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together."
|
|
15489
15617
|
},
|
|
15490
|
-
fixable: "code",
|
|
15491
15618
|
messages: {
|
|
15492
|
-
combineAssertions: "
|
|
15619
|
+
combineAssertions: "Consider combining these {{count}} assertions on `{{receiver}}` with `toMatchObject` when structural matching preserves property presence and getter or proxy behavior.",
|
|
15493
15620
|
assertArrayOnce: "These {{count}} assertions check `{{receiver}}[0]`\u2026`{{receiver}}[{{last}}]` one at a time, which never checks how long `{{receiver}}` is \u2014 extra elements pass unnoticed. Assert the array once: `expect({{receiver}}).{{matcher}}([ \u2026 ])`."
|
|
15494
15621
|
},
|
|
15495
15622
|
schema: []
|
|
@@ -15560,11 +15687,6 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15560
15687
|
expectedIsLiteral: literal !== null
|
|
15561
15688
|
};
|
|
15562
15689
|
}
|
|
15563
|
-
function hasInterveningComment(run) {
|
|
15564
|
-
return run.some(
|
|
15565
|
-
(assertion, index) => sourceCode.getCommentsInside(assertion.statement).length > 0 || index > 0 && sourceCode.getCommentsBefore(assertion.statement).length > 0
|
|
15566
|
-
);
|
|
15567
|
-
}
|
|
15568
15690
|
function reportPropertyRun(run) {
|
|
15569
15691
|
const tree = /* @__PURE__ */ new Map();
|
|
15570
15692
|
const paths = [];
|
|
@@ -15611,16 +15733,10 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15611
15733
|
return;
|
|
15612
15734
|
}
|
|
15613
15735
|
const receiverText = `${sourceCode.getText(first.receiver)}${commonPrefix.map((name) => `.${name}`).join("")}`;
|
|
15614
|
-
const renderTree = (value) => [...value.entries()].map(([name, child]) => `${name}: ${child instanceof Map ? `{ ${renderTree(child)} }` : child}`).join(", ");
|
|
15615
|
-
const properties = renderTree(tree);
|
|
15616
15736
|
context.report({
|
|
15617
15737
|
node: first.statement,
|
|
15618
15738
|
messageId: "combineAssertions",
|
|
15619
|
-
data: { count: String(run.length), receiver: receiverText }
|
|
15620
|
-
fix: hasInterveningComment(run) ? null : (fixer) => [
|
|
15621
|
-
fixer.replaceText(first.statement, `expect(${receiverText}).toMatchObject({ ${properties} });`),
|
|
15622
|
-
...run.slice(1).map((assertion) => fixer.remove(assertion.statement))
|
|
15623
|
-
]
|
|
15739
|
+
data: { count: String(run.length), receiver: receiverText }
|
|
15624
15740
|
});
|
|
15625
15741
|
}
|
|
15626
15742
|
function reportIndexRun(run) {
|
|
@@ -15688,7 +15804,7 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15688
15804
|
});
|
|
15689
15805
|
|
|
15690
15806
|
// src/rules/repeated-static-call-cases.ts
|
|
15691
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as
|
|
15807
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils27 } from "@typescript-eslint/utils";
|
|
15692
15808
|
var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
|
|
15693
15809
|
summary: "Report three or more consecutive literal call assertions that should be independently named test cases.",
|
|
15694
15810
|
rationale: "Copy-pasted cases obscure the input table and stop later cases from being reported after the first failure.",
|
|
@@ -15714,7 +15830,7 @@ function staticMemberName5(node) {
|
|
|
15714
15830
|
return null;
|
|
15715
15831
|
}
|
|
15716
15832
|
function importedName6(identifier, context, modules) {
|
|
15717
|
-
const variable =
|
|
15833
|
+
const variable = ASTUtils27.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
15718
15834
|
if (variable === null || variable.defs.length === 0) return identifier.name;
|
|
15719
15835
|
for (const definition of variable.defs) {
|
|
15720
15836
|
if (definition.node.type !== AST_NODE_TYPES68.ImportSpecifier) continue;
|
|
@@ -16292,11 +16408,11 @@ var prefer_zod_infer_default = createRule({
|
|
|
16292
16408
|
continue;
|
|
16293
16409
|
}
|
|
16294
16410
|
const key = member.key;
|
|
16295
|
-
const
|
|
16296
|
-
if (
|
|
16411
|
+
const propertyName6 = key.type === AST_NODE_TYPES69.Identifier ? key.name : key.type === AST_NODE_TYPES69.Literal && typeof key.value === "string" ? key.value : null;
|
|
16412
|
+
if (propertyName6 === null) {
|
|
16297
16413
|
continue;
|
|
16298
16414
|
}
|
|
16299
|
-
const propertyTokens = nameTokens(
|
|
16415
|
+
const propertyTokens = nameTokens(propertyName6);
|
|
16300
16416
|
if (propertyTokens.length < 2) {
|
|
16301
16417
|
continue;
|
|
16302
16418
|
}
|
|
@@ -16311,7 +16427,7 @@ var prefer_zod_infer_default = createRule({
|
|
|
16311
16427
|
node: annotation,
|
|
16312
16428
|
owner,
|
|
16313
16429
|
ownerName,
|
|
16314
|
-
propertyName:
|
|
16430
|
+
propertyName: propertyName6,
|
|
16315
16431
|
propertyTokens
|
|
16316
16432
|
});
|
|
16317
16433
|
}
|
|
@@ -16715,12 +16831,13 @@ var require_assert_never_default = createRule({
|
|
|
16715
16831
|
});
|
|
16716
16832
|
|
|
16717
16833
|
// src/rules/require-fetch-timeout.ts
|
|
16718
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as
|
|
16834
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils28 } from "@typescript-eslint/utils";
|
|
16719
16835
|
var REQUIRE_FETCH_TIMEOUT_DOCUMENTATION = {
|
|
16720
|
-
summary: "Require an abort
|
|
16836
|
+
summary: "Require an explicit abort signal on locally analyzable global fetch calls.",
|
|
16721
16837
|
rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
|
|
16722
16838
|
remediation: "Pass an abort signal, such as `AbortSignal.timeout(ms)`, in the fetch init.",
|
|
16723
16839
|
category: "correctness",
|
|
16840
|
+
limitations: ["Signal presence establishes an explicit cancellation path, not a guaranteed timeout. Forwarded Request objects can carry an existing signal."],
|
|
16724
16841
|
examples: [
|
|
16725
16842
|
{ 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 },
|
|
16726
16843
|
{ 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 }
|
|
@@ -16766,7 +16883,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
16766
16883
|
meta: {
|
|
16767
16884
|
type: "problem",
|
|
16768
16885
|
docs: {
|
|
16769
|
-
description: "Require an abort
|
|
16886
|
+
description: "Require an explicit abort signal on locally analyzable global fetch calls."
|
|
16770
16887
|
},
|
|
16771
16888
|
schema: [
|
|
16772
16889
|
{
|
|
@@ -16782,7 +16899,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
16782
16899
|
}
|
|
16783
16900
|
],
|
|
16784
16901
|
messages: {
|
|
16785
|
-
missingSignal: "This `fetch()` has no abort
|
|
16902
|
+
missingSignal: "This `fetch()` has no explicit abort signal. Pass `AbortSignal.timeout(ms)` for a deadline, or an owner-managed signal for cancellation."
|
|
16786
16903
|
}
|
|
16787
16904
|
},
|
|
16788
16905
|
defaultOptions: [{}],
|
|
@@ -16796,7 +16913,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
16796
16913
|
}
|
|
16797
16914
|
function resolvesToGlobal(identifier) {
|
|
16798
16915
|
const scope = context.sourceCode.getScope(identifier);
|
|
16799
|
-
const variable =
|
|
16916
|
+
const variable = ASTUtils28.findVariable(scope, identifier.name);
|
|
16800
16917
|
return variable === null || variable.defs.length === 0;
|
|
16801
16918
|
}
|
|
16802
16919
|
function isGlobalFetchCall2(callee) {
|
|
@@ -16806,7 +16923,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
16806
16923
|
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);
|
|
16807
16924
|
}
|
|
16808
16925
|
function localConstInitProvablyLacksSignal(identifier) {
|
|
16809
|
-
const variable =
|
|
16926
|
+
const variable = ASTUtils28.findVariable(
|
|
16810
16927
|
context.sourceCode.getScope(identifier),
|
|
16811
16928
|
identifier.name
|
|
16812
16929
|
);
|
|
@@ -16825,13 +16942,23 @@ var require_fetch_timeout_default = createRule({
|
|
|
16825
16942
|
}
|
|
16826
16943
|
return true;
|
|
16827
16944
|
}
|
|
16945
|
+
function isForwardedRequest(argument) {
|
|
16946
|
+
let value = argument;
|
|
16947
|
+
if (value.type === AST_NODE_TYPES71.Identifier) {
|
|
16948
|
+
const binding = ASTUtils28.findVariable(context.sourceCode.getScope(value), value.name);
|
|
16949
|
+
const definition = binding?.defs.length === 1 ? binding.defs[0] : void 0;
|
|
16950
|
+
if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
|
|
16951
|
+
value = definition.node.init;
|
|
16952
|
+
}
|
|
16953
|
+
return value.type === AST_NODE_TYPES71.NewExpression && value.callee.type === AST_NODE_TYPES71.Identifier && value.callee.name === "Request" && resolvesToGlobal(value.callee);
|
|
16954
|
+
}
|
|
16828
16955
|
return {
|
|
16829
16956
|
CallExpression(node) {
|
|
16830
16957
|
if (!isGlobalFetchCall2(node.callee)) {
|
|
16831
16958
|
return;
|
|
16832
16959
|
}
|
|
16833
16960
|
const [first, init] = node.arguments;
|
|
16834
|
-
if (node.arguments.length === 1 &&
|
|
16961
|
+
if (first !== void 0 && (node.arguments.length === 1 && !isInlineUrl(first, resolvesToGlobal) || isForwardedRequest(first))) {
|
|
16835
16962
|
return;
|
|
16836
16963
|
}
|
|
16837
16964
|
if (init === void 0 || initProvablyLacksSignal(init) || init.type === AST_NODE_TYPES71.Identifier && localConstInitProvablyLacksSignal(init)) {
|
|
@@ -17844,7 +17971,7 @@ function isStaticValue(node) {
|
|
|
17844
17971
|
}
|
|
17845
17972
|
return false;
|
|
17846
17973
|
}
|
|
17847
|
-
function
|
|
17974
|
+
function propertyName5(property) {
|
|
17848
17975
|
if (property.computed) return null;
|
|
17849
17976
|
if (property.key.type === AST_NODE_TYPES75.Identifier) return property.key.name;
|
|
17850
17977
|
return typeof property.key.value === "string" ? property.key.value : null;
|
|
@@ -17881,7 +18008,7 @@ var require_static_next_matcher_default = createRule({
|
|
|
17881
18008
|
continue;
|
|
17882
18009
|
}
|
|
17883
18010
|
for (const property of config.properties) {
|
|
17884
|
-
if (property.type !== AST_NODE_TYPES75.Property ||
|
|
18011
|
+
if (property.type !== AST_NODE_TYPES75.Property || propertyName5(property) !== "matcher" || property.value.type === AST_NODE_TYPES75.AssignmentPattern) {
|
|
17885
18012
|
continue;
|
|
17886
18013
|
}
|
|
17887
18014
|
if (!isStaticValue(property.value)) {
|
|
@@ -17895,21 +18022,21 @@ var require_static_next_matcher_default = createRule({
|
|
|
17895
18022
|
});
|
|
17896
18023
|
|
|
17897
18024
|
// src/rules/require-use-form-default-values.ts
|
|
17898
|
-
import { ASTUtils as
|
|
18025
|
+
import { ASTUtils as ASTUtils29 } from "@typescript-eslint/utils";
|
|
17899
18026
|
var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
|
|
17900
|
-
summary: "react-hook-form useForm call without
|
|
18027
|
+
summary: "react-hook-form useForm call without explicit initial or reactive values",
|
|
17901
18028
|
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.",
|
|
17902
|
-
remediation: "
|
|
18029
|
+
remediation: "Provide defaultValues for initial state, or values when reactive external state owns initialization; choose schema-appropriate values for controlled fields.",
|
|
17903
18030
|
category: "correctness",
|
|
17904
18031
|
limitations: [
|
|
17905
|
-
"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."
|
|
18032
|
+
"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."
|
|
17906
18033
|
],
|
|
17907
18034
|
examples: [
|
|
17908
18035
|
{
|
|
17909
18036
|
id: "form-with-initial-values",
|
|
17910
18037
|
title: "Give the form an explicit initial shape",
|
|
17911
18038
|
outcome: "no-match",
|
|
17912
|
-
files: [{ path: "profile-form.tsx", source: "import { useForm } from 'react-hook-form'
|
|
18039
|
+
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')} />; }" }],
|
|
17913
18040
|
focusPath: "profile-form.tsx",
|
|
17914
18041
|
expectedCount: 0,
|
|
17915
18042
|
public: true
|
|
@@ -17918,16 +18045,16 @@ var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
|
|
|
17918
18045
|
id: "form-without-initial-values",
|
|
17919
18046
|
title: "Do not leave form initialization implicit",
|
|
17920
18047
|
outcome: "match",
|
|
17921
|
-
files: [{ path: "profile-form.tsx", source: "import { useForm } from 'react-hook-form'
|
|
18048
|
+
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')} />; }" }],
|
|
17922
18049
|
focusPath: "profile-form.tsx",
|
|
17923
18050
|
expectedCount: 1,
|
|
17924
18051
|
public: true
|
|
17925
18052
|
}
|
|
17926
18053
|
]
|
|
17927
18054
|
};
|
|
17928
|
-
function
|
|
18055
|
+
function hasInitializationOrUnknownOptions(options) {
|
|
17929
18056
|
return options?.type === "ObjectExpression" && options.properties.some(
|
|
17930
|
-
(property) => property.type === "
|
|
18057
|
+
(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)))
|
|
17931
18058
|
);
|
|
17932
18059
|
}
|
|
17933
18060
|
var require_use_form_default_values_default = createRule({
|
|
@@ -17938,7 +18065,7 @@ var require_use_form_default_values_default = createRule({
|
|
|
17938
18065
|
docs: { description: REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION.summary },
|
|
17939
18066
|
schema: [],
|
|
17940
18067
|
messages: {
|
|
17941
|
-
requireUseFormDefaultValues: "
|
|
18068
|
+
requireUseFormDefaultValues: "Provide defaultValues or reactive values to useForm so controlled fields have an explicit initial shape."
|
|
17942
18069
|
}
|
|
17943
18070
|
},
|
|
17944
18071
|
defaultOptions: [],
|
|
@@ -17949,15 +18076,15 @@ var require_use_form_default_values_default = createRule({
|
|
|
17949
18076
|
if (node.source.value !== "react-hook-form") return;
|
|
17950
18077
|
for (const specifier of node.specifiers) {
|
|
17951
18078
|
if (specifier.type !== "ImportSpecifier" || (specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value) !== "useForm") continue;
|
|
17952
|
-
const variable =
|
|
18079
|
+
const variable = ASTUtils29.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
|
|
17953
18080
|
if (variable) importedHooks.add(variable);
|
|
17954
18081
|
}
|
|
17955
18082
|
},
|
|
17956
18083
|
CallExpression(node) {
|
|
17957
18084
|
if (node.callee.type !== "Identifier") return;
|
|
17958
|
-
const variable =
|
|
18085
|
+
const variable = ASTUtils29.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
|
|
17959
18086
|
const options = node.arguments[0];
|
|
17960
|
-
if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" ||
|
|
18087
|
+
if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" || hasInitializationOrUnknownOptions(options)) return;
|
|
17961
18088
|
context.report({ node, messageId: "requireUseFormDefaultValues" });
|
|
17962
18089
|
}
|
|
17963
18090
|
};
|
|
@@ -17970,10 +18097,10 @@ var ACTION_MODULE_RE = /(?:^|\/)app\/.*\/(?:actions|[^/]+-actions)\.[cm]?[jt]s$/
|
|
|
17970
18097
|
var REQUIRE_USE_SERVER_IN_ACTIONS_FILE_DOCUMENTATION = {
|
|
17971
18098
|
summary: "route action module missing the use server directive",
|
|
17972
18099
|
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.",
|
|
17973
|
-
remediation: "
|
|
18100
|
+
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.",
|
|
17974
18101
|
category: "correctness",
|
|
17975
18102
|
limitations: [
|
|
17976
|
-
"Only
|
|
18103
|
+
"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."
|
|
17977
18104
|
],
|
|
17978
18105
|
examples: [
|
|
17979
18106
|
{
|
|
@@ -17998,9 +18125,10 @@ var REQUIRE_USE_SERVER_IN_ACTIONS_FILE_DOCUMENTATION = {
|
|
|
17998
18125
|
};
|
|
17999
18126
|
function isExportedAsyncFunction(node) {
|
|
18000
18127
|
const declaration = node.declaration;
|
|
18001
|
-
|
|
18128
|
+
const unmarked = (fn) => fn.async && !(fn.body?.type === "BlockStatement" && fn.body.body.some((statement) => statement.type === "ExpressionStatement" && statement.directive === "use server"));
|
|
18129
|
+
if (declaration?.type === "FunctionDeclaration") return unmarked(declaration);
|
|
18002
18130
|
return declaration?.type === "VariableDeclaration" && declaration.declarations.some(
|
|
18003
|
-
(item) => item.init?.type === "ArrowFunctionExpression" || item.init?.type === "FunctionExpression" ? item.init
|
|
18131
|
+
(item) => item.init?.type === "ArrowFunctionExpression" || item.init?.type === "FunctionExpression" ? unmarked(item.init) : false
|
|
18004
18132
|
);
|
|
18005
18133
|
}
|
|
18006
18134
|
var require_use_server_in_actions_file_default = createRule({
|
|
@@ -18033,7 +18161,7 @@ var require_use_server_in_actions_file_default = createRule({
|
|
|
18033
18161
|
// src/rules/require-zod-form-validation.ts
|
|
18034
18162
|
import {
|
|
18035
18163
|
AST_NODE_TYPES as AST_NODE_TYPES76,
|
|
18036
|
-
ASTUtils as
|
|
18164
|
+
ASTUtils as ASTUtils30
|
|
18037
18165
|
} from "@typescript-eslint/utils";
|
|
18038
18166
|
var REQUIRE_ZOD_FORM_VALIDATION_DOCUMENTATION = {
|
|
18039
18167
|
summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
|
|
@@ -18101,7 +18229,7 @@ var require_zod_form_validation_default = createRule({
|
|
|
18101
18229
|
return {};
|
|
18102
18230
|
}
|
|
18103
18231
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
18104
|
-
const resolvedBinding = (identifier) =>
|
|
18232
|
+
const resolvedBinding = (identifier) => ASTUtils30.findVariable(
|
|
18105
18233
|
context.sourceCode.getScope(identifier),
|
|
18106
18234
|
identifier.name
|
|
18107
18235
|
);
|
|
@@ -18398,7 +18526,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
18398
18526
|
});
|
|
18399
18527
|
|
|
18400
18528
|
// src/rules/stepdown.ts
|
|
18401
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as
|
|
18529
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils31 } from "@typescript-eslint/utils";
|
|
18402
18530
|
var STEPDOWN_DOCUMENTATION = {
|
|
18403
18531
|
summary: "Place a private helper below its sole direct same-scope caller.",
|
|
18404
18532
|
rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
|
|
@@ -18603,7 +18731,7 @@ function methodName(node) {
|
|
|
18603
18731
|
return !node.computed && node.key.type === AST_NODE_TYPES77.Identifier ? node.key.name : null;
|
|
18604
18732
|
}
|
|
18605
18733
|
function referencedMethod(context, node, classVariables) {
|
|
18606
|
-
const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ?
|
|
18734
|
+
const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils31.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
|
|
18607
18735
|
const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
|
|
18608
18736
|
if (node.object.type !== AST_NODE_TYPES77.ThisExpression && !isClassReference) return null;
|
|
18609
18737
|
if (node.property.type === AST_NODE_TYPES77.PrivateIdentifier) return `#${node.property.name}`;
|
|
@@ -18656,11 +18784,11 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
18656
18784
|
const pinned = /* @__PURE__ */ new Set();
|
|
18657
18785
|
const classVariables = /* @__PURE__ */ new Set();
|
|
18658
18786
|
if (node.id !== null) {
|
|
18659
|
-
const internal =
|
|
18787
|
+
const internal = ASTUtils31.findVariable(context.sourceCode.getScope(node), node.id.name);
|
|
18660
18788
|
if (internal !== null) classVariables.add(internal);
|
|
18661
18789
|
}
|
|
18662
18790
|
if (node.type === AST_NODE_TYPES77.ClassExpression && node.parent.type === AST_NODE_TYPES77.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES77.Identifier) {
|
|
18663
|
-
const outer =
|
|
18791
|
+
const outer = ASTUtils31.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
|
|
18664
18792
|
if (outer !== null) classVariables.add(outer);
|
|
18665
18793
|
}
|
|
18666
18794
|
for (const method of methods) {
|
|
@@ -18696,7 +18824,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
18696
18824
|
return;
|
|
18697
18825
|
}
|
|
18698
18826
|
if (binding.type !== AST_NODE_TYPES77.Identifier) return;
|
|
18699
|
-
const variable =
|
|
18827
|
+
const variable = ASTUtils31.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
18700
18828
|
if (variable !== null) {
|
|
18701
18829
|
methodClassVariables.add(variable);
|
|
18702
18830
|
methodAliases.add(variable);
|
|
@@ -18726,7 +18854,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
18726
18854
|
return;
|
|
18727
18855
|
}
|
|
18728
18856
|
if (!privateNames.has(target)) return;
|
|
18729
|
-
const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ?
|
|
18857
|
+
const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils31.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
|
|
18730
18858
|
if (objectVariable !== null && methodAliases.has(objectVariable)) {
|
|
18731
18859
|
pinned.add(target);
|
|
18732
18860
|
return;
|
|
@@ -18925,14 +19053,14 @@ function staticMemberName7(node) {
|
|
|
18925
19053
|
if (node.computed && node.property.type === AST_NODE_TYPES78.Literal && typeof node.property.value === "string") return node.property.value;
|
|
18926
19054
|
return null;
|
|
18927
19055
|
}
|
|
18928
|
-
function
|
|
18929
|
-
if (node.type === AST_NODE_TYPES78.AwaitExpression) return
|
|
18930
|
-
if (node.type === AST_NODE_TYPES78.ChainExpression) return
|
|
18931
|
-
if (node.type === AST_NODE_TYPES78.TSAsExpression || node.type === AST_NODE_TYPES78.TSNonNullExpression || node.type === AST_NODE_TYPES78.TSTypeAssertion) return
|
|
19056
|
+
function unwrap7(node) {
|
|
19057
|
+
if (node.type === AST_NODE_TYPES78.AwaitExpression) return unwrap7(node.argument);
|
|
19058
|
+
if (node.type === AST_NODE_TYPES78.ChainExpression) return unwrap7(node.expression);
|
|
19059
|
+
if (node.type === AST_NODE_TYPES78.TSAsExpression || node.type === AST_NODE_TYPES78.TSNonNullExpression || node.type === AST_NODE_TYPES78.TSTypeAssertion) return unwrap7(node.expression);
|
|
18932
19060
|
return node;
|
|
18933
19061
|
}
|
|
18934
19062
|
function stringValue(node) {
|
|
18935
|
-
const current =
|
|
19063
|
+
const current = unwrap7(node);
|
|
18936
19064
|
if (current.type === AST_NODE_TYPES78.Literal && typeof current.value === "string") return current.value;
|
|
18937
19065
|
if (current.type === AST_NODE_TYPES78.TemplateLiteral && current.expressions.length === 0) return current.quasis[0]?.value.cooked ?? null;
|
|
18938
19066
|
return null;
|
|
@@ -18941,7 +19069,7 @@ function importSource(node) {
|
|
|
18941
19069
|
return typeof node.source.value === "string" ? node.source.value : null;
|
|
18942
19070
|
}
|
|
18943
19071
|
function requireSource(node) {
|
|
18944
|
-
const current =
|
|
19072
|
+
const current = unwrap7(node);
|
|
18945
19073
|
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;
|
|
18946
19074
|
return stringValue(current.arguments[0]);
|
|
18947
19075
|
}
|
|
@@ -18979,7 +19107,7 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
18979
19107
|
return /* @__PURE__ */ new Set();
|
|
18980
19108
|
};
|
|
18981
19109
|
const sourcePath = (node) => {
|
|
18982
|
-
const current =
|
|
19110
|
+
const current = unwrap7(node);
|
|
18983
19111
|
const value = stringValue(current);
|
|
18984
19112
|
if (value !== null) return sourceSuffixRe.test(value);
|
|
18985
19113
|
if (current.type === AST_NODE_TYPES78.Identifier) return visible("paths", current.name);
|
|
@@ -18994,37 +19122,37 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
18994
19122
|
return false;
|
|
18995
19123
|
};
|
|
18996
19124
|
const rawRead = (node) => {
|
|
18997
|
-
const current =
|
|
19125
|
+
const current = unwrap7(node);
|
|
18998
19126
|
if (current.type !== AST_NODE_TYPES78.CallExpression || current.arguments.length === 0) return false;
|
|
18999
|
-
const callee =
|
|
19127
|
+
const callee = unwrap7(current.callee);
|
|
19000
19128
|
if (callee.type === AST_NODE_TYPES78.Identifier) {
|
|
19001
19129
|
return visible("fsReaders", callee.name) && sourcePath(current.arguments[0]);
|
|
19002
19130
|
}
|
|
19003
19131
|
if (callee.type !== AST_NODE_TYPES78.MemberExpression) return false;
|
|
19004
19132
|
const name2 = staticMemberName7(callee);
|
|
19005
|
-
const object =
|
|
19133
|
+
const object = unwrap7(callee.object);
|
|
19006
19134
|
return name2 !== null && FS_READERS.has(name2) && object.type === AST_NODE_TYPES78.Identifier && visible("fsObjects", object.name) && sourcePath(current.arguments[0]);
|
|
19007
19135
|
};
|
|
19008
19136
|
const rawOrigins = (node) => {
|
|
19009
|
-
const current =
|
|
19137
|
+
const current = unwrap7(node);
|
|
19010
19138
|
if (current.type === AST_NODE_TYPES78.Identifier) return visibleRawOrigins(current.name);
|
|
19011
19139
|
if (rawRead(current)) return /* @__PURE__ */ new Set([`${current.range[0]}:${current.range[1]}`]);
|
|
19012
19140
|
if (current.type === AST_NODE_TYPES78.BinaryExpression && current.operator === "+") return /* @__PURE__ */ new Set([...rawOrigins(current.left), ...rawOrigins(current.right)]);
|
|
19013
19141
|
if (current.type === AST_NODE_TYPES78.MemberExpression && staticMemberName7(current) === "length") return rawOrigins(current.object);
|
|
19014
19142
|
if (current.type !== AST_NODE_TYPES78.CallExpression) return /* @__PURE__ */ new Set();
|
|
19015
|
-
const callee =
|
|
19143
|
+
const callee = unwrap7(current.callee);
|
|
19016
19144
|
if (callee.type !== AST_NODE_TYPES78.MemberExpression) return /* @__PURE__ */ new Set();
|
|
19017
19145
|
const name2 = staticMemberName7(callee);
|
|
19018
19146
|
return name2 !== null && TEXT_TRANSFORMS.has(name2) ? rawOrigins(callee.object) : /* @__PURE__ */ new Set();
|
|
19019
19147
|
};
|
|
19020
19148
|
const evidenceOrigins = (node) => {
|
|
19021
|
-
const current =
|
|
19149
|
+
const current = unwrap7(node);
|
|
19022
19150
|
const direct = rawOrigins(current);
|
|
19023
19151
|
if (direct.size > 0) return direct;
|
|
19024
19152
|
if (current.type === AST_NODE_TYPES78.BinaryExpression || current.type === AST_NODE_TYPES78.LogicalExpression) return /* @__PURE__ */ new Set([...evidenceOrigins(current.left), ...evidenceOrigins(current.right)]);
|
|
19025
19153
|
if (current.type === AST_NODE_TYPES78.UnaryExpression) return evidenceOrigins(current.argument);
|
|
19026
19154
|
if (current.type !== AST_NODE_TYPES78.CallExpression) return /* @__PURE__ */ new Set();
|
|
19027
|
-
const callee =
|
|
19155
|
+
const callee = unwrap7(current.callee);
|
|
19028
19156
|
if (callee.type !== AST_NODE_TYPES78.MemberExpression) return /* @__PURE__ */ new Set();
|
|
19029
19157
|
const name2 = staticMemberName7(callee);
|
|
19030
19158
|
if (name2 !== null && TEXT_PREDICATES.has(name2)) return rawOrigins(callee.object);
|
|
@@ -19032,15 +19160,15 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
19032
19160
|
return /* @__PURE__ */ new Set();
|
|
19033
19161
|
};
|
|
19034
19162
|
const rawAssertionOrigins = (node) => {
|
|
19035
|
-
const callee =
|
|
19163
|
+
const callee = unwrap7(node.callee);
|
|
19036
19164
|
if (callee.type === AST_NODE_TYPES78.Identifier && callee.name === "assert") {
|
|
19037
19165
|
return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
19038
19166
|
}
|
|
19039
19167
|
if (callee.type !== AST_NODE_TYPES78.MemberExpression) return /* @__PURE__ */ new Set();
|
|
19040
19168
|
const matcher = staticMemberName7(callee);
|
|
19041
19169
|
if (matcher === null) return /* @__PURE__ */ new Set();
|
|
19042
|
-
let receiver =
|
|
19043
|
-
while (receiver.type === AST_NODE_TYPES78.MemberExpression && EXPECT_MODIFIERS2.has(staticMemberName7(receiver) ?? "")) receiver =
|
|
19170
|
+
let receiver = unwrap7(callee.object);
|
|
19171
|
+
while (receiver.type === AST_NODE_TYPES78.MemberExpression && EXPECT_MODIFIERS2.has(staticMemberName7(receiver) ?? "")) receiver = unwrap7(receiver.object);
|
|
19044
19172
|
if (receiver.type === AST_NODE_TYPES78.CallExpression && receiver.callee.type === AST_NODE_TYPES78.Identifier && receiver.callee.name === "expect") {
|
|
19045
19173
|
if (!EXPECT_MATCHERS.has(matcher)) return /* @__PURE__ */ new Set();
|
|
19046
19174
|
return new Set([...receiver.arguments, ...node.arguments].flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
@@ -19049,7 +19177,7 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
19049
19177
|
return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
19050
19178
|
};
|
|
19051
19179
|
const rawRegexExtractionOrigins = (node) => {
|
|
19052
|
-
const callee =
|
|
19180
|
+
const callee = unwrap7(node.callee);
|
|
19053
19181
|
if (callee.type !== AST_NODE_TYPES78.MemberExpression || staticMemberName7(callee) !== "matchAll" || node.arguments.length !== 1) return /* @__PURE__ */ new Set();
|
|
19054
19182
|
const argument = node.arguments[0];
|
|
19055
19183
|
if (argument?.type !== AST_NODE_TYPES78.Literal || !(argument.value instanceof RegExp)) return /* @__PURE__ */ new Set();
|
|
@@ -19072,11 +19200,11 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
19072
19200
|
}
|
|
19073
19201
|
};
|
|
19074
19202
|
const sourceCollection = (node) => {
|
|
19075
|
-
const current =
|
|
19203
|
+
const current = unwrap7(node);
|
|
19076
19204
|
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));
|
|
19077
19205
|
};
|
|
19078
19206
|
const declaredNames2 = (node) => {
|
|
19079
|
-
const current =
|
|
19207
|
+
const current = unwrap7(node);
|
|
19080
19208
|
if (current.type === AST_NODE_TYPES78.Identifier) return [current.name];
|
|
19081
19209
|
if (current.type === AST_NODE_TYPES78.AssignmentPattern) return declaredNames2(current.left);
|
|
19082
19210
|
if (current.type === AST_NODE_TYPES78.RestElement) return declaredNames2(current.argument);
|
|
@@ -19128,7 +19256,7 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
|
19128
19256
|
if (node.left.type === AST_NODE_TYPES78.Identifier) declare(node.left.name, { path: sourcePath(node.right), rawOrigins: rawOrigins(node.right) });
|
|
19129
19257
|
},
|
|
19130
19258
|
ForOfStatement(node) {
|
|
19131
|
-
const right =
|
|
19259
|
+
const right = unwrap7(node.right);
|
|
19132
19260
|
const collection = right.type === AST_NODE_TYPES78.Identifier && visible("collections", right.name);
|
|
19133
19261
|
const left = node.left.type === AST_NODE_TYPES78.VariableDeclaration ? node.left.declarations[0]?.id : node.left;
|
|
19134
19262
|
if (collection && left?.type === AST_NODE_TYPES78.Identifier) declare(left.name, { path: true });
|
|
@@ -19161,7 +19289,8 @@ var SOLE_EXPORT_MATCHES_FILENAME_DOCUMENTATION = {
|
|
|
19161
19289
|
category: "maintainability",
|
|
19162
19290
|
limitations: [
|
|
19163
19291
|
"Framework entrypoints, generic stems covered by no-generic-single-export-module, tests, generated files, anonymous defaults, CommonJS, and re-exports are excluded.",
|
|
19164
|
-
"The rule compares the primary filename stem and preserves conventional suffixes such as .server or .worker."
|
|
19292
|
+
"The rule compares the primary filename stem and preserves a single private underscore prefix and conventional suffixes such as .server or .worker.",
|
|
19293
|
+
"Exported destructuring patterns are excluded rather than undercounted as public exports."
|
|
19165
19294
|
],
|
|
19166
19295
|
examples: [
|
|
19167
19296
|
{ 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 },
|
|
@@ -19217,7 +19346,7 @@ var sole_export_matches_filename_default = createRule({
|
|
|
19217
19346
|
create(context) {
|
|
19218
19347
|
const fileStem = stem3(context.filename);
|
|
19219
19348
|
const normalizedFilename = context.filename.replaceAll("\\", "/");
|
|
19220
|
-
if (EXCLUDED_STEMS.has(fileStem) || normalizedFilename.includes("/pages/") || context.filename.endsWith(".d.ts") || isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
|
|
19349
|
+
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 {};
|
|
19221
19350
|
return {
|
|
19222
19351
|
"Program:exit"(program) {
|
|
19223
19352
|
const exports = [];
|
|
@@ -19236,6 +19365,7 @@ var sole_export_matches_filename_default = createRule({
|
|
|
19236
19365
|
publicExports.add(declaration.id.name);
|
|
19237
19366
|
}
|
|
19238
19367
|
if (declaration?.type === AST_NODE_TYPES79.VariableDeclaration) {
|
|
19368
|
+
if (declaration.declarations.some((item) => item.id.type !== AST_NODE_TYPES79.Identifier)) return;
|
|
19239
19369
|
for (const item of declaration.declarations) {
|
|
19240
19370
|
if (item.id.type === AST_NODE_TYPES79.Identifier) publicExports.add(item.id.name);
|
|
19241
19371
|
}
|
|
@@ -19258,8 +19388,12 @@ var sole_export_matches_filename_default = createRule({
|
|
|
19258
19388
|
if (unique.size !== 1 || publicExports.size !== 1) return;
|
|
19259
19389
|
const only = [...unique.values()][0];
|
|
19260
19390
|
if (only === void 0) return;
|
|
19261
|
-
|
|
19262
|
-
if (
|
|
19391
|
+
if (only.name === "onRouterTransitionStart" && /(?:^|\/)instrumentation-client\.[jt]s$/u.test(normalizedFilename)) return;
|
|
19392
|
+
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;
|
|
19393
|
+
const exportedStem = kebabCase(only.name);
|
|
19394
|
+
if (exportedStem === "") return;
|
|
19395
|
+
const expected = `${fileStem.startsWith("_") ? "_" : ""}${exportedStem}`;
|
|
19396
|
+
if (expected === fileStem.toLowerCase()) return;
|
|
19263
19397
|
context.report({ node: only.node, messageId: "matchSoleExport", data: { exported: only.name, expected } });
|
|
19264
19398
|
}
|
|
19265
19399
|
};
|
|
@@ -19307,7 +19441,7 @@ var iac_source_coupled_test_default = createSourceCoupledRule(
|
|
|
19307
19441
|
// src/rules/require-pascal-case-zod-schema-name.ts
|
|
19308
19442
|
import {
|
|
19309
19443
|
AST_NODE_TYPES as AST_NODE_TYPES80,
|
|
19310
|
-
ASTUtils as
|
|
19444
|
+
ASTUtils as ASTUtils32
|
|
19311
19445
|
} from "@typescript-eslint/utils";
|
|
19312
19446
|
var REQUIRE_PASCAL_CASE_ZOD_SCHEMA_NAME_DOCUMENTATION = {
|
|
19313
19447
|
summary: "Require confirmed module-level Zod schema contracts to use PascalCase with a `Schema` suffix.",
|
|
@@ -19508,7 +19642,7 @@ var require_pascal_case_zod_schema_name_default = createRule({
|
|
|
19508
19642
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
19509
19643
|
const schemaBindings = /* @__PURE__ */ new Set();
|
|
19510
19644
|
function resolvedBinding(identifier) {
|
|
19511
|
-
return
|
|
19645
|
+
return ASTUtils32.findVariable(
|
|
19512
19646
|
context.sourceCode.getScope(identifier),
|
|
19513
19647
|
identifier.name
|
|
19514
19648
|
);
|
|
@@ -19753,7 +19887,7 @@ var RULES = {
|
|
|
19753
19887
|
};
|
|
19754
19888
|
var meta = {
|
|
19755
19889
|
name: "@sarj/eslint-plugin",
|
|
19756
|
-
version: "15.17.
|
|
19890
|
+
version: "15.17.9"
|
|
19757
19891
|
};
|
|
19758
19892
|
var APPLICATION_ONLY_RULES = [];
|
|
19759
19893
|
var LIBRARY_IMPORT_POLICY = ["error", {
|
|
@@ -19775,6 +19909,7 @@ var ADVISORY_RULES = [
|
|
|
19775
19909
|
"@sarj/prefer-nullish-filter-predicate",
|
|
19776
19910
|
"@sarj/prefer-shared-zod-enum",
|
|
19777
19911
|
"@sarj/prefer-switch-for-repeated-equality",
|
|
19912
|
+
"@sarj/prefer-whole-object-assertion",
|
|
19778
19913
|
"@sarj/require-interface-for-exported-class",
|
|
19779
19914
|
"@sarj/require-sql-access-class",
|
|
19780
19915
|
"@sarj/sole-export-matches-filename"
|
|
@@ -19853,7 +19988,7 @@ var RECOMMENDED_RULES = {
|
|
|
19853
19988
|
"@sarj/prefer-switch-for-repeated-equality": "warn",
|
|
19854
19989
|
"@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
|
|
19855
19990
|
"@sarj/prefer-server-actions": "error",
|
|
19856
|
-
"@sarj/prefer-whole-object-assertion": "
|
|
19991
|
+
"@sarj/prefer-whole-object-assertion": "warn",
|
|
19857
19992
|
"@sarj/repeated-static-call-cases": "error",
|
|
19858
19993
|
"@sarj/prefer-zod-infer": "error",
|
|
19859
19994
|
"@sarj/require-assert-never": "error",
|
|
@@ -19950,7 +20085,7 @@ var STRICT_RULES = {
|
|
|
19950
20085
|
"@sarj/prefer-switch-for-repeated-equality": "warn",
|
|
19951
20086
|
"@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
|
|
19952
20087
|
"@sarj/prefer-server-actions": "error",
|
|
19953
|
-
"@sarj/prefer-whole-object-assertion": "
|
|
20088
|
+
"@sarj/prefer-whole-object-assertion": "warn",
|
|
19954
20089
|
"@sarj/repeated-static-call-cases": "error",
|
|
19955
20090
|
"@sarj/prefer-zod-infer": "error",
|
|
19956
20091
|
"@sarj/require-assert-never": "error",
|