@sarj/eslint-plugin 2.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1070 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +1075 -114
- package/dist/index.js.map +1 -1
- package/package.json +4 -5
package/dist/index.cjs
CHANGED
|
@@ -28,45 +28,30 @@ module.exports = __toCommonJS(index_exports);
|
|
|
28
28
|
// src/rules/enforce-file-structure.ts
|
|
29
29
|
var import_utils = require("@typescript-eslint/utils");
|
|
30
30
|
var SECTION = {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
functions: 3,
|
|
35
|
-
exports: 4
|
|
31
|
+
declarations: 0,
|
|
32
|
+
functions: 1,
|
|
33
|
+
exports: 2
|
|
36
34
|
};
|
|
37
|
-
var SECTION_NAMES = [
|
|
38
|
-
"imports",
|
|
39
|
-
"types",
|
|
40
|
-
"constants",
|
|
41
|
-
"functions",
|
|
42
|
-
"exports"
|
|
43
|
-
];
|
|
35
|
+
var SECTION_NAMES = ["declarations", "functions", "exports"];
|
|
44
36
|
var sectionName = (ordinal) => {
|
|
45
37
|
const name = SECTION_NAMES[ordinal];
|
|
46
38
|
return name ?? "unknown";
|
|
47
39
|
};
|
|
48
|
-
var
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
var SERVER_ACTION_FILE_RE = /(?:^|\/)actions\/|\.action\.[jt]sx?$|(?:^|\/)actions\.[jt]sx?$/;
|
|
41
|
+
var isFunctionExpression = (node) => node.type === import_utils.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils.AST_NODE_TYPES.FunctionExpression;
|
|
42
|
+
var isFunctionLikeVariable = (statement) => statement.declarations.length > 0 && statement.declarations.every(
|
|
43
|
+
(decl) => decl.init !== null && isFunctionExpression(decl.init)
|
|
44
|
+
);
|
|
53
45
|
var getStatementSection = (statement) => {
|
|
54
46
|
switch (statement.type) {
|
|
55
47
|
case import_utils.AST_NODE_TYPES.ImportDeclaration:
|
|
56
|
-
return SECTION.imports;
|
|
57
48
|
case import_utils.AST_NODE_TYPES.TSTypeAliasDeclaration:
|
|
58
49
|
case import_utils.AST_NODE_TYPES.TSInterfaceDeclaration:
|
|
59
50
|
case import_utils.AST_NODE_TYPES.TSEnumDeclaration:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (firstDeclarator !== void 0 && isConstantNamed(firstDeclarator)) {
|
|
65
|
-
return SECTION.constants;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return SECTION.functions;
|
|
69
|
-
}
|
|
51
|
+
case import_utils.AST_NODE_TYPES.ClassDeclaration:
|
|
52
|
+
return SECTION.declarations;
|
|
53
|
+
case import_utils.AST_NODE_TYPES.VariableDeclaration:
|
|
54
|
+
return isFunctionLikeVariable(statement) ? SECTION.functions : SECTION.declarations;
|
|
70
55
|
case import_utils.AST_NODE_TYPES.FunctionDeclaration:
|
|
71
56
|
return SECTION.functions;
|
|
72
57
|
case import_utils.AST_NODE_TYPES.ExportNamedDeclaration:
|
|
@@ -91,7 +76,7 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
|
|
|
91
76
|
meta: {
|
|
92
77
|
type: "suggestion",
|
|
93
78
|
docs: {
|
|
94
|
-
description: "Enforce
|
|
79
|
+
description: "Enforce that function definitions follow the file's top-of-file declarations (imports, types, constants, classes) \u2014 the stepdown rule. Ordering among non-function declarations is not enforced. Server-action files (under `/actions/`, named `*.action.ts`, or `actions.ts`) must also begin with a `use server` directive."
|
|
95
80
|
},
|
|
96
81
|
schema: [],
|
|
97
82
|
messages: {
|
|
@@ -102,7 +87,7 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
|
|
|
102
87
|
defaultOptions: [],
|
|
103
88
|
create(context) {
|
|
104
89
|
const filename = context.filename;
|
|
105
|
-
const isServerAction =
|
|
90
|
+
const isServerAction = SERVER_ACTION_FILE_RE.test(filename);
|
|
106
91
|
return {
|
|
107
92
|
Program(node) {
|
|
108
93
|
const body = node.body;
|
|
@@ -115,9 +100,8 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
|
|
|
115
100
|
});
|
|
116
101
|
}
|
|
117
102
|
}
|
|
118
|
-
let currentSection = SECTION.
|
|
103
|
+
let currentSection = SECTION.declarations;
|
|
119
104
|
for (const statement of body) {
|
|
120
|
-
if (isUseServerDirective(statement)) continue;
|
|
121
105
|
if (statement.type === import_utils.AST_NODE_TYPES.ExpressionStatement && statement.expression.type === import_utils.AST_NODE_TYPES.Literal && typeof statement.expression.value === "string" && statement.expression.value.startsWith("use ")) {
|
|
122
106
|
continue;
|
|
123
107
|
}
|
|
@@ -153,7 +137,7 @@ var HTTP_METHOD_NAMES = /* @__PURE__ */ new Set([
|
|
|
153
137
|
"head",
|
|
154
138
|
"options"
|
|
155
139
|
]);
|
|
156
|
-
var
|
|
140
|
+
var ANALYTICS_SEGMENTS = /* @__PURE__ */ new Set([
|
|
157
141
|
"analytics",
|
|
158
142
|
"telemetry",
|
|
159
143
|
"track",
|
|
@@ -162,7 +146,7 @@ var ANALYTICS_KEYWORDS = [
|
|
|
162
146
|
"beacon",
|
|
163
147
|
"metrics",
|
|
164
148
|
"event"
|
|
165
|
-
];
|
|
149
|
+
]);
|
|
166
150
|
function isEffectHookCall(node) {
|
|
167
151
|
const callee = node.callee;
|
|
168
152
|
if (callee.type === import_utils2.AST_NODE_TYPES.Identifier) {
|
|
@@ -236,7 +220,7 @@ function extractUrlString(node) {
|
|
|
236
220
|
function isAnalyticsCall(node) {
|
|
237
221
|
const url = extractUrlString(node).toLowerCase();
|
|
238
222
|
if (url === "") return false;
|
|
239
|
-
return
|
|
223
|
+
return url.split(/[/.]/).some((segment) => ANALYTICS_SEGMENTS.has(segment));
|
|
240
224
|
}
|
|
241
225
|
var no_client_side_data_fetching_default = import_utils2.ESLintUtils.RuleCreator(
|
|
242
226
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
@@ -275,8 +259,105 @@ var no_client_side_data_fetching_default = import_utils2.ESLintUtils.RuleCreator
|
|
|
275
259
|
}
|
|
276
260
|
});
|
|
277
261
|
|
|
278
|
-
// src/rules/no-
|
|
262
|
+
// src/rules/no-comment-cruft.ts
|
|
279
263
|
var import_utils3 = require("@typescript-eslint/utils");
|
|
264
|
+
var LEADING_PREAMBLE_MIN = 4;
|
|
265
|
+
var DIRECTIVE_RE = /^(eslint\b|eslint-|@ts-|prettier-ignore|prettier\b|biome-|c8\b|v8\b|istanbul\b|@type\b|@vite|webpack|<reference|global\b|noinspection|todo\b|fixme\b|hack\b|xxx\b)/i;
|
|
266
|
+
var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
|
|
267
|
+
var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
|
|
268
|
+
var BANNER_RUN_RE = /={4,}|-{4,}|#{4,}|\*{4,}|~{4,}/;
|
|
269
|
+
var REGION_RE = /^#?(?:end)?region\b/i;
|
|
270
|
+
var CODE_KEYWORD_RE = /^(import |export |const |let |var |function\b|class |interface |type \w|enum |return\b|throw |await |async |if\s*\(|for\s*\(|while\s*\(|switch\s*\(|new |console\.)/;
|
|
271
|
+
var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
|
|
272
|
+
var CALL_OR_ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$|^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
|
|
273
|
+
function stripCommentMarker(line) {
|
|
274
|
+
return line.replace(/^\s*\/\//, "").replace(/^\s*\*+/, "").trim();
|
|
275
|
+
}
|
|
276
|
+
function isDirective(text) {
|
|
277
|
+
return DIRECTIVE_RE.test(text.trim());
|
|
278
|
+
}
|
|
279
|
+
function isBanner(text) {
|
|
280
|
+
const t = text.trim();
|
|
281
|
+
if (!t) return false;
|
|
282
|
+
return BANNER_FULL_RE.test(t) || BANNER_RUN_RE.test(t) || REGION_RE.test(t);
|
|
283
|
+
}
|
|
284
|
+
function looksLikeCode(text) {
|
|
285
|
+
const t = text.trim();
|
|
286
|
+
if (!t) return false;
|
|
287
|
+
if (CODE_KEYWORD_RE.test(t) && CODE_TAIL_RE.test(t)) return true;
|
|
288
|
+
return CALL_OR_ASSIGN_RE.test(t);
|
|
289
|
+
}
|
|
290
|
+
var no_comment_cruft_default = import_utils3.ESLintUtils.RuleCreator(
|
|
291
|
+
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
292
|
+
)({
|
|
293
|
+
name: "no-comment-cruft",
|
|
294
|
+
meta: {
|
|
295
|
+
type: "suggestion",
|
|
296
|
+
docs: {
|
|
297
|
+
description: "Flag commented-out code, section-banner comments, and leading file-header comment preambles."
|
|
298
|
+
},
|
|
299
|
+
schema: [],
|
|
300
|
+
messages: {
|
|
301
|
+
commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
|
|
302
|
+
sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
|
|
303
|
+
fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines."
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
defaultOptions: [],
|
|
307
|
+
create(context) {
|
|
308
|
+
const sourceCode = context.sourceCode;
|
|
309
|
+
function isStandalone(comment) {
|
|
310
|
+
const before = sourceCode.getTokenBefore(comment, {
|
|
311
|
+
includeComments: false
|
|
312
|
+
});
|
|
313
|
+
return !before || before.loc.end.line < comment.loc.start.line;
|
|
314
|
+
}
|
|
315
|
+
function isJsDoc(comment) {
|
|
316
|
+
return comment.type === "Block" && /^\*/.test(comment.value);
|
|
317
|
+
}
|
|
318
|
+
function reportLeadingPreamble(comments, firstCodeLine) {
|
|
319
|
+
const leading = [];
|
|
320
|
+
let prevLine = null;
|
|
321
|
+
for (const comment of comments) {
|
|
322
|
+
if (comment.type !== "Line") break;
|
|
323
|
+
if (comment.loc.start.line >= firstCodeLine) break;
|
|
324
|
+
if (!isStandalone(comment)) break;
|
|
325
|
+
const body = stripCommentMarker(comment.value);
|
|
326
|
+
if (isDirective(body) || body.startsWith("!")) continue;
|
|
327
|
+
if (prevLine !== null && comment.loc.start.line !== prevLine + 1) break;
|
|
328
|
+
leading.push(comment);
|
|
329
|
+
prevLine = comment.loc.start.line;
|
|
330
|
+
}
|
|
331
|
+
const first = leading[0];
|
|
332
|
+
if (first === void 0 || leading.length < LEADING_PREAMBLE_MIN) return;
|
|
333
|
+
const isLicense = leading.some(
|
|
334
|
+
(c) => LICENSE_RE.test(stripCommentMarker(c.value))
|
|
335
|
+
);
|
|
336
|
+
if (!isLicense) {
|
|
337
|
+
context.report({ node: first, messageId: "fileHeaderPreamble" });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
Program() {
|
|
342
|
+
const comments = sourceCode.getAllComments();
|
|
343
|
+
const firstCodeLine = sourceCode.ast.tokens[0]?.loc.start.line ?? Number.MAX_SAFE_INTEGER;
|
|
344
|
+
for (const comment of comments) {
|
|
345
|
+
if (isJsDoc(comment) || !isStandalone(comment)) continue;
|
|
346
|
+
const texts = comment.value.split("\n").map(stripCommentMarker).filter((l) => l.length > 0 && !isDirective(l));
|
|
347
|
+
if (texts.some(isBanner)) {
|
|
348
|
+
context.report({ node: comment, messageId: "sectionBanner" });
|
|
349
|
+
} else if (texts.some(looksLikeCode)) {
|
|
350
|
+
context.report({ node: comment, messageId: "commentedOutCode" });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
reportLeadingPreamble(comments, firstCodeLine);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// src/rules/no-enum.ts
|
|
360
|
+
var import_utils4 = require("@typescript-eslint/utils");
|
|
280
361
|
var DEFAULT_IGNORE_PATTERNS = [
|
|
281
362
|
/[\\/]generated[\\/]/,
|
|
282
363
|
/\.gen\.tsx?$/,
|
|
@@ -295,7 +376,7 @@ function hasGeneratedMarker(sourceText) {
|
|
|
295
376
|
const head = sourceText.slice(0, 1024);
|
|
296
377
|
return /@generated\b/.test(head);
|
|
297
378
|
}
|
|
298
|
-
var no_enum_default =
|
|
379
|
+
var no_enum_default = import_utils4.ESLintUtils.RuleCreator(
|
|
299
380
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
300
381
|
)({
|
|
301
382
|
name: "no-enum",
|
|
@@ -345,9 +426,260 @@ var no_enum_default = import_utils3.ESLintUtils.RuleCreator(
|
|
|
345
426
|
}
|
|
346
427
|
});
|
|
347
428
|
|
|
429
|
+
// src/rules/no-insecure-random-id.ts
|
|
430
|
+
var import_utils5 = require("@typescript-eslint/utils");
|
|
431
|
+
var NAME_PATTERN = /id|token|key|secret|uuid|nonce|session|password|salt/i;
|
|
432
|
+
function isMathRandomCall(node) {
|
|
433
|
+
if (node.type !== "CallExpression") {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const callee = node.callee;
|
|
437
|
+
if (callee.type !== "MemberExpression" || callee.computed) {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
const { object, property } = callee;
|
|
441
|
+
return object.type === "Identifier" && object.name === "Math" && property.type === "Identifier" && property.name === "random";
|
|
442
|
+
}
|
|
443
|
+
function isPartOfToString36Chain(node) {
|
|
444
|
+
let current = node;
|
|
445
|
+
let parent = current.parent;
|
|
446
|
+
while (parent) {
|
|
447
|
+
if (parent.type === "MemberExpression" && parent.object === current && !parent.computed && parent.property.type === "Identifier" && parent.property.name === "toString") {
|
|
448
|
+
const grandparent = parent.parent;
|
|
449
|
+
if (grandparent && grandparent.type === "CallExpression" && grandparent.callee === parent) {
|
|
450
|
+
const firstArg = grandparent.arguments[0];
|
|
451
|
+
if (firstArg && firstArg.type === "Literal" && firstArg.value === 36) {
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (parent.type === "MemberExpression" && parent.object === current) {
|
|
457
|
+
current = parent;
|
|
458
|
+
parent = current.parent;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (parent.type === "CallExpression" && parent.callee === current) {
|
|
462
|
+
current = parent;
|
|
463
|
+
parent = current.parent;
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
function findEnclosingName(node) {
|
|
471
|
+
let current = node;
|
|
472
|
+
let parent = current.parent;
|
|
473
|
+
while (parent) {
|
|
474
|
+
if (parent.type === "VariableDeclarator" && parent.init === current) {
|
|
475
|
+
if (parent.id.type === "Identifier") {
|
|
476
|
+
return parent.id.name;
|
|
477
|
+
}
|
|
478
|
+
return void 0;
|
|
479
|
+
}
|
|
480
|
+
if (parent.type === "Property" && parent.value === current) {
|
|
481
|
+
const key = parent.key;
|
|
482
|
+
if (!parent.computed && key.type === "Identifier") {
|
|
483
|
+
return key.name;
|
|
484
|
+
}
|
|
485
|
+
if (key.type === "Literal" && typeof key.value === "string") {
|
|
486
|
+
return key.value;
|
|
487
|
+
}
|
|
488
|
+
return void 0;
|
|
489
|
+
}
|
|
490
|
+
if (parent.type === "PropertyDefinition" && parent.value === current) {
|
|
491
|
+
const key = parent.key;
|
|
492
|
+
if (!parent.computed && key.type === "Identifier") {
|
|
493
|
+
return key.name;
|
|
494
|
+
}
|
|
495
|
+
if (key.type === "Literal" && typeof key.value === "string") {
|
|
496
|
+
return key.value;
|
|
497
|
+
}
|
|
498
|
+
return void 0;
|
|
499
|
+
}
|
|
500
|
+
if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression" || parent.type === "BlockStatement" || parent.type === "ReturnStatement" || parent.type === "ExpressionStatement") {
|
|
501
|
+
return void 0;
|
|
502
|
+
}
|
|
503
|
+
current = parent;
|
|
504
|
+
parent = current.parent;
|
|
505
|
+
}
|
|
506
|
+
return void 0;
|
|
507
|
+
}
|
|
508
|
+
var no_insecure_random_id_default = import_utils5.ESLintUtils.RuleCreator(
|
|
509
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
510
|
+
)({
|
|
511
|
+
name: "no-insecure-random-id",
|
|
512
|
+
meta: {
|
|
513
|
+
type: "problem",
|
|
514
|
+
docs: {
|
|
515
|
+
description: "Disallow using `Math.random()` to generate identifiers, tokens, or secrets; use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead."
|
|
516
|
+
},
|
|
517
|
+
schema: [],
|
|
518
|
+
messages: {
|
|
519
|
+
insecureRandomId: "`Math.random()` is not cryptographically secure and is predictable; do not use it to generate IDs, tokens, or secrets. Use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead."
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
defaultOptions: [],
|
|
523
|
+
create(context) {
|
|
524
|
+
return {
|
|
525
|
+
CallExpression(node) {
|
|
526
|
+
if (!isMathRandomCall(node)) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (isPartOfToString36Chain(node)) {
|
|
530
|
+
context.report({ node, messageId: "insecureRandomId" });
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const name = findEnclosingName(node);
|
|
534
|
+
if (name !== void 0 && NAME_PATTERN.test(name)) {
|
|
535
|
+
context.report({ node, messageId: "insecureRandomId" });
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// src/rules/no-json-stringify-error.ts
|
|
543
|
+
var import_utils6 = require("@typescript-eslint/utils");
|
|
544
|
+
var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
|
|
545
|
+
function isCatchBinding(scope, name) {
|
|
546
|
+
let current = scope;
|
|
547
|
+
while (current) {
|
|
548
|
+
const variable = current.set.get(name);
|
|
549
|
+
if (variable) {
|
|
550
|
+
for (const def of variable.defs) {
|
|
551
|
+
if (def.type === "CatchClause") {
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
current = current.upper;
|
|
557
|
+
}
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
function isJsonStringify(callee) {
|
|
561
|
+
return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
|
|
562
|
+
}
|
|
563
|
+
var no_json_stringify_error_default = import_utils6.ESLintUtils.RuleCreator(
|
|
564
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
565
|
+
)({
|
|
566
|
+
name: "no-json-stringify-error",
|
|
567
|
+
meta: {
|
|
568
|
+
type: "problem",
|
|
569
|
+
docs: {
|
|
570
|
+
description: "Disallow `JSON.stringify` on an Error value; it yields `{}` because `message`/`stack` are non-enumerable."
|
|
571
|
+
},
|
|
572
|
+
schema: [],
|
|
573
|
+
messages: {
|
|
574
|
+
noJsonStringifyError: "`JSON.stringify` on an Error yields `{}` because `message`/`stack` are non-enumerable. Log `err.message` / `err.stack`, or use a proper error serializer."
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
defaultOptions: [],
|
|
578
|
+
create(context) {
|
|
579
|
+
return {
|
|
580
|
+
CallExpression(node) {
|
|
581
|
+
if (!isJsonStringify(node.callee)) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const firstArg = node.arguments[0];
|
|
585
|
+
if (!firstArg || firstArg.type !== "Identifier") {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const name = firstArg.name;
|
|
589
|
+
const scope = context.sourceCode.getScope(firstArg);
|
|
590
|
+
if (ERROR_NAME_PATTERN.test(name) || isCatchBinding(scope, name)) {
|
|
591
|
+
context.report({
|
|
592
|
+
node,
|
|
593
|
+
messageId: "noJsonStringifyError"
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// src/rules/no-log-only-catch.ts
|
|
602
|
+
var import_utils7 = require("@typescript-eslint/utils");
|
|
603
|
+
var DEFAULT_IGNORE_PATTERNS2 = [
|
|
604
|
+
/\.test\./,
|
|
605
|
+
/\.spec\./,
|
|
606
|
+
/[\\/]__tests__[\\/]/
|
|
607
|
+
];
|
|
608
|
+
var CONSOLE_METHODS = /* @__PURE__ */ new Set([
|
|
609
|
+
"log",
|
|
610
|
+
"error",
|
|
611
|
+
"warn",
|
|
612
|
+
"info",
|
|
613
|
+
"debug"
|
|
614
|
+
]);
|
|
615
|
+
function isConsoleCallStatement(statement) {
|
|
616
|
+
if (statement.type !== "ExpressionStatement") {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
const expr = statement.expression;
|
|
620
|
+
if (expr.type !== "CallExpression") {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
const callee = expr.callee;
|
|
624
|
+
if (callee.type !== "MemberExpression") {
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
const { object, property } = callee;
|
|
628
|
+
if (object.type !== "Identifier" || object.name !== "console") {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
if (callee.computed) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
if (property.type !== "Identifier") {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
return CONSOLE_METHODS.has(property.name);
|
|
638
|
+
}
|
|
639
|
+
var no_log_only_catch_default = import_utils7.ESLintUtils.RuleCreator(
|
|
640
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
641
|
+
)({
|
|
642
|
+
name: "no-log-only-catch",
|
|
643
|
+
meta: {
|
|
644
|
+
type: "problem",
|
|
645
|
+
docs: {
|
|
646
|
+
description: "Disallow `catch` clauses that only log (or do nothing) and then swallow the error; rethrow or handle it instead."
|
|
647
|
+
},
|
|
648
|
+
schema: [],
|
|
649
|
+
messages: {
|
|
650
|
+
noLogOnlyCatch: "Logging then swallowing the error hides failures. Rethrow the error or handle it for real."
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
defaultOptions: [],
|
|
654
|
+
create(context) {
|
|
655
|
+
const filename = context.filename;
|
|
656
|
+
const isIgnoredByDefault = DEFAULT_IGNORE_PATTERNS2.some(
|
|
657
|
+
(re) => re.test(filename)
|
|
658
|
+
);
|
|
659
|
+
if (isIgnoredByDefault) {
|
|
660
|
+
return {};
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
CatchClause(node) {
|
|
664
|
+
const statements = node.body.body;
|
|
665
|
+
if (statements.length === 0) {
|
|
666
|
+
context.report({ node, messageId: "noLogOnlyCatch" });
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const everyStatementIsConsoleLog = statements.every(
|
|
670
|
+
(statement) => isConsoleCallStatement(statement)
|
|
671
|
+
);
|
|
672
|
+
if (everyStatementIsConsoleLog) {
|
|
673
|
+
context.report({ node, messageId: "noLogOnlyCatch" });
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
|
|
348
680
|
// src/rules/no-raw-env.ts
|
|
349
|
-
var
|
|
350
|
-
var no_raw_env_default =
|
|
681
|
+
var import_utils8 = require("@typescript-eslint/utils");
|
|
682
|
+
var no_raw_env_default = import_utils8.ESLintUtils.RuleCreator(
|
|
351
683
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
352
684
|
)({
|
|
353
685
|
name: "no-raw-env",
|
|
@@ -379,8 +711,300 @@ var no_raw_env_default = import_utils4.ESLintUtils.RuleCreator(
|
|
|
379
711
|
}
|
|
380
712
|
});
|
|
381
713
|
|
|
714
|
+
// src/rules/no-sentinel-return-on-catch.ts
|
|
715
|
+
var import_utils9 = require("@typescript-eslint/utils");
|
|
716
|
+
function isSentinelArgument(arg) {
|
|
717
|
+
if (arg === null) {
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
if (arg.type === import_utils9.AST_NODE_TYPES.Literal && arg.value === null) {
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
if (arg.type === import_utils9.AST_NODE_TYPES.Literal && arg.value === false) {
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
if (arg.type === import_utils9.AST_NODE_TYPES.Identifier && arg.name === "undefined") {
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
if (arg.type === import_utils9.AST_NODE_TYPES.ArrayExpression && arg.elements.length === 0) {
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
if (arg.type === import_utils9.AST_NODE_TYPES.ObjectExpression && arg.properties.length === 0) {
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
function containsThrow(node) {
|
|
738
|
+
let found = false;
|
|
739
|
+
const visit = (current) => {
|
|
740
|
+
if (found) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (current.type === import_utils9.AST_NODE_TYPES.ThrowStatement) {
|
|
744
|
+
found = true;
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (current.type === import_utils9.AST_NODE_TYPES.FunctionDeclaration || current.type === import_utils9.AST_NODE_TYPES.FunctionExpression || current.type === import_utils9.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
for (const key of Object.keys(current)) {
|
|
751
|
+
if (key === "parent") {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const value = current[key];
|
|
755
|
+
if (Array.isArray(value)) {
|
|
756
|
+
for (const child of value) {
|
|
757
|
+
if (isNode(child)) {
|
|
758
|
+
visit(child);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
} else if (isNode(value)) {
|
|
762
|
+
visit(value);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
visit(node);
|
|
767
|
+
return found;
|
|
768
|
+
}
|
|
769
|
+
function isNode(value) {
|
|
770
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
771
|
+
}
|
|
772
|
+
var no_sentinel_return_on_catch_default = import_utils9.ESLintUtils.RuleCreator(
|
|
773
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
774
|
+
)({
|
|
775
|
+
name: "no-sentinel-return-on-catch",
|
|
776
|
+
meta: {
|
|
777
|
+
type: "problem",
|
|
778
|
+
docs: {
|
|
779
|
+
description: "Disallow swallowing a caught error by returning an empty sentinel (`null`, `undefined`, `false`, `[]`, `{}`) as the final statement of a `catch` block."
|
|
780
|
+
},
|
|
781
|
+
schema: [],
|
|
782
|
+
messages: {
|
|
783
|
+
noSentinelReturn: "This `catch` block swallows the error by returning an empty sentinel. Rethrow it, return a typed Result, or handle the error explicitly."
|
|
784
|
+
}
|
|
785
|
+
},
|
|
786
|
+
defaultOptions: [],
|
|
787
|
+
create(context) {
|
|
788
|
+
return {
|
|
789
|
+
CatchClause(node) {
|
|
790
|
+
const body = node.body.body;
|
|
791
|
+
if (body.length === 0) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const last = body[body.length - 1];
|
|
795
|
+
if (last === void 0 || last.type !== import_utils9.AST_NODE_TYPES.ReturnStatement) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if (!isSentinelArgument(last.argument)) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
if (containsThrow(node.body)) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
context.report({
|
|
805
|
+
node: last,
|
|
806
|
+
messageId: "noSentinelReturn"
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// src/rules/no-sequential-await.ts
|
|
814
|
+
var import_utils10 = require("@typescript-eslint/utils");
|
|
815
|
+
function isFunctionLike(node) {
|
|
816
|
+
return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
|
817
|
+
}
|
|
818
|
+
function isLoop(node) {
|
|
819
|
+
return node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement" || node.type === "WhileStatement" || node.type === "DoWhileStatement";
|
|
820
|
+
}
|
|
821
|
+
var no_sequential_await_default = import_utils10.ESLintUtils.RuleCreator(
|
|
822
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
823
|
+
)({
|
|
824
|
+
name: "no-sequential-await",
|
|
825
|
+
meta: {
|
|
826
|
+
type: "problem",
|
|
827
|
+
docs: {
|
|
828
|
+
description: "Disallow serial `await` inside a loop; use `await Promise.all(...)` to run the operations concurrently."
|
|
829
|
+
},
|
|
830
|
+
schema: [],
|
|
831
|
+
messages: {
|
|
832
|
+
noSequentialAwait: "Avoid `await` inside a loop \u2014 it serializes I/O. Collect the promises and `await Promise.all(xs.map(async (x) => ...))` instead."
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
defaultOptions: [],
|
|
836
|
+
create(context) {
|
|
837
|
+
function findAwaitInScope(node) {
|
|
838
|
+
if (node.type === "AwaitExpression") {
|
|
839
|
+
return node;
|
|
840
|
+
}
|
|
841
|
+
if (isFunctionLike(node)) {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
for (const key of Object.keys(node)) {
|
|
845
|
+
if (key === "parent") {
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
const value = node[key];
|
|
849
|
+
if (Array.isArray(value)) {
|
|
850
|
+
for (const child of value) {
|
|
851
|
+
if (isNode2(child) && !isLoop(child)) {
|
|
852
|
+
const found = findAwaitInScope(child);
|
|
853
|
+
if (found) {
|
|
854
|
+
return found;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
} else if (isNode2(value) && !isLoop(value)) {
|
|
859
|
+
const found = findAwaitInScope(value);
|
|
860
|
+
if (found) {
|
|
861
|
+
return found;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
function isNode2(value) {
|
|
868
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
869
|
+
}
|
|
870
|
+
function checkLoop(node) {
|
|
871
|
+
const parts = [node.body];
|
|
872
|
+
if (node.type === "ForStatement") {
|
|
873
|
+
parts.push(node.init, node.test, node.update);
|
|
874
|
+
} else if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
|
|
875
|
+
parts.push(node.right);
|
|
876
|
+
} else {
|
|
877
|
+
parts.push(node.test);
|
|
878
|
+
}
|
|
879
|
+
for (const part of parts) {
|
|
880
|
+
if (part && !isLoop(part) && findAwaitInScope(part)) {
|
|
881
|
+
context.report({ node, messageId: "noSequentialAwait" });
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
ForStatement: checkLoop,
|
|
888
|
+
ForInStatement: checkLoop,
|
|
889
|
+
WhileStatement: checkLoop,
|
|
890
|
+
DoWhileStatement: checkLoop,
|
|
891
|
+
ForOfStatement(node) {
|
|
892
|
+
if (node.await) {
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
checkLoop(node);
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
// src/rules/no-string-concat-in-loop.ts
|
|
902
|
+
var import_utils11 = require("@typescript-eslint/utils");
|
|
903
|
+
var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
904
|
+
"ForStatement",
|
|
905
|
+
"ForOfStatement",
|
|
906
|
+
"ForInStatement",
|
|
907
|
+
"WhileStatement",
|
|
908
|
+
"DoWhileStatement"
|
|
909
|
+
]);
|
|
910
|
+
function isStringLiteralInit(node) {
|
|
911
|
+
if (node === null) {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
if (node.type === "TemplateLiteral") {
|
|
915
|
+
return true;
|
|
916
|
+
}
|
|
917
|
+
if (node.type === "Literal") {
|
|
918
|
+
return typeof node.value === "string";
|
|
919
|
+
}
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
function findVariable(scope, name) {
|
|
923
|
+
let current = scope;
|
|
924
|
+
while (current !== null) {
|
|
925
|
+
const variable = current.variables.find((v) => v.name === name);
|
|
926
|
+
if (variable !== void 0) {
|
|
927
|
+
return variable;
|
|
928
|
+
}
|
|
929
|
+
current = current.upper;
|
|
930
|
+
}
|
|
931
|
+
return void 0;
|
|
932
|
+
}
|
|
933
|
+
function isStringInitializedVariable(variable) {
|
|
934
|
+
if (variable.defs.length !== 1) {
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
const def = variable.defs[0];
|
|
938
|
+
if (def === void 0 || def.type !== "Variable") {
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
const declarator = def.node;
|
|
942
|
+
if (declarator.type !== "VariableDeclarator") {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
return isStringLiteralInit(declarator.init);
|
|
946
|
+
}
|
|
947
|
+
function isInsideLoopBody(node) {
|
|
948
|
+
let child = node;
|
|
949
|
+
let parent = node.parent;
|
|
950
|
+
while (parent !== void 0 && parent !== null) {
|
|
951
|
+
if (LOOP_NODE_TYPES.has(parent.type)) {
|
|
952
|
+
const loop = parent;
|
|
953
|
+
if (loop.body === child) {
|
|
954
|
+
return true;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
child = parent;
|
|
958
|
+
parent = parent.parent;
|
|
959
|
+
}
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
var no_string_concat_in_loop_default = import_utils11.ESLintUtils.RuleCreator(
|
|
963
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
964
|
+
)({
|
|
965
|
+
name: "no-string-concat-in-loop",
|
|
966
|
+
meta: {
|
|
967
|
+
type: "suggestion",
|
|
968
|
+
docs: {
|
|
969
|
+
description: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead."
|
|
970
|
+
},
|
|
971
|
+
schema: [],
|
|
972
|
+
messages: {
|
|
973
|
+
noStringConcatInLoop: 'Avoid building a string with `+=` inside a loop \u2014 this is O(n^2). Push the parts onto an array and use `arr.join("")` after the loop.'
|
|
974
|
+
}
|
|
975
|
+
},
|
|
976
|
+
defaultOptions: [],
|
|
977
|
+
create(context) {
|
|
978
|
+
return {
|
|
979
|
+
AssignmentExpression(node) {
|
|
980
|
+
if (node.operator !== "+=") {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
if (node.left.type !== "Identifier") {
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
if (!isInsideLoopBody(node)) {
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const scope = context.sourceCode.getScope(node);
|
|
990
|
+
const variable = findVariable(scope, node.left.name);
|
|
991
|
+
if (variable === void 0) {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (!isStringInitializedVariable(variable)) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
context.report({
|
|
998
|
+
node,
|
|
999
|
+
messageId: "noStringConcatInLoop"
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
|
|
382
1006
|
// src/rules/no-unnecessary-use-client.ts
|
|
383
|
-
var
|
|
1007
|
+
var import_utils12 = require("@typescript-eslint/utils");
|
|
384
1008
|
var HOOK_REGEX = /^use([A-Z]|$)/;
|
|
385
1009
|
var EVENT_PROP_REGEX = /^on[A-Z]/;
|
|
386
1010
|
var ERROR_FILE_REGEX = /\b(?:global-)?error\.[jt]sx?$/;
|
|
@@ -403,16 +1027,16 @@ var BROWSER_GLOBALS = /* @__PURE__ */ new Set([
|
|
|
403
1027
|
]);
|
|
404
1028
|
var CLIENT_ONLY_PACKAGES_REGEX = /^(?:@radix-ui\/|framer-motion|react-dom|react-day-picker|@floating-ui\/|react-select|react-toastify|react-hook-form|recharts|react-dropzone|react-slick|react-swipeable|react-resizable|react-draggable|react-beautiful-dnd|@hello-pangea\/dnd|react-virtualized|react-window|@tanstack\/react-table|@tanstack\/react-query|react-redux|recoil|jotai|zustand|@tippyjs\/react|react-color|react-datepicker|next-themes|react-helmet|react-helmet-async|styled-components|@emotion\/)/;
|
|
405
1029
|
var isUseClientDirective = (node) => {
|
|
406
|
-
return node.type ===
|
|
1030
|
+
return node.type === import_utils12.AST_NODE_TYPES.ExpressionStatement && node.expression.type === import_utils12.AST_NODE_TYPES.Literal && node.expression.value === "use client";
|
|
407
1031
|
};
|
|
408
1032
|
var isGlobalReference = (node, context) => {
|
|
409
1033
|
if (!BROWSER_GLOBALS.has(node.name)) return false;
|
|
410
1034
|
const parent = node.parent;
|
|
411
1035
|
if (parent !== void 0) {
|
|
412
|
-
if (parent.type ===
|
|
1036
|
+
if (parent.type === import_utils12.AST_NODE_TYPES.MemberExpression && parent.property === node && !parent.computed) {
|
|
413
1037
|
return false;
|
|
414
1038
|
}
|
|
415
|
-
if (parent.type ===
|
|
1039
|
+
if (parent.type === import_utils12.AST_NODE_TYPES.Property && parent.key === node && !parent.computed) {
|
|
416
1040
|
return false;
|
|
417
1041
|
}
|
|
418
1042
|
if (parent.type.startsWith("TS")) {
|
|
@@ -429,7 +1053,7 @@ var isGlobalReference = (node, context) => {
|
|
|
429
1053
|
}
|
|
430
1054
|
return true;
|
|
431
1055
|
};
|
|
432
|
-
var no_unnecessary_use_client_default =
|
|
1056
|
+
var no_unnecessary_use_client_default = import_utils12.ESLintUtils.RuleCreator(
|
|
433
1057
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
434
1058
|
)({
|
|
435
1059
|
name: "no-unnecessary-use-client",
|
|
@@ -452,13 +1076,13 @@ var no_unnecessary_use_client_default = import_utils5.ESLintUtils.RuleCreator(
|
|
|
452
1076
|
let directiveNode = null;
|
|
453
1077
|
let hasClientIndicator = false;
|
|
454
1078
|
const markIfHookOrContext = (callee) => {
|
|
455
|
-
if (callee.type ===
|
|
1079
|
+
if (callee.type === import_utils12.AST_NODE_TYPES.Identifier) {
|
|
456
1080
|
if (HOOK_REGEX.test(callee.name) || callee.name === "createContext") {
|
|
457
1081
|
hasClientIndicator = true;
|
|
458
1082
|
}
|
|
459
1083
|
return;
|
|
460
1084
|
}
|
|
461
|
-
if (callee.type ===
|
|
1085
|
+
if (callee.type === import_utils12.AST_NODE_TYPES.MemberExpression && callee.property.type === import_utils12.AST_NODE_TYPES.Identifier) {
|
|
462
1086
|
const name = callee.property.name;
|
|
463
1087
|
if (HOOK_REGEX.test(name) || name === "createContext") {
|
|
464
1088
|
hasClientIndicator = true;
|
|
@@ -468,7 +1092,7 @@ var no_unnecessary_use_client_default = import_utils5.ESLintUtils.RuleCreator(
|
|
|
468
1092
|
return {
|
|
469
1093
|
Program(node) {
|
|
470
1094
|
for (const stmt of node.body) {
|
|
471
|
-
if (stmt.type !==
|
|
1095
|
+
if (stmt.type !== import_utils12.AST_NODE_TYPES.ExpressionStatement) break;
|
|
472
1096
|
if (isUseClientDirective(stmt)) {
|
|
473
1097
|
directiveNode = stmt;
|
|
474
1098
|
break;
|
|
@@ -476,35 +1100,43 @@ var no_unnecessary_use_client_default = import_utils5.ESLintUtils.RuleCreator(
|
|
|
476
1100
|
}
|
|
477
1101
|
},
|
|
478
1102
|
CallExpression(node) {
|
|
1103
|
+
if (directiveNode === null) return;
|
|
479
1104
|
markIfHookOrContext(node.callee);
|
|
480
1105
|
},
|
|
481
1106
|
JSXAttribute(node) {
|
|
482
|
-
if (
|
|
1107
|
+
if (directiveNode === null) return;
|
|
1108
|
+
if (node.name.type === import_utils12.AST_NODE_TYPES.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
|
|
483
1109
|
hasClientIndicator = true;
|
|
484
1110
|
}
|
|
485
1111
|
},
|
|
486
1112
|
ImportDeclaration(node) {
|
|
1113
|
+
if (directiveNode === null) return;
|
|
487
1114
|
if (typeof node.source.value === "string" && CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)) {
|
|
488
1115
|
hasClientIndicator = true;
|
|
489
1116
|
}
|
|
490
1117
|
},
|
|
491
1118
|
ExportNamedDeclaration(node) {
|
|
1119
|
+
if (directiveNode === null) return;
|
|
492
1120
|
if (node.source !== null) {
|
|
493
1121
|
hasClientIndicator = true;
|
|
494
1122
|
}
|
|
495
1123
|
},
|
|
496
1124
|
ExportAllDeclaration(node) {
|
|
1125
|
+
if (directiveNode === null) return;
|
|
497
1126
|
if (node.source !== null) {
|
|
498
1127
|
hasClientIndicator = true;
|
|
499
1128
|
}
|
|
500
1129
|
},
|
|
501
1130
|
ClassDeclaration() {
|
|
1131
|
+
if (directiveNode === null) return;
|
|
502
1132
|
hasClientIndicator = true;
|
|
503
1133
|
},
|
|
504
1134
|
ClassExpression() {
|
|
1135
|
+
if (directiveNode === null) return;
|
|
505
1136
|
hasClientIndicator = true;
|
|
506
1137
|
},
|
|
507
1138
|
Identifier(node) {
|
|
1139
|
+
if (directiveNode === null) return;
|
|
508
1140
|
if (isGlobalReference(node, context)) {
|
|
509
1141
|
hasClientIndicator = true;
|
|
510
1142
|
}
|
|
@@ -521,14 +1153,98 @@ var no_unnecessary_use_client_default = import_utils5.ESLintUtils.RuleCreator(
|
|
|
521
1153
|
}
|
|
522
1154
|
});
|
|
523
1155
|
|
|
1156
|
+
// src/rules/prefer-discriminated-union.ts
|
|
1157
|
+
var import_utils13 = require("@typescript-eslint/utils");
|
|
1158
|
+
var import_utils14 = require("@typescript-eslint/utils");
|
|
1159
|
+
var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
|
|
1160
|
+
"success",
|
|
1161
|
+
"ok",
|
|
1162
|
+
"error",
|
|
1163
|
+
"failed",
|
|
1164
|
+
"isError"
|
|
1165
|
+
]);
|
|
1166
|
+
var MIN_OPTIONAL_MEMBERS = 2;
|
|
1167
|
+
function getMemberName(member) {
|
|
1168
|
+
if (member.type !== import_utils14.AST_NODE_TYPES.TSPropertySignature) {
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
const { key } = member;
|
|
1172
|
+
if (key.type === import_utils14.AST_NODE_TYPES.Identifier) {
|
|
1173
|
+
return key.name;
|
|
1174
|
+
}
|
|
1175
|
+
if (key.type === import_utils14.AST_NODE_TYPES.Literal && typeof key.value === "string") {
|
|
1176
|
+
return key.value;
|
|
1177
|
+
}
|
|
1178
|
+
return null;
|
|
1179
|
+
}
|
|
1180
|
+
function isBooleanTyped(member) {
|
|
1181
|
+
return member.typeAnnotation?.typeAnnotation.type === import_utils14.AST_NODE_TYPES.TSBooleanKeyword;
|
|
1182
|
+
}
|
|
1183
|
+
function looksLikeMutuallyExclusiveState(typeLiteral) {
|
|
1184
|
+
let hasStatusBoolean = false;
|
|
1185
|
+
let optionalCount = 0;
|
|
1186
|
+
for (const member of typeLiteral.members) {
|
|
1187
|
+
if (member.type !== import_utils14.AST_NODE_TYPES.TSPropertySignature) {
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
if (member.optional) {
|
|
1191
|
+
optionalCount += 1;
|
|
1192
|
+
}
|
|
1193
|
+
const name = getMemberName(member);
|
|
1194
|
+
if (name !== null && STATUS_MEMBER_NAMES.has(name) && isBooleanTyped(member)) {
|
|
1195
|
+
hasStatusBoolean = true;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
|
|
1199
|
+
}
|
|
1200
|
+
var prefer_discriminated_union_default = import_utils13.ESLintUtils.RuleCreator(
|
|
1201
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
1202
|
+
)({
|
|
1203
|
+
name: "prefer-discriminated-union",
|
|
1204
|
+
meta: {
|
|
1205
|
+
type: "suggestion",
|
|
1206
|
+
docs: {
|
|
1207
|
+
description: "Flag object types with a boolean status flag and many optionals; model them as a discriminated union instead."
|
|
1208
|
+
},
|
|
1209
|
+
schema: [],
|
|
1210
|
+
messages: {
|
|
1211
|
+
preferDiscriminatedUnion: "This object type uses a boolean status flag alongside several optional fields, which lets illegal states be representable. Model it as a `z.discriminatedUnion` / discriminated union (e.g. `{ ok: true; data: T } | { ok: false; error: E }`) to make illegal states unrepresentable."
|
|
1212
|
+
}
|
|
1213
|
+
},
|
|
1214
|
+
defaultOptions: [],
|
|
1215
|
+
create(context) {
|
|
1216
|
+
function checkTypeLiteral(typeLiteral, reportNode) {
|
|
1217
|
+
if (looksLikeMutuallyExclusiveState(typeLiteral)) {
|
|
1218
|
+
context.report({
|
|
1219
|
+
node: reportNode,
|
|
1220
|
+
messageId: "preferDiscriminatedUnion"
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return {
|
|
1225
|
+
TSInterfaceDeclaration(node) {
|
|
1226
|
+
const synthetic = {
|
|
1227
|
+
...node.body,
|
|
1228
|
+
type: import_utils14.AST_NODE_TYPES.TSTypeLiteral,
|
|
1229
|
+
members: node.body.body
|
|
1230
|
+
};
|
|
1231
|
+
checkTypeLiteral(synthetic, node);
|
|
1232
|
+
},
|
|
1233
|
+
"TSTypeAliasDeclaration > TSTypeLiteral"(node) {
|
|
1234
|
+
checkTypeLiteral(node, node.parent);
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
|
|
524
1240
|
// src/rules/prefer-schema-for-api-payload.ts
|
|
525
|
-
var
|
|
1241
|
+
var import_utils15 = require("@typescript-eslint/utils");
|
|
526
1242
|
var unwrap = (node) => {
|
|
527
1243
|
let current = node;
|
|
528
1244
|
while (current !== null && current !== void 0) {
|
|
529
|
-
if (current.type ===
|
|
1245
|
+
if (current.type === import_utils15.AST_NODE_TYPES.TSAsExpression || current.type === import_utils15.AST_NODE_TYPES.TSTypeAssertion || current.type === import_utils15.AST_NODE_TYPES.TSNonNullExpression || current.type === import_utils15.AST_NODE_TYPES.TSSatisfiesExpression) {
|
|
530
1246
|
current = current.expression;
|
|
531
|
-
} else if (current.type ===
|
|
1247
|
+
} else if (current.type === import_utils15.AST_NODE_TYPES.ChainExpression) {
|
|
532
1248
|
current = current.expression;
|
|
533
1249
|
} else {
|
|
534
1250
|
break;
|
|
@@ -539,20 +1255,20 @@ var unwrap = (node) => {
|
|
|
539
1255
|
var isJsonCall = (node) => {
|
|
540
1256
|
let current = unwrap(node);
|
|
541
1257
|
if (current === null) return false;
|
|
542
|
-
if (current.type ===
|
|
1258
|
+
if (current.type === import_utils15.AST_NODE_TYPES.AwaitExpression) {
|
|
543
1259
|
current = unwrap(current.argument);
|
|
544
1260
|
}
|
|
545
|
-
if (current === null || current.type !==
|
|
1261
|
+
if (current === null || current.type !== import_utils15.AST_NODE_TYPES.CallExpression) {
|
|
546
1262
|
return false;
|
|
547
1263
|
}
|
|
548
1264
|
const callee = unwrap(current.callee);
|
|
549
|
-
if (callee === null || callee.type !==
|
|
1265
|
+
if (callee === null || callee.type !== import_utils15.AST_NODE_TYPES.MemberExpression) {
|
|
550
1266
|
return false;
|
|
551
1267
|
}
|
|
552
1268
|
const property = unwrap(callee.property);
|
|
553
|
-
return property !== null && property.type ===
|
|
1269
|
+
return property !== null && property.type === import_utils15.AST_NODE_TYPES.Identifier && property.name === "json";
|
|
554
1270
|
};
|
|
555
|
-
var
|
|
1271
|
+
var findVariable2 = (scope, name) => {
|
|
556
1272
|
let current = scope;
|
|
557
1273
|
while (current !== null) {
|
|
558
1274
|
const variable = current.set.get(name);
|
|
@@ -563,13 +1279,13 @@ var findVariable = (scope, name) => {
|
|
|
563
1279
|
};
|
|
564
1280
|
var isUnvalidatedVariableRef = (node, scope, tracked) => {
|
|
565
1281
|
const unwrapped = unwrap(node);
|
|
566
|
-
if (unwrapped === null || unwrapped.type !==
|
|
1282
|
+
if (unwrapped === null || unwrapped.type !== import_utils15.AST_NODE_TYPES.Identifier) {
|
|
567
1283
|
return false;
|
|
568
1284
|
}
|
|
569
|
-
const variable =
|
|
1285
|
+
const variable = findVariable2(scope, unwrapped.name);
|
|
570
1286
|
return variable !== null && tracked.has(variable);
|
|
571
1287
|
};
|
|
572
|
-
var prefer_schema_for_api_payload_default =
|
|
1288
|
+
var prefer_schema_for_api_payload_default = import_utils15.ESLintUtils.RuleCreator(
|
|
573
1289
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
574
1290
|
)({
|
|
575
1291
|
name: "prefer-schema-for-api-payload",
|
|
@@ -597,11 +1313,11 @@ var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreato
|
|
|
597
1313
|
return {
|
|
598
1314
|
VariableDeclarator(node) {
|
|
599
1315
|
const scope = context.sourceCode.getScope(node);
|
|
600
|
-
if (node.id.type ===
|
|
1316
|
+
if (node.id.type === import_utils15.AST_NODE_TYPES.Identifier) {
|
|
601
1317
|
trackInitializer(node);
|
|
602
1318
|
return;
|
|
603
1319
|
}
|
|
604
|
-
if (node.id.type ===
|
|
1320
|
+
if (node.id.type === import_utils15.AST_NODE_TYPES.ObjectPattern || node.id.type === import_utils15.AST_NODE_TYPES.ArrayPattern) {
|
|
605
1321
|
if (isJsonCall(node.init)) {
|
|
606
1322
|
context.report({ node: node.id, messageId: "unparsedJsonAccess" });
|
|
607
1323
|
return;
|
|
@@ -613,8 +1329,8 @@ var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreato
|
|
|
613
1329
|
},
|
|
614
1330
|
AssignmentExpression(node) {
|
|
615
1331
|
const scope = context.sourceCode.getScope(node);
|
|
616
|
-
if (node.left.type ===
|
|
617
|
-
const variable =
|
|
1332
|
+
if (node.left.type === import_utils15.AST_NODE_TYPES.Identifier) {
|
|
1333
|
+
const variable = findVariable2(scope, node.left.name);
|
|
618
1334
|
if (variable === null) return;
|
|
619
1335
|
if (isJsonCall(node.right)) {
|
|
620
1336
|
unvalidatedVariables.add(variable);
|
|
@@ -623,7 +1339,7 @@ var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreato
|
|
|
623
1339
|
}
|
|
624
1340
|
return;
|
|
625
1341
|
}
|
|
626
|
-
if (node.left.type ===
|
|
1342
|
+
if (node.left.type === import_utils15.AST_NODE_TYPES.ObjectPattern || node.left.type === import_utils15.AST_NODE_TYPES.ArrayPattern) {
|
|
627
1343
|
if (isJsonCall(node.right)) {
|
|
628
1344
|
context.report({
|
|
629
1345
|
node: node.left,
|
|
@@ -644,15 +1360,15 @@ var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreato
|
|
|
644
1360
|
const obj = unwrap(node.object);
|
|
645
1361
|
if (isJsonCall(obj)) {
|
|
646
1362
|
const parent = node.parent;
|
|
647
|
-
if (parent.type ===
|
|
1363
|
+
if (parent.type === import_utils15.AST_NODE_TYPES.CallExpression && parent.callee === node && node.property.type === import_utils15.AST_NODE_TYPES.Identifier && (node.property.name === "parse" || node.property.name === "safeParse")) {
|
|
648
1364
|
return;
|
|
649
1365
|
}
|
|
650
1366
|
context.report({ node, messageId: "unparsedJsonAccess" });
|
|
651
1367
|
return;
|
|
652
1368
|
}
|
|
653
|
-
if (obj !== null && obj.type ===
|
|
1369
|
+
if (obj !== null && obj.type === import_utils15.AST_NODE_TYPES.Identifier && isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)) {
|
|
654
1370
|
context.report({ node, messageId: "unparsedJsonAccess" });
|
|
655
|
-
const variable =
|
|
1371
|
+
const variable = findVariable2(scope, obj.name);
|
|
656
1372
|
if (variable !== null) {
|
|
657
1373
|
unvalidatedVariables.delete(variable);
|
|
658
1374
|
}
|
|
@@ -662,8 +1378,153 @@ var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreato
|
|
|
662
1378
|
}
|
|
663
1379
|
});
|
|
664
1380
|
|
|
1381
|
+
// src/rules/prefer-semantic-colors.ts
|
|
1382
|
+
var import_utils16 = require("@typescript-eslint/utils");
|
|
1383
|
+
|
|
1384
|
+
// src/rules/_tailwind.ts
|
|
1385
|
+
var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^!/, "");
|
|
1386
|
+
var classTokens = (value) => value.split(/\s+/).filter(Boolean);
|
|
1387
|
+
|
|
1388
|
+
// src/rules/prefer-semantic-colors.ts
|
|
1389
|
+
var COLOR_PREFIXES = "text|bg|border(?:-[trblxyse])?|ring(?:-offset)?|fill|stroke|from|via|to|divide|decoration|placeholder|accent|caret|shadow|outline";
|
|
1390
|
+
var PALETTE = "red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone";
|
|
1391
|
+
var COLOR_FN = "rgba?|hsla?|hwb|oklch|oklab|lab|lch|color";
|
|
1392
|
+
var RAW_PALETTE_RE = new RegExp(`^(?:${COLOR_PREFIXES})-(?:${PALETTE})-\\d{2,3}(?:/\\d{1,3})?$`);
|
|
1393
|
+
var ARBITRARY_COLOR_RE = new RegExp(
|
|
1394
|
+
`^(?:${COLOR_PREFIXES})-\\[(?:#[0-9a-fA-F]{3,8}|(?:${COLOR_FN})\\([^\\]]*\\))\\]$`,
|
|
1395
|
+
"i"
|
|
1396
|
+
);
|
|
1397
|
+
var CLASS_FNS = /* @__PURE__ */ new Set(["cn", "clsx", "cva", "tv", "cx", "twMerge", "classnames", "classNames"]);
|
|
1398
|
+
var CLASS_NAME_RE = /class/i;
|
|
1399
|
+
var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
|
|
1400
|
+
"color",
|
|
1401
|
+
"background",
|
|
1402
|
+
"backgroundColor",
|
|
1403
|
+
"borderColor",
|
|
1404
|
+
"borderTopColor",
|
|
1405
|
+
"borderRightColor",
|
|
1406
|
+
"borderBottomColor",
|
|
1407
|
+
"borderLeftColor",
|
|
1408
|
+
"outlineColor",
|
|
1409
|
+
"caretColor",
|
|
1410
|
+
"textDecorationColor",
|
|
1411
|
+
"columnRuleColor",
|
|
1412
|
+
"fill",
|
|
1413
|
+
"stroke",
|
|
1414
|
+
"stopColor",
|
|
1415
|
+
"floodColor",
|
|
1416
|
+
"lightingColor"
|
|
1417
|
+
]);
|
|
1418
|
+
var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
|
|
1419
|
+
var propName = (key) => {
|
|
1420
|
+
if (key.type === import_utils16.AST_NODE_TYPES.Identifier) return key.name;
|
|
1421
|
+
if (key.type === import_utils16.AST_NODE_TYPES.Literal && typeof key.value === "string") return key.value;
|
|
1422
|
+
return null;
|
|
1423
|
+
};
|
|
1424
|
+
var prefer_semantic_colors_default = import_utils16.ESLintUtils.RuleCreator(
|
|
1425
|
+
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
1426
|
+
)({
|
|
1427
|
+
name: "prefer-semantic-colors",
|
|
1428
|
+
meta: {
|
|
1429
|
+
type: "suggestion",
|
|
1430
|
+
docs: {
|
|
1431
|
+
description: "Enforce design-system semantic color tokens (bg-primary, text-destructive, \u2026) over raw Tailwind palette classes (text-red-500), arbitrary color values (bg-[#fff]), and inline color literals."
|
|
1432
|
+
},
|
|
1433
|
+
schema: [],
|
|
1434
|
+
messages: {
|
|
1435
|
+
rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
|
|
1436
|
+
arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
|
|
1437
|
+
inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
defaultOptions: [],
|
|
1441
|
+
create(context) {
|
|
1442
|
+
const reportClasses = (value, node) => {
|
|
1443
|
+
for (const token of classTokens(value)) {
|
|
1444
|
+
const base = tailwindBase(token);
|
|
1445
|
+
if (RAW_PALETTE_RE.test(base)) {
|
|
1446
|
+
context.report({ node, messageId: "rawPalette", data: { class: token } });
|
|
1447
|
+
} else if (ARBITRARY_COLOR_RE.test(base)) {
|
|
1448
|
+
context.report({ node, messageId: "arbitraryColor", data: { class: token } });
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
const checkClassNode = (node) => {
|
|
1453
|
+
if (node === null) return;
|
|
1454
|
+
switch (node.type) {
|
|
1455
|
+
case import_utils16.AST_NODE_TYPES.Literal:
|
|
1456
|
+
if (typeof node.value === "string") reportClasses(node.value, node);
|
|
1457
|
+
break;
|
|
1458
|
+
case import_utils16.AST_NODE_TYPES.TemplateLiteral:
|
|
1459
|
+
for (const quasi of node.quasis) reportClasses(quasi.value.cooked ?? "", quasi);
|
|
1460
|
+
break;
|
|
1461
|
+
case import_utils16.AST_NODE_TYPES.ArrayExpression:
|
|
1462
|
+
for (const element of node.elements) {
|
|
1463
|
+
if (element !== null && element.type !== import_utils16.AST_NODE_TYPES.SpreadElement) checkClassNode(element);
|
|
1464
|
+
}
|
|
1465
|
+
break;
|
|
1466
|
+
case import_utils16.AST_NODE_TYPES.ObjectExpression:
|
|
1467
|
+
for (const property of node.properties) {
|
|
1468
|
+
if (property.type === import_utils16.AST_NODE_TYPES.Property) checkClassNode(property.value);
|
|
1469
|
+
}
|
|
1470
|
+
break;
|
|
1471
|
+
case import_utils16.AST_NODE_TYPES.ConditionalExpression:
|
|
1472
|
+
checkClassNode(node.consequent);
|
|
1473
|
+
checkClassNode(node.alternate);
|
|
1474
|
+
break;
|
|
1475
|
+
case import_utils16.AST_NODE_TYPES.LogicalExpression:
|
|
1476
|
+
checkClassNode(node.right);
|
|
1477
|
+
break;
|
|
1478
|
+
default:
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1481
|
+
};
|
|
1482
|
+
const checkColorValueNode = (node) => {
|
|
1483
|
+
if (node.type === import_utils16.AST_NODE_TYPES.Literal && typeof node.value === "string" && RAW_COLOR_VALUE_RE.test(node.value)) {
|
|
1484
|
+
context.report({ node, messageId: "inlineColor", data: { value: node.value } });
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
return {
|
|
1488
|
+
"JSXAttribute[name.name='className']"(node) {
|
|
1489
|
+
if (node.value === null) return;
|
|
1490
|
+
if (node.value.type === import_utils16.AST_NODE_TYPES.Literal) checkClassNode(node.value);
|
|
1491
|
+
else if (node.value.type === import_utils16.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
1492
|
+
if (node.value.expression.type !== import_utils16.AST_NODE_TYPES.JSXEmptyExpression) {
|
|
1493
|
+
checkClassNode(node.value.expression);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
},
|
|
1497
|
+
CallExpression(node) {
|
|
1498
|
+
if (node.callee.type === import_utils16.AST_NODE_TYPES.Identifier && CLASS_FNS.has(node.callee.name)) {
|
|
1499
|
+
for (const arg of node.arguments) {
|
|
1500
|
+
if (arg.type !== import_utils16.AST_NODE_TYPES.SpreadElement) checkClassNode(arg);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
},
|
|
1504
|
+
VariableDeclarator(node) {
|
|
1505
|
+
if (node.id.type === import_utils16.AST_NODE_TYPES.Identifier && CLASS_NAME_RE.test(node.id.name)) {
|
|
1506
|
+
checkClassNode(node.init);
|
|
1507
|
+
}
|
|
1508
|
+
},
|
|
1509
|
+
Property(node) {
|
|
1510
|
+
const name = propName(node.key);
|
|
1511
|
+
if (name !== null && CLASS_NAME_RE.test(name)) checkClassNode(node.value);
|
|
1512
|
+
},
|
|
1513
|
+
// SVG presentation attributes: <path fill="#000" stroke="#fff" />
|
|
1514
|
+
"JSXAttribute[name.name=/^(fill|stroke|color)$/]"(node) {
|
|
1515
|
+
if (node.value?.type === import_utils16.AST_NODE_TYPES.Literal) checkColorValueNode(node.value);
|
|
1516
|
+
},
|
|
1517
|
+
// Inline style objects: style={{ color: "#111827", backgroundColor: "#fff" }}
|
|
1518
|
+
"JSXAttribute[name.name='style'] ObjectExpression > Property"(node) {
|
|
1519
|
+
const name = propName(node.key);
|
|
1520
|
+
if (name !== null && STYLE_COLOR_PROPS.has(name)) checkColorValueNode(node.value);
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
|
|
665
1526
|
// src/rules/prefer-server-actions.ts
|
|
666
|
-
var
|
|
1527
|
+
var import_utils17 = require("@typescript-eslint/utils");
|
|
667
1528
|
var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
668
1529
|
var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
|
|
669
1530
|
var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
|
|
@@ -723,7 +1584,7 @@ function isMutationMethod(node, context) {
|
|
|
723
1584
|
}
|
|
724
1585
|
return false;
|
|
725
1586
|
}
|
|
726
|
-
function getPropertyNode(objNode,
|
|
1587
|
+
function getPropertyNode(objNode, propName2) {
|
|
727
1588
|
if (!objNode || objNode.type !== "ObjectExpression") return null;
|
|
728
1589
|
for (const prop of objNode.properties) {
|
|
729
1590
|
if (prop.type !== "Property") continue;
|
|
@@ -733,7 +1594,7 @@ function getPropertyNode(objNode, propName) {
|
|
|
733
1594
|
} else if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
|
|
734
1595
|
keyName = prop.key.value;
|
|
735
1596
|
}
|
|
736
|
-
if (keyName ===
|
|
1597
|
+
if (keyName === propName2) {
|
|
737
1598
|
if (prop.value.type === "AssignmentPattern" || prop.value.type === "ArrayPattern" || prop.value.type === "ObjectPattern") {
|
|
738
1599
|
return null;
|
|
739
1600
|
}
|
|
@@ -742,7 +1603,7 @@ function getPropertyNode(objNode, propName) {
|
|
|
742
1603
|
}
|
|
743
1604
|
return null;
|
|
744
1605
|
}
|
|
745
|
-
var prefer_server_actions_default =
|
|
1606
|
+
var prefer_server_actions_default = import_utils17.ESLintUtils.RuleCreator(
|
|
746
1607
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
747
1608
|
)({
|
|
748
1609
|
name: "prefer-server-actions",
|
|
@@ -781,7 +1642,10 @@ var prefer_server_actions_default = import_utils7.ESLintUtils.RuleCreator(
|
|
|
781
1642
|
const methodName = node.callee.property.name.toLowerCase();
|
|
782
1643
|
if (AXIOS_MUTATION_METHODS.has(methodName)) {
|
|
783
1644
|
const urlArg = node.arguments[0];
|
|
784
|
-
|
|
1645
|
+
const hasHandlerArg = node.arguments.some(
|
|
1646
|
+
(arg) => arg.type === "ArrowFunctionExpression" || arg.type === "FunctionExpression"
|
|
1647
|
+
);
|
|
1648
|
+
if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context)) {
|
|
785
1649
|
isMutation = true;
|
|
786
1650
|
}
|
|
787
1651
|
}
|
|
@@ -807,14 +1671,14 @@ var prefer_server_actions_default = import_utils7.ESLintUtils.RuleCreator(
|
|
|
807
1671
|
});
|
|
808
1672
|
|
|
809
1673
|
// src/rules/prefer-shadcn.ts
|
|
810
|
-
var
|
|
1674
|
+
var import_utils18 = require("@typescript-eslint/utils");
|
|
811
1675
|
var REPLACEMENTS = {
|
|
812
1676
|
input: "Input",
|
|
813
1677
|
select: "Select",
|
|
814
1678
|
textarea: "Textarea",
|
|
815
1679
|
dialog: "Dialog"
|
|
816
1680
|
};
|
|
817
|
-
var prefer_shadcn_default =
|
|
1681
|
+
var prefer_shadcn_default = import_utils18.ESLintUtils.RuleCreator(
|
|
818
1682
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
819
1683
|
)({
|
|
820
1684
|
name: "prefer-shadcn",
|
|
@@ -855,36 +1719,52 @@ var prefer_shadcn_default = import_utils8.ESLintUtils.RuleCreator(
|
|
|
855
1719
|
});
|
|
856
1720
|
|
|
857
1721
|
// src/rules/require-assert-never.ts
|
|
858
|
-
var
|
|
1722
|
+
var import_utils19 = require("@typescript-eslint/utils");
|
|
859
1723
|
var isAssertNeverCall = (expression) => {
|
|
860
|
-
if (expression.type !==
|
|
1724
|
+
if (expression.type !== import_utils19.AST_NODE_TYPES.CallExpression) return false;
|
|
861
1725
|
const callee = expression.callee;
|
|
862
|
-
|
|
1726
|
+
if (callee.type === import_utils19.AST_NODE_TYPES.Identifier) {
|
|
1727
|
+
return callee.name === "assertNever";
|
|
1728
|
+
}
|
|
1729
|
+
if (callee.type === import_utils19.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils19.AST_NODE_TYPES.Identifier) {
|
|
1730
|
+
return callee.property.name === "assertNever";
|
|
1731
|
+
}
|
|
1732
|
+
return false;
|
|
863
1733
|
};
|
|
864
1734
|
var statementContainsAssertNever = (statement) => {
|
|
865
|
-
if (statement.type ===
|
|
1735
|
+
if (statement.type === import_utils19.AST_NODE_TYPES.ExpressionStatement) {
|
|
866
1736
|
return isAssertNeverCall(statement.expression);
|
|
867
1737
|
}
|
|
868
|
-
if (statement.type ===
|
|
1738
|
+
if (statement.type === import_utils19.AST_NODE_TYPES.ThrowStatement) {
|
|
869
1739
|
return isAssertNeverCall(statement.argument);
|
|
870
1740
|
}
|
|
871
|
-
if (statement.type ===
|
|
1741
|
+
if (statement.type === import_utils19.AST_NODE_TYPES.ReturnStatement) {
|
|
1742
|
+
return statement.argument !== null && isAssertNeverCall(statement.argument);
|
|
1743
|
+
}
|
|
1744
|
+
if (statement.type === import_utils19.AST_NODE_TYPES.BlockStatement) {
|
|
872
1745
|
return statement.body.some(statementContainsAssertNever);
|
|
873
1746
|
}
|
|
874
1747
|
return false;
|
|
875
1748
|
};
|
|
876
|
-
var
|
|
1749
|
+
var isRuntimeHandlingStatement = (statement) => {
|
|
1750
|
+
if (statement.type === import_utils19.AST_NODE_TYPES.EmptyStatement) return false;
|
|
1751
|
+
if (statement.type === import_utils19.AST_NODE_TYPES.BlockStatement) {
|
|
1752
|
+
return statement.body.some(isRuntimeHandlingStatement);
|
|
1753
|
+
}
|
|
1754
|
+
return true;
|
|
1755
|
+
};
|
|
1756
|
+
var require_assert_never_default = import_utils19.ESLintUtils.RuleCreator(
|
|
877
1757
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
878
1758
|
)({
|
|
879
1759
|
name: "require-assert-never",
|
|
880
1760
|
meta: {
|
|
881
1761
|
type: "problem",
|
|
882
1762
|
docs: {
|
|
883
|
-
description: "Require switch
|
|
1763
|
+
description: "Require an exhaustive-style switch whose `default` case does no runtime work to call `assertNever(_)` so that discriminated unions are exhaustively checked at compile time. Switches with a legitimate runtime default (a reducer's `return state`, an HTTP-status `return fallback()`, a `break`, a `throw`, etc.) are left alone."
|
|
884
1764
|
},
|
|
885
1765
|
schema: [],
|
|
886
1766
|
messages: {
|
|
887
|
-
missingAssertNever: "
|
|
1767
|
+
missingAssertNever: "Empty switch `default` case \u2014 add runtime handling or call `assertNever()` so the discriminated union is exhaustively checked at compile time."
|
|
888
1768
|
}
|
|
889
1769
|
},
|
|
890
1770
|
defaultOptions: [],
|
|
@@ -895,10 +1775,8 @@ var require_assert_never_default = import_utils9.ESLintUtils.RuleCreator(
|
|
|
895
1775
|
(caseNode) => caseNode.test === null
|
|
896
1776
|
);
|
|
897
1777
|
if (!defaultCase) return;
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
);
|
|
901
|
-
if (hasAssertNever) return;
|
|
1778
|
+
if (defaultCase.consequent.some(statementContainsAssertNever)) return;
|
|
1779
|
+
if (defaultCase.consequent.some(isRuntimeHandlingStatement)) return;
|
|
902
1780
|
context.report({
|
|
903
1781
|
node: defaultCase,
|
|
904
1782
|
messageId: "missingAssertNever"
|
|
@@ -909,43 +1787,91 @@ var require_assert_never_default = import_utils9.ESLintUtils.RuleCreator(
|
|
|
909
1787
|
});
|
|
910
1788
|
|
|
911
1789
|
// src/rules/require-zod-form-validation.ts
|
|
912
|
-
var
|
|
913
|
-
var
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1790
|
+
var import_utils20 = require("@typescript-eslint/utils");
|
|
1791
|
+
var ZOD_SCHEMA_NAME_RE = /Schema$|^Z[A-Z]/;
|
|
1792
|
+
var looksLikeZodSchema = (node) => {
|
|
1793
|
+
let current = node;
|
|
1794
|
+
while (true) {
|
|
1795
|
+
if (current.type === import_utils20.AST_NODE_TYPES.Identifier) {
|
|
1796
|
+
return current.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
|
|
1797
|
+
}
|
|
1798
|
+
if (current.type === import_utils20.AST_NODE_TYPES.CallExpression) {
|
|
1799
|
+
current = current.callee;
|
|
1800
|
+
continue;
|
|
1801
|
+
}
|
|
1802
|
+
if (current.type === import_utils20.AST_NODE_TYPES.MemberExpression) {
|
|
1803
|
+
current = current.object;
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
917
1806
|
return false;
|
|
918
1807
|
}
|
|
919
|
-
return callee.object.type === import_utils10.AST_NODE_TYPES.Identifier && callee.object.name === "formData";
|
|
920
1808
|
};
|
|
921
|
-
var
|
|
922
|
-
if (node.type !==
|
|
1809
|
+
var isZodParseCall = (node) => {
|
|
1810
|
+
if (node.type !== import_utils20.AST_NODE_TYPES.CallExpression) return false;
|
|
923
1811
|
const callee = node.callee;
|
|
924
|
-
if (callee.type !==
|
|
925
|
-
|
|
1812
|
+
if (callee.type !== import_utils20.AST_NODE_TYPES.MemberExpression) return false;
|
|
1813
|
+
if (callee.computed) return false;
|
|
1814
|
+
if (callee.property.type !== import_utils20.AST_NODE_TYPES.Identifier) return false;
|
|
1815
|
+
const method = callee.property.name;
|
|
1816
|
+
if (method !== "parse" && method !== "safeParse") return false;
|
|
1817
|
+
return looksLikeZodSchema(callee.object);
|
|
926
1818
|
};
|
|
927
|
-
var
|
|
1819
|
+
var isFormDataMethodCall = (node) => {
|
|
1820
|
+
let current = node;
|
|
1821
|
+
if (current.type === import_utils20.AST_NODE_TYPES.AwaitExpression) {
|
|
1822
|
+
current = current.argument;
|
|
1823
|
+
}
|
|
1824
|
+
if (current.type !== import_utils20.AST_NODE_TYPES.CallExpression) return false;
|
|
1825
|
+
const callee = current.callee;
|
|
1826
|
+
return callee.type === import_utils20.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils20.AST_NODE_TYPES.Identifier && callee.property.name === "formData";
|
|
1827
|
+
};
|
|
1828
|
+
var require_zod_form_validation_default = import_utils20.ESLintUtils.RuleCreator(
|
|
928
1829
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
929
1830
|
)({
|
|
930
1831
|
name: "require-zod-form-validation",
|
|
931
1832
|
meta: {
|
|
932
1833
|
type: "problem",
|
|
933
1834
|
docs: {
|
|
934
|
-
description: "Require Zod validation (`Schema.parse(...)`) when reading values out of a `FormData` object."
|
|
1835
|
+
description: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object."
|
|
935
1836
|
},
|
|
936
1837
|
schema: [],
|
|
937
1838
|
messages: {
|
|
938
|
-
missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse())"
|
|
1839
|
+
missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse() / Schema.safeParse())"
|
|
939
1840
|
}
|
|
940
1841
|
},
|
|
941
1842
|
defaultOptions: [],
|
|
942
1843
|
create(context) {
|
|
1844
|
+
const isFormSourceIdentifier = (node) => {
|
|
1845
|
+
if (node.type !== import_utils20.AST_NODE_TYPES.Identifier) return false;
|
|
1846
|
+
if (/formdata/i.test(node.name)) return true;
|
|
1847
|
+
let scope = context.sourceCode.getScope(node);
|
|
1848
|
+
while (scope !== null) {
|
|
1849
|
+
const variable = scope.set.get(node.name);
|
|
1850
|
+
if (variable !== void 0 && variable.defs.length === 1) {
|
|
1851
|
+
const def = variable.defs[0];
|
|
1852
|
+
if (def !== void 0 && def.type === "Variable" && def.node.type === import_utils20.AST_NODE_TYPES.VariableDeclarator && def.node.init !== null) {
|
|
1853
|
+
return isFormDataMethodCall(def.node.init);
|
|
1854
|
+
}
|
|
1855
|
+
return false;
|
|
1856
|
+
}
|
|
1857
|
+
scope = scope.upper;
|
|
1858
|
+
}
|
|
1859
|
+
return false;
|
|
1860
|
+
};
|
|
1861
|
+
const isFormDataGetCall = (node) => {
|
|
1862
|
+
const callee = node.callee;
|
|
1863
|
+
if (callee.type !== import_utils20.AST_NODE_TYPES.MemberExpression) return false;
|
|
1864
|
+
if (callee.property.type !== import_utils20.AST_NODE_TYPES.Identifier || callee.property.name !== "get") {
|
|
1865
|
+
return false;
|
|
1866
|
+
}
|
|
1867
|
+
return isFormSourceIdentifier(callee.object);
|
|
1868
|
+
};
|
|
943
1869
|
return {
|
|
944
1870
|
CallExpression(node) {
|
|
945
1871
|
if (!isFormDataGetCall(node)) return;
|
|
946
1872
|
let parent = node.parent;
|
|
947
1873
|
while (parent !== null && parent !== void 0) {
|
|
948
|
-
if (
|
|
1874
|
+
if (isZodParseCall(parent)) return;
|
|
949
1875
|
parent = parent.parent;
|
|
950
1876
|
}
|
|
951
1877
|
context.report({
|
|
@@ -958,15 +1884,15 @@ var require_zod_form_validation_default = import_utils10.ESLintUtils.RuleCreator
|
|
|
958
1884
|
});
|
|
959
1885
|
|
|
960
1886
|
// src/rules/zod-naming-convention.ts
|
|
961
|
-
var
|
|
1887
|
+
var import_utils21 = require("@typescript-eslint/utils");
|
|
962
1888
|
var calleeChainStartsWithZ = (node) => {
|
|
963
1889
|
let current = node;
|
|
964
|
-
while (current.type ===
|
|
1890
|
+
while (current.type === import_utils21.AST_NODE_TYPES.MemberExpression) {
|
|
965
1891
|
const receiver = current.object;
|
|
966
|
-
if (receiver.type ===
|
|
1892
|
+
if (receiver.type === import_utils21.AST_NODE_TYPES.Identifier && receiver.name === "z") {
|
|
967
1893
|
return true;
|
|
968
1894
|
}
|
|
969
|
-
if (receiver.type ===
|
|
1895
|
+
if (receiver.type === import_utils21.AST_NODE_TYPES.CallExpression) {
|
|
970
1896
|
current = receiver.callee;
|
|
971
1897
|
continue;
|
|
972
1898
|
}
|
|
@@ -974,7 +1900,7 @@ var calleeChainStartsWithZ = (node) => {
|
|
|
974
1900
|
}
|
|
975
1901
|
return false;
|
|
976
1902
|
};
|
|
977
|
-
var zod_naming_convention_default =
|
|
1903
|
+
var zod_naming_convention_default = import_utils21.ESLintUtils.RuleCreator(
|
|
978
1904
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
979
1905
|
)({
|
|
980
1906
|
name: "zod-naming-convention",
|
|
@@ -994,11 +1920,11 @@ var zod_naming_convention_default = import_utils11.ESLintUtils.RuleCreator(
|
|
|
994
1920
|
VariableDeclarator(node) {
|
|
995
1921
|
const init = node.init;
|
|
996
1922
|
if (init === null || init === void 0) return;
|
|
997
|
-
if (init.type !==
|
|
1923
|
+
if (init.type !== import_utils21.AST_NODE_TYPES.CallExpression) return;
|
|
998
1924
|
const callee = init.callee;
|
|
999
|
-
if (callee.type !==
|
|
1925
|
+
if (callee.type !== import_utils21.AST_NODE_TYPES.MemberExpression) return;
|
|
1000
1926
|
if (!calleeChainStartsWithZ(callee)) return;
|
|
1001
|
-
if (node.id.type !==
|
|
1927
|
+
if (node.id.type !== import_utils21.AST_NODE_TYPES.Identifier) return;
|
|
1002
1928
|
const variableName = node.id.name;
|
|
1003
1929
|
if (variableName.startsWith("Z")) return;
|
|
1004
1930
|
context.report({
|
|
@@ -1014,10 +1940,19 @@ var zod_naming_convention_default = import_utils11.ESLintUtils.RuleCreator(
|
|
|
1014
1940
|
var rules = {
|
|
1015
1941
|
"enforce-file-structure": enforce_file_structure_default,
|
|
1016
1942
|
"no-client-side-data-fetching": no_client_side_data_fetching_default,
|
|
1943
|
+
"no-comment-cruft": no_comment_cruft_default,
|
|
1017
1944
|
"no-enum": no_enum_default,
|
|
1945
|
+
"no-insecure-random-id": no_insecure_random_id_default,
|
|
1946
|
+
"no-json-stringify-error": no_json_stringify_error_default,
|
|
1947
|
+
"no-log-only-catch": no_log_only_catch_default,
|
|
1018
1948
|
"no-raw-env": no_raw_env_default,
|
|
1949
|
+
"no-sentinel-return-on-catch": no_sentinel_return_on_catch_default,
|
|
1950
|
+
"no-sequential-await": no_sequential_await_default,
|
|
1951
|
+
"no-string-concat-in-loop": no_string_concat_in_loop_default,
|
|
1019
1952
|
"no-unnecessary-use-client": no_unnecessary_use_client_default,
|
|
1953
|
+
"prefer-discriminated-union": prefer_discriminated_union_default,
|
|
1020
1954
|
"prefer-schema-for-api-payload": prefer_schema_for_api_payload_default,
|
|
1955
|
+
"prefer-semantic-colors": prefer_semantic_colors_default,
|
|
1021
1956
|
"prefer-server-actions": prefer_server_actions_default,
|
|
1022
1957
|
"prefer-shadcn": prefer_shadcn_default,
|
|
1023
1958
|
"require-assert-never": require_assert_never_default,
|
|
@@ -1027,7 +1962,7 @@ var rules = {
|
|
|
1027
1962
|
var plugin = {
|
|
1028
1963
|
meta: {
|
|
1029
1964
|
name: "@sarj/eslint-plugin",
|
|
1030
|
-
version: "2.
|
|
1965
|
+
version: "2.2.0"
|
|
1031
1966
|
},
|
|
1032
1967
|
rules,
|
|
1033
1968
|
configs: {
|
|
@@ -1041,7 +1976,18 @@ var plugin = {
|
|
|
1041
1976
|
"@sarj/no-client-side-data-fetching": "warn",
|
|
1042
1977
|
"@sarj/prefer-server-actions": "warn",
|
|
1043
1978
|
"@sarj/no-unnecessary-use-client": "warn",
|
|
1044
|
-
"@sarj/prefer-schema-for-api-payload": "warn"
|
|
1979
|
+
"@sarj/prefer-schema-for-api-payload": "warn",
|
|
1980
|
+
// Distilled from sarj-audit skills — warn in recommended, error in strict.
|
|
1981
|
+
"@sarj/no-sequential-await": "warn",
|
|
1982
|
+
"@sarj/no-sentinel-return-on-catch": "warn",
|
|
1983
|
+
"@sarj/no-log-only-catch": "warn",
|
|
1984
|
+
"@sarj/no-insecure-random-id": "warn",
|
|
1985
|
+
"@sarj/no-json-stringify-error": "warn",
|
|
1986
|
+
"@sarj/no-string-concat-in-loop": "warn",
|
|
1987
|
+
"@sarj/prefer-discriminated-union": "warn",
|
|
1988
|
+
"@sarj/no-comment-cruft": "warn",
|
|
1989
|
+
// Frontend / styling — distilled from frontend PR-review mining.
|
|
1990
|
+
"@sarj/prefer-semantic-colors": "warn"
|
|
1045
1991
|
}
|
|
1046
1992
|
},
|
|
1047
1993
|
strict: {
|
|
@@ -1057,7 +2003,19 @@ var plugin = {
|
|
|
1057
2003
|
"@sarj/no-client-side-data-fetching": "error",
|
|
1058
2004
|
"@sarj/prefer-server-actions": "error",
|
|
1059
2005
|
"@sarj/no-unnecessary-use-client": "error",
|
|
1060
|
-
"@sarj/prefer-schema-for-api-payload": "error"
|
|
2006
|
+
"@sarj/prefer-schema-for-api-payload": "error",
|
|
2007
|
+
// Distilled from sarj-audit skills.
|
|
2008
|
+
"@sarj/no-sequential-await": "error",
|
|
2009
|
+
"@sarj/no-sentinel-return-on-catch": "error",
|
|
2010
|
+
"@sarj/no-log-only-catch": "error",
|
|
2011
|
+
"@sarj/no-insecure-random-id": "error",
|
|
2012
|
+
"@sarj/no-json-stringify-error": "error",
|
|
2013
|
+
"@sarj/no-string-concat-in-loop": "error",
|
|
2014
|
+
"@sarj/prefer-discriminated-union": "error",
|
|
2015
|
+
"@sarj/no-comment-cruft": "error",
|
|
2016
|
+
// Frontend / styling — distilled from frontend PR-review mining. Stylistic,
|
|
2017
|
+
// no autofix → warn (rollout should prove the FP rate before raising it).
|
|
2018
|
+
"@sarj/prefer-semantic-colors": "warn"
|
|
1061
2019
|
}
|
|
1062
2020
|
}
|
|
1063
2021
|
}
|