@sarj/eslint-plugin 19.17.0 → 19.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog.js +59 -3
- package/dist/{chunk-4USNCXB3.js → chunk-UI4ADSAA.js} +224 -55
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/catalog.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
publicDocumentation,
|
|
3
3
|
ruleSpec,
|
|
4
4
|
src_default
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UI4ADSAA.js";
|
|
6
6
|
|
|
7
7
|
// src/docs/directive-disable-enable-pair.ts
|
|
8
8
|
var DIRECTIVE_DISABLE_ENABLE_PAIR_DOCUMENTATION = {
|
|
@@ -5118,11 +5118,13 @@ var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
|
|
|
5118
5118
|
summary: "Preserve producer-proven tag/payload and Boolean state relationships in return types.",
|
|
5119
5119
|
defaultLevel: "warning",
|
|
5120
5120
|
rationale: "A flat object with optional branch data loses the relationship between a tag and its payload. Callers cannot narrow to the required fields, and the type permits incomplete or contradictory combinations.",
|
|
5121
|
-
remediation: "If the variants are mutually exclusive, represent each with a literal tag and its required payload.
|
|
5121
|
+
remediation: "If the variants are mutually exclusive, represent each with a literal tag and its required payload. For predicates over one current state, consider returning that authoritative state instead of storing multiple derived flags. Preserve every valid state, independent dimension, shared field, payload value type and nullability. For a producer returning a subset of a broader wire contract, give the producer a precise domain return type after validation.",
|
|
5122
5122
|
category: "correctness",
|
|
5123
5123
|
autofix: "safe",
|
|
5124
5124
|
limitations: [
|
|
5125
|
-
"Names, Boolean counts and optional-field counts never establish a finding. A producer's literal writes establish either tag-specific payload presence or a pair of required Boolean fields with two or three distinct combinations, with both fields varying. Boolean members may use boolean, true | false or one unmerged nongeneric local alias of either. All four combinations, constant dimensions
|
|
5125
|
+
"Names, Boolean counts and optional-field counts never establish a finding. A producer's literal writes establish either tag-specific payload presence or a pair of required Boolean fields with two or three distinct combinations, with both fields varying. Boolean members may use boolean, true | false or one unmerged nongeneric local alias of either. All four combinations, constant dimensions and optional/unknown dimensions are excluded. Expression-valued flags require the separate typed evidence described below. At most eight Boolean fields are compared. Only one relationship is reported per contract; existing literal/payload evidence takes precedence. Independently nullable payloads do not erase Boolean evidence.",
|
|
5126
|
+
"With type services enabled, a synchronous nongeneric producer with exactly one own return, a final direct object return and a supported flat local contract also exposes strict equality predicates over the same earlier local const binding. Native types at both reads must be finite domains of at most sixteen string/number literals containing both distinct singleton comparison values. Literal constants and direct named-import/local enum members are supported; enum declarations and members are resolved by symbol identity and raw runtime literal values, including renamed imports and reexports. At most 128 candidate pairs per file are analyzed; exceeding the bound abstains from all typed findings in that file. No automatic fix is offered for these projections.",
|
|
5127
|
+
"The const snapshot may be destructured or copied from another primitive binding, passed to callbacks, or read by closures without losing its value. Mutable bindings, parameters and module captures, property reads/getters, open/nullable/object/symbol domains, async producers, multiple own returns, generic/inherited/imported contracts and dynamic eval abstain. Nested callback returns do not belong to the producer. Enum namespace writes, aliases, escapes, computed accesses, calls and merged declarations in the analyzed file abstain. Cross-file enum declarations can supply type/constant identity; external mutation, dishonest declarations/assertions and runtime monkeypatching are not modeled. The analysis proves a relationship under those type and mutation assumptions, not domain intent or a whole-program ownership guarantee.",
|
|
5126
5128
|
"Producer analysis requires a finite literal tag, every declared tag syntactically witnessed, exhaustive simple return paths without literal constant conditions, plain object returns, and consistent optional-field presence for each tag. It resolves only unmerged local aliases/interfaces without inheritance or generics, one local alias of a literal tag domain, and the built-in Promise around async returns. Callable or opaque own then properties abstain for async producers because Promise assimilation can replace the fulfilled value. Inherited then methods and external prototype mutation are not modeled. This is syntax-only evidence of field presence; explicit undefined, void and null payloads do not establish payload requirements, while expression value types are not inferred. Type-only assertion/satisfies/non-null wrappers, literal computed string keys and a sole-use const immediately before its return are recognized. Const alias chains, observation, mutation and dynamic eval abstain. Dynamic returns, spreads, dynamic computed keys, accessors, loops and try statements are deliberately skipped.",
|
|
5127
5129
|
"A switch producer is supported only for a non-overloaded function declaration when its sole body statement switches on an unchanged direct string or number parameter, has unique same-kind literal labels and a default, and every nonempty case body is a sole object return or throw, optionally inside sole blocks. Empty labels may share the next terminating body; nested conditions, conditional returns, statement prefixes, breaks, other fallthrough, captured/closed inputs, dynamic eval and switches under preceding control flow abstain. Broad parameter domains establish input witnesses for each label and the default; this does not infer branch-specific runtime effects or expression value types. Path visits, depth and returned objects are bounded conservatively.",
|
|
5128
5130
|
"These are refactoring advisories, not proven defects or proof that other writers cannot produce more states. Historical observations, partial results, unknown states and independently completing operations may need the broader contract. Review all readers and writers before migrating a shared contract; preserve every valid combination. A union of existing literal flag pairs is the smallest type-only replacement; a named disposition is a reviewed domain migration.",
|
|
@@ -5138,6 +5140,8 @@ var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
|
|
|
5138
5140
|
{ id: "explicit-produced-variants", scenarioId: "produced-variants", title: "Give each produced variant its payload", outcome: "no-match", files: [{ path: "src/job.ts", source: 'function job(done: boolean): { phase: "queued"; ticket: string } | { phase: "complete" } { return done ? { phase: "complete" } : { phase: "queued", ticket: "t" }; }' }], focusPath: "src/job.ts", expectedCount: 0, public: true },
|
|
5139
5141
|
{ id: "flat-switch-producer", scenarioId: "switch-producer", title: "A complete dispatcher preserves each returned variant", outcome: "match", files: [{ path: "src/job.ts", source: 'function job(command: string): { phase: "queued" | "complete"; ticket?: string } { switch (command) { case "start": return { phase: "queued", ticket: "t" }; default: return { phase: "complete" }; } }' }], focusPath: "src/job.ts", expectedCount: 1, public: true },
|
|
5140
5142
|
{ id: "explicit-switch-variants", scenarioId: "switch-producer", title: "An explicit dispatcher result supports payload narrowing", outcome: "no-match", files: [{ path: "src/job.ts", source: 'function job(command: string): { phase: "queued"; ticket: string } | { phase: "complete" } { switch (command) { case "start": return { phase: "queued", ticket: "t" }; default: return { phase: "complete" }; } }' }], focusPath: "src/job.ts", expectedCount: 0, public: true },
|
|
5143
|
+
{ id: "immutable-state-projections", scenarioId: "state-projection", title: "Two predicates over one immutable phase cannot both be true", outcome: "match", files: [{ path: "src/projection.ts", source: 'type Phase = "ready" | "waiting" | "unknown"; declare function snapshot(): Phase; function state(): { ready: boolean; waiting: boolean } { const phase = snapshot(); return { ready: phase === "ready", waiting: phase === "waiting" }; }' }], focusPath: "src/projection.ts", expectedCount: 1, public: true },
|
|
5144
|
+
{ id: "authoritative-state", scenarioId: "state-projection", title: "Return the phase while preserving the independent timeout dimension", outcome: "no-match", files: [{ path: "src/projection.ts", source: 'type Phase = "ready" | "waiting" | "reconnecting" | "unknown"; declare function snapshot(): Phase; function state(timeout: boolean): { phase: Phase; timeout: boolean } { const phase = snapshot(); return { phase, timeout }; }' }], focusPath: "src/projection.ts", expectedCount: 0, public: true },
|
|
5141
5145
|
{
|
|
5142
5146
|
id: "safe-autofix",
|
|
5143
5147
|
title: "Preserve an unused private producer's exact existing variants",
|
|
@@ -6437,6 +6441,57 @@ var PRODUCTION_DERIVED_TEST_CASES_DOCUMENTATION = {
|
|
|
6437
6441
|
]
|
|
6438
6442
|
};
|
|
6439
6443
|
|
|
6444
|
+
// src/docs/react-prefer-updater-function.ts
|
|
6445
|
+
var CART = "import { useState } from 'react'; export function Cart({ load }: { load: () => Promise<string> }) { const [items, setItems] = useState<string[]>([]); const add = async () => { const item = await load(); ";
|
|
6446
|
+
var CART_END = ' }; return <button type="button" onClick={add}>{items.length}</button>; }';
|
|
6447
|
+
var RENDER_PHASE = "import { useState } from 'react'; export function Panel({ request }: { request: boolean }) { const [dialog, setDialog] = useState({ open: request, request }); if (dialog.request !== request) { setDialog({ open: request || dialog.open, request }); } return <p>{String(dialog.open)}</p>; }";
|
|
6448
|
+
var REACT_PREFER_UPDATER_FUNCTION_DOCUMENTATION = {
|
|
6449
|
+
defaultLevel: "warning",
|
|
6450
|
+
summary: "Use the updater form when the next React state is built from the current state.",
|
|
6451
|
+
rationale: "A state variable holds the snapshot from the render that created the callback. After an await, in a timer, promise callback, subscription or effect, or when two updates run before React re-renders, that snapshot is stale. `setItems([...items, item])` then drops whatever another update added in the meantime. An updater receives the latest queued state, so each update builds on the one before it.",
|
|
6452
|
+
remediation: "Pass an updater instead. Rewrite `setItems([...items, item])` as `setItems((previous) => [...previous, item])`, and read `previous` wherever the argument read the state or a local derived from it. Compute IDs, timestamps and other impure values before the call, because React runs updaters twice in Strict Mode development builds.",
|
|
6453
|
+
category: "correctness",
|
|
6454
|
+
limitations: [
|
|
6455
|
+
"The rule is advisory and has no automatic fix. In a synchronous event handler that updates once, the snapshot is still current, so the rewrite keeps today's behavior and protects the handler against a later await or a second update.",
|
|
6456
|
+
"The rule pairs a setter with its state through one array destructure of a React useState call, including renamed, default and namespace React imports, and resolves every read by scope. It ignores setters reached through props, aliases or custom hooks that return state tuples. It also ignores object-destructured state, useReducer dispatch and reassigned bindings.",
|
|
6457
|
+
"The rule reports only state reads that the setter argument itself evaluates. It ignores reads inside nested functions, type-only reads, a bare identity write and next values first stored in a local. It also ignores setter calls made directly in the function that declares the state, because they run during render, where the state belongs to the render in progress."
|
|
6458
|
+
],
|
|
6459
|
+
references: [
|
|
6460
|
+
"https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state",
|
|
6461
|
+
"https://react.dev/learn/queueing-a-series-of-state-updates",
|
|
6462
|
+
"https://bun.com/docs/lint/rules/bun/react-prefer-updater-function"
|
|
6463
|
+
],
|
|
6464
|
+
examples: [
|
|
6465
|
+
{
|
|
6466
|
+
id: "snapshot-after-await",
|
|
6467
|
+
title: "Append to the render snapshot after an await",
|
|
6468
|
+
outcome: "match",
|
|
6469
|
+
files: [{ path: "src/cart.tsx", source: `${CART}setItems([...items, item]);${CART_END}` }],
|
|
6470
|
+
focusPath: "src/cart.tsx",
|
|
6471
|
+
expectedCount: 1,
|
|
6472
|
+
public: true
|
|
6473
|
+
},
|
|
6474
|
+
{
|
|
6475
|
+
id: "updater-after-await",
|
|
6476
|
+
title: "Append to the latest state through an updater",
|
|
6477
|
+
outcome: "no-match",
|
|
6478
|
+
files: [{ path: "src/cart.tsx", source: `${CART}setItems((previous) => [...previous, item]);${CART_END}` }],
|
|
6479
|
+
focusPath: "src/cart.tsx",
|
|
6480
|
+
expectedCount: 0,
|
|
6481
|
+
public: true
|
|
6482
|
+
},
|
|
6483
|
+
{
|
|
6484
|
+
id: "render-phase-update",
|
|
6485
|
+
title: "Adjust state from the render in progress",
|
|
6486
|
+
outcome: "no-match",
|
|
6487
|
+
files: [{ path: "src/panel.tsx", source: RENDER_PHASE }],
|
|
6488
|
+
focusPath: "src/panel.tsx",
|
|
6489
|
+
expectedCount: 0,
|
|
6490
|
+
public: false
|
|
6491
|
+
}
|
|
6492
|
+
]
|
|
6493
|
+
};
|
|
6494
|
+
|
|
6440
6495
|
// src/docs/repeated-static-call-cases.ts
|
|
6441
6496
|
var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
|
|
6442
6497
|
summary: "Review three or more consecutive static-input call assertions as potential named cases.",
|
|
@@ -7574,6 +7629,7 @@ var RULE_DOCUMENTATION = {
|
|
|
7574
7629
|
"prefer-zod-infer": PREFER_ZOD_INFER_DOCUMENTATION,
|
|
7575
7630
|
"prefer-zod-parse-output-type": PREFER_ZOD_PARSE_OUTPUT_TYPE_DOCUMENTATION,
|
|
7576
7631
|
"production-derived-test-cases": PRODUCTION_DERIVED_TEST_CASES_DOCUMENTATION,
|
|
7632
|
+
"react-prefer-updater-function": REACT_PREFER_UPDATER_FUNCTION_DOCUMENTATION,
|
|
7577
7633
|
"repeated-static-call-cases": REPEATED_STATIC_CALL_CASES_DOCUMENTATION,
|
|
7578
7634
|
"require-assert-never": REQUIRE_ASSERT_NEVER_DOCUMENTATION,
|
|
7579
7635
|
"require-button-accessible-name": REQUIRE_BUTTON_ACCESSIBLE_NAME_DOCUMENTATION,
|
|
@@ -16467,6 +16467,9 @@ var VIEWS = /* @__PURE__ */ new WeakMap();
|
|
|
16467
16467
|
var DIRECT_EVAL = /* @__PURE__ */ new WeakMap();
|
|
16468
16468
|
var REACT_IMPORTS = /* @__PURE__ */ new WeakMap();
|
|
16469
16469
|
var PARAMETER_BINDINGS = /* @__PURE__ */ new WeakMap();
|
|
16470
|
+
function importsReactValue(source) {
|
|
16471
|
+
return source.ast.body.some((statement) => statement.type === "ImportDeclaration" && statement.source.value === "react" && statement.importKind !== "type");
|
|
16472
|
+
}
|
|
16470
16473
|
function renderFunction(node) {
|
|
16471
16474
|
let parent = parentOf(node);
|
|
16472
16475
|
while (parent !== null) {
|
|
@@ -16644,7 +16647,7 @@ var no_readonly_prop_state_default = defineRule({
|
|
|
16644
16647
|
tsrs = null;
|
|
16645
16648
|
pending = [];
|
|
16646
16649
|
const source = context.sourceCode;
|
|
16647
|
-
if (isGeneratedFile({ filename: context.filename, cwd: context.cwd }, source.text) || isTestFile({ filename: context.filename, cwd: context.cwd }) || !
|
|
16650
|
+
if (isGeneratedFile({ filename: context.filename, cwd: context.cwd }, source.text) || isTestFile({ filename: context.filename, cwd: context.cwd }) || !importsReactValue(source)) return false;
|
|
16648
16651
|
tsrs = tsrsServices(context);
|
|
16649
16652
|
return true;
|
|
16650
16653
|
},
|
|
@@ -23411,6 +23414,94 @@ function literalTag(node) {
|
|
|
23411
23414
|
return typeof node.value === "string" || typeof node.value === "number" || typeof node.value === "boolean" ? node.value : void 0;
|
|
23412
23415
|
}
|
|
23413
23416
|
|
|
23417
|
+
// src/rules/_discriminated-union-projections.ts
|
|
23418
|
+
function projectedBooleanPairs(owner, returned2, source) {
|
|
23419
|
+
if (owner.async || owner.generator || isPresent(owner.typeParameters) || owner.body?.type !== "BlockStatement" || parentOf(returned2) !== owner.body || owner.body.body.at(-1) !== returned2 || returned2.argument?.type !== "ObjectExpression") return [];
|
|
23420
|
+
const contract = returnContract(owner, source);
|
|
23421
|
+
const shape = contract === void 0 ? void 0 : objectContract(contract.members);
|
|
23422
|
+
if (shape === void 0 || contract === void 0) return [];
|
|
23423
|
+
const properties2 = returnedProperties([returned2.argument], shape)?.[0];
|
|
23424
|
+
if (properties2 === void 0) return [];
|
|
23425
|
+
const fields2 = [...shape.members].filter(([, member3]) => !member3.optional && booleanType(member3.typeAnnotation?.typeAnnotation, source));
|
|
23426
|
+
if (fields2.length > 8) return [];
|
|
23427
|
+
const comparisons = [];
|
|
23428
|
+
for (const [field] of fields2) {
|
|
23429
|
+
const value = properties2.get(field);
|
|
23430
|
+
if (value?.type !== "BinaryExpression" || value.operator !== "===" || value.left.type !== "Identifier" || !constantSyntax(value.right, source)) continue;
|
|
23431
|
+
const binding3 = localSnapshot(value.left, owner, source);
|
|
23432
|
+
if (binding3 !== null) comparisons.push({ field, binding: binding3, left: value.left, right: value.right });
|
|
23433
|
+
}
|
|
23434
|
+
return comparisons.flatMap((left, index) => comparisons.slice(index + 1).filter((right) => right.binding === left.binding).map((right) => ({ reportNode: contract.reportNode, left, right })));
|
|
23435
|
+
}
|
|
23436
|
+
function localSnapshot(identifier, owner, source) {
|
|
23437
|
+
const variable = findVariable(source.getScope(identifier), identifier.name);
|
|
23438
|
+
const declaration = variable?.defs[0]?.node;
|
|
23439
|
+
if (variable === null || variable.defs.length !== 1 || declaration?.type !== "VariableDeclarator" || !isConstDeclaration(parentOf(declaration)) || !isPresent(declaration.init) || enclosingFunction(declaration) !== owner || declaration.range[1] >= identifier.range[0] || !stableValueBinding(variable)) return null;
|
|
23440
|
+
return variable;
|
|
23441
|
+
}
|
|
23442
|
+
function constantSyntax(node, source) {
|
|
23443
|
+
if (node.type === "Literal") return typeof node.value === "string" || typeof node.value === "number" && Number.isFinite(node.value);
|
|
23444
|
+
if (node.type !== "MemberExpression" || node.computed || node.optional || node.object.type !== "Identifier" || node.property.type !== "Identifier") return false;
|
|
23445
|
+
const variable = findVariable(source.getScope(node.object), node.object.name);
|
|
23446
|
+
const definition = variable?.defs[0]?.node;
|
|
23447
|
+
if (variable === null || variable.defs.length !== 1 || definition?.type !== "TSEnumDeclaration" && definition?.type !== "ImportSpecifier" || !stableValueBinding(variable)) return false;
|
|
23448
|
+
return variable.references.every((reference) => {
|
|
23449
|
+
if (typeOnlyReference(reference)) return true;
|
|
23450
|
+
const member3 = parentOf(reference.identifier);
|
|
23451
|
+
if (member3?.type !== "MemberExpression" || member3.object !== reference.identifier || member3.computed || member3.optional || writtenMember(member3)) return false;
|
|
23452
|
+
const use = parentOf(member3);
|
|
23453
|
+
return !((use?.type === "CallExpression" || use?.type === "NewExpression") && use.callee === member3);
|
|
23454
|
+
});
|
|
23455
|
+
}
|
|
23456
|
+
function enumConstant(node, native) {
|
|
23457
|
+
if (node.type !== "MemberExpression") return false;
|
|
23458
|
+
const root = native.node(node.object);
|
|
23459
|
+
const member3 = native.node(node.property);
|
|
23460
|
+
let symbol = root === void 0 ? void 0 : native.checker.getSymbolAtLocation(root);
|
|
23461
|
+
if (symbol === void 0 || member3 === void 0) return false;
|
|
23462
|
+
if ((symbol.flags & native.sync.SymbolFlags.Alias) !== 0) symbol = native.checker.getAliasedSymbol(symbol);
|
|
23463
|
+
const declarations = symbol.declarations;
|
|
23464
|
+
if (declarations.length !== 1 || declarations[0]?.kind !== native.ast.SyntaxKind.EnumDeclaration) return false;
|
|
23465
|
+
const selected = native.checker.getSymbolAtLocation(member3)?.declarations;
|
|
23466
|
+
return selected?.length === 1 && selected[0]?.kind === native.ast.SyntaxKind.EnumMember;
|
|
23467
|
+
}
|
|
23468
|
+
function projectedBooleanEvidence(file, candidates2) {
|
|
23469
|
+
const nodes = candidates2.flatMap(({ left, right }) => [left.left, right.left, left.right, right.right]);
|
|
23470
|
+
const types = file.typesOf(nodes);
|
|
23471
|
+
const unions = types.flatMap((type) => type !== void 0 && type.isUnionType() ? [type] : []);
|
|
23472
|
+
const lists = file.batch(unions.map((type) => type.getTypes.gen()));
|
|
23473
|
+
let index = 0;
|
|
23474
|
+
const domains = types.map((type) => type === void 0 ? void 0 : primitiveValues(type.isUnionType() ? lists[index++] ?? [] : [type], file));
|
|
23475
|
+
const native = nativeTypes(file);
|
|
23476
|
+
const constants = /* @__PURE__ */ new Map();
|
|
23477
|
+
function constant(node, value) {
|
|
23478
|
+
if (node.type === "Literal") return node.value === value;
|
|
23479
|
+
let valid = constants.get(node);
|
|
23480
|
+
if (valid === void 0) {
|
|
23481
|
+
valid = enumConstant(node, native);
|
|
23482
|
+
constants.set(node, valid);
|
|
23483
|
+
}
|
|
23484
|
+
return valid;
|
|
23485
|
+
}
|
|
23486
|
+
return candidates2.map(({ left, right }, ordinal) => {
|
|
23487
|
+
const [a, b, x, y] = domains.slice(ordinal * 4, ordinal * 4 + 4);
|
|
23488
|
+
const first = x?.[0];
|
|
23489
|
+
const second = y?.[0];
|
|
23490
|
+
return a !== void 0 && b !== void 0 && x?.length === 1 && y?.length === 1 && first !== void 0 && second !== void 0 && first !== second && constant(left.right, first) && constant(right.right, second) && a.includes(first) && a.includes(second) && b.includes(first) && b.includes(second);
|
|
23491
|
+
});
|
|
23492
|
+
}
|
|
23493
|
+
function primitiveValues(types, file) {
|
|
23494
|
+
if (types.length < 1 || types.length > 16) return void 0;
|
|
23495
|
+
const values = [];
|
|
23496
|
+
for (const type of types) {
|
|
23497
|
+
if ((type.flags & (file.flags.StringLiteral | file.flags.NumberLiteral)) === 0) return void 0;
|
|
23498
|
+
const value = type.value;
|
|
23499
|
+
if (typeof value !== "string" && !(typeof value === "number" && Number.isFinite(value))) return void 0;
|
|
23500
|
+
values.push(value);
|
|
23501
|
+
}
|
|
23502
|
+
return values;
|
|
23503
|
+
}
|
|
23504
|
+
|
|
23414
23505
|
// src/rules/prefer-discriminated-union.ts
|
|
23415
23506
|
var NAME99 = "prefer-discriminated-union";
|
|
23416
23507
|
var prefer_discriminated_union_default = defineRule({
|
|
@@ -23424,12 +23515,14 @@ var prefer_discriminated_union_default = defineRule({
|
|
|
23424
23515
|
schema: [],
|
|
23425
23516
|
messages: {
|
|
23426
23517
|
preferProducedVariants: "This producer returns tag-specific payloads for `{{tag}}`, but its flat return contract makes them optional. If these variants define the domain, use a discriminated union with each variant's required payload, or give this producer a precise return type separate from the broader contract.",
|
|
23427
|
-
preferProducedState: "This producer returns a restricted set of literal combinations for `{{fields}}`, while its Boolean fields permit all four. Review a union preserving every returned combination, or a typed disposition if these are domain phases. Other writers and readers of a shared contract require review."
|
|
23518
|
+
preferProducedState: "This producer returns a restricted set of literal combinations for `{{fields}}`, while its Boolean fields permit all four. Review a union preserving every returned combination, or a typed disposition if these are domain phases. Other writers and readers of a shared contract require review.",
|
|
23519
|
+
preferProjectedState: "This producer compares the same immutable primitive snapshot with distinct state values for `{{fields}}`, but its Boolean return contract also permits true/true. Review returning the authoritative state or preserving every produced combination in a union. Keep independent dimensions and unknown states; other writers and readers of a shared contract require review."
|
|
23428
23520
|
}
|
|
23429
23521
|
},
|
|
23430
23522
|
createOnce(context) {
|
|
23431
23523
|
const reportedContracts = /* @__PURE__ */ new Set();
|
|
23432
23524
|
const producers = [];
|
|
23525
|
+
const returns = /* @__PURE__ */ new Map();
|
|
23433
23526
|
function rememberProducer(node) {
|
|
23434
23527
|
producers.push(node);
|
|
23435
23528
|
}
|
|
@@ -23449,6 +23542,7 @@ var prefer_discriminated_union_default = defineRule({
|
|
|
23449
23542
|
before() {
|
|
23450
23543
|
reportedContracts.clear();
|
|
23451
23544
|
producers.length = 0;
|
|
23545
|
+
returns.clear();
|
|
23452
23546
|
const file = { filename: context.filename, cwd: context.cwd };
|
|
23453
23547
|
if (isTestFile(file) || isGeneratedFile(file, context.sourceCode.text)) return false;
|
|
23454
23548
|
return void 0;
|
|
@@ -23456,8 +23550,28 @@ var prefer_discriminated_union_default = defineRule({
|
|
|
23456
23550
|
ArrowFunctionExpression: rememberProducer,
|
|
23457
23551
|
FunctionDeclaration: rememberProducer,
|
|
23458
23552
|
FunctionExpression: rememberProducer,
|
|
23553
|
+
ReturnStatement(node) {
|
|
23554
|
+
const owner = enclosingFunction(node);
|
|
23555
|
+
if (owner !== null) returns.set(owner, returns.has(owner) ? null : node);
|
|
23556
|
+
},
|
|
23459
23557
|
"Program:exit"() {
|
|
23460
23558
|
for (const producer of producers) checkProducer(producer);
|
|
23559
|
+
if (hasIdentifierNamed(context.sourceCode, "eval")) return;
|
|
23560
|
+
const candidates2 = [];
|
|
23561
|
+
for (const producer of producers) {
|
|
23562
|
+
const returned2 = returns.get(producer);
|
|
23563
|
+
if (!isPresent(returned2)) continue;
|
|
23564
|
+
candidates2.push(...projectedBooleanPairs(producer, returned2, context.sourceCode).filter(({ reportNode }) => !reportedContracts.has(reportNode)));
|
|
23565
|
+
if (candidates2.length > 128) return;
|
|
23566
|
+
}
|
|
23567
|
+
if (candidates2.length === 0) return;
|
|
23568
|
+
const evidence = tsrsServices(context)?.run((file) => projectedBooleanEvidence(file, candidates2));
|
|
23569
|
+
if (!isPresent(evidence)) return;
|
|
23570
|
+
candidates2.forEach(({ reportNode, left, right }, index) => {
|
|
23571
|
+
if (evidence[index] !== true || reportedContracts.has(reportNode)) return;
|
|
23572
|
+
reportedContracts.add(reportNode);
|
|
23573
|
+
context.report({ node: reportNode, messageId: "preferProjectedState", data: { fields: [left.field, right.field].join("`, `") } });
|
|
23574
|
+
});
|
|
23461
23575
|
}
|
|
23462
23576
|
};
|
|
23463
23577
|
}
|
|
@@ -31806,8 +31920,62 @@ var production_derived_test_cases_default = defineRule({
|
|
|
31806
31920
|
}
|
|
31807
31921
|
});
|
|
31808
31922
|
|
|
31923
|
+
// src/rules/react-prefer-updater-function.ts
|
|
31924
|
+
var NAME131 = "react-prefer-updater-function";
|
|
31925
|
+
function stateForSetter(source, setter) {
|
|
31926
|
+
const declarator = setter.defs[0]?.node;
|
|
31927
|
+
if (setter.defs.length !== 1 || declarator?.type !== "VariableDeclarator" || declarator.id.type !== "ArrayPattern" || declarator.init === null) return null;
|
|
31928
|
+
const [value, update] = declarator.id.elements;
|
|
31929
|
+
const call = unwrapExpression(declarator.init);
|
|
31930
|
+
if (value?.type !== "Identifier" || update?.type !== "Identifier" || !setter.identifiers.includes(update) || call.type !== "CallExpression" || !reactHook(source, call.callee, "useState") || !stableValueBinding(setter)) return null;
|
|
31931
|
+
const state = source.getDeclaredVariables(declarator).find((variable) => variable.identifiers.includes(value));
|
|
31932
|
+
return state !== void 0 && stableValueBinding(state) ? { state, owner: enclosingFunction(declarator) } : null;
|
|
31933
|
+
}
|
|
31934
|
+
function readsStateEagerly(state, argument) {
|
|
31935
|
+
return state.references.some((reference) => reference.isRead() && !typeOnlyReference(reference) && evaluatedBy(reference.identifier, argument));
|
|
31936
|
+
}
|
|
31937
|
+
function evaluatedBy(node, argument) {
|
|
31938
|
+
for (let current = node; current !== null && !isFunction(current); current = parentOf(current)) {
|
|
31939
|
+
if (current === argument) return true;
|
|
31940
|
+
}
|
|
31941
|
+
return false;
|
|
31942
|
+
}
|
|
31943
|
+
function isFunction(node) {
|
|
31944
|
+
return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration";
|
|
31945
|
+
}
|
|
31946
|
+
var react_prefer_updater_function_default = defineRule({
|
|
31947
|
+
meta: {
|
|
31948
|
+
type: "problem",
|
|
31949
|
+
docs: { description: "Use the updater form when the next React state is built from the current state.", url: docsUrl(NAME131) },
|
|
31950
|
+
schema: [],
|
|
31951
|
+
messages: {
|
|
31952
|
+
preferUpdaterFunction: "`{{setter}}` builds the next state from the `{{state}}` snapshot, which is stale after an await, in timers and effects, or when two updates run before a re-render. Pass an updater: `{{setter}}((previous) => ...)`, reading `previous` wherever the argument read `{{state}}` or a value derived from it. Compute impure values such as IDs or timestamps before the call so the updater stays pure."
|
|
31953
|
+
}
|
|
31954
|
+
},
|
|
31955
|
+
createOnce(context) {
|
|
31956
|
+
return {
|
|
31957
|
+
before() {
|
|
31958
|
+
const source = context.sourceCode;
|
|
31959
|
+
return !isGeneratedFile({ filename: context.filename, cwd: context.cwd }, source.text) && importsReactValue(source);
|
|
31960
|
+
},
|
|
31961
|
+
CallExpression(node) {
|
|
31962
|
+
const [argument] = node.arguments;
|
|
31963
|
+
const callee = unwrapExpression(node.callee);
|
|
31964
|
+
if (node.arguments.length !== 1 || argument === void 0 || argument.type === "SpreadElement" || callee.type !== "Identifier") return;
|
|
31965
|
+
const value = unwrapExpression(argument);
|
|
31966
|
+
if (value.type === "Identifier" || isFunction(value)) return;
|
|
31967
|
+
const source = context.sourceCode;
|
|
31968
|
+
const setter = findVariable(source.getScope(callee), callee.name);
|
|
31969
|
+
const binding3 = setter === null ? null : stateForSetter(source, setter);
|
|
31970
|
+
if (setter === null || binding3 === null || enclosingFunction(node) === binding3.owner || !readsStateEagerly(binding3.state, argument)) return;
|
|
31971
|
+
context.report({ node, messageId: "preferUpdaterFunction", data: { setter: setter.name, state: binding3.state.name } });
|
|
31972
|
+
}
|
|
31973
|
+
};
|
|
31974
|
+
}
|
|
31975
|
+
});
|
|
31976
|
+
|
|
31809
31977
|
// src/rules/repeated-static-call-cases.ts
|
|
31810
|
-
var
|
|
31978
|
+
var NAME132 = "repeated-static-call-cases";
|
|
31811
31979
|
var TEST_MODULES2 = /* @__PURE__ */ new Set(["@jest/globals", "@playwright/test", "bun:test", "node:test", "vitest"]);
|
|
31812
31980
|
var TEST_NAMES3 = /* @__PURE__ */ new Set(["it", "test"]);
|
|
31813
31981
|
var TEST_MODIFIERS5 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
|
|
@@ -31931,7 +32099,7 @@ var repeated_static_call_cases_default = defineRule({
|
|
|
31931
32099
|
type: "suggestion",
|
|
31932
32100
|
docs: {
|
|
31933
32101
|
description: "Review three or more consecutive static-input call assertions as potential named cases.",
|
|
31934
|
-
url: docsUrl(
|
|
32102
|
+
url: docsUrl(NAME132)
|
|
31935
32103
|
},
|
|
31936
32104
|
schema: [],
|
|
31937
32105
|
messages: { repeatedStaticCallCases: "These {{count}} consecutive assertions repeat a call with static inputs. If independent, use named parameterized cases or subtests; preserve ordered scenarios as one test." }
|
|
@@ -32074,7 +32242,7 @@ function isPrimitiveReturn(node, annotation) {
|
|
|
32074
32242
|
}
|
|
32075
32243
|
|
|
32076
32244
|
// src/rules/require-assert-never.ts
|
|
32077
|
-
var
|
|
32245
|
+
var NAME133 = "require-assert-never";
|
|
32078
32246
|
function isRuntimeHandlingStatement(statement) {
|
|
32079
32247
|
if (statement.type === "EmptyStatement") return false;
|
|
32080
32248
|
if (statement.type === "BreakStatement") return statement.label !== null;
|
|
@@ -32144,7 +32312,7 @@ var require_assert_never_default = defineRule({
|
|
|
32144
32312
|
fixable: "code",
|
|
32145
32313
|
docs: {
|
|
32146
32314
|
description: "Require a never witness in empty switch defaults so discriminated unions remain exhaustive at compile time.",
|
|
32147
|
-
url: docsUrl(
|
|
32315
|
+
url: docsUrl(NAME133)
|
|
32148
32316
|
},
|
|
32149
32317
|
schema: [],
|
|
32150
32318
|
messages: {
|
|
@@ -32188,7 +32356,7 @@ var require_assert_never_default = defineRule({
|
|
|
32188
32356
|
});
|
|
32189
32357
|
|
|
32190
32358
|
// src/rules/require-button-accessible-name.ts
|
|
32191
|
-
var
|
|
32359
|
+
var NAME134 = "require-button-accessible-name";
|
|
32192
32360
|
var DELEGATING_PROPS = /* @__PURE__ */ new Set(["render", "asChild"]);
|
|
32193
32361
|
function delegatesRendering(opening) {
|
|
32194
32362
|
return opening.attributes.some(
|
|
@@ -32201,7 +32369,7 @@ var require_button_accessible_name_default = defineRule({
|
|
|
32201
32369
|
type: "problem",
|
|
32202
32370
|
docs: {
|
|
32203
32371
|
description: "Require an accessible name on statically unnamed native or configured JSX buttons.",
|
|
32204
|
-
url: docsUrl(
|
|
32372
|
+
url: docsUrl(NAME134)
|
|
32205
32373
|
},
|
|
32206
32374
|
schema: [
|
|
32207
32375
|
{
|
|
@@ -32255,7 +32423,7 @@ var require_button_accessible_name_default = defineRule({
|
|
|
32255
32423
|
});
|
|
32256
32424
|
|
|
32257
32425
|
// src/rules/require-camelcase-properties.ts
|
|
32258
|
-
var
|
|
32426
|
+
var NAME135 = "require-camelcase-properties";
|
|
32259
32427
|
var SNAKE_CASE_RE2 = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/u;
|
|
32260
32428
|
function blocksRename(member3, name) {
|
|
32261
32429
|
if (member3.type === "TSIndexSignature" || "computed" in member3 && member3.computed) return true;
|
|
@@ -32269,7 +32437,7 @@ var require_camelcase_properties_default = defineRule({
|
|
|
32269
32437
|
fixable: "code",
|
|
32270
32438
|
docs: {
|
|
32271
32439
|
description: "Require unquoted lower snake_case TypeScript properties and dot access to use camelCase.",
|
|
32272
|
-
url: docsUrl(
|
|
32440
|
+
url: docsUrl(NAME135)
|
|
32273
32441
|
},
|
|
32274
32442
|
schema: [],
|
|
32275
32443
|
defaultOptions: [],
|
|
@@ -32333,7 +32501,7 @@ var require_camelcase_properties_default = defineRule({
|
|
|
32333
32501
|
});
|
|
32334
32502
|
|
|
32335
32503
|
// src/rules/require-constrained-schema-integer.ts
|
|
32336
|
-
var
|
|
32504
|
+
var NAME136 = "require-constrained-schema-integer";
|
|
32337
32505
|
var FIELD_MINIMUMS = /* @__PURE__ */ new Map([["limit", 1], ["version", 0]]);
|
|
32338
32506
|
var NUMBER_FACTORIES = /* @__PURE__ */ new Set(["number", "int", "int32", "uint32"]);
|
|
32339
32507
|
var OBJECT_FACTORIES = /* @__PURE__ */ new Set(["object", "strictObject", "looseObject"]);
|
|
@@ -32375,7 +32543,7 @@ var require_constrained_schema_integer_default = defineRule({
|
|
|
32375
32543
|
type: "suggestion",
|
|
32376
32544
|
docs: {
|
|
32377
32545
|
description: "Declare integer and lower-bound validation for numeric limit and version schema fields.",
|
|
32378
|
-
url: docsUrl(
|
|
32546
|
+
url: docsUrl(NAME136)
|
|
32379
32547
|
},
|
|
32380
32548
|
schema: [],
|
|
32381
32549
|
messages: { constrainInteger: "Declare `{{field}}` as an integer with a lower bound of {{minimum}} or greater, or reuse its named domain schema." }
|
|
@@ -32854,14 +33022,14 @@ function closedNominalInheritanceFix(sourceCode2, file, call, consumer, implemen
|
|
|
32854
33022
|
}
|
|
32855
33023
|
|
|
32856
33024
|
// src/rules/require-explicit-contract-implementation.ts
|
|
32857
|
-
var
|
|
33025
|
+
var NAME137 = "require-explicit-contract-implementation";
|
|
32858
33026
|
var require_explicit_contract_implementation_default = defineRule({
|
|
32859
33027
|
meta: {
|
|
32860
33028
|
type: "problem",
|
|
32861
33029
|
fixable: "code",
|
|
32862
33030
|
docs: {
|
|
32863
33031
|
description: "Require nominal inheritance when an injected abstract class is rejected by an instanceof guard.",
|
|
32864
|
-
url: docsUrl(
|
|
33032
|
+
url: docsUrl(NAME137)
|
|
32865
33033
|
},
|
|
32866
33034
|
schema: [],
|
|
32867
33035
|
messages: {
|
|
@@ -33552,7 +33720,7 @@ function abstractContractOperations(node, functionAliases) {
|
|
|
33552
33720
|
}
|
|
33553
33721
|
|
|
33554
33722
|
// src/rules/require-explicit-service-contract.ts
|
|
33555
|
-
var
|
|
33723
|
+
var NAME138 = "require-explicit-service-contract";
|
|
33556
33724
|
var JAVASCRIPT_FILE_RE2 = /\.(?:js|jsx|mjs|cjs)$/iu;
|
|
33557
33725
|
var BOUNDARY_SUFFIX = /(?:Service|ServiceImpl|Store|Repository|Provider|Executor|Enqueuer|Runner|Platform|Manager|ManagerImpl|Detector|Control|Refresher|Reader|Settlement|Retry|Stop|Catalog|DAO)$/u;
|
|
33558
33726
|
var NON_SERVICE_SUFFIX = /(?:Router|Middleware|Builder)$/u;
|
|
@@ -33568,7 +33736,7 @@ var require_explicit_service_contract_default = defineRule({
|
|
|
33568
33736
|
type: "problem",
|
|
33569
33737
|
docs: {
|
|
33570
33738
|
description: "Require explicit contracts for classes that invoke retained collaborators.",
|
|
33571
|
-
url: docsUrl(
|
|
33739
|
+
url: docsUrl(NAME138)
|
|
33572
33740
|
},
|
|
33573
33741
|
schema: [],
|
|
33574
33742
|
fixable: "code",
|
|
@@ -33604,7 +33772,7 @@ var require_explicit_service_contract_default = defineRule({
|
|
|
33604
33772
|
});
|
|
33605
33773
|
|
|
33606
33774
|
// src/rules/require-fetch-timeout.ts
|
|
33607
|
-
var
|
|
33775
|
+
var NAME139 = "require-fetch-timeout";
|
|
33608
33776
|
function emptySignal(expression, resolvesToGlobal) {
|
|
33609
33777
|
const value = unwrapExpression(expression);
|
|
33610
33778
|
if (value.type === "Literal" && value.value === null)
|
|
@@ -33666,7 +33834,7 @@ var require_fetch_timeout_default = defineRule({
|
|
|
33666
33834
|
type: "problem",
|
|
33667
33835
|
docs: {
|
|
33668
33836
|
description: "Require an explicit abort signal on locally analyzable global fetch calls.",
|
|
33669
|
-
url: docsUrl(
|
|
33837
|
+
url: docsUrl(NAME139)
|
|
33670
33838
|
},
|
|
33671
33839
|
schema: [
|
|
33672
33840
|
{
|
|
@@ -33758,13 +33926,13 @@ var require_fetch_timeout_default = defineRule({
|
|
|
33758
33926
|
});
|
|
33759
33927
|
|
|
33760
33928
|
// src/rules/require-ime-safe-enter-handler.ts
|
|
33761
|
-
var
|
|
33929
|
+
var NAME140 = "require-ime-safe-enter-handler";
|
|
33762
33930
|
var require_ime_safe_enter_handler_default = defineRule({
|
|
33763
33931
|
meta: {
|
|
33764
33932
|
type: "problem",
|
|
33765
33933
|
docs: {
|
|
33766
33934
|
description: "Preserve IME composition before Enter submits or blurs a native text control.",
|
|
33767
|
-
url: docsUrl(
|
|
33935
|
+
url: docsUrl(NAME140)
|
|
33768
33936
|
},
|
|
33769
33937
|
schema: [],
|
|
33770
33938
|
messages: {
|
|
@@ -33840,7 +34008,7 @@ function nativeAction(node, event) {
|
|
|
33840
34008
|
}
|
|
33841
34009
|
|
|
33842
34010
|
// src/rules/require-interface-for-exported-class.ts
|
|
33843
|
-
var
|
|
34011
|
+
var NAME141 = "require-interface-for-exported-class";
|
|
33844
34012
|
var JAVASCRIPT_FILE_RE3 = /\.(?:js|jsx|mjs|cjs)$/iu;
|
|
33845
34013
|
function isPublicInstanceMember2(member3) {
|
|
33846
34014
|
return !member3.static && member3.accessibility !== "private" && member3.accessibility !== "protected" && member3.key.type !== "PrivateIdentifier";
|
|
@@ -33901,7 +34069,7 @@ var require_interface_for_exported_class_default = defineRule({
|
|
|
33901
34069
|
type: "suggestion",
|
|
33902
34070
|
docs: {
|
|
33903
34071
|
description: "Require exported concrete classes with public behavior to declare a contract.",
|
|
33904
|
-
url: docsUrl(
|
|
34072
|
+
url: docsUrl(NAME141)
|
|
33905
34073
|
},
|
|
33906
34074
|
schema: [],
|
|
33907
34075
|
fixable: "code",
|
|
@@ -33943,7 +34111,7 @@ var require_interface_for_exported_class_default = defineRule({
|
|
|
33943
34111
|
});
|
|
33944
34112
|
|
|
33945
34113
|
// src/rules/require-jwt-lifetime.ts
|
|
33946
|
-
var
|
|
34114
|
+
var NAME142 = "require-jwt-lifetime";
|
|
33947
34115
|
function isJoseValueImport(declaration) {
|
|
33948
34116
|
return declaration.type === "ImportDeclaration" && declaration.importKind !== "type" && declaration.source.value === "jose";
|
|
33949
34117
|
}
|
|
@@ -33952,7 +34120,7 @@ var require_jwt_lifetime_default = defineRule({
|
|
|
33952
34120
|
type: "problem",
|
|
33953
34121
|
docs: {
|
|
33954
34122
|
description: "Require an expiration claim or a maximum token age when returning a verified JWT.",
|
|
33955
|
-
url: docsUrl(
|
|
34123
|
+
url: docsUrl(NAME142)
|
|
33956
34124
|
},
|
|
33957
34125
|
schema: [],
|
|
33958
34126
|
messages: { lifetime: "Require the JWT exp claim or set maxTokenAge before returning this verification result." }
|
|
@@ -34011,7 +34179,7 @@ var require_jwt_lifetime_default = defineRule({
|
|
|
34011
34179
|
});
|
|
34012
34180
|
|
|
34013
34181
|
// src/rules/require-pascal-case-zod-schema-name.ts
|
|
34014
|
-
var
|
|
34182
|
+
var NAME143 = "require-pascal-case-zod-schema-name";
|
|
34015
34183
|
var PASCAL_SCHEMA_NAME_RE = /^[A-Z][A-Za-z0-9]*Schema$/;
|
|
34016
34184
|
var BENCHMARK_PATH_RE2 = /(^|[\\/])(?:benchmarks?|bench)[\\/]/;
|
|
34017
34185
|
var NON_SCHEMA_TERMINALS2 = /* @__PURE__ */ new Set([
|
|
@@ -34182,7 +34350,7 @@ var require_pascal_case_zod_schema_name_default = defineRule({
|
|
|
34182
34350
|
fixable: "code",
|
|
34183
34351
|
docs: {
|
|
34184
34352
|
description: "Require confirmed module-level Zod schema contracts to use PascalCase with a `Schema` suffix.",
|
|
34185
|
-
url: docsUrl(
|
|
34353
|
+
url: docsUrl(NAME143)
|
|
34186
34354
|
},
|
|
34187
34355
|
schema: [],
|
|
34188
34356
|
defaultOptions: [],
|
|
@@ -34292,7 +34460,7 @@ var require_pascal_case_zod_schema_name_default = defineRule({
|
|
|
34292
34460
|
});
|
|
34293
34461
|
|
|
34294
34462
|
// src/rules/require-port-for-service.ts
|
|
34295
|
-
var
|
|
34463
|
+
var NAME144 = "require-port-for-service";
|
|
34296
34464
|
var HTTP_TRANSPORT_TYPE_RE = /^(?:KyInstance|AxiosInstance|Session)$/;
|
|
34297
34465
|
var TRANSPORT_WRAPPER_NAME_RE = /(?:Client$|^Http[A-Z])/;
|
|
34298
34466
|
var ERROR_VALUE_NAME_RE = /Error$/;
|
|
@@ -34426,7 +34594,7 @@ var require_port_for_service_default = defineRule({
|
|
|
34426
34594
|
type: "suggestion",
|
|
34427
34595
|
docs: {
|
|
34428
34596
|
description: "Advise when an exported service with injected collaborators has public methods not covered by its declared ports.",
|
|
34429
|
-
url: docsUrl(
|
|
34597
|
+
url: docsUrl(NAME144)
|
|
34430
34598
|
},
|
|
34431
34599
|
schema: [],
|
|
34432
34600
|
fixable: "code",
|
|
@@ -34466,7 +34634,7 @@ var require_port_for_service_default = defineRule({
|
|
|
34466
34634
|
});
|
|
34467
34635
|
|
|
34468
34636
|
// src/rules/require-sql-access-class.ts
|
|
34469
|
-
var
|
|
34637
|
+
var NAME145 = "require-sql-access-class";
|
|
34470
34638
|
var DIRECT_EXECUTION_METHODS = /* @__PURE__ */ new Set([
|
|
34471
34639
|
"all",
|
|
34472
34640
|
"batch",
|
|
@@ -34664,7 +34832,7 @@ var require_sql_access_class_default = defineRule({
|
|
|
34664
34832
|
type: "suggestion",
|
|
34665
34833
|
docs: {
|
|
34666
34834
|
description: "Keep SQL reads and writes inside a class that receives its database dependency.",
|
|
34667
|
-
url: docsUrl(
|
|
34835
|
+
url: docsUrl(NAME145)
|
|
34668
34836
|
},
|
|
34669
34837
|
schema: [],
|
|
34670
34838
|
messages: {
|
|
@@ -34736,7 +34904,7 @@ function staticMatcherFix(sourceCode2, property) {
|
|
|
34736
34904
|
}
|
|
34737
34905
|
|
|
34738
34906
|
// src/rules/require-static-next-matcher.ts
|
|
34739
|
-
var
|
|
34907
|
+
var NAME146 = "require-static-next-matcher";
|
|
34740
34908
|
var NEXT_ENTRY_FILE = /(?:^|[/\\])(?:middleware|proxy)\.[cm]?[jt]sx?$/u;
|
|
34741
34909
|
function isStaticValue(node) {
|
|
34742
34910
|
const value = unwrapExpression(node);
|
|
@@ -34768,7 +34936,7 @@ var require_static_next_matcher_default = defineRule({
|
|
|
34768
34936
|
fixable: "code",
|
|
34769
34937
|
docs: {
|
|
34770
34938
|
description: "Require Next.js middleware and proxy matcher configuration to contain only build-time literals.",
|
|
34771
|
-
url: docsUrl(
|
|
34939
|
+
url: docsUrl(NAME146)
|
|
34772
34940
|
},
|
|
34773
34941
|
schema: [],
|
|
34774
34942
|
messages: {
|
|
@@ -34812,7 +34980,7 @@ var require_static_next_matcher_default = defineRule({
|
|
|
34812
34980
|
});
|
|
34813
34981
|
|
|
34814
34982
|
// src/rules/require-svg-accessible-name.ts
|
|
34815
|
-
var
|
|
34983
|
+
var NAME147 = "require-svg-accessible-name";
|
|
34816
34984
|
var DECORATIVE_ROLES = /* @__PURE__ */ new Set(["none", "presentation"]);
|
|
34817
34985
|
var NAME_ATTRIBUTES = ["aria-label", "aria-labelledby"];
|
|
34818
34986
|
var NO_ICON_MODULES = [];
|
|
@@ -34821,7 +34989,7 @@ var require_svg_accessible_name_default = defineRule({
|
|
|
34821
34989
|
type: "problem",
|
|
34822
34990
|
docs: {
|
|
34823
34991
|
description: "Require a name or explicit decorative semantics on inline SVG elements.",
|
|
34824
|
-
url: docsUrl(
|
|
34992
|
+
url: docsUrl(NAME147)
|
|
34825
34993
|
},
|
|
34826
34994
|
schema: [],
|
|
34827
34995
|
messages: {
|
|
@@ -34853,7 +35021,7 @@ var require_svg_accessible_name_default = defineRule({
|
|
|
34853
35021
|
});
|
|
34854
35022
|
|
|
34855
35023
|
// src/rules/require-use-form-default-values.ts
|
|
34856
|
-
var
|
|
35024
|
+
var NAME148 = "require-use-form-default-values";
|
|
34857
35025
|
var UNSAFE_FORM_MEMBERS = /* @__PURE__ */ new Set(["__defineGetter__", "__defineSetter__", "__proto__", "valueOf"]);
|
|
34858
35026
|
function staticPropertyName2(property) {
|
|
34859
35027
|
return property.computed ? null : getPropertyName(property);
|
|
@@ -34896,7 +35064,7 @@ var require_use_form_default_values_default = defineRule({
|
|
|
34896
35064
|
type: "problem",
|
|
34897
35065
|
docs: {
|
|
34898
35066
|
description: "Require explicit form-level initialization or a field default for directly bound Controller fields.",
|
|
34899
|
-
url: docsUrl(
|
|
35067
|
+
url: docsUrl(NAME148)
|
|
34900
35068
|
},
|
|
34901
35069
|
schema: [],
|
|
34902
35070
|
messages: { requireUseFormDefaultValues: "Add non-undefined defaultValues/values to the associated useForm call or non-undefined defaultValue to this field." }
|
|
@@ -35035,7 +35203,7 @@ var require_use_form_default_values_default = defineRule({
|
|
|
35035
35203
|
});
|
|
35036
35204
|
|
|
35037
35205
|
// src/rules/require-use-server-in-actions-file.ts
|
|
35038
|
-
var
|
|
35206
|
+
var NAME149 = "require-use-server-in-actions-file";
|
|
35039
35207
|
var ACTION_MODULE_RE = /(?:^|\/)app\/.*\/(?:actions|[^/]+-actions)\.[cm]?[jt]s$/u;
|
|
35040
35208
|
function isUseServerDirective2(statement) {
|
|
35041
35209
|
return statement.type === "ExpressionStatement" && statement.directive === "use server";
|
|
@@ -35056,7 +35224,7 @@ var require_use_server_in_actions_file_default = defineRule({
|
|
|
35056
35224
|
type: "problem",
|
|
35057
35225
|
docs: {
|
|
35058
35226
|
description: "route action module missing the use server directive",
|
|
35059
|
-
url: docsUrl(
|
|
35227
|
+
url: docsUrl(NAME149)
|
|
35060
35228
|
},
|
|
35061
35229
|
schema: [],
|
|
35062
35230
|
messages: {
|
|
@@ -35082,7 +35250,7 @@ var require_use_server_in_actions_file_default = defineRule({
|
|
|
35082
35250
|
});
|
|
35083
35251
|
|
|
35084
35252
|
// src/rules/require-zod-form-validation.ts
|
|
35085
|
-
var
|
|
35253
|
+
var NAME150 = "require-zod-form-validation";
|
|
35086
35254
|
var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
|
|
35087
35255
|
"parse",
|
|
35088
35256
|
"safeParse",
|
|
@@ -35141,7 +35309,7 @@ var require_zod_form_validation_default = defineRule({
|
|
|
35141
35309
|
type: "problem",
|
|
35142
35310
|
docs: {
|
|
35143
35311
|
description: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
|
|
35144
|
-
url: docsUrl(
|
|
35312
|
+
url: docsUrl(NAME150)
|
|
35145
35313
|
},
|
|
35146
35314
|
schema: [],
|
|
35147
35315
|
messages: {
|
|
@@ -35429,7 +35597,7 @@ function declaresFramework(metadata, name) {
|
|
|
35429
35597
|
}
|
|
35430
35598
|
|
|
35431
35599
|
// src/rules/sole-export-matches-filename.ts
|
|
35432
|
-
var
|
|
35600
|
+
var NAME151 = "sole-export-matches-filename";
|
|
35433
35601
|
var EXCLUDED_STEMS = /* @__PURE__ */ new Set(["global", "index", "types"]);
|
|
35434
35602
|
function stem3(filename) {
|
|
35435
35603
|
const base = filename.replaceAll("\\", "/").split("/").at(-1) ?? "";
|
|
@@ -35443,7 +35611,7 @@ var sole_export_matches_filename_default = defineRule({
|
|
|
35443
35611
|
type: "suggestion",
|
|
35444
35612
|
docs: {
|
|
35445
35613
|
description: "Make a module filename reflect its sole named public runtime export.",
|
|
35446
|
-
url: docsUrl(
|
|
35614
|
+
url: docsUrl(NAME151)
|
|
35447
35615
|
},
|
|
35448
35616
|
schema: [],
|
|
35449
35617
|
messages: {
|
|
@@ -35497,7 +35665,7 @@ var source_coupled_test_default = createSourceCoupledRule(
|
|
|
35497
35665
|
);
|
|
35498
35666
|
|
|
35499
35667
|
// src/rules/stepdown.ts
|
|
35500
|
-
function
|
|
35668
|
+
function isFunction2(node) {
|
|
35501
35669
|
return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
|
|
35502
35670
|
}
|
|
35503
35671
|
function isErasedExpression2(node) {
|
|
@@ -35520,7 +35688,7 @@ function wrappedExpression(node) {
|
|
|
35520
35688
|
function enclosingFunction2(node) {
|
|
35521
35689
|
let current = parentOf(node);
|
|
35522
35690
|
while (current !== null) {
|
|
35523
|
-
if (
|
|
35691
|
+
if (isFunction2(current)) return current;
|
|
35524
35692
|
current = parentOf(current);
|
|
35525
35693
|
}
|
|
35526
35694
|
return void 0;
|
|
@@ -35779,7 +35947,7 @@ function isDeferredModuleStatement(statement) {
|
|
|
35779
35947
|
if (node === null || node.type === "FunctionDeclaration" || node.type === "TSDeclareFunction" || node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration" || node.type === "ImportDeclaration" || node.type === "ExportAllDeclaration" || node.type === "EmptyStatement") {
|
|
35780
35948
|
return true;
|
|
35781
35949
|
}
|
|
35782
|
-
return node.type === "VariableDeclaration" && node.declarations.every((declaration) => declaration.id.type === "Identifier" && declaration.init !== null &&
|
|
35950
|
+
return node.type === "VariableDeclaration" && node.declarations.every((declaration) => declaration.id.type === "Identifier" && declaration.init !== null && isFunction2(unwrapExpression4(declaration.init)));
|
|
35783
35951
|
}
|
|
35784
35952
|
function exportedNames(program) {
|
|
35785
35953
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -35820,7 +35988,7 @@ function moduleDefinitions(program) {
|
|
|
35820
35988
|
}
|
|
35821
35989
|
function constFunctionDefinition(declarator) {
|
|
35822
35990
|
const initializer = declarator.init === null ? null : unwrapExpression4(declarator.init);
|
|
35823
|
-
if (declarator.id.type !== "Identifier" || initializer === null || !
|
|
35991
|
+
if (declarator.id.type !== "Identifier" || initializer === null || !isFunction2(initializer)) return [];
|
|
35824
35992
|
const { name } = declarator.id;
|
|
35825
35993
|
return [{ key: name, name, node: declarator, functionNode: initializer, bindingNode: declarator }];
|
|
35826
35994
|
}
|
|
@@ -35899,7 +36067,7 @@ function referencedPropertyName(node) {
|
|
|
35899
36067
|
}
|
|
35900
36068
|
function walk3(node, visitorKeys, visit2, nestedFunction = false) {
|
|
35901
36069
|
visit2(node, nestedFunction);
|
|
35902
|
-
const nested = nestedFunction ||
|
|
36070
|
+
const nested = nestedFunction || isFunction2(node) || node.type === "ClassDeclaration" || node.type === "ClassExpression";
|
|
35903
36071
|
forEachAstChild(node, visitorKeys, (child) => {
|
|
35904
36072
|
walk3(child, visitorKeys, visit2, nested);
|
|
35905
36073
|
});
|
|
@@ -35920,7 +36088,7 @@ function classScope(state, node, computedReferences, privateBandOnly) {
|
|
|
35920
36088
|
const visitorKeys = state.sourceCode.visitorKeys;
|
|
35921
36089
|
const methods = node.body.body.flatMap((member3) => member3.type === "MethodDefinition" ? [member3] : []);
|
|
35922
36090
|
const counts = classMethodCounts(node.body, methods);
|
|
35923
|
-
const implementations = methods.filter((method) =>
|
|
36091
|
+
const implementations = methods.filter((method) => isFunction2(method.value));
|
|
35924
36092
|
const implementationCounts = classMethodCounts(node.body, implementations);
|
|
35925
36093
|
const scopeDefinitions = implementations.flatMap((method) => {
|
|
35926
36094
|
const name = methodName(method);
|
|
@@ -35958,7 +36126,7 @@ function classScope(state, node, computedReferences, privateBandOnly) {
|
|
|
35958
36126
|
return;
|
|
35959
36127
|
}
|
|
35960
36128
|
const methodFunction = method.value;
|
|
35961
|
-
if (!
|
|
36129
|
+
if (!isFunction2(methodFunction) || methodFunction.body === null) return;
|
|
35962
36130
|
const body3 = methodFunction.body;
|
|
35963
36131
|
const caller = methodKey(name, method.static);
|
|
35964
36132
|
const receivers = new Map(classReceivers);
|
|
@@ -36186,7 +36354,7 @@ var stepdown_default = defineRule({
|
|
|
36186
36354
|
});
|
|
36187
36355
|
|
|
36188
36356
|
// src/rules/store-insert-requires-on-conflict.ts
|
|
36189
|
-
var
|
|
36357
|
+
var NAME152 = "store-insert-requires-on-conflict";
|
|
36190
36358
|
var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
|
|
36191
36359
|
var CONFLICT_HANDLED = /\bON\s+CONFLICT\b|\bON\s+DUPLICATE\s+KEY\b|\bINSERT\s+OR\s+(?:IGNORE|REPLACE)\b|\bINSERT\b[\s\S]*?\bSELECT\b[\s\S]*?\bWHERE\s+NOT\s+EXISTS\b/i;
|
|
36192
36360
|
var REPLAY_CONTRACT_NAME = /(?:enqueue|ensure|migrate|recordOnce|schedule|seed|upsert|getOrCreate|createIfAbsent|insertIfAbsent)/i;
|
|
@@ -36215,7 +36383,7 @@ var store_insert_requires_on_conflict_default = defineRule({
|
|
|
36215
36383
|
type: "problem",
|
|
36216
36384
|
docs: {
|
|
36217
36385
|
description: "Review conflict handling for embedded inserts in replay-named callables.",
|
|
36218
|
-
url: docsUrl(
|
|
36386
|
+
url: docsUrl(NAME152)
|
|
36219
36387
|
},
|
|
36220
36388
|
schema: [],
|
|
36221
36389
|
messages: {
|
|
@@ -36240,7 +36408,7 @@ var store_insert_requires_on_conflict_default = defineRule({
|
|
|
36240
36408
|
});
|
|
36241
36409
|
|
|
36242
36410
|
// src/rules/test-loops-over-literal-cases.ts
|
|
36243
|
-
var
|
|
36411
|
+
var NAME153 = "test-loops-over-literal-cases";
|
|
36244
36412
|
var TEST_CALLERS3 = /* @__PURE__ */ new Set(["it", "test"]);
|
|
36245
36413
|
var TEST_MODIFIERS6 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
|
|
36246
36414
|
var ASSERTION_ROOTS = /* @__PURE__ */ new Set([
|
|
@@ -36336,7 +36504,7 @@ var test_loops_over_literal_cases_default = defineRule({
|
|
|
36336
36504
|
type: "suggestion",
|
|
36337
36505
|
docs: {
|
|
36338
36506
|
description: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
|
|
36339
|
-
url: docsUrl(
|
|
36507
|
+
url: docsUrl(NAME153)
|
|
36340
36508
|
},
|
|
36341
36509
|
schema: [],
|
|
36342
36510
|
messages: {
|
|
@@ -36400,7 +36568,7 @@ var test_loops_over_literal_cases_default = defineRule({
|
|
|
36400
36568
|
});
|
|
36401
36569
|
|
|
36402
36570
|
// src/rules/test-phase-label-comment.ts
|
|
36403
|
-
var
|
|
36571
|
+
var NAME154 = "test-phase-label-comment";
|
|
36404
36572
|
var PHASE_WORD = String.raw`arrange|act|assert(?:ion)?s?|given|when|then|exercise|execute|verif(?:y|ication)|cleanup|prepare|sanity(?:\s+check)?`;
|
|
36405
36573
|
var PHASE_RE = new RegExp(
|
|
36406
36574
|
String.raw`^[-=~*_#.\s]{0,40}(?:${PHASE_WORD})(?:\s*(?:[/&+,|]|->|and)\s*(?:${PHASE_WORD}))*[-=~*_#.\s:;!–—]{0,40}$`,
|
|
@@ -36428,7 +36596,7 @@ var test_phase_label_comment_default = defineRule({
|
|
|
36428
36596
|
fixable: "code",
|
|
36429
36597
|
docs: {
|
|
36430
36598
|
description: "Tests must not use bare Arrange, Act, Assert, Given, When, or Then phase comments.",
|
|
36431
|
-
url: docsUrl(
|
|
36599
|
+
url: docsUrl(NAME154)
|
|
36432
36600
|
},
|
|
36433
36601
|
schema: [],
|
|
36434
36602
|
messages: { removeLabel: "Bare test phase label \u2014 delete it and let the test names and helpers carry the structure." }
|
|
@@ -36611,6 +36779,7 @@ var RULES = {
|
|
|
36611
36779
|
"prefer-zod-infer": prefer_zod_infer_default,
|
|
36612
36780
|
"prefer-zod-parse-output-type": prefer_zod_parse_output_type_default,
|
|
36613
36781
|
"production-derived-test-cases": production_derived_test_cases_default,
|
|
36782
|
+
"react-prefer-updater-function": react_prefer_updater_function_default,
|
|
36614
36783
|
"repeated-static-call-cases": repeated_static_call_cases_default,
|
|
36615
36784
|
"require-assert-never": require_assert_never_default,
|
|
36616
36785
|
"require-button-accessible-name": require_button_accessible_name_default,
|
package/dist/index.js
CHANGED