@sarj/eslint-plugin 15.17.6 → 15.17.8
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 +35 -96
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +35 -96
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6869,22 +6869,23 @@ var no_restated_comment_default = createRule({
|
|
|
6869
6869
|
// src/rules/no-restated-jsdoc.ts
|
|
6870
6870
|
var import_utils39 = require("@typescript-eslint/utils");
|
|
6871
6871
|
var NO_RESTATED_JSDOC_DOCUMENTATION = {
|
|
6872
|
-
summary: "Flag
|
|
6872
|
+
summary: "Flag JSDoc prose that appears to repeat declaration names without adding behavioral information.",
|
|
6873
6873
|
rationale: "Signature-only JSDoc duplicates type information and drifts without helping callers.",
|
|
6874
6874
|
remediation: "Delete the block or document behavior, constraints, failures, or context the signature cannot express.",
|
|
6875
6875
|
category: "maintainability",
|
|
6876
6876
|
aliases: ["jsdoc-restates-signature"],
|
|
6877
6877
|
autofix: "suggestion",
|
|
6878
|
-
limitations: ["Generated files, detached blocks, unknown tags, empty blocks, and JSDoc with information absent from the signature are excluded."],
|
|
6878
|
+
limitations: ["Generated files, detached blocks, intervening comments, unknown tags, explicit JSDoc type payloads, empty blocks, and JSDoc with information absent from the signature are excluded.", "Negation, conditions, constraints, sentinel values, numeric details, and quoted text conservatively preserve the block, even when a declaration name contains the same words."],
|
|
6879
6879
|
examples: [
|
|
6880
6880
|
{ id: "behavioral-jsdoc", title: "Document behavior absent from the signature", outcome: "no-match", files: [{ path: "src/users.ts", source: "/** Get the user while bypassing the read replica. */\nexport function getUser(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 0, public: true },
|
|
6881
|
-
{ id: "signature-jsdoc", title: "Remove JSDoc that only repeats the signature", outcome: "match", files: [{ path: "src/users.ts", source: "/** Get the user by id. */\nexport function getUserById(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true }
|
|
6881
|
+
{ id: "signature-jsdoc", title: "Remove JSDoc that only repeats the signature", outcome: "match", files: [{ path: "src/users.ts", source: "/** Get the user by id. */\nexport function getUserById(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true },
|
|
6882
|
+
{ id: "negated-behavior", scenarioId: "negation", title: "Keep behavior even when its words resemble the declaration", outcome: "no-match", files: [{ path: "src/users.ts", source: "/** Does not cache the user. */\nexport function cacheUser(user: unknown) { return user; }" }], focusPath: "src/users.ts", expectedCount: 0, public: true },
|
|
6883
|
+
{ id: "repeated-behavior-name", scenarioId: "negation", title: "Review prose that merely repeats the declaration name", outcome: "match", files: [{ path: "src/users.ts", source: "/** Cache the user. */\nexport function cacheUser(user: unknown) { return user; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true }
|
|
6882
6884
|
]
|
|
6883
6885
|
};
|
|
6884
6886
|
var MODELLED_TAGS = /* @__PURE__ */ new Set([
|
|
6885
6887
|
"arg",
|
|
6886
6888
|
"argument",
|
|
6887
|
-
"async",
|
|
6888
6889
|
"description",
|
|
6889
6890
|
"param",
|
|
6890
6891
|
"return",
|
|
@@ -6903,7 +6904,8 @@ var STOPWORDS2 = new Set(
|
|
|
6903
6904
|
returns returning result optional required default true false null undefined
|
|
6904
6905
|
string number boolean array list promise`.split(/\s+/)
|
|
6905
6906
|
);
|
|
6906
|
-
var WORD_RE2 =
|
|
6907
|
+
var WORD_RE2 = new RegExp("\\p{L}+", "gu");
|
|
6908
|
+
var BEHAVIORAL_PROSE_RE = /\b(?:not|no|never|if|when|unless|must|should|may|can|could|would|optional|required|default|true|false|null|undefined|before|after|until|once|again|only|always)\b|\d|["'`<>=+*/%&|!~^-]/i;
|
|
6907
6909
|
function parseJsDoc(value) {
|
|
6908
6910
|
const lines = value.replace(/^\*/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, ""));
|
|
6909
6911
|
const description = [];
|
|
@@ -6923,12 +6925,13 @@ function parseJsDoc(value) {
|
|
|
6923
6925
|
return { description: description.join("\n").trim(), tags };
|
|
6924
6926
|
}
|
|
6925
6927
|
function covered(text, known) {
|
|
6928
|
+
if (BEHAVIORAL_PROSE_RE.test(text)) return false;
|
|
6926
6929
|
const stems = /* @__PURE__ */ new Set();
|
|
6927
6930
|
for (const token of known) stems.add(stem(token));
|
|
6928
6931
|
return proseTokens(text).every((word) => known.has(word) || stems.has(stem(word)));
|
|
6929
6932
|
}
|
|
6930
6933
|
function proseTokens(text) {
|
|
6931
|
-
return (text.match(WORD_RE2) ?? []).map((word) => word.toLowerCase()).filter((word) =>
|
|
6934
|
+
return (text.match(WORD_RE2) ?? []).map((word) => word.toLowerCase()).filter((word) => !STOPWORDS2.has(word));
|
|
6932
6935
|
}
|
|
6933
6936
|
function declarationNames(node) {
|
|
6934
6937
|
switch (node.type) {
|
|
@@ -6985,11 +6988,11 @@ var no_restated_jsdoc_default = createRule({
|
|
|
6985
6988
|
type: "suggestion",
|
|
6986
6989
|
hasSuggestions: true,
|
|
6987
6990
|
docs: {
|
|
6988
|
-
description: "Flag
|
|
6991
|
+
description: "Flag JSDoc prose that appears to repeat declaration names without adding behavioral information."
|
|
6989
6992
|
},
|
|
6990
6993
|
schema: [],
|
|
6991
6994
|
messages: {
|
|
6992
|
-
restatesSignature: "JSDoc
|
|
6995
|
+
restatesSignature: "JSDoc appears to repeat declaration names \u2014 consider removing repetition. Keep type contracts, constraints, failures, and rationale.",
|
|
6993
6996
|
deleteBlock: "Delete the JSDoc block."
|
|
6994
6997
|
}
|
|
6995
6998
|
},
|
|
@@ -7012,8 +7015,9 @@ var no_restated_jsdoc_default = createRule({
|
|
|
7012
7015
|
const tagNames = new Set(tags.map((tag) => tag.name));
|
|
7013
7016
|
if ([...tagNames].some((name) => !MODELLED_TAGS.has(name))) continue;
|
|
7014
7017
|
if (isProtected(describedText)) continue;
|
|
7015
|
-
const token = sourceCode.getTokenAfter(comment, { includeComments:
|
|
7018
|
+
const token = sourceCode.getTokenAfter(comment, { includeComments: true });
|
|
7016
7019
|
if (token === null || token.loc.start.line !== comment.loc.end.line + 1) continue;
|
|
7020
|
+
if (token.type === "Line" || token.type === "Block") continue;
|
|
7017
7021
|
let node = sourceCode.getNodeByRangeIndex(token.range[0]);
|
|
7018
7022
|
let declaration = null;
|
|
7019
7023
|
while (node != null && node.type !== import_utils39.AST_NODE_TYPES.Program) {
|
|
@@ -7024,6 +7028,8 @@ var no_restated_jsdoc_default = createRule({
|
|
|
7024
7028
|
if (declaration === null) continue;
|
|
7025
7029
|
const paramTags = tags.filter((tag) => PARAM_TAGS.has(tag.name));
|
|
7026
7030
|
const returnTags = tags.filter((tag) => RETURN_TAGS.has(tag.name));
|
|
7031
|
+
if ([...paramTags, ...returnTags].some((tag) => /^\s*\{/.test(tag.text))) continue;
|
|
7032
|
+
if (paramTags.some((tag) => /^\s*(?:\[|[A-Za-z_$][\w$]*\.)/.test(tag.text))) continue;
|
|
7027
7033
|
if (describedText.length === 0 && paramTags.length === 0 && returnTags.length === 0) {
|
|
7028
7034
|
continue;
|
|
7029
7035
|
}
|
|
@@ -10074,8 +10080,8 @@ var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
|
|
|
10074
10080
|
rationale: "Wrapping a TypeScript enum preserves its emitted runtime object and duplicates the schema's value definition across two constructs.",
|
|
10075
10081
|
remediation: "Pass string literals directly to `z.enum` and derive the TypeScript type with `z.infer`.",
|
|
10076
10082
|
category: "maintainability",
|
|
10077
|
-
autofix: "
|
|
10078
|
-
limitations: ["
|
|
10083
|
+
autofix: "none",
|
|
10084
|
+
limitations: ["Migration is manual: replacing an enum-like object with a value array changes the public schema.enum keys and can affect consumers."],
|
|
10079
10085
|
examples: [
|
|
10080
10086
|
{
|
|
10081
10087
|
id: "zod-literal-enum",
|
|
@@ -10093,8 +10099,7 @@ var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
|
|
|
10093
10099
|
files: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.nativeEnum({ Active: "active", Inactive: "inactive" });' }],
|
|
10094
10100
|
focusPath: "src/status.ts",
|
|
10095
10101
|
expectedCount: 1,
|
|
10096
|
-
public: true
|
|
10097
|
-
fixedFiles: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.enum(["active", "inactive"]);' }]
|
|
10102
|
+
public: true
|
|
10098
10103
|
}
|
|
10099
10104
|
]
|
|
10100
10105
|
};
|
|
@@ -10119,26 +10124,6 @@ function unwrap2(node) {
|
|
|
10119
10124
|
}
|
|
10120
10125
|
return node;
|
|
10121
10126
|
}
|
|
10122
|
-
function stringValueTexts(node, sourceCode) {
|
|
10123
|
-
const texts = [];
|
|
10124
|
-
for (const prop of node.properties) {
|
|
10125
|
-
if (prop.type !== import_utils56.AST_NODE_TYPES.Property) {
|
|
10126
|
-
return null;
|
|
10127
|
-
}
|
|
10128
|
-
if (prop.computed || prop.shorthand || prop.method || prop.kind !== "init") {
|
|
10129
|
-
return null;
|
|
10130
|
-
}
|
|
10131
|
-
const value = prop.value;
|
|
10132
|
-
if (value.type !== import_utils56.AST_NODE_TYPES.Literal || typeof value.value !== "string") {
|
|
10133
|
-
return null;
|
|
10134
|
-
}
|
|
10135
|
-
const text = sourceCode.getText(value);
|
|
10136
|
-
if (!texts.includes(text)) {
|
|
10137
|
-
texts.push(text);
|
|
10138
|
-
}
|
|
10139
|
-
}
|
|
10140
|
-
return texts.length > 0 ? texts : null;
|
|
10141
|
-
}
|
|
10142
10127
|
function resolvesToLocalEnum(node, scope) {
|
|
10143
10128
|
let current = scope;
|
|
10144
10129
|
while (current !== null) {
|
|
@@ -10170,7 +10155,6 @@ var no_zod_native_enum_default = createRule({
|
|
|
10170
10155
|
documentation: NO_ZOD_NATIVE_ENUM_DOCUMENTATION,
|
|
10171
10156
|
meta: {
|
|
10172
10157
|
type: "suggestion",
|
|
10173
|
-
fixable: "code",
|
|
10174
10158
|
docs: {
|
|
10175
10159
|
description: 'Disallow `z.nativeEnum()` (and `z.enum()` over a TypeScript enum); use `z.enum(["a", "b"])` with a string-literal union instead.'
|
|
10176
10160
|
},
|
|
@@ -10215,30 +10199,6 @@ var no_zod_native_enum_default = createRule({
|
|
|
10215
10199
|
}
|
|
10216
10200
|
return false;
|
|
10217
10201
|
}
|
|
10218
|
-
function buildFix(node) {
|
|
10219
|
-
const callee = node.callee;
|
|
10220
|
-
if (callee.type !== import_utils56.AST_NODE_TYPES.MemberExpression || callee.property.type !== import_utils56.AST_NODE_TYPES.Identifier) {
|
|
10221
|
-
return null;
|
|
10222
|
-
}
|
|
10223
|
-
const arg = node.arguments[0];
|
|
10224
|
-
if (arg === void 0 || node.arguments.length !== 1 || arg.type === import_utils56.AST_NODE_TYPES.SpreadElement) {
|
|
10225
|
-
return null;
|
|
10226
|
-
}
|
|
10227
|
-
const inner = unwrap2(arg);
|
|
10228
|
-
if (inner.type !== import_utils56.AST_NODE_TYPES.ObjectExpression) {
|
|
10229
|
-
return null;
|
|
10230
|
-
}
|
|
10231
|
-
const values = stringValueTexts(inner, sourceCode);
|
|
10232
|
-
if (values === null) {
|
|
10233
|
-
return null;
|
|
10234
|
-
}
|
|
10235
|
-
const property = callee.property;
|
|
10236
|
-
const replacementArg = `[${values.join(", ")}]`;
|
|
10237
|
-
return (fixer) => [
|
|
10238
|
-
fixer.replaceText(property, "enum"),
|
|
10239
|
-
fixer.replaceText(arg, replacementArg)
|
|
10240
|
-
];
|
|
10241
|
-
}
|
|
10242
10202
|
return {
|
|
10243
10203
|
ImportDeclaration(node) {
|
|
10244
10204
|
if (!isZodModule2(node.source.value)) {
|
|
@@ -10259,11 +10219,9 @@ var no_zod_native_enum_default = createRule({
|
|
|
10259
10219
|
},
|
|
10260
10220
|
CallExpression(node) {
|
|
10261
10221
|
if (isZodMemberCall(node, "nativeEnum")) {
|
|
10262
|
-
const fix = buildFix(node);
|
|
10263
10222
|
context.report({
|
|
10264
10223
|
node,
|
|
10265
|
-
messageId: "nativeEnum"
|
|
10266
|
-
...fix === null ? {} : { fix }
|
|
10224
|
+
messageId: "nativeEnum"
|
|
10267
10225
|
});
|
|
10268
10226
|
return;
|
|
10269
10227
|
}
|
|
@@ -12870,11 +12828,11 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
|
12870
12828
|
rationale: "One multi-value literal expresses the same closed value domain without repeated schema wrappers.",
|
|
12871
12829
|
remediation: "Replace the union with z.literal([value1, value2, ...]).",
|
|
12872
12830
|
category: "maintainability",
|
|
12873
|
-
autofix: "
|
|
12831
|
+
autofix: "none",
|
|
12874
12832
|
limitations: [
|
|
12875
12833
|
"Bare zod imports are analyzed only when the rule option explicitly declares zodMajorVersion: 4; explicit zod/v4 entrypoints are self-declaring.",
|
|
12876
12834
|
"All-string domains are left to zod/prefer-enum-over-literal-union.",
|
|
12877
|
-
"
|
|
12835
|
+
"Migration is manual: ZodLiteral and ZodUnion expose different introspection APIs and validation error shapes even when they accept the same values."
|
|
12878
12836
|
],
|
|
12879
12837
|
examples: [
|
|
12880
12838
|
{
|
|
@@ -12897,10 +12855,6 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
|
12897
12855
|
path: "src/schema.ts",
|
|
12898
12856
|
source: "import { z } from 'zod'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
|
|
12899
12857
|
}],
|
|
12900
|
-
fixedFiles: [{
|
|
12901
|
-
path: "src/schema.ts",
|
|
12902
|
-
source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
|
|
12903
|
-
}],
|
|
12904
12858
|
focusPath: "src/schema.ts",
|
|
12905
12859
|
expectedCount: 1,
|
|
12906
12860
|
public: true
|
|
@@ -12930,7 +12884,6 @@ var prefer_multi_value_zod_literal_default = createRule({
|
|
|
12930
12884
|
documentation: PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION,
|
|
12931
12885
|
meta: {
|
|
12932
12886
|
type: "suggestion",
|
|
12933
|
-
fixable: "code",
|
|
12934
12887
|
docs: {
|
|
12935
12888
|
description: "Use the Zod 4 multi-value literal API instead of a union of literal schemas."
|
|
12936
12889
|
},
|
|
@@ -12994,15 +12947,10 @@ var prefer_multi_value_zod_literal_default = createRule({
|
|
|
12994
12947
|
}
|
|
12995
12948
|
if (values.every(isStaticString)) return;
|
|
12996
12949
|
const namespace = node.callee.object.name;
|
|
12997
|
-
const hasComments = context.sourceCode.getCommentsInside(node).length > 0;
|
|
12998
12950
|
context.report({
|
|
12999
12951
|
node,
|
|
13000
12952
|
messageId: "useMultiValueLiteral",
|
|
13001
|
-
data: { zod: namespace }
|
|
13002
|
-
fix: hasComments ? null : (fixer) => fixer.replaceText(
|
|
13003
|
-
node,
|
|
13004
|
-
`${namespace}.literal([${values.map((value) => context.sourceCode.getText(value)).join(", ")}])`
|
|
13005
|
-
)
|
|
12953
|
+
data: { zod: namespace }
|
|
13006
12954
|
});
|
|
13007
12955
|
}
|
|
13008
12956
|
};
|
|
@@ -15433,13 +15381,14 @@ var MIN_RUN_LENGTH = 2;
|
|
|
15433
15381
|
var PREFER_WHOLE_OBJECT_ASSERTION_DOCUMENTATION = {
|
|
15434
15382
|
summary: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together.",
|
|
15435
15383
|
rationale: "One whole-object assertion presents related expectations together and produces a complete structural diff.",
|
|
15436
|
-
remediation: "
|
|
15384
|
+
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.",
|
|
15437
15385
|
category: "testing",
|
|
15438
15386
|
aliases: ["strict-test-assertions"],
|
|
15439
|
-
autofix: "
|
|
15387
|
+
autofix: "none",
|
|
15388
|
+
limitations: ["No automatic rewrite: whole-object matching can require previously absent properties and change observable getter or proxy reads."],
|
|
15440
15389
|
examples: [
|
|
15441
15390
|
{ 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 },
|
|
15442
|
-
{ id: "member-run", title: "
|
|
15391
|
+
{ 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 }
|
|
15443
15392
|
]
|
|
15444
15393
|
};
|
|
15445
15394
|
function literalText(node, getText) {
|
|
@@ -15495,9 +15444,8 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15495
15444
|
docs: {
|
|
15496
15445
|
description: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together."
|
|
15497
15446
|
},
|
|
15498
|
-
fixable: "code",
|
|
15499
15447
|
messages: {
|
|
15500
|
-
combineAssertions: "
|
|
15448
|
+
combineAssertions: "Consider combining these {{count}} assertions on `{{receiver}}` with `toMatchObject` when structural matching preserves property presence and getter or proxy behavior.",
|
|
15501
15449
|
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 ])`."
|
|
15502
15450
|
},
|
|
15503
15451
|
schema: []
|
|
@@ -15568,11 +15516,6 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15568
15516
|
expectedIsLiteral: literal !== null
|
|
15569
15517
|
};
|
|
15570
15518
|
}
|
|
15571
|
-
function hasInterveningComment(run) {
|
|
15572
|
-
return run.some(
|
|
15573
|
-
(assertion, index) => sourceCode.getCommentsInside(assertion.statement).length > 0 || index > 0 && sourceCode.getCommentsBefore(assertion.statement).length > 0
|
|
15574
|
-
);
|
|
15575
|
-
}
|
|
15576
15519
|
function reportPropertyRun(run) {
|
|
15577
15520
|
const tree = /* @__PURE__ */ new Map();
|
|
15578
15521
|
const paths = [];
|
|
@@ -15619,16 +15562,10 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15619
15562
|
return;
|
|
15620
15563
|
}
|
|
15621
15564
|
const receiverText = `${sourceCode.getText(first.receiver)}${commonPrefix.map((name) => `.${name}`).join("")}`;
|
|
15622
|
-
const renderTree = (value) => [...value.entries()].map(([name, child]) => `${name}: ${child instanceof Map ? `{ ${renderTree(child)} }` : child}`).join(", ");
|
|
15623
|
-
const properties = renderTree(tree);
|
|
15624
15565
|
context.report({
|
|
15625
15566
|
node: first.statement,
|
|
15626
15567
|
messageId: "combineAssertions",
|
|
15627
|
-
data: { count: String(run.length), receiver: receiverText }
|
|
15628
|
-
fix: hasInterveningComment(run) ? null : (fixer) => [
|
|
15629
|
-
fixer.replaceText(first.statement, `expect(${receiverText}).toMatchObject({ ${properties} });`),
|
|
15630
|
-
...run.slice(1).map((assertion) => fixer.remove(assertion.statement))
|
|
15631
|
-
]
|
|
15568
|
+
data: { count: String(run.length), receiver: receiverText }
|
|
15632
15569
|
});
|
|
15633
15570
|
}
|
|
15634
15571
|
function reportIndexRun(run) {
|
|
@@ -19752,7 +19689,7 @@ var RULES = {
|
|
|
19752
19689
|
};
|
|
19753
19690
|
var meta = {
|
|
19754
19691
|
name: "@sarj/eslint-plugin",
|
|
19755
|
-
version: "15.17.
|
|
19692
|
+
version: "15.17.8"
|
|
19756
19693
|
};
|
|
19757
19694
|
var APPLICATION_ONLY_RULES = [];
|
|
19758
19695
|
var LIBRARY_IMPORT_POLICY = ["error", {
|
|
@@ -19763,6 +19700,7 @@ var ADVISORY_RULES = [
|
|
|
19763
19700
|
"@sarj/excessive-commentary",
|
|
19764
19701
|
"@sarj/no-bespoke-api-case-conversion",
|
|
19765
19702
|
"@sarj/no-restated-comment",
|
|
19703
|
+
"@sarj/no-restated-jsdoc",
|
|
19766
19704
|
"@sarj/prefer-millisecond-control-duration-schema",
|
|
19767
19705
|
"@sarj/prefer-module-level-refined-schema",
|
|
19768
19706
|
"@sarj/prefer-multi-value-zod-literal",
|
|
@@ -19773,6 +19711,7 @@ var ADVISORY_RULES = [
|
|
|
19773
19711
|
"@sarj/prefer-nullish-filter-predicate",
|
|
19774
19712
|
"@sarj/prefer-shared-zod-enum",
|
|
19775
19713
|
"@sarj/prefer-switch-for-repeated-equality",
|
|
19714
|
+
"@sarj/prefer-whole-object-assertion",
|
|
19776
19715
|
"@sarj/require-interface-for-exported-class",
|
|
19777
19716
|
"@sarj/require-sql-access-class",
|
|
19778
19717
|
"@sarj/sole-export-matches-filename"
|
|
@@ -19811,7 +19750,7 @@ var RECOMMENDED_RULES = {
|
|
|
19811
19750
|
"@sarj/no-repeated-string-literal": "error",
|
|
19812
19751
|
"@sarj/no-router-refresh-polling": "error",
|
|
19813
19752
|
"@sarj/no-restated-comment": "warn",
|
|
19814
|
-
"@sarj/no-restated-jsdoc": "
|
|
19753
|
+
"@sarj/no-restated-jsdoc": "warn",
|
|
19815
19754
|
"@sarj/no-secret-in-log": "error",
|
|
19816
19755
|
"@sarj/no-server-env-in-client-component": "error",
|
|
19817
19756
|
"@sarj/no-select-star": "error",
|
|
@@ -19851,7 +19790,7 @@ var RECOMMENDED_RULES = {
|
|
|
19851
19790
|
"@sarj/prefer-switch-for-repeated-equality": "warn",
|
|
19852
19791
|
"@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
|
|
19853
19792
|
"@sarj/prefer-server-actions": "error",
|
|
19854
|
-
"@sarj/prefer-whole-object-assertion": "
|
|
19793
|
+
"@sarj/prefer-whole-object-assertion": "warn",
|
|
19855
19794
|
"@sarj/repeated-static-call-cases": "error",
|
|
19856
19795
|
"@sarj/prefer-zod-infer": "error",
|
|
19857
19796
|
"@sarj/require-assert-never": "error",
|
|
@@ -19907,7 +19846,7 @@ var STRICT_RULES = {
|
|
|
19907
19846
|
"@sarj/no-repeated-string-literal": "error",
|
|
19908
19847
|
"@sarj/no-router-refresh-polling": "error",
|
|
19909
19848
|
"@sarj/no-restated-comment": "warn",
|
|
19910
|
-
"@sarj/no-restated-jsdoc": "
|
|
19849
|
+
"@sarj/no-restated-jsdoc": "warn",
|
|
19911
19850
|
"@sarj/no-secret-in-log": "error",
|
|
19912
19851
|
"@sarj/no-server-env-in-client-component": "error",
|
|
19913
19852
|
"@sarj/no-select-star": "error",
|
|
@@ -19948,7 +19887,7 @@ var STRICT_RULES = {
|
|
|
19948
19887
|
"@sarj/prefer-switch-for-repeated-equality": "warn",
|
|
19949
19888
|
"@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
|
|
19950
19889
|
"@sarj/prefer-server-actions": "error",
|
|
19951
|
-
"@sarj/prefer-whole-object-assertion": "
|
|
19890
|
+
"@sarj/prefer-whole-object-assertion": "warn",
|
|
19952
19891
|
"@sarj/repeated-static-call-cases": "error",
|
|
19953
19892
|
"@sarj/prefer-zod-infer": "error",
|
|
19954
19893
|
"@sarj/require-assert-never": "error",
|
package/dist/index.d.cts
CHANGED
|
@@ -320,7 +320,7 @@ declare const RULES: {
|
|
|
320
320
|
/** @deprecated All repositories use one policy; retained for import compatibility. */
|
|
321
321
|
declare const APPLICATION_ONLY_RULES: readonly [];
|
|
322
322
|
/** Rules staged as non-blocking warnings while corpus adoption evidence accumulates. */
|
|
323
|
-
declare const ADVISORY_RULES: readonly ["@sarj/excessive-commentary", "@sarj/no-bespoke-api-case-conversion", "@sarj/no-restated-comment", "@sarj/prefer-millisecond-control-duration-schema", "@sarj/prefer-module-level-refined-schema", "@sarj/prefer-multi-value-zod-literal", "@sarj/prefer-named-callback-domain", "@sarj/prefer-named-complex-return-type", "@sarj/prefer-node-crypto-hash", "@sarj/prefer-node-fs-promises", "@sarj/prefer-nullish-filter-predicate", "@sarj/prefer-shared-zod-enum", "@sarj/prefer-switch-for-repeated-equality", "@sarj/require-interface-for-exported-class", "@sarj/require-sql-access-class", "@sarj/sole-export-matches-filename"];
|
|
323
|
+
declare const ADVISORY_RULES: readonly ["@sarj/excessive-commentary", "@sarj/no-bespoke-api-case-conversion", "@sarj/no-restated-comment", "@sarj/no-restated-jsdoc", "@sarj/prefer-millisecond-control-duration-schema", "@sarj/prefer-module-level-refined-schema", "@sarj/prefer-multi-value-zod-literal", "@sarj/prefer-named-callback-domain", "@sarj/prefer-named-complex-return-type", "@sarj/prefer-node-crypto-hash", "@sarj/prefer-node-fs-promises", "@sarj/prefer-nullish-filter-predicate", "@sarj/prefer-shared-zod-enum", "@sarj/prefer-switch-for-repeated-equality", "@sarj/prefer-whole-object-assertion", "@sarj/require-interface-for-exported-class", "@sarj/require-sql-access-class", "@sarj/sole-export-matches-filename"];
|
|
324
324
|
declare const RECOMMENDED_RULES: {
|
|
325
325
|
readonly "no-restricted-imports": readonly ["error", {
|
|
326
326
|
readonly paths: {
|
|
@@ -538,7 +538,7 @@ declare const RECOMMENDED_RULES: {
|
|
|
538
538
|
readonly "@sarj/no-repeated-string-literal": "error";
|
|
539
539
|
readonly "@sarj/no-router-refresh-polling": "error";
|
|
540
540
|
readonly "@sarj/no-restated-comment": "warn";
|
|
541
|
-
readonly "@sarj/no-restated-jsdoc": "
|
|
541
|
+
readonly "@sarj/no-restated-jsdoc": "warn";
|
|
542
542
|
readonly "@sarj/no-secret-in-log": "error";
|
|
543
543
|
readonly "@sarj/no-server-env-in-client-component": "error";
|
|
544
544
|
readonly "@sarj/no-select-star": "error";
|
|
@@ -582,7 +582,7 @@ declare const RECOMMENDED_RULES: {
|
|
|
582
582
|
readonly requireSemanticTokens: true;
|
|
583
583
|
}];
|
|
584
584
|
readonly "@sarj/prefer-server-actions": "error";
|
|
585
|
-
readonly "@sarj/prefer-whole-object-assertion": "
|
|
585
|
+
readonly "@sarj/prefer-whole-object-assertion": "warn";
|
|
586
586
|
readonly "@sarj/repeated-static-call-cases": "error";
|
|
587
587
|
readonly "@sarj/prefer-zod-infer": "error";
|
|
588
588
|
readonly "@sarj/require-assert-never": "error";
|
|
@@ -821,7 +821,7 @@ declare const STRICT_RULES: {
|
|
|
821
821
|
readonly "@sarj/no-repeated-string-literal": "error";
|
|
822
822
|
readonly "@sarj/no-router-refresh-polling": "error";
|
|
823
823
|
readonly "@sarj/no-restated-comment": "warn";
|
|
824
|
-
readonly "@sarj/no-restated-jsdoc": "
|
|
824
|
+
readonly "@sarj/no-restated-jsdoc": "warn";
|
|
825
825
|
readonly "@sarj/no-secret-in-log": "error";
|
|
826
826
|
readonly "@sarj/no-server-env-in-client-component": "error";
|
|
827
827
|
readonly "@sarj/no-select-star": "error";
|
|
@@ -866,7 +866,7 @@ declare const STRICT_RULES: {
|
|
|
866
866
|
readonly requireSemanticTokens: true;
|
|
867
867
|
}];
|
|
868
868
|
readonly "@sarj/prefer-server-actions": "error";
|
|
869
|
-
readonly "@sarj/prefer-whole-object-assertion": "
|
|
869
|
+
readonly "@sarj/prefer-whole-object-assertion": "warn";
|
|
870
870
|
readonly "@sarj/repeated-static-call-cases": "error";
|
|
871
871
|
readonly "@sarj/prefer-zod-infer": "error";
|
|
872
872
|
readonly "@sarj/require-assert-never": "error";
|
|
@@ -893,7 +893,7 @@ type FlatPreset = {
|
|
|
893
893
|
declare const PLUGIN: {
|
|
894
894
|
readonly meta: {
|
|
895
895
|
readonly name: "@sarj/eslint-plugin";
|
|
896
|
-
readonly version: "15.17.
|
|
896
|
+
readonly version: "15.17.8";
|
|
897
897
|
};
|
|
898
898
|
readonly rules: {
|
|
899
899
|
readonly "excessive-commentary": DocumentedRule<readonly [], "excessive">;
|
package/dist/index.d.ts
CHANGED
|
@@ -320,7 +320,7 @@ declare const RULES: {
|
|
|
320
320
|
/** @deprecated All repositories use one policy; retained for import compatibility. */
|
|
321
321
|
declare const APPLICATION_ONLY_RULES: readonly [];
|
|
322
322
|
/** Rules staged as non-blocking warnings while corpus adoption evidence accumulates. */
|
|
323
|
-
declare const ADVISORY_RULES: readonly ["@sarj/excessive-commentary", "@sarj/no-bespoke-api-case-conversion", "@sarj/no-restated-comment", "@sarj/prefer-millisecond-control-duration-schema", "@sarj/prefer-module-level-refined-schema", "@sarj/prefer-multi-value-zod-literal", "@sarj/prefer-named-callback-domain", "@sarj/prefer-named-complex-return-type", "@sarj/prefer-node-crypto-hash", "@sarj/prefer-node-fs-promises", "@sarj/prefer-nullish-filter-predicate", "@sarj/prefer-shared-zod-enum", "@sarj/prefer-switch-for-repeated-equality", "@sarj/require-interface-for-exported-class", "@sarj/require-sql-access-class", "@sarj/sole-export-matches-filename"];
|
|
323
|
+
declare const ADVISORY_RULES: readonly ["@sarj/excessive-commentary", "@sarj/no-bespoke-api-case-conversion", "@sarj/no-restated-comment", "@sarj/no-restated-jsdoc", "@sarj/prefer-millisecond-control-duration-schema", "@sarj/prefer-module-level-refined-schema", "@sarj/prefer-multi-value-zod-literal", "@sarj/prefer-named-callback-domain", "@sarj/prefer-named-complex-return-type", "@sarj/prefer-node-crypto-hash", "@sarj/prefer-node-fs-promises", "@sarj/prefer-nullish-filter-predicate", "@sarj/prefer-shared-zod-enum", "@sarj/prefer-switch-for-repeated-equality", "@sarj/prefer-whole-object-assertion", "@sarj/require-interface-for-exported-class", "@sarj/require-sql-access-class", "@sarj/sole-export-matches-filename"];
|
|
324
324
|
declare const RECOMMENDED_RULES: {
|
|
325
325
|
readonly "no-restricted-imports": readonly ["error", {
|
|
326
326
|
readonly paths: {
|
|
@@ -538,7 +538,7 @@ declare const RECOMMENDED_RULES: {
|
|
|
538
538
|
readonly "@sarj/no-repeated-string-literal": "error";
|
|
539
539
|
readonly "@sarj/no-router-refresh-polling": "error";
|
|
540
540
|
readonly "@sarj/no-restated-comment": "warn";
|
|
541
|
-
readonly "@sarj/no-restated-jsdoc": "
|
|
541
|
+
readonly "@sarj/no-restated-jsdoc": "warn";
|
|
542
542
|
readonly "@sarj/no-secret-in-log": "error";
|
|
543
543
|
readonly "@sarj/no-server-env-in-client-component": "error";
|
|
544
544
|
readonly "@sarj/no-select-star": "error";
|
|
@@ -582,7 +582,7 @@ declare const RECOMMENDED_RULES: {
|
|
|
582
582
|
readonly requireSemanticTokens: true;
|
|
583
583
|
}];
|
|
584
584
|
readonly "@sarj/prefer-server-actions": "error";
|
|
585
|
-
readonly "@sarj/prefer-whole-object-assertion": "
|
|
585
|
+
readonly "@sarj/prefer-whole-object-assertion": "warn";
|
|
586
586
|
readonly "@sarj/repeated-static-call-cases": "error";
|
|
587
587
|
readonly "@sarj/prefer-zod-infer": "error";
|
|
588
588
|
readonly "@sarj/require-assert-never": "error";
|
|
@@ -821,7 +821,7 @@ declare const STRICT_RULES: {
|
|
|
821
821
|
readonly "@sarj/no-repeated-string-literal": "error";
|
|
822
822
|
readonly "@sarj/no-router-refresh-polling": "error";
|
|
823
823
|
readonly "@sarj/no-restated-comment": "warn";
|
|
824
|
-
readonly "@sarj/no-restated-jsdoc": "
|
|
824
|
+
readonly "@sarj/no-restated-jsdoc": "warn";
|
|
825
825
|
readonly "@sarj/no-secret-in-log": "error";
|
|
826
826
|
readonly "@sarj/no-server-env-in-client-component": "error";
|
|
827
827
|
readonly "@sarj/no-select-star": "error";
|
|
@@ -866,7 +866,7 @@ declare const STRICT_RULES: {
|
|
|
866
866
|
readonly requireSemanticTokens: true;
|
|
867
867
|
}];
|
|
868
868
|
readonly "@sarj/prefer-server-actions": "error";
|
|
869
|
-
readonly "@sarj/prefer-whole-object-assertion": "
|
|
869
|
+
readonly "@sarj/prefer-whole-object-assertion": "warn";
|
|
870
870
|
readonly "@sarj/repeated-static-call-cases": "error";
|
|
871
871
|
readonly "@sarj/prefer-zod-infer": "error";
|
|
872
872
|
readonly "@sarj/require-assert-never": "error";
|
|
@@ -893,7 +893,7 @@ type FlatPreset = {
|
|
|
893
893
|
declare const PLUGIN: {
|
|
894
894
|
readonly meta: {
|
|
895
895
|
readonly name: "@sarj/eslint-plugin";
|
|
896
|
-
readonly version: "15.17.
|
|
896
|
+
readonly version: "15.17.8";
|
|
897
897
|
};
|
|
898
898
|
readonly rules: {
|
|
899
899
|
readonly "excessive-commentary": DocumentedRule<readonly [], "excessive">;
|
package/dist/index.js
CHANGED
|
@@ -6828,22 +6828,23 @@ var no_restated_comment_default = createRule({
|
|
|
6828
6828
|
// src/rules/no-restated-jsdoc.ts
|
|
6829
6829
|
import { AST_NODE_TYPES as AST_NODE_TYPES29 } from "@typescript-eslint/utils";
|
|
6830
6830
|
var NO_RESTATED_JSDOC_DOCUMENTATION = {
|
|
6831
|
-
summary: "Flag
|
|
6831
|
+
summary: "Flag JSDoc prose that appears to repeat declaration names without adding behavioral information.",
|
|
6832
6832
|
rationale: "Signature-only JSDoc duplicates type information and drifts without helping callers.",
|
|
6833
6833
|
remediation: "Delete the block or document behavior, constraints, failures, or context the signature cannot express.",
|
|
6834
6834
|
category: "maintainability",
|
|
6835
6835
|
aliases: ["jsdoc-restates-signature"],
|
|
6836
6836
|
autofix: "suggestion",
|
|
6837
|
-
limitations: ["Generated files, detached blocks, unknown tags, empty blocks, and JSDoc with information absent from the signature are excluded."],
|
|
6837
|
+
limitations: ["Generated files, detached blocks, intervening comments, unknown tags, explicit JSDoc type payloads, empty blocks, and JSDoc with information absent from the signature are excluded.", "Negation, conditions, constraints, sentinel values, numeric details, and quoted text conservatively preserve the block, even when a declaration name contains the same words."],
|
|
6838
6838
|
examples: [
|
|
6839
6839
|
{ id: "behavioral-jsdoc", title: "Document behavior absent from the signature", outcome: "no-match", files: [{ path: "src/users.ts", source: "/** Get the user while bypassing the read replica. */\nexport function getUser(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 0, public: true },
|
|
6840
|
-
{ id: "signature-jsdoc", title: "Remove JSDoc that only repeats the signature", outcome: "match", files: [{ path: "src/users.ts", source: "/** Get the user by id. */\nexport function getUserById(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true }
|
|
6840
|
+
{ id: "signature-jsdoc", title: "Remove JSDoc that only repeats the signature", outcome: "match", files: [{ path: "src/users.ts", source: "/** Get the user by id. */\nexport function getUserById(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true },
|
|
6841
|
+
{ id: "negated-behavior", scenarioId: "negation", title: "Keep behavior even when its words resemble the declaration", outcome: "no-match", files: [{ path: "src/users.ts", source: "/** Does not cache the user. */\nexport function cacheUser(user: unknown) { return user; }" }], focusPath: "src/users.ts", expectedCount: 0, public: true },
|
|
6842
|
+
{ id: "repeated-behavior-name", scenarioId: "negation", title: "Review prose that merely repeats the declaration name", outcome: "match", files: [{ path: "src/users.ts", source: "/** Cache the user. */\nexport function cacheUser(user: unknown) { return user; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true }
|
|
6841
6843
|
]
|
|
6842
6844
|
};
|
|
6843
6845
|
var MODELLED_TAGS = /* @__PURE__ */ new Set([
|
|
6844
6846
|
"arg",
|
|
6845
6847
|
"argument",
|
|
6846
|
-
"async",
|
|
6847
6848
|
"description",
|
|
6848
6849
|
"param",
|
|
6849
6850
|
"return",
|
|
@@ -6862,7 +6863,8 @@ var STOPWORDS2 = new Set(
|
|
|
6862
6863
|
returns returning result optional required default true false null undefined
|
|
6863
6864
|
string number boolean array list promise`.split(/\s+/)
|
|
6864
6865
|
);
|
|
6865
|
-
var WORD_RE2 =
|
|
6866
|
+
var WORD_RE2 = new RegExp("\\p{L}+", "gu");
|
|
6867
|
+
var BEHAVIORAL_PROSE_RE = /\b(?:not|no|never|if|when|unless|must|should|may|can|could|would|optional|required|default|true|false|null|undefined|before|after|until|once|again|only|always)\b|\d|["'`<>=+*/%&|!~^-]/i;
|
|
6866
6868
|
function parseJsDoc(value) {
|
|
6867
6869
|
const lines = value.replace(/^\*/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, ""));
|
|
6868
6870
|
const description = [];
|
|
@@ -6882,12 +6884,13 @@ function parseJsDoc(value) {
|
|
|
6882
6884
|
return { description: description.join("\n").trim(), tags };
|
|
6883
6885
|
}
|
|
6884
6886
|
function covered(text, known) {
|
|
6887
|
+
if (BEHAVIORAL_PROSE_RE.test(text)) return false;
|
|
6885
6888
|
const stems = /* @__PURE__ */ new Set();
|
|
6886
6889
|
for (const token of known) stems.add(stem(token));
|
|
6887
6890
|
return proseTokens(text).every((word) => known.has(word) || stems.has(stem(word)));
|
|
6888
6891
|
}
|
|
6889
6892
|
function proseTokens(text) {
|
|
6890
|
-
return (text.match(WORD_RE2) ?? []).map((word) => word.toLowerCase()).filter((word) =>
|
|
6893
|
+
return (text.match(WORD_RE2) ?? []).map((word) => word.toLowerCase()).filter((word) => !STOPWORDS2.has(word));
|
|
6891
6894
|
}
|
|
6892
6895
|
function declarationNames(node) {
|
|
6893
6896
|
switch (node.type) {
|
|
@@ -6944,11 +6947,11 @@ var no_restated_jsdoc_default = createRule({
|
|
|
6944
6947
|
type: "suggestion",
|
|
6945
6948
|
hasSuggestions: true,
|
|
6946
6949
|
docs: {
|
|
6947
|
-
description: "Flag
|
|
6950
|
+
description: "Flag JSDoc prose that appears to repeat declaration names without adding behavioral information."
|
|
6948
6951
|
},
|
|
6949
6952
|
schema: [],
|
|
6950
6953
|
messages: {
|
|
6951
|
-
restatesSignature: "JSDoc
|
|
6954
|
+
restatesSignature: "JSDoc appears to repeat declaration names \u2014 consider removing repetition. Keep type contracts, constraints, failures, and rationale.",
|
|
6952
6955
|
deleteBlock: "Delete the JSDoc block."
|
|
6953
6956
|
}
|
|
6954
6957
|
},
|
|
@@ -6971,8 +6974,9 @@ var no_restated_jsdoc_default = createRule({
|
|
|
6971
6974
|
const tagNames = new Set(tags.map((tag) => tag.name));
|
|
6972
6975
|
if ([...tagNames].some((name) => !MODELLED_TAGS.has(name))) continue;
|
|
6973
6976
|
if (isProtected(describedText)) continue;
|
|
6974
|
-
const token = sourceCode.getTokenAfter(comment, { includeComments:
|
|
6977
|
+
const token = sourceCode.getTokenAfter(comment, { includeComments: true });
|
|
6975
6978
|
if (token === null || token.loc.start.line !== comment.loc.end.line + 1) continue;
|
|
6979
|
+
if (token.type === "Line" || token.type === "Block") continue;
|
|
6976
6980
|
let node = sourceCode.getNodeByRangeIndex(token.range[0]);
|
|
6977
6981
|
let declaration = null;
|
|
6978
6982
|
while (node != null && node.type !== AST_NODE_TYPES29.Program) {
|
|
@@ -6983,6 +6987,8 @@ var no_restated_jsdoc_default = createRule({
|
|
|
6983
6987
|
if (declaration === null) continue;
|
|
6984
6988
|
const paramTags = tags.filter((tag) => PARAM_TAGS.has(tag.name));
|
|
6985
6989
|
const returnTags = tags.filter((tag) => RETURN_TAGS.has(tag.name));
|
|
6990
|
+
if ([...paramTags, ...returnTags].some((tag) => /^\s*\{/.test(tag.text))) continue;
|
|
6991
|
+
if (paramTags.some((tag) => /^\s*(?:\[|[A-Za-z_$][\w$]*\.)/.test(tag.text))) continue;
|
|
6986
6992
|
if (describedText.length === 0 && paramTags.length === 0 && returnTags.length === 0) {
|
|
6987
6993
|
continue;
|
|
6988
6994
|
}
|
|
@@ -10040,8 +10046,8 @@ var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
|
|
|
10040
10046
|
rationale: "Wrapping a TypeScript enum preserves its emitted runtime object and duplicates the schema's value definition across two constructs.",
|
|
10041
10047
|
remediation: "Pass string literals directly to `z.enum` and derive the TypeScript type with `z.infer`.",
|
|
10042
10048
|
category: "maintainability",
|
|
10043
|
-
autofix: "
|
|
10044
|
-
limitations: ["
|
|
10049
|
+
autofix: "none",
|
|
10050
|
+
limitations: ["Migration is manual: replacing an enum-like object with a value array changes the public schema.enum keys and can affect consumers."],
|
|
10045
10051
|
examples: [
|
|
10046
10052
|
{
|
|
10047
10053
|
id: "zod-literal-enum",
|
|
@@ -10059,8 +10065,7 @@ var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
|
|
|
10059
10065
|
files: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.nativeEnum({ Active: "active", Inactive: "inactive" });' }],
|
|
10060
10066
|
focusPath: "src/status.ts",
|
|
10061
10067
|
expectedCount: 1,
|
|
10062
|
-
public: true
|
|
10063
|
-
fixedFiles: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.enum(["active", "inactive"]);' }]
|
|
10068
|
+
public: true
|
|
10064
10069
|
}
|
|
10065
10070
|
]
|
|
10066
10071
|
};
|
|
@@ -10085,26 +10090,6 @@ function unwrap2(node) {
|
|
|
10085
10090
|
}
|
|
10086
10091
|
return node;
|
|
10087
10092
|
}
|
|
10088
|
-
function stringValueTexts(node, sourceCode) {
|
|
10089
|
-
const texts = [];
|
|
10090
|
-
for (const prop of node.properties) {
|
|
10091
|
-
if (prop.type !== AST_NODE_TYPES41.Property) {
|
|
10092
|
-
return null;
|
|
10093
|
-
}
|
|
10094
|
-
if (prop.computed || prop.shorthand || prop.method || prop.kind !== "init") {
|
|
10095
|
-
return null;
|
|
10096
|
-
}
|
|
10097
|
-
const value = prop.value;
|
|
10098
|
-
if (value.type !== AST_NODE_TYPES41.Literal || typeof value.value !== "string") {
|
|
10099
|
-
return null;
|
|
10100
|
-
}
|
|
10101
|
-
const text = sourceCode.getText(value);
|
|
10102
|
-
if (!texts.includes(text)) {
|
|
10103
|
-
texts.push(text);
|
|
10104
|
-
}
|
|
10105
|
-
}
|
|
10106
|
-
return texts.length > 0 ? texts : null;
|
|
10107
|
-
}
|
|
10108
10093
|
function resolvesToLocalEnum(node, scope) {
|
|
10109
10094
|
let current = scope;
|
|
10110
10095
|
while (current !== null) {
|
|
@@ -10136,7 +10121,6 @@ var no_zod_native_enum_default = createRule({
|
|
|
10136
10121
|
documentation: NO_ZOD_NATIVE_ENUM_DOCUMENTATION,
|
|
10137
10122
|
meta: {
|
|
10138
10123
|
type: "suggestion",
|
|
10139
|
-
fixable: "code",
|
|
10140
10124
|
docs: {
|
|
10141
10125
|
description: 'Disallow `z.nativeEnum()` (and `z.enum()` over a TypeScript enum); use `z.enum(["a", "b"])` with a string-literal union instead.'
|
|
10142
10126
|
},
|
|
@@ -10181,30 +10165,6 @@ var no_zod_native_enum_default = createRule({
|
|
|
10181
10165
|
}
|
|
10182
10166
|
return false;
|
|
10183
10167
|
}
|
|
10184
|
-
function buildFix(node) {
|
|
10185
|
-
const callee = node.callee;
|
|
10186
|
-
if (callee.type !== AST_NODE_TYPES41.MemberExpression || callee.property.type !== AST_NODE_TYPES41.Identifier) {
|
|
10187
|
-
return null;
|
|
10188
|
-
}
|
|
10189
|
-
const arg = node.arguments[0];
|
|
10190
|
-
if (arg === void 0 || node.arguments.length !== 1 || arg.type === AST_NODE_TYPES41.SpreadElement) {
|
|
10191
|
-
return null;
|
|
10192
|
-
}
|
|
10193
|
-
const inner = unwrap2(arg);
|
|
10194
|
-
if (inner.type !== AST_NODE_TYPES41.ObjectExpression) {
|
|
10195
|
-
return null;
|
|
10196
|
-
}
|
|
10197
|
-
const values = stringValueTexts(inner, sourceCode);
|
|
10198
|
-
if (values === null) {
|
|
10199
|
-
return null;
|
|
10200
|
-
}
|
|
10201
|
-
const property = callee.property;
|
|
10202
|
-
const replacementArg = `[${values.join(", ")}]`;
|
|
10203
|
-
return (fixer) => [
|
|
10204
|
-
fixer.replaceText(property, "enum"),
|
|
10205
|
-
fixer.replaceText(arg, replacementArg)
|
|
10206
|
-
];
|
|
10207
|
-
}
|
|
10208
10168
|
return {
|
|
10209
10169
|
ImportDeclaration(node) {
|
|
10210
10170
|
if (!isZodModule2(node.source.value)) {
|
|
@@ -10225,11 +10185,9 @@ var no_zod_native_enum_default = createRule({
|
|
|
10225
10185
|
},
|
|
10226
10186
|
CallExpression(node) {
|
|
10227
10187
|
if (isZodMemberCall(node, "nativeEnum")) {
|
|
10228
|
-
const fix = buildFix(node);
|
|
10229
10188
|
context.report({
|
|
10230
10189
|
node,
|
|
10231
|
-
messageId: "nativeEnum"
|
|
10232
|
-
...fix === null ? {} : { fix }
|
|
10190
|
+
messageId: "nativeEnum"
|
|
10233
10191
|
});
|
|
10234
10192
|
return;
|
|
10235
10193
|
}
|
|
@@ -12848,11 +12806,11 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
|
12848
12806
|
rationale: "One multi-value literal expresses the same closed value domain without repeated schema wrappers.",
|
|
12849
12807
|
remediation: "Replace the union with z.literal([value1, value2, ...]).",
|
|
12850
12808
|
category: "maintainability",
|
|
12851
|
-
autofix: "
|
|
12809
|
+
autofix: "none",
|
|
12852
12810
|
limitations: [
|
|
12853
12811
|
"Bare zod imports are analyzed only when the rule option explicitly declares zodMajorVersion: 4; explicit zod/v4 entrypoints are self-declaring.",
|
|
12854
12812
|
"All-string domains are left to zod/prefer-enum-over-literal-union.",
|
|
12855
|
-
"
|
|
12813
|
+
"Migration is manual: ZodLiteral and ZodUnion expose different introspection APIs and validation error shapes even when they accept the same values."
|
|
12856
12814
|
],
|
|
12857
12815
|
examples: [
|
|
12858
12816
|
{
|
|
@@ -12875,10 +12833,6 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
|
|
|
12875
12833
|
path: "src/schema.ts",
|
|
12876
12834
|
source: "import { z } from 'zod'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
|
|
12877
12835
|
}],
|
|
12878
|
-
fixedFiles: [{
|
|
12879
|
-
path: "src/schema.ts",
|
|
12880
|
-
source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
|
|
12881
|
-
}],
|
|
12882
12836
|
focusPath: "src/schema.ts",
|
|
12883
12837
|
expectedCount: 1,
|
|
12884
12838
|
public: true
|
|
@@ -12908,7 +12862,6 @@ var prefer_multi_value_zod_literal_default = createRule({
|
|
|
12908
12862
|
documentation: PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION,
|
|
12909
12863
|
meta: {
|
|
12910
12864
|
type: "suggestion",
|
|
12911
|
-
fixable: "code",
|
|
12912
12865
|
docs: {
|
|
12913
12866
|
description: "Use the Zod 4 multi-value literal API instead of a union of literal schemas."
|
|
12914
12867
|
},
|
|
@@ -12972,15 +12925,10 @@ var prefer_multi_value_zod_literal_default = createRule({
|
|
|
12972
12925
|
}
|
|
12973
12926
|
if (values.every(isStaticString)) return;
|
|
12974
12927
|
const namespace = node.callee.object.name;
|
|
12975
|
-
const hasComments = context.sourceCode.getCommentsInside(node).length > 0;
|
|
12976
12928
|
context.report({
|
|
12977
12929
|
node,
|
|
12978
12930
|
messageId: "useMultiValueLiteral",
|
|
12979
|
-
data: { zod: namespace }
|
|
12980
|
-
fix: hasComments ? null : (fixer) => fixer.replaceText(
|
|
12981
|
-
node,
|
|
12982
|
-
`${namespace}.literal([${values.map((value) => context.sourceCode.getText(value)).join(", ")}])`
|
|
12983
|
-
)
|
|
12931
|
+
data: { zod: namespace }
|
|
12984
12932
|
});
|
|
12985
12933
|
}
|
|
12986
12934
|
};
|
|
@@ -15419,13 +15367,14 @@ var MIN_RUN_LENGTH = 2;
|
|
|
15419
15367
|
var PREFER_WHOLE_OBJECT_ASSERTION_DOCUMENTATION = {
|
|
15420
15368
|
summary: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together.",
|
|
15421
15369
|
rationale: "One whole-object assertion presents related expectations together and produces a complete structural diff.",
|
|
15422
|
-
remediation: "
|
|
15370
|
+
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.",
|
|
15423
15371
|
category: "testing",
|
|
15424
15372
|
aliases: ["strict-test-assertions"],
|
|
15425
|
-
autofix: "
|
|
15373
|
+
autofix: "none",
|
|
15374
|
+
limitations: ["No automatic rewrite: whole-object matching can require previously absent properties and change observable getter or proxy reads."],
|
|
15426
15375
|
examples: [
|
|
15427
15376
|
{ 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 },
|
|
15428
|
-
{ id: "member-run", title: "
|
|
15377
|
+
{ 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 }
|
|
15429
15378
|
]
|
|
15430
15379
|
};
|
|
15431
15380
|
function literalText(node, getText) {
|
|
@@ -15481,9 +15430,8 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15481
15430
|
docs: {
|
|
15482
15431
|
description: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together."
|
|
15483
15432
|
},
|
|
15484
|
-
fixable: "code",
|
|
15485
15433
|
messages: {
|
|
15486
|
-
combineAssertions: "
|
|
15434
|
+
combineAssertions: "Consider combining these {{count}} assertions on `{{receiver}}` with `toMatchObject` when structural matching preserves property presence and getter or proxy behavior.",
|
|
15487
15435
|
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 ])`."
|
|
15488
15436
|
},
|
|
15489
15437
|
schema: []
|
|
@@ -15554,11 +15502,6 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15554
15502
|
expectedIsLiteral: literal !== null
|
|
15555
15503
|
};
|
|
15556
15504
|
}
|
|
15557
|
-
function hasInterveningComment(run) {
|
|
15558
|
-
return run.some(
|
|
15559
|
-
(assertion, index) => sourceCode.getCommentsInside(assertion.statement).length > 0 || index > 0 && sourceCode.getCommentsBefore(assertion.statement).length > 0
|
|
15560
|
-
);
|
|
15561
|
-
}
|
|
15562
15505
|
function reportPropertyRun(run) {
|
|
15563
15506
|
const tree = /* @__PURE__ */ new Map();
|
|
15564
15507
|
const paths = [];
|
|
@@ -15605,16 +15548,10 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
15605
15548
|
return;
|
|
15606
15549
|
}
|
|
15607
15550
|
const receiverText = `${sourceCode.getText(first.receiver)}${commonPrefix.map((name) => `.${name}`).join("")}`;
|
|
15608
|
-
const renderTree = (value) => [...value.entries()].map(([name, child]) => `${name}: ${child instanceof Map ? `{ ${renderTree(child)} }` : child}`).join(", ");
|
|
15609
|
-
const properties = renderTree(tree);
|
|
15610
15551
|
context.report({
|
|
15611
15552
|
node: first.statement,
|
|
15612
15553
|
messageId: "combineAssertions",
|
|
15613
|
-
data: { count: String(run.length), receiver: receiverText }
|
|
15614
|
-
fix: hasInterveningComment(run) ? null : (fixer) => [
|
|
15615
|
-
fixer.replaceText(first.statement, `expect(${receiverText}).toMatchObject({ ${properties} });`),
|
|
15616
|
-
...run.slice(1).map((assertion) => fixer.remove(assertion.statement))
|
|
15617
|
-
]
|
|
15554
|
+
data: { count: String(run.length), receiver: receiverText }
|
|
15618
15555
|
});
|
|
15619
15556
|
}
|
|
15620
15557
|
function reportIndexRun(run) {
|
|
@@ -19747,7 +19684,7 @@ var RULES = {
|
|
|
19747
19684
|
};
|
|
19748
19685
|
var meta = {
|
|
19749
19686
|
name: "@sarj/eslint-plugin",
|
|
19750
|
-
version: "15.17.
|
|
19687
|
+
version: "15.17.8"
|
|
19751
19688
|
};
|
|
19752
19689
|
var APPLICATION_ONLY_RULES = [];
|
|
19753
19690
|
var LIBRARY_IMPORT_POLICY = ["error", {
|
|
@@ -19758,6 +19695,7 @@ var ADVISORY_RULES = [
|
|
|
19758
19695
|
"@sarj/excessive-commentary",
|
|
19759
19696
|
"@sarj/no-bespoke-api-case-conversion",
|
|
19760
19697
|
"@sarj/no-restated-comment",
|
|
19698
|
+
"@sarj/no-restated-jsdoc",
|
|
19761
19699
|
"@sarj/prefer-millisecond-control-duration-schema",
|
|
19762
19700
|
"@sarj/prefer-module-level-refined-schema",
|
|
19763
19701
|
"@sarj/prefer-multi-value-zod-literal",
|
|
@@ -19768,6 +19706,7 @@ var ADVISORY_RULES = [
|
|
|
19768
19706
|
"@sarj/prefer-nullish-filter-predicate",
|
|
19769
19707
|
"@sarj/prefer-shared-zod-enum",
|
|
19770
19708
|
"@sarj/prefer-switch-for-repeated-equality",
|
|
19709
|
+
"@sarj/prefer-whole-object-assertion",
|
|
19771
19710
|
"@sarj/require-interface-for-exported-class",
|
|
19772
19711
|
"@sarj/require-sql-access-class",
|
|
19773
19712
|
"@sarj/sole-export-matches-filename"
|
|
@@ -19806,7 +19745,7 @@ var RECOMMENDED_RULES = {
|
|
|
19806
19745
|
"@sarj/no-repeated-string-literal": "error",
|
|
19807
19746
|
"@sarj/no-router-refresh-polling": "error",
|
|
19808
19747
|
"@sarj/no-restated-comment": "warn",
|
|
19809
|
-
"@sarj/no-restated-jsdoc": "
|
|
19748
|
+
"@sarj/no-restated-jsdoc": "warn",
|
|
19810
19749
|
"@sarj/no-secret-in-log": "error",
|
|
19811
19750
|
"@sarj/no-server-env-in-client-component": "error",
|
|
19812
19751
|
"@sarj/no-select-star": "error",
|
|
@@ -19846,7 +19785,7 @@ var RECOMMENDED_RULES = {
|
|
|
19846
19785
|
"@sarj/prefer-switch-for-repeated-equality": "warn",
|
|
19847
19786
|
"@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
|
|
19848
19787
|
"@sarj/prefer-server-actions": "error",
|
|
19849
|
-
"@sarj/prefer-whole-object-assertion": "
|
|
19788
|
+
"@sarj/prefer-whole-object-assertion": "warn",
|
|
19850
19789
|
"@sarj/repeated-static-call-cases": "error",
|
|
19851
19790
|
"@sarj/prefer-zod-infer": "error",
|
|
19852
19791
|
"@sarj/require-assert-never": "error",
|
|
@@ -19902,7 +19841,7 @@ var STRICT_RULES = {
|
|
|
19902
19841
|
"@sarj/no-repeated-string-literal": "error",
|
|
19903
19842
|
"@sarj/no-router-refresh-polling": "error",
|
|
19904
19843
|
"@sarj/no-restated-comment": "warn",
|
|
19905
|
-
"@sarj/no-restated-jsdoc": "
|
|
19844
|
+
"@sarj/no-restated-jsdoc": "warn",
|
|
19906
19845
|
"@sarj/no-secret-in-log": "error",
|
|
19907
19846
|
"@sarj/no-server-env-in-client-component": "error",
|
|
19908
19847
|
"@sarj/no-select-star": "error",
|
|
@@ -19943,7 +19882,7 @@ var STRICT_RULES = {
|
|
|
19943
19882
|
"@sarj/prefer-switch-for-repeated-equality": "warn",
|
|
19944
19883
|
"@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
|
|
19945
19884
|
"@sarj/prefer-server-actions": "error",
|
|
19946
|
-
"@sarj/prefer-whole-object-assertion": "
|
|
19885
|
+
"@sarj/prefer-whole-object-assertion": "warn",
|
|
19947
19886
|
"@sarj/repeated-static-call-cases": "error",
|
|
19948
19887
|
"@sarj/prefer-zod-infer": "error",
|
|
19949
19888
|
"@sarj/require-assert-never": "error",
|