@sarj/eslint-plugin 2.1.1 → 2.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 +1596 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +1568 -109
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,45 +1,30 @@
|
|
|
1
1
|
// src/rules/enforce-file-structure.ts
|
|
2
2
|
import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
3
3
|
var SECTION = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
functions: 3,
|
|
8
|
-
exports: 4
|
|
4
|
+
declarations: 0,
|
|
5
|
+
functions: 1,
|
|
6
|
+
exports: 2
|
|
9
7
|
};
|
|
10
|
-
var SECTION_NAMES = [
|
|
11
|
-
"imports",
|
|
12
|
-
"types",
|
|
13
|
-
"constants",
|
|
14
|
-
"functions",
|
|
15
|
-
"exports"
|
|
16
|
-
];
|
|
8
|
+
var SECTION_NAMES = ["declarations", "functions", "exports"];
|
|
17
9
|
var sectionName = (ordinal) => {
|
|
18
10
|
const name = SECTION_NAMES[ordinal];
|
|
19
11
|
return name ?? "unknown";
|
|
20
12
|
};
|
|
21
|
-
var
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
var SERVER_ACTION_FILE_RE = /(?:^|\/)actions\/|\.action\.[jt]sx?$|(?:^|\/)actions\.[jt]sx?$/;
|
|
14
|
+
var isFunctionExpression = (node) => node.type === AST_NODE_TYPES.ArrowFunctionExpression || node.type === AST_NODE_TYPES.FunctionExpression;
|
|
15
|
+
var isFunctionLikeVariable = (statement) => statement.declarations.length > 0 && statement.declarations.every(
|
|
16
|
+
(decl) => decl.init !== null && isFunctionExpression(decl.init)
|
|
17
|
+
);
|
|
26
18
|
var getStatementSection = (statement) => {
|
|
27
19
|
switch (statement.type) {
|
|
28
20
|
case AST_NODE_TYPES.ImportDeclaration:
|
|
29
|
-
return SECTION.imports;
|
|
30
21
|
case AST_NODE_TYPES.TSTypeAliasDeclaration:
|
|
31
22
|
case AST_NODE_TYPES.TSInterfaceDeclaration:
|
|
32
23
|
case AST_NODE_TYPES.TSEnumDeclaration:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (firstDeclarator !== void 0 && isConstantNamed(firstDeclarator)) {
|
|
38
|
-
return SECTION.constants;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return SECTION.functions;
|
|
42
|
-
}
|
|
24
|
+
case AST_NODE_TYPES.ClassDeclaration:
|
|
25
|
+
return SECTION.declarations;
|
|
26
|
+
case AST_NODE_TYPES.VariableDeclaration:
|
|
27
|
+
return isFunctionLikeVariable(statement) ? SECTION.functions : SECTION.declarations;
|
|
43
28
|
case AST_NODE_TYPES.FunctionDeclaration:
|
|
44
29
|
return SECTION.functions;
|
|
45
30
|
case AST_NODE_TYPES.ExportNamedDeclaration:
|
|
@@ -64,7 +49,7 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
|
|
|
64
49
|
meta: {
|
|
65
50
|
type: "suggestion",
|
|
66
51
|
docs: {
|
|
67
|
-
description: "Enforce
|
|
52
|
+
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."
|
|
68
53
|
},
|
|
69
54
|
schema: [],
|
|
70
55
|
messages: {
|
|
@@ -75,7 +60,7 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
|
|
|
75
60
|
defaultOptions: [],
|
|
76
61
|
create(context) {
|
|
77
62
|
const filename = context.filename;
|
|
78
|
-
const isServerAction =
|
|
63
|
+
const isServerAction = SERVER_ACTION_FILE_RE.test(filename);
|
|
79
64
|
return {
|
|
80
65
|
Program(node) {
|
|
81
66
|
const body = node.body;
|
|
@@ -88,9 +73,8 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
|
|
|
88
73
|
});
|
|
89
74
|
}
|
|
90
75
|
}
|
|
91
|
-
let currentSection = SECTION.
|
|
76
|
+
let currentSection = SECTION.declarations;
|
|
92
77
|
for (const statement of body) {
|
|
93
|
-
if (isUseServerDirective(statement)) continue;
|
|
94
78
|
if (statement.type === AST_NODE_TYPES.ExpressionStatement && statement.expression.type === AST_NODE_TYPES.Literal && typeof statement.expression.value === "string" && statement.expression.value.startsWith("use ")) {
|
|
95
79
|
continue;
|
|
96
80
|
}
|
|
@@ -129,7 +113,7 @@ var HTTP_METHOD_NAMES = /* @__PURE__ */ new Set([
|
|
|
129
113
|
"head",
|
|
130
114
|
"options"
|
|
131
115
|
]);
|
|
132
|
-
var
|
|
116
|
+
var ANALYTICS_SEGMENTS = /* @__PURE__ */ new Set([
|
|
133
117
|
"analytics",
|
|
134
118
|
"telemetry",
|
|
135
119
|
"track",
|
|
@@ -138,7 +122,7 @@ var ANALYTICS_KEYWORDS = [
|
|
|
138
122
|
"beacon",
|
|
139
123
|
"metrics",
|
|
140
124
|
"event"
|
|
141
|
-
];
|
|
125
|
+
]);
|
|
142
126
|
function isEffectHookCall(node) {
|
|
143
127
|
const callee = node.callee;
|
|
144
128
|
if (callee.type === AST_NODE_TYPES2.Identifier) {
|
|
@@ -212,7 +196,7 @@ function extractUrlString(node) {
|
|
|
212
196
|
function isAnalyticsCall(node) {
|
|
213
197
|
const url = extractUrlString(node).toLowerCase();
|
|
214
198
|
if (url === "") return false;
|
|
215
|
-
return
|
|
199
|
+
return url.split(/[/.]/).some((segment) => ANALYTICS_SEGMENTS.has(segment));
|
|
216
200
|
}
|
|
217
201
|
var no_client_side_data_fetching_default = ESLintUtils2.RuleCreator(
|
|
218
202
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
@@ -251,8 +235,105 @@ var no_client_side_data_fetching_default = ESLintUtils2.RuleCreator(
|
|
|
251
235
|
}
|
|
252
236
|
});
|
|
253
237
|
|
|
254
|
-
// src/rules/no-
|
|
238
|
+
// src/rules/no-comment-cruft.ts
|
|
255
239
|
import { ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils";
|
|
240
|
+
var LEADING_PREAMBLE_MIN = 4;
|
|
241
|
+
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;
|
|
242
|
+
var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
|
|
243
|
+
var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
|
|
244
|
+
var BANNER_RUN_RE = /={4,}|-{4,}|#{4,}|\*{4,}|~{4,}/;
|
|
245
|
+
var REGION_RE = /^#?(?:end)?region\b/i;
|
|
246
|
+
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\.)/;
|
|
247
|
+
var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
|
|
248
|
+
var CALL_OR_ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$|^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
|
|
249
|
+
function stripCommentMarker(line) {
|
|
250
|
+
return line.replace(/^\s*\/\//, "").replace(/^\s*\*+/, "").trim();
|
|
251
|
+
}
|
|
252
|
+
function isDirective(text) {
|
|
253
|
+
return DIRECTIVE_RE.test(text.trim());
|
|
254
|
+
}
|
|
255
|
+
function isBanner(text) {
|
|
256
|
+
const t = text.trim();
|
|
257
|
+
if (!t) return false;
|
|
258
|
+
return BANNER_FULL_RE.test(t) || BANNER_RUN_RE.test(t) || REGION_RE.test(t);
|
|
259
|
+
}
|
|
260
|
+
function looksLikeCode(text) {
|
|
261
|
+
const t = text.trim();
|
|
262
|
+
if (!t) return false;
|
|
263
|
+
if (CODE_KEYWORD_RE.test(t) && CODE_TAIL_RE.test(t)) return true;
|
|
264
|
+
return CALL_OR_ASSIGN_RE.test(t);
|
|
265
|
+
}
|
|
266
|
+
var no_comment_cruft_default = ESLintUtils3.RuleCreator(
|
|
267
|
+
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
268
|
+
)({
|
|
269
|
+
name: "no-comment-cruft",
|
|
270
|
+
meta: {
|
|
271
|
+
type: "suggestion",
|
|
272
|
+
docs: {
|
|
273
|
+
description: "Flag commented-out code, section-banner comments, and leading file-header comment preambles."
|
|
274
|
+
},
|
|
275
|
+
schema: [],
|
|
276
|
+
messages: {
|
|
277
|
+
commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
|
|
278
|
+
sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
|
|
279
|
+
fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines."
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
defaultOptions: [],
|
|
283
|
+
create(context) {
|
|
284
|
+
const sourceCode = context.sourceCode;
|
|
285
|
+
function isStandalone(comment) {
|
|
286
|
+
const before = sourceCode.getTokenBefore(comment, {
|
|
287
|
+
includeComments: false
|
|
288
|
+
});
|
|
289
|
+
return !before || before.loc.end.line < comment.loc.start.line;
|
|
290
|
+
}
|
|
291
|
+
function isJsDoc(comment) {
|
|
292
|
+
return comment.type === "Block" && /^\*/.test(comment.value);
|
|
293
|
+
}
|
|
294
|
+
function reportLeadingPreamble(comments, firstCodeLine) {
|
|
295
|
+
const leading = [];
|
|
296
|
+
let prevLine = null;
|
|
297
|
+
for (const comment of comments) {
|
|
298
|
+
if (comment.type !== "Line") break;
|
|
299
|
+
if (comment.loc.start.line >= firstCodeLine) break;
|
|
300
|
+
if (!isStandalone(comment)) break;
|
|
301
|
+
const body = stripCommentMarker(comment.value);
|
|
302
|
+
if (isDirective(body) || body.startsWith("!")) continue;
|
|
303
|
+
if (prevLine !== null && comment.loc.start.line !== prevLine + 1) break;
|
|
304
|
+
leading.push(comment);
|
|
305
|
+
prevLine = comment.loc.start.line;
|
|
306
|
+
}
|
|
307
|
+
const first = leading[0];
|
|
308
|
+
if (first === void 0 || leading.length < LEADING_PREAMBLE_MIN) return;
|
|
309
|
+
const isLicense = leading.some(
|
|
310
|
+
(c) => LICENSE_RE.test(stripCommentMarker(c.value))
|
|
311
|
+
);
|
|
312
|
+
if (!isLicense) {
|
|
313
|
+
context.report({ node: first, messageId: "fileHeaderPreamble" });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
Program() {
|
|
318
|
+
const comments = sourceCode.getAllComments();
|
|
319
|
+
const firstCodeLine = sourceCode.ast.tokens[0]?.loc.start.line ?? Number.MAX_SAFE_INTEGER;
|
|
320
|
+
for (const comment of comments) {
|
|
321
|
+
if (isJsDoc(comment) || !isStandalone(comment)) continue;
|
|
322
|
+
const texts = comment.value.split("\n").map(stripCommentMarker).filter((l) => l.length > 0 && !isDirective(l));
|
|
323
|
+
if (texts.some(isBanner)) {
|
|
324
|
+
context.report({ node: comment, messageId: "sectionBanner" });
|
|
325
|
+
} else if (texts.some(looksLikeCode)) {
|
|
326
|
+
context.report({ node: comment, messageId: "commentedOutCode" });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
reportLeadingPreamble(comments, firstCodeLine);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// src/rules/no-enum.ts
|
|
336
|
+
import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
|
|
256
337
|
var DEFAULT_IGNORE_PATTERNS = [
|
|
257
338
|
/[\\/]generated[\\/]/,
|
|
258
339
|
/\.gen\.tsx?$/,
|
|
@@ -271,7 +352,7 @@ function hasGeneratedMarker(sourceText) {
|
|
|
271
352
|
const head = sourceText.slice(0, 1024);
|
|
272
353
|
return /@generated\b/.test(head);
|
|
273
354
|
}
|
|
274
|
-
var no_enum_default =
|
|
355
|
+
var no_enum_default = ESLintUtils4.RuleCreator(
|
|
275
356
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
276
357
|
)({
|
|
277
358
|
name: "no-enum",
|
|
@@ -322,7 +403,7 @@ var no_enum_default = ESLintUtils3.RuleCreator(
|
|
|
322
403
|
});
|
|
323
404
|
|
|
324
405
|
// src/rules/no-insecure-random-id.ts
|
|
325
|
-
import { ESLintUtils as
|
|
406
|
+
import { ESLintUtils as ESLintUtils5 } from "@typescript-eslint/utils";
|
|
326
407
|
var NAME_PATTERN = /id|token|key|secret|uuid|nonce|session|password|salt/i;
|
|
327
408
|
function isMathRandomCall(node) {
|
|
328
409
|
if (node.type !== "CallExpression") {
|
|
@@ -400,7 +481,7 @@ function findEnclosingName(node) {
|
|
|
400
481
|
}
|
|
401
482
|
return void 0;
|
|
402
483
|
}
|
|
403
|
-
var no_insecure_random_id_default =
|
|
484
|
+
var no_insecure_random_id_default = ESLintUtils5.RuleCreator(
|
|
404
485
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
405
486
|
)({
|
|
406
487
|
name: "no-insecure-random-id",
|
|
@@ -435,7 +516,7 @@ var no_insecure_random_id_default = ESLintUtils4.RuleCreator(
|
|
|
435
516
|
});
|
|
436
517
|
|
|
437
518
|
// src/rules/no-json-stringify-error.ts
|
|
438
|
-
import { ESLintUtils as
|
|
519
|
+
import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
|
|
439
520
|
var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
|
|
440
521
|
function isCatchBinding(scope, name) {
|
|
441
522
|
let current = scope;
|
|
@@ -455,7 +536,7 @@ function isCatchBinding(scope, name) {
|
|
|
455
536
|
function isJsonStringify(callee) {
|
|
456
537
|
return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
|
|
457
538
|
}
|
|
458
|
-
var no_json_stringify_error_default =
|
|
539
|
+
var no_json_stringify_error_default = ESLintUtils6.RuleCreator(
|
|
459
540
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
460
541
|
)({
|
|
461
542
|
name: "no-json-stringify-error",
|
|
@@ -494,7 +575,7 @@ var no_json_stringify_error_default = ESLintUtils5.RuleCreator(
|
|
|
494
575
|
});
|
|
495
576
|
|
|
496
577
|
// src/rules/no-log-only-catch.ts
|
|
497
|
-
import { ESLintUtils as
|
|
578
|
+
import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
|
|
498
579
|
var DEFAULT_IGNORE_PATTERNS2 = [
|
|
499
580
|
/\.test\./,
|
|
500
581
|
/\.spec\./,
|
|
@@ -531,7 +612,7 @@ function isConsoleCallStatement(statement) {
|
|
|
531
612
|
}
|
|
532
613
|
return CONSOLE_METHODS.has(property.name);
|
|
533
614
|
}
|
|
534
|
-
var no_log_only_catch_default =
|
|
615
|
+
var no_log_only_catch_default = ESLintUtils7.RuleCreator(
|
|
535
616
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
536
617
|
)({
|
|
537
618
|
name: "no-log-only-catch",
|
|
@@ -573,8 +654,8 @@ var no_log_only_catch_default = ESLintUtils6.RuleCreator(
|
|
|
573
654
|
});
|
|
574
655
|
|
|
575
656
|
// src/rules/no-raw-env.ts
|
|
576
|
-
import { ESLintUtils as
|
|
577
|
-
var no_raw_env_default =
|
|
657
|
+
import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
|
|
658
|
+
var no_raw_env_default = ESLintUtils8.RuleCreator(
|
|
578
659
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
579
660
|
)({
|
|
580
661
|
name: "no-raw-env",
|
|
@@ -608,7 +689,7 @@ var no_raw_env_default = ESLintUtils7.RuleCreator(
|
|
|
608
689
|
|
|
609
690
|
// src/rules/no-sentinel-return-on-catch.ts
|
|
610
691
|
import {
|
|
611
|
-
ESLintUtils as
|
|
692
|
+
ESLintUtils as ESLintUtils9,
|
|
612
693
|
AST_NODE_TYPES as AST_NODE_TYPES3
|
|
613
694
|
} from "@typescript-eslint/utils";
|
|
614
695
|
function isSentinelArgument(arg) {
|
|
@@ -667,7 +748,7 @@ function containsThrow(node) {
|
|
|
667
748
|
function isNode(value) {
|
|
668
749
|
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
669
750
|
}
|
|
670
|
-
var no_sentinel_return_on_catch_default =
|
|
751
|
+
var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
|
|
671
752
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
672
753
|
)({
|
|
673
754
|
name: "no-sentinel-return-on-catch",
|
|
@@ -709,14 +790,14 @@ var no_sentinel_return_on_catch_default = ESLintUtils8.RuleCreator(
|
|
|
709
790
|
});
|
|
710
791
|
|
|
711
792
|
// src/rules/no-sequential-await.ts
|
|
712
|
-
import { ESLintUtils as
|
|
793
|
+
import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
|
|
713
794
|
function isFunctionLike(node) {
|
|
714
795
|
return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
|
715
796
|
}
|
|
716
797
|
function isLoop(node) {
|
|
717
798
|
return node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement" || node.type === "WhileStatement" || node.type === "DoWhileStatement";
|
|
718
799
|
}
|
|
719
|
-
var no_sequential_await_default =
|
|
800
|
+
var no_sequential_await_default = ESLintUtils10.RuleCreator(
|
|
720
801
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
721
802
|
)({
|
|
722
803
|
name: "no-sequential-await",
|
|
@@ -746,14 +827,14 @@ var no_sequential_await_default = ESLintUtils9.RuleCreator(
|
|
|
746
827
|
const value = node[key];
|
|
747
828
|
if (Array.isArray(value)) {
|
|
748
829
|
for (const child of value) {
|
|
749
|
-
if (
|
|
830
|
+
if (isNode4(child) && !isLoop(child)) {
|
|
750
831
|
const found = findAwaitInScope(child);
|
|
751
832
|
if (found) {
|
|
752
833
|
return found;
|
|
753
834
|
}
|
|
754
835
|
}
|
|
755
836
|
}
|
|
756
|
-
} else if (
|
|
837
|
+
} else if (isNode4(value) && !isLoop(value)) {
|
|
757
838
|
const found = findAwaitInScope(value);
|
|
758
839
|
if (found) {
|
|
759
840
|
return found;
|
|
@@ -762,7 +843,7 @@ var no_sequential_await_default = ESLintUtils9.RuleCreator(
|
|
|
762
843
|
}
|
|
763
844
|
return null;
|
|
764
845
|
}
|
|
765
|
-
function
|
|
846
|
+
function isNode4(value) {
|
|
766
847
|
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
767
848
|
}
|
|
768
849
|
function checkLoop(node) {
|
|
@@ -797,7 +878,7 @@ var no_sequential_await_default = ESLintUtils9.RuleCreator(
|
|
|
797
878
|
});
|
|
798
879
|
|
|
799
880
|
// src/rules/no-string-concat-in-loop.ts
|
|
800
|
-
import { ESLintUtils as
|
|
881
|
+
import { ESLintUtils as ESLintUtils11 } from "@typescript-eslint/utils";
|
|
801
882
|
var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
802
883
|
"ForStatement",
|
|
803
884
|
"ForOfStatement",
|
|
@@ -857,7 +938,7 @@ function isInsideLoopBody(node) {
|
|
|
857
938
|
}
|
|
858
939
|
return false;
|
|
859
940
|
}
|
|
860
|
-
var no_string_concat_in_loop_default =
|
|
941
|
+
var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
|
|
861
942
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
862
943
|
)({
|
|
863
944
|
name: "no-string-concat-in-loop",
|
|
@@ -904,7 +985,7 @@ var no_string_concat_in_loop_default = ESLintUtils10.RuleCreator(
|
|
|
904
985
|
// src/rules/no-unnecessary-use-client.ts
|
|
905
986
|
import {
|
|
906
987
|
AST_NODE_TYPES as AST_NODE_TYPES4,
|
|
907
|
-
ESLintUtils as
|
|
988
|
+
ESLintUtils as ESLintUtils12
|
|
908
989
|
} from "@typescript-eslint/utils";
|
|
909
990
|
var HOOK_REGEX = /^use([A-Z]|$)/;
|
|
910
991
|
var EVENT_PROP_REGEX = /^on[A-Z]/;
|
|
@@ -954,7 +1035,7 @@ var isGlobalReference = (node, context) => {
|
|
|
954
1035
|
}
|
|
955
1036
|
return true;
|
|
956
1037
|
};
|
|
957
|
-
var no_unnecessary_use_client_default =
|
|
1038
|
+
var no_unnecessary_use_client_default = ESLintUtils12.RuleCreator(
|
|
958
1039
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
959
1040
|
)({
|
|
960
1041
|
name: "no-unnecessary-use-client",
|
|
@@ -1001,35 +1082,43 @@ var no_unnecessary_use_client_default = ESLintUtils11.RuleCreator(
|
|
|
1001
1082
|
}
|
|
1002
1083
|
},
|
|
1003
1084
|
CallExpression(node) {
|
|
1085
|
+
if (directiveNode === null) return;
|
|
1004
1086
|
markIfHookOrContext(node.callee);
|
|
1005
1087
|
},
|
|
1006
1088
|
JSXAttribute(node) {
|
|
1089
|
+
if (directiveNode === null) return;
|
|
1007
1090
|
if (node.name.type === AST_NODE_TYPES4.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
|
|
1008
1091
|
hasClientIndicator = true;
|
|
1009
1092
|
}
|
|
1010
1093
|
},
|
|
1011
1094
|
ImportDeclaration(node) {
|
|
1095
|
+
if (directiveNode === null) return;
|
|
1012
1096
|
if (typeof node.source.value === "string" && CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)) {
|
|
1013
1097
|
hasClientIndicator = true;
|
|
1014
1098
|
}
|
|
1015
1099
|
},
|
|
1016
1100
|
ExportNamedDeclaration(node) {
|
|
1101
|
+
if (directiveNode === null) return;
|
|
1017
1102
|
if (node.source !== null) {
|
|
1018
1103
|
hasClientIndicator = true;
|
|
1019
1104
|
}
|
|
1020
1105
|
},
|
|
1021
1106
|
ExportAllDeclaration(node) {
|
|
1107
|
+
if (directiveNode === null) return;
|
|
1022
1108
|
if (node.source !== null) {
|
|
1023
1109
|
hasClientIndicator = true;
|
|
1024
1110
|
}
|
|
1025
1111
|
},
|
|
1026
1112
|
ClassDeclaration() {
|
|
1113
|
+
if (directiveNode === null) return;
|
|
1027
1114
|
hasClientIndicator = true;
|
|
1028
1115
|
},
|
|
1029
1116
|
ClassExpression() {
|
|
1117
|
+
if (directiveNode === null) return;
|
|
1030
1118
|
hasClientIndicator = true;
|
|
1031
1119
|
},
|
|
1032
1120
|
Identifier(node) {
|
|
1121
|
+
if (directiveNode === null) return;
|
|
1033
1122
|
if (isGlobalReference(node, context)) {
|
|
1034
1123
|
hasClientIndicator = true;
|
|
1035
1124
|
}
|
|
@@ -1047,7 +1136,7 @@ var no_unnecessary_use_client_default = ESLintUtils11.RuleCreator(
|
|
|
1047
1136
|
});
|
|
1048
1137
|
|
|
1049
1138
|
// src/rules/prefer-discriminated-union.ts
|
|
1050
|
-
import { ESLintUtils as
|
|
1139
|
+
import { ESLintUtils as ESLintUtils13 } from "@typescript-eslint/utils";
|
|
1051
1140
|
import { AST_NODE_TYPES as AST_NODE_TYPES5 } from "@typescript-eslint/utils";
|
|
1052
1141
|
var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
|
|
1053
1142
|
"success",
|
|
@@ -1090,7 +1179,7 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
|
|
|
1090
1179
|
}
|
|
1091
1180
|
return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
|
|
1092
1181
|
}
|
|
1093
|
-
var prefer_discriminated_union_default =
|
|
1182
|
+
var prefer_discriminated_union_default = ESLintUtils13.RuleCreator(
|
|
1094
1183
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
1095
1184
|
)({
|
|
1096
1185
|
name: "prefer-discriminated-union",
|
|
@@ -1133,7 +1222,7 @@ var prefer_discriminated_union_default = ESLintUtils12.RuleCreator(
|
|
|
1133
1222
|
// src/rules/prefer-schema-for-api-payload.ts
|
|
1134
1223
|
import {
|
|
1135
1224
|
AST_NODE_TYPES as AST_NODE_TYPES6,
|
|
1136
|
-
ESLintUtils as
|
|
1225
|
+
ESLintUtils as ESLintUtils14
|
|
1137
1226
|
} from "@typescript-eslint/utils";
|
|
1138
1227
|
var unwrap = (node) => {
|
|
1139
1228
|
let current = node;
|
|
@@ -1181,7 +1270,7 @@ var isUnvalidatedVariableRef = (node, scope, tracked) => {
|
|
|
1181
1270
|
const variable = findVariable2(scope, unwrapped.name);
|
|
1182
1271
|
return variable !== null && tracked.has(variable);
|
|
1183
1272
|
};
|
|
1184
|
-
var prefer_schema_for_api_payload_default =
|
|
1273
|
+
var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
|
|
1185
1274
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
1186
1275
|
)({
|
|
1187
1276
|
name: "prefer-schema-for-api-payload",
|
|
@@ -1274,8 +1363,153 @@ var prefer_schema_for_api_payload_default = ESLintUtils13.RuleCreator(
|
|
|
1274
1363
|
}
|
|
1275
1364
|
});
|
|
1276
1365
|
|
|
1366
|
+
// src/rules/prefer-semantic-colors.ts
|
|
1367
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES7, ESLintUtils as ESLintUtils15 } from "@typescript-eslint/utils";
|
|
1368
|
+
|
|
1369
|
+
// src/rules/_tailwind.ts
|
|
1370
|
+
var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^!/, "");
|
|
1371
|
+
var classTokens = (value) => value.split(/\s+/).filter(Boolean);
|
|
1372
|
+
|
|
1373
|
+
// src/rules/prefer-semantic-colors.ts
|
|
1374
|
+
var COLOR_PREFIXES = "text|bg|border(?:-[trblxyse])?|ring(?:-offset)?|fill|stroke|from|via|to|divide|decoration|placeholder|accent|caret|shadow|outline";
|
|
1375
|
+
var PALETTE = "red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone";
|
|
1376
|
+
var COLOR_FN = "rgba?|hsla?|hwb|oklch|oklab|lab|lch|color";
|
|
1377
|
+
var RAW_PALETTE_RE = new RegExp(`^(?:${COLOR_PREFIXES})-(?:${PALETTE})-\\d{2,3}(?:/\\d{1,3})?$`);
|
|
1378
|
+
var ARBITRARY_COLOR_RE = new RegExp(
|
|
1379
|
+
`^(?:${COLOR_PREFIXES})-\\[(?:#[0-9a-fA-F]{3,8}|(?:${COLOR_FN})\\([^\\]]*\\))\\]$`,
|
|
1380
|
+
"i"
|
|
1381
|
+
);
|
|
1382
|
+
var CLASS_FNS = /* @__PURE__ */ new Set(["cn", "clsx", "cva", "tv", "cx", "twMerge", "classnames", "classNames"]);
|
|
1383
|
+
var CLASS_NAME_RE = /class/i;
|
|
1384
|
+
var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
|
|
1385
|
+
"color",
|
|
1386
|
+
"background",
|
|
1387
|
+
"backgroundColor",
|
|
1388
|
+
"borderColor",
|
|
1389
|
+
"borderTopColor",
|
|
1390
|
+
"borderRightColor",
|
|
1391
|
+
"borderBottomColor",
|
|
1392
|
+
"borderLeftColor",
|
|
1393
|
+
"outlineColor",
|
|
1394
|
+
"caretColor",
|
|
1395
|
+
"textDecorationColor",
|
|
1396
|
+
"columnRuleColor",
|
|
1397
|
+
"fill",
|
|
1398
|
+
"stroke",
|
|
1399
|
+
"stopColor",
|
|
1400
|
+
"floodColor",
|
|
1401
|
+
"lightingColor"
|
|
1402
|
+
]);
|
|
1403
|
+
var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
|
|
1404
|
+
var propName = (key) => {
|
|
1405
|
+
if (key.type === AST_NODE_TYPES7.Identifier) return key.name;
|
|
1406
|
+
if (key.type === AST_NODE_TYPES7.Literal && typeof key.value === "string") return key.value;
|
|
1407
|
+
return null;
|
|
1408
|
+
};
|
|
1409
|
+
var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
|
|
1410
|
+
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
1411
|
+
)({
|
|
1412
|
+
name: "prefer-semantic-colors",
|
|
1413
|
+
meta: {
|
|
1414
|
+
type: "suggestion",
|
|
1415
|
+
docs: {
|
|
1416
|
+
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."
|
|
1417
|
+
},
|
|
1418
|
+
schema: [],
|
|
1419
|
+
messages: {
|
|
1420
|
+
rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
|
|
1421
|
+
arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
|
|
1422
|
+
inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
|
|
1423
|
+
}
|
|
1424
|
+
},
|
|
1425
|
+
defaultOptions: [],
|
|
1426
|
+
create(context) {
|
|
1427
|
+
const reportClasses = (value, node) => {
|
|
1428
|
+
for (const token of classTokens(value)) {
|
|
1429
|
+
const base = tailwindBase(token);
|
|
1430
|
+
if (RAW_PALETTE_RE.test(base)) {
|
|
1431
|
+
context.report({ node, messageId: "rawPalette", data: { class: token } });
|
|
1432
|
+
} else if (ARBITRARY_COLOR_RE.test(base)) {
|
|
1433
|
+
context.report({ node, messageId: "arbitraryColor", data: { class: token } });
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
const checkClassNode = (node) => {
|
|
1438
|
+
if (node === null) return;
|
|
1439
|
+
switch (node.type) {
|
|
1440
|
+
case AST_NODE_TYPES7.Literal:
|
|
1441
|
+
if (typeof node.value === "string") reportClasses(node.value, node);
|
|
1442
|
+
break;
|
|
1443
|
+
case AST_NODE_TYPES7.TemplateLiteral:
|
|
1444
|
+
for (const quasi of node.quasis) reportClasses(quasi.value.cooked ?? "", quasi);
|
|
1445
|
+
break;
|
|
1446
|
+
case AST_NODE_TYPES7.ArrayExpression:
|
|
1447
|
+
for (const element of node.elements) {
|
|
1448
|
+
if (element !== null && element.type !== AST_NODE_TYPES7.SpreadElement) checkClassNode(element);
|
|
1449
|
+
}
|
|
1450
|
+
break;
|
|
1451
|
+
case AST_NODE_TYPES7.ObjectExpression:
|
|
1452
|
+
for (const property of node.properties) {
|
|
1453
|
+
if (property.type === AST_NODE_TYPES7.Property) checkClassNode(property.value);
|
|
1454
|
+
}
|
|
1455
|
+
break;
|
|
1456
|
+
case AST_NODE_TYPES7.ConditionalExpression:
|
|
1457
|
+
checkClassNode(node.consequent);
|
|
1458
|
+
checkClassNode(node.alternate);
|
|
1459
|
+
break;
|
|
1460
|
+
case AST_NODE_TYPES7.LogicalExpression:
|
|
1461
|
+
checkClassNode(node.right);
|
|
1462
|
+
break;
|
|
1463
|
+
default:
|
|
1464
|
+
break;
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
const checkColorValueNode = (node) => {
|
|
1468
|
+
if (node.type === AST_NODE_TYPES7.Literal && typeof node.value === "string" && RAW_COLOR_VALUE_RE.test(node.value)) {
|
|
1469
|
+
context.report({ node, messageId: "inlineColor", data: { value: node.value } });
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1472
|
+
return {
|
|
1473
|
+
"JSXAttribute[name.name='className']"(node) {
|
|
1474
|
+
if (node.value === null) return;
|
|
1475
|
+
if (node.value.type === AST_NODE_TYPES7.Literal) checkClassNode(node.value);
|
|
1476
|
+
else if (node.value.type === AST_NODE_TYPES7.JSXExpressionContainer) {
|
|
1477
|
+
if (node.value.expression.type !== AST_NODE_TYPES7.JSXEmptyExpression) {
|
|
1478
|
+
checkClassNode(node.value.expression);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
},
|
|
1482
|
+
CallExpression(node) {
|
|
1483
|
+
if (node.callee.type === AST_NODE_TYPES7.Identifier && CLASS_FNS.has(node.callee.name)) {
|
|
1484
|
+
for (const arg of node.arguments) {
|
|
1485
|
+
if (arg.type !== AST_NODE_TYPES7.SpreadElement) checkClassNode(arg);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
},
|
|
1489
|
+
VariableDeclarator(node) {
|
|
1490
|
+
if (node.id.type === AST_NODE_TYPES7.Identifier && CLASS_NAME_RE.test(node.id.name)) {
|
|
1491
|
+
checkClassNode(node.init);
|
|
1492
|
+
}
|
|
1493
|
+
},
|
|
1494
|
+
Property(node) {
|
|
1495
|
+
const name = propName(node.key);
|
|
1496
|
+
if (name !== null && CLASS_NAME_RE.test(name)) checkClassNode(node.value);
|
|
1497
|
+
},
|
|
1498
|
+
// SVG presentation attributes: <path fill="#000" stroke="#fff" />
|
|
1499
|
+
"JSXAttribute[name.name=/^(fill|stroke|color)$/]"(node) {
|
|
1500
|
+
if (node.value?.type === AST_NODE_TYPES7.Literal) checkColorValueNode(node.value);
|
|
1501
|
+
},
|
|
1502
|
+
// Inline style objects: style={{ color: "#111827", backgroundColor: "#fff" }}
|
|
1503
|
+
"JSXAttribute[name.name='style'] ObjectExpression > Property"(node) {
|
|
1504
|
+
const name = propName(node.key);
|
|
1505
|
+
if (name !== null && STYLE_COLOR_PROPS.has(name)) checkColorValueNode(node.value);
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1277
1511
|
// src/rules/prefer-server-actions.ts
|
|
1278
|
-
import { ESLintUtils as
|
|
1512
|
+
import { ESLintUtils as ESLintUtils16 } from "@typescript-eslint/utils";
|
|
1279
1513
|
var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
1280
1514
|
var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
|
|
1281
1515
|
var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
|
|
@@ -1335,17 +1569,17 @@ function isMutationMethod(node, context) {
|
|
|
1335
1569
|
}
|
|
1336
1570
|
return false;
|
|
1337
1571
|
}
|
|
1338
|
-
function getPropertyNode(objNode,
|
|
1572
|
+
function getPropertyNode(objNode, propName2) {
|
|
1339
1573
|
if (!objNode || objNode.type !== "ObjectExpression") return null;
|
|
1340
1574
|
for (const prop of objNode.properties) {
|
|
1341
1575
|
if (prop.type !== "Property") continue;
|
|
1342
|
-
let
|
|
1576
|
+
let keyName2 = null;
|
|
1343
1577
|
if (prop.key.type === "Identifier" && !prop.computed) {
|
|
1344
|
-
|
|
1578
|
+
keyName2 = prop.key.name;
|
|
1345
1579
|
} else if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
|
|
1346
|
-
|
|
1580
|
+
keyName2 = prop.key.value;
|
|
1347
1581
|
}
|
|
1348
|
-
if (
|
|
1582
|
+
if (keyName2 === propName2) {
|
|
1349
1583
|
if (prop.value.type === "AssignmentPattern" || prop.value.type === "ArrayPattern" || prop.value.type === "ObjectPattern") {
|
|
1350
1584
|
return null;
|
|
1351
1585
|
}
|
|
@@ -1354,7 +1588,7 @@ function getPropertyNode(objNode, propName) {
|
|
|
1354
1588
|
}
|
|
1355
1589
|
return null;
|
|
1356
1590
|
}
|
|
1357
|
-
var prefer_server_actions_default =
|
|
1591
|
+
var prefer_server_actions_default = ESLintUtils16.RuleCreator(
|
|
1358
1592
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
1359
1593
|
)({
|
|
1360
1594
|
name: "prefer-server-actions",
|
|
@@ -1393,7 +1627,10 @@ var prefer_server_actions_default = ESLintUtils14.RuleCreator(
|
|
|
1393
1627
|
const methodName = node.callee.property.name.toLowerCase();
|
|
1394
1628
|
if (AXIOS_MUTATION_METHODS.has(methodName)) {
|
|
1395
1629
|
const urlArg = node.arguments[0];
|
|
1396
|
-
|
|
1630
|
+
const hasHandlerArg = node.arguments.some(
|
|
1631
|
+
(arg) => arg.type === "ArrowFunctionExpression" || arg.type === "FunctionExpression"
|
|
1632
|
+
);
|
|
1633
|
+
if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context)) {
|
|
1397
1634
|
isMutation = true;
|
|
1398
1635
|
}
|
|
1399
1636
|
}
|
|
@@ -1419,14 +1656,14 @@ var prefer_server_actions_default = ESLintUtils14.RuleCreator(
|
|
|
1419
1656
|
});
|
|
1420
1657
|
|
|
1421
1658
|
// src/rules/prefer-shadcn.ts
|
|
1422
|
-
import { ESLintUtils as
|
|
1659
|
+
import { ESLintUtils as ESLintUtils17 } from "@typescript-eslint/utils";
|
|
1423
1660
|
var REPLACEMENTS = {
|
|
1424
1661
|
input: "Input",
|
|
1425
1662
|
select: "Select",
|
|
1426
1663
|
textarea: "Textarea",
|
|
1427
1664
|
dialog: "Dialog"
|
|
1428
1665
|
};
|
|
1429
|
-
var prefer_shadcn_default =
|
|
1666
|
+
var prefer_shadcn_default = ESLintUtils17.RuleCreator(
|
|
1430
1667
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
1431
1668
|
)({
|
|
1432
1669
|
name: "prefer-shadcn",
|
|
@@ -1467,36 +1704,52 @@ var prefer_shadcn_default = ESLintUtils15.RuleCreator(
|
|
|
1467
1704
|
});
|
|
1468
1705
|
|
|
1469
1706
|
// src/rules/require-assert-never.ts
|
|
1470
|
-
import { ESLintUtils as
|
|
1707
|
+
import { ESLintUtils as ESLintUtils18, AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
|
|
1471
1708
|
var isAssertNeverCall = (expression) => {
|
|
1472
|
-
if (expression.type !==
|
|
1709
|
+
if (expression.type !== AST_NODE_TYPES8.CallExpression) return false;
|
|
1473
1710
|
const callee = expression.callee;
|
|
1474
|
-
|
|
1711
|
+
if (callee.type === AST_NODE_TYPES8.Identifier) {
|
|
1712
|
+
return callee.name === "assertNever";
|
|
1713
|
+
}
|
|
1714
|
+
if (callee.type === AST_NODE_TYPES8.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES8.Identifier) {
|
|
1715
|
+
return callee.property.name === "assertNever";
|
|
1716
|
+
}
|
|
1717
|
+
return false;
|
|
1475
1718
|
};
|
|
1476
1719
|
var statementContainsAssertNever = (statement) => {
|
|
1477
|
-
if (statement.type ===
|
|
1720
|
+
if (statement.type === AST_NODE_TYPES8.ExpressionStatement) {
|
|
1478
1721
|
return isAssertNeverCall(statement.expression);
|
|
1479
1722
|
}
|
|
1480
|
-
if (statement.type ===
|
|
1723
|
+
if (statement.type === AST_NODE_TYPES8.ThrowStatement) {
|
|
1481
1724
|
return isAssertNeverCall(statement.argument);
|
|
1482
1725
|
}
|
|
1483
|
-
if (statement.type ===
|
|
1726
|
+
if (statement.type === AST_NODE_TYPES8.ReturnStatement) {
|
|
1727
|
+
return statement.argument !== null && isAssertNeverCall(statement.argument);
|
|
1728
|
+
}
|
|
1729
|
+
if (statement.type === AST_NODE_TYPES8.BlockStatement) {
|
|
1484
1730
|
return statement.body.some(statementContainsAssertNever);
|
|
1485
1731
|
}
|
|
1486
1732
|
return false;
|
|
1487
1733
|
};
|
|
1488
|
-
var
|
|
1734
|
+
var isRuntimeHandlingStatement = (statement) => {
|
|
1735
|
+
if (statement.type === AST_NODE_TYPES8.EmptyStatement) return false;
|
|
1736
|
+
if (statement.type === AST_NODE_TYPES8.BlockStatement) {
|
|
1737
|
+
return statement.body.some(isRuntimeHandlingStatement);
|
|
1738
|
+
}
|
|
1739
|
+
return true;
|
|
1740
|
+
};
|
|
1741
|
+
var require_assert_never_default = ESLintUtils18.RuleCreator(
|
|
1489
1742
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
1490
1743
|
)({
|
|
1491
1744
|
name: "require-assert-never",
|
|
1492
1745
|
meta: {
|
|
1493
1746
|
type: "problem",
|
|
1494
1747
|
docs: {
|
|
1495
|
-
description: "Require switch
|
|
1748
|
+
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."
|
|
1496
1749
|
},
|
|
1497
1750
|
schema: [],
|
|
1498
1751
|
messages: {
|
|
1499
|
-
missingAssertNever: "
|
|
1752
|
+
missingAssertNever: "Empty switch `default` case \u2014 add runtime handling or call `assertNever()` so the discriminated union is exhaustively checked at compile time."
|
|
1500
1753
|
}
|
|
1501
1754
|
},
|
|
1502
1755
|
defaultOptions: [],
|
|
@@ -1507,10 +1760,8 @@ var require_assert_never_default = ESLintUtils16.RuleCreator(
|
|
|
1507
1760
|
(caseNode) => caseNode.test === null
|
|
1508
1761
|
);
|
|
1509
1762
|
if (!defaultCase) return;
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
);
|
|
1513
|
-
if (hasAssertNever) return;
|
|
1763
|
+
if (defaultCase.consequent.some(statementContainsAssertNever)) return;
|
|
1764
|
+
if (defaultCase.consequent.some(isRuntimeHandlingStatement)) return;
|
|
1514
1765
|
context.report({
|
|
1515
1766
|
node: defaultCase,
|
|
1516
1767
|
messageId: "missingAssertNever"
|
|
@@ -1521,43 +1772,91 @@ var require_assert_never_default = ESLintUtils16.RuleCreator(
|
|
|
1521
1772
|
});
|
|
1522
1773
|
|
|
1523
1774
|
// src/rules/require-zod-form-validation.ts
|
|
1524
|
-
import { ESLintUtils as
|
|
1525
|
-
var
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1775
|
+
import { ESLintUtils as ESLintUtils19, AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
|
|
1776
|
+
var ZOD_SCHEMA_NAME_RE = /Schema$|^Z[A-Z]/;
|
|
1777
|
+
var looksLikeZodSchema = (node) => {
|
|
1778
|
+
let current = node;
|
|
1779
|
+
while (true) {
|
|
1780
|
+
if (current.type === AST_NODE_TYPES9.Identifier) {
|
|
1781
|
+
return current.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
|
|
1782
|
+
}
|
|
1783
|
+
if (current.type === AST_NODE_TYPES9.CallExpression) {
|
|
1784
|
+
current = current.callee;
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
if (current.type === AST_NODE_TYPES9.MemberExpression) {
|
|
1788
|
+
current = current.object;
|
|
1789
|
+
continue;
|
|
1790
|
+
}
|
|
1529
1791
|
return false;
|
|
1530
1792
|
}
|
|
1531
|
-
return callee.object.type === AST_NODE_TYPES8.Identifier && callee.object.name === "formData";
|
|
1532
1793
|
};
|
|
1533
|
-
var
|
|
1534
|
-
if (node.type !==
|
|
1794
|
+
var isZodParseCall = (node) => {
|
|
1795
|
+
if (node.type !== AST_NODE_TYPES9.CallExpression) return false;
|
|
1535
1796
|
const callee = node.callee;
|
|
1536
|
-
if (callee.type !==
|
|
1537
|
-
|
|
1797
|
+
if (callee.type !== AST_NODE_TYPES9.MemberExpression) return false;
|
|
1798
|
+
if (callee.computed) return false;
|
|
1799
|
+
if (callee.property.type !== AST_NODE_TYPES9.Identifier) return false;
|
|
1800
|
+
const method = callee.property.name;
|
|
1801
|
+
if (method !== "parse" && method !== "safeParse") return false;
|
|
1802
|
+
return looksLikeZodSchema(callee.object);
|
|
1538
1803
|
};
|
|
1539
|
-
var
|
|
1804
|
+
var isFormDataMethodCall = (node) => {
|
|
1805
|
+
let current = node;
|
|
1806
|
+
if (current.type === AST_NODE_TYPES9.AwaitExpression) {
|
|
1807
|
+
current = current.argument;
|
|
1808
|
+
}
|
|
1809
|
+
if (current.type !== AST_NODE_TYPES9.CallExpression) return false;
|
|
1810
|
+
const callee = current.callee;
|
|
1811
|
+
return callee.type === AST_NODE_TYPES9.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES9.Identifier && callee.property.name === "formData";
|
|
1812
|
+
};
|
|
1813
|
+
var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
|
|
1540
1814
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
1541
1815
|
)({
|
|
1542
1816
|
name: "require-zod-form-validation",
|
|
1543
1817
|
meta: {
|
|
1544
1818
|
type: "problem",
|
|
1545
1819
|
docs: {
|
|
1546
|
-
description: "Require Zod validation (`Schema.parse(...)`) when reading values out of a `FormData` object."
|
|
1820
|
+
description: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object."
|
|
1547
1821
|
},
|
|
1548
1822
|
schema: [],
|
|
1549
1823
|
messages: {
|
|
1550
|
-
missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse())"
|
|
1824
|
+
missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse() / Schema.safeParse())"
|
|
1551
1825
|
}
|
|
1552
1826
|
},
|
|
1553
1827
|
defaultOptions: [],
|
|
1554
1828
|
create(context) {
|
|
1829
|
+
const isFormSourceIdentifier = (node) => {
|
|
1830
|
+
if (node.type !== AST_NODE_TYPES9.Identifier) return false;
|
|
1831
|
+
if (/formdata/i.test(node.name)) return true;
|
|
1832
|
+
let scope = context.sourceCode.getScope(node);
|
|
1833
|
+
while (scope !== null) {
|
|
1834
|
+
const variable = scope.set.get(node.name);
|
|
1835
|
+
if (variable !== void 0 && variable.defs.length === 1) {
|
|
1836
|
+
const def = variable.defs[0];
|
|
1837
|
+
if (def !== void 0 && def.type === "Variable" && def.node.type === AST_NODE_TYPES9.VariableDeclarator && def.node.init !== null) {
|
|
1838
|
+
return isFormDataMethodCall(def.node.init);
|
|
1839
|
+
}
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1842
|
+
scope = scope.upper;
|
|
1843
|
+
}
|
|
1844
|
+
return false;
|
|
1845
|
+
};
|
|
1846
|
+
const isFormDataGetCall = (node) => {
|
|
1847
|
+
const callee = node.callee;
|
|
1848
|
+
if (callee.type !== AST_NODE_TYPES9.MemberExpression) return false;
|
|
1849
|
+
if (callee.property.type !== AST_NODE_TYPES9.Identifier || callee.property.name !== "get") {
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
return isFormSourceIdentifier(callee.object);
|
|
1853
|
+
};
|
|
1555
1854
|
return {
|
|
1556
1855
|
CallExpression(node) {
|
|
1557
1856
|
if (!isFormDataGetCall(node)) return;
|
|
1558
1857
|
let parent = node.parent;
|
|
1559
1858
|
while (parent !== null && parent !== void 0) {
|
|
1560
|
-
if (
|
|
1859
|
+
if (isZodParseCall(parent)) return;
|
|
1561
1860
|
parent = parent.parent;
|
|
1562
1861
|
}
|
|
1563
1862
|
context.report({
|
|
@@ -1570,15 +1869,15 @@ var require_zod_form_validation_default = ESLintUtils17.RuleCreator(
|
|
|
1570
1869
|
});
|
|
1571
1870
|
|
|
1572
1871
|
// src/rules/zod-naming-convention.ts
|
|
1573
|
-
import { ESLintUtils as
|
|
1872
|
+
import { ESLintUtils as ESLintUtils20, AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
|
|
1574
1873
|
var calleeChainStartsWithZ = (node) => {
|
|
1575
1874
|
let current = node;
|
|
1576
|
-
while (current.type ===
|
|
1875
|
+
while (current.type === AST_NODE_TYPES10.MemberExpression) {
|
|
1577
1876
|
const receiver = current.object;
|
|
1578
|
-
if (receiver.type ===
|
|
1877
|
+
if (receiver.type === AST_NODE_TYPES10.Identifier && receiver.name === "z") {
|
|
1579
1878
|
return true;
|
|
1580
1879
|
}
|
|
1581
|
-
if (receiver.type ===
|
|
1880
|
+
if (receiver.type === AST_NODE_TYPES10.CallExpression) {
|
|
1582
1881
|
current = receiver.callee;
|
|
1583
1882
|
continue;
|
|
1584
1883
|
}
|
|
@@ -1586,7 +1885,7 @@ var calleeChainStartsWithZ = (node) => {
|
|
|
1586
1885
|
}
|
|
1587
1886
|
return false;
|
|
1588
1887
|
};
|
|
1589
|
-
var zod_naming_convention_default =
|
|
1888
|
+
var zod_naming_convention_default = ESLintUtils20.RuleCreator(
|
|
1590
1889
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
1591
1890
|
)({
|
|
1592
1891
|
name: "zod-naming-convention",
|
|
@@ -1606,11 +1905,11 @@ var zod_naming_convention_default = ESLintUtils18.RuleCreator(
|
|
|
1606
1905
|
VariableDeclarator(node) {
|
|
1607
1906
|
const init = node.init;
|
|
1608
1907
|
if (init === null || init === void 0) return;
|
|
1609
|
-
if (init.type !==
|
|
1908
|
+
if (init.type !== AST_NODE_TYPES10.CallExpression) return;
|
|
1610
1909
|
const callee = init.callee;
|
|
1611
|
-
if (callee.type !==
|
|
1910
|
+
if (callee.type !== AST_NODE_TYPES10.MemberExpression) return;
|
|
1612
1911
|
if (!calleeChainStartsWithZ(callee)) return;
|
|
1613
|
-
if (node.id.type !==
|
|
1912
|
+
if (node.id.type !== AST_NODE_TYPES10.Identifier) return;
|
|
1614
1913
|
const variableName = node.id.name;
|
|
1615
1914
|
if (variableName.startsWith("Z")) return;
|
|
1616
1915
|
context.report({
|
|
@@ -1622,10 +1921,1141 @@ var zod_naming_convention_default = ESLintUtils18.RuleCreator(
|
|
|
1622
1921
|
}
|
|
1623
1922
|
});
|
|
1624
1923
|
|
|
1924
|
+
// src/rules/no-cors-wildcard-with-credentials.ts
|
|
1925
|
+
import { ESLintUtils as ESLintUtils21 } from "@typescript-eslint/utils";
|
|
1926
|
+
var ACAO_HEADER = "access-control-allow-origin";
|
|
1927
|
+
var ACAC_HEADER = "access-control-allow-credentials";
|
|
1928
|
+
var HEADER_SET_METHODS = /* @__PURE__ */ new Set(["setheader", "set", "append"]);
|
|
1929
|
+
function isTrueLiteral(node) {
|
|
1930
|
+
return node.type === "Literal" && node.value === true;
|
|
1931
|
+
}
|
|
1932
|
+
function isCredentialsTrueValue(node) {
|
|
1933
|
+
if (node.type === "Literal") {
|
|
1934
|
+
if (node.value === true) {
|
|
1935
|
+
return true;
|
|
1936
|
+
}
|
|
1937
|
+
if (typeof node.value === "string") {
|
|
1938
|
+
return node.value.trim().toLowerCase() === "true";
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
return false;
|
|
1942
|
+
}
|
|
1943
|
+
function isStarLiteral(node) {
|
|
1944
|
+
return node.type === "Literal" && node.value === "*";
|
|
1945
|
+
}
|
|
1946
|
+
function subtreeContainsStarLiteral(node) {
|
|
1947
|
+
if (isStarLiteral(node)) {
|
|
1948
|
+
return true;
|
|
1949
|
+
}
|
|
1950
|
+
for (const key of Object.keys(node)) {
|
|
1951
|
+
if (key === "parent" || key === "loc" || key === "range") {
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
const value = node[key];
|
|
1955
|
+
if (Array.isArray(value)) {
|
|
1956
|
+
for (const child of value) {
|
|
1957
|
+
if (isNode2(child) && subtreeContainsStarLiteral(child)) {
|
|
1958
|
+
return true;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
} else if (isNode2(value) && subtreeContainsStarLiteral(value)) {
|
|
1962
|
+
return true;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return false;
|
|
1966
|
+
}
|
|
1967
|
+
function isNode2(value) {
|
|
1968
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
1969
|
+
}
|
|
1970
|
+
function propertyKeyName(prop) {
|
|
1971
|
+
if (prop.computed) {
|
|
1972
|
+
return void 0;
|
|
1973
|
+
}
|
|
1974
|
+
const key = prop.key;
|
|
1975
|
+
if (key.type === "Identifier") {
|
|
1976
|
+
return key.name;
|
|
1977
|
+
}
|
|
1978
|
+
if (key.type === "Literal" && typeof key.value === "string") {
|
|
1979
|
+
return key.value;
|
|
1980
|
+
}
|
|
1981
|
+
return void 0;
|
|
1982
|
+
}
|
|
1983
|
+
function calleeName(node) {
|
|
1984
|
+
const callee = node.callee;
|
|
1985
|
+
if (callee.type === "Identifier") {
|
|
1986
|
+
return callee.name;
|
|
1987
|
+
}
|
|
1988
|
+
if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
|
|
1989
|
+
return callee.property.name;
|
|
1990
|
+
}
|
|
1991
|
+
return void 0;
|
|
1992
|
+
}
|
|
1993
|
+
function isCorsWildcardCredentialsCall(node) {
|
|
1994
|
+
const name = calleeName(node);
|
|
1995
|
+
if (name === void 0 || name.toLowerCase() !== "cors") {
|
|
1996
|
+
return false;
|
|
1997
|
+
}
|
|
1998
|
+
const options = node.arguments.find(
|
|
1999
|
+
(arg) => arg.type === "ObjectExpression"
|
|
2000
|
+
);
|
|
2001
|
+
if (options === void 0) {
|
|
2002
|
+
return false;
|
|
2003
|
+
}
|
|
2004
|
+
let hasCredentials = false;
|
|
2005
|
+
let hasWildcardOrigin = false;
|
|
2006
|
+
for (const prop of options.properties) {
|
|
2007
|
+
if (prop.type !== "Property") {
|
|
2008
|
+
continue;
|
|
2009
|
+
}
|
|
2010
|
+
const key = propertyKeyName(prop);
|
|
2011
|
+
if (key === "credentials" && isTrueLiteral(prop.value)) {
|
|
2012
|
+
hasCredentials = true;
|
|
2013
|
+
} else if (key === "origin" && subtreeContainsStarLiteral(prop.value)) {
|
|
2014
|
+
hasWildcardOrigin = true;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
return hasCredentials && hasWildcardOrigin;
|
|
2018
|
+
}
|
|
2019
|
+
function isWildcardCredentialsHeaderObject(node) {
|
|
2020
|
+
let wildcardOrigin = false;
|
|
2021
|
+
let credentialsTrue = false;
|
|
2022
|
+
for (const prop of node.properties) {
|
|
2023
|
+
if (prop.type !== "Property") {
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
const key = propertyKeyName(prop);
|
|
2027
|
+
if (key === void 0) {
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
const header = key.toLowerCase();
|
|
2031
|
+
if (header === ACAO_HEADER && isStarLiteral(prop.value)) {
|
|
2032
|
+
wildcardOrigin = true;
|
|
2033
|
+
} else if (header === ACAC_HEADER && isCredentialsTrueValue(prop.value)) {
|
|
2034
|
+
credentialsTrue = true;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return wildcardOrigin && credentialsTrue;
|
|
2038
|
+
}
|
|
2039
|
+
function classifyHeaderSetCall(node) {
|
|
2040
|
+
const callee = node.callee;
|
|
2041
|
+
if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !HEADER_SET_METHODS.has(callee.property.name.toLowerCase())) {
|
|
2042
|
+
return void 0;
|
|
2043
|
+
}
|
|
2044
|
+
const [nameArg, valueArg] = node.arguments;
|
|
2045
|
+
if (nameArg === void 0 || valueArg === void 0 || nameArg.type !== "Literal" || typeof nameArg.value !== "string") {
|
|
2046
|
+
return void 0;
|
|
2047
|
+
}
|
|
2048
|
+
const header = nameArg.value.toLowerCase();
|
|
2049
|
+
if (header === ACAO_HEADER && isStarLiteral(valueArg)) {
|
|
2050
|
+
return "origin";
|
|
2051
|
+
}
|
|
2052
|
+
if (header === ACAC_HEADER && isCredentialsTrueValue(valueArg)) {
|
|
2053
|
+
return "credentials";
|
|
2054
|
+
}
|
|
2055
|
+
return void 0;
|
|
2056
|
+
}
|
|
2057
|
+
function enclosingScope(node) {
|
|
2058
|
+
let current = node.parent;
|
|
2059
|
+
while (current) {
|
|
2060
|
+
if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
|
|
2061
|
+
return current;
|
|
2062
|
+
}
|
|
2063
|
+
current = current.parent;
|
|
2064
|
+
}
|
|
2065
|
+
return void 0;
|
|
2066
|
+
}
|
|
2067
|
+
var no_cors_wildcard_with_credentials_default = ESLintUtils21.RuleCreator(
|
|
2068
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2069
|
+
)({
|
|
2070
|
+
name: "no-cors-wildcard-with-credentials",
|
|
2071
|
+
meta: {
|
|
2072
|
+
type: "problem",
|
|
2073
|
+
docs: {
|
|
2074
|
+
description: 'Disallow CORS that reflects any Origin (`"*"`) while allowing credentials; any site could then read authenticated responses. Enumerate explicit trusted origins instead.'
|
|
2075
|
+
},
|
|
2076
|
+
schema: [],
|
|
2077
|
+
messages: {
|
|
2078
|
+
corsWildcardWithCredentials: 'CORS reflects any Origin (`"*"`) while allowing credentials \u2014 any site can read authenticated responses. Enumerate explicit trusted origins instead of using `"*"` with credentials.'
|
|
2079
|
+
}
|
|
2080
|
+
},
|
|
2081
|
+
defaultOptions: [],
|
|
2082
|
+
create(context) {
|
|
2083
|
+
const scopeHeaderSets = /* @__PURE__ */ new Map();
|
|
2084
|
+
function recordHeaderSet(node, kind) {
|
|
2085
|
+
const key = enclosingScope(node) ?? "module";
|
|
2086
|
+
let entry = scopeHeaderSets.get(key);
|
|
2087
|
+
if (entry === void 0) {
|
|
2088
|
+
entry = { originNodes: [], credentialsNodes: [] };
|
|
2089
|
+
scopeHeaderSets.set(key, entry);
|
|
2090
|
+
}
|
|
2091
|
+
if (kind === "origin") {
|
|
2092
|
+
entry.originNodes.push(node);
|
|
2093
|
+
} else {
|
|
2094
|
+
entry.credentialsNodes.push(node);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
return {
|
|
2098
|
+
NewExpression(node) {
|
|
2099
|
+
if (isCorsWildcardCredentialsCall(node)) {
|
|
2100
|
+
context.report({ node, messageId: "corsWildcardWithCredentials" });
|
|
2101
|
+
}
|
|
2102
|
+
},
|
|
2103
|
+
CallExpression(node) {
|
|
2104
|
+
if (isCorsWildcardCredentialsCall(node)) {
|
|
2105
|
+
context.report({ node, messageId: "corsWildcardWithCredentials" });
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
const kind = classifyHeaderSetCall(node);
|
|
2109
|
+
if (kind !== void 0) {
|
|
2110
|
+
recordHeaderSet(node, kind);
|
|
2111
|
+
}
|
|
2112
|
+
},
|
|
2113
|
+
ObjectExpression(node) {
|
|
2114
|
+
if (isWildcardCredentialsHeaderObject(node)) {
|
|
2115
|
+
context.report({ node, messageId: "corsWildcardWithCredentials" });
|
|
2116
|
+
}
|
|
2117
|
+
},
|
|
2118
|
+
"Program:exit"() {
|
|
2119
|
+
for (const { originNodes, credentialsNodes } of scopeHeaderSets.values()) {
|
|
2120
|
+
if (originNodes.length > 0 && credentialsNodes.length > 0) {
|
|
2121
|
+
for (const node of originNodes) {
|
|
2122
|
+
context.report({
|
|
2123
|
+
node,
|
|
2124
|
+
messageId: "corsWildcardWithCredentials"
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2134
|
+
// src/rules/no-fat-try-blocks.ts
|
|
2135
|
+
import {
|
|
2136
|
+
ESLintUtils as ESLintUtils22,
|
|
2137
|
+
AST_NODE_TYPES as AST_NODE_TYPES11
|
|
2138
|
+
} from "@typescript-eslint/utils";
|
|
2139
|
+
var MAX_TRY_BODY_STATEMENTS = 3;
|
|
2140
|
+
var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
2141
|
+
AST_NODE_TYPES11.FunctionDeclaration,
|
|
2142
|
+
AST_NODE_TYPES11.FunctionExpression,
|
|
2143
|
+
AST_NODE_TYPES11.ArrowFunctionExpression
|
|
2144
|
+
]);
|
|
2145
|
+
var PURE_METHODS = /* @__PURE__ */ new Set([
|
|
2146
|
+
"map",
|
|
2147
|
+
"filter",
|
|
2148
|
+
"forEach",
|
|
2149
|
+
"reduce",
|
|
2150
|
+
"reduceRight",
|
|
2151
|
+
"find",
|
|
2152
|
+
"findIndex",
|
|
2153
|
+
"findLast",
|
|
2154
|
+
"findLastIndex",
|
|
2155
|
+
"some",
|
|
2156
|
+
"every",
|
|
2157
|
+
"push",
|
|
2158
|
+
"pop",
|
|
2159
|
+
"shift",
|
|
2160
|
+
"unshift",
|
|
2161
|
+
"slice",
|
|
2162
|
+
"splice",
|
|
2163
|
+
"concat",
|
|
2164
|
+
"flat",
|
|
2165
|
+
"flatMap",
|
|
2166
|
+
"join",
|
|
2167
|
+
"reverse",
|
|
2168
|
+
"sort",
|
|
2169
|
+
"fill",
|
|
2170
|
+
"includes",
|
|
2171
|
+
"indexOf",
|
|
2172
|
+
"lastIndexOf",
|
|
2173
|
+
"at",
|
|
2174
|
+
"keys",
|
|
2175
|
+
"values",
|
|
2176
|
+
"entries",
|
|
2177
|
+
"has",
|
|
2178
|
+
"get",
|
|
2179
|
+
"set",
|
|
2180
|
+
"add",
|
|
2181
|
+
"delete",
|
|
2182
|
+
"clear",
|
|
2183
|
+
"toString",
|
|
2184
|
+
"toLocaleString",
|
|
2185
|
+
"valueOf",
|
|
2186
|
+
"charAt",
|
|
2187
|
+
"charCodeAt",
|
|
2188
|
+
"codePointAt",
|
|
2189
|
+
"split",
|
|
2190
|
+
"padStart",
|
|
2191
|
+
"padEnd",
|
|
2192
|
+
"repeat",
|
|
2193
|
+
"trim",
|
|
2194
|
+
"trimStart",
|
|
2195
|
+
"trimEnd",
|
|
2196
|
+
"toUpperCase",
|
|
2197
|
+
"toLowerCase",
|
|
2198
|
+
"toFixed",
|
|
2199
|
+
"toPrecision",
|
|
2200
|
+
"startsWith",
|
|
2201
|
+
"endsWith"
|
|
2202
|
+
]);
|
|
2203
|
+
var PURE_NAMESPACES = /* @__PURE__ */ new Set([
|
|
2204
|
+
"Object",
|
|
2205
|
+
"Array",
|
|
2206
|
+
"Math",
|
|
2207
|
+
"JSON",
|
|
2208
|
+
"Number",
|
|
2209
|
+
"String",
|
|
2210
|
+
"Boolean",
|
|
2211
|
+
"console"
|
|
2212
|
+
]);
|
|
2213
|
+
var PURE_CONSTRUCTORS = /* @__PURE__ */ new Set([
|
|
2214
|
+
"Map",
|
|
2215
|
+
"Set",
|
|
2216
|
+
"WeakMap",
|
|
2217
|
+
"WeakSet",
|
|
2218
|
+
"Date",
|
|
2219
|
+
"Error",
|
|
2220
|
+
"TypeError",
|
|
2221
|
+
"RangeError",
|
|
2222
|
+
"Array",
|
|
2223
|
+
"Object",
|
|
2224
|
+
"Headers",
|
|
2225
|
+
"URLSearchParams",
|
|
2226
|
+
"FormData"
|
|
2227
|
+
]);
|
|
2228
|
+
function isNode3(value) {
|
|
2229
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
2230
|
+
}
|
|
2231
|
+
function isPureCall(node) {
|
|
2232
|
+
const callee = node.callee;
|
|
2233
|
+
if (callee.type !== AST_NODE_TYPES11.MemberExpression) {
|
|
2234
|
+
return false;
|
|
2235
|
+
}
|
|
2236
|
+
const property = callee.property;
|
|
2237
|
+
if (property.type !== AST_NODE_TYPES11.Identifier) {
|
|
2238
|
+
return false;
|
|
2239
|
+
}
|
|
2240
|
+
if (callee.object.type === AST_NODE_TYPES11.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
|
|
2241
|
+
return true;
|
|
2242
|
+
}
|
|
2243
|
+
return PURE_METHODS.has(property.name);
|
|
2244
|
+
}
|
|
2245
|
+
function isPureNew(node) {
|
|
2246
|
+
return node.callee.type === AST_NODE_TYPES11.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
|
|
2247
|
+
}
|
|
2248
|
+
function subtreeMatches(stmt, predicate) {
|
|
2249
|
+
let found = false;
|
|
2250
|
+
const visit = (current) => {
|
|
2251
|
+
if (found) {
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
if (predicate(current)) {
|
|
2255
|
+
found = true;
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
for (const key of Object.keys(current)) {
|
|
2259
|
+
if (key === "parent") {
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
if (NESTED_FUNCTION_TYPES.has(current.type) && key === "body") {
|
|
2263
|
+
continue;
|
|
2264
|
+
}
|
|
2265
|
+
const value = current[key];
|
|
2266
|
+
if (Array.isArray(value)) {
|
|
2267
|
+
for (const child of value) {
|
|
2268
|
+
if (isNode3(child)) {
|
|
2269
|
+
visit(child);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
} else if (isNode3(value)) {
|
|
2273
|
+
visit(value);
|
|
2274
|
+
}
|
|
2275
|
+
if (found) {
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
};
|
|
2280
|
+
visit(stmt);
|
|
2281
|
+
return found;
|
|
2282
|
+
}
|
|
2283
|
+
var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === AST_NODE_TYPES11.AwaitExpression);
|
|
2284
|
+
var hasThrowingCallOrNew = (stmt) => subtreeMatches(
|
|
2285
|
+
stmt,
|
|
2286
|
+
(n) => n.type === AST_NODE_TYPES11.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES11.NewExpression && !isPureNew(n)
|
|
2287
|
+
);
|
|
2288
|
+
function unwrap2(expr) {
|
|
2289
|
+
let current = expr;
|
|
2290
|
+
while (current.type === AST_NODE_TYPES11.ChainExpression || current.type === AST_NODE_TYPES11.TSNonNullExpression) {
|
|
2291
|
+
current = current.expression;
|
|
2292
|
+
}
|
|
2293
|
+
return current;
|
|
2294
|
+
}
|
|
2295
|
+
function canThrow(stmt) {
|
|
2296
|
+
if (hasAwait(stmt)) {
|
|
2297
|
+
return true;
|
|
2298
|
+
}
|
|
2299
|
+
if (stmt.type === AST_NODE_TYPES11.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES11.CallExpression) {
|
|
2300
|
+
return false;
|
|
2301
|
+
}
|
|
2302
|
+
return hasThrowingCallOrNew(stmt);
|
|
2303
|
+
}
|
|
2304
|
+
function handlerRethrows(handler) {
|
|
2305
|
+
if (handler === null) {
|
|
2306
|
+
return false;
|
|
2307
|
+
}
|
|
2308
|
+
const body = handler.body.body;
|
|
2309
|
+
const last = body[body.length - 1];
|
|
2310
|
+
return last !== void 0 && last.type === AST_NODE_TYPES11.ThrowStatement;
|
|
2311
|
+
}
|
|
2312
|
+
var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
|
|
2313
|
+
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2314
|
+
)({
|
|
2315
|
+
name: "no-fat-try-blocks",
|
|
2316
|
+
meta: {
|
|
2317
|
+
type: "problem",
|
|
2318
|
+
docs: {
|
|
2319
|
+
description: "Disallow `try` blocks with more than three top-level statements that can throw \u2014 isolate the throwing statement and move non-throwing work outside."
|
|
2320
|
+
},
|
|
2321
|
+
schema: [],
|
|
2322
|
+
messages: {
|
|
2323
|
+
fatTryBlock: "This `try` block has {{count}} statements that can throw (max {{max}}). Isolate the throwing statement(s); move non-throwing work outside the `try`."
|
|
2324
|
+
}
|
|
2325
|
+
},
|
|
2326
|
+
defaultOptions: [],
|
|
2327
|
+
create(context) {
|
|
2328
|
+
const sourceCode = context.sourceCode;
|
|
2329
|
+
return {
|
|
2330
|
+
TryStatement(node) {
|
|
2331
|
+
if (node.finalizer !== null) {
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
if (handlerRethrows(node.handler)) {
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
const count = node.block.body.filter(canThrow).length;
|
|
2338
|
+
if (count <= MAX_TRY_BODY_STATEMENTS) {
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
const tryKeyword = sourceCode.getFirstToken(node);
|
|
2342
|
+
context.report({
|
|
2343
|
+
node: tryKeyword ?? node,
|
|
2344
|
+
messageId: "fatTryBlock",
|
|
2345
|
+
data: { count, max: MAX_TRY_BODY_STATEMENTS }
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2351
|
+
|
|
2352
|
+
// src/rules/no-secret-in-log.ts
|
|
2353
|
+
import { ESLintUtils as ESLintUtils23 } from "@typescript-eslint/utils";
|
|
2354
|
+
var LOG_METHODS = /* @__PURE__ */ new Set([
|
|
2355
|
+
"debug",
|
|
2356
|
+
"info",
|
|
2357
|
+
"warn",
|
|
2358
|
+
"warning",
|
|
2359
|
+
"error",
|
|
2360
|
+
"exception",
|
|
2361
|
+
"critical",
|
|
2362
|
+
"trace",
|
|
2363
|
+
"log",
|
|
2364
|
+
"fatal",
|
|
2365
|
+
"success"
|
|
2366
|
+
]);
|
|
2367
|
+
var LOGGER_NAMES = /* @__PURE__ */ new Set([
|
|
2368
|
+
"logger",
|
|
2369
|
+
"log",
|
|
2370
|
+
"logging",
|
|
2371
|
+
"loguru",
|
|
2372
|
+
"console",
|
|
2373
|
+
"_logger",
|
|
2374
|
+
"_log"
|
|
2375
|
+
]);
|
|
2376
|
+
var LOGGER_FACTORIES = /* @__PURE__ */ new Set(["getlogger", "get_logger"]);
|
|
2377
|
+
var SECRET_WORDS = /* @__PURE__ */ new Set([
|
|
2378
|
+
"token",
|
|
2379
|
+
"secret",
|
|
2380
|
+
"password",
|
|
2381
|
+
"passwd",
|
|
2382
|
+
"jwt",
|
|
2383
|
+
"secrets",
|
|
2384
|
+
"passwords",
|
|
2385
|
+
"credential",
|
|
2386
|
+
"credentials",
|
|
2387
|
+
"authorization",
|
|
2388
|
+
"signature",
|
|
2389
|
+
"hmac",
|
|
2390
|
+
"digest",
|
|
2391
|
+
"hash",
|
|
2392
|
+
"apikey"
|
|
2393
|
+
]);
|
|
2394
|
+
var INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
|
|
2395
|
+
"count",
|
|
2396
|
+
"counts",
|
|
2397
|
+
"budget",
|
|
2398
|
+
"limit",
|
|
2399
|
+
"limits",
|
|
2400
|
+
"id",
|
|
2401
|
+
"ids",
|
|
2402
|
+
"enabled",
|
|
2403
|
+
"disabled",
|
|
2404
|
+
"flag",
|
|
2405
|
+
"flags",
|
|
2406
|
+
"present",
|
|
2407
|
+
"set",
|
|
2408
|
+
"unset",
|
|
2409
|
+
"configured",
|
|
2410
|
+
"missing",
|
|
2411
|
+
"required",
|
|
2412
|
+
"valid",
|
|
2413
|
+
"invalid",
|
|
2414
|
+
"exists",
|
|
2415
|
+
"type",
|
|
2416
|
+
"types"
|
|
2417
|
+
]);
|
|
2418
|
+
var REDACTION_RE = /prefix|suffix|redact|mask|hash|hint|_len|length/i;
|
|
2419
|
+
var WHOLE_TOKEN_REDACTION_MARKERS = /* @__PURE__ */ new Set(["tag"]);
|
|
2420
|
+
var CAMEL_RE = /[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|\d+/g;
|
|
2421
|
+
var SEGMENT_RE = /[^A-Za-z0-9]+/;
|
|
2422
|
+
function tokenize(identifier) {
|
|
2423
|
+
const tokens = [];
|
|
2424
|
+
for (const segment of identifier.split(SEGMENT_RE)) {
|
|
2425
|
+
if (!segment) {
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
tokens.push(segment.toLowerCase());
|
|
2429
|
+
for (const part of segment.match(CAMEL_RE) ?? []) {
|
|
2430
|
+
tokens.push(part.toLowerCase());
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
return tokens;
|
|
2434
|
+
}
|
|
2435
|
+
function hasApiKey(tokens) {
|
|
2436
|
+
for (let i = 0; i + 1 < tokens.length; i++) {
|
|
2437
|
+
if (tokens[i] === "api" && tokens[i + 1] === "key") {
|
|
2438
|
+
return true;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
function isSecretName(identifier) {
|
|
2444
|
+
const tokens = tokenize(identifier);
|
|
2445
|
+
const last = tokens.at(-1);
|
|
2446
|
+
if (last !== void 0 && INNOCUOUS_WORDS.has(last)) {
|
|
2447
|
+
return false;
|
|
2448
|
+
}
|
|
2449
|
+
if (tokens.some((tok) => SECRET_WORDS.has(tok))) {
|
|
2450
|
+
return true;
|
|
2451
|
+
}
|
|
2452
|
+
return hasApiKey(tokens);
|
|
2453
|
+
}
|
|
2454
|
+
function isSecretKeyword(name) {
|
|
2455
|
+
if (REDACTION_RE.test(name)) {
|
|
2456
|
+
return false;
|
|
2457
|
+
}
|
|
2458
|
+
if (tokenize(name).some((tok) => WHOLE_TOKEN_REDACTION_MARKERS.has(tok))) {
|
|
2459
|
+
return false;
|
|
2460
|
+
}
|
|
2461
|
+
return isSecretName(name);
|
|
2462
|
+
}
|
|
2463
|
+
function isLoggerExpr(expr) {
|
|
2464
|
+
switch (expr.type) {
|
|
2465
|
+
case "Identifier":
|
|
2466
|
+
return LOGGER_NAMES.has(expr.name.toLowerCase());
|
|
2467
|
+
case "MemberExpression": {
|
|
2468
|
+
const { property, object } = expr;
|
|
2469
|
+
if (!expr.computed && property.type === "Identifier") {
|
|
2470
|
+
const lowered = property.name.toLowerCase();
|
|
2471
|
+
if (LOGGER_NAMES.has(lowered) || LOGGER_FACTORIES.has(lowered)) {
|
|
2472
|
+
return true;
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
return isLoggerExpr(object);
|
|
2476
|
+
}
|
|
2477
|
+
case "CallExpression": {
|
|
2478
|
+
const callee = expr.callee;
|
|
2479
|
+
if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier" && LOGGER_FACTORIES.has(callee.property.name.toLowerCase())) {
|
|
2480
|
+
return true;
|
|
2481
|
+
}
|
|
2482
|
+
if (callee.type !== "Super") {
|
|
2483
|
+
return isLoggerExpr(callee);
|
|
2484
|
+
}
|
|
2485
|
+
return false;
|
|
2486
|
+
}
|
|
2487
|
+
default:
|
|
2488
|
+
return false;
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
function propertyKeyName2(prop) {
|
|
2492
|
+
if (prop.computed) {
|
|
2493
|
+
return null;
|
|
2494
|
+
}
|
|
2495
|
+
if (prop.key.type === "Identifier") {
|
|
2496
|
+
return prop.key.name;
|
|
2497
|
+
}
|
|
2498
|
+
if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
|
|
2499
|
+
return prop.key.value;
|
|
2500
|
+
}
|
|
2501
|
+
return null;
|
|
2502
|
+
}
|
|
2503
|
+
var no_secret_in_log_default = ESLintUtils23.RuleCreator(
|
|
2504
|
+
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2505
|
+
)({
|
|
2506
|
+
name: "no-secret-in-log",
|
|
2507
|
+
meta: {
|
|
2508
|
+
type: "problem",
|
|
2509
|
+
docs: {
|
|
2510
|
+
description: "Disallow passing a secret-named value to a logging call; it leaks to log sinks. Redact or omit it."
|
|
2511
|
+
},
|
|
2512
|
+
schema: [],
|
|
2513
|
+
messages: {
|
|
2514
|
+
noSecretInLog: "Secret `{{name}}` passed to a logging call leaks it to log sinks. Redact (e.g. `{{name}}Prefix: {{name}}.slice(0, 6)`) or omit it."
|
|
2515
|
+
}
|
|
2516
|
+
},
|
|
2517
|
+
defaultOptions: [],
|
|
2518
|
+
create(context) {
|
|
2519
|
+
return {
|
|
2520
|
+
CallExpression(node) {
|
|
2521
|
+
const callee = node.callee;
|
|
2522
|
+
if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !LOG_METHODS.has(callee.property.name)) {
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
if (!isLoggerExpr(callee.object)) {
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
for (const arg of node.arguments) {
|
|
2529
|
+
if (arg.type === "Identifier") {
|
|
2530
|
+
if (isSecretKeyword(arg.name)) {
|
|
2531
|
+
context.report({
|
|
2532
|
+
node: arg,
|
|
2533
|
+
messageId: "noSecretInLog",
|
|
2534
|
+
data: { name: arg.name }
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
continue;
|
|
2538
|
+
}
|
|
2539
|
+
if (arg.type === "ObjectExpression") {
|
|
2540
|
+
for (const prop of arg.properties) {
|
|
2541
|
+
if (prop.type !== "Property") {
|
|
2542
|
+
continue;
|
|
2543
|
+
}
|
|
2544
|
+
const keyName2 = propertyKeyName2(prop);
|
|
2545
|
+
if (keyName2 !== null && isSecretKeyword(keyName2)) {
|
|
2546
|
+
context.report({
|
|
2547
|
+
node: prop,
|
|
2548
|
+
messageId: "noSecretInLog",
|
|
2549
|
+
data: { name: keyName2 }
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
|
|
2560
|
+
// src/rules/no-template-literal-in-log.ts
|
|
2561
|
+
import { ESLintUtils as ESLintUtils24 } from "@typescript-eslint/utils";
|
|
2562
|
+
var LOG_METHODS2 = /* @__PURE__ */ new Set([
|
|
2563
|
+
"debug",
|
|
2564
|
+
"info",
|
|
2565
|
+
"warn",
|
|
2566
|
+
"warning",
|
|
2567
|
+
"error",
|
|
2568
|
+
"exception",
|
|
2569
|
+
"critical",
|
|
2570
|
+
"trace",
|
|
2571
|
+
"log",
|
|
2572
|
+
"fatal",
|
|
2573
|
+
"success"
|
|
2574
|
+
]);
|
|
2575
|
+
var LOGGER_NAMES2 = /* @__PURE__ */ new Set([
|
|
2576
|
+
"console",
|
|
2577
|
+
"logger",
|
|
2578
|
+
"log",
|
|
2579
|
+
"_log",
|
|
2580
|
+
"_logger"
|
|
2581
|
+
]);
|
|
2582
|
+
var LOGGER_FACTORIES2 = /* @__PURE__ */ new Set([
|
|
2583
|
+
"getlogger",
|
|
2584
|
+
"createlogger"
|
|
2585
|
+
]);
|
|
2586
|
+
function looksLikeLogger(node) {
|
|
2587
|
+
switch (node.type) {
|
|
2588
|
+
case "Identifier": {
|
|
2589
|
+
const name = node.name.toLowerCase();
|
|
2590
|
+
return LOGGER_NAMES2.has(name) || LOGGER_FACTORIES2.has(name);
|
|
2591
|
+
}
|
|
2592
|
+
case "MemberExpression": {
|
|
2593
|
+
if (!node.computed && node.property.type === "Identifier") {
|
|
2594
|
+
const prop = node.property.name.toLowerCase();
|
|
2595
|
+
if (LOGGER_NAMES2.has(prop) || LOGGER_FACTORIES2.has(prop)) {
|
|
2596
|
+
return true;
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
return looksLikeLogger(node.object);
|
|
2600
|
+
}
|
|
2601
|
+
case "CallExpression":
|
|
2602
|
+
return looksLikeLogger(node.callee);
|
|
2603
|
+
default:
|
|
2604
|
+
return false;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
function findInterpolatingTemplate(node) {
|
|
2608
|
+
if (node.type === "TemplateLiteral") {
|
|
2609
|
+
return node.expressions.length > 0 ? node : null;
|
|
2610
|
+
}
|
|
2611
|
+
if (node.type === "BinaryExpression" && node.operator === "+") {
|
|
2612
|
+
return findInterpolatingTemplate(node.left) ?? findInterpolatingTemplate(node.right);
|
|
2613
|
+
}
|
|
2614
|
+
return null;
|
|
2615
|
+
}
|
|
2616
|
+
function messageArg(node, method, receiver) {
|
|
2617
|
+
const levelFirst = method === "log" && !isConsoleReceiver(receiver);
|
|
2618
|
+
const arg = node.arguments[levelFirst ? 1 : 0];
|
|
2619
|
+
if (arg === void 0 || arg.type === "SpreadElement") {
|
|
2620
|
+
return null;
|
|
2621
|
+
}
|
|
2622
|
+
return arg;
|
|
2623
|
+
}
|
|
2624
|
+
function isConsoleReceiver(node) {
|
|
2625
|
+
return node.type === "Identifier" && node.name === "console";
|
|
2626
|
+
}
|
|
2627
|
+
var no_template_literal_in_log_default = ESLintUtils24.RuleCreator(
|
|
2628
|
+
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2629
|
+
)({
|
|
2630
|
+
name: "no-template-literal-in-log",
|
|
2631
|
+
meta: {
|
|
2632
|
+
type: "problem",
|
|
2633
|
+
docs: {
|
|
2634
|
+
description: "Disallow an interpolating template literal as a logging message \u2014 pass variables as structured fields so logs stay filterable and templates stay constant."
|
|
2635
|
+
},
|
|
2636
|
+
schema: [],
|
|
2637
|
+
messages: {
|
|
2638
|
+
noTemplateLiteralInLog: "Interpolating template literal as a logging message \u2014 pass variables as structured fields (logger.info('msg', { key })) instead."
|
|
2639
|
+
}
|
|
2640
|
+
},
|
|
2641
|
+
defaultOptions: [],
|
|
2642
|
+
create(context) {
|
|
2643
|
+
return {
|
|
2644
|
+
CallExpression(node) {
|
|
2645
|
+
const callee = node.callee;
|
|
2646
|
+
if (callee.type !== "MemberExpression" || callee.computed) {
|
|
2647
|
+
return;
|
|
2648
|
+
}
|
|
2649
|
+
if (callee.property.type !== "Identifier") {
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
const method = callee.property.name;
|
|
2653
|
+
if (!LOG_METHODS2.has(method)) {
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
if (!looksLikeLogger(callee.object)) {
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
const arg = messageArg(node, method, callee.object);
|
|
2660
|
+
if (arg === null) {
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
if (findInterpolatingTemplate(arg) !== null) {
|
|
2664
|
+
context.report({ node, messageId: "noTemplateLiteralInLog" });
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
});
|
|
2670
|
+
|
|
2671
|
+
// src/rules/prefer-string-literal-union.ts
|
|
2672
|
+
import {
|
|
2673
|
+
ESLintUtils as ESLintUtils25,
|
|
2674
|
+
AST_NODE_TYPES as AST_NODE_TYPES12
|
|
2675
|
+
} from "@typescript-eslint/utils";
|
|
2676
|
+
var CHOICE_TOKENS = /* @__PURE__ */ new Set([
|
|
2677
|
+
"status",
|
|
2678
|
+
"state",
|
|
2679
|
+
"kind",
|
|
2680
|
+
"role",
|
|
2681
|
+
"priority",
|
|
2682
|
+
"severity",
|
|
2683
|
+
"direction",
|
|
2684
|
+
"tier",
|
|
2685
|
+
"stage",
|
|
2686
|
+
"type",
|
|
2687
|
+
"mode",
|
|
2688
|
+
"level"
|
|
2689
|
+
]);
|
|
2690
|
+
var LOWER_TOKEN_RE = /^[a-z][a-z0-9_-]{0,30}$/;
|
|
2691
|
+
var MIN_CLUSTER_SIZE = 2;
|
|
2692
|
+
var IGNORE_PATTERNS = [
|
|
2693
|
+
/[\\/]generated[\\/]/,
|
|
2694
|
+
/\.gen\.tsx?$/,
|
|
2695
|
+
/\.generated\.tsx?$/,
|
|
2696
|
+
/\.d\.ts$/
|
|
2697
|
+
];
|
|
2698
|
+
function isIgnoredFile(filename, sourceText) {
|
|
2699
|
+
if (IGNORE_PATTERNS.some((re) => re.test(filename))) {
|
|
2700
|
+
return true;
|
|
2701
|
+
}
|
|
2702
|
+
return /@generated\b/.test(sourceText.slice(0, 1024));
|
|
2703
|
+
}
|
|
2704
|
+
function lastWord(name) {
|
|
2705
|
+
const words = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[_\s]+/).filter((w) => w.length > 0);
|
|
2706
|
+
const last = words[words.length - 1] ?? name;
|
|
2707
|
+
return last.toLowerCase();
|
|
2708
|
+
}
|
|
2709
|
+
function isChoiceLikeName(name) {
|
|
2710
|
+
return CHOICE_TOKENS.has(lastWord(name));
|
|
2711
|
+
}
|
|
2712
|
+
function keyName(key) {
|
|
2713
|
+
if (key.type === AST_NODE_TYPES12.Identifier) {
|
|
2714
|
+
return key.name;
|
|
2715
|
+
}
|
|
2716
|
+
if (key.type === AST_NODE_TYPES12.Literal && typeof key.value === "string") {
|
|
2717
|
+
return key.value;
|
|
2718
|
+
}
|
|
2719
|
+
return null;
|
|
2720
|
+
}
|
|
2721
|
+
function isStringLiteralUnion(node) {
|
|
2722
|
+
if (node?.type !== AST_NODE_TYPES12.TSUnionType) {
|
|
2723
|
+
return false;
|
|
2724
|
+
}
|
|
2725
|
+
const stringMembers = node.types.filter(
|
|
2726
|
+
(t) => t.type === AST_NODE_TYPES12.TSLiteralType && t.literal.type === AST_NODE_TYPES12.Literal && typeof t.literal.value === "string"
|
|
2727
|
+
);
|
|
2728
|
+
return stringMembers.length >= MIN_CLUSTER_SIZE;
|
|
2729
|
+
}
|
|
2730
|
+
function refKey(node) {
|
|
2731
|
+
if (node.type === AST_NODE_TYPES12.Identifier) {
|
|
2732
|
+
return node.name;
|
|
2733
|
+
}
|
|
2734
|
+
if (node.type === AST_NODE_TYPES12.MemberExpression && !node.computed) {
|
|
2735
|
+
const inner = refKey(node.object);
|
|
2736
|
+
if (inner === null || node.property.type !== AST_NODE_TYPES12.Identifier) {
|
|
2737
|
+
return null;
|
|
2738
|
+
}
|
|
2739
|
+
return `${inner}.${node.property.name}`;
|
|
2740
|
+
}
|
|
2741
|
+
return null;
|
|
2742
|
+
}
|
|
2743
|
+
function strLiteral(node) {
|
|
2744
|
+
if (node.type === AST_NODE_TYPES12.Literal && typeof node.value === "string") {
|
|
2745
|
+
return node.value;
|
|
2746
|
+
}
|
|
2747
|
+
return null;
|
|
2748
|
+
}
|
|
2749
|
+
var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
|
|
2750
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2751
|
+
)({
|
|
2752
|
+
name: "prefer-string-literal-union",
|
|
2753
|
+
meta: {
|
|
2754
|
+
type: "suggestion",
|
|
2755
|
+
docs: {
|
|
2756
|
+
description: "Flag raw `string` choice fields and string-literal comparison clusters; prefer a string-literal union type."
|
|
2757
|
+
},
|
|
2758
|
+
schema: [],
|
|
2759
|
+
messages: {
|
|
2760
|
+
bareChoiceField: '`{{name}}: string` looks like a choice field \u2014 prefer a string-literal union type (e.g. `type X = "a" | "b"`). Enums are banned by `no-enum`; use a union.',
|
|
2761
|
+
comparisonCluster: '`{{key}}` is compared against a closed set of string literals \u2014 define a string-literal union type (e.g. `type X = "a" | "b"`).'
|
|
2762
|
+
}
|
|
2763
|
+
},
|
|
2764
|
+
defaultOptions: [],
|
|
2765
|
+
create(context) {
|
|
2766
|
+
const filename = context.filename;
|
|
2767
|
+
const sourceText = context.sourceCode.getText();
|
|
2768
|
+
if (isIgnoredFile(filename, sourceText)) {
|
|
2769
|
+
return {};
|
|
2770
|
+
}
|
|
2771
|
+
const scopeStack = [];
|
|
2772
|
+
const validClusters = [];
|
|
2773
|
+
const bareChoiceProps = [];
|
|
2774
|
+
const containersWithUnion = /* @__PURE__ */ new Set();
|
|
2775
|
+
function pushScope() {
|
|
2776
|
+
scopeStack.push(/* @__PURE__ */ new Map());
|
|
2777
|
+
}
|
|
2778
|
+
function popScope() {
|
|
2779
|
+
const clusters = scopeStack.pop();
|
|
2780
|
+
if (clusters === void 0) {
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
for (const entry of clusters.values()) {
|
|
2784
|
+
if (entry.allTokens && entry.literals.size >= MIN_CLUSTER_SIZE) {
|
|
2785
|
+
validClusters.push(entry.node);
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
function accumulate(key, literals, node) {
|
|
2790
|
+
const scope = scopeStack[scopeStack.length - 1];
|
|
2791
|
+
if (scope === void 0) {
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
const allTokens = literals.every((lit) => LOWER_TOKEN_RE.test(lit));
|
|
2795
|
+
const existing = scope.get(key);
|
|
2796
|
+
if (existing === void 0) {
|
|
2797
|
+
scope.set(key, {
|
|
2798
|
+
node,
|
|
2799
|
+
literals: new Set(literals),
|
|
2800
|
+
allTokens
|
|
2801
|
+
});
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
for (const lit of literals) {
|
|
2805
|
+
existing.literals.add(lit);
|
|
2806
|
+
}
|
|
2807
|
+
existing.allTokens = existing.allTokens && allTokens;
|
|
2808
|
+
}
|
|
2809
|
+
function collectProperty(key, typeNode, container, node) {
|
|
2810
|
+
if (isStringLiteralUnion(typeNode)) {
|
|
2811
|
+
containersWithUnion.add(container);
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
if (typeNode?.type !== AST_NODE_TYPES12.TSStringKeyword) {
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
const name = keyName(key);
|
|
2818
|
+
if (name === null || !isChoiceLikeName(name)) {
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
bareChoiceProps.push({ name, container, node });
|
|
2822
|
+
}
|
|
2823
|
+
return {
|
|
2824
|
+
FunctionDeclaration: pushScope,
|
|
2825
|
+
"FunctionDeclaration:exit": popScope,
|
|
2826
|
+
FunctionExpression: pushScope,
|
|
2827
|
+
"FunctionExpression:exit": popScope,
|
|
2828
|
+
ArrowFunctionExpression: pushScope,
|
|
2829
|
+
"ArrowFunctionExpression:exit": popScope,
|
|
2830
|
+
BinaryExpression(node) {
|
|
2831
|
+
if (node.operator !== "===" && node.operator !== "!==" && node.operator !== "==" && node.operator !== "!=") {
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
const leftKey = refKey(node.left);
|
|
2835
|
+
const rightLit = strLiteral(node.right);
|
|
2836
|
+
const rightKey = refKey(node.right);
|
|
2837
|
+
const leftLit = strLiteral(node.left);
|
|
2838
|
+
if (leftKey !== null && rightLit !== null) {
|
|
2839
|
+
accumulate(leftKey, [rightLit], node);
|
|
2840
|
+
} else if (rightKey !== null && leftLit !== null) {
|
|
2841
|
+
accumulate(rightKey, [leftLit], node);
|
|
2842
|
+
}
|
|
2843
|
+
},
|
|
2844
|
+
SwitchStatement(node) {
|
|
2845
|
+
const key = refKey(node.discriminant);
|
|
2846
|
+
if (key === null) {
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
const literals = [];
|
|
2850
|
+
for (const c of node.cases) {
|
|
2851
|
+
if (c.test !== null) {
|
|
2852
|
+
const lit = strLiteral(c.test);
|
|
2853
|
+
if (lit !== null) {
|
|
2854
|
+
literals.push(lit);
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
if (literals.length > 0) {
|
|
2859
|
+
accumulate(key, literals, node);
|
|
2860
|
+
}
|
|
2861
|
+
},
|
|
2862
|
+
TSPropertySignature(node) {
|
|
2863
|
+
collectProperty(
|
|
2864
|
+
node.key,
|
|
2865
|
+
node.typeAnnotation?.typeAnnotation,
|
|
2866
|
+
node.parent,
|
|
2867
|
+
node
|
|
2868
|
+
);
|
|
2869
|
+
},
|
|
2870
|
+
PropertyDefinition(node) {
|
|
2871
|
+
collectProperty(
|
|
2872
|
+
node.key,
|
|
2873
|
+
node.typeAnnotation?.typeAnnotation,
|
|
2874
|
+
node.parent,
|
|
2875
|
+
node
|
|
2876
|
+
);
|
|
2877
|
+
},
|
|
2878
|
+
"Program:exit"() {
|
|
2879
|
+
for (const clusterNode of validClusters) {
|
|
2880
|
+
context.report({
|
|
2881
|
+
node: clusterNode,
|
|
2882
|
+
messageId: "comparisonCluster",
|
|
2883
|
+
data: { key: refKeyText(clusterNode) }
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
for (const prop of bareChoiceProps) {
|
|
2887
|
+
if (containersWithUnion.has(prop.container)) {
|
|
2888
|
+
context.report({
|
|
2889
|
+
node: prop.node,
|
|
2890
|
+
messageId: "bareChoiceField",
|
|
2891
|
+
data: { name: prop.name }
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2897
|
+
function refKeyText(node) {
|
|
2898
|
+
if (node.type === AST_NODE_TYPES12.BinaryExpression) {
|
|
2899
|
+
return refKey(node.left) ?? refKey(node.right) ?? "value";
|
|
2900
|
+
}
|
|
2901
|
+
if (node.type === AST_NODE_TYPES12.SwitchStatement) {
|
|
2902
|
+
return refKey(node.discriminant) ?? "value";
|
|
2903
|
+
}
|
|
2904
|
+
return "value";
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
});
|
|
2908
|
+
|
|
2909
|
+
// src/rules/single-public-export.ts
|
|
2910
|
+
import { ESLintUtils as ESLintUtils26, AST_NODE_TYPES as AST_NODE_TYPES13 } from "@typescript-eslint/utils";
|
|
2911
|
+
var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
|
|
2912
|
+
"util",
|
|
2913
|
+
"utils",
|
|
2914
|
+
"helper",
|
|
2915
|
+
"helpers",
|
|
2916
|
+
"common",
|
|
2917
|
+
"constant",
|
|
2918
|
+
"constants",
|
|
2919
|
+
"type",
|
|
2920
|
+
"types",
|
|
2921
|
+
"model",
|
|
2922
|
+
"models",
|
|
2923
|
+
"shared",
|
|
2924
|
+
"misc"
|
|
2925
|
+
]);
|
|
2926
|
+
var CONVENTIONAL_BUCKET_EXPORTS = /* @__PURE__ */ new Set(["cn"]);
|
|
2927
|
+
var ACRONYM_OVERRIDES = [
|
|
2928
|
+
[/OAuth/g, "Oauth"],
|
|
2929
|
+
[/GraphQL/g, "Graphql"],
|
|
2930
|
+
[/gRPC/g, "Grpc"]
|
|
2931
|
+
];
|
|
2932
|
+
var CAMEL_BOUNDARY_RE = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
|
|
2933
|
+
var TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
|
|
2934
|
+
var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
|
|
2935
|
+
var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
|
|
2936
|
+
var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
|
|
2937
|
+
var kebabCase = (name) => {
|
|
2938
|
+
let normalized = name;
|
|
2939
|
+
for (const [pattern, replacement] of ACRONYM_OVERRIDES) {
|
|
2940
|
+
normalized = normalized.replace(pattern, replacement);
|
|
2941
|
+
}
|
|
2942
|
+
return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
|
|
2943
|
+
};
|
|
2944
|
+
var isFunctionExpression2 = (node) => node !== null && (node.type === AST_NODE_TYPES13.ArrowFunctionExpression || node.type === AST_NODE_TYPES13.FunctionExpression);
|
|
2945
|
+
var functionConstName = (decl) => {
|
|
2946
|
+
if (decl.declarations.length !== 1) return null;
|
|
2947
|
+
const [declarator] = decl.declarations;
|
|
2948
|
+
if (declarator === void 0) return null;
|
|
2949
|
+
if (declarator.id.type !== AST_NODE_TYPES13.Identifier) return null;
|
|
2950
|
+
if (!isFunctionExpression2(declarator.init)) return null;
|
|
2951
|
+
return declarator.id.name;
|
|
2952
|
+
};
|
|
2953
|
+
var summarizeExports = (body) => {
|
|
2954
|
+
let names = 0;
|
|
2955
|
+
let hasReExport = false;
|
|
2956
|
+
let candidate = null;
|
|
2957
|
+
const addCandidate = (name, node) => {
|
|
2958
|
+
names += 1;
|
|
2959
|
+
candidate = { name, node };
|
|
2960
|
+
};
|
|
2961
|
+
for (const statement of body) {
|
|
2962
|
+
switch (statement.type) {
|
|
2963
|
+
case AST_NODE_TYPES13.ExportAllDeclaration:
|
|
2964
|
+
hasReExport = true;
|
|
2965
|
+
break;
|
|
2966
|
+
case AST_NODE_TYPES13.ExportDefaultDeclaration: {
|
|
2967
|
+
names += 1;
|
|
2968
|
+
const decl = statement.declaration;
|
|
2969
|
+
if (decl.type === AST_NODE_TYPES13.FunctionDeclaration && decl.id !== null) {
|
|
2970
|
+
candidate = { name: decl.id.name, node: statement };
|
|
2971
|
+
} else if (decl.type === AST_NODE_TYPES13.ClassDeclaration && decl.id !== null) {
|
|
2972
|
+
candidate = { name: decl.id.name, node: statement };
|
|
2973
|
+
}
|
|
2974
|
+
break;
|
|
2975
|
+
}
|
|
2976
|
+
case AST_NODE_TYPES13.ExportNamedDeclaration: {
|
|
2977
|
+
if (statement.source !== null) {
|
|
2978
|
+
hasReExport = true;
|
|
2979
|
+
break;
|
|
2980
|
+
}
|
|
2981
|
+
const decl = statement.declaration;
|
|
2982
|
+
if (decl === null) {
|
|
2983
|
+
names += statement.specifiers.length;
|
|
2984
|
+
break;
|
|
2985
|
+
}
|
|
2986
|
+
switch (decl.type) {
|
|
2987
|
+
case AST_NODE_TYPES13.FunctionDeclaration:
|
|
2988
|
+
if (decl.id !== null) addCandidate(decl.id.name, statement);
|
|
2989
|
+
else names += 1;
|
|
2990
|
+
break;
|
|
2991
|
+
case AST_NODE_TYPES13.ClassDeclaration:
|
|
2992
|
+
if (decl.id !== null) addCandidate(decl.id.name, statement);
|
|
2993
|
+
else names += 1;
|
|
2994
|
+
break;
|
|
2995
|
+
case AST_NODE_TYPES13.VariableDeclaration: {
|
|
2996
|
+
const fnName = functionConstName(decl);
|
|
2997
|
+
if (fnName !== null && decl.declarations.length === 1) {
|
|
2998
|
+
addCandidate(fnName, statement);
|
|
2999
|
+
} else {
|
|
3000
|
+
names += decl.declarations.length;
|
|
3001
|
+
}
|
|
3002
|
+
break;
|
|
3003
|
+
}
|
|
3004
|
+
default:
|
|
3005
|
+
names += 1;
|
|
3006
|
+
}
|
|
3007
|
+
break;
|
|
3008
|
+
}
|
|
3009
|
+
default:
|
|
3010
|
+
break;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
return { names, hasReExport, candidate };
|
|
3014
|
+
};
|
|
3015
|
+
var single_public_export_default = ESLintUtils26.RuleCreator(
|
|
3016
|
+
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3017
|
+
)({
|
|
3018
|
+
name: "single-public-export",
|
|
3019
|
+
meta: {
|
|
3020
|
+
type: "suggestion",
|
|
3021
|
+
docs: {
|
|
3022
|
+
description: "A junk-drawer module stem (`utils`, `helpers`, `types`, ...) with a single public function/class/const export should be renamed after that export."
|
|
3023
|
+
},
|
|
3024
|
+
schema: [],
|
|
3025
|
+
messages: {
|
|
3026
|
+
renameJunkDrawer: "Module stem `{{stem}}` is a generic junk-drawer name; its sole public export is `{{name}}` \u2014 rename the file to `{{expected}}.ts` to describe its responsibility."
|
|
3027
|
+
}
|
|
3028
|
+
},
|
|
3029
|
+
defaultOptions: [],
|
|
3030
|
+
create(context) {
|
|
3031
|
+
const base = basename(context.filename);
|
|
3032
|
+
if (base.endsWith(".d.ts")) return {};
|
|
3033
|
+
if (TEST_FILE_RE.test(base)) return {};
|
|
3034
|
+
const stem = stemOf(base);
|
|
3035
|
+
if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
|
|
3036
|
+
return {
|
|
3037
|
+
Program(node) {
|
|
3038
|
+
const { names, hasReExport, candidate } = summarizeExports(node.body);
|
|
3039
|
+
if (hasReExport) return;
|
|
3040
|
+
if (names !== 1 || candidate === null) return;
|
|
3041
|
+
if (CONVENTIONAL_BUCKET_EXPORTS.has(candidate.name)) return;
|
|
3042
|
+
const expected = kebabCase(candidate.name);
|
|
3043
|
+
if (stem === expected) return;
|
|
3044
|
+
context.report({
|
|
3045
|
+
node: candidate.node,
|
|
3046
|
+
messageId: "renameJunkDrawer",
|
|
3047
|
+
data: { stem, name: candidate.name, expected }
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
3052
|
+
});
|
|
3053
|
+
|
|
1625
3054
|
// src/index.ts
|
|
1626
3055
|
var rules = {
|
|
1627
3056
|
"enforce-file-structure": enforce_file_structure_default,
|
|
1628
3057
|
"no-client-side-data-fetching": no_client_side_data_fetching_default,
|
|
3058
|
+
"no-comment-cruft": no_comment_cruft_default,
|
|
1629
3059
|
"no-enum": no_enum_default,
|
|
1630
3060
|
"no-insecure-random-id": no_insecure_random_id_default,
|
|
1631
3061
|
"no-json-stringify-error": no_json_stringify_error_default,
|
|
@@ -1637,16 +3067,23 @@ var rules = {
|
|
|
1637
3067
|
"no-unnecessary-use-client": no_unnecessary_use_client_default,
|
|
1638
3068
|
"prefer-discriminated-union": prefer_discriminated_union_default,
|
|
1639
3069
|
"prefer-schema-for-api-payload": prefer_schema_for_api_payload_default,
|
|
3070
|
+
"prefer-semantic-colors": prefer_semantic_colors_default,
|
|
1640
3071
|
"prefer-server-actions": prefer_server_actions_default,
|
|
1641
3072
|
"prefer-shadcn": prefer_shadcn_default,
|
|
1642
3073
|
"require-assert-never": require_assert_never_default,
|
|
1643
3074
|
"require-zod-form-validation": require_zod_form_validation_default,
|
|
1644
|
-
"zod-naming-convention": zod_naming_convention_default
|
|
3075
|
+
"zod-naming-convention": zod_naming_convention_default,
|
|
3076
|
+
"no-cors-wildcard-with-credentials": no_cors_wildcard_with_credentials_default,
|
|
3077
|
+
"no-fat-try-blocks": no_fat_try_blocks_default,
|
|
3078
|
+
"no-secret-in-log": no_secret_in_log_default,
|
|
3079
|
+
"no-template-literal-in-log": no_template_literal_in_log_default,
|
|
3080
|
+
"prefer-string-literal-union": prefer_string_literal_union_default,
|
|
3081
|
+
"single-public-export": single_public_export_default
|
|
1645
3082
|
};
|
|
1646
3083
|
var plugin = {
|
|
1647
3084
|
meta: {
|
|
1648
3085
|
name: "@sarj/eslint-plugin",
|
|
1649
|
-
version: "2.
|
|
3086
|
+
version: "2.3.0"
|
|
1650
3087
|
},
|
|
1651
3088
|
rules,
|
|
1652
3089
|
configs: {
|
|
@@ -1668,7 +3105,17 @@ var plugin = {
|
|
|
1668
3105
|
"@sarj/no-insecure-random-id": "warn",
|
|
1669
3106
|
"@sarj/no-json-stringify-error": "warn",
|
|
1670
3107
|
"@sarj/no-string-concat-in-loop": "warn",
|
|
1671
|
-
"@sarj/prefer-discriminated-union": "warn"
|
|
3108
|
+
"@sarj/prefer-discriminated-union": "warn",
|
|
3109
|
+
"@sarj/no-comment-cruft": "warn",
|
|
3110
|
+
// Frontend / styling — distilled from frontend PR-review mining.
|
|
3111
|
+
"@sarj/prefer-semantic-colors": "warn",
|
|
3112
|
+
// Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
|
|
3113
|
+
"@sarj/no-fat-try-blocks": "warn",
|
|
3114
|
+
"@sarj/no-cors-wildcard-with-credentials": "warn",
|
|
3115
|
+
"@sarj/no-template-literal-in-log": "warn",
|
|
3116
|
+
"@sarj/no-secret-in-log": "warn",
|
|
3117
|
+
"@sarj/single-public-export": "warn",
|
|
3118
|
+
"@sarj/prefer-string-literal-union": "warn"
|
|
1672
3119
|
}
|
|
1673
3120
|
},
|
|
1674
3121
|
strict: {
|
|
@@ -1692,7 +3139,19 @@ var plugin = {
|
|
|
1692
3139
|
"@sarj/no-insecure-random-id": "error",
|
|
1693
3140
|
"@sarj/no-json-stringify-error": "error",
|
|
1694
3141
|
"@sarj/no-string-concat-in-loop": "error",
|
|
1695
|
-
"@sarj/prefer-discriminated-union": "error"
|
|
3142
|
+
"@sarj/prefer-discriminated-union": "error",
|
|
3143
|
+
"@sarj/no-comment-cruft": "error",
|
|
3144
|
+
// Frontend / styling — distilled from frontend PR-review mining. Stylistic,
|
|
3145
|
+
// no autofix → warn (rollout should prove the FP rate before raising it).
|
|
3146
|
+
"@sarj/prefer-semantic-colors": "warn",
|
|
3147
|
+
// Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
|
|
3148
|
+
"@sarj/no-fat-try-blocks": "error",
|
|
3149
|
+
"@sarj/no-cors-wildcard-with-credentials": "error",
|
|
3150
|
+
"@sarj/no-template-literal-in-log": "error",
|
|
3151
|
+
"@sarj/no-secret-in-log": "error",
|
|
3152
|
+
"@sarj/single-public-export": "error",
|
|
3153
|
+
// High-volume/stylistic — warn until rollout proves FP rate.
|
|
3154
|
+
"@sarj/prefer-string-literal-union": "warn"
|
|
1696
3155
|
}
|
|
1697
3156
|
}
|
|
1698
3157
|
}
|