@sarj/eslint-plugin 15.1.0 → 15.3.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 +472 -105
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +498 -127
- 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) {
|
|
@@ -3207,14 +3224,12 @@ var noJsonStringifyErrorDocumentation = {
|
|
|
3207
3224
|
rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
|
|
3208
3225
|
remediation: "Serialize explicit error fields or use an error-aware serializer.",
|
|
3209
3226
|
category: "correctness",
|
|
3210
|
-
limitations: ["The rule uses local
|
|
3227
|
+
limitations: ["The rule uses local catch-binding and constructor provenance rather than type information."],
|
|
3211
3228
|
examples: [
|
|
3212
3229
|
{ id: "explicit-error-message", title: "Serialize an enumerable error field", outcome: "no-match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err.message }); }" }], focusPath: "src/report.ts", expectedCount: 0, public: true },
|
|
3213
3230
|
{ id: "stringified-error", title: "Do not stringify an Error object", outcome: "match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err }); }" }], focusPath: "src/report.ts", expectedCount: 1, public: true }
|
|
3214
3231
|
]
|
|
3215
3232
|
};
|
|
3216
|
-
var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
|
|
3217
|
-
var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
|
|
3218
3233
|
var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
|
|
3219
3234
|
var PAYLOAD_PROPS = /* @__PURE__ */ new Set([
|
|
3220
3235
|
"data",
|
|
@@ -3242,6 +3257,21 @@ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
|
3242
3257
|
"TypeError",
|
|
3243
3258
|
"URIError"
|
|
3244
3259
|
]);
|
|
3260
|
+
function identifierIsProvenError(identifier, scope) {
|
|
3261
|
+
if (isCatchBinding(scope, identifier.name)) return true;
|
|
3262
|
+
let current = scope;
|
|
3263
|
+
while (current !== null && !current.set.has(identifier.name)) {
|
|
3264
|
+
current = current.upper;
|
|
3265
|
+
}
|
|
3266
|
+
const variable = current?.set.get(identifier.name);
|
|
3267
|
+
if (variable === void 0 || variable.defs.length !== 1) return false;
|
|
3268
|
+
const definition = variable.defs[0];
|
|
3269
|
+
if (definition?.type !== "Variable") return false;
|
|
3270
|
+
const initializer = definition.node.init;
|
|
3271
|
+
return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && variable.references.every(
|
|
3272
|
+
(reference) => !reference.isWrite() || reference.init === true
|
|
3273
|
+
);
|
|
3274
|
+
}
|
|
3245
3275
|
function isCatchBinding(scope, name) {
|
|
3246
3276
|
let current = scope;
|
|
3247
3277
|
while (current) {
|
|
@@ -3257,22 +3287,6 @@ function isCatchBinding(scope, name) {
|
|
|
3257
3287
|
}
|
|
3258
3288
|
return false;
|
|
3259
3289
|
}
|
|
3260
|
-
function memberSuggestsError(member, scope) {
|
|
3261
|
-
const propName2 = !member.computed && member.property.type === "Identifier" ? member.property.name : null;
|
|
3262
|
-
if (propName2 !== null && ERROR_PROP_PATTERN.test(propName2)) {
|
|
3263
|
-
return true;
|
|
3264
|
-
}
|
|
3265
|
-
const base = member.object;
|
|
3266
|
-
const baseSuggestsError = base.type === "Identifier" && (ERROR_NAME_PATTERN.test(base.name) || isCatchBinding(scope, base.name));
|
|
3267
|
-
if (baseSuggestsError) {
|
|
3268
|
-
if (propName2 === null) {
|
|
3269
|
-
return true;
|
|
3270
|
-
}
|
|
3271
|
-
const lowered = propName2.toLowerCase();
|
|
3272
|
-
return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
|
|
3273
|
-
}
|
|
3274
|
-
return false;
|
|
3275
|
-
}
|
|
3276
3290
|
function positiveErrorSubject(test) {
|
|
3277
3291
|
return instanceofErrorSubject(test) ?? typeGuardSubject(test);
|
|
3278
3292
|
}
|
|
@@ -3376,27 +3390,25 @@ function directLiteralValues(argument) {
|
|
|
3376
3390
|
}
|
|
3377
3391
|
function expressionSuggestsError(expression, scope) {
|
|
3378
3392
|
if (expression.type === "Identifier") {
|
|
3379
|
-
return
|
|
3393
|
+
return identifierIsProvenError(expression, scope);
|
|
3394
|
+
}
|
|
3395
|
+
if (expression.type === "NewExpression" && expression.callee.type === "Identifier") {
|
|
3396
|
+
return BUILTIN_ERROR_CONSTRUCTORS.has(expression.callee.name);
|
|
3380
3397
|
}
|
|
3381
3398
|
return expression.type === "MemberExpression" && memberSuggestsError(expression, scope);
|
|
3382
3399
|
}
|
|
3383
|
-
function
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
if (definition?.type !== "Variable") return false;
|
|
3394
|
-
const initializer = definition.node.init;
|
|
3395
|
-
return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && variable.references.every(
|
|
3396
|
-
(reference) => !reference.isWrite() || reference.init === true
|
|
3397
|
-
);
|
|
3400
|
+
function memberSuggestsError(member, scope) {
|
|
3401
|
+
const propName2 = !member.computed && member.property.type === "Identifier" ? member.property.name : null;
|
|
3402
|
+
const base = member.object;
|
|
3403
|
+
const baseSuggestsError = base.type === "Identifier" && identifierIsProvenError(base, scope);
|
|
3404
|
+
if (baseSuggestsError) {
|
|
3405
|
+
if (propName2 === null) {
|
|
3406
|
+
return true;
|
|
3407
|
+
}
|
|
3408
|
+
const lowered = propName2.toLowerCase();
|
|
3409
|
+
return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
|
|
3398
3410
|
}
|
|
3399
|
-
return
|
|
3411
|
+
return false;
|
|
3400
3412
|
}
|
|
3401
3413
|
var no_json_stringify_error_default = createRule({
|
|
3402
3414
|
name: "no-json-stringify-error",
|
|
@@ -3423,9 +3435,8 @@ var no_json_stringify_error_default = createRule({
|
|
|
3423
3435
|
return;
|
|
3424
3436
|
}
|
|
3425
3437
|
const scope = context.sourceCode.getScope(firstArg);
|
|
3426
|
-
const isNestedLiteral = firstArg.type === "ObjectExpression" || firstArg.type === "ArrayExpression";
|
|
3427
3438
|
const unsafeValue = directLiteralValues(firstArg).find(
|
|
3428
|
-
(value) =>
|
|
3439
|
+
(value) => expressionSuggestsError(value, scope) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
|
|
3429
3440
|
);
|
|
3430
3441
|
if (unsafeValue === void 0) {
|
|
3431
3442
|
return;
|
|
@@ -3994,7 +4005,7 @@ var VALUE_TAG_RE = /@(example|deprecated|see|remarks|throws|internal|public|alph
|
|
|
3994
4005
|
var BOUNDARY_RE = /(?<=[.!?])["'`)\]]*\s+(?=[A-Z0-9`])/;
|
|
3995
4006
|
var BULLET_RE = /^\s*(?:[-*+] |\d+[.)] )/;
|
|
3996
4007
|
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;
|
|
4008
|
+
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
4009
|
function body(comment) {
|
|
3999
4010
|
return comment.value.replace(/^\*/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, "")).join("\n").trim();
|
|
4000
4011
|
}
|
|
@@ -4886,6 +4897,7 @@ var ANALYTICS_SEGMENTS2 = /* @__PURE__ */ new Set([
|
|
|
4886
4897
|
]);
|
|
4887
4898
|
var SERVER_ACTION_SKIP_FILE_RE = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
|
|
4888
4899
|
var NON_REACT_FRAMEWORK_RE = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
|
|
4900
|
+
var NEXT_MODULE_PATH_RE = /(?:^|[/\\])(?:app|pages)[/\\]/u;
|
|
4889
4901
|
function isGlobalFetchCall(node, resolvesToGlobal) {
|
|
4890
4902
|
const callee = node.callee;
|
|
4891
4903
|
if (callee.type === "Identifier") {
|
|
@@ -5001,6 +5013,13 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
5001
5013
|
const nonReactFramework = context.sourceCode.ast.body.some(
|
|
5002
5014
|
(statement) => statement.type === AST_NODE_TYPES18.ImportDeclaration && typeof statement.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(statement.source.value)
|
|
5003
5015
|
);
|
|
5016
|
+
const hasUseClientDirective = context.sourceCode.ast.body.some(
|
|
5017
|
+
(statement) => statement.type === AST_NODE_TYPES18.ExpressionStatement && statement.expression.type === AST_NODE_TYPES18.Literal && statement.expression.value === "use client"
|
|
5018
|
+
);
|
|
5019
|
+
const hasNextImport = context.sourceCode.ast.body.some(
|
|
5020
|
+
(statement) => statement.type === AST_NODE_TYPES18.ImportDeclaration && typeof statement.source.value === "string" && (statement.source.value === "next" || statement.source.value.startsWith("next/"))
|
|
5021
|
+
);
|
|
5022
|
+
const hasNextEvidence = hasNextImport || hasUseClientDirective && NEXT_MODULE_PATH_RE.test(filename);
|
|
5004
5023
|
function resolvesToGlobal(identifier) {
|
|
5005
5024
|
const variable = ASTUtils5.findVariable(
|
|
5006
5025
|
context.sourceCode.getScope(identifier),
|
|
@@ -5061,7 +5080,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
5061
5080
|
return resolved?.type === AST_NODE_TYPES18.LogicalExpression && resolved.operator === "||" && (isMutationMethod2(resolved.left) || isMutationMethod2(resolved.right));
|
|
5062
5081
|
}
|
|
5063
5082
|
function serverActionOwns(node) {
|
|
5064
|
-
if (node.callee.type !== AST_NODE_TYPES18.Identifier || SERVER_ACTION_SKIP_FILE_RE.test(filename) || nonReactFramework) {
|
|
5083
|
+
if (node.callee.type !== AST_NODE_TYPES18.Identifier || !hasNextEvidence || SERVER_ACTION_SKIP_FILE_RE.test(filename) || nonReactFramework) {
|
|
5065
5084
|
return false;
|
|
5066
5085
|
}
|
|
5067
5086
|
const url = node.arguments[0];
|
|
@@ -6280,6 +6299,20 @@ var SAFE_PARSE_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
|
6280
6299
|
function isBodyDecodeNode(node) {
|
|
6281
6300
|
return node.type === AST_NODE_TYPES23.CallExpression && node.callee.type === AST_NODE_TYPES23.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES23.Identifier && BODY_DECODE_METHODS.has(node.callee.property.name);
|
|
6282
6301
|
}
|
|
6302
|
+
function isSafeParseSupportCall(node) {
|
|
6303
|
+
const callee = node.callee;
|
|
6304
|
+
if (callee.type !== AST_NODE_TYPES23.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES23.Identifier) {
|
|
6305
|
+
return false;
|
|
6306
|
+
}
|
|
6307
|
+
if (callee.property.name === "isArray" && callee.object.type === AST_NODE_TYPES23.Identifier && callee.object.name === "Array") {
|
|
6308
|
+
return true;
|
|
6309
|
+
}
|
|
6310
|
+
if (callee.property.name !== "getItem") return false;
|
|
6311
|
+
if (callee.object.type === AST_NODE_TYPES23.Identifier && (callee.object.name === "localStorage" || callee.object.name === "sessionStorage")) {
|
|
6312
|
+
return true;
|
|
6313
|
+
}
|
|
6314
|
+
return callee.object.type === AST_NODE_TYPES23.MemberExpression && !callee.object.computed && callee.object.object.type === AST_NODE_TYPES23.Identifier && (callee.object.object.name === "window" || callee.object.object.name === "globalThis") && callee.object.property.type === AST_NODE_TYPES23.Identifier && (callee.object.property.name === "localStorage" || callee.object.property.name === "sessionStorage");
|
|
6315
|
+
}
|
|
6283
6316
|
var BODY_DECODE_METHODS = /* @__PURE__ */ new Set([
|
|
6284
6317
|
"json",
|
|
6285
6318
|
"text",
|
|
@@ -6348,6 +6381,7 @@ function tryReturnsSafeParse(catchNode) {
|
|
|
6348
6381
|
if (current.type === AST_NODE_TYPES23.CallExpression || current.type === AST_NODE_TYPES23.NewExpression) {
|
|
6349
6382
|
if (isParseShapedNode(current) || isBodyDecodeNode(current)) {
|
|
6350
6383
|
sawSafeParse = true;
|
|
6384
|
+
} else if (current.type === AST_NODE_TYPES23.CallExpression && isSafeParseSupportCall(current)) {
|
|
6351
6385
|
} else {
|
|
6352
6386
|
sawUnsafeOperation = true;
|
|
6353
6387
|
return;
|
|
@@ -6810,22 +6844,25 @@ var noStorageInStatelessModulesDocumentation = {
|
|
|
6810
6844
|
rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
|
|
6811
6845
|
remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
|
|
6812
6846
|
category: "architecture",
|
|
6813
|
-
limitations: ["The rule is disabled until module path patterns are configured
|
|
6847
|
+
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
6848
|
examples: [
|
|
6815
6849
|
{ 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
6850
|
{ 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
6851
|
]
|
|
6818
6852
|
};
|
|
6819
6853
|
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;
|
|
6854
|
+
return patterns.map((pattern) => new RegExp(pattern));
|
|
6828
6855
|
}
|
|
6856
|
+
var STORAGE_RECEIVER_WORDS = /* @__PURE__ */ new Set([
|
|
6857
|
+
"bucket",
|
|
6858
|
+
"cache",
|
|
6859
|
+
"kv",
|
|
6860
|
+
"namespace",
|
|
6861
|
+
"r2",
|
|
6862
|
+
"redis",
|
|
6863
|
+
"storage",
|
|
6864
|
+
"store"
|
|
6865
|
+
]);
|
|
6829
6866
|
function storageMethodName(node, methods) {
|
|
6830
6867
|
const callee = node.callee;
|
|
6831
6868
|
if (callee.type !== AST_NODE_TYPES26.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES26.Identifier) {
|
|
@@ -6838,8 +6875,31 @@ function storageMethodName(node, methods) {
|
|
|
6838
6875
|
if (node.arguments.length < (MIN_ARGUMENTS.get(name) ?? 1)) {
|
|
6839
6876
|
return null;
|
|
6840
6877
|
}
|
|
6878
|
+
if (name === "put" && !isStorageLikeReceiver(callee.object)) {
|
|
6879
|
+
return null;
|
|
6880
|
+
}
|
|
6841
6881
|
return name;
|
|
6842
6882
|
}
|
|
6883
|
+
function isStorageLikeReceiver(node) {
|
|
6884
|
+
if (node.type === AST_NODE_TYPES26.Identifier) {
|
|
6885
|
+
return isStorageIdentifier(node.name);
|
|
6886
|
+
}
|
|
6887
|
+
if (node.type !== AST_NODE_TYPES26.MemberExpression) {
|
|
6888
|
+
return false;
|
|
6889
|
+
}
|
|
6890
|
+
if (!node.computed && node.property.type === AST_NODE_TYPES26.Identifier && isStorageIdentifier(node.property.name)) {
|
|
6891
|
+
return true;
|
|
6892
|
+
}
|
|
6893
|
+
return isStorageLikeReceiver(node.object);
|
|
6894
|
+
}
|
|
6895
|
+
function isStorageIdentifier(name) {
|
|
6896
|
+
return identifierWords(name).some(
|
|
6897
|
+
(word) => STORAGE_RECEIVER_WORDS.has(word.toLowerCase())
|
|
6898
|
+
);
|
|
6899
|
+
}
|
|
6900
|
+
function identifierWords(name) {
|
|
6901
|
+
return name.match(/[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)|\d+/gu) ?? [name];
|
|
6902
|
+
}
|
|
6843
6903
|
var no_storage_in_stateless_modules_default = createRule({
|
|
6844
6904
|
name: "no-storage-in-stateless-modules",
|
|
6845
6905
|
documentation: noStorageInStatelessModulesDocumentation,
|
|
@@ -7241,12 +7301,86 @@ var no_tautological_expect_default = createRule({
|
|
|
7241
7301
|
});
|
|
7242
7302
|
|
|
7243
7303
|
// src/rules/no-typed-doc-sections.ts
|
|
7304
|
+
var TYPED_TAG_RE2 = /^\s*@(arg|argument|param|return|returns|yield|yields)\b(.*)$/iu;
|
|
7305
|
+
var PARAM_TAGS2 = /* @__PURE__ */ new Set(["arg", "argument", "param"]);
|
|
7306
|
+
var PARAMETER_FILLER = /* @__PURE__ */ new Set([
|
|
7307
|
+
"a",
|
|
7308
|
+
"an",
|
|
7309
|
+
"argument",
|
|
7310
|
+
"given",
|
|
7311
|
+
"input",
|
|
7312
|
+
"parameter",
|
|
7313
|
+
"passed",
|
|
7314
|
+
"provided",
|
|
7315
|
+
"the",
|
|
7316
|
+
"value"
|
|
7317
|
+
]);
|
|
7318
|
+
var RESULT_FILLER = /* @__PURE__ */ new Set([
|
|
7319
|
+
"a",
|
|
7320
|
+
"an",
|
|
7321
|
+
"array",
|
|
7322
|
+
"boolean",
|
|
7323
|
+
"generator",
|
|
7324
|
+
"number",
|
|
7325
|
+
"object",
|
|
7326
|
+
"output",
|
|
7327
|
+
"promise",
|
|
7328
|
+
"result",
|
|
7329
|
+
"return",
|
|
7330
|
+
"returned",
|
|
7331
|
+
"returns",
|
|
7332
|
+
"string",
|
|
7333
|
+
"the",
|
|
7334
|
+
"value"
|
|
7335
|
+
]);
|
|
7336
|
+
function hasVacuousTypedTag(text) {
|
|
7337
|
+
const tags = typedTags(text);
|
|
7338
|
+
return tags.length > 0 && tags.some(isVacuousTag);
|
|
7339
|
+
}
|
|
7340
|
+
function typedTags(text) {
|
|
7341
|
+
const tags = [];
|
|
7342
|
+
for (const raw of text.split("\n")) {
|
|
7343
|
+
const match = TYPED_TAG_RE2.exec(raw);
|
|
7344
|
+
if (match !== null) {
|
|
7345
|
+
tags.push({ kind: (match[1] ?? "").toLowerCase(), payload: (match[2] ?? "").trim() });
|
|
7346
|
+
} else if (tags.length > 0 && raw.trim().length > 0 && !raw.trim().startsWith("@")) {
|
|
7347
|
+
const last = tags.at(-1);
|
|
7348
|
+
last.payload = `${last.payload} ${raw.trim()}`.trim();
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7351
|
+
return tags.map(({ kind, payload }) => {
|
|
7352
|
+
let rest = payload.replace(/^\{[^}\n]+\}\s*/u, "").trim();
|
|
7353
|
+
if (!PARAM_TAGS2.has(kind)) {
|
|
7354
|
+
return { kind, name: null, description: rest.replace(/^-\s*/u, "").trim() };
|
|
7355
|
+
}
|
|
7356
|
+
const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s*|\s+)?(.*)$/u.exec(rest);
|
|
7357
|
+
if (match === null) return { kind, name: null, description: "" };
|
|
7358
|
+
const rawName = (match[1] ?? "").replace(/^\[/u, "").replace(/\]$/u, "").split("=")[0] ?? "";
|
|
7359
|
+
rest = (match[2] ?? "").trim();
|
|
7360
|
+
return { kind, name: rawName, description: rest };
|
|
7361
|
+
});
|
|
7362
|
+
}
|
|
7363
|
+
function isVacuousTag(tag) {
|
|
7364
|
+
const description = words(tag.description).map(canonicalWord);
|
|
7365
|
+
if (description.length === 0) return true;
|
|
7366
|
+
if (tag.name === null) return description.every((word) => RESULT_FILLER.has(word));
|
|
7367
|
+
const nameWords2 = new Set(words(tag.name).map(canonicalWord));
|
|
7368
|
+
return description.every((word) => PARAMETER_FILLER.has(word) || nameWords2.has(word));
|
|
7369
|
+
}
|
|
7370
|
+
function words(text) {
|
|
7371
|
+
return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[a-z][a-z0-9]*/gu) ?? [];
|
|
7372
|
+
}
|
|
7373
|
+
function canonicalWord(word) {
|
|
7374
|
+
if (["identifier", "identifiers", "ids"].includes(word)) return "id";
|
|
7375
|
+
if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
|
|
7376
|
+
return word;
|
|
7377
|
+
}
|
|
7244
7378
|
var noTypedDocSectionsDocumentation = {
|
|
7245
7379
|
summary: "Reject typed-signature repetition while preserving behavior that types cannot express.",
|
|
7246
7380
|
rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
|
|
7247
7381
|
remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
|
|
7248
7382
|
category: "maintainability",
|
|
7249
|
-
limitations: ["
|
|
7383
|
+
limitations: ["Description-free or name-restating parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
|
|
7250
7384
|
examples: [
|
|
7251
7385
|
{
|
|
7252
7386
|
id: "behavioral-documentation",
|
|
@@ -7284,7 +7418,7 @@ var no_typed_doc_sections_default = createRule({
|
|
|
7284
7418
|
return {
|
|
7285
7419
|
Program() {
|
|
7286
7420
|
for (const group of proseGroups(context.filename, context.sourceCode, true)) {
|
|
7287
|
-
if (group.hasTypedTags && documentsTypedFunction(context.sourceCode, group.comment)) {
|
|
7421
|
+
if (group.hasTypedTags && hasVacuousTypedTag(group.text) && documentsTypedFunction(context.sourceCode, group.comment)) {
|
|
7288
7422
|
context.report({ node: group.comment, messageId: "typedSection" });
|
|
7289
7423
|
}
|
|
7290
7424
|
}
|
|
@@ -7385,28 +7519,32 @@ var STOPWORDS3 = /* @__PURE__ */ new Set([
|
|
|
7385
7519
|
"with"
|
|
7386
7520
|
]);
|
|
7387
7521
|
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;
|
|
7522
|
+
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
7523
|
function narratesValue(body2, code) {
|
|
7389
7524
|
if (body2.length === 0 || DIRECTIVE_RE5.test(body2) || hasExternalReference(body2)) return false;
|
|
7390
7525
|
const codeNumbers = numbersIn(code);
|
|
7391
7526
|
if (codeNumbers.size === 0) return false;
|
|
7392
|
-
const
|
|
7393
|
-
if (
|
|
7527
|
+
const words2 = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
|
|
7528
|
+
if (words2.length === 0) return false;
|
|
7394
7529
|
const commentNumbers = numbersIn(body2);
|
|
7395
7530
|
if (commentNumbers.size === 0) return false;
|
|
7396
7531
|
for (const number of commentNumbers) {
|
|
7397
7532
|
if (!codeNumbers.has(number)) return false;
|
|
7398
7533
|
}
|
|
7399
|
-
if (!
|
|
7534
|
+
if (!words2.some((word) => UNIT_WORDS.has(word))) return false;
|
|
7400
7535
|
const identifiers = codeTokens(code);
|
|
7401
7536
|
const stems = /* @__PURE__ */ new Set();
|
|
7402
7537
|
for (const token of identifiers) stems.add(stem(token));
|
|
7403
|
-
return
|
|
7538
|
+
return words2.every(
|
|
7404
7539
|
(word) => STOPWORDS3.has(word) || UNIT_WORDS.has(word) || commentNumbers.has(word) || identifiers.has(word) || stems.has(stem(word))
|
|
7405
7540
|
);
|
|
7406
7541
|
}
|
|
7407
7542
|
function numbersIn(text) {
|
|
7408
7543
|
return new Set(text.match(NUMBER_RE) ?? []);
|
|
7409
7544
|
}
|
|
7545
|
+
function nameAlreadyCarriesUnit(code) {
|
|
7546
|
+
return (code.match(/[A-Za-z_$][\w$]*/gu) ?? []).some((identifier) => UNIT_NAME_SUFFIX_RE.test(identifier));
|
|
7547
|
+
}
|
|
7410
7548
|
var no_trailing_value_narration_default = createRule({
|
|
7411
7549
|
name: "no-trailing-value-narration",
|
|
7412
7550
|
documentation: noTrailingValueNarrationDocumentation,
|
|
@@ -7417,6 +7555,7 @@ var no_trailing_value_narration_default = createRule({
|
|
|
7417
7555
|
},
|
|
7418
7556
|
schema: [],
|
|
7419
7557
|
messages: {
|
|
7558
|
+
deleteNarration: "Trailing comment restates the literal and the identifier already names its unit \u2014 delete the comment so it cannot drift.",
|
|
7420
7559
|
narratesValue: "Trailing comment restates the literal on this line \u2014 put the unit in the name (STALE_TIME_MS) so it cannot drift."
|
|
7421
7560
|
}
|
|
7422
7561
|
},
|
|
@@ -7449,7 +7588,10 @@ var no_trailing_value_narration_default = createRule({
|
|
|
7449
7588
|
const code = line.slice(0, comment.loc.start.column);
|
|
7450
7589
|
const body2 = comment.value.replace(/^\*+/, "").replace(/\*+$/, "").trim();
|
|
7451
7590
|
if (narratesValue(body2, code)) {
|
|
7452
|
-
context.report({
|
|
7591
|
+
context.report({
|
|
7592
|
+
node: comment,
|
|
7593
|
+
messageId: nameAlreadyCarriesUnit(code) ? "deleteNarration" : "narratesValue"
|
|
7594
|
+
});
|
|
7453
7595
|
}
|
|
7454
7596
|
}
|
|
7455
7597
|
}
|
|
@@ -8367,7 +8509,8 @@ var no_unsafe_mock_casting_default = createRule({
|
|
|
8367
8509
|
// src/rules/no-zod-native-enum.ts
|
|
8368
8510
|
import {
|
|
8369
8511
|
ESLintUtils as ESLintUtils2,
|
|
8370
|
-
AST_NODE_TYPES as AST_NODE_TYPES34
|
|
8512
|
+
AST_NODE_TYPES as AST_NODE_TYPES34,
|
|
8513
|
+
ASTUtils as ASTUtils8
|
|
8371
8514
|
} from "@typescript-eslint/utils";
|
|
8372
8515
|
import * as ts from "typescript";
|
|
8373
8516
|
var noZodNativeEnumDocumentation = {
|
|
@@ -8496,15 +8639,23 @@ var no_zod_native_enum_default = createRule({
|
|
|
8496
8639
|
} catch {
|
|
8497
8640
|
services = null;
|
|
8498
8641
|
}
|
|
8499
|
-
const
|
|
8500
|
-
const
|
|
8642
|
+
const zodImportedBindings = /* @__PURE__ */ new Map();
|
|
8643
|
+
const zodNamespaceBindings = /* @__PURE__ */ new Set();
|
|
8644
|
+
function resolvedBinding(identifier) {
|
|
8645
|
+
return ASTUtils8.findVariable(
|
|
8646
|
+
sourceCode.getScope(identifier),
|
|
8647
|
+
identifier.name
|
|
8648
|
+
);
|
|
8649
|
+
}
|
|
8501
8650
|
function isZodMemberCall(node, api) {
|
|
8502
8651
|
const callee = node.callee;
|
|
8503
|
-
if (callee.type === AST_NODE_TYPES34.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES34.Identifier &&
|
|
8504
|
-
|
|
8652
|
+
if (callee.type === AST_NODE_TYPES34.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES34.Identifier && callee.property.type === AST_NODE_TYPES34.Identifier) {
|
|
8653
|
+
const binding = resolvedBinding(callee.object);
|
|
8654
|
+
return binding !== null && zodNamespaceBindings.has(binding) && callee.property.name === api;
|
|
8505
8655
|
}
|
|
8506
8656
|
if (callee.type === AST_NODE_TYPES34.Identifier) {
|
|
8507
|
-
|
|
8657
|
+
const binding = resolvedBinding(callee);
|
|
8658
|
+
return binding !== null && zodImportedBindings.get(binding) === api;
|
|
8508
8659
|
}
|
|
8509
8660
|
return false;
|
|
8510
8661
|
}
|
|
@@ -8539,10 +8690,14 @@ var no_zod_native_enum_default = createRule({
|
|
|
8539
8690
|
}
|
|
8540
8691
|
for (const spec of node.specifiers) {
|
|
8541
8692
|
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
|
-
|
|
8693
|
+
const binding = resolvedBinding(spec.local);
|
|
8694
|
+
if (binding !== null) zodNamespaceBindings.add(binding);
|
|
8543
8695
|
}
|
|
8544
8696
|
if (spec.type === AST_NODE_TYPES34.ImportSpecifier && spec.imported.type === AST_NODE_TYPES34.Identifier) {
|
|
8545
|
-
|
|
8697
|
+
const binding = resolvedBinding(spec.local);
|
|
8698
|
+
if (binding !== null) {
|
|
8699
|
+
zodImportedBindings.set(binding, spec.imported.name);
|
|
8700
|
+
}
|
|
8546
8701
|
}
|
|
8547
8702
|
}
|
|
8548
8703
|
},
|
|
@@ -8579,7 +8734,7 @@ var no_zod_native_enum_default = createRule({
|
|
|
8579
8734
|
});
|
|
8580
8735
|
|
|
8581
8736
|
// src/rules/test-loops-over-literal-cases.ts
|
|
8582
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES35, ASTUtils as
|
|
8737
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES35, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
|
|
8583
8738
|
var testLoopsOverLiteralCasesDocumentation = {
|
|
8584
8739
|
summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
|
|
8585
8740
|
rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
|
|
@@ -8742,7 +8897,7 @@ var test_loops_over_literal_cases_default = createRule({
|
|
|
8742
8897
|
return {};
|
|
8743
8898
|
}
|
|
8744
8899
|
const isFrameworkIdentifier = (identifier, modules) => {
|
|
8745
|
-
const variable =
|
|
8900
|
+
const variable = ASTUtils9.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
8746
8901
|
if (variable === null || variable.defs.length === 0) return true;
|
|
8747
8902
|
return variable.defs.some((definition) => {
|
|
8748
8903
|
let current = definition.node;
|
|
@@ -9179,7 +9334,7 @@ var prefer_input_group_search_default = createRule({
|
|
|
9179
9334
|
});
|
|
9180
9335
|
|
|
9181
9336
|
// src/rules/prefer-immutable-module-constant.ts
|
|
9182
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES39, ASTUtils as
|
|
9337
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES39, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
|
|
9183
9338
|
var preferImmutableModuleConstantDocumentation = {
|
|
9184
9339
|
summary: "Require module-level constant collections to expose readonly state.",
|
|
9185
9340
|
rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
|
|
@@ -9332,7 +9487,7 @@ var prefer_immutable_module_constant_default = createRule({
|
|
|
9332
9487
|
create(context) {
|
|
9333
9488
|
const sourceCode = context.sourceCode;
|
|
9334
9489
|
const isUnshadowedGlobal = (identifier) => {
|
|
9335
|
-
const variable =
|
|
9490
|
+
const variable = ASTUtils10.findVariable(sourceCode.getScope(identifier), identifier.name);
|
|
9336
9491
|
return variable === null || variable.defs.length === 0;
|
|
9337
9492
|
};
|
|
9338
9493
|
if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
|
|
@@ -10249,7 +10404,7 @@ var prefer_module_level_schema_default = createRule({
|
|
|
10249
10404
|
});
|
|
10250
10405
|
|
|
10251
10406
|
// src/rules/prefer-native-random-uuid.ts
|
|
10252
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES43, ASTUtils as
|
|
10407
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES43, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
|
|
10253
10408
|
var preferNativeRandomUuidDocumentation = {
|
|
10254
10409
|
summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
|
|
10255
10410
|
rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
|
|
@@ -10285,7 +10440,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
10285
10440
|
const directBindings = /* @__PURE__ */ new Set();
|
|
10286
10441
|
const namespaceBindings = /* @__PURE__ */ new Set();
|
|
10287
10442
|
function resolve(identifier) {
|
|
10288
|
-
return
|
|
10443
|
+
return ASTUtils11.findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
10289
10444
|
}
|
|
10290
10445
|
function record(identifier, destination) {
|
|
10291
10446
|
const variable = resolve(identifier);
|
|
@@ -10348,7 +10503,7 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
10348
10503
|
});
|
|
10349
10504
|
|
|
10350
10505
|
// src/rules/prefer-non-nullable-collection.ts
|
|
10351
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as
|
|
10506
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
|
|
10352
10507
|
var preferNonNullableCollectionDocumentation = {
|
|
10353
10508
|
summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
|
|
10354
10509
|
rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
|
|
@@ -10478,14 +10633,14 @@ function directlyCoalesced(node) {
|
|
|
10478
10633
|
return parent?.type === AST_NODE_TYPES44.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
|
|
10479
10634
|
}
|
|
10480
10635
|
function identifierIsOnlyCoalesced(context, binding, fn) {
|
|
10481
|
-
const variable =
|
|
10636
|
+
const variable = ASTUtils12.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
10482
10637
|
if (variable === null || variable.references.length === 0) return false;
|
|
10483
10638
|
return variable.references.every(
|
|
10484
10639
|
(reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
|
|
10485
10640
|
);
|
|
10486
10641
|
}
|
|
10487
10642
|
function memberIsOnlyCoalesced(context, object, property, fn) {
|
|
10488
|
-
const variable =
|
|
10643
|
+
const variable = ASTUtils12.findVariable(context.sourceCode.getScope(object), object.name);
|
|
10489
10644
|
if (variable === null) return false;
|
|
10490
10645
|
const accesses = variable.references.flatMap((reference) => {
|
|
10491
10646
|
if (!belongsToFunction(reference.identifier, fn)) return [null];
|
|
@@ -10757,7 +10912,70 @@ var bindingValidationPolarity = (test, bindingName) => {
|
|
|
10757
10912
|
}
|
|
10758
10913
|
return test.type === AST_NODE_TYPES45.CallExpression && test.arguments.length === 1 && test.arguments[0]?.type === AST_NODE_TYPES45.Identifier && test.arguments[0].name === bindingName && test.callee.type === AST_NODE_TYPES45.MemberExpression && !test.callee.computed && test.callee.object.type === AST_NODE_TYPES45.Identifier && test.callee.object.name === "Array" && test.callee.property.type === AST_NODE_TYPES45.Identifier && test.callee.property.name === "isArray" ? "valid-when-true" : null;
|
|
10759
10914
|
};
|
|
10915
|
+
var plainMemberAccess = (node) => node.type === AST_NODE_TYPES45.MemberExpression && !node.computed && node.object.type === AST_NODE_TYPES45.Identifier && node.property.type === AST_NODE_TYPES45.Identifier ? { object: node.object.name, property: node.property.name } : null;
|
|
10916
|
+
var isSamePlainMember = (node, access) => {
|
|
10917
|
+
const candidate = plainMemberAccess(node);
|
|
10918
|
+
return candidate !== null && candidate.object === access.object && candidate.property === access.property;
|
|
10919
|
+
};
|
|
10760
10920
|
var nodeWithin2 = (node, container) => node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
|
|
10921
|
+
var isUseWithinValidatedBranch = (node, bindingName) => {
|
|
10922
|
+
for (let current = node.parent; current !== void 0 && current !== null; current = current.parent) {
|
|
10923
|
+
if (current.type === AST_NODE_TYPES45.ConditionalExpression) {
|
|
10924
|
+
const polarity = bindingValidationPolarity(current.test, bindingName);
|
|
10925
|
+
if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && nodeWithin2(node, current.alternate)) {
|
|
10926
|
+
return true;
|
|
10927
|
+
}
|
|
10928
|
+
}
|
|
10929
|
+
if (current.type === AST_NODE_TYPES45.IfStatement) {
|
|
10930
|
+
const polarity = bindingValidationPolarity(current.test, bindingName);
|
|
10931
|
+
if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && current.alternate !== null && nodeWithin2(node, current.alternate)) {
|
|
10932
|
+
return true;
|
|
10933
|
+
}
|
|
10934
|
+
}
|
|
10935
|
+
if (current.type === AST_NODE_TYPES45.FunctionDeclaration || current.type === AST_NODE_TYPES45.FunctionExpression || current.type === AST_NODE_TYPES45.ArrowFunctionExpression) {
|
|
10936
|
+
return false;
|
|
10937
|
+
}
|
|
10938
|
+
}
|
|
10939
|
+
return false;
|
|
10940
|
+
};
|
|
10941
|
+
var isMemberUseWithinValidatedBranch = (node, access) => {
|
|
10942
|
+
for (let current = node.parent; current !== void 0 && current !== null; current = current.parent) {
|
|
10943
|
+
if (current.type === AST_NODE_TYPES45.ConditionalExpression) {
|
|
10944
|
+
const polarity = memberValidationPolarity(current.test, access);
|
|
10945
|
+
if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && nodeWithin2(node, current.alternate)) {
|
|
10946
|
+
return true;
|
|
10947
|
+
}
|
|
10948
|
+
}
|
|
10949
|
+
if (current.type === AST_NODE_TYPES45.IfStatement) {
|
|
10950
|
+
const polarity = memberValidationPolarity(current.test, access);
|
|
10951
|
+
if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && current.alternate !== null && nodeWithin2(node, current.alternate)) {
|
|
10952
|
+
return true;
|
|
10953
|
+
}
|
|
10954
|
+
}
|
|
10955
|
+
if (current.type === AST_NODE_TYPES45.FunctionDeclaration || current.type === AST_NODE_TYPES45.FunctionExpression || current.type === AST_NODE_TYPES45.ArrowFunctionExpression) {
|
|
10956
|
+
return false;
|
|
10957
|
+
}
|
|
10958
|
+
}
|
|
10959
|
+
return false;
|
|
10960
|
+
};
|
|
10961
|
+
var memberValidationPolarity = (test, access) => {
|
|
10962
|
+
if (test.type === AST_NODE_TYPES45.UnaryExpression && test.operator === "!") {
|
|
10963
|
+
const inner = memberValidationPolarity(test.argument, access);
|
|
10964
|
+
return inner === "valid-when-true" ? "valid-when-false" : inner === "valid-when-false" ? "valid-when-true" : null;
|
|
10965
|
+
}
|
|
10966
|
+
if (test.type === AST_NODE_TYPES45.BinaryExpression) {
|
|
10967
|
+
const isMatchingTypeof = (node) => node.type === AST_NODE_TYPES45.UnaryExpression && node.operator === "typeof" && isSamePlainMember(node.argument, access);
|
|
10968
|
+
const isPrimitiveType = (node) => node.type === AST_NODE_TYPES45.Literal && typeof node.value === "string" && PRIMITIVE_TYPEOF_RESULTS.has(node.value);
|
|
10969
|
+
if (!(isMatchingTypeof(test.left) && isPrimitiveType(test.right) || isMatchingTypeof(test.right) && isPrimitiveType(test.left))) {
|
|
10970
|
+
return null;
|
|
10971
|
+
}
|
|
10972
|
+
if (test.operator === "===" || test.operator === "==") {
|
|
10973
|
+
return "valid-when-true";
|
|
10974
|
+
}
|
|
10975
|
+
return test.operator === "!==" || test.operator === "!=" ? "valid-when-false" : null;
|
|
10976
|
+
}
|
|
10977
|
+
return test.type === AST_NODE_TYPES45.CallExpression && test.arguments.length === 1 && test.arguments[0] !== void 0 && test.arguments[0].type !== AST_NODE_TYPES45.SpreadElement && isSamePlainMember(test.arguments[0], access) && test.callee.type === AST_NODE_TYPES45.MemberExpression && !test.callee.computed && test.callee.object.type === AST_NODE_TYPES45.Identifier && test.callee.object.name === "Array" && test.callee.property.type === AST_NODE_TYPES45.Identifier && test.callee.property.name === "isArray" ? "valid-when-true" : null;
|
|
10978
|
+
};
|
|
10761
10979
|
var isFullyValidatedExtractedBinding = (member, source, context) => {
|
|
10762
10980
|
const isValidationReference = (identifier) => {
|
|
10763
10981
|
for (let current = identifier.parent; current !== void 0 && current !== null; current = current.parent) {
|
|
@@ -11027,7 +11245,14 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
11027
11245
|
return;
|
|
11028
11246
|
}
|
|
11029
11247
|
const variable = obj?.type === AST_NODE_TYPES45.Identifier ? unvalidatedVariableRef(obj, scope, unvalidatedVariables) : null;
|
|
11030
|
-
if (variable !== null) {
|
|
11248
|
+
if (variable !== null && obj?.type === AST_NODE_TYPES45.Identifier) {
|
|
11249
|
+
if (isUseWithinValidatedBranch(node, obj.name)) {
|
|
11250
|
+
return;
|
|
11251
|
+
}
|
|
11252
|
+
const access = plainMemberAccess(node);
|
|
11253
|
+
if (access !== null && isMemberUseWithinValidatedBranch(node, access)) {
|
|
11254
|
+
return;
|
|
11255
|
+
}
|
|
11031
11256
|
if (isFullyValidatedExtractedBinding(node, variable, context)) {
|
|
11032
11257
|
return;
|
|
11033
11258
|
}
|
|
@@ -11479,16 +11704,17 @@ var preferServerActionsDocumentation = {
|
|
|
11479
11704
|
rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
|
|
11480
11705
|
remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
|
|
11481
11706
|
category: "architecture",
|
|
11482
|
-
limitations: ["Only statically recognizable /api/ mutations in
|
|
11707
|
+
limitations: ["Only statically recognizable /api/ mutations in modules with positive Next.js evidence are reported: an explicit next import, or an app/pages path with a top-level use-client directive."],
|
|
11483
11708
|
examples: [
|
|
11484
11709
|
{ id: "server-action-call", title: "Call a Server Action", outcome: "no-match", files: [{ path: "app/tasks/page.tsx", source: "import { createTask } from './actions'; await createTask(input);" }], focusPath: "app/tasks/page.tsx", expectedCount: 0, public: true },
|
|
11485
|
-
{ id: "api-mutation", title: "Do not mutate through an API route", outcome: "match", files: [{ path: "app/tasks/page.tsx", source: "await fetch('/api/tasks', { method: 'POST', body });" }], focusPath: "app/tasks/page.tsx", expectedCount: 1, public: true }
|
|
11710
|
+
{ id: "api-mutation", title: "Do not mutate through an API route", outcome: "match", files: [{ path: "app/tasks/page.tsx", source: "'use client'; await fetch('/api/tasks', { method: 'POST', body });" }], focusPath: "app/tasks/page.tsx", expectedCount: 1, public: true }
|
|
11486
11711
|
]
|
|
11487
11712
|
};
|
|
11488
11713
|
var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
11489
11714
|
var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
|
|
11490
11715
|
var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
|
|
11491
11716
|
var NON_REACT_FRAMEWORK_RE2 = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
|
|
11717
|
+
var NEXT_MODULE_PATH_RE2 = /(?:^|[/\\])(?:app|pages)[/\\]/u;
|
|
11492
11718
|
function getScope(context, node) {
|
|
11493
11719
|
return context.sourceCode.getScope(node);
|
|
11494
11720
|
}
|
|
@@ -11602,6 +11828,16 @@ var prefer_server_actions_default = createRule({
|
|
|
11602
11828
|
const isNonReactFramework = context.sourceCode.ast.body.some(
|
|
11603
11829
|
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE2.test(node.source.value)
|
|
11604
11830
|
);
|
|
11831
|
+
const hasUseClientDirective = context.sourceCode.ast.body.some(
|
|
11832
|
+
(node) => node.type === "ExpressionStatement" && node.expression.type === "Literal" && node.expression.value === "use client"
|
|
11833
|
+
);
|
|
11834
|
+
const hasNextImport = context.sourceCode.ast.body.some(
|
|
11835
|
+
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && (node.source.value === "next" || node.source.value.startsWith("next/"))
|
|
11836
|
+
);
|
|
11837
|
+
const hasNextEvidence = hasNextImport || hasUseClientDirective && NEXT_MODULE_PATH_RE2.test(filename);
|
|
11838
|
+
if (!hasNextEvidence) {
|
|
11839
|
+
return {};
|
|
11840
|
+
}
|
|
11605
11841
|
return {
|
|
11606
11842
|
CallExpression(node) {
|
|
11607
11843
|
if (isNonReactFramework) return;
|
|
@@ -12327,6 +12563,9 @@ var requireAssertNeverDocumentation = {
|
|
|
12327
12563
|
};
|
|
12328
12564
|
var isRuntimeHandlingStatement = (statement) => {
|
|
12329
12565
|
if (statement.type === AST_NODE_TYPES49.EmptyStatement) return false;
|
|
12566
|
+
if (statement.type === AST_NODE_TYPES49.BreakStatement) {
|
|
12567
|
+
return statement.label !== null;
|
|
12568
|
+
}
|
|
12330
12569
|
if (statement.type === AST_NODE_TYPES49.TSTypeAliasDeclaration || statement.type === AST_NODE_TYPES49.TSInterfaceDeclaration) {
|
|
12331
12570
|
return false;
|
|
12332
12571
|
}
|
|
@@ -12356,7 +12595,12 @@ function isExhaustiveFiniteSwitch(node, services) {
|
|
|
12356
12595
|
const discriminant = services.esTreeNodeToTSNodeMap.get(node.discriminant);
|
|
12357
12596
|
const discriminantType = checker.getTypeAtLocation(discriminant);
|
|
12358
12597
|
const constituents = discriminantType.isUnion() ? discriminantType.types : [discriminantType];
|
|
12359
|
-
if (constituents.length
|
|
12598
|
+
if (!discriminantType.isUnion() || constituents.length < 2) return false;
|
|
12599
|
+
if (constituents.every(
|
|
12600
|
+
(constituent) => (constituent.flags & ts2.TypeFlags.BooleanLiteral) !== 0
|
|
12601
|
+
)) {
|
|
12602
|
+
return false;
|
|
12603
|
+
}
|
|
12360
12604
|
const expected = /* @__PURE__ */ new Set();
|
|
12361
12605
|
for (const constituent of constituents) {
|
|
12362
12606
|
const key = finiteTypeKey(constituent, checker);
|
|
@@ -12424,7 +12668,7 @@ var require_assert_never_default = createRule({
|
|
|
12424
12668
|
});
|
|
12425
12669
|
|
|
12426
12670
|
// src/rules/require-fetch-timeout.ts
|
|
12427
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES50, ASTUtils as
|
|
12671
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES50, ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
|
|
12428
12672
|
var requireFetchTimeoutDocumentation = {
|
|
12429
12673
|
summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
|
|
12430
12674
|
rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
|
|
@@ -12505,7 +12749,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
12505
12749
|
}
|
|
12506
12750
|
function resolvesToGlobal(identifier) {
|
|
12507
12751
|
const scope = context.sourceCode.getScope(identifier);
|
|
12508
|
-
const variable =
|
|
12752
|
+
const variable = ASTUtils13.findVariable(scope, identifier.name);
|
|
12509
12753
|
return variable === null || variable.defs.length === 0;
|
|
12510
12754
|
}
|
|
12511
12755
|
function isGlobalFetchCall2(callee) {
|
|
@@ -12515,7 +12759,7 @@ var require_fetch_timeout_default = createRule({
|
|
|
12515
12759
|
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
12760
|
}
|
|
12517
12761
|
function localConstInitProvablyLacksSignal(identifier) {
|
|
12518
|
-
const variable =
|
|
12762
|
+
const variable = ASTUtils13.findVariable(
|
|
12519
12763
|
context.sourceCode.getScope(identifier),
|
|
12520
12764
|
identifier.name
|
|
12521
12765
|
);
|
|
@@ -13157,34 +13401,35 @@ var require_static_next_matcher_default = createRule({
|
|
|
13157
13401
|
});
|
|
13158
13402
|
|
|
13159
13403
|
// src/rules/require-zod-form-validation.ts
|
|
13160
|
-
import {
|
|
13404
|
+
import {
|
|
13405
|
+
AST_NODE_TYPES as AST_NODE_TYPES53,
|
|
13406
|
+
ASTUtils as ASTUtils14
|
|
13407
|
+
} from "@typescript-eslint/utils";
|
|
13161
13408
|
var requireZodFormValidationDocumentation = {
|
|
13162
13409
|
summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
|
|
13163
13410
|
rationale: "FormData values are untrusted strings or files and need runtime validation before use.",
|
|
13164
13411
|
remediation: "Read the value inside a Zod schema's `parse` or `safeParse` input.",
|
|
13165
13412
|
category: "security",
|
|
13413
|
+
limitations: [
|
|
13414
|
+
"Tests are excluded; imported schema-shaped names are trusted when their implementation is outside the linted file.",
|
|
13415
|
+
"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."
|
|
13416
|
+
],
|
|
13166
13417
|
examples: [
|
|
13167
13418
|
{ 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
13419
|
{ 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
13420
|
]
|
|
13170
13421
|
};
|
|
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) => {
|
|
13422
|
+
var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
|
|
13423
|
+
"parse",
|
|
13424
|
+
"safeParse",
|
|
13425
|
+
"parseAsync",
|
|
13426
|
+
"safeParseAsync"
|
|
13427
|
+
]);
|
|
13428
|
+
var zodReceiverRoot = (node) => {
|
|
13184
13429
|
let current = node;
|
|
13185
13430
|
while (true) {
|
|
13186
13431
|
if (current.type === AST_NODE_TYPES53.Identifier) {
|
|
13187
|
-
return current
|
|
13432
|
+
return current;
|
|
13188
13433
|
}
|
|
13189
13434
|
if (current.type === AST_NODE_TYPES53.CallExpression) {
|
|
13190
13435
|
current = current.callee;
|
|
@@ -13194,7 +13439,7 @@ var looksLikeZodSchema = (node) => {
|
|
|
13194
13439
|
current = current.object;
|
|
13195
13440
|
continue;
|
|
13196
13441
|
}
|
|
13197
|
-
return
|
|
13442
|
+
return null;
|
|
13198
13443
|
}
|
|
13199
13444
|
};
|
|
13200
13445
|
var isFormDataMethodCall = (node) => {
|
|
@@ -13224,6 +13469,34 @@ var require_zod_form_validation_default = createRule({
|
|
|
13224
13469
|
if (isTestFile(context.filename)) {
|
|
13225
13470
|
return {};
|
|
13226
13471
|
}
|
|
13472
|
+
const zodBindings = /* @__PURE__ */ new Set();
|
|
13473
|
+
const resolvedBinding = (identifier) => ASTUtils14.findVariable(
|
|
13474
|
+
context.sourceCode.getScope(identifier),
|
|
13475
|
+
identifier.name
|
|
13476
|
+
);
|
|
13477
|
+
const isProvablyNonZodLocal = (identifier) => {
|
|
13478
|
+
const binding = resolvedBinding(identifier);
|
|
13479
|
+
if (binding === null || zodBindings.has(binding) || binding.defs.length !== 1) {
|
|
13480
|
+
return false;
|
|
13481
|
+
}
|
|
13482
|
+
const definition = binding.defs[0];
|
|
13483
|
+
if (definition?.type !== "Variable" || definition.node.type !== AST_NODE_TYPES53.VariableDeclarator) {
|
|
13484
|
+
return false;
|
|
13485
|
+
}
|
|
13486
|
+
const init = definition.node.init;
|
|
13487
|
+
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;
|
|
13488
|
+
};
|
|
13489
|
+
const isZodParseCall = (node) => {
|
|
13490
|
+
if (node.type !== AST_NODE_TYPES53.CallExpression) return false;
|
|
13491
|
+
const callee = node.callee;
|
|
13492
|
+
if (callee.type !== AST_NODE_TYPES53.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES53.Identifier || !ZOD_PARSE_METHODS.has(callee.property.name)) {
|
|
13493
|
+
return false;
|
|
13494
|
+
}
|
|
13495
|
+
const root = zodReceiverRoot(callee.object);
|
|
13496
|
+
if (root === null) return false;
|
|
13497
|
+
const binding = resolvedBinding(root);
|
|
13498
|
+
return binding !== null && zodBindings.has(binding) || (root.name === "z" || ZOD_SCHEMA_NAME_RE.test(root.name)) && !isProvablyNonZodLocal(root);
|
|
13499
|
+
};
|
|
13227
13500
|
const isFormSourceIdentifier = (node) => {
|
|
13228
13501
|
if (node.type !== AST_NODE_TYPES53.Identifier) return false;
|
|
13229
13502
|
if (/formdata/i.test(node.name)) return true;
|
|
@@ -13249,14 +13522,15 @@ var require_zod_form_validation_default = createRule({
|
|
|
13249
13522
|
}
|
|
13250
13523
|
return isFormSourceIdentifier(callee.object);
|
|
13251
13524
|
};
|
|
13252
|
-
const
|
|
13525
|
+
const zodParseAncestor = (node) => {
|
|
13253
13526
|
let parent = node.parent;
|
|
13254
13527
|
while (parent !== null && parent !== void 0) {
|
|
13255
|
-
if (isZodParseCall(parent)) return
|
|
13528
|
+
if (isZodParseCall(parent)) return parent;
|
|
13256
13529
|
parent = parent.parent;
|
|
13257
13530
|
}
|
|
13258
|
-
return
|
|
13531
|
+
return null;
|
|
13259
13532
|
};
|
|
13533
|
+
const hasZodParseAncestor = (node) => zodParseAncestor(node) !== null;
|
|
13260
13534
|
const isInstanceofNarrowing = (node) => {
|
|
13261
13535
|
const parent = node.parent;
|
|
13262
13536
|
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 +13547,111 @@ var require_zod_form_validation_default = createRule({
|
|
|
13273
13547
|
}
|
|
13274
13548
|
return null;
|
|
13275
13549
|
};
|
|
13550
|
+
const containingStatement = (node) => {
|
|
13551
|
+
let current = node;
|
|
13552
|
+
while (current.parent !== void 0) {
|
|
13553
|
+
const parent = current.parent;
|
|
13554
|
+
if (parent.type === AST_NODE_TYPES53.BlockStatement || parent.type === AST_NODE_TYPES53.Program) {
|
|
13555
|
+
return current;
|
|
13556
|
+
}
|
|
13557
|
+
current = parent;
|
|
13558
|
+
}
|
|
13559
|
+
return null;
|
|
13560
|
+
};
|
|
13561
|
+
const zodParseMethod = (call) => {
|
|
13562
|
+
const callee = call.callee;
|
|
13563
|
+
return callee.type === AST_NODE_TYPES53.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES53.Identifier ? callee.property.name : null;
|
|
13564
|
+
};
|
|
13565
|
+
const hasConditionalAncestorBeforeStatement = (node, statement) => {
|
|
13566
|
+
let current = node.parent;
|
|
13567
|
+
while (current !== void 0 && current !== statement) {
|
|
13568
|
+
if (current.type === AST_NODE_TYPES53.LogicalExpression || current.type === AST_NODE_TYPES53.ConditionalExpression) {
|
|
13569
|
+
return true;
|
|
13570
|
+
}
|
|
13571
|
+
current = current.parent;
|
|
13572
|
+
}
|
|
13573
|
+
return false;
|
|
13574
|
+
};
|
|
13575
|
+
const isAwaitedBeforeStatement = (node, statement) => {
|
|
13576
|
+
let current = node.parent;
|
|
13577
|
+
while (current !== void 0 && current !== statement) {
|
|
13578
|
+
if (current.type === AST_NODE_TYPES53.AwaitExpression) return true;
|
|
13579
|
+
current = current.parent;
|
|
13580
|
+
}
|
|
13581
|
+
return false;
|
|
13582
|
+
};
|
|
13583
|
+
const guaranteedValidationStatement = (declarator, reference) => {
|
|
13584
|
+
const parse2 = zodParseAncestor(reference);
|
|
13585
|
+
if (parse2 === null) return null;
|
|
13586
|
+
const declarationStatement = containingStatement(declarator);
|
|
13587
|
+
const validationStatement = containingStatement(parse2);
|
|
13588
|
+
if (declarationStatement === null || validationStatement === null || declarationStatement.parent !== validationStatement.parent || validationStatement.range[0] <= declarationStatement.range[1] || hasConditionalAncestorBeforeStatement(parse2, validationStatement)) {
|
|
13589
|
+
return null;
|
|
13590
|
+
}
|
|
13591
|
+
if (validationStatement.type !== AST_NODE_TYPES53.VariableDeclaration && validationStatement.type !== AST_NODE_TYPES53.ExpressionStatement) {
|
|
13592
|
+
return null;
|
|
13593
|
+
}
|
|
13594
|
+
const method = zodParseMethod(parse2);
|
|
13595
|
+
if (method === "parse") return validationStatement;
|
|
13596
|
+
if (method === "parseAsync" && isAwaitedBeforeStatement(parse2, validationStatement)) {
|
|
13597
|
+
return validationStatement;
|
|
13598
|
+
}
|
|
13599
|
+
return null;
|
|
13600
|
+
};
|
|
13601
|
+
const isSafePrevalidationInspection = (identifier) => {
|
|
13602
|
+
const parent = identifier.parent;
|
|
13603
|
+
if (parent.type === AST_NODE_TYPES53.UnaryExpression && parent.operator === "typeof") {
|
|
13604
|
+
return true;
|
|
13605
|
+
}
|
|
13606
|
+
if (parent.type !== AST_NODE_TYPES53.BinaryExpression || parent.left !== identifier) {
|
|
13607
|
+
return false;
|
|
13608
|
+
}
|
|
13609
|
+
if (parent.operator === "instanceof") {
|
|
13610
|
+
return parent.right.type === AST_NODE_TYPES53.Identifier && (parent.right.name === "File" || parent.right.name === "Blob");
|
|
13611
|
+
}
|
|
13612
|
+
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");
|
|
13613
|
+
};
|
|
13614
|
+
const statementWithinBlock = (node, block) => {
|
|
13615
|
+
let current = node;
|
|
13616
|
+
while (current.parent !== void 0 && current.parent !== block) {
|
|
13617
|
+
current = current.parent;
|
|
13618
|
+
}
|
|
13619
|
+
return current.parent === block ? current : null;
|
|
13620
|
+
};
|
|
13276
13621
|
const bindingIsValidated = (declarator) => {
|
|
13277
13622
|
const variable = context.sourceCode.getDeclaredVariables(declarator)[0];
|
|
13278
13623
|
if (variable === void 0) return false;
|
|
13279
|
-
|
|
13280
|
-
(
|
|
13624
|
+
const references = variable.references.filter((reference) => !reference.isWriteOnly()).map((reference) => reference.identifier).filter(
|
|
13625
|
+
(identifier) => identifier.type === AST_NODE_TYPES53.Identifier
|
|
13281
13626
|
);
|
|
13627
|
+
if (references.length === 0) return false;
|
|
13628
|
+
if (references.some(isInstanceofNarrowing)) return true;
|
|
13629
|
+
const validationStatements = references.map((reference) => guaranteedValidationStatement(declarator, reference)).filter(
|
|
13630
|
+
(statement) => statement !== null
|
|
13631
|
+
);
|
|
13632
|
+
const declarationStatement = containingStatement(declarator);
|
|
13633
|
+
const declarationBlock = declarationStatement?.parent;
|
|
13634
|
+
return references.every((reference) => {
|
|
13635
|
+
if (zodParseAncestor(reference) !== null || isSafePrevalidationInspection(reference)) {
|
|
13636
|
+
return true;
|
|
13637
|
+
}
|
|
13638
|
+
if (declarationBlock === void 0) return false;
|
|
13639
|
+
const useStatement = statementWithinBlock(reference, declarationBlock);
|
|
13640
|
+
return useStatement !== null && validationStatements.some(
|
|
13641
|
+
(statement) => statement.range[1] < useStatement.range[0]
|
|
13642
|
+
);
|
|
13643
|
+
});
|
|
13282
13644
|
};
|
|
13283
13645
|
return {
|
|
13646
|
+
ImportDeclaration(node) {
|
|
13647
|
+
if (!isZodModule(node.source.value)) return;
|
|
13648
|
+
for (const specifier of node.specifiers) {
|
|
13649
|
+
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")) {
|
|
13650
|
+
const binding = resolvedBinding(specifier.local);
|
|
13651
|
+
if (binding !== null) zodBindings.add(binding);
|
|
13652
|
+
}
|
|
13653
|
+
}
|
|
13654
|
+
},
|
|
13284
13655
|
CallExpression(node) {
|
|
13285
13656
|
if (!isFormDataGetCall(node)) return;
|
|
13286
13657
|
if (hasZodParseAncestor(node) || isInstanceofNarrowing(node)) return;
|
|
@@ -13360,7 +13731,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
13360
13731
|
});
|
|
13361
13732
|
|
|
13362
13733
|
// src/rules/stepdown.ts
|
|
13363
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES54, ASTUtils as
|
|
13734
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES54, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
|
|
13364
13735
|
var stepdownDocumentation = {
|
|
13365
13736
|
summary: "Place a private helper below its sole direct same-scope caller.",
|
|
13366
13737
|
rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
|
|
@@ -13553,7 +13924,7 @@ function methodName(node) {
|
|
|
13553
13924
|
return !node.computed && node.key.type === AST_NODE_TYPES54.Identifier ? node.key.name : null;
|
|
13554
13925
|
}
|
|
13555
13926
|
function referencedMethod(context, node, classVariables) {
|
|
13556
|
-
const objectVariable = node.object.type === AST_NODE_TYPES54.Identifier ?
|
|
13927
|
+
const objectVariable = node.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils15.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
|
|
13557
13928
|
const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
|
|
13558
13929
|
if (node.object.type !== AST_NODE_TYPES54.ThisExpression && !isClassReference) return null;
|
|
13559
13930
|
if (node.property.type === AST_NODE_TYPES54.PrivateIdentifier) return `#${node.property.name}`;
|
|
@@ -13606,11 +13977,11 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
13606
13977
|
const pinned = /* @__PURE__ */ new Set();
|
|
13607
13978
|
const classVariables = /* @__PURE__ */ new Set();
|
|
13608
13979
|
if (node.id !== null) {
|
|
13609
|
-
const internal =
|
|
13980
|
+
const internal = ASTUtils15.findVariable(context.sourceCode.getScope(node), node.id.name);
|
|
13610
13981
|
if (internal !== null) classVariables.add(internal);
|
|
13611
13982
|
}
|
|
13612
13983
|
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 =
|
|
13984
|
+
const outer = ASTUtils15.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
|
|
13614
13985
|
if (outer !== null) classVariables.add(outer);
|
|
13615
13986
|
}
|
|
13616
13987
|
for (const method of methods) {
|
|
@@ -13646,7 +14017,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
13646
14017
|
return;
|
|
13647
14018
|
}
|
|
13648
14019
|
if (binding.type !== AST_NODE_TYPES54.Identifier) return;
|
|
13649
|
-
const variable =
|
|
14020
|
+
const variable = ASTUtils15.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
13650
14021
|
if (variable !== null) {
|
|
13651
14022
|
methodClassVariables.add(variable);
|
|
13652
14023
|
methodAliases.add(variable);
|
|
@@ -13676,7 +14047,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
13676
14047
|
return;
|
|
13677
14048
|
}
|
|
13678
14049
|
if (!privateNames.has(target)) return;
|
|
13679
|
-
const objectVariable = current.object.type === AST_NODE_TYPES54.Identifier ?
|
|
14050
|
+
const objectVariable = current.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils15.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
|
|
13680
14051
|
if (objectVariable !== null && methodAliases.has(objectVariable)) {
|
|
13681
14052
|
pinned.add(target);
|
|
13682
14053
|
return;
|
|
@@ -13760,7 +14131,7 @@ var stepdown_default = createRule({
|
|
|
13760
14131
|
// src/rules/zod-naming-convention.ts
|
|
13761
14132
|
import {
|
|
13762
14133
|
AST_NODE_TYPES as AST_NODE_TYPES55,
|
|
13763
|
-
ASTUtils as
|
|
14134
|
+
ASTUtils as ASTUtils16
|
|
13764
14135
|
} from "@typescript-eslint/utils";
|
|
13765
14136
|
var zodNamingConventionDocumentation = {
|
|
13766
14137
|
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 +14218,7 @@ var zod_naming_convention_default = createRule({
|
|
|
13847
14218
|
const acceptsSchemaWord = convention !== "prefix";
|
|
13848
14219
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
13849
14220
|
function resolvedBinding(identifier) {
|
|
13850
|
-
return
|
|
14221
|
+
return ASTUtils16.findVariable(
|
|
13851
14222
|
context.sourceCode.getScope(identifier),
|
|
13852
14223
|
identifier.name
|
|
13853
14224
|
);
|
|
@@ -14043,7 +14414,7 @@ var rules = {
|
|
|
14043
14414
|
};
|
|
14044
14415
|
var meta = {
|
|
14045
14416
|
name: "@sarj/eslint-plugin",
|
|
14046
|
-
version: "15.
|
|
14417
|
+
version: "15.3.0"
|
|
14047
14418
|
};
|
|
14048
14419
|
var applicationOnlyRules = [
|
|
14049
14420
|
"no-restricted-library-load",
|