@sarj/eslint-plugin 15.1.0 → 15.2.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/index.cjs +332 -63
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +358 -85
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -967,11 +967,11 @@ function isTrivialInitializer(node) {
|
|
|
967
967
|
}
|
|
968
968
|
function restatesStatementHead(body2, statement) {
|
|
969
969
|
if (statement === null) return false;
|
|
970
|
-
const
|
|
971
|
-
const opener =
|
|
972
|
-
if (opener === void 0 ||
|
|
970
|
+
const words2 = body2.match(/[A-Za-z][\w$]*/g) ?? [];
|
|
971
|
+
const opener = words2[0];
|
|
972
|
+
if (opener === void 0 || words2.length > NARRATION_MAX_WORDS) return false;
|
|
973
973
|
if (!NARRATION_VERB_RE.test(opener)) return false;
|
|
974
|
-
const content =
|
|
974
|
+
const content = words2.slice(1).map(normalizeToken).filter((word) => !NARRATION_STOPWORDS.has(word));
|
|
975
975
|
if (content.length < NARRATION_MIN_CONTENT) return false;
|
|
976
976
|
const head = statement.split("(")[0] ?? statement;
|
|
977
977
|
const code = headTokens(head);
|
|
@@ -1239,8 +1239,8 @@ function isRedundantNarration(body2, statementBelow, standalone, isolatedEnumera
|
|
|
1239
1239
|
if (META_COMMENTARY_RE.test(t) && !justified) return true;
|
|
1240
1240
|
if (isBareDeferral(t) && !justified) return true;
|
|
1241
1241
|
if (HELPER_OPENER_RE.test(t) || LETS_RE.test(t)) return true;
|
|
1242
|
-
const
|
|
1243
|
-
if (
|
|
1242
|
+
const words2 = t.split(/\s+/);
|
|
1243
|
+
if (words2.length > 1 && words2.length <= 4 && DUMMY_TRANSLATION_RE.test(t) && !/[():=]/.test(t)) {
|
|
1244
1244
|
const lowerT = t.toLowerCase();
|
|
1245
1245
|
if (!RATIONALE_WORDS.some((word) => lowerT.includes(word)) && restatesWholeStatement(t, statementBelow)) {
|
|
1246
1246
|
return true;
|
|
@@ -1322,8 +1322,8 @@ function isWeakWalkthroughComment(body2, statement) {
|
|
|
1322
1322
|
if (normalized.length === 0 || normalized.endsWith("?") || normalized.split(/\s+/).length > WALL_MAX_WORDS || isDirective(normalized) || isProtected(normalized) || !WALL_NARRATION_RE.test(normalized)) {
|
|
1323
1323
|
return false;
|
|
1324
1324
|
}
|
|
1325
|
-
const
|
|
1326
|
-
const described =
|
|
1325
|
+
const words2 = contentTokens(normalized);
|
|
1326
|
+
const described = words2.slice(1);
|
|
1327
1327
|
if (described.length === 0) return false;
|
|
1328
1328
|
const code = codeTokens(statement);
|
|
1329
1329
|
const matched = described.filter((word) => restates([word], code)).length;
|
|
@@ -2670,7 +2670,7 @@ var no_fat_try_blocks_default = createRule({
|
|
|
2670
2670
|
const sourceCode = context.sourceCode;
|
|
2671
2671
|
return {
|
|
2672
2672
|
TryStatement(node) {
|
|
2673
|
-
if (node.finalizer !== null) {
|
|
2673
|
+
if (node.finalizer !== null && node.handler === null) {
|
|
2674
2674
|
return;
|
|
2675
2675
|
}
|
|
2676
2676
|
if (handlerRethrows(node.handler)) {
|
|
@@ -2910,15 +2910,32 @@ var noHandRolledSpinnerDocumentation = {
|
|
|
2910
2910
|
rationale: "One-off loading indicators duplicate a shared primitive and let accessibility and styling diverge.",
|
|
2911
2911
|
remediation: "Render the design-system Spinner component instead.",
|
|
2912
2912
|
category: "maintainability",
|
|
2913
|
-
limitations: ["Only static className values on div and span elements are inspected."],
|
|
2913
|
+
limitations: ["Only static className values on div and span elements are inspected; tests, stories, generated files, and the design-system implementation are excluded."],
|
|
2914
2914
|
examples: [
|
|
2915
2915
|
{ id: "design-system-spinner", title: "Use the shared spinner", outcome: "no-match", files: [{ path: "src/loading-state.tsx", source: '<Spinner className="size-4" />' }], focusPath: "src/loading-state.tsx", expectedCount: 0, public: true },
|
|
2916
2916
|
{ id: "border-ring-spinner", title: "Do not rebuild a spinner", outcome: "match", files: [{ path: "src/loading-state.tsx", source: '<div className="size-4 animate-spin rounded-full border-2 border-t-transparent" />' }], focusPath: "src/loading-state.tsx", expectedCount: 1, public: true }
|
|
2917
2917
|
]
|
|
2918
2918
|
};
|
|
2919
2919
|
var DESIGN_SYSTEM_PATH = /(?:^|[/\\])components[/\\]ui[/\\]/u;
|
|
2920
|
-
var
|
|
2921
|
-
var
|
|
2920
|
+
var DIRECTIONAL_BORDER = /^border-([trblsexy])-(.+)$/u;
|
|
2921
|
+
var CSS_LENGTH = /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:cap|ch|cm|dvh|dvw|em|ex|ic|in|lh|lvh|lvw|mm|pc|pt|px|q|rcap|rch|rem|rex|ric|rlh|svh|svw|vb|vh|vi|vmax|vmin|vw|%)$/u;
|
|
2922
|
+
var ARBITRARY_LENGTH_FUNCTION = /^(?:calc|clamp|max|min)\(.+\)$/u;
|
|
2923
|
+
function isBorderWidthValue(value) {
|
|
2924
|
+
if (/^\d+$/u.test(value)) return true;
|
|
2925
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
2926
|
+
const arbitrary = value.slice(1, -1);
|
|
2927
|
+
const length = arbitrary.startsWith("length:") ? arbitrary.slice("length:".length) : arbitrary;
|
|
2928
|
+
return CSS_LENGTH.test(length) || ARBITRARY_LENGTH_FUNCTION.test(length) || arbitrary.startsWith("length:") && /^var\(.+\)$/u.test(length);
|
|
2929
|
+
}
|
|
2930
|
+
return value.startsWith("(length:") && value.endsWith(")") && value.length > "(length:)".length;
|
|
2931
|
+
}
|
|
2932
|
+
function isBorderWidth(token) {
|
|
2933
|
+
return token === "border" || token.startsWith("border-") && isBorderWidthValue(token.slice("border-".length));
|
|
2934
|
+
}
|
|
2935
|
+
function isContrastingEdge(token) {
|
|
2936
|
+
const match = DIRECTIONAL_BORDER.exec(token);
|
|
2937
|
+
return match?.[2] !== void 0 && !isBorderWidthValue(match[2]);
|
|
2938
|
+
}
|
|
2922
2939
|
function staticClassName(attribute) {
|
|
2923
2940
|
const value = attribute.value;
|
|
2924
2941
|
if (value?.type === AST_NODE_TYPES11.Literal && typeof value.value === "string") {
|
|
@@ -2947,7 +2964,7 @@ var no_hand_rolled_spinner_default = createRule({
|
|
|
2947
2964
|
},
|
|
2948
2965
|
defaultOptions: [],
|
|
2949
2966
|
create(context) {
|
|
2950
|
-
if (DESIGN_SYSTEM_PATH.test(context.filename)) {
|
|
2967
|
+
if (DESIGN_SYSTEM_PATH.test(context.filename) || isTestFile(context.filename) || isStoryFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
2951
2968
|
return {};
|
|
2952
2969
|
}
|
|
2953
2970
|
return {
|
|
@@ -2962,7 +2979,7 @@ var no_hand_rolled_spinner_default = createRule({
|
|
|
2962
2979
|
const className = staticClassName(classNameAttribute);
|
|
2963
2980
|
if (className === null) return;
|
|
2964
2981
|
const classes = className.split(/\s+/u);
|
|
2965
|
-
if (classes.includes("animate-spin") && classes.includes("rounded-full") && classes.some(
|
|
2982
|
+
if (classes.includes("animate-spin") && classes.includes("rounded-full") && classes.some(isBorderWidth) && classes.some(isContrastingEdge)) {
|
|
2966
2983
|
context.report({ node, messageId: "handRolledSpinner" });
|
|
2967
2984
|
}
|
|
2968
2985
|
}
|
|
@@ -3028,9 +3045,9 @@ function nameWords(name) {
|
|
|
3028
3045
|
return name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").split(/[^A-Za-z0-9]+/u).filter(Boolean).map((word) => word.toLowerCase());
|
|
3029
3046
|
}
|
|
3030
3047
|
function isStrongSecurityName(name) {
|
|
3031
|
-
const
|
|
3032
|
-
return
|
|
3033
|
-
(word, index) => word === "api" &&
|
|
3048
|
+
const words2 = nameWords(name);
|
|
3049
|
+
return words2.some((word) => STRONG_SECURITY_WORDS.has(word)) || words2.some(
|
|
3050
|
+
(word, index) => word === "api" && words2[index + 1] === "key" || word === "auth" && words2[index + 1] === "id" || word === "verification" && words2[index + 1] === "code"
|
|
3034
3051
|
);
|
|
3035
3052
|
}
|
|
3036
3053
|
function isNonSecurityName(name) {
|
|
@@ -3994,7 +4011,7 @@ var VALUE_TAG_RE = /@(example|deprecated|see|remarks|throws|internal|public|alph
|
|
|
3994
4011
|
var BOUNDARY_RE = /(?<=[.!?])["'`)\]]*\s+(?=[A-Z0-9`])/;
|
|
3995
4012
|
var BULLET_RE = /^\s*(?:[-*+] |\d+[.)] )/;
|
|
3996
4013
|
var HEADING_RE = /^[A-Za-z][A-Za-z ]+:$/;
|
|
3997
|
-
var TECHNICAL_ANCHOR_RE = /https?:\/\/|`[^`\n]+`|:[a-z][a-z0-9_-]*:|(["'])[^"'\n]+\1|\d|\b[a-z][a-z0-9]*[A-Z][A-Za-z0-9]*\b|\b[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9_]+\b|(?:^|\s)(?:[\w.-]+\/)+[\w.-]+|\b[\w.-]+\.(?:py|pyi|js|jsx|ts|tsx|json|ya?ml|toml|csv|parquet|md)\b|->|=>|==|!=|<=|>=|\|/mu;
|
|
4014
|
+
var TECHNICAL_ANCHOR_RE = /https?:\/\/|`[^`\n]+`|:[a-z][a-z0-9_-]*:|(["'])[^"'\n]+\1|\bv?\d+\.\d+(?:\.\d+)?\b|\b\d+(?:\.\d+)?\s?(?:ns|us|ms|s|sec|secs|seconds?|mins?|minutes?|hours?|days?|bytes?|kib|mib|gib|kb|mb|gb|hz|khz|mhz|px|%)\b|\b[a-z][a-z0-9]*[A-Z][A-Za-z0-9]*\b|\b[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9_]+\b|(?:^|\s)(?:[\w.-]+\/)+[\w.-]+|\b[\w.-]+\.(?:py|pyi|js|jsx|ts|tsx|json|ya?ml|toml|csv|parquet|md)\b|->|=>|==|!=|<=|>=|\|/mu;
|
|
3998
4015
|
function body(comment) {
|
|
3999
4016
|
return comment.value.replace(/^\*/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, "")).join("\n").trim();
|
|
4000
4017
|
}
|
|
@@ -6810,22 +6827,25 @@ var noStorageInStatelessModulesDocumentation = {
|
|
|
6810
6827
|
rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
|
|
6811
6828
|
remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
|
|
6812
6829
|
category: "architecture",
|
|
6813
|
-
limitations: ["The rule is disabled until module path patterns are configured
|
|
6830
|
+
limitations: ["The rule is disabled until module path patterns are configured, recognizes only configured storage method names, and requires storage-like receiver evidence for the overloaded `put` method."],
|
|
6814
6831
|
examples: [
|
|
6815
6832
|
{ id: "system-of-record", title: "Read from the system of record", outcome: "no-match", files: [{ path: "src/engineer-digest/post.ts", source: "const issues = await linear.listIssues();" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 0, public: true },
|
|
6816
6833
|
{ id: "private-storage", title: "Do not write private state in a stateless module", outcome: "match", files: [{ path: "src/engineer-digest/post.ts", source: "await kv.put('digest:last', timestamp);" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 1, public: true }
|
|
6817
6834
|
]
|
|
6818
6835
|
};
|
|
6819
6836
|
function compile2(patterns) {
|
|
6820
|
-
|
|
6821
|
-
for (const pattern of patterns) {
|
|
6822
|
-
try {
|
|
6823
|
-
compiled.push(new RegExp(pattern));
|
|
6824
|
-
} catch {
|
|
6825
|
-
}
|
|
6826
|
-
}
|
|
6827
|
-
return compiled;
|
|
6837
|
+
return patterns.map((pattern) => new RegExp(pattern));
|
|
6828
6838
|
}
|
|
6839
|
+
var STORAGE_RECEIVER_WORDS = /* @__PURE__ */ new Set([
|
|
6840
|
+
"bucket",
|
|
6841
|
+
"cache",
|
|
6842
|
+
"kv",
|
|
6843
|
+
"namespace",
|
|
6844
|
+
"r2",
|
|
6845
|
+
"redis",
|
|
6846
|
+
"storage",
|
|
6847
|
+
"store"
|
|
6848
|
+
]);
|
|
6829
6849
|
function storageMethodName(node, methods) {
|
|
6830
6850
|
const callee = node.callee;
|
|
6831
6851
|
if (callee.type !== AST_NODE_TYPES26.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES26.Identifier) {
|
|
@@ -6838,8 +6858,31 @@ function storageMethodName(node, methods) {
|
|
|
6838
6858
|
if (node.arguments.length < (MIN_ARGUMENTS.get(name) ?? 1)) {
|
|
6839
6859
|
return null;
|
|
6840
6860
|
}
|
|
6861
|
+
if (name === "put" && !isStorageLikeReceiver(callee.object)) {
|
|
6862
|
+
return null;
|
|
6863
|
+
}
|
|
6841
6864
|
return name;
|
|
6842
6865
|
}
|
|
6866
|
+
function isStorageLikeReceiver(node) {
|
|
6867
|
+
if (node.type === AST_NODE_TYPES26.Identifier) {
|
|
6868
|
+
return isStorageIdentifier(node.name);
|
|
6869
|
+
}
|
|
6870
|
+
if (node.type !== AST_NODE_TYPES26.MemberExpression) {
|
|
6871
|
+
return false;
|
|
6872
|
+
}
|
|
6873
|
+
if (!node.computed && node.property.type === AST_NODE_TYPES26.Identifier && isStorageIdentifier(node.property.name)) {
|
|
6874
|
+
return true;
|
|
6875
|
+
}
|
|
6876
|
+
return isStorageLikeReceiver(node.object);
|
|
6877
|
+
}
|
|
6878
|
+
function isStorageIdentifier(name) {
|
|
6879
|
+
return identifierWords(name).some(
|
|
6880
|
+
(word) => STORAGE_RECEIVER_WORDS.has(word.toLowerCase())
|
|
6881
|
+
);
|
|
6882
|
+
}
|
|
6883
|
+
function identifierWords(name) {
|
|
6884
|
+
return name.match(/[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)|\d+/gu) ?? [name];
|
|
6885
|
+
}
|
|
6843
6886
|
var no_storage_in_stateless_modules_default = createRule({
|
|
6844
6887
|
name: "no-storage-in-stateless-modules",
|
|
6845
6888
|
documentation: noStorageInStatelessModulesDocumentation,
|
|
@@ -7241,12 +7284,86 @@ var no_tautological_expect_default = createRule({
|
|
|
7241
7284
|
});
|
|
7242
7285
|
|
|
7243
7286
|
// src/rules/no-typed-doc-sections.ts
|
|
7287
|
+
var TYPED_TAG_RE2 = /^\s*@(arg|argument|param|return|returns|yield|yields)\b(.*)$/iu;
|
|
7288
|
+
var PARAM_TAGS2 = /* @__PURE__ */ new Set(["arg", "argument", "param"]);
|
|
7289
|
+
var PARAMETER_FILLER = /* @__PURE__ */ new Set([
|
|
7290
|
+
"a",
|
|
7291
|
+
"an",
|
|
7292
|
+
"argument",
|
|
7293
|
+
"given",
|
|
7294
|
+
"input",
|
|
7295
|
+
"parameter",
|
|
7296
|
+
"passed",
|
|
7297
|
+
"provided",
|
|
7298
|
+
"the",
|
|
7299
|
+
"value"
|
|
7300
|
+
]);
|
|
7301
|
+
var RESULT_FILLER = /* @__PURE__ */ new Set([
|
|
7302
|
+
"a",
|
|
7303
|
+
"an",
|
|
7304
|
+
"array",
|
|
7305
|
+
"boolean",
|
|
7306
|
+
"generator",
|
|
7307
|
+
"number",
|
|
7308
|
+
"object",
|
|
7309
|
+
"output",
|
|
7310
|
+
"promise",
|
|
7311
|
+
"result",
|
|
7312
|
+
"return",
|
|
7313
|
+
"returned",
|
|
7314
|
+
"returns",
|
|
7315
|
+
"string",
|
|
7316
|
+
"the",
|
|
7317
|
+
"value"
|
|
7318
|
+
]);
|
|
7319
|
+
function hasVacuousTypedTag(text) {
|
|
7320
|
+
const tags = typedTags(text);
|
|
7321
|
+
return tags.length > 0 && tags.some(isVacuousTag);
|
|
7322
|
+
}
|
|
7323
|
+
function typedTags(text) {
|
|
7324
|
+
const tags = [];
|
|
7325
|
+
for (const raw of text.split("\n")) {
|
|
7326
|
+
const match = TYPED_TAG_RE2.exec(raw);
|
|
7327
|
+
if (match !== null) {
|
|
7328
|
+
tags.push({ kind: (match[1] ?? "").toLowerCase(), payload: (match[2] ?? "").trim() });
|
|
7329
|
+
} else if (tags.length > 0 && raw.trim().length > 0 && !raw.trim().startsWith("@")) {
|
|
7330
|
+
const last = tags.at(-1);
|
|
7331
|
+
last.payload = `${last.payload} ${raw.trim()}`.trim();
|
|
7332
|
+
}
|
|
7333
|
+
}
|
|
7334
|
+
return tags.map(({ kind, payload }) => {
|
|
7335
|
+
let rest = payload.replace(/^\{[^}\n]+\}\s*/u, "").trim();
|
|
7336
|
+
if (!PARAM_TAGS2.has(kind)) {
|
|
7337
|
+
return { kind, name: null, description: rest.replace(/^-\s*/u, "").trim() };
|
|
7338
|
+
}
|
|
7339
|
+
const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s*|\s+)?(.*)$/u.exec(rest);
|
|
7340
|
+
if (match === null) return { kind, name: null, description: "" };
|
|
7341
|
+
const rawName = (match[1] ?? "").replace(/^\[/u, "").replace(/\]$/u, "").split("=")[0] ?? "";
|
|
7342
|
+
rest = (match[2] ?? "").trim();
|
|
7343
|
+
return { kind, name: rawName, description: rest };
|
|
7344
|
+
});
|
|
7345
|
+
}
|
|
7346
|
+
function isVacuousTag(tag) {
|
|
7347
|
+
const description = words(tag.description).map(canonicalWord);
|
|
7348
|
+
if (description.length === 0) return true;
|
|
7349
|
+
if (tag.name === null) return description.every((word) => RESULT_FILLER.has(word));
|
|
7350
|
+
const nameWords2 = new Set(words(tag.name).map(canonicalWord));
|
|
7351
|
+
return description.every((word) => PARAMETER_FILLER.has(word) || nameWords2.has(word));
|
|
7352
|
+
}
|
|
7353
|
+
function words(text) {
|
|
7354
|
+
return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[a-z][a-z0-9]*/gu) ?? [];
|
|
7355
|
+
}
|
|
7356
|
+
function canonicalWord(word) {
|
|
7357
|
+
if (["identifier", "identifiers", "ids"].includes(word)) return "id";
|
|
7358
|
+
if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
|
|
7359
|
+
return word;
|
|
7360
|
+
}
|
|
7244
7361
|
var noTypedDocSectionsDocumentation = {
|
|
7245
7362
|
summary: "Reject typed-signature repetition while preserving behavior that types cannot express.",
|
|
7246
7363
|
rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
|
|
7247
7364
|
remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
|
|
7248
7365
|
category: "maintainability",
|
|
7249
|
-
limitations: ["
|
|
7366
|
+
limitations: ["Description-free or name-restating parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
|
|
7250
7367
|
examples: [
|
|
7251
7368
|
{
|
|
7252
7369
|
id: "behavioral-documentation",
|
|
@@ -7284,7 +7401,7 @@ var no_typed_doc_sections_default = createRule({
|
|
|
7284
7401
|
return {
|
|
7285
7402
|
Program() {
|
|
7286
7403
|
for (const group of proseGroups(context.filename, context.sourceCode, true)) {
|
|
7287
|
-
if (group.hasTypedTags && documentsTypedFunction(context.sourceCode, group.comment)) {
|
|
7404
|
+
if (group.hasTypedTags && hasVacuousTypedTag(group.text) && documentsTypedFunction(context.sourceCode, group.comment)) {
|
|
7288
7405
|
context.report({ node: group.comment, messageId: "typedSection" });
|
|
7289
7406
|
}
|
|
7290
7407
|
}
|
|
@@ -7385,28 +7502,32 @@ var STOPWORDS3 = /* @__PURE__ */ new Set([
|
|
|
7385
7502
|
"with"
|
|
7386
7503
|
]);
|
|
7387
7504
|
var DIRECTIVE_RE5 = /^\s*(?:eslint\b|eslint-|sarj-noqa\b|@ts-|prettier|biome-|c8\b|v8\b|istanbul\b|todo\b|fixme\b|hack\b|xxx\b)/i;
|
|
7505
|
+
var UNIT_NAME_SUFFIX_RE = /(?:_(?:NS|US|MS|S|SEC|SECS|SECOND|SECONDS|MIN|MINS|MINUTE|MINUTES|HOUR|HOURS|DAY|DAYS|BYTE|BYTES|KB|MB|GB|HZ|KHZ|MHZ|PX)|(?:Ns|Us|Ms|Sec|Secs|Second|Seconds|Min|Mins|Minute|Minutes|Hour|Hours|Day|Days|Byte|Bytes|Kb|Mb|Gb|Hz|Khz|Mhz|Px))$/u;
|
|
7388
7506
|
function narratesValue(body2, code) {
|
|
7389
7507
|
if (body2.length === 0 || DIRECTIVE_RE5.test(body2) || hasExternalReference(body2)) return false;
|
|
7390
7508
|
const codeNumbers = numbersIn(code);
|
|
7391
7509
|
if (codeNumbers.size === 0) return false;
|
|
7392
|
-
const
|
|
7393
|
-
if (
|
|
7510
|
+
const words2 = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
|
|
7511
|
+
if (words2.length === 0) return false;
|
|
7394
7512
|
const commentNumbers = numbersIn(body2);
|
|
7395
7513
|
if (commentNumbers.size === 0) return false;
|
|
7396
7514
|
for (const number of commentNumbers) {
|
|
7397
7515
|
if (!codeNumbers.has(number)) return false;
|
|
7398
7516
|
}
|
|
7399
|
-
if (!
|
|
7517
|
+
if (!words2.some((word) => UNIT_WORDS.has(word))) return false;
|
|
7400
7518
|
const identifiers = codeTokens(code);
|
|
7401
7519
|
const stems = /* @__PURE__ */ new Set();
|
|
7402
7520
|
for (const token of identifiers) stems.add(stem(token));
|
|
7403
|
-
return
|
|
7521
|
+
return words2.every(
|
|
7404
7522
|
(word) => STOPWORDS3.has(word) || UNIT_WORDS.has(word) || commentNumbers.has(word) || identifiers.has(word) || stems.has(stem(word))
|
|
7405
7523
|
);
|
|
7406
7524
|
}
|
|
7407
7525
|
function numbersIn(text) {
|
|
7408
7526
|
return new Set(text.match(NUMBER_RE) ?? []);
|
|
7409
7527
|
}
|
|
7528
|
+
function nameAlreadyCarriesUnit(code) {
|
|
7529
|
+
return (code.match(/[A-Za-z_$][\w$]*/gu) ?? []).some((identifier) => UNIT_NAME_SUFFIX_RE.test(identifier));
|
|
7530
|
+
}
|
|
7410
7531
|
var no_trailing_value_narration_default = createRule({
|
|
7411
7532
|
name: "no-trailing-value-narration",
|
|
7412
7533
|
documentation: noTrailingValueNarrationDocumentation,
|
|
@@ -7417,6 +7538,7 @@ var no_trailing_value_narration_default = createRule({
|
|
|
7417
7538
|
},
|
|
7418
7539
|
schema: [],
|
|
7419
7540
|
messages: {
|
|
7541
|
+
deleteNarration: "Trailing comment restates the literal and the identifier already names its unit \u2014 delete the comment so it cannot drift.",
|
|
7420
7542
|
narratesValue: "Trailing comment restates the literal on this line \u2014 put the unit in the name (STALE_TIME_MS) so it cannot drift."
|
|
7421
7543
|
}
|
|
7422
7544
|
},
|
|
@@ -7449,7 +7571,10 @@ var no_trailing_value_narration_default = createRule({
|
|
|
7449
7571
|
const code = line.slice(0, comment.loc.start.column);
|
|
7450
7572
|
const body2 = comment.value.replace(/^\*+/, "").replace(/\*+$/, "").trim();
|
|
7451
7573
|
if (narratesValue(body2, code)) {
|
|
7452
|
-
context.report({
|
|
7574
|
+
context.report({
|
|
7575
|
+
node: comment,
|
|
7576
|
+
messageId: nameAlreadyCarriesUnit(code) ? "deleteNarration" : "narratesValue"
|
|
7577
|
+
});
|
|
7453
7578
|
}
|
|
7454
7579
|
}
|
|
7455
7580
|
}
|
|
@@ -8367,7 +8492,8 @@ var no_unsafe_mock_casting_default = createRule({
|
|
|
8367
8492
|
// src/rules/no-zod-native-enum.ts
|
|
8368
8493
|
import {
|
|
8369
8494
|
ESLintUtils as ESLintUtils2,
|
|
8370
|
-
AST_NODE_TYPES as AST_NODE_TYPES34
|
|
8495
|
+
AST_NODE_TYPES as AST_NODE_TYPES34,
|
|
8496
|
+
ASTUtils as ASTUtils8
|
|
8371
8497
|
} from "@typescript-eslint/utils";
|
|
8372
8498
|
import * as ts from "typescript";
|
|
8373
8499
|
var noZodNativeEnumDocumentation = {
|
|
@@ -8496,15 +8622,23 @@ var no_zod_native_enum_default = createRule({
|
|
|
8496
8622
|
} catch {
|
|
8497
8623
|
services = null;
|
|
8498
8624
|
}
|
|
8499
|
-
const
|
|
8500
|
-
const
|
|
8625
|
+
const zodImportedBindings = /* @__PURE__ */ new Map();
|
|
8626
|
+
const zodNamespaceBindings = /* @__PURE__ */ new Set();
|
|
8627
|
+
function resolvedBinding(identifier) {
|
|
8628
|
+
return ASTUtils8.findVariable(
|
|
8629
|
+
sourceCode.getScope(identifier),
|
|
8630
|
+
identifier.name
|
|
8631
|
+
);
|
|
8632
|
+
}
|
|
8501
8633
|
function isZodMemberCall(node, api) {
|
|
8502
8634
|
const callee = node.callee;
|
|
8503
|
-
if (callee.type === AST_NODE_TYPES34.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES34.Identifier &&
|
|
8504
|
-
|
|
8635
|
+
if (callee.type === AST_NODE_TYPES34.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES34.Identifier && callee.property.type === AST_NODE_TYPES34.Identifier) {
|
|
8636
|
+
const binding = resolvedBinding(callee.object);
|
|
8637
|
+
return binding !== null && zodNamespaceBindings.has(binding) && callee.property.name === api;
|
|
8505
8638
|
}
|
|
8506
8639
|
if (callee.type === AST_NODE_TYPES34.Identifier) {
|
|
8507
|
-
|
|
8640
|
+
const binding = resolvedBinding(callee);
|
|
8641
|
+
return binding !== null && zodImportedBindings.get(binding) === api;
|
|
8508
8642
|
}
|
|
8509
8643
|
return false;
|
|
8510
8644
|
}
|
|
@@ -8539,10 +8673,14 @@ var no_zod_native_enum_default = createRule({
|
|
|
8539
8673
|
}
|
|
8540
8674
|
for (const spec of node.specifiers) {
|
|
8541
8675
|
if (spec.type === AST_NODE_TYPES34.ImportNamespaceSpecifier || spec.type === AST_NODE_TYPES34.ImportDefaultSpecifier || spec.type === AST_NODE_TYPES34.ImportSpecifier && (spec.imported.type === AST_NODE_TYPES34.Identifier ? spec.imported.name === "z" : spec.imported.value === "z")) {
|
|
8542
|
-
|
|
8676
|
+
const binding = resolvedBinding(spec.local);
|
|
8677
|
+
if (binding !== null) zodNamespaceBindings.add(binding);
|
|
8543
8678
|
}
|
|
8544
8679
|
if (spec.type === AST_NODE_TYPES34.ImportSpecifier && spec.imported.type === AST_NODE_TYPES34.Identifier) {
|
|
8545
|
-
|
|
8680
|
+
const binding = resolvedBinding(spec.local);
|
|
8681
|
+
if (binding !== null) {
|
|
8682
|
+
zodImportedBindings.set(binding, spec.imported.name);
|
|
8683
|
+
}
|
|
8546
8684
|
}
|
|
8547
8685
|
}
|
|
8548
8686
|
},
|
|
@@ -8579,7 +8717,7 @@ var no_zod_native_enum_default = createRule({
|
|
|
8579
8717
|
});
|
|
8580
8718
|
|
|
8581
8719
|
// src/rules/test-loops-over-literal-cases.ts
|
|
8582
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES35, ASTUtils as
|
|
8720
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES35, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
|
|
8583
8721
|
var testLoopsOverLiteralCasesDocumentation = {
|
|
8584
8722
|
summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
|
|
8585
8723
|
rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
|
|
@@ -8742,7 +8880,7 @@ var test_loops_over_literal_cases_default = createRule({
|
|
|
8742
8880
|
return {};
|
|
8743
8881
|
}
|
|
8744
8882
|
const isFrameworkIdentifier = (identifier, modules) => {
|
|
8745
|
-
const variable =
|
|
8883
|
+
const variable = ASTUtils9.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
8746
8884
|
if (variable === null || variable.defs.length === 0) return true;
|
|
8747
8885
|
return variable.defs.some((definition) => {
|
|
8748
8886
|
let current = definition.node;
|
|
@@ -9179,7 +9317,7 @@ var prefer_input_group_search_default = createRule({
|
|
|
9179
9317
|
});
|
|
9180
9318
|
|
|
9181
9319
|
// src/rules/prefer-immutable-module-constant.ts
|
|
9182
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES39, ASTUtils as
|
|
9320
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES39, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
|
|
9183
9321
|
var preferImmutableModuleConstantDocumentation = {
|
|
9184
9322
|
summary: "Require module-level constant collections to expose readonly state.",
|
|
9185
9323
|
rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
|
|
@@ -9332,7 +9470,7 @@ var prefer_immutable_module_constant_default = createRule({
|
|
|
9332
9470
|
create(context) {
|
|
9333
9471
|
const sourceCode = context.sourceCode;
|
|
9334
9472
|
const isUnshadowedGlobal = (identifier) => {
|
|
9335
|
-
const variable =
|
|
9473
|
+
const variable = ASTUtils10.findVariable(sourceCode.getScope(identifier), identifier.name);
|
|
9336
9474
|
return variable === null || variable.defs.length === 0;
|
|
9337
9475
|
};
|
|
9338
9476
|
if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
|
|
@@ -10249,7 +10387,7 @@ var prefer_module_level_schema_default = createRule({
|
|
|
10249
10387
|
});
|
|
10250
10388
|
|
|
10251
10389
|
// src/rules/prefer-native-random-uuid.ts
|
|
10252
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES43, ASTUtils as
|
|
10390
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES43, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
|
|
10253
10391
|
var preferNativeRandomUuidDocumentation = {
|
|
10254
10392
|
summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
|
|
10255
10393
|
rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
|
|
@@ -10285,7 +10423,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
10285
10423
|
const directBindings = /* @__PURE__ */ new Set();
|
|
10286
10424
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
10287
10425
|
function resolve(identifier) {
|
|
10288
|
-
return
|
|
10426
|
+
return ASTUtils11.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
10289
10427
|
}
|
|
10290
10428
|
function record(identifier, destination) {
|
|
10291
10429
|
const variable = resolve(identifier);
|
|
@@ -10348,7 +10486,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
10348
10486
|
});
|
|
10349
10487
|
|
|
10350
10488
|
// src/rules/prefer-non-nullable-collection.ts
|
|
10351
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as
|
|
10489
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
|
|
10352
10490
|
var preferNonNullableCollectionDocumentation = {
|
|
10353
10491
|
summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
|
|
10354
10492
|
rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
|
|
@@ -10478,14 +10616,14 @@ function directlyCoalesced(node) {
|
|
|
10478
10616
|
return parent?.type === AST_NODE_TYPES44.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
|
|
10479
10617
|
}
|
|
10480
10618
|
function identifierIsOnlyCoalesced(context, binding, fn) {
|
|
10481
|
-
const variable =
|
|
10619
|
+
const variable = ASTUtils12.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
10482
10620
|
if (variable === null || variable.references.length === 0) return false;
|
|
10483
10621
|
return variable.references.every(
|
|
10484
10622
|
(reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
|
|
10485
10623
|
);
|
|
10486
10624
|
}
|
|
10487
10625
|
function memberIsOnlyCoalesced(context, object, property, fn) {
|
|
10488
|
-
const variable =
|
|
10626
|
+
const variable = ASTUtils12.findVariable(context.sourceCode.getScope(object), object.name);
|
|
10489
10627
|
if (variable === null) return false;
|
|
10490
10628
|
const accesses = variable.references.flatMap((reference) => {
|
|
10491
10629
|
if (!belongsToFunction(reference.identifier, fn)) return [null];
|
|
@@ -12327,6 +12465,9 @@ var requireAssertNeverDocumentation = {
|
|
|
12327
12465
|
};
|
|
12328
12466
|
var isRuntimeHandlingStatement = (statement) => {
|
|
12329
12467
|
if (statement.type === AST_NODE_TYPES49.EmptyStatement) return false;
|
|
12468
|
+
if (statement.type === AST_NODE_TYPES49.BreakStatement) {
|
|
12469
|
+
return statement.label !== null;
|
|
12470
|
+
}
|
|
12330
12471
|
if (statement.type === AST_NODE_TYPES49.TSTypeAliasDeclaration || statement.type === AST_NODE_TYPES49.TSInterfaceDeclaration) {
|
|
12331
12472
|
return false;
|
|
12332
12473
|
}
|
|
@@ -12356,7 +12497,12 @@ function isExhaustiveFiniteSwitch(node, services) {
|
|
|
12356
12497
|
const discriminant = services.esTreeNodeToTSNodeMap.get(node.discriminant);
|
|
12357
12498
|
const discriminantType = checker.getTypeAtLocation(discriminant);
|
|
12358
12499
|
const constituents = discriminantType.isUnion() ? discriminantType.types : [discriminantType];
|
|
12359
|
-
if (constituents.length
|
|
12500
|
+
if (!discriminantType.isUnion() || constituents.length < 2) return false;
|
|
12501
|
+
if (constituents.every(
|
|
12502
|
+
(constituent) => (constituent.flags & ts2.TypeFlags.BooleanLiteral) !== 0
|
|
12503
|
+
)) {
|
|
12504
|
+
return false;
|
|
12505
|
+
}
|
|
12360
12506
|
const expected = /* @__PURE__ */ new Set();
|
|
12361
12507
|
for (const constituent of constituents) {
|
|
12362
12508
|
const key = finiteTypeKey(constituent, checker);
|
|
@@ -12424,7 +12570,7 @@ var require_assert_never_default = createRule({
|
|
|
12424
12570
|
});
|
|
12425
12571
|
|
|
12426
12572
|
// src/rules/require-fetch-timeout.ts
|
|
12427
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES50, ASTUtils as
|
|
12573
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES50, ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
|
|
12428
12574
|
var requireFetchTimeoutDocumentation = {
|
|
12429
12575
|
summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
|
|
12430
12576
|
rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
|
|
@@ -12505,7 +12651,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
12505
12651
|
}
|
|
12506
12652
|
function resolvesToGlobal(identifier) {
|
|
12507
12653
|
const scope = context.sourceCode.getScope(identifier);
|
|
12508
|
-
const variable =
|
|
12654
|
+
const variable = ASTUtils13.findVariable(scope, identifier.name);
|
|
12509
12655
|
return variable === null || variable.defs.length === 0;
|
|
12510
12656
|
}
|
|
12511
12657
|
function isGlobalFetchCall2(callee) {
|
|
@@ -12515,7 +12661,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
12515
12661
|
return callee.type === AST_NODE_TYPES50.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES50.Identifier && callee.property.name === "fetch" && callee.object.type === AST_NODE_TYPES50.Identifier && GLOBAL_OBJECTS2.has(callee.object.name) && resolvesToGlobal(callee.object);
|
|
12516
12662
|
}
|
|
12517
12663
|
function localConstInitProvablyLacksSignal(identifier) {
|
|
12518
|
-
const variable =
|
|
12664
|
+
const variable = ASTUtils13.findVariable(
|
|
12519
12665
|
context.sourceCode.getScope(identifier),
|
|
12520
12666
|
identifier.name
|
|
12521
12667
|
);
|
|
@@ -13157,34 +13303,35 @@ var require_static_next_matcher_default = createRule({
|
|
|
13157
13303
|
});
|
|
13158
13304
|
|
|
13159
13305
|
// src/rules/require-zod-form-validation.ts
|
|
13160
|
-
import {
|
|
13306
|
+
import {
|
|
13307
|
+
AST_NODE_TYPES as AST_NODE_TYPES53,
|
|
13308
|
+
ASTUtils as ASTUtils14
|
|
13309
|
+
} from "@typescript-eslint/utils";
|
|
13161
13310
|
var requireZodFormValidationDocumentation = {
|
|
13162
13311
|
summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
|
|
13163
13312
|
rationale: "FormData values are untrusted strings or files and need runtime validation before use.",
|
|
13164
13313
|
remediation: "Read the value inside a Zod schema's `parse` or `safeParse` input.",
|
|
13165
13314
|
category: "security",
|
|
13315
|
+
limitations: [
|
|
13316
|
+
"Tests are excluded; imported schema-shaped names are trusted when their implementation is outside the linted file.",
|
|
13317
|
+
"Delayed raw-value use is accepted only after an unconditional successful parse in the same block; safeParse remains valid when the raw binding has no unvalidated consumer."
|
|
13318
|
+
],
|
|
13166
13319
|
examples: [
|
|
13167
13320
|
{ id: "validated-form-value", title: "Validate the form value", outcome: "no-match", files: [{ path: "src/action.ts", source: "const input = UserSchema.parse({ name: formData.get('name') });" }], focusPath: "src/action.ts", expectedCount: 0, public: true },
|
|
13168
13321
|
{ id: "raw-form-value", title: "Do not use a raw form value", outcome: "match", files: [{ path: "src/action.ts", source: "const name = formData.get('name');" }], focusPath: "src/action.ts", expectedCount: 1, public: true }
|
|
13169
13322
|
]
|
|
13170
13323
|
};
|
|
13171
|
-
var
|
|
13172
|
-
|
|
13173
|
-
|
|
13174
|
-
|
|
13175
|
-
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
if (method !== "parse" && method !== "safeParse" && method !== "parseAsync" && method !== "safeParseAsync") {
|
|
13179
|
-
return false;
|
|
13180
|
-
}
|
|
13181
|
-
return looksLikeZodSchema(callee.object);
|
|
13182
|
-
};
|
|
13183
|
-
var looksLikeZodSchema = (node) => {
|
|
13324
|
+
var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
|
|
13325
|
+
"parse",
|
|
13326
|
+
"safeParse",
|
|
13327
|
+
"parseAsync",
|
|
13328
|
+
"safeParseAsync"
|
|
13329
|
+
]);
|
|
13330
|
+
var zodReceiverRoot = (node) => {
|
|
13184
13331
|
let current = node;
|
|
13185
13332
|
while (true) {
|
|
13186
13333
|
if (current.type === AST_NODE_TYPES53.Identifier) {
|
|
13187
|
-
return current
|
|
13334
|
+
return current;
|
|
13188
13335
|
}
|
|
13189
13336
|
if (current.type === AST_NODE_TYPES53.CallExpression) {
|
|
13190
13337
|
current = current.callee;
|
|
@@ -13194,7 +13341,7 @@ var looksLikeZodSchema = (node) => {
|
|
|
13194
13341
|
current = current.object;
|
|
13195
13342
|
continue;
|
|
13196
13343
|
}
|
|
13197
|
-
return
|
|
13344
|
+
return null;
|
|
13198
13345
|
}
|
|
13199
13346
|
};
|
|
13200
13347
|
var isFormDataMethodCall = (node) => {
|
|
@@ -13224,6 +13371,34 @@ var require_zod_form_validation_default = createRule({
|
|
|
13224
13371
|
if (isTestFile(context.filename)) {
|
|
13225
13372
|
return {};
|
|
13226
13373
|
}
|
|
13374
|
+
const zodBindings = /* @__PURE__ */ new Set();
|
|
13375
|
+
const resolvedBinding = (identifier) => ASTUtils14.findVariable(
|
|
13376
|
+
context.sourceCode.getScope(identifier),
|
|
13377
|
+
identifier.name
|
|
13378
|
+
);
|
|
13379
|
+
const isProvablyNonZodLocal = (identifier) => {
|
|
13380
|
+
const binding = resolvedBinding(identifier);
|
|
13381
|
+
if (binding === null || zodBindings.has(binding) || binding.defs.length !== 1) {
|
|
13382
|
+
return false;
|
|
13383
|
+
}
|
|
13384
|
+
const definition = binding.defs[0];
|
|
13385
|
+
if (definition?.type !== "Variable" || definition.node.type !== AST_NODE_TYPES53.VariableDeclarator) {
|
|
13386
|
+
return false;
|
|
13387
|
+
}
|
|
13388
|
+
const init = definition.node.init;
|
|
13389
|
+
return init?.type === AST_NODE_TYPES53.ObjectExpression || init?.type === AST_NODE_TYPES53.ArrayExpression || init?.type === AST_NODE_TYPES53.Literal || init?.type === AST_NODE_TYPES53.ArrowFunctionExpression || init?.type === AST_NODE_TYPES53.FunctionExpression;
|
|
13390
|
+
};
|
|
13391
|
+
const isZodParseCall = (node) => {
|
|
13392
|
+
if (node.type !== AST_NODE_TYPES53.CallExpression) return false;
|
|
13393
|
+
const callee = node.callee;
|
|
13394
|
+
if (callee.type !== AST_NODE_TYPES53.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES53.Identifier || !ZOD_PARSE_METHODS.has(callee.property.name)) {
|
|
13395
|
+
return false;
|
|
13396
|
+
}
|
|
13397
|
+
const root = zodReceiverRoot(callee.object);
|
|
13398
|
+
if (root === null) return false;
|
|
13399
|
+
const binding = resolvedBinding(root);
|
|
13400
|
+
return binding !== null && zodBindings.has(binding) || (root.name === "z" || ZOD_SCHEMA_NAME_RE.test(root.name)) && !isProvablyNonZodLocal(root);
|
|
13401
|
+
};
|
|
13227
13402
|
const isFormSourceIdentifier = (node) => {
|
|
13228
13403
|
if (node.type !== AST_NODE_TYPES53.Identifier) return false;
|
|
13229
13404
|
if (/formdata/i.test(node.name)) return true;
|
|
@@ -13249,14 +13424,15 @@ var require_zod_form_validation_default = createRule({
|
|
|
13249
13424
|
}
|
|
13250
13425
|
return isFormSourceIdentifier(callee.object);
|
|
13251
13426
|
};
|
|
13252
|
-
const
|
|
13427
|
+
const zodParseAncestor = (node) => {
|
|
13253
13428
|
let parent = node.parent;
|
|
13254
13429
|
while (parent !== null && parent !== void 0) {
|
|
13255
|
-
if (isZodParseCall(parent)) return
|
|
13430
|
+
if (isZodParseCall(parent)) return parent;
|
|
13256
13431
|
parent = parent.parent;
|
|
13257
13432
|
}
|
|
13258
|
-
return
|
|
13433
|
+
return null;
|
|
13259
13434
|
};
|
|
13435
|
+
const hasZodParseAncestor = (node) => zodParseAncestor(node) !== null;
|
|
13260
13436
|
const isInstanceofNarrowing = (node) => {
|
|
13261
13437
|
const parent = node.parent;
|
|
13262
13438
|
return parent !== null && parent !== void 0 && parent.type === AST_NODE_TYPES53.BinaryExpression && parent.operator === "instanceof" && parent.left === node && parent.right.type === AST_NODE_TYPES53.Identifier && (parent.right.name === "File" || parent.right.name === "Blob");
|
|
@@ -13273,14 +13449,111 @@ var require_zod_form_validation_default = createRule({
|
|
|
13273
13449
|
}
|
|
13274
13450
|
return null;
|
|
13275
13451
|
};
|
|
13452
|
+
const containingStatement = (node) => {
|
|
13453
|
+
let current = node;
|
|
13454
|
+
while (current.parent !== void 0) {
|
|
13455
|
+
const parent = current.parent;
|
|
13456
|
+
if (parent.type === AST_NODE_TYPES53.BlockStatement || parent.type === AST_NODE_TYPES53.Program) {
|
|
13457
|
+
return current;
|
|
13458
|
+
}
|
|
13459
|
+
current = parent;
|
|
13460
|
+
}
|
|
13461
|
+
return null;
|
|
13462
|
+
};
|
|
13463
|
+
const zodParseMethod = (call) => {
|
|
13464
|
+
const callee = call.callee;
|
|
13465
|
+
return callee.type === AST_NODE_TYPES53.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES53.Identifier ? callee.property.name : null;
|
|
13466
|
+
};
|
|
13467
|
+
const hasConditionalAncestorBeforeStatement = (node, statement) => {
|
|
13468
|
+
let current = node.parent;
|
|
13469
|
+
while (current !== void 0 && current !== statement) {
|
|
13470
|
+
if (current.type === AST_NODE_TYPES53.LogicalExpression || current.type === AST_NODE_TYPES53.ConditionalExpression) {
|
|
13471
|
+
return true;
|
|
13472
|
+
}
|
|
13473
|
+
current = current.parent;
|
|
13474
|
+
}
|
|
13475
|
+
return false;
|
|
13476
|
+
};
|
|
13477
|
+
const isAwaitedBeforeStatement = (node, statement) => {
|
|
13478
|
+
let current = node.parent;
|
|
13479
|
+
while (current !== void 0 && current !== statement) {
|
|
13480
|
+
if (current.type === AST_NODE_TYPES53.AwaitExpression) return true;
|
|
13481
|
+
current = current.parent;
|
|
13482
|
+
}
|
|
13483
|
+
return false;
|
|
13484
|
+
};
|
|
13485
|
+
const guaranteedValidationStatement = (declarator, reference) => {
|
|
13486
|
+
const parse2 = zodParseAncestor(reference);
|
|
13487
|
+
if (parse2 === null) return null;
|
|
13488
|
+
const declarationStatement = containingStatement(declarator);
|
|
13489
|
+
const validationStatement = containingStatement(parse2);
|
|
13490
|
+
if (declarationStatement === null || validationStatement === null || declarationStatement.parent !== validationStatement.parent || validationStatement.range[0] <= declarationStatement.range[1] || hasConditionalAncestorBeforeStatement(parse2, validationStatement)) {
|
|
13491
|
+
return null;
|
|
13492
|
+
}
|
|
13493
|
+
if (validationStatement.type !== AST_NODE_TYPES53.VariableDeclaration && validationStatement.type !== AST_NODE_TYPES53.ExpressionStatement) {
|
|
13494
|
+
return null;
|
|
13495
|
+
}
|
|
13496
|
+
const method = zodParseMethod(parse2);
|
|
13497
|
+
if (method === "parse") return validationStatement;
|
|
13498
|
+
if (method === "parseAsync" && isAwaitedBeforeStatement(parse2, validationStatement)) {
|
|
13499
|
+
return validationStatement;
|
|
13500
|
+
}
|
|
13501
|
+
return null;
|
|
13502
|
+
};
|
|
13503
|
+
const isSafePrevalidationInspection = (identifier) => {
|
|
13504
|
+
const parent = identifier.parent;
|
|
13505
|
+
if (parent.type === AST_NODE_TYPES53.UnaryExpression && parent.operator === "typeof") {
|
|
13506
|
+
return true;
|
|
13507
|
+
}
|
|
13508
|
+
if (parent.type !== AST_NODE_TYPES53.BinaryExpression || parent.left !== identifier) {
|
|
13509
|
+
return false;
|
|
13510
|
+
}
|
|
13511
|
+
if (parent.operator === "instanceof") {
|
|
13512
|
+
return parent.right.type === AST_NODE_TYPES53.Identifier && (parent.right.name === "File" || parent.right.name === "Blob");
|
|
13513
|
+
}
|
|
13514
|
+
return ["===", "!==", "==", "!="].includes(parent.operator) && (parent.right.type === AST_NODE_TYPES53.Literal && parent.right.value === null || parent.right.type === AST_NODE_TYPES53.Identifier && parent.right.name === "undefined");
|
|
13515
|
+
};
|
|
13516
|
+
const statementWithinBlock = (node, block) => {
|
|
13517
|
+
let current = node;
|
|
13518
|
+
while (current.parent !== void 0 && current.parent !== block) {
|
|
13519
|
+
current = current.parent;
|
|
13520
|
+
}
|
|
13521
|
+
return current.parent === block ? current : null;
|
|
13522
|
+
};
|
|
13276
13523
|
const bindingIsValidated = (declarator) => {
|
|
13277
13524
|
const variable = context.sourceCode.getDeclaredVariables(declarator)[0];
|
|
13278
13525
|
if (variable === void 0) return false;
|
|
13279
|
-
|
|
13280
|
-
(
|
|
13526
|
+
const references = variable.references.filter((reference) => !reference.isWriteOnly()).map((reference) => reference.identifier).filter(
|
|
13527
|
+
(identifier) => identifier.type === AST_NODE_TYPES53.Identifier
|
|
13281
13528
|
);
|
|
13529
|
+
if (references.length === 0) return false;
|
|
13530
|
+
if (references.some(isInstanceofNarrowing)) return true;
|
|
13531
|
+
const validationStatements = references.map((reference) => guaranteedValidationStatement(declarator, reference)).filter(
|
|
13532
|
+
(statement) => statement !== null
|
|
13533
|
+
);
|
|
13534
|
+
const declarationStatement = containingStatement(declarator);
|
|
13535
|
+
const declarationBlock = declarationStatement?.parent;
|
|
13536
|
+
return references.every((reference) => {
|
|
13537
|
+
if (zodParseAncestor(reference) !== null || isSafePrevalidationInspection(reference)) {
|
|
13538
|
+
return true;
|
|
13539
|
+
}
|
|
13540
|
+
if (declarationBlock === void 0) return false;
|
|
13541
|
+
const useStatement = statementWithinBlock(reference, declarationBlock);
|
|
13542
|
+
return useStatement !== null && validationStatements.some(
|
|
13543
|
+
(statement) => statement.range[1] < useStatement.range[0]
|
|
13544
|
+
);
|
|
13545
|
+
});
|
|
13282
13546
|
};
|
|
13283
13547
|
return {
|
|
13548
|
+
ImportDeclaration(node) {
|
|
13549
|
+
if (!isZodModule(node.source.value)) return;
|
|
13550
|
+
for (const specifier of node.specifiers) {
|
|
13551
|
+
if (specifier.type === AST_NODE_TYPES53.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES53.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES53.ImportSpecifier && (specifier.imported.type === AST_NODE_TYPES53.Identifier ? specifier.imported.name === "z" : specifier.imported.value === "z")) {
|
|
13552
|
+
const binding = resolvedBinding(specifier.local);
|
|
13553
|
+
if (binding !== null) zodBindings.add(binding);
|
|
13554
|
+
}
|
|
13555
|
+
}
|
|
13556
|
+
},
|
|
13284
13557
|
CallExpression(node) {
|
|
13285
13558
|
if (!isFormDataGetCall(node)) return;
|
|
13286
13559
|
if (hasZodParseAncestor(node) || isInstanceofNarrowing(node)) return;
|
|
@@ -13360,7 +13633,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
13360
13633
|
});
|
|
13361
13634
|
|
|
13362
13635
|
// src/rules/stepdown.ts
|
|
13363
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES54, ASTUtils as
|
|
13636
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES54, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
|
|
13364
13637
|
var stepdownDocumentation = {
|
|
13365
13638
|
summary: "Place a private helper below its sole direct same-scope caller.",
|
|
13366
13639
|
rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
|
|
@@ -13553,7 +13826,7 @@ function methodName(node) {
|
|
|
13553
13826
|
return !node.computed && node.key.type === AST_NODE_TYPES54.Identifier ? node.key.name : null;
|
|
13554
13827
|
}
|
|
13555
13828
|
function referencedMethod(context, node, classVariables) {
|
|
13556
|
-
const objectVariable = node.object.type === AST_NODE_TYPES54.Identifier ?
|
|
13829
|
+
const objectVariable = node.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils15.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
|
|
13557
13830
|
const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
|
|
13558
13831
|
if (node.object.type !== AST_NODE_TYPES54.ThisExpression && !isClassReference) return null;
|
|
13559
13832
|
if (node.property.type === AST_NODE_TYPES54.PrivateIdentifier) return `#${node.property.name}`;
|
|
@@ -13606,11 +13879,11 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
13606
13879
|
const pinned = /* @__PURE__ */ new Set();
|
|
13607
13880
|
const classVariables = /* @__PURE__ */ new Set();
|
|
13608
13881
|
if (node.id !== null) {
|
|
13609
|
-
const internal =
|
|
13882
|
+
const internal = ASTUtils15.findVariable(context.sourceCode.getScope(node), node.id.name);
|
|
13610
13883
|
if (internal !== null) classVariables.add(internal);
|
|
13611
13884
|
}
|
|
13612
13885
|
if (node.type === AST_NODE_TYPES54.ClassExpression && node.parent.type === AST_NODE_TYPES54.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES54.Identifier) {
|
|
13613
|
-
const outer =
|
|
13886
|
+
const outer = ASTUtils15.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
|
|
13614
13887
|
if (outer !== null) classVariables.add(outer);
|
|
13615
13888
|
}
|
|
13616
13889
|
for (const method of methods) {
|
|
@@ -13646,7 +13919,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
13646
13919
|
return;
|
|
13647
13920
|
}
|
|
13648
13921
|
if (binding.type !== AST_NODE_TYPES54.Identifier) return;
|
|
13649
|
-
const variable =
|
|
13922
|
+
const variable = ASTUtils15.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
13650
13923
|
if (variable !== null) {
|
|
13651
13924
|
methodClassVariables.add(variable);
|
|
13652
13925
|
methodAliases.add(variable);
|
|
@@ -13676,7 +13949,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
13676
13949
|
return;
|
|
13677
13950
|
}
|
|
13678
13951
|
if (!privateNames.has(target)) return;
|
|
13679
|
-
const objectVariable = current.object.type === AST_NODE_TYPES54.Identifier ?
|
|
13952
|
+
const objectVariable = current.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils15.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
|
|
13680
13953
|
if (objectVariable !== null && methodAliases.has(objectVariable)) {
|
|
13681
13954
|
pinned.add(target);
|
|
13682
13955
|
return;
|
|
@@ -13760,7 +14033,7 @@ var stepdown_default = createRule({
|
|
|
13760
14033
|
// src/rules/zod-naming-convention.ts
|
|
13761
14034
|
import {
|
|
13762
14035
|
AST_NODE_TYPES as AST_NODE_TYPES55,
|
|
13763
|
-
ASTUtils as
|
|
14036
|
+
ASTUtils as ASTUtils16
|
|
13764
14037
|
} from "@typescript-eslint/utils";
|
|
13765
14038
|
var zodNamingConventionDocumentation = {
|
|
13766
14039
|
summary: "Enforce a consistent Zod schema naming convention \u2014 a `Z` prefix (`ZUser`) or a `Schema` suffix (`userSchema`); both are accepted by default.",
|
|
@@ -13847,7 +14120,7 @@ var zod_naming_convention_default = createRule({
|
|
|
13847
14120
|
const acceptsSchemaWord = convention !== "prefix";
|
|
13848
14121
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
13849
14122
|
function resolvedBinding(identifier) {
|
|
13850
|
-
return
|
|
14123
|
+
return ASTUtils16.findVariable(
|
|
13851
14124
|
context.sourceCode.getScope(identifier),
|
|
13852
14125
|
identifier.name
|
|
13853
14126
|
);
|
|
@@ -14043,7 +14316,7 @@ var rules = {
|
|
|
14043
14316
|
};
|
|
14044
14317
|
var meta = {
|
|
14045
14318
|
name: "@sarj/eslint-plugin",
|
|
14046
|
-
version: "15.
|
|
14319
|
+
version: "15.2.0"
|
|
14047
14320
|
};
|
|
14048
14321
|
var applicationOnlyRules = [
|
|
14049
14322
|
"no-restricted-library-load",
|