@sarj/eslint-plugin 15.2.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 +141 -43
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +141 -43
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3264,14 +3264,12 @@ var noJsonStringifyErrorDocumentation = {
|
|
|
3264
3264
|
rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
|
|
3265
3265
|
remediation: "Serialize explicit error fields or use an error-aware serializer.",
|
|
3266
3266
|
category: "correctness",
|
|
3267
|
-
limitations: ["The rule uses local
|
|
3267
|
+
limitations: ["The rule uses local catch-binding and constructor provenance rather than type information."],
|
|
3268
3268
|
examples: [
|
|
3269
3269
|
{ 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 },
|
|
3270
3270
|
{ 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 }
|
|
3271
3271
|
]
|
|
3272
3272
|
};
|
|
3273
|
-
var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
|
|
3274
|
-
var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
|
|
3275
3273
|
var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
|
|
3276
3274
|
var PAYLOAD_PROPS = /* @__PURE__ */ new Set([
|
|
3277
3275
|
"data",
|
|
@@ -3299,6 +3297,21 @@ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
|
3299
3297
|
"TypeError",
|
|
3300
3298
|
"URIError"
|
|
3301
3299
|
]);
|
|
3300
|
+
function identifierIsProvenError(identifier, scope) {
|
|
3301
|
+
if (isCatchBinding(scope, identifier.name)) return true;
|
|
3302
|
+
let current = scope;
|
|
3303
|
+
while (current !== null && !current.set.has(identifier.name)) {
|
|
3304
|
+
current = current.upper;
|
|
3305
|
+
}
|
|
3306
|
+
const variable = current?.set.get(identifier.name);
|
|
3307
|
+
if (variable === void 0 || variable.defs.length !== 1) return false;
|
|
3308
|
+
const definition = variable.defs[0];
|
|
3309
|
+
if (definition?.type !== "Variable") return false;
|
|
3310
|
+
const initializer = definition.node.init;
|
|
3311
|
+
return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && variable.references.every(
|
|
3312
|
+
(reference) => !reference.isWrite() || reference.init === true
|
|
3313
|
+
);
|
|
3314
|
+
}
|
|
3302
3315
|
function isCatchBinding(scope, name) {
|
|
3303
3316
|
let current = scope;
|
|
3304
3317
|
while (current) {
|
|
@@ -3314,22 +3327,6 @@ function isCatchBinding(scope, name) {
|
|
|
3314
3327
|
}
|
|
3315
3328
|
return false;
|
|
3316
3329
|
}
|
|
3317
|
-
function memberSuggestsError(member, scope) {
|
|
3318
|
-
const propName2 = !member.computed && member.property.type === "Identifier" ? member.property.name : null;
|
|
3319
|
-
if (propName2 !== null && ERROR_PROP_PATTERN.test(propName2)) {
|
|
3320
|
-
return true;
|
|
3321
|
-
}
|
|
3322
|
-
const base = member.object;
|
|
3323
|
-
const baseSuggestsError = base.type === "Identifier" && (ERROR_NAME_PATTERN.test(base.name) || isCatchBinding(scope, base.name));
|
|
3324
|
-
if (baseSuggestsError) {
|
|
3325
|
-
if (propName2 === null) {
|
|
3326
|
-
return true;
|
|
3327
|
-
}
|
|
3328
|
-
const lowered = propName2.toLowerCase();
|
|
3329
|
-
return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
|
|
3330
|
-
}
|
|
3331
|
-
return false;
|
|
3332
|
-
}
|
|
3333
3330
|
function positiveErrorSubject(test) {
|
|
3334
3331
|
return instanceofErrorSubject(test) ?? typeGuardSubject(test);
|
|
3335
3332
|
}
|
|
@@ -3433,27 +3430,25 @@ function directLiteralValues(argument) {
|
|
|
3433
3430
|
}
|
|
3434
3431
|
function expressionSuggestsError(expression, scope) {
|
|
3435
3432
|
if (expression.type === "Identifier") {
|
|
3436
|
-
return
|
|
3433
|
+
return identifierIsProvenError(expression, scope);
|
|
3434
|
+
}
|
|
3435
|
+
if (expression.type === "NewExpression" && expression.callee.type === "Identifier") {
|
|
3436
|
+
return BUILTIN_ERROR_CONSTRUCTORS.has(expression.callee.name);
|
|
3437
3437
|
}
|
|
3438
3438
|
return expression.type === "MemberExpression" && memberSuggestsError(expression, scope);
|
|
3439
3439
|
}
|
|
3440
|
-
function
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
if (definition?.type !== "Variable") return false;
|
|
3451
|
-
const initializer = definition.node.init;
|
|
3452
|
-
return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && variable.references.every(
|
|
3453
|
-
(reference) => !reference.isWrite() || reference.init === true
|
|
3454
|
-
);
|
|
3440
|
+
function memberSuggestsError(member, scope) {
|
|
3441
|
+
const propName2 = !member.computed && member.property.type === "Identifier" ? member.property.name : null;
|
|
3442
|
+
const base = member.object;
|
|
3443
|
+
const baseSuggestsError = base.type === "Identifier" && identifierIsProvenError(base, scope);
|
|
3444
|
+
if (baseSuggestsError) {
|
|
3445
|
+
if (propName2 === null) {
|
|
3446
|
+
return true;
|
|
3447
|
+
}
|
|
3448
|
+
const lowered = propName2.toLowerCase();
|
|
3449
|
+
return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
|
|
3455
3450
|
}
|
|
3456
|
-
return
|
|
3451
|
+
return false;
|
|
3457
3452
|
}
|
|
3458
3453
|
var no_json_stringify_error_default = createRule({
|
|
3459
3454
|
name: "no-json-stringify-error",
|
|
@@ -3480,9 +3475,8 @@ var no_json_stringify_error_default = createRule({
|
|
|
3480
3475
|
return;
|
|
3481
3476
|
}
|
|
3482
3477
|
const scope = context.sourceCode.getScope(firstArg);
|
|
3483
|
-
const isNestedLiteral = firstArg.type === "ObjectExpression" || firstArg.type === "ArrayExpression";
|
|
3484
3478
|
const unsafeValue = directLiteralValues(firstArg).find(
|
|
3485
|
-
(value) =>
|
|
3479
|
+
(value) => expressionSuggestsError(value, scope) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
|
|
3486
3480
|
);
|
|
3487
3481
|
if (unsafeValue === void 0) {
|
|
3488
3482
|
return;
|
|
@@ -4941,6 +4935,7 @@ var ANALYTICS_SEGMENTS2 = /* @__PURE__ */ new Set([
|
|
|
4941
4935
|
]);
|
|
4942
4936
|
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\/)/;
|
|
4943
4937
|
var NON_REACT_FRAMEWORK_RE = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
|
|
4938
|
+
var NEXT_MODULE_PATH_RE = /(?:^|[/\\])(?:app|pages)[/\\]/u;
|
|
4944
4939
|
function isGlobalFetchCall(node, resolvesToGlobal) {
|
|
4945
4940
|
const callee = node.callee;
|
|
4946
4941
|
if (callee.type === "Identifier") {
|
|
@@ -5056,6 +5051,13 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
5056
5051
|
const nonReactFramework = context.sourceCode.ast.body.some(
|
|
5057
5052
|
(statement) => statement.type === import_utils25.AST_NODE_TYPES.ImportDeclaration && typeof statement.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(statement.source.value)
|
|
5058
5053
|
);
|
|
5054
|
+
const hasUseClientDirective = context.sourceCode.ast.body.some(
|
|
5055
|
+
(statement) => statement.type === import_utils25.AST_NODE_TYPES.ExpressionStatement && statement.expression.type === import_utils25.AST_NODE_TYPES.Literal && statement.expression.value === "use client"
|
|
5056
|
+
);
|
|
5057
|
+
const hasNextImport = context.sourceCode.ast.body.some(
|
|
5058
|
+
(statement) => statement.type === import_utils25.AST_NODE_TYPES.ImportDeclaration && typeof statement.source.value === "string" && (statement.source.value === "next" || statement.source.value.startsWith("next/"))
|
|
5059
|
+
);
|
|
5060
|
+
const hasNextEvidence = hasNextImport || hasUseClientDirective && NEXT_MODULE_PATH_RE.test(filename);
|
|
5059
5061
|
function resolvesToGlobal(identifier) {
|
|
5060
5062
|
const variable = import_utils25.ASTUtils.findVariable(
|
|
5061
5063
|
context.sourceCode.getScope(identifier),
|
|
@@ -5116,7 +5118,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
5116
5118
|
return resolved?.type === import_utils25.AST_NODE_TYPES.LogicalExpression && resolved.operator === "||" && (isMutationMethod2(resolved.left) || isMutationMethod2(resolved.right));
|
|
5117
5119
|
}
|
|
5118
5120
|
function serverActionOwns(node) {
|
|
5119
|
-
if (node.callee.type !== import_utils25.AST_NODE_TYPES.Identifier || SERVER_ACTION_SKIP_FILE_RE.test(filename) || nonReactFramework) {
|
|
5121
|
+
if (node.callee.type !== import_utils25.AST_NODE_TYPES.Identifier || !hasNextEvidence || SERVER_ACTION_SKIP_FILE_RE.test(filename) || nonReactFramework) {
|
|
5120
5122
|
return false;
|
|
5121
5123
|
}
|
|
5122
5124
|
const url = node.arguments[0];
|
|
@@ -6335,6 +6337,20 @@ var SAFE_PARSE_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
|
6335
6337
|
function isBodyDecodeNode(node) {
|
|
6336
6338
|
return node.type === import_utils32.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils32.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils32.AST_NODE_TYPES.Identifier && BODY_DECODE_METHODS.has(node.callee.property.name);
|
|
6337
6339
|
}
|
|
6340
|
+
function isSafeParseSupportCall(node) {
|
|
6341
|
+
const callee = node.callee;
|
|
6342
|
+
if (callee.type !== import_utils32.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== import_utils32.AST_NODE_TYPES.Identifier) {
|
|
6343
|
+
return false;
|
|
6344
|
+
}
|
|
6345
|
+
if (callee.property.name === "isArray" && callee.object.type === import_utils32.AST_NODE_TYPES.Identifier && callee.object.name === "Array") {
|
|
6346
|
+
return true;
|
|
6347
|
+
}
|
|
6348
|
+
if (callee.property.name !== "getItem") return false;
|
|
6349
|
+
if (callee.object.type === import_utils32.AST_NODE_TYPES.Identifier && (callee.object.name === "localStorage" || callee.object.name === "sessionStorage")) {
|
|
6350
|
+
return true;
|
|
6351
|
+
}
|
|
6352
|
+
return callee.object.type === import_utils32.AST_NODE_TYPES.MemberExpression && !callee.object.computed && callee.object.object.type === import_utils32.AST_NODE_TYPES.Identifier && (callee.object.object.name === "window" || callee.object.object.name === "globalThis") && callee.object.property.type === import_utils32.AST_NODE_TYPES.Identifier && (callee.object.property.name === "localStorage" || callee.object.property.name === "sessionStorage");
|
|
6353
|
+
}
|
|
6338
6354
|
var BODY_DECODE_METHODS = /* @__PURE__ */ new Set([
|
|
6339
6355
|
"json",
|
|
6340
6356
|
"text",
|
|
@@ -6403,6 +6419,7 @@ function tryReturnsSafeParse(catchNode) {
|
|
|
6403
6419
|
if (current.type === import_utils32.AST_NODE_TYPES.CallExpression || current.type === import_utils32.AST_NODE_TYPES.NewExpression) {
|
|
6404
6420
|
if (isParseShapedNode(current) || isBodyDecodeNode(current)) {
|
|
6405
6421
|
sawSafeParse = true;
|
|
6422
|
+
} else if (current.type === import_utils32.AST_NODE_TYPES.CallExpression && isSafeParseSupportCall(current)) {
|
|
6406
6423
|
} else {
|
|
6407
6424
|
sawUnsafeOperation = true;
|
|
6408
6425
|
return;
|
|
@@ -10926,7 +10943,70 @@ var bindingValidationPolarity = (test, bindingName) => {
|
|
|
10926
10943
|
}
|
|
10927
10944
|
return test.type === import_utils57.AST_NODE_TYPES.CallExpression && test.arguments.length === 1 && test.arguments[0]?.type === import_utils57.AST_NODE_TYPES.Identifier && test.arguments[0].name === bindingName && test.callee.type === import_utils57.AST_NODE_TYPES.MemberExpression && !test.callee.computed && test.callee.object.type === import_utils57.AST_NODE_TYPES.Identifier && test.callee.object.name === "Array" && test.callee.property.type === import_utils57.AST_NODE_TYPES.Identifier && test.callee.property.name === "isArray" ? "valid-when-true" : null;
|
|
10928
10945
|
};
|
|
10946
|
+
var plainMemberAccess = (node) => node.type === import_utils57.AST_NODE_TYPES.MemberExpression && !node.computed && node.object.type === import_utils57.AST_NODE_TYPES.Identifier && node.property.type === import_utils57.AST_NODE_TYPES.Identifier ? { object: node.object.name, property: node.property.name } : null;
|
|
10947
|
+
var isSamePlainMember = (node, access) => {
|
|
10948
|
+
const candidate = plainMemberAccess(node);
|
|
10949
|
+
return candidate !== null && candidate.object === access.object && candidate.property === access.property;
|
|
10950
|
+
};
|
|
10929
10951
|
var nodeWithin2 = (node, container) => node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
|
|
10952
|
+
var isUseWithinValidatedBranch = (node, bindingName) => {
|
|
10953
|
+
for (let current = node.parent; current !== void 0 && current !== null; current = current.parent) {
|
|
10954
|
+
if (current.type === import_utils57.AST_NODE_TYPES.ConditionalExpression) {
|
|
10955
|
+
const polarity = bindingValidationPolarity(current.test, bindingName);
|
|
10956
|
+
if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && nodeWithin2(node, current.alternate)) {
|
|
10957
|
+
return true;
|
|
10958
|
+
}
|
|
10959
|
+
}
|
|
10960
|
+
if (current.type === import_utils57.AST_NODE_TYPES.IfStatement) {
|
|
10961
|
+
const polarity = bindingValidationPolarity(current.test, bindingName);
|
|
10962
|
+
if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && current.alternate !== null && nodeWithin2(node, current.alternate)) {
|
|
10963
|
+
return true;
|
|
10964
|
+
}
|
|
10965
|
+
}
|
|
10966
|
+
if (current.type === import_utils57.AST_NODE_TYPES.FunctionDeclaration || current.type === import_utils57.AST_NODE_TYPES.FunctionExpression || current.type === import_utils57.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
10967
|
+
return false;
|
|
10968
|
+
}
|
|
10969
|
+
}
|
|
10970
|
+
return false;
|
|
10971
|
+
};
|
|
10972
|
+
var isMemberUseWithinValidatedBranch = (node, access) => {
|
|
10973
|
+
for (let current = node.parent; current !== void 0 && current !== null; current = current.parent) {
|
|
10974
|
+
if (current.type === import_utils57.AST_NODE_TYPES.ConditionalExpression) {
|
|
10975
|
+
const polarity = memberValidationPolarity(current.test, access);
|
|
10976
|
+
if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && nodeWithin2(node, current.alternate)) {
|
|
10977
|
+
return true;
|
|
10978
|
+
}
|
|
10979
|
+
}
|
|
10980
|
+
if (current.type === import_utils57.AST_NODE_TYPES.IfStatement) {
|
|
10981
|
+
const polarity = memberValidationPolarity(current.test, access);
|
|
10982
|
+
if (polarity === "valid-when-true" && nodeWithin2(node, current.consequent) || polarity === "valid-when-false" && current.alternate !== null && nodeWithin2(node, current.alternate)) {
|
|
10983
|
+
return true;
|
|
10984
|
+
}
|
|
10985
|
+
}
|
|
10986
|
+
if (current.type === import_utils57.AST_NODE_TYPES.FunctionDeclaration || current.type === import_utils57.AST_NODE_TYPES.FunctionExpression || current.type === import_utils57.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
10987
|
+
return false;
|
|
10988
|
+
}
|
|
10989
|
+
}
|
|
10990
|
+
return false;
|
|
10991
|
+
};
|
|
10992
|
+
var memberValidationPolarity = (test, access) => {
|
|
10993
|
+
if (test.type === import_utils57.AST_NODE_TYPES.UnaryExpression && test.operator === "!") {
|
|
10994
|
+
const inner = memberValidationPolarity(test.argument, access);
|
|
10995
|
+
return inner === "valid-when-true" ? "valid-when-false" : inner === "valid-when-false" ? "valid-when-true" : null;
|
|
10996
|
+
}
|
|
10997
|
+
if (test.type === import_utils57.AST_NODE_TYPES.BinaryExpression) {
|
|
10998
|
+
const isMatchingTypeof = (node) => node.type === import_utils57.AST_NODE_TYPES.UnaryExpression && node.operator === "typeof" && isSamePlainMember(node.argument, access);
|
|
10999
|
+
const isPrimitiveType = (node) => node.type === import_utils57.AST_NODE_TYPES.Literal && typeof node.value === "string" && PRIMITIVE_TYPEOF_RESULTS.has(node.value);
|
|
11000
|
+
if (!(isMatchingTypeof(test.left) && isPrimitiveType(test.right) || isMatchingTypeof(test.right) && isPrimitiveType(test.left))) {
|
|
11001
|
+
return null;
|
|
11002
|
+
}
|
|
11003
|
+
if (test.operator === "===" || test.operator === "==") {
|
|
11004
|
+
return "valid-when-true";
|
|
11005
|
+
}
|
|
11006
|
+
return test.operator === "!==" || test.operator === "!=" ? "valid-when-false" : null;
|
|
11007
|
+
}
|
|
11008
|
+
return test.type === import_utils57.AST_NODE_TYPES.CallExpression && test.arguments.length === 1 && test.arguments[0] !== void 0 && test.arguments[0].type !== import_utils57.AST_NODE_TYPES.SpreadElement && isSamePlainMember(test.arguments[0], access) && test.callee.type === import_utils57.AST_NODE_TYPES.MemberExpression && !test.callee.computed && test.callee.object.type === import_utils57.AST_NODE_TYPES.Identifier && test.callee.object.name === "Array" && test.callee.property.type === import_utils57.AST_NODE_TYPES.Identifier && test.callee.property.name === "isArray" ? "valid-when-true" : null;
|
|
11009
|
+
};
|
|
10930
11010
|
var isFullyValidatedExtractedBinding = (member, source, context) => {
|
|
10931
11011
|
const isValidationReference = (identifier) => {
|
|
10932
11012
|
for (let current = identifier.parent; current !== void 0 && current !== null; current = current.parent) {
|
|
@@ -11196,7 +11276,14 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
11196
11276
|
return;
|
|
11197
11277
|
}
|
|
11198
11278
|
const variable = obj?.type === import_utils57.AST_NODE_TYPES.Identifier ? unvalidatedVariableRef(obj, scope, unvalidatedVariables) : null;
|
|
11199
|
-
if (variable !== null) {
|
|
11279
|
+
if (variable !== null && obj?.type === import_utils57.AST_NODE_TYPES.Identifier) {
|
|
11280
|
+
if (isUseWithinValidatedBranch(node, obj.name)) {
|
|
11281
|
+
return;
|
|
11282
|
+
}
|
|
11283
|
+
const access = plainMemberAccess(node);
|
|
11284
|
+
if (access !== null && isMemberUseWithinValidatedBranch(node, access)) {
|
|
11285
|
+
return;
|
|
11286
|
+
}
|
|
11200
11287
|
if (isFullyValidatedExtractedBinding(node, variable, context)) {
|
|
11201
11288
|
return;
|
|
11202
11289
|
}
|
|
@@ -11648,16 +11735,17 @@ var preferServerActionsDocumentation = {
|
|
|
11648
11735
|
rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
|
|
11649
11736
|
remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
|
|
11650
11737
|
category: "architecture",
|
|
11651
|
-
limitations: ["Only statically recognizable /api/ mutations in
|
|
11738
|
+
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."],
|
|
11652
11739
|
examples: [
|
|
11653
11740
|
{ 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 },
|
|
11654
|
-
{ 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 }
|
|
11741
|
+
{ 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 }
|
|
11655
11742
|
]
|
|
11656
11743
|
};
|
|
11657
11744
|
var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
11658
11745
|
var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
|
|
11659
11746
|
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\/)/;
|
|
11660
11747
|
var NON_REACT_FRAMEWORK_RE2 = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
|
|
11748
|
+
var NEXT_MODULE_PATH_RE2 = /(?:^|[/\\])(?:app|pages)[/\\]/u;
|
|
11661
11749
|
function getScope(context, node) {
|
|
11662
11750
|
return context.sourceCode.getScope(node);
|
|
11663
11751
|
}
|
|
@@ -11771,6 +11859,16 @@ var prefer_server_actions_default = createRule({
|
|
|
11771
11859
|
const isNonReactFramework = context.sourceCode.ast.body.some(
|
|
11772
11860
|
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE2.test(node.source.value)
|
|
11773
11861
|
);
|
|
11862
|
+
const hasUseClientDirective = context.sourceCode.ast.body.some(
|
|
11863
|
+
(node) => node.type === "ExpressionStatement" && node.expression.type === "Literal" && node.expression.value === "use client"
|
|
11864
|
+
);
|
|
11865
|
+
const hasNextImport = context.sourceCode.ast.body.some(
|
|
11866
|
+
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && (node.source.value === "next" || node.source.value.startsWith("next/"))
|
|
11867
|
+
);
|
|
11868
|
+
const hasNextEvidence = hasNextImport || hasUseClientDirective && NEXT_MODULE_PATH_RE2.test(filename);
|
|
11869
|
+
if (!hasNextEvidence) {
|
|
11870
|
+
return {};
|
|
11871
|
+
}
|
|
11774
11872
|
return {
|
|
11775
11873
|
CallExpression(node) {
|
|
11776
11874
|
if (isNonReactFramework) return;
|
|
@@ -14338,7 +14436,7 @@ var rules = {
|
|
|
14338
14436
|
};
|
|
14339
14437
|
var meta = {
|
|
14340
14438
|
name: "@sarj/eslint-plugin",
|
|
14341
|
-
version: "15.
|
|
14439
|
+
version: "15.3.0"
|
|
14342
14440
|
};
|
|
14343
14441
|
var applicationOnlyRules = [
|
|
14344
14442
|
"no-restricted-library-load",
|
package/dist/index.d.cts
CHANGED
|
@@ -414,7 +414,7 @@ type FlatPreset = {
|
|
|
414
414
|
declare const plugin: {
|
|
415
415
|
readonly meta: {
|
|
416
416
|
readonly name: "@sarj/eslint-plugin";
|
|
417
|
-
readonly version: "15.
|
|
417
|
+
readonly version: "15.3.0";
|
|
418
418
|
};
|
|
419
419
|
readonly rules: {
|
|
420
420
|
readonly "duplicate-test-body": DocumentedRule<readonly [], "duplicateTestBody">;
|
package/dist/index.d.ts
CHANGED
|
@@ -414,7 +414,7 @@ type FlatPreset = {
|
|
|
414
414
|
declare const plugin: {
|
|
415
415
|
readonly meta: {
|
|
416
416
|
readonly name: "@sarj/eslint-plugin";
|
|
417
|
-
readonly version: "15.
|
|
417
|
+
readonly version: "15.3.0";
|
|
418
418
|
};
|
|
419
419
|
readonly rules: {
|
|
420
420
|
readonly "duplicate-test-body": DocumentedRule<readonly [], "duplicateTestBody">;
|
package/dist/index.js
CHANGED
|
@@ -3224,14 +3224,12 @@ var noJsonStringifyErrorDocumentation = {
|
|
|
3224
3224
|
rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
|
|
3225
3225
|
remediation: "Serialize explicit error fields or use an error-aware serializer.",
|
|
3226
3226
|
category: "correctness",
|
|
3227
|
-
limitations: ["The rule uses local
|
|
3227
|
+
limitations: ["The rule uses local catch-binding and constructor provenance rather than type information."],
|
|
3228
3228
|
examples: [
|
|
3229
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 },
|
|
3230
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 }
|
|
3231
3231
|
]
|
|
3232
3232
|
};
|
|
3233
|
-
var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
|
|
3234
|
-
var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
|
|
3235
3233
|
var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
|
|
3236
3234
|
var PAYLOAD_PROPS = /* @__PURE__ */ new Set([
|
|
3237
3235
|
"data",
|
|
@@ -3259,6 +3257,21 @@ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
|
3259
3257
|
"TypeError",
|
|
3260
3258
|
"URIError"
|
|
3261
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
|
+
}
|
|
3262
3275
|
function isCatchBinding(scope, name) {
|
|
3263
3276
|
let current = scope;
|
|
3264
3277
|
while (current) {
|
|
@@ -3274,22 +3287,6 @@ function isCatchBinding(scope, name) {
|
|
|
3274
3287
|
}
|
|
3275
3288
|
return false;
|
|
3276
3289
|
}
|
|
3277
|
-
function memberSuggestsError(member, scope) {
|
|
3278
|
-
const propName2 = !member.computed && member.property.type === "Identifier" ? member.property.name : null;
|
|
3279
|
-
if (propName2 !== null && ERROR_PROP_PATTERN.test(propName2)) {
|
|
3280
|
-
return true;
|
|
3281
|
-
}
|
|
3282
|
-
const base = member.object;
|
|
3283
|
-
const baseSuggestsError = base.type === "Identifier" && (ERROR_NAME_PATTERN.test(base.name) || isCatchBinding(scope, base.name));
|
|
3284
|
-
if (baseSuggestsError) {
|
|
3285
|
-
if (propName2 === null) {
|
|
3286
|
-
return true;
|
|
3287
|
-
}
|
|
3288
|
-
const lowered = propName2.toLowerCase();
|
|
3289
|
-
return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
|
|
3290
|
-
}
|
|
3291
|
-
return false;
|
|
3292
|
-
}
|
|
3293
3290
|
function positiveErrorSubject(test) {
|
|
3294
3291
|
return instanceofErrorSubject(test) ?? typeGuardSubject(test);
|
|
3295
3292
|
}
|
|
@@ -3393,27 +3390,25 @@ function directLiteralValues(argument) {
|
|
|
3393
3390
|
}
|
|
3394
3391
|
function expressionSuggestsError(expression, scope) {
|
|
3395
3392
|
if (expression.type === "Identifier") {
|
|
3396
|
-
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);
|
|
3397
3397
|
}
|
|
3398
3398
|
return expression.type === "MemberExpression" && memberSuggestsError(expression, scope);
|
|
3399
3399
|
}
|
|
3400
|
-
function
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
if (definition?.type !== "Variable") return false;
|
|
3411
|
-
const initializer = definition.node.init;
|
|
3412
|
-
return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && variable.references.every(
|
|
3413
|
-
(reference) => !reference.isWrite() || reference.init === true
|
|
3414
|
-
);
|
|
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);
|
|
3415
3410
|
}
|
|
3416
|
-
return
|
|
3411
|
+
return false;
|
|
3417
3412
|
}
|
|
3418
3413
|
var no_json_stringify_error_default = createRule({
|
|
3419
3414
|
name: "no-json-stringify-error",
|
|
@@ -3440,9 +3435,8 @@ var no_json_stringify_error_default = createRule({
|
|
|
3440
3435
|
return;
|
|
3441
3436
|
}
|
|
3442
3437
|
const scope = context.sourceCode.getScope(firstArg);
|
|
3443
|
-
const isNestedLiteral = firstArg.type === "ObjectExpression" || firstArg.type === "ArrayExpression";
|
|
3444
3438
|
const unsafeValue = directLiteralValues(firstArg).find(
|
|
3445
|
-
(value) =>
|
|
3439
|
+
(value) => expressionSuggestsError(value, scope) && !isGuardedByInstanceofError(node, value, context.sourceCode) && !isNarrowedByEarlyReturn(node, value, context.sourceCode)
|
|
3446
3440
|
);
|
|
3447
3441
|
if (unsafeValue === void 0) {
|
|
3448
3442
|
return;
|
|
@@ -4903,6 +4897,7 @@ var ANALYTICS_SEGMENTS2 = /* @__PURE__ */ new Set([
|
|
|
4903
4897
|
]);
|
|
4904
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\/)/;
|
|
4905
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;
|
|
4906
4901
|
function isGlobalFetchCall(node, resolvesToGlobal) {
|
|
4907
4902
|
const callee = node.callee;
|
|
4908
4903
|
if (callee.type === "Identifier") {
|
|
@@ -5018,6 +5013,13 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
5018
5013
|
const nonReactFramework = context.sourceCode.ast.body.some(
|
|
5019
5014
|
(statement) => statement.type === AST_NODE_TYPES18.ImportDeclaration && typeof statement.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(statement.source.value)
|
|
5020
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);
|
|
5021
5023
|
function resolvesToGlobal(identifier) {
|
|
5022
5024
|
const variable = ASTUtils5.findVariable(
|
|
5023
5025
|
context.sourceCode.getScope(identifier),
|
|
@@ -5078,7 +5080,7 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
5078
5080
|
return resolved?.type === AST_NODE_TYPES18.LogicalExpression && resolved.operator === "||" && (isMutationMethod2(resolved.left) || isMutationMethod2(resolved.right));
|
|
5079
5081
|
}
|
|
5080
5082
|
function serverActionOwns(node) {
|
|
5081
|
-
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) {
|
|
5082
5084
|
return false;
|
|
5083
5085
|
}
|
|
5084
5086
|
const url = node.arguments[0];
|
|
@@ -6297,6 +6299,20 @@ var SAFE_PARSE_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
|
6297
6299
|
function isBodyDecodeNode(node) {
|
|
6298
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);
|
|
6299
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
|
+
}
|
|
6300
6316
|
var BODY_DECODE_METHODS = /* @__PURE__ */ new Set([
|
|
6301
6317
|
"json",
|
|
6302
6318
|
"text",
|
|
@@ -6365,6 +6381,7 @@ function tryReturnsSafeParse(catchNode) {
|
|
|
6365
6381
|
if (current.type === AST_NODE_TYPES23.CallExpression || current.type === AST_NODE_TYPES23.NewExpression) {
|
|
6366
6382
|
if (isParseShapedNode(current) || isBodyDecodeNode(current)) {
|
|
6367
6383
|
sawSafeParse = true;
|
|
6384
|
+
} else if (current.type === AST_NODE_TYPES23.CallExpression && isSafeParseSupportCall(current)) {
|
|
6368
6385
|
} else {
|
|
6369
6386
|
sawUnsafeOperation = true;
|
|
6370
6387
|
return;
|
|
@@ -10895,7 +10912,70 @@ var bindingValidationPolarity = (test, bindingName) => {
|
|
|
10895
10912
|
}
|
|
10896
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;
|
|
10897
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
|
+
};
|
|
10898
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
|
+
};
|
|
10899
10979
|
var isFullyValidatedExtractedBinding = (member, source, context) => {
|
|
10900
10980
|
const isValidationReference = (identifier) => {
|
|
10901
10981
|
for (let current = identifier.parent; current !== void 0 && current !== null; current = current.parent) {
|
|
@@ -11165,7 +11245,14 @@ var prefer_schema_for_api_payload_default = createRule({
|
|
|
11165
11245
|
return;
|
|
11166
11246
|
}
|
|
11167
11247
|
const variable = obj?.type === AST_NODE_TYPES45.Identifier ? unvalidatedVariableRef(obj, scope, unvalidatedVariables) : null;
|
|
11168
|
-
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
|
+
}
|
|
11169
11256
|
if (isFullyValidatedExtractedBinding(node, variable, context)) {
|
|
11170
11257
|
return;
|
|
11171
11258
|
}
|
|
@@ -11617,16 +11704,17 @@ var preferServerActionsDocumentation = {
|
|
|
11617
11704
|
rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
|
|
11618
11705
|
remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
|
|
11619
11706
|
category: "architecture",
|
|
11620
|
-
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."],
|
|
11621
11708
|
examples: [
|
|
11622
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 },
|
|
11623
|
-
{ 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 }
|
|
11624
11711
|
]
|
|
11625
11712
|
};
|
|
11626
11713
|
var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
11627
11714
|
var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
|
|
11628
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\/)/;
|
|
11629
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;
|
|
11630
11718
|
function getScope(context, node) {
|
|
11631
11719
|
return context.sourceCode.getScope(node);
|
|
11632
11720
|
}
|
|
@@ -11740,6 +11828,16 @@ var prefer_server_actions_default = createRule({
|
|
|
11740
11828
|
const isNonReactFramework = context.sourceCode.ast.body.some(
|
|
11741
11829
|
(node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE2.test(node.source.value)
|
|
11742
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
|
+
}
|
|
11743
11841
|
return {
|
|
11744
11842
|
CallExpression(node) {
|
|
11745
11843
|
if (isNonReactFramework) return;
|
|
@@ -14316,7 +14414,7 @@ var rules = {
|
|
|
14316
14414
|
};
|
|
14317
14415
|
var meta = {
|
|
14318
14416
|
name: "@sarj/eslint-plugin",
|
|
14319
|
-
version: "15.
|
|
14417
|
+
version: "15.3.0"
|
|
14320
14418
|
};
|
|
14321
14419
|
var applicationOnlyRules = [
|
|
14322
14420
|
"no-restricted-library-load",
|