@sarj/eslint-plugin 2.12.0 → 2.12.1
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 +123 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -4
- package/dist/index.d.ts +16 -4
- package/dist/index.js +123 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,30 @@ module.exports = __toCommonJS(index_exports);
|
|
|
37
37
|
|
|
38
38
|
// src/rules/enforce-file-structure.ts
|
|
39
39
|
var import_utils = require("@typescript-eslint/utils");
|
|
40
|
+
|
|
41
|
+
// src/rules/_paths.ts
|
|
42
|
+
var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
|
|
43
|
+
var STORY_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
|
|
44
|
+
var GENERATED_FILE_RE = /([\\/]generated[\\/])|(\.gen\.[cm]?[jt]sx?$)|(\.generated\.[cm]?[jt]sx?$)|(\.d\.[cm]?ts$)/;
|
|
45
|
+
function isTestFile(filename) {
|
|
46
|
+
const normalized = filename.replaceAll("\\", "/");
|
|
47
|
+
const base = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
48
|
+
if (/\.(test|spec|e2e|integration)\.[cm]?[jt]sx?$/.test(base)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return /(^|\/)(tests?|__tests__|__mocks__|fixtures|e2e|integration)\//.test(normalized);
|
|
52
|
+
}
|
|
53
|
+
function isStoryFile(filename) {
|
|
54
|
+
return STORY_FILE_RE.test(filename);
|
|
55
|
+
}
|
|
56
|
+
function isGeneratedFile(filename, sourceText = "") {
|
|
57
|
+
return GENERATED_FILE_RE.test(filename.replaceAll("\\", "/")) || /@generated\b/.test(sourceText.slice(0, 1024));
|
|
58
|
+
}
|
|
59
|
+
function isScriptFile(filename) {
|
|
60
|
+
return SCRIPT_FILE_RE.test(filename);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/rules/enforce-file-structure.ts
|
|
40
64
|
var classifyStatement = (statement) => {
|
|
41
65
|
switch (statement.type) {
|
|
42
66
|
case import_utils.AST_NODE_TYPES.ImportDeclaration:
|
|
@@ -73,6 +97,9 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
|
|
|
73
97
|
},
|
|
74
98
|
defaultOptions: [],
|
|
75
99
|
create(context) {
|
|
100
|
+
if (isTestFile(context.filename)) {
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
76
103
|
return {
|
|
77
104
|
Program(node) {
|
|
78
105
|
const body = node.body;
|
|
@@ -599,22 +626,6 @@ var no_enum_default = import_utils4.ESLintUtils.RuleCreator(
|
|
|
599
626
|
|
|
600
627
|
// src/rules/no-insecure-random-id.ts
|
|
601
628
|
var import_utils5 = require("@typescript-eslint/utils");
|
|
602
|
-
|
|
603
|
-
// src/rules/_paths.ts
|
|
604
|
-
var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
|
|
605
|
-
function isTestFile(filename) {
|
|
606
|
-
const normalized = filename.replaceAll("\\", "/");
|
|
607
|
-
const base = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
608
|
-
if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(base)) {
|
|
609
|
-
return true;
|
|
610
|
-
}
|
|
611
|
-
return /(^|\/)(tests?|__tests__|__mocks__|fixtures)\//.test(normalized);
|
|
612
|
-
}
|
|
613
|
-
function isScriptFile(filename) {
|
|
614
|
-
return SCRIPT_FILE_RE.test(filename);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// src/rules/no-insecure-random-id.ts
|
|
618
629
|
var STRONG_SECURITY_PATTERN = /token|secret|csrf|password|passwd|apikey|api[-_]?key|nonce|salt|uuid|authid/i;
|
|
619
630
|
var NON_SECURITY_ID_PATTERN = /temp|tmp|cache|correlation|request|req|trace|execution|dev|hmr|mock|test|perf|marker/i;
|
|
620
631
|
var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
|
|
@@ -1158,6 +1169,10 @@ var no_log_only_catch_default = import_utils8.ESLintUtils.RuleCreator(
|
|
|
1158
1169
|
// src/rules/no-raw-env.ts
|
|
1159
1170
|
var import_utils9 = require("@typescript-eslint/utils");
|
|
1160
1171
|
var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
|
|
1172
|
+
var ENV_BOUNDARY_FILE_RE = /(^|[\\/])(?:env|client-env|server-env|client-settings|server-settings)\.[cm]?[jt]sx?$/;
|
|
1173
|
+
function isValidatedEnvBoundary(filename, sourceText) {
|
|
1174
|
+
return ENV_BOUNDARY_FILE_RE.test(filename.replaceAll("\\", "/")) && /\bz\.object\s*\(/.test(sourceText) && /\.parse\s*\(/.test(sourceText);
|
|
1175
|
+
}
|
|
1161
1176
|
function isProcessEnv(node) {
|
|
1162
1177
|
return !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
|
|
1163
1178
|
}
|
|
@@ -1207,7 +1222,7 @@ var no_raw_env_default = import_utils9.ESLintUtils.RuleCreator(
|
|
|
1207
1222
|
defaultOptions: [],
|
|
1208
1223
|
create(context) {
|
|
1209
1224
|
const filename = context.filename;
|
|
1210
|
-
if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/"))) {
|
|
1225
|
+
if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/")) || isValidatedEnvBoundary(filename, context.sourceCode.text)) {
|
|
1211
1226
|
return {};
|
|
1212
1227
|
}
|
|
1213
1228
|
return {
|
|
@@ -2518,6 +2533,8 @@ var prefer_schema_for_api_payload_default = import_utils16.ESLintUtils.RuleCreat
|
|
|
2518
2533
|
|
|
2519
2534
|
// src/rules/prefer-semantic-colors.ts
|
|
2520
2535
|
var import_utils17 = require("@typescript-eslint/utils");
|
|
2536
|
+
var import_fs = require("fs");
|
|
2537
|
+
var import_path = require("path");
|
|
2521
2538
|
|
|
2522
2539
|
// src/rules/_tailwind.ts
|
|
2523
2540
|
var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^!/, "");
|
|
@@ -2555,6 +2572,20 @@ var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
|
|
|
2555
2572
|
]);
|
|
2556
2573
|
var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
|
|
2557
2574
|
var STORIES_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
|
|
2575
|
+
var SEMANTIC_TOKEN_RE = /--(?:background|foreground|primary|secondary|muted|accent|destructive|border|card|popover)\b|(?:bg|text|border)-(?:background|foreground|primary|secondary|muted|accent|destructive|border|card|popover)\b/;
|
|
2576
|
+
var DETECTION_FILES = [
|
|
2577
|
+
"components.json",
|
|
2578
|
+
"tailwind.config.js",
|
|
2579
|
+
"tailwind.config.cjs",
|
|
2580
|
+
"tailwind.config.mjs",
|
|
2581
|
+
"tailwind.config.ts",
|
|
2582
|
+
"app/globals.css",
|
|
2583
|
+
"src/app/globals.css",
|
|
2584
|
+
"src/index.css",
|
|
2585
|
+
"src/styles/globals.css",
|
|
2586
|
+
"styles/globals.css"
|
|
2587
|
+
];
|
|
2588
|
+
var semanticTokenCache = /* @__PURE__ */ new Map();
|
|
2558
2589
|
var SVG_DEFS_CONTAINERS = /* @__PURE__ */ new Set([
|
|
2559
2590
|
"mask",
|
|
2560
2591
|
"clipPath",
|
|
@@ -2583,6 +2614,34 @@ var isInsideSvg = (node) => {
|
|
|
2583
2614
|
}
|
|
2584
2615
|
return false;
|
|
2585
2616
|
};
|
|
2617
|
+
var hasSemanticTokenSystem = (filename) => {
|
|
2618
|
+
let dir = (0, import_path.dirname)(filename);
|
|
2619
|
+
const root = (0, import_path.parse)(dir).root;
|
|
2620
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
2621
|
+
const cached = semanticTokenCache.get(dir);
|
|
2622
|
+
if (cached !== void 0) return cached;
|
|
2623
|
+
let found = false;
|
|
2624
|
+
for (const rel of DETECTION_FILES) {
|
|
2625
|
+
const candidate = (0, import_path.join)(dir, rel);
|
|
2626
|
+
if (!(0, import_fs.existsSync)(candidate)) continue;
|
|
2627
|
+
if (rel === "components.json") {
|
|
2628
|
+
found = true;
|
|
2629
|
+
break;
|
|
2630
|
+
}
|
|
2631
|
+
try {
|
|
2632
|
+
if (SEMANTIC_TOKEN_RE.test((0, import_fs.readFileSync)(candidate, "utf8"))) {
|
|
2633
|
+
found = true;
|
|
2634
|
+
break;
|
|
2635
|
+
}
|
|
2636
|
+
} catch {
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
semanticTokenCache.set(dir, found);
|
|
2640
|
+
if (found || dir === root) return found;
|
|
2641
|
+
dir = (0, import_path.dirname)(dir);
|
|
2642
|
+
}
|
|
2643
|
+
return false;
|
|
2644
|
+
};
|
|
2586
2645
|
var propName = (key) => {
|
|
2587
2646
|
if (key.type === import_utils17.AST_NODE_TYPES.Identifier) return key.name;
|
|
2588
2647
|
if (key.type === import_utils17.AST_NODE_TYPES.Literal && typeof key.value === "string") return key.value;
|
|
@@ -2597,16 +2656,27 @@ var prefer_semantic_colors_default = import_utils17.ESLintUtils.RuleCreator(
|
|
|
2597
2656
|
docs: {
|
|
2598
2657
|
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."
|
|
2599
2658
|
},
|
|
2600
|
-
schema: [
|
|
2659
|
+
schema: [
|
|
2660
|
+
{
|
|
2661
|
+
type: "object",
|
|
2662
|
+
additionalProperties: false,
|
|
2663
|
+
properties: {
|
|
2664
|
+
requireSemanticTokens: { type: "boolean" }
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
],
|
|
2601
2668
|
messages: {
|
|
2602
2669
|
rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
|
|
2603
2670
|
arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
|
|
2604
2671
|
inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
|
|
2605
2672
|
}
|
|
2606
2673
|
},
|
|
2607
|
-
defaultOptions: [],
|
|
2608
|
-
create(context) {
|
|
2674
|
+
defaultOptions: [{}],
|
|
2675
|
+
create(context, [options]) {
|
|
2609
2676
|
if (STORIES_FILE_RE.test(context.filename)) return {};
|
|
2677
|
+
if (options?.requireSemanticTokens === true && !hasSemanticTokenSystem(context.filename)) {
|
|
2678
|
+
return {};
|
|
2679
|
+
}
|
|
2610
2680
|
const reportClasses = (value, node) => {
|
|
2611
2681
|
for (const token of classTokens(value)) {
|
|
2612
2682
|
const base = tailwindBase(token);
|
|
@@ -2865,7 +2935,7 @@ var INPUT_TYPE_REPLACEMENTS = {
|
|
|
2865
2935
|
radio: "RadioGroup",
|
|
2866
2936
|
range: "Slider"
|
|
2867
2937
|
};
|
|
2868
|
-
var SKIPPED_INPUT_TYPES = /* @__PURE__ */ new Set(["hidden"]);
|
|
2938
|
+
var SKIPPED_INPUT_TYPES = /* @__PURE__ */ new Set(["file", "hidden"]);
|
|
2869
2939
|
var kebabCase = (component) => component.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
2870
2940
|
var literalTypeAttr = (node) => {
|
|
2871
2941
|
for (const attribute of node.attributes) {
|
|
@@ -2879,8 +2949,12 @@ var literalTypeAttr = (node) => {
|
|
|
2879
2949
|
}
|
|
2880
2950
|
return null;
|
|
2881
2951
|
};
|
|
2952
|
+
var hasFileAcceptAttr = (node) => node.attributes.some(
|
|
2953
|
+
(attribute) => attribute.type === import_utils19.AST_NODE_TYPES.JSXAttribute && attribute.name.type === import_utils19.AST_NODE_TYPES.JSXIdentifier && attribute.name.name === "accept"
|
|
2954
|
+
);
|
|
2882
2955
|
var resolveInputReplacement = (node) => {
|
|
2883
2956
|
const typeAttr = literalTypeAttr(node);
|
|
2957
|
+
if (hasFileAcceptAttr(node)) return null;
|
|
2884
2958
|
if (typeAttr === null || typeAttr.kind === "dynamic") return "Input";
|
|
2885
2959
|
if (SKIPPED_INPUT_TYPES.has(typeAttr.value)) return null;
|
|
2886
2960
|
return INPUT_TYPE_REPLACEMENTS[typeAttr.value] ?? "Input";
|
|
@@ -2901,7 +2975,7 @@ var prefer_shadcn_default = import_utils19.ESLintUtils.RuleCreator(
|
|
|
2901
2975
|
},
|
|
2902
2976
|
defaultOptions: [],
|
|
2903
2977
|
create(context) {
|
|
2904
|
-
if (isTestFile(context.filename)) {
|
|
2978
|
+
if (isTestFile(context.filename) || isStoryFile(context.filename)) {
|
|
2905
2979
|
return {};
|
|
2906
2980
|
}
|
|
2907
2981
|
return {
|
|
@@ -3219,7 +3293,7 @@ var zod_naming_convention_default = import_utils22.ESLintUtils.RuleCreator(
|
|
|
3219
3293
|
const convention = optionsArg?.convention ?? "either";
|
|
3220
3294
|
const { test, messageId } = CONVENTIONS[convention];
|
|
3221
3295
|
const acceptsSchemaWord = convention !== "prefix";
|
|
3222
|
-
if (isTestFile(context.filename)) {
|
|
3296
|
+
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
3223
3297
|
return {};
|
|
3224
3298
|
}
|
|
3225
3299
|
return {
|
|
@@ -5518,7 +5592,9 @@ var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
|
|
|
5518
5592
|
var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
|
|
5519
5593
|
var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
|
|
5520
5594
|
var SENTINEL_PREFIX_RE = /^(skip|sentinel|empty|none|missing|unset|placeholder|dummy|fake|example|noop)[A-Z]/;
|
|
5595
|
+
var AST_NODE_TYPE_RE = /^(?:TS|JSX)?[A-Z][A-Za-z]*(?:Signature|Keyword|Expression|Declaration|Element|Literal|Identifier)$/;
|
|
5521
5596
|
function isConstantReference(identifier) {
|
|
5597
|
+
if (AST_NODE_TYPE_RE.test(identifier)) return true;
|
|
5522
5598
|
if (isAuthSecretName(identifier) && !SENTINEL_WORDS.test(identifier)) return false;
|
|
5523
5599
|
return identifier === identifier.toUpperCase() && /[A-Za-z]/.test(identifier);
|
|
5524
5600
|
}
|
|
@@ -5785,6 +5861,7 @@ var GLOBAL_RECEIVERS = /* @__PURE__ */ new Set([
|
|
|
5785
5861
|
"window",
|
|
5786
5862
|
"self"
|
|
5787
5863
|
]);
|
|
5864
|
+
var PRESIGNED_URL_NAME_RE = /(?:pre-?signed|signed|upload|download)Url$/i;
|
|
5788
5865
|
function isGlobalFetchCall(node) {
|
|
5789
5866
|
const callee = node.callee;
|
|
5790
5867
|
if (callee.type === "Identifier") {
|
|
@@ -5795,6 +5872,23 @@ function isGlobalFetchCall(node) {
|
|
|
5795
5872
|
}
|
|
5796
5873
|
return false;
|
|
5797
5874
|
}
|
|
5875
|
+
function identifierLikeName(node) {
|
|
5876
|
+
if (node.type === "Identifier") {
|
|
5877
|
+
return node.name;
|
|
5878
|
+
}
|
|
5879
|
+
if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
|
|
5880
|
+
return node.property.name;
|
|
5881
|
+
}
|
|
5882
|
+
return null;
|
|
5883
|
+
}
|
|
5884
|
+
function isPresignedUrlTransfer(node) {
|
|
5885
|
+
const first = node.arguments[0];
|
|
5886
|
+
if (first === void 0) {
|
|
5887
|
+
return false;
|
|
5888
|
+
}
|
|
5889
|
+
const name = identifierLikeName(first);
|
|
5890
|
+
return name !== null && PRESIGNED_URL_NAME_RE.test(name);
|
|
5891
|
+
}
|
|
5798
5892
|
function compile(patterns) {
|
|
5799
5893
|
const compiled = [];
|
|
5800
5894
|
for (const pattern of patterns) {
|
|
@@ -5841,6 +5935,9 @@ var no_raw_fetch_outside_clients_default = import_utils42.ESLintUtils.RuleCreato
|
|
|
5841
5935
|
}
|
|
5842
5936
|
return {
|
|
5843
5937
|
CallExpression(node) {
|
|
5938
|
+
if (isPresignedUrlTransfer(node)) {
|
|
5939
|
+
return;
|
|
5940
|
+
}
|
|
5844
5941
|
if (isGlobalFetchCall(node)) {
|
|
5845
5942
|
context.report({ node, messageId: "rawFetch" });
|
|
5846
5943
|
}
|
|
@@ -6466,7 +6563,7 @@ var rules = {
|
|
|
6466
6563
|
var plugin = {
|
|
6467
6564
|
meta: {
|
|
6468
6565
|
name: "@sarj/eslint-plugin",
|
|
6469
|
-
version: "2.12.
|
|
6566
|
+
version: "2.12.1"
|
|
6470
6567
|
},
|
|
6471
6568
|
rules,
|
|
6472
6569
|
configs: {
|
|
@@ -6491,7 +6588,7 @@ var plugin = {
|
|
|
6491
6588
|
"@sarj/prefer-discriminated-union": "warn",
|
|
6492
6589
|
"@sarj/no-comment-cruft": "warn",
|
|
6493
6590
|
// Frontend / styling — distilled from frontend PR-review mining.
|
|
6494
|
-
"@sarj/prefer-semantic-colors": "warn",
|
|
6591
|
+
"@sarj/prefer-semantic-colors": ["warn", { requireSemanticTokens: true }],
|
|
6495
6592
|
// Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
|
|
6496
6593
|
"@sarj/no-fat-try-blocks": "warn",
|
|
6497
6594
|
"@sarj/no-cors-wildcard-with-credentials": "warn",
|
|
@@ -6550,7 +6647,7 @@ var plugin = {
|
|
|
6550
6647
|
"@sarj/no-comment-cruft": "error",
|
|
6551
6648
|
// Frontend / styling — distilled from frontend PR-review mining. Stylistic,
|
|
6552
6649
|
// no autofix → warn (rollout should prove the FP rate before raising it).
|
|
6553
|
-
"@sarj/prefer-semantic-colors": "error",
|
|
6650
|
+
"@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
|
|
6554
6651
|
// Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
|
|
6555
6652
|
"@sarj/no-fat-try-blocks": "error",
|
|
6556
6653
|
"@sarj/no-cors-wildcard-with-credentials": "error",
|