pasika 0.4.2 → 0.5.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/README.md +36 -17
- package/dist/eslint/pasika/index.d.ts +26 -82
- package/dist/eslint/pasika/index.js +585 -409
- package/package.json +3 -2
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import css from "@eslint/css";
|
|
3
3
|
import jsonPlugin from "@eslint/json";
|
|
4
4
|
import markdown from "@eslint/markdown";
|
|
5
|
+
import tsParser from "@typescript-eslint/parser";
|
|
5
6
|
|
|
6
7
|
// eslint/rules/filename-case.ts
|
|
7
8
|
import path2 from "path";
|
|
@@ -66,11 +67,12 @@ function componentFromVariable(node) {
|
|
|
66
67
|
smart: containsSmartCall(initializer)
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
|
-
function parseComponentInfo(text, filename) {
|
|
70
|
+
function parseComponentInfo(text, filename, options) {
|
|
70
71
|
const sourceFile = ts.createSourceFile(path.resolve(filename), text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
71
72
|
const components = [];
|
|
72
73
|
for (const statement of sourceFile.statements) {
|
|
73
|
-
|
|
74
|
+
const exported = isExported(statement);
|
|
75
|
+
if (!exported && !options?.includeNonExported) continue;
|
|
74
76
|
if (ts.isFunctionDeclaration(statement)) {
|
|
75
77
|
const component = componentFromFunction(statement);
|
|
76
78
|
if (component) components.push(component);
|
|
@@ -227,33 +229,40 @@ function report(context, filename, ext) {
|
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
// eslint/rules/import-boundaries.ts
|
|
232
|
+
import path4 from "path";
|
|
233
|
+
|
|
234
|
+
// eslint/rules/project-root.ts
|
|
230
235
|
import path3 from "path";
|
|
231
|
-
|
|
236
|
+
function sourceRootOf(context) {
|
|
237
|
+
return path3.resolve(context.cwd ?? process.cwd(), "src");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// eslint/rules/import-boundaries.ts
|
|
232
241
|
var rootSupportFolders = /* @__PURE__ */ new Set(["config", "constants", "hooks", "locales", "schemas", "types", "utils"]);
|
|
233
242
|
var moduleExtensions = /* @__PURE__ */ new Set([".cjs", ".cts", ".js", ".jsx", ".mjs", ".mts", ".ts", ".tsx"]);
|
|
234
243
|
var styleExtensions = /* @__PURE__ */ new Set([".css", ".less", ".sass", ".scss"]);
|
|
235
|
-
function resolveSourceImport(filename, importPath) {
|
|
244
|
+
function resolveSourceImport(sourceRoot, filename, importPath) {
|
|
236
245
|
if (importPath.startsWith("@/")) {
|
|
237
|
-
return
|
|
246
|
+
return path4.resolve(sourceRoot, importPath.slice(2));
|
|
238
247
|
}
|
|
239
248
|
if (importPath.startsWith(".")) {
|
|
240
|
-
return
|
|
249
|
+
return path4.resolve(path4.dirname(filename), importPath);
|
|
241
250
|
}
|
|
242
251
|
return void 0;
|
|
243
252
|
}
|
|
244
|
-
function sourceSegments(absolutePath) {
|
|
245
|
-
const relativePath =
|
|
246
|
-
if (relativePath.startsWith("..") ||
|
|
253
|
+
function sourceSegments(sourceRoot, absolutePath) {
|
|
254
|
+
const relativePath = path4.relative(sourceRoot, absolutePath);
|
|
255
|
+
if (relativePath.startsWith("..") || path4.isAbsolute(relativePath)) {
|
|
247
256
|
return void 0;
|
|
248
257
|
}
|
|
249
|
-
return relativePath.split(
|
|
258
|
+
return relativePath.split(path4.sep);
|
|
250
259
|
}
|
|
251
260
|
function relativeSpecifier(filename, resolvedPath) {
|
|
252
|
-
const relativePath =
|
|
261
|
+
const relativePath = path4.relative(path4.dirname(filename), resolvedPath).split(path4.sep).join("/");
|
|
253
262
|
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
254
263
|
}
|
|
255
|
-
function aliasSpecifier(resolvedPath) {
|
|
256
|
-
return `@/${(sourceSegments(resolvedPath) ?? []).join("/")}`;
|
|
264
|
+
function aliasSpecifier(sourceRoot, resolvedPath) {
|
|
265
|
+
return `@/${(sourceSegments(sourceRoot, resolvedPath) ?? []).join("/")}`;
|
|
257
266
|
}
|
|
258
267
|
function segmentCount(specifier) {
|
|
259
268
|
return specifier.replace(/^@\//, "").split("/").filter((segment) => segment !== "." && segment !== "").length;
|
|
@@ -261,8 +270,8 @@ function segmentCount(specifier) {
|
|
|
261
270
|
function describeSegments(count) {
|
|
262
271
|
return `${String(count)} segment${count === 1 ? "" : "s"}`;
|
|
263
272
|
}
|
|
264
|
-
function prefersRelative(filename, resolvedPath) {
|
|
265
|
-
return segmentCount(relativeSpecifier(filename, resolvedPath)) <= segmentCount(aliasSpecifier(resolvedPath));
|
|
273
|
+
function prefersRelative(sourceRoot, filename, resolvedPath) {
|
|
274
|
+
return segmentCount(relativeSpecifier(filename, resolvedPath)) <= segmentCount(aliasSpecifier(sourceRoot, resolvedPath));
|
|
266
275
|
}
|
|
267
276
|
var importBoundariesRule = {
|
|
268
277
|
meta: {
|
|
@@ -271,6 +280,7 @@ var importBoundariesRule = {
|
|
|
271
280
|
},
|
|
272
281
|
create(context) {
|
|
273
282
|
const filename = context.filename;
|
|
283
|
+
const sourceRoot = sourceRootOf(context);
|
|
274
284
|
if (!filename) {
|
|
275
285
|
return {};
|
|
276
286
|
}
|
|
@@ -279,18 +289,18 @@ var importBoundariesRule = {
|
|
|
279
289
|
return;
|
|
280
290
|
}
|
|
281
291
|
const importPath = source.value;
|
|
282
|
-
const resolvedPath = resolveSourceImport(filename, importPath);
|
|
292
|
+
const resolvedPath = resolveSourceImport(sourceRoot, filename, importPath);
|
|
283
293
|
if (!resolvedPath) {
|
|
284
294
|
return;
|
|
285
295
|
}
|
|
286
|
-
const importer = sourceSegments(filename);
|
|
287
|
-
const imported = sourceSegments(resolvedPath);
|
|
296
|
+
const importer = sourceSegments(sourceRoot, filename);
|
|
297
|
+
const imported = sourceSegments(sourceRoot, resolvedPath);
|
|
288
298
|
if (!importer || !imported || importer.length === 0 || imported.length === 0) {
|
|
289
299
|
return;
|
|
290
300
|
}
|
|
291
301
|
const [importerLayer = "", importerFeature] = importer;
|
|
292
302
|
const [importedLayer = "", importedFeature] = imported;
|
|
293
|
-
const extension =
|
|
303
|
+
const extension = path4.extname(importPath);
|
|
294
304
|
const isCodeModule = !extension || moduleExtensions.has(extension);
|
|
295
305
|
const isAppLocalStyleImport = importerLayer === "app" && importedLayer === "app" && styleExtensions.has(extension);
|
|
296
306
|
const importedIsRootSupport = rootSupportFolders.has(importedLayer);
|
|
@@ -304,21 +314,21 @@ var importBoundariesRule = {
|
|
|
304
314
|
return;
|
|
305
315
|
}
|
|
306
316
|
const relativeForm = relativeSpecifier(filename, resolvedPath);
|
|
307
|
-
const aliasForm = aliasSpecifier(resolvedPath);
|
|
317
|
+
const aliasForm = aliasSpecifier(sourceRoot, resolvedPath);
|
|
308
318
|
const relativeSegments = segmentCount(relativeForm);
|
|
309
319
|
const aliasSegments = segmentCount(aliasForm);
|
|
310
320
|
function describeChoice(preferred, preferredSegments, other, otherSegments) {
|
|
311
321
|
const tie = preferredSegments === otherSegments ? ", and a tie goes to the relative path" : "";
|
|
312
322
|
return `Use "${preferred}" (${describeSegments(preferredSegments)}) instead of "${other}" (${describeSegments(otherSegments)})${tie}.`;
|
|
313
323
|
}
|
|
314
|
-
if (prefersRelative(filename, resolvedPath) && importPath.startsWith("@/")) {
|
|
324
|
+
if (prefersRelative(sourceRoot, filename, resolvedPath) && importPath.startsWith("@/")) {
|
|
315
325
|
context.report({
|
|
316
326
|
node: source,
|
|
317
327
|
message: describeChoice(relativeForm, relativeSegments, aliasForm, aliasSegments)
|
|
318
328
|
});
|
|
319
329
|
return;
|
|
320
330
|
}
|
|
321
|
-
if (!prefersRelative(filename, resolvedPath) && importPath.startsWith(".")) {
|
|
331
|
+
if (!prefersRelative(sourceRoot, filename, resolvedPath) && importPath.startsWith(".")) {
|
|
322
332
|
context.report({
|
|
323
333
|
node: source,
|
|
324
334
|
message: describeChoice(aliasForm, aliasSegments, relativeForm, relativeSegments)
|
|
@@ -350,59 +360,26 @@ var importBoundariesRule = {
|
|
|
350
360
|
};
|
|
351
361
|
|
|
352
362
|
// eslint/rules/no-mixed-concerns.ts
|
|
353
|
-
function getExportName(declaration) {
|
|
354
|
-
if (declaration?.type === "FunctionDeclaration" && declaration.id) {
|
|
355
|
-
return declaration.id.name ?? null;
|
|
356
|
-
}
|
|
357
|
-
if (declaration?.type === "VariableDeclaration") {
|
|
358
|
-
const declarator = declaration.declarations?.[0];
|
|
359
|
-
if (declarator?.id?.type === "Identifier" && (declarator.init?.type === "ArrowFunctionExpression" || declarator.init?.type === "FunctionExpression")) {
|
|
360
|
-
return declarator.id.name ?? null;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
if (declaration?.type === "ArrowFunctionExpression") {
|
|
364
|
-
return "default";
|
|
365
|
-
}
|
|
366
|
-
if (declaration?.type === "FunctionExpression" && declaration.id) {
|
|
367
|
-
return declaration.id.name ?? null;
|
|
368
|
-
}
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
363
|
var noMixedConcernsRule = {
|
|
372
364
|
meta: {
|
|
373
365
|
schema: [],
|
|
374
366
|
type: "problem",
|
|
375
367
|
docs: {
|
|
376
|
-
description: "Enforce one
|
|
368
|
+
description: "Enforce one React component per .tsx file, counting private components."
|
|
377
369
|
}
|
|
378
370
|
},
|
|
379
371
|
create(context) {
|
|
380
372
|
if (!context.filename.endsWith(".tsx")) return {};
|
|
381
|
-
|
|
382
|
-
const extraExports = [];
|
|
383
|
-
function registerExport(name) {
|
|
384
|
-
exportedComponentCount++;
|
|
385
|
-
if (exportedComponentCount > 1) {
|
|
386
|
-
extraExports.push({ name });
|
|
387
|
-
}
|
|
388
|
-
}
|
|
373
|
+
const sourceCode = context.sourceCode.text;
|
|
389
374
|
return {
|
|
390
|
-
ExportNamedDeclaration(node) {
|
|
391
|
-
const name = getExportName(node.declaration);
|
|
392
|
-
if (name) registerExport(name);
|
|
393
|
-
},
|
|
394
|
-
ExportDefaultDeclaration(node) {
|
|
395
|
-
const name = getExportName(node.declaration);
|
|
396
|
-
if (name) registerExport(name);
|
|
397
|
-
},
|
|
398
375
|
"Program:exit"() {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
}
|
|
376
|
+
const components = parseComponentInfo(sourceCode, context.filename, { includeNonExported: true });
|
|
377
|
+
if (components.length <= 1) return;
|
|
378
|
+
for (const extra of components.slice(1)) {
|
|
379
|
+
context.report({
|
|
380
|
+
loc: { line: 1, column: 0 },
|
|
381
|
+
message: `File defines multiple components. "${extra.name}" is an extra component. Move it to its own file. Each component file MUST define exactly one component. See docs/code-organization-guide/rules/no-mixed-concerns-rule.md`
|
|
382
|
+
});
|
|
406
383
|
}
|
|
407
384
|
}
|
|
408
385
|
};
|
|
@@ -480,7 +457,7 @@ var noArbitraryTailwindRule = {
|
|
|
480
457
|
|
|
481
458
|
// eslint/rules/unknown-utility.ts
|
|
482
459
|
import { readdirSync, readFileSync, statSync } from "fs";
|
|
483
|
-
import
|
|
460
|
+
import path5 from "path";
|
|
484
461
|
var PREFIX_NAMESPACES = {
|
|
485
462
|
bg: ["color"],
|
|
486
463
|
text: ["color", "text"],
|
|
@@ -642,7 +619,19 @@ var DEFAULT_TOKENS = {
|
|
|
642
619
|
blur: ["none", "xs", "sm", "md", "lg", "xl", "2xl", "3xl"],
|
|
643
620
|
animate: ["none", "spin", "ping", "pulse", "bounce"],
|
|
644
621
|
ease: ["linear", "in", "out", "in-out"],
|
|
645
|
-
|
|
622
|
+
// Note: aspect's auto/square, radius's none/full, leading's none, blur's
|
|
623
|
+
// none, animate's none, and ease's linear are static built-ins covered by
|
|
624
|
+
// STATIC_VALUE_TOKENS; the remainder are theme-derived.
|
|
625
|
+
aspect: ["video"]
|
|
626
|
+
};
|
|
627
|
+
var STATIC_VALUE_TOKENS = {
|
|
628
|
+
aspect: ["auto", "square"],
|
|
629
|
+
radius: ["none", "full"],
|
|
630
|
+
leading: ["none"],
|
|
631
|
+
blur: ["none"],
|
|
632
|
+
animate: ["none"],
|
|
633
|
+
ease: ["linear", "initial"],
|
|
634
|
+
z: ["auto"]
|
|
646
635
|
};
|
|
647
636
|
var DEFAULT_PALETTE_FAMILIES = /* @__PURE__ */ new Set([
|
|
648
637
|
"red",
|
|
@@ -670,6 +659,7 @@ var DEFAULT_PALETTE_FAMILIES = /* @__PURE__ */ new Set([
|
|
|
670
659
|
]);
|
|
671
660
|
var DEFAULT_PALETTE_SHADES = /* @__PURE__ */ new Set(["50", "100", "200", "300", "400", "500", "600", "700", "800", "900", "950"]);
|
|
672
661
|
var DEFAULT_COLOR_SPECIALS = /* @__PURE__ */ new Set(["white", "black", "transparent", "current", "inherit"]);
|
|
662
|
+
var STATIC_COLOR_SPECIALS = /* @__PURE__ */ new Set(["transparent", "current", "inherit"]);
|
|
673
663
|
var NUMERIC_TOKEN_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:%|px|rem|em)?$/;
|
|
674
664
|
var SIDE_WIDTH_TOKEN_RE = /^(?:x|y|t|r|b|l|s|e)-(?:\d+\.?\d*|\.\d+)(?:%|px|rem|em)?$/;
|
|
675
665
|
var OFFSET_TOKEN_RE = /^offset-(?:\d+\.?\d*|\.\d+)(?:%|px|rem|em)?$/;
|
|
@@ -682,7 +672,7 @@ function stylesheetFiles(dir) {
|
|
|
682
672
|
}
|
|
683
673
|
return entries.flatMap((entry) => {
|
|
684
674
|
if (entry.startsWith(".") || entry === "node_modules") return [];
|
|
685
|
-
const entryPath =
|
|
675
|
+
const entryPath = path5.join(dir, entry);
|
|
686
676
|
let stats;
|
|
687
677
|
try {
|
|
688
678
|
stats = statSync(entryPath);
|
|
@@ -690,7 +680,7 @@ function stylesheetFiles(dir) {
|
|
|
690
680
|
return [];
|
|
691
681
|
}
|
|
692
682
|
if (stats.isDirectory()) return stylesheetFiles(entryPath);
|
|
693
|
-
return
|
|
683
|
+
return path5.extname(entry) === ".css" ? [entryPath] : [];
|
|
694
684
|
});
|
|
695
685
|
}
|
|
696
686
|
function themeBlocks(css2) {
|
|
@@ -711,10 +701,36 @@ function themeBlocks(css2) {
|
|
|
711
701
|
}
|
|
712
702
|
return blocks;
|
|
713
703
|
}
|
|
704
|
+
function classSelectors(css2) {
|
|
705
|
+
const blocks = [];
|
|
706
|
+
let index = 0;
|
|
707
|
+
while (index < css2.length) {
|
|
708
|
+
const match = /@utility\s*\{/.exec(css2.slice(index));
|
|
709
|
+
if (!match) break;
|
|
710
|
+
const open = index + match.index + match[0].length - 1;
|
|
711
|
+
let depth = 1;
|
|
712
|
+
let cursor = open + 1;
|
|
713
|
+
for (; cursor < css2.length && depth > 0; cursor++) {
|
|
714
|
+
if (css2[cursor] === "{") depth++;
|
|
715
|
+
else if (css2[cursor] === "}") depth--;
|
|
716
|
+
}
|
|
717
|
+
blocks.push(css2.slice(open + 1, cursor - 1));
|
|
718
|
+
index = cursor;
|
|
719
|
+
}
|
|
720
|
+
let selectors = css2;
|
|
721
|
+
for (const block of blocks) selectors = selectors.replace(block, "");
|
|
722
|
+
const names = [];
|
|
723
|
+
for (const match of selectors.matchAll(/\.(?<className>[a-z][a-z0-9_-]*)(?=\s*[.,:#>{]|$)/gi)) {
|
|
724
|
+
const className = match.groups?.className;
|
|
725
|
+
if (className) names.push(className);
|
|
726
|
+
}
|
|
727
|
+
return names;
|
|
728
|
+
}
|
|
714
729
|
function readInventory(files) {
|
|
715
730
|
const utilities = /* @__PURE__ */ new Set();
|
|
716
731
|
const utilityPrefixes = /* @__PURE__ */ new Set();
|
|
717
732
|
const themeTokensByNamespace = /* @__PURE__ */ new Map();
|
|
733
|
+
const plainClasses = /* @__PURE__ */ new Set();
|
|
718
734
|
let defaultsReset = false;
|
|
719
735
|
for (const file of files) {
|
|
720
736
|
let css2;
|
|
@@ -730,6 +746,7 @@ function readInventory(files) {
|
|
|
730
746
|
const dash = utilityName.indexOf("-");
|
|
731
747
|
if (dash > 0) utilityPrefixes.add(utilityName.slice(0, dash));
|
|
732
748
|
}
|
|
749
|
+
for (const plainClass of classSelectors(css2)) plainClasses.add(plainClass);
|
|
733
750
|
for (const block of themeBlocks(css2)) {
|
|
734
751
|
if (/--\*\s*:\s*initial\b/.test(block)) defaultsReset = true;
|
|
735
752
|
for (const decl of block.matchAll(/--(?<namespace>[a-z][a-z0-9]*)-(?<token>[a-z0-9][a-z0-9_-]*)\s*:/g)) {
|
|
@@ -745,10 +762,11 @@ function readInventory(files) {
|
|
|
745
762
|
}
|
|
746
763
|
}
|
|
747
764
|
}
|
|
748
|
-
return { utilities, utilityPrefixes, themeTokens: themeTokensByNamespace, defaultsReset };
|
|
765
|
+
return { utilities, utilityPrefixes, themeTokens: themeTokensByNamespace, defaultsReset, plainClasses };
|
|
749
766
|
}
|
|
750
767
|
function isColorToken(token, inventory) {
|
|
751
768
|
if (inventory.themeTokens.get("color")?.has(token)) return true;
|
|
769
|
+
if (STATIC_COLOR_SPECIALS.has(token)) return true;
|
|
752
770
|
if (inventory.defaultsReset) return false;
|
|
753
771
|
if (DEFAULT_COLOR_SPECIALS.has(token)) return true;
|
|
754
772
|
const hyphen = token.lastIndexOf("-");
|
|
@@ -770,6 +788,7 @@ function isKnown(className, inventory) {
|
|
|
770
788
|
if (inventory.utilities.has(utility)) return true;
|
|
771
789
|
const namespaces = PREFIX_NAMESPACES[prefix];
|
|
772
790
|
if (!namespaces) {
|
|
791
|
+
if (STATIC_VALUE_TOKENS[prefix]?.includes(token)) return true;
|
|
773
792
|
const projectTokens = inventory.themeTokens.get(prefix);
|
|
774
793
|
if (projectTokens) {
|
|
775
794
|
if (projectTokens.has(token)) return true;
|
|
@@ -786,6 +805,7 @@ function isKnown(className, inventory) {
|
|
|
786
805
|
continue;
|
|
787
806
|
}
|
|
788
807
|
if (inventory.themeTokens.get(namespace)?.has(token)) return true;
|
|
808
|
+
if (STATIC_VALUE_TOKENS[namespace]?.includes(token)) return true;
|
|
789
809
|
if (!inventory.defaultsReset && DEFAULT_TOKENS[namespace]?.includes(token)) return true;
|
|
790
810
|
}
|
|
791
811
|
return false;
|
|
@@ -800,11 +820,11 @@ var unknownUtilityRule = {
|
|
|
800
820
|
}
|
|
801
821
|
},
|
|
802
822
|
create(context) {
|
|
803
|
-
const
|
|
823
|
+
const sourceRoot = sourceRootOf(context);
|
|
804
824
|
let inventory;
|
|
805
825
|
try {
|
|
806
|
-
if (!statSync(
|
|
807
|
-
inventory = readInventory(stylesheetFiles(
|
|
826
|
+
if (!statSync(sourceRoot).isDirectory()) return {};
|
|
827
|
+
inventory = readInventory(stylesheetFiles(sourceRoot));
|
|
808
828
|
} catch {
|
|
809
829
|
return {};
|
|
810
830
|
}
|
|
@@ -813,12 +833,19 @@ var unknownUtilityRule = {
|
|
|
813
833
|
for (const candidate of value.split(/\s+/)) {
|
|
814
834
|
if (!candidate || seen.has(candidate)) continue;
|
|
815
835
|
seen.add(candidate);
|
|
816
|
-
if (
|
|
836
|
+
if (isKnown(candidate, inventory)) continue;
|
|
837
|
+
const plainBase = candidate.replace(/!+$/, "").split(":").pop() ?? "";
|
|
838
|
+
if (inventory.plainClasses.has(plainBase)) {
|
|
817
839
|
context.report({
|
|
818
840
|
node,
|
|
819
|
-
message: `Utility class "${candidate}" is
|
|
841
|
+
message: `Utility class "${candidate}" is defined as a plain CSS selector in a stylesheet, not as an @utility (or @theme variable). Define it with @utility so the framework can own and validate it.`
|
|
820
842
|
});
|
|
843
|
+
continue;
|
|
821
844
|
}
|
|
845
|
+
context.report({
|
|
846
|
+
node,
|
|
847
|
+
message: `Utility class "${candidate}" is not a custom @utility, a theme-generated utility, or a built-in Tailwind utility.`
|
|
848
|
+
});
|
|
822
849
|
}
|
|
823
850
|
}
|
|
824
851
|
function checkExpression(node, expression) {
|
|
@@ -885,7 +912,9 @@ function isOuterLayoutClass(className) {
|
|
|
885
912
|
function stringArguments(node) {
|
|
886
913
|
if (node?.type === "Literal" && typeof node.value === "string") return [node.value];
|
|
887
914
|
if (node?.type === "TemplateLiteral") return node.quasis.map((quasi) => quasi.value.raw);
|
|
888
|
-
if (node?.type === "ConditionalExpression")
|
|
915
|
+
if (node?.type === "ConditionalExpression") {
|
|
916
|
+
return [...stringArguments(node.consequent), ...stringArguments(node.alternate)];
|
|
917
|
+
}
|
|
889
918
|
if (node?.type === "LogicalExpression") return [...stringArguments(node.left), ...stringArguments(node.right)];
|
|
890
919
|
if (node?.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "cn") {
|
|
891
920
|
return node.arguments.flatMap((argument) => stringArguments(argument));
|
|
@@ -901,7 +930,10 @@ var enforceCnMergeRule = {
|
|
|
901
930
|
const element = node.parent.parent;
|
|
902
931
|
const isComponentProp = isComponentName(element.openingElement?.name);
|
|
903
932
|
if (isComponentProp && attributeName !== "className" && attributeName.endsWith("ClassName")) {
|
|
904
|
-
context.report({
|
|
933
|
+
context.report({
|
|
934
|
+
node,
|
|
935
|
+
message: "Expose appearance through typed variant props instead of separate internal class-name props. See docs/styling-guide/rules/class-composition-rule.md"
|
|
936
|
+
});
|
|
905
937
|
return;
|
|
906
938
|
}
|
|
907
939
|
if (attributeName !== "className" && attributeName !== "class") return;
|
|
@@ -910,31 +942,60 @@ var enforceCnMergeRule = {
|
|
|
910
942
|
if (isComponentProp && attributeName === "className") {
|
|
911
943
|
const expression = valueNode.type === "JSXExpressionContainer" ? valueNode.expression : valueNode;
|
|
912
944
|
const invalidClass = stringArguments(expression).flatMap((value) => value.split(/\s+/).filter(Boolean)).find((className) => !isOuterLayoutClass(className));
|
|
913
|
-
if (invalidClass)
|
|
945
|
+
if (invalidClass) {
|
|
946
|
+
context.report({
|
|
947
|
+
node,
|
|
948
|
+
message: `Passed className contains non-layout utility "${invalidClass}". Expose appearance through typed variant props. See docs/styling-guide/rules/class-composition-rule.md`
|
|
949
|
+
});
|
|
950
|
+
}
|
|
914
951
|
return;
|
|
915
952
|
}
|
|
916
953
|
if (valueNode.type === "Literal" && typeof valueNode.value === "string") {
|
|
917
|
-
if (classCount(valueNode.value) > 5)
|
|
954
|
+
if (classCount(valueNode.value) > 5) {
|
|
955
|
+
context.report({
|
|
956
|
+
node,
|
|
957
|
+
message: "Static className with more than 5 classes must use cn() with grouped string literals. See docs/styling-guide/rules/class-composition-rule.md"
|
|
958
|
+
});
|
|
959
|
+
}
|
|
918
960
|
return;
|
|
919
961
|
}
|
|
920
962
|
if (valueNode.type !== "JSXExpressionContainer") return;
|
|
921
963
|
const expr = valueNode.expression;
|
|
922
964
|
if (expr.type === "BinaryExpression" && expr.operator === "+") {
|
|
923
|
-
context.report({
|
|
965
|
+
context.report({
|
|
966
|
+
node,
|
|
967
|
+
message: "Use cn() instead of + operator for className. See docs/styling-guide/rules/class-composition-rule.md"
|
|
968
|
+
});
|
|
924
969
|
return;
|
|
925
970
|
}
|
|
926
971
|
if (expr.type === "TemplateLiteral" && expr.expressions.length > 0) {
|
|
927
|
-
context.report({
|
|
972
|
+
context.report({
|
|
973
|
+
node,
|
|
974
|
+
message: "Use cn() instead of template literals with conditionals for className. See docs/styling-guide/rules/class-composition-rule.md"
|
|
975
|
+
});
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
if (expr.type === "LogicalExpression" || expr.type === "ConditionalExpression") {
|
|
979
|
+
context.report({
|
|
980
|
+
node,
|
|
981
|
+
message: "Use cn() for conditional classes in className. See docs/styling-guide/rules/class-composition-rule.md"
|
|
982
|
+
});
|
|
928
983
|
return;
|
|
929
984
|
}
|
|
930
985
|
if (expr.type === "Literal" && typeof expr.value === "string" && classCount(expr.value) > 5) {
|
|
931
|
-
context.report({
|
|
986
|
+
context.report({
|
|
987
|
+
node,
|
|
988
|
+
message: "Static className with more than 5 classes must use cn() with grouped string literals. See docs/styling-guide/rules/class-composition-rule.md"
|
|
989
|
+
});
|
|
932
990
|
return;
|
|
933
991
|
}
|
|
934
992
|
if (expr.type === "CallExpression" && expr.callee.type === "Identifier" && expr.callee.name === "cn") {
|
|
935
993
|
for (const arg of expr.arguments) {
|
|
936
994
|
if (arg.type === "Literal" && typeof arg.value === "string" && classCount(arg.value) > 5) {
|
|
937
|
-
context.report({
|
|
995
|
+
context.report({
|
|
996
|
+
node: arg,
|
|
997
|
+
message: "Each cn() string argument must contain at most 5 class names. Group by styling concern. See docs/styling-guide/rules/class-composition-rule.md"
|
|
998
|
+
});
|
|
938
999
|
}
|
|
939
1000
|
}
|
|
940
1001
|
}
|
|
@@ -1044,7 +1105,7 @@ var enforceCvaVariantPropsRule = {
|
|
|
1044
1105
|
};
|
|
1045
1106
|
|
|
1046
1107
|
// eslint/rules/enforce-barrel-exports.ts
|
|
1047
|
-
import
|
|
1108
|
+
import path6 from "path";
|
|
1048
1109
|
import fs from "fs";
|
|
1049
1110
|
function isPascalCase3(str) {
|
|
1050
1111
|
return /^[A-Z][A-Za-z0-9]*$/.test(str);
|
|
@@ -1064,14 +1125,14 @@ var enforceBarrelExportsRule = {
|
|
|
1064
1125
|
create(context) {
|
|
1065
1126
|
const filename = context.filename;
|
|
1066
1127
|
if (!filename) return {};
|
|
1067
|
-
const baseName =
|
|
1128
|
+
const baseName = path6.basename(filename);
|
|
1068
1129
|
if (baseName !== "index.ts" && baseName !== "index.cts" && baseName !== "index.mts") return {};
|
|
1069
|
-
const dirPath =
|
|
1070
|
-
const folderName =
|
|
1071
|
-
const parentFolderName =
|
|
1130
|
+
const dirPath = path6.dirname(filename);
|
|
1131
|
+
const folderName = path6.basename(dirPath);
|
|
1132
|
+
const parentFolderName = path6.basename(path6.dirname(dirPath));
|
|
1072
1133
|
if (SUPPORT_FOLDERS.has(folderName)) return {};
|
|
1073
1134
|
if (!isPascalCase3(folderName) && !isKebabCase2(folderName)) return {};
|
|
1074
|
-
const matchingTsx = fs.existsSync(
|
|
1135
|
+
const matchingTsx = fs.existsSync(path6.join(dirPath, `${folderName}.tsx`)) ? folderName : null;
|
|
1075
1136
|
if (!matchingTsx) return {};
|
|
1076
1137
|
if (!isPascalCase3(parentFolderName) && !isKebabCase2(parentFolderName)) return {};
|
|
1077
1138
|
const reExportedNames = /* @__PURE__ */ new Set();
|
|
@@ -1106,14 +1167,14 @@ var enforceBarrelExportsRule = {
|
|
|
1106
1167
|
};
|
|
1107
1168
|
|
|
1108
1169
|
// eslint/rules/component-placement.ts
|
|
1109
|
-
import
|
|
1170
|
+
import path10 from "path";
|
|
1110
1171
|
|
|
1111
1172
|
// eslint/project/index.ts
|
|
1112
1173
|
import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
1113
|
-
import
|
|
1174
|
+
import path8 from "path";
|
|
1114
1175
|
|
|
1115
1176
|
// eslint/project/parse-module.ts
|
|
1116
|
-
import
|
|
1177
|
+
import path7 from "path";
|
|
1117
1178
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1118
1179
|
import ts2 from "typescript";
|
|
1119
1180
|
var isPascalCase4 = (name) => /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
@@ -1214,7 +1275,7 @@ function parseModule(file) {
|
|
|
1214
1275
|
exports.push({ name: statement.name.text, kind: "type", line: lineOf(sourceFile, statement) });
|
|
1215
1276
|
}
|
|
1216
1277
|
}
|
|
1217
|
-
return { file:
|
|
1278
|
+
return { file: path7.resolve(file), imports, exports };
|
|
1218
1279
|
}
|
|
1219
1280
|
|
|
1220
1281
|
// eslint/project/index.ts
|
|
@@ -1231,7 +1292,7 @@ function listSourceFiles(dir) {
|
|
|
1231
1292
|
}
|
|
1232
1293
|
return entries.flatMap((entry) => {
|
|
1233
1294
|
if (entry.startsWith(".") || entry === "node_modules") return [];
|
|
1234
|
-
const entryPath =
|
|
1295
|
+
const entryPath = path8.join(dir, entry);
|
|
1235
1296
|
let stats;
|
|
1236
1297
|
try {
|
|
1237
1298
|
stats = statSync2(entryPath);
|
|
@@ -1239,7 +1300,7 @@ function listSourceFiles(dir) {
|
|
|
1239
1300
|
return [];
|
|
1240
1301
|
}
|
|
1241
1302
|
if (stats.isDirectory()) return listSourceFiles(entryPath);
|
|
1242
|
-
return MODULE_EXTENSIONS.includes(
|
|
1303
|
+
return MODULE_EXTENSIONS.includes(path8.extname(entry)) ? [entryPath] : [];
|
|
1243
1304
|
});
|
|
1244
1305
|
}
|
|
1245
1306
|
function fingerprint(files) {
|
|
@@ -1252,19 +1313,19 @@ function fingerprint(files) {
|
|
|
1252
1313
|
}
|
|
1253
1314
|
return `${String(files.length)}:${String(total)}`;
|
|
1254
1315
|
}
|
|
1255
|
-
function resolveSpecifier(fromFile, specifier,
|
|
1316
|
+
function resolveSpecifier(fromFile, specifier, sourceRoot) {
|
|
1256
1317
|
let base;
|
|
1257
1318
|
if (specifier.startsWith("@/")) {
|
|
1258
|
-
base =
|
|
1319
|
+
base = path8.resolve(sourceRoot, specifier.slice(2));
|
|
1259
1320
|
} else if (specifier.startsWith(".")) {
|
|
1260
|
-
base =
|
|
1321
|
+
base = path8.resolve(path8.dirname(fromFile), specifier);
|
|
1261
1322
|
} else {
|
|
1262
1323
|
return void 0;
|
|
1263
1324
|
}
|
|
1264
1325
|
const candidates = [
|
|
1265
1326
|
base,
|
|
1266
1327
|
...MODULE_EXTENSIONS.map((extension) => `${base}${extension}`),
|
|
1267
|
-
...INDEX_BASENAMES.map((name) =>
|
|
1328
|
+
...INDEX_BASENAMES.map((name) => path8.join(base, name))
|
|
1268
1329
|
];
|
|
1269
1330
|
for (const candidate of candidates) {
|
|
1270
1331
|
try {
|
|
@@ -1274,7 +1335,7 @@ function resolveSpecifier(fromFile, specifier, sourceRoot2) {
|
|
|
1274
1335
|
}
|
|
1275
1336
|
return void 0;
|
|
1276
1337
|
}
|
|
1277
|
-
function build(
|
|
1338
|
+
function build(sourceRoot, files) {
|
|
1278
1339
|
const modules = /* @__PURE__ */ new Map();
|
|
1279
1340
|
const consumers = /* @__PURE__ */ new Map();
|
|
1280
1341
|
const symbolConsumers = /* @__PURE__ */ new Map();
|
|
@@ -1286,7 +1347,7 @@ function build(sourceRoot2, files) {
|
|
|
1286
1347
|
}
|
|
1287
1348
|
for (const [file, module] of modules) {
|
|
1288
1349
|
for (const moduleImport of module.imports) {
|
|
1289
|
-
const target = resolveSpecifier(file, moduleImport.specifier,
|
|
1350
|
+
const target = resolveSpecifier(file, moduleImport.specifier, sourceRoot);
|
|
1290
1351
|
if (!target || !modules.has(target)) continue;
|
|
1291
1352
|
const fileConsumers = consumers.get(target) ?? /* @__PURE__ */ new Set();
|
|
1292
1353
|
fileConsumers.add(file);
|
|
@@ -1299,39 +1360,39 @@ function build(sourceRoot2, files) {
|
|
|
1299
1360
|
}
|
|
1300
1361
|
}
|
|
1301
1362
|
}
|
|
1302
|
-
return { sourceRoot
|
|
1363
|
+
return { sourceRoot, modules, consumers, symbolConsumers };
|
|
1303
1364
|
}
|
|
1304
1365
|
var cache;
|
|
1305
|
-
function getProjectIndex(
|
|
1366
|
+
function getProjectIndex(sourceRoot) {
|
|
1306
1367
|
const now = Date.now();
|
|
1307
|
-
if (cache?.index.sourceRoot ===
|
|
1368
|
+
if (cache?.index.sourceRoot === sourceRoot && now - cache.checkedAt < REVALIDATE_AFTER_MS) {
|
|
1308
1369
|
return cache.index;
|
|
1309
1370
|
}
|
|
1310
1371
|
try {
|
|
1311
|
-
if (!statSync2(
|
|
1372
|
+
if (!statSync2(sourceRoot).isDirectory()) return void 0;
|
|
1312
1373
|
} catch {
|
|
1313
1374
|
return void 0;
|
|
1314
1375
|
}
|
|
1315
|
-
const files = listSourceFiles(
|
|
1376
|
+
const files = listSourceFiles(sourceRoot).sort((left, right) => left.localeCompare(right));
|
|
1316
1377
|
const currentFingerprint = fingerprint(files);
|
|
1317
|
-
if (cache?.index.sourceRoot ===
|
|
1378
|
+
if (cache?.index.sourceRoot === sourceRoot && cache.fingerprint === currentFingerprint) {
|
|
1318
1379
|
cache.checkedAt = now;
|
|
1319
1380
|
return cache.index;
|
|
1320
1381
|
}
|
|
1321
|
-
const index = build(
|
|
1382
|
+
const index = build(sourceRoot, files);
|
|
1322
1383
|
cache = { index, checkedAt: now, fingerprint: currentFingerprint };
|
|
1323
1384
|
return index;
|
|
1324
1385
|
}
|
|
1325
1386
|
|
|
1326
1387
|
// eslint/project/ccf.ts
|
|
1327
|
-
import
|
|
1388
|
+
import path9 from "path";
|
|
1328
1389
|
var SUPPORT_FOLDERS2 = /* @__PURE__ */ new Set(["hooks", "types", "schemas", "constants", "utils"]);
|
|
1329
|
-
function segmentsOf(file,
|
|
1330
|
-
const relative =
|
|
1331
|
-
return relative.startsWith("..") ? [] : relative.split(
|
|
1390
|
+
function segmentsOf(file, sourceRoot) {
|
|
1391
|
+
const relative = path9.relative(sourceRoot, file);
|
|
1392
|
+
return relative.startsWith("..") ? [] : relative.split(path9.sep);
|
|
1332
1393
|
}
|
|
1333
|
-
function folderSegmentsOf(file,
|
|
1334
|
-
return segmentsOf(file,
|
|
1394
|
+
function folderSegmentsOf(file, sourceRoot) {
|
|
1395
|
+
return segmentsOf(file, sourceRoot).slice(0, -1);
|
|
1335
1396
|
}
|
|
1336
1397
|
var isUnderApp = (segments) => segments[0] === "app";
|
|
1337
1398
|
var isConfigModule = (segments) => segments[0] === "config";
|
|
@@ -1374,8 +1435,8 @@ function resolveComponentPlacement(componentFile, index) {
|
|
|
1374
1435
|
return { countedConsumers: counted, expectedFolder: shared, reason: "ccf" };
|
|
1375
1436
|
}
|
|
1376
1437
|
var formatFolder = (folder) => `src/${folder.join("/")}/`;
|
|
1377
|
-
function owningFolderOf(consumer,
|
|
1378
|
-
return outOfSupportFolders(folderSegmentsOf(consumer,
|
|
1438
|
+
function owningFolderOf(consumer, sourceRoot) {
|
|
1439
|
+
return outOfSupportFolders(folderSegmentsOf(consumer, sourceRoot));
|
|
1379
1440
|
}
|
|
1380
1441
|
var configModuleOf = (segments) => isConfigModule(segments) && segments.length >= 3 ? segments[1] : void 0;
|
|
1381
1442
|
function resolveSupportPlacement(supportFile, supportFolder, index) {
|
|
@@ -1405,9 +1466,9 @@ function resolveSupportPlacement(supportFile, supportFolder, index) {
|
|
|
1405
1466
|
}
|
|
1406
1467
|
return { countedConsumers: consumers, expectedFolder: [...shared, supportFolder], reason: "ccf" };
|
|
1407
1468
|
}
|
|
1408
|
-
function describeConsumers(consumers,
|
|
1469
|
+
function describeConsumers(consumers, sourceRoot) {
|
|
1409
1470
|
const shown = 3;
|
|
1410
|
-
const names = consumers.map((consumer) =>
|
|
1471
|
+
const names = consumers.map((consumer) => path9.relative(path9.dirname(sourceRoot), consumer).split(path9.sep).join("/")).sort((left, right) => left.localeCompare(right));
|
|
1411
1472
|
if (names.length <= shown) return names.join(", ");
|
|
1412
1473
|
return `${names.slice(0, shown).join(", ")} and ${String(names.length - shown)} more`;
|
|
1413
1474
|
}
|
|
@@ -1430,16 +1491,16 @@ var componentPlacementRule = {
|
|
|
1430
1491
|
create(context) {
|
|
1431
1492
|
const filename = context.filename;
|
|
1432
1493
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
|
|
1433
|
-
const
|
|
1434
|
-
const index = getProjectIndex(
|
|
1494
|
+
const sourceRoot = sourceRootOf(context);
|
|
1495
|
+
const index = getProjectIndex(sourceRoot);
|
|
1435
1496
|
if (!index) return {};
|
|
1436
|
-
const componentFile =
|
|
1437
|
-
const segments = segmentsOf(componentFile,
|
|
1497
|
+
const componentFile = path10.resolve(filename);
|
|
1498
|
+
const segments = segmentsOf(componentFile, sourceRoot);
|
|
1438
1499
|
if (segments.length === 0) return {};
|
|
1439
1500
|
if (isUnderApp(segments) || isConfigModule(segments)) return {};
|
|
1440
1501
|
const module = index.modules.get(componentFile);
|
|
1441
1502
|
if (!module?.exports.some((moduleExport) => moduleExport.kind === "component")) return {};
|
|
1442
|
-
const currentFolder = folderSegmentsOf(componentFile,
|
|
1503
|
+
const currentFolder = folderSegmentsOf(componentFile, sourceRoot);
|
|
1443
1504
|
const placement = resolveComponentPlacement(componentFile, index);
|
|
1444
1505
|
return {
|
|
1445
1506
|
Program(node) {
|
|
@@ -1458,7 +1519,7 @@ var componentPlacementRule = {
|
|
|
1458
1519
|
context.report({
|
|
1459
1520
|
node,
|
|
1460
1521
|
loc: { line: 1, column: 0 },
|
|
1461
|
-
message: `Move this component to ${formatFolder(placement.expectedFolder)} \u2014 ${explanation}. Imported by ${describeConsumers(placement.countedConsumers,
|
|
1522
|
+
message: `Move this component to ${formatFolder(placement.expectedFolder)} \u2014 ${explanation}. Imported by ${describeConsumers(placement.countedConsumers, sourceRoot)}. See docs/code-organization-guide/rules/component-placement-rule.md`
|
|
1462
1523
|
});
|
|
1463
1524
|
}
|
|
1464
1525
|
};
|
|
@@ -1466,7 +1527,7 @@ var componentPlacementRule = {
|
|
|
1466
1527
|
};
|
|
1467
1528
|
|
|
1468
1529
|
// eslint/rules/support-file-placement.ts
|
|
1469
|
-
import
|
|
1530
|
+
import path11 from "path";
|
|
1470
1531
|
var CONFIG_OWNED_FOLDERS = /* @__PURE__ */ new Set(["types", "constants"]);
|
|
1471
1532
|
var REASON_TEXT2 = {
|
|
1472
1533
|
"app-consumer": "a file under src/app/ imports it, so it belongs to the app-wide support folder",
|
|
@@ -1485,16 +1546,16 @@ var supportFilePlacementRule = {
|
|
|
1485
1546
|
}
|
|
1486
1547
|
},
|
|
1487
1548
|
create(context) {
|
|
1488
|
-
const
|
|
1489
|
-
const supportFile =
|
|
1490
|
-
const currentFolder = folderSegmentsOf(supportFile,
|
|
1549
|
+
const sourceRoot = sourceRootOf(context);
|
|
1550
|
+
const supportFile = path11.resolve(context.filename);
|
|
1551
|
+
const currentFolder = folderSegmentsOf(supportFile, sourceRoot);
|
|
1491
1552
|
const supportFolder = currentFolder[currentFolder.length - 1];
|
|
1492
1553
|
if (supportFolder === void 0 || !SUPPORT_FOLDERS2.has(supportFolder)) return {};
|
|
1493
|
-
const index = getProjectIndex(
|
|
1554
|
+
const index = getProjectIndex(sourceRoot);
|
|
1494
1555
|
if (!index) return {};
|
|
1495
1556
|
const placement = resolveSupportPlacement(supportFile, supportFolder, index);
|
|
1496
1557
|
if (!placement || sameFolder2(currentFolder, placement.expectedFolder)) return {};
|
|
1497
|
-
if (isConfigModule(segmentsOf(supportFile,
|
|
1558
|
+
if (isConfigModule(segmentsOf(supportFile, sourceRoot)) && CONFIG_OWNED_FOLDERS.has(supportFolder)) {
|
|
1498
1559
|
if (placement.reason !== "config-module") return {};
|
|
1499
1560
|
}
|
|
1500
1561
|
return {
|
|
@@ -1502,7 +1563,7 @@ var supportFilePlacementRule = {
|
|
|
1502
1563
|
context.report({
|
|
1503
1564
|
node,
|
|
1504
1565
|
loc: { line: 1, column: 0 },
|
|
1505
|
-
message: `Move this file to ${formatFolder(placement.expectedFolder)} \u2014 ${REASON_TEXT2[placement.reason] ?? "that is where its consumers place it"}. Imported by ${describeConsumers(placement.countedConsumers,
|
|
1566
|
+
message: `Move this file to ${formatFolder(placement.expectedFolder)} \u2014 ${REASON_TEXT2[placement.reason] ?? "that is where its consumers place it"}. Imported by ${describeConsumers(placement.countedConsumers, sourceRoot)}.`
|
|
1506
1567
|
});
|
|
1507
1568
|
}
|
|
1508
1569
|
};
|
|
@@ -1511,7 +1572,7 @@ var supportFilePlacementRule = {
|
|
|
1511
1572
|
|
|
1512
1573
|
// eslint/rules/application-structure.ts
|
|
1513
1574
|
import fs2 from "fs";
|
|
1514
|
-
import
|
|
1575
|
+
import path12 from "path";
|
|
1515
1576
|
var MODULE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
1516
1577
|
var ROUTING_FILES = /* @__PURE__ */ new Set([
|
|
1517
1578
|
"default",
|
|
@@ -1540,7 +1601,7 @@ function report2(context, message) {
|
|
|
1540
1601
|
};
|
|
1541
1602
|
}
|
|
1542
1603
|
function isCodeFile(filename) {
|
|
1543
|
-
return MODULE_EXTENSIONS2.has(
|
|
1604
|
+
return MODULE_EXTENSIONS2.has(path12.extname(filename));
|
|
1544
1605
|
}
|
|
1545
1606
|
function isRootSupportFolder(folder) {
|
|
1546
1607
|
return SUPPORT_FOLDERS2.has(folder);
|
|
@@ -1558,28 +1619,28 @@ function expectedSupportFolder(kinds) {
|
|
|
1558
1619
|
}
|
|
1559
1620
|
return void 0;
|
|
1560
1621
|
}
|
|
1561
|
-
function configModuleRoot(filename,
|
|
1562
|
-
const segments = segmentsOf(filename,
|
|
1622
|
+
function configModuleRoot(filename, sourceRoot) {
|
|
1623
|
+
const segments = segmentsOf(filename, sourceRoot);
|
|
1563
1624
|
if (segments[0] !== "config" || segments.length < 3) return void 0;
|
|
1564
|
-
return
|
|
1625
|
+
return path12.join(sourceRoot, "config", segments[1] ?? "");
|
|
1565
1626
|
}
|
|
1566
1627
|
function componentFolderStart(segments) {
|
|
1567
1628
|
if (segments[0] === "features") return 2;
|
|
1568
1629
|
if (segments[0] === "compositions" || segments[0] === "shared") return 1;
|
|
1569
1630
|
return -1;
|
|
1570
1631
|
}
|
|
1571
|
-
function componentFolderViolation(segments,
|
|
1632
|
+
function componentFolderViolation(segments, sourceRoot) {
|
|
1572
1633
|
const start = componentFolderStart(segments);
|
|
1573
1634
|
if (start < 0) return void 0;
|
|
1574
1635
|
for (let depth = segments.length - 2; depth >= start; depth -= 1) {
|
|
1575
1636
|
const folder = segments[depth];
|
|
1576
1637
|
if (!folder || SUPPORT_FOLDERS2.has(folder)) continue;
|
|
1577
|
-
const folderPath =
|
|
1638
|
+
const folderPath = path12.join(sourceRoot, ...segments.slice(0, depth + 1));
|
|
1578
1639
|
const label = `src/${segments.slice(0, depth + 1).join("/")}/`;
|
|
1579
|
-
if (!fs2.existsSync(
|
|
1640
|
+
if (!fs2.existsSync(path12.join(folderPath, `${folder}.tsx`))) {
|
|
1580
1641
|
return `A folder that is not a support folder must be a component folder; add "${folder}.tsx" to ${label} or move its files into a support folder.`;
|
|
1581
1642
|
}
|
|
1582
|
-
if (!fs2.existsSync(
|
|
1643
|
+
if (!fs2.existsSync(path12.join(folderPath, "index.ts"))) {
|
|
1583
1644
|
return `A component folder must have an index.ts that named-re-exports its component; add index.ts to ${label}.`;
|
|
1584
1645
|
}
|
|
1585
1646
|
}
|
|
@@ -1594,9 +1655,9 @@ var applicationStructureRule = {
|
|
|
1594
1655
|
}
|
|
1595
1656
|
},
|
|
1596
1657
|
create(context) {
|
|
1597
|
-
const filename =
|
|
1598
|
-
const
|
|
1599
|
-
const segments = segmentsOf(filename,
|
|
1658
|
+
const filename = path12.resolve(context.filename);
|
|
1659
|
+
const sourceRoot = sourceRootOf(context);
|
|
1660
|
+
const segments = segmentsOf(filename, sourceRoot);
|
|
1600
1661
|
if (segments.length === 0) return {};
|
|
1601
1662
|
const [topLevel, secondLevel] = segments;
|
|
1602
1663
|
if (topLevel === void 0) return {};
|
|
@@ -1624,43 +1685,43 @@ var applicationStructureRule = {
|
|
|
1624
1685
|
"A configuration module must be a src/config/<config-name>/ folder with index.ts as its entry point."
|
|
1625
1686
|
);
|
|
1626
1687
|
}
|
|
1627
|
-
const moduleRoot = configModuleRoot(filename,
|
|
1628
|
-
if (moduleRoot && !fs2.existsSync(
|
|
1688
|
+
const moduleRoot = configModuleRoot(filename, sourceRoot);
|
|
1689
|
+
if (moduleRoot && !fs2.existsSync(path12.join(moduleRoot, "index.ts"))) {
|
|
1629
1690
|
return report2(
|
|
1630
1691
|
context,
|
|
1631
|
-
`Add src/config/${
|
|
1692
|
+
`Add src/config/${path12.basename(moduleRoot)}/index.ts as the configuration module entry point.`
|
|
1632
1693
|
);
|
|
1633
1694
|
}
|
|
1634
|
-
if (moduleRoot && segments.length === 3 &&
|
|
1695
|
+
if (moduleRoot && segments.length === 3 && path12.basename(filename) !== "index.ts") {
|
|
1635
1696
|
const kinds2 = exportedKinds(filename);
|
|
1636
1697
|
const expected2 = expectedSupportFolder(kinds2);
|
|
1637
1698
|
if (expected2 !== void 0) {
|
|
1638
1699
|
return report2(
|
|
1639
1700
|
context,
|
|
1640
|
-
`Move this configuration support file into src/config/${
|
|
1701
|
+
`Move this configuration support file into src/config/${path12.basename(moduleRoot)}/${expected2}/.`
|
|
1641
1702
|
);
|
|
1642
1703
|
}
|
|
1643
1704
|
}
|
|
1644
1705
|
}
|
|
1645
1706
|
if (topLevel === "app" && isCodeFile(filename)) {
|
|
1646
|
-
const basename =
|
|
1647
|
-
const currentFolder2 =
|
|
1707
|
+
const basename = path12.basename(filename, path12.extname(filename));
|
|
1708
|
+
const currentFolder2 = path12.basename(path12.dirname(filename));
|
|
1648
1709
|
if (SUPPORT_FOLDERS2.has(currentFolder2)) {
|
|
1649
1710
|
return report2(
|
|
1650
1711
|
context,
|
|
1651
1712
|
"src/app/ may contain routing files and framework assets, but ordinary components and support files must live outside src/app/."
|
|
1652
1713
|
);
|
|
1653
1714
|
}
|
|
1654
|
-
if (!ROUTING_FILES.has(basename) &&
|
|
1715
|
+
if (!ROUTING_FILES.has(basename) && path12.extname(filename) !== ".css") {
|
|
1655
1716
|
return report2(
|
|
1656
1717
|
context,
|
|
1657
1718
|
"src/app/ may contain routing files and framework assets, but ordinary components and support files must live outside src/app/."
|
|
1658
1719
|
);
|
|
1659
1720
|
}
|
|
1660
1721
|
}
|
|
1661
|
-
const currentFolder =
|
|
1722
|
+
const currentFolder = path12.basename(path12.dirname(filename));
|
|
1662
1723
|
const kinds = exportedKinds(filename);
|
|
1663
|
-
const isConfigModuleRoot = topLevel === "config" && segments.length === 3 &&
|
|
1724
|
+
const isConfigModuleRoot = topLevel === "config" && segments.length === 3 && path12.basename(filename) === "index.ts";
|
|
1664
1725
|
if (isConfigModuleRoot) return {};
|
|
1665
1726
|
if (!SUPPORT_FOLDERS2.has(currentFolder)) {
|
|
1666
1727
|
const expected2 = expectedSupportFolder(kinds);
|
|
@@ -1670,14 +1731,14 @@ var applicationStructureRule = {
|
|
|
1670
1731
|
`Move this file to a ${expected2}/ folder; ${currentFolder}/ is not a recognized support folder.`
|
|
1671
1732
|
);
|
|
1672
1733
|
}
|
|
1673
|
-
const violation2 = componentFolderViolation(segments,
|
|
1734
|
+
const violation2 = componentFolderViolation(segments, sourceRoot);
|
|
1674
1735
|
if (violation2 !== void 0) return report2(context, violation2);
|
|
1675
1736
|
return {};
|
|
1676
1737
|
}
|
|
1677
1738
|
if (kinds.has("component")) {
|
|
1678
1739
|
return report2(
|
|
1679
1740
|
context,
|
|
1680
|
-
`A support folder must not contain a component; move ${
|
|
1741
|
+
`A support folder must not contain a component; move ${path12.basename(filename)} beside ${currentFolder}/.`
|
|
1681
1742
|
);
|
|
1682
1743
|
}
|
|
1683
1744
|
const expected = expectedSupportFolder(kinds);
|
|
@@ -1687,14 +1748,14 @@ var applicationStructureRule = {
|
|
|
1687
1748
|
`Move this file to a ${expected}/ folder; ${currentFolder}/ is reserved for ${expected === "utils" ? "utilities" : expected}.`
|
|
1688
1749
|
);
|
|
1689
1750
|
}
|
|
1690
|
-
const violation = componentFolderViolation(segments,
|
|
1751
|
+
const violation = componentFolderViolation(segments, sourceRoot);
|
|
1691
1752
|
if (violation !== void 0) return report2(context, violation);
|
|
1692
1753
|
return {};
|
|
1693
1754
|
}
|
|
1694
1755
|
};
|
|
1695
1756
|
|
|
1696
1757
|
// eslint/rules/named-exports.ts
|
|
1697
|
-
import
|
|
1758
|
+
import path13 from "path";
|
|
1698
1759
|
var FRAMEWORK_DEFAULT_EXPORT_FILES = /* @__PURE__ */ new Set([
|
|
1699
1760
|
"default",
|
|
1700
1761
|
"error",
|
|
@@ -1706,9 +1767,9 @@ var FRAMEWORK_DEFAULT_EXPORT_FILES = /* @__PURE__ */ new Set([
|
|
|
1706
1767
|
"template"
|
|
1707
1768
|
]);
|
|
1708
1769
|
function isFrameworkDefaultExportFile(filename) {
|
|
1709
|
-
const normalized = filename.replaceAll(
|
|
1770
|
+
const normalized = filename.replaceAll(path13.sep, "/");
|
|
1710
1771
|
if (!normalized.includes("/src/app/")) return false;
|
|
1711
|
-
const basename =
|
|
1772
|
+
const basename = path13.basename(filename, path13.extname(filename));
|
|
1712
1773
|
return FRAMEWORK_DEFAULT_EXPORT_FILES.has(basename);
|
|
1713
1774
|
}
|
|
1714
1775
|
var namedExportsRule = {
|
|
@@ -1733,7 +1794,7 @@ var namedExportsRule = {
|
|
|
1733
1794
|
};
|
|
1734
1795
|
|
|
1735
1796
|
// eslint/rules/data-testid-case.ts
|
|
1736
|
-
import
|
|
1797
|
+
import path14 from "path";
|
|
1737
1798
|
var NEXT_ROUTING_FILES2 = /* @__PURE__ */ new Set([
|
|
1738
1799
|
"page",
|
|
1739
1800
|
"layout",
|
|
@@ -1757,9 +1818,9 @@ var dataTestIdCaseRule = {
|
|
|
1757
1818
|
}
|
|
1758
1819
|
},
|
|
1759
1820
|
create(context) {
|
|
1760
|
-
const filename =
|
|
1821
|
+
const filename = path14.resolve(context.filename);
|
|
1761
1822
|
if (!filename.endsWith(".tsx")) return {};
|
|
1762
|
-
const base =
|
|
1823
|
+
const base = path14.basename(filename, path14.extname(filename));
|
|
1763
1824
|
if (NEXT_ROUTING_FILES2.has(base)) return {};
|
|
1764
1825
|
const text = context.sourceCode.text;
|
|
1765
1826
|
const components = parseComponentInfo(text, filename);
|
|
@@ -1801,7 +1862,7 @@ function toKebabCase(value) {
|
|
|
1801
1862
|
|
|
1802
1863
|
// eslint/rules/support-folder-shape.ts
|
|
1803
1864
|
import fs3 from "fs";
|
|
1804
|
-
import
|
|
1865
|
+
import path15 from "path";
|
|
1805
1866
|
var SUPPORT_FOLDERS3 = /* @__PURE__ */ new Set(["constants", "types", "schemas"]);
|
|
1806
1867
|
var INDEX_NAMES = /* @__PURE__ */ new Set(["index.ts", "index.tsx", "index.mts", "index.cts"]);
|
|
1807
1868
|
var supportFolderShapeRule = {
|
|
@@ -1813,12 +1874,12 @@ var supportFolderShapeRule = {
|
|
|
1813
1874
|
}
|
|
1814
1875
|
},
|
|
1815
1876
|
create(context) {
|
|
1816
|
-
const filename =
|
|
1817
|
-
const baseName =
|
|
1877
|
+
const filename = path15.resolve(context.filename);
|
|
1878
|
+
const baseName = path15.basename(filename);
|
|
1818
1879
|
if (!INDEX_NAMES.has(baseName)) return {};
|
|
1819
|
-
const folder =
|
|
1880
|
+
const folder = path15.basename(path15.dirname(filename));
|
|
1820
1881
|
if (!SUPPORT_FOLDERS3.has(folder)) return {};
|
|
1821
|
-
const directory =
|
|
1882
|
+
const directory = path15.dirname(filename);
|
|
1822
1883
|
let entries;
|
|
1823
1884
|
try {
|
|
1824
1885
|
entries = fs3.readdirSync(directory);
|
|
@@ -1836,7 +1897,7 @@ var supportFolderShapeRule = {
|
|
|
1836
1897
|
const exportPattern = /export\s+(?:\{[^}]*\}|\*[^;]*)\s+from\s+["'](?<specifier>\.[^"']+)["']/g;
|
|
1837
1898
|
for (const match of source.matchAll(exportPattern)) {
|
|
1838
1899
|
const specifier = match.groups?.specifier;
|
|
1839
|
-
if (specifier) exportedFiles.add(
|
|
1900
|
+
if (specifier) exportedFiles.add(path15.basename(specifier));
|
|
1840
1901
|
}
|
|
1841
1902
|
const missing = siblingModules.filter((entry) => {
|
|
1842
1903
|
const stem = entry.replace(/\.(?:[cm]?tsx?|jsx?)$/, "");
|
|
@@ -1853,7 +1914,7 @@ var supportFolderShapeRule = {
|
|
|
1853
1914
|
};
|
|
1854
1915
|
|
|
1855
1916
|
// eslint/rules/import-through-index.ts
|
|
1856
|
-
import
|
|
1917
|
+
import path16 from "path";
|
|
1857
1918
|
var importThroughIndexRule = {
|
|
1858
1919
|
meta: {
|
|
1859
1920
|
schema: [],
|
|
@@ -1863,19 +1924,19 @@ var importThroughIndexRule = {
|
|
|
1863
1924
|
}
|
|
1864
1925
|
},
|
|
1865
1926
|
create(context) {
|
|
1866
|
-
const filename =
|
|
1867
|
-
const
|
|
1927
|
+
const filename = path16.resolve(context.filename);
|
|
1928
|
+
const sourceRoot = sourceRootOf2(context, filename);
|
|
1868
1929
|
return {
|
|
1869
1930
|
Program(node) {
|
|
1870
1931
|
for (const specifier of importSpecifiers(context.sourceCode.text)) {
|
|
1871
|
-
const target = resolveSpecifier(filename, specifier,
|
|
1932
|
+
const target = resolveSpecifier(filename, specifier, sourceRoot);
|
|
1872
1933
|
if (!target) continue;
|
|
1873
|
-
const targetSegments = segmentsOf(target,
|
|
1934
|
+
const targetSegments = segmentsOf(target, sourceRoot);
|
|
1874
1935
|
const supportFolderIndex = targetSegments.findIndex(
|
|
1875
1936
|
(segment) => ["constants", "types", "schemas"].includes(segment)
|
|
1876
1937
|
);
|
|
1877
1938
|
const supportFolder = supportFolderIndex >= 0 ? targetSegments[supportFolderIndex] : void 0;
|
|
1878
|
-
if (!supportFolder ||
|
|
1939
|
+
if (!supportFolder || path16.basename(target).startsWith("index.")) continue;
|
|
1879
1940
|
const folderIndex = targetSegments.slice(0, supportFolderIndex + 1);
|
|
1880
1941
|
const expected = `@/${folderIndex.join("/")}`;
|
|
1881
1942
|
context.report({
|
|
@@ -1896,14 +1957,15 @@ function importSpecifiers(source) {
|
|
|
1896
1957
|
}
|
|
1897
1958
|
return specifiers;
|
|
1898
1959
|
}
|
|
1899
|
-
function
|
|
1900
|
-
const marker = `${
|
|
1960
|
+
function sourceRootOf2(context, filename) {
|
|
1961
|
+
const marker = `${path16.sep}src${path16.sep}`;
|
|
1901
1962
|
const srcIndex = filename.lastIndexOf(marker);
|
|
1902
|
-
|
|
1963
|
+
if (srcIndex >= 0) return filename.slice(0, srcIndex + marker.length - 1);
|
|
1964
|
+
return path16.resolve(context.cwd ?? process.cwd(), "src");
|
|
1903
1965
|
}
|
|
1904
1966
|
|
|
1905
1967
|
// eslint/rules/util-file-name.ts
|
|
1906
|
-
import
|
|
1968
|
+
import path17 from "path";
|
|
1907
1969
|
function toKebabCase2(value) {
|
|
1908
1970
|
return value.replace(/(?<lower>[a-z0-9])(?<upper>[A-Z])/g, "$<lower>-$<upper>").replace(/(?<first>[A-Z])(?<rest>[A-Z][a-z])/g, "$<first>-$<rest>").toLowerCase();
|
|
1909
1971
|
}
|
|
@@ -1916,7 +1978,7 @@ var utilFileNameRule = {
|
|
|
1916
1978
|
}
|
|
1917
1979
|
},
|
|
1918
1980
|
create(context) {
|
|
1919
|
-
const filename =
|
|
1981
|
+
const filename = path17.resolve(context.filename);
|
|
1920
1982
|
const segments = filename.replace(/\\/g, "/").split("/");
|
|
1921
1983
|
if (!segments.includes("utils")) return {};
|
|
1922
1984
|
let module;
|
|
@@ -1930,13 +1992,13 @@ var utilFileNameRule = {
|
|
|
1930
1992
|
const functionName = functions[0]?.name;
|
|
1931
1993
|
if (!functionName) return {};
|
|
1932
1994
|
const expected = toKebabCase2(functionName);
|
|
1933
|
-
const actual =
|
|
1995
|
+
const actual = path17.basename(filename, path17.extname(filename));
|
|
1934
1996
|
if (!expected || actual === expected) return {};
|
|
1935
1997
|
return {
|
|
1936
1998
|
Program(node) {
|
|
1937
1999
|
context.report({
|
|
1938
2000
|
node,
|
|
1939
|
-
message: `A utility file exporting ${functionName} must be named ${expected}.${
|
|
2001
|
+
message: `A utility file exporting ${functionName} must be named ${expected}.${path17.extname(filename).slice(1)}.`
|
|
1940
2002
|
});
|
|
1941
2003
|
}
|
|
1942
2004
|
};
|
|
@@ -1944,7 +2006,7 @@ var utilFileNameRule = {
|
|
|
1944
2006
|
};
|
|
1945
2007
|
|
|
1946
2008
|
// eslint/rules/no-util-barrel.ts
|
|
1947
|
-
import
|
|
2009
|
+
import path18 from "path";
|
|
1948
2010
|
var noUtilBarrelRule = {
|
|
1949
2011
|
meta: {
|
|
1950
2012
|
schema: [],
|
|
@@ -1954,16 +2016,16 @@ var noUtilBarrelRule = {
|
|
|
1954
2016
|
}
|
|
1955
2017
|
},
|
|
1956
2018
|
create(context) {
|
|
1957
|
-
const filename =
|
|
1958
|
-
const
|
|
2019
|
+
const filename = path18.resolve(context.filename);
|
|
2020
|
+
const sourceRoot = sourceRootOf3(context, filename);
|
|
1959
2021
|
return {
|
|
1960
2022
|
Program(node) {
|
|
1961
2023
|
for (const specifier of importSpecifiers2(context.sourceCode.text)) {
|
|
1962
|
-
const target = resolveSpecifier(filename, specifier,
|
|
2024
|
+
const target = resolveSpecifier(filename, specifier, sourceRoot);
|
|
1963
2025
|
if (!target) continue;
|
|
1964
2026
|
const segments = target.replace(/\\/g, "/").split("/");
|
|
1965
2027
|
const utilsIndex = segments.lastIndexOf("utils");
|
|
1966
|
-
if (utilsIndex < 0 || !
|
|
2028
|
+
if (utilsIndex < 0 || !path18.basename(target).startsWith("index.")) continue;
|
|
1967
2029
|
context.report({
|
|
1968
2030
|
node,
|
|
1969
2031
|
message: `Import utilities directly instead of through "${specifier}". See docs/code-organization-guide/rules/utilities-rule.md`
|
|
@@ -1982,10 +2044,11 @@ function importSpecifiers2(source) {
|
|
|
1982
2044
|
}
|
|
1983
2045
|
return specifiers;
|
|
1984
2046
|
}
|
|
1985
|
-
function
|
|
1986
|
-
const marker = `${
|
|
2047
|
+
function sourceRootOf3(context, filename) {
|
|
2048
|
+
const marker = `${path18.sep}src${path18.sep}`;
|
|
1987
2049
|
const srcIndex = filename.lastIndexOf(marker);
|
|
1988
|
-
|
|
2050
|
+
if (srcIndex >= 0) return filename.slice(0, srcIndex + marker.length - 1);
|
|
2051
|
+
return path18.resolve(context.cwd ?? process.cwd(), "src");
|
|
1989
2052
|
}
|
|
1990
2053
|
|
|
1991
2054
|
// eslint/rules/jsx-hygiene.ts
|
|
@@ -2361,12 +2424,12 @@ var cvaBooleanVariantsRule = {
|
|
|
2361
2424
|
};
|
|
2362
2425
|
|
|
2363
2426
|
// eslint/rules/cross-feature-import.ts
|
|
2364
|
-
import
|
|
2427
|
+
import path19 from "path";
|
|
2365
2428
|
var FEATURES_SEGMENT = "features";
|
|
2366
|
-
function featureNameOf(resolvedPath,
|
|
2367
|
-
const relative =
|
|
2429
|
+
function featureNameOf(resolvedPath, sourceRoot) {
|
|
2430
|
+
const relative = path19.relative(sourceRoot, resolvedPath);
|
|
2368
2431
|
if (relative.startsWith("..")) return void 0;
|
|
2369
|
-
const segments = relative.split(
|
|
2432
|
+
const segments = relative.split(path19.sep);
|
|
2370
2433
|
if (segments[0] !== FEATURES_SEGMENT || segments.length < 2) return void 0;
|
|
2371
2434
|
return segments[1];
|
|
2372
2435
|
}
|
|
@@ -2381,10 +2444,10 @@ var crossFeatureImportRule = {
|
|
|
2381
2444
|
create(context) {
|
|
2382
2445
|
const filename = context.filename;
|
|
2383
2446
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
|
|
2384
|
-
const
|
|
2385
|
-
const fileRelative =
|
|
2447
|
+
const sourceRoot = sourceRootOf(context);
|
|
2448
|
+
const fileRelative = path19.relative(sourceRoot, filename);
|
|
2386
2449
|
if (fileRelative.startsWith("..")) return {};
|
|
2387
|
-
const fileSegments = fileRelative.split(
|
|
2450
|
+
const fileSegments = fileRelative.split(path19.sep);
|
|
2388
2451
|
const isInCompositions = fileSegments[0] === "compositions";
|
|
2389
2452
|
const isInApp = fileSegments[0] === "app";
|
|
2390
2453
|
const isConfig = fileSegments[0] === "config";
|
|
@@ -2398,12 +2461,12 @@ var crossFeatureImportRule = {
|
|
|
2398
2461
|
if (typeof source.value !== "string") return;
|
|
2399
2462
|
let resolved;
|
|
2400
2463
|
if (source.value.startsWith("@/")) {
|
|
2401
|
-
resolved =
|
|
2464
|
+
resolved = path19.resolve(sourceRoot, source.value.slice(2));
|
|
2402
2465
|
} else if (source.value.startsWith(".")) {
|
|
2403
|
-
resolved =
|
|
2466
|
+
resolved = path19.resolve(path19.dirname(filename), source.value);
|
|
2404
2467
|
}
|
|
2405
2468
|
if (!resolved) return;
|
|
2406
|
-
const feature = featureNameOf(resolved,
|
|
2469
|
+
const feature = featureNameOf(resolved, sourceRoot);
|
|
2407
2470
|
if (feature) importedFeatures.add(feature);
|
|
2408
2471
|
if (importedFeatures.size >= 2) {
|
|
2409
2472
|
alreadyReported = true;
|
|
@@ -2419,7 +2482,7 @@ var crossFeatureImportRule = {
|
|
|
2419
2482
|
};
|
|
2420
2483
|
|
|
2421
2484
|
// eslint/rules/pure-function-extract.ts
|
|
2422
|
-
import
|
|
2485
|
+
import path20 from "path";
|
|
2423
2486
|
function isComponentLikeName(name) {
|
|
2424
2487
|
return /^[A-Z]/.test(name);
|
|
2425
2488
|
}
|
|
@@ -2447,10 +2510,10 @@ var pureFunctionExtractRule = {
|
|
|
2447
2510
|
create(context) {
|
|
2448
2511
|
const filename = context.filename;
|
|
2449
2512
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
|
|
2450
|
-
const
|
|
2451
|
-
const relative =
|
|
2513
|
+
const sourceRoot = sourceRootOf(context);
|
|
2514
|
+
const relative = path20.relative(sourceRoot, filename);
|
|
2452
2515
|
if (relative.startsWith("..")) return {};
|
|
2453
|
-
const segments = relative.split(
|
|
2516
|
+
const segments = relative.split(path20.sep);
|
|
2454
2517
|
if (segments[0] === "utils") return {};
|
|
2455
2518
|
if (segments[0] === "app") return {};
|
|
2456
2519
|
const supportFolders = /* @__PURE__ */ new Set(["hooks", "types", "schemas", "constants", "utils"]);
|
|
@@ -2490,7 +2553,7 @@ var pureFunctionExtractRule = {
|
|
|
2490
2553
|
};
|
|
2491
2554
|
|
|
2492
2555
|
// eslint/rules/hook-complexity.ts
|
|
2493
|
-
import
|
|
2556
|
+
import path21 from "path";
|
|
2494
2557
|
import ts4 from "typescript";
|
|
2495
2558
|
var REACT_HOOKS = /* @__PURE__ */ new Set([
|
|
2496
2559
|
"useState",
|
|
@@ -2542,10 +2605,10 @@ var hookComplexityRule = {
|
|
|
2542
2605
|
},
|
|
2543
2606
|
create(context) {
|
|
2544
2607
|
const filename = context.filename;
|
|
2545
|
-
const
|
|
2546
|
-
const relative =
|
|
2608
|
+
const sourceRoot = sourceRootOf(context);
|
|
2609
|
+
const relative = path21.relative(sourceRoot, filename);
|
|
2547
2610
|
if (relative.startsWith("..")) return {};
|
|
2548
|
-
const segments = relative.split(
|
|
2611
|
+
const segments = relative.split(path21.sep);
|
|
2549
2612
|
const sourceText = context.sourceCode.text;
|
|
2550
2613
|
function checkHook(node, name, body, exported) {
|
|
2551
2614
|
if (!exported) return;
|
|
@@ -2587,9 +2650,9 @@ var hookComplexityRule = {
|
|
|
2587
2650
|
};
|
|
2588
2651
|
|
|
2589
2652
|
// eslint/rules/locale-dotted-path.ts
|
|
2590
|
-
import
|
|
2653
|
+
import path22 from "path";
|
|
2591
2654
|
function isInLocalesDir(filename) {
|
|
2592
|
-
const segments =
|
|
2655
|
+
const segments = path22.resolve(filename).split(path22.sep);
|
|
2593
2656
|
const srcIdx = segments.lastIndexOf("src");
|
|
2594
2657
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
2595
2658
|
}
|
|
@@ -2638,9 +2701,9 @@ var localeDottedPathRule = {
|
|
|
2638
2701
|
};
|
|
2639
2702
|
|
|
2640
2703
|
// eslint/rules/locales-location.ts
|
|
2641
|
-
import
|
|
2704
|
+
import path23 from "path";
|
|
2642
2705
|
function isLocalesFile(filename) {
|
|
2643
|
-
const segments =
|
|
2706
|
+
const segments = path23.resolve(filename).split(path23.sep);
|
|
2644
2707
|
const srcIdx = segments.lastIndexOf("src");
|
|
2645
2708
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
2646
2709
|
}
|
|
@@ -2659,7 +2722,7 @@ var localesLocationRule = {
|
|
|
2659
2722
|
create(context) {
|
|
2660
2723
|
if (isLocalesFile(context.filename)) return {};
|
|
2661
2724
|
const filename = context.filename;
|
|
2662
|
-
const segments =
|
|
2725
|
+
const segments = path23.resolve(filename).split(path23.sep);
|
|
2663
2726
|
const srcIdx = segments.lastIndexOf("src");
|
|
2664
2727
|
if (srcIdx === -1) return {};
|
|
2665
2728
|
const folder = segments[srcIdx + 1];
|
|
@@ -2681,7 +2744,7 @@ var localesLocationRule = {
|
|
|
2681
2744
|
};
|
|
2682
2745
|
|
|
2683
2746
|
// eslint/rules/hook-extraction.ts
|
|
2684
|
-
import
|
|
2747
|
+
import path24 from "path";
|
|
2685
2748
|
var hookExtractionRule = {
|
|
2686
2749
|
meta: {
|
|
2687
2750
|
schema: [],
|
|
@@ -2691,15 +2754,15 @@ var hookExtractionRule = {
|
|
|
2691
2754
|
}
|
|
2692
2755
|
},
|
|
2693
2756
|
create(context) {
|
|
2694
|
-
const
|
|
2695
|
-
const file =
|
|
2696
|
-
const segments = segmentsOf(file,
|
|
2757
|
+
const sourceRoot = sourceRootOf(context);
|
|
2758
|
+
const file = path24.resolve(context.filename);
|
|
2759
|
+
const segments = segmentsOf(file, sourceRoot);
|
|
2697
2760
|
if (segments.length === 0) return {};
|
|
2698
|
-
const index = getProjectIndex(
|
|
2761
|
+
const index = getProjectIndex(sourceRoot);
|
|
2699
2762
|
if (!index) return {};
|
|
2700
2763
|
const module = index.modules.get(file);
|
|
2701
2764
|
if (!module) return {};
|
|
2702
|
-
const folder = folderSegmentsOf(file,
|
|
2765
|
+
const folder = folderSegmentsOf(file, sourceRoot);
|
|
2703
2766
|
const alreadyInHooksFolder = folder.length > 0 && folder[folder.length - 1] === "hooks";
|
|
2704
2767
|
const reusedHooks = module.exports.filter((exp) => exp.kind === "hook" && !alreadyInHooksFolder).map((exp) => ({ exp, consumers: index.symbolConsumers.get(symbolKey(file, exp.name)) })).filter(({ consumers }) => (consumers?.size ?? 0) >= 2);
|
|
2705
2768
|
if (reusedHooks.length === 0) return {};
|
|
@@ -2718,7 +2781,7 @@ var hookExtractionRule = {
|
|
|
2718
2781
|
};
|
|
2719
2782
|
|
|
2720
2783
|
// eslint/rules/value-extraction.ts
|
|
2721
|
-
import
|
|
2784
|
+
import path25 from "path";
|
|
2722
2785
|
var valueExtractionRule = {
|
|
2723
2786
|
meta: {
|
|
2724
2787
|
schema: [],
|
|
@@ -2728,21 +2791,21 @@ var valueExtractionRule = {
|
|
|
2728
2791
|
}
|
|
2729
2792
|
},
|
|
2730
2793
|
create(context) {
|
|
2731
|
-
const
|
|
2732
|
-
const file =
|
|
2733
|
-
const segments = segmentsOf(file,
|
|
2794
|
+
const sourceRoot = sourceRootOf(context);
|
|
2795
|
+
const file = path25.resolve(context.filename);
|
|
2796
|
+
const segments = segmentsOf(file, sourceRoot);
|
|
2734
2797
|
if (segments.length === 0 || segments[0] !== "app") return {};
|
|
2735
|
-
const index = getProjectIndex(
|
|
2798
|
+
const index = getProjectIndex(sourceRoot);
|
|
2736
2799
|
if (!index) return {};
|
|
2737
2800
|
const consumers = [...index.consumers.get(file) ?? []];
|
|
2738
|
-
const outsideApp = consumers.filter((consumer) => segmentsOf(consumer,
|
|
2801
|
+
const outsideApp = consumers.filter((consumer) => segmentsOf(consumer, sourceRoot)[0] !== "app");
|
|
2739
2802
|
if (outsideApp.length === 0) return {};
|
|
2740
2803
|
return {
|
|
2741
2804
|
Program(node) {
|
|
2742
2805
|
context.report({
|
|
2743
2806
|
node,
|
|
2744
2807
|
loc: { line: 1, column: 0 },
|
|
2745
|
-
message: `This file in src/app/ is imported by ${describeConsumers(outsideApp,
|
|
2808
|
+
message: `This file in src/app/ is imported by ${describeConsumers(outsideApp, sourceRoot)} outside src/app/. Extract the value to a shared or feature folder so it can be imported independently. See docs/code-organization-guide/rules/constants-rule.md`
|
|
2746
2809
|
});
|
|
2747
2810
|
}
|
|
2748
2811
|
};
|
|
@@ -2750,7 +2813,7 @@ var valueExtractionRule = {
|
|
|
2750
2813
|
};
|
|
2751
2814
|
|
|
2752
2815
|
// eslint/rules/config-extraction.ts
|
|
2753
|
-
import
|
|
2816
|
+
import path26 from "path";
|
|
2754
2817
|
var configExtractionRule = {
|
|
2755
2818
|
meta: {
|
|
2756
2819
|
schema: [],
|
|
@@ -2760,12 +2823,12 @@ var configExtractionRule = {
|
|
|
2760
2823
|
}
|
|
2761
2824
|
},
|
|
2762
2825
|
create(context) {
|
|
2763
|
-
const
|
|
2764
|
-
const file =
|
|
2765
|
-
const segments = segmentsOf(file,
|
|
2826
|
+
const sourceRoot = sourceRootOf(context);
|
|
2827
|
+
const file = path26.resolve(context.filename);
|
|
2828
|
+
const segments = segmentsOf(file, sourceRoot);
|
|
2766
2829
|
if (segments.length < 3 || segments[0] !== "config") return {};
|
|
2767
2830
|
if (SUPPORT_FOLDERS2.has(segments[2] ?? "")) return {};
|
|
2768
|
-
const index = getProjectIndex(
|
|
2831
|
+
const index = getProjectIndex(sourceRoot);
|
|
2769
2832
|
if (!index) return {};
|
|
2770
2833
|
const module = index.modules.get(file);
|
|
2771
2834
|
if (!module) return {};
|
|
@@ -2774,11 +2837,11 @@ var configExtractionRule = {
|
|
|
2774
2837
|
const findings = [];
|
|
2775
2838
|
for (const exp of suspects) {
|
|
2776
2839
|
const consumers = [...index.symbolConsumers.get(symbolKey(file, exp.name)) ?? []];
|
|
2777
|
-
const outsideConfig = consumers.filter((consumer) => segmentsOf(consumer,
|
|
2840
|
+
const outsideConfig = consumers.filter((consumer) => segmentsOf(consumer, sourceRoot)[0] !== "config");
|
|
2778
2841
|
if (outsideConfig.length > 0) {
|
|
2779
2842
|
findings.push({
|
|
2780
2843
|
line: exp.line,
|
|
2781
|
-
message: `${exp.kind} "${exp.name}" in a configuration module is imported by ${describeConsumers(outsideConfig,
|
|
2844
|
+
message: `${exp.kind} "${exp.name}" in a configuration module is imported by ${describeConsumers(outsideConfig, sourceRoot)} outside src/config/. Move it to the matching root support folder. See docs/code-organization-guide/rules/configuration-rule.md`
|
|
2782
2845
|
});
|
|
2783
2846
|
} else if (consumers.length > 0) {
|
|
2784
2847
|
findings.push({
|
|
@@ -2799,7 +2862,7 @@ var configExtractionRule = {
|
|
|
2799
2862
|
};
|
|
2800
2863
|
|
|
2801
2864
|
// eslint/rules/component-nesting.ts
|
|
2802
|
-
import
|
|
2865
|
+
import path27 from "path";
|
|
2803
2866
|
var componentNestingRule = {
|
|
2804
2867
|
meta: {
|
|
2805
2868
|
schema: [],
|
|
@@ -2809,18 +2872,18 @@ var componentNestingRule = {
|
|
|
2809
2872
|
}
|
|
2810
2873
|
},
|
|
2811
2874
|
create(context) {
|
|
2812
|
-
const
|
|
2813
|
-
const file =
|
|
2814
|
-
const segments = segmentsOf(file,
|
|
2875
|
+
const sourceRoot = sourceRootOf(context);
|
|
2876
|
+
const file = path27.resolve(context.filename);
|
|
2877
|
+
const segments = segmentsOf(file, sourceRoot);
|
|
2815
2878
|
if (segments.length !== 4 || segments[0] !== "features") return {};
|
|
2816
|
-
const index = getProjectIndex(
|
|
2879
|
+
const index = getProjectIndex(sourceRoot);
|
|
2817
2880
|
if (!index) return {};
|
|
2818
2881
|
const module = index.modules.get(file);
|
|
2819
2882
|
if (!module?.exports.some((exp) => exp.kind === "component")) return {};
|
|
2820
2883
|
const folder = segments.slice(0, 3);
|
|
2821
2884
|
const hasChildComponent = [...index.modules.values()].some((candidate) => {
|
|
2822
2885
|
if (candidate.file === file) return false;
|
|
2823
|
-
const candidateSegments = segmentsOf(candidate.file,
|
|
2886
|
+
const candidateSegments = segmentsOf(candidate.file, sourceRoot);
|
|
2824
2887
|
if (candidateSegments.length !== 4) return false;
|
|
2825
2888
|
if (candidateSegments[0] !== folder[0] || candidateSegments[1] !== folder[1] || candidateSegments[2] !== folder[2]) {
|
|
2826
2889
|
return false;
|
|
@@ -2843,7 +2906,7 @@ var componentNestingRule = {
|
|
|
2843
2906
|
};
|
|
2844
2907
|
|
|
2845
2908
|
// eslint/rules/stay-flat.ts
|
|
2846
|
-
import
|
|
2909
|
+
import path28 from "path";
|
|
2847
2910
|
var stayFlatRule = {
|
|
2848
2911
|
meta: {
|
|
2849
2912
|
schema: [],
|
|
@@ -2853,11 +2916,11 @@ var stayFlatRule = {
|
|
|
2853
2916
|
}
|
|
2854
2917
|
},
|
|
2855
2918
|
create(context) {
|
|
2856
|
-
const
|
|
2857
|
-
const file =
|
|
2858
|
-
const segments = segmentsOf(file,
|
|
2919
|
+
const sourceRoot = sourceRootOf(context);
|
|
2920
|
+
const file = path28.resolve(context.filename);
|
|
2921
|
+
const segments = segmentsOf(file, sourceRoot);
|
|
2859
2922
|
if (segments.length !== 3 || segments[0] !== "features") return {};
|
|
2860
|
-
const index = getProjectIndex(
|
|
2923
|
+
const index = getProjectIndex(sourceRoot);
|
|
2861
2924
|
if (!index) return {};
|
|
2862
2925
|
const module = index.modules.get(file);
|
|
2863
2926
|
if (!module?.exports.some((exp) => exp.kind === "component")) return {};
|
|
@@ -2865,7 +2928,7 @@ var stayFlatRule = {
|
|
|
2865
2928
|
if (!featureName) return {};
|
|
2866
2929
|
const exclusiveChildren = [...index.modules.values()].filter((candidate) => {
|
|
2867
2930
|
if (candidate.file === file) return false;
|
|
2868
|
-
const candidateSegments = segmentsOf(candidate.file,
|
|
2931
|
+
const candidateSegments = segmentsOf(candidate.file, sourceRoot);
|
|
2869
2932
|
if (candidateSegments.length !== 3) return false;
|
|
2870
2933
|
if (candidateSegments[0] !== "features" || candidateSegments[1] !== featureName) return false;
|
|
2871
2934
|
if (candidateSegments[2]?.startsWith("index.")) return false;
|
|
@@ -2874,7 +2937,7 @@ var stayFlatRule = {
|
|
|
2874
2937
|
if (consumers.size === 0) return false;
|
|
2875
2938
|
return [...consumers].every((consumer) => {
|
|
2876
2939
|
if (consumer === file) return true;
|
|
2877
|
-
const consumerSegments = segmentsOf(consumer,
|
|
2940
|
+
const consumerSegments = segmentsOf(consumer, sourceRoot);
|
|
2878
2941
|
if (consumerSegments[0] !== "features" || consumerSegments[1] !== featureName) return false;
|
|
2879
2942
|
return !(index.modules.get(consumer)?.exports.some((exp) => exp.kind === "component") ?? false);
|
|
2880
2943
|
});
|
|
@@ -2894,7 +2957,7 @@ var stayFlatRule = {
|
|
|
2894
2957
|
};
|
|
2895
2958
|
|
|
2896
2959
|
// eslint/rules/type-extraction.ts
|
|
2897
|
-
import
|
|
2960
|
+
import path29 from "path";
|
|
2898
2961
|
var typeExtractionRule = {
|
|
2899
2962
|
meta: {
|
|
2900
2963
|
schema: [],
|
|
@@ -2904,11 +2967,11 @@ var typeExtractionRule = {
|
|
|
2904
2967
|
}
|
|
2905
2968
|
},
|
|
2906
2969
|
create(context) {
|
|
2907
|
-
const
|
|
2908
|
-
const file =
|
|
2909
|
-
const segments = segmentsOf(file,
|
|
2970
|
+
const sourceRoot = sourceRootOf(context);
|
|
2971
|
+
const file = path29.resolve(context.filename);
|
|
2972
|
+
const segments = segmentsOf(file, sourceRoot);
|
|
2910
2973
|
if (segments.length === 0) return {};
|
|
2911
|
-
const index = getProjectIndex(
|
|
2974
|
+
const index = getProjectIndex(sourceRoot);
|
|
2912
2975
|
if (!index) return {};
|
|
2913
2976
|
const module = index.modules.get(file);
|
|
2914
2977
|
if (!module) return {};
|
|
@@ -2925,7 +2988,7 @@ var typeExtractionRule = {
|
|
|
2925
2988
|
if (independent2.length === 0) continue;
|
|
2926
2989
|
findings.push({
|
|
2927
2990
|
line: exp.line,
|
|
2928
|
-
message: `${exp.kind} "${exp.name}" is imported by ${describeConsumers(independent2,
|
|
2991
|
+
message: `${exp.kind} "${exp.name}" is imported by ${describeConsumers(independent2, sourceRoot)} without the component "${componentExport.name}" that defines it. Extract it to a types/ or schemas/ folder. See docs/code-organization-guide/rules/types-and-schemas-rule.md`
|
|
2929
2992
|
});
|
|
2930
2993
|
continue;
|
|
2931
2994
|
}
|
|
@@ -2936,7 +2999,7 @@ var typeExtractionRule = {
|
|
|
2936
2999
|
if (independent.length === 0) continue;
|
|
2937
3000
|
findings.push({
|
|
2938
3001
|
line: exp.line,
|
|
2939
|
-
message: `${exp.kind} "${exp.name}" is imported by ${describeConsumers(independent,
|
|
3002
|
+
message: `${exp.kind} "${exp.name}" is imported by ${describeConsumers(independent, sourceRoot)} without using the code in this file. Extract it to a types/ or schemas/ folder. See docs/code-organization-guide/rules/types-and-schemas-rule.md`
|
|
2940
3003
|
});
|
|
2941
3004
|
}
|
|
2942
3005
|
if (findings.length === 0) return {};
|
|
@@ -2951,7 +3014,7 @@ var typeExtractionRule = {
|
|
|
2951
3014
|
};
|
|
2952
3015
|
|
|
2953
3016
|
// eslint/rules/locale-placement.ts
|
|
2954
|
-
import
|
|
3017
|
+
import path30 from "path";
|
|
2955
3018
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2956
3019
|
import ts5 from "typescript";
|
|
2957
3020
|
var LOCALE_ACCESS = /\blocales\.(?<key>[A-Za-z_$][\w$]*)/g;
|
|
@@ -2998,14 +3061,14 @@ var localePlacementRule = {
|
|
|
2998
3061
|
}
|
|
2999
3062
|
},
|
|
3000
3063
|
create(context) {
|
|
3001
|
-
const
|
|
3002
|
-
const file =
|
|
3003
|
-
const segments = segmentsOf(file,
|
|
3064
|
+
const sourceRoot = sourceRootOf(context);
|
|
3065
|
+
const file = path30.resolve(context.filename);
|
|
3066
|
+
const segments = segmentsOf(file, sourceRoot);
|
|
3004
3067
|
if (segments.length === 0) return {};
|
|
3005
|
-
const index = getProjectIndex(
|
|
3068
|
+
const index = getProjectIndex(sourceRoot);
|
|
3006
3069
|
if (!index) return {};
|
|
3007
3070
|
const localesFile = [...index.modules.keys()].find((candidate) => {
|
|
3008
|
-
const candidateSegments = segmentsOf(candidate,
|
|
3071
|
+
const candidateSegments = segmentsOf(candidate, sourceRoot);
|
|
3009
3072
|
return candidateSegments.length === 2 && candidateSegments[0] === "locales" && candidateSegments[1]?.startsWith("index.");
|
|
3010
3073
|
});
|
|
3011
3074
|
if (!localesFile || file !== localesFile) return {};
|
|
@@ -3017,10 +3080,10 @@ var localePlacementRule = {
|
|
|
3017
3080
|
for (const [candidateFile, module] of index.modules) {
|
|
3018
3081
|
if (candidateFile === localesFile) continue;
|
|
3019
3082
|
const importsLocales = module.imports.some(
|
|
3020
|
-
(moduleImport) => resolveSpecifier(candidateFile, moduleImport.specifier,
|
|
3083
|
+
(moduleImport) => resolveSpecifier(candidateFile, moduleImport.specifier, sourceRoot) === localesFile
|
|
3021
3084
|
);
|
|
3022
3085
|
if (!importsLocales) continue;
|
|
3023
|
-
const candidateSegments = segmentsOf(candidateFile,
|
|
3086
|
+
const candidateSegments = segmentsOf(candidateFile, sourceRoot);
|
|
3024
3087
|
for (const match of readFileSync3(candidateFile, "utf8").matchAll(LOCALE_ACCESS)) {
|
|
3025
3088
|
const key = match.groups?.key;
|
|
3026
3089
|
if (key === void 0) continue;
|
|
@@ -3045,7 +3108,7 @@ var localePlacementRule = {
|
|
|
3045
3108
|
if (current?.kind === "nested") {
|
|
3046
3109
|
findings.push({
|
|
3047
3110
|
line: current.line,
|
|
3048
|
-
message: `Locale "${key}" is read by ${describeConsumers([...readers],
|
|
3111
|
+
message: `Locale "${key}" is read by ${describeConsumers([...readers], sourceRoot)} and must live at the top level of locales. See docs/code-organization-guide/rules/locales-rule.md`
|
|
3049
3112
|
});
|
|
3050
3113
|
}
|
|
3051
3114
|
continue;
|
|
@@ -3077,7 +3140,7 @@ var localePlacementRule = {
|
|
|
3077
3140
|
};
|
|
3078
3141
|
|
|
3079
3142
|
// eslint/rules/sole-state-owner.ts
|
|
3080
|
-
import
|
|
3143
|
+
import path31 from "path";
|
|
3081
3144
|
import ts6 from "typescript";
|
|
3082
3145
|
function findStateHooks(node) {
|
|
3083
3146
|
const hooks = [];
|
|
@@ -3157,7 +3220,7 @@ var soleStateOwnerRule = {
|
|
|
3157
3220
|
}
|
|
3158
3221
|
},
|
|
3159
3222
|
create(context) {
|
|
3160
|
-
const filename =
|
|
3223
|
+
const filename = path31.resolve(context.filename);
|
|
3161
3224
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
|
|
3162
3225
|
const text = context.sourceCode.text;
|
|
3163
3226
|
const components = parseComponentInfo(text, filename);
|
|
@@ -3236,7 +3299,7 @@ function usesOutsideJsx(declaration, hook, children) {
|
|
|
3236
3299
|
}
|
|
3237
3300
|
|
|
3238
3301
|
// eslint/rules/locale-key-shape.ts
|
|
3239
|
-
import
|
|
3302
|
+
import path32 from "path";
|
|
3240
3303
|
var MAX_KEY_LENGTH = 30;
|
|
3241
3304
|
var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
|
|
3242
3305
|
"Button",
|
|
@@ -3284,8 +3347,9 @@ var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
|
|
|
3284
3347
|
"ScrollBar"
|
|
3285
3348
|
]);
|
|
3286
3349
|
var CAMEL_CASE = /^[a-z][a-zA-Z0-9]*$/;
|
|
3350
|
+
var ENGLISH = /^[A-Za-z0-9_]*$/;
|
|
3287
3351
|
function isLocalesFile2(filename) {
|
|
3288
|
-
const segments =
|
|
3352
|
+
const segments = path32.resolve(filename).split(path32.sep);
|
|
3289
3353
|
const srcIdx = segments.lastIndexOf("src");
|
|
3290
3354
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
3291
3355
|
}
|
|
@@ -3298,6 +3362,14 @@ function reportFor(context, node, message) {
|
|
|
3298
3362
|
context.report({ node, message });
|
|
3299
3363
|
}
|
|
3300
3364
|
function checkKey(context, node, name) {
|
|
3365
|
+
if (!ENGLISH.test(name)) {
|
|
3366
|
+
reportFor(
|
|
3367
|
+
context,
|
|
3368
|
+
node,
|
|
3369
|
+
`Locale key "${name}" must be English; write it in the Latin alphabet. See docs/code-organization-guide/rules/locales-rule.md`
|
|
3370
|
+
);
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3301
3373
|
if (!CAMEL_CASE.test(name)) {
|
|
3302
3374
|
reportFor(
|
|
3303
3375
|
context,
|
|
@@ -3348,7 +3420,7 @@ var localeKeyShapeRule = {
|
|
|
3348
3420
|
};
|
|
3349
3421
|
|
|
3350
3422
|
// eslint/rules/shared-style-dedup.ts
|
|
3351
|
-
import
|
|
3423
|
+
import path33 from "path";
|
|
3352
3424
|
import { readFileSync as readFileSync4, statSync as statSync3 } from "fs";
|
|
3353
3425
|
var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
|
|
3354
3426
|
var comboCache;
|
|
@@ -3389,11 +3461,11 @@ var sharedStyleDedupRule = {
|
|
|
3389
3461
|
}
|
|
3390
3462
|
},
|
|
3391
3463
|
create(context) {
|
|
3392
|
-
const
|
|
3393
|
-
const file =
|
|
3394
|
-
const segments = segmentsOf(file,
|
|
3464
|
+
const sourceRoot = sourceRootOf(context);
|
|
3465
|
+
const file = path33.resolve(context.filename);
|
|
3466
|
+
const segments = segmentsOf(file, sourceRoot);
|
|
3395
3467
|
if (segments.length === 0) return {};
|
|
3396
|
-
const index = getProjectIndex(
|
|
3468
|
+
const index = getProjectIndex(sourceRoot);
|
|
3397
3469
|
if (!index) return {};
|
|
3398
3470
|
const combos = combosFor(index);
|
|
3399
3471
|
const sharedHere = [...combos.entries()].filter(([, users]) => users.size >= 2 && users.has(file)).filter(([, users]) => [...users].sort((left, right) => left.localeCompare(right))[0] === file);
|
|
@@ -3594,7 +3666,7 @@ var zodSchemaValidationRule = {
|
|
|
3594
3666
|
};
|
|
3595
3667
|
|
|
3596
3668
|
// eslint/rules/source-under-src.ts
|
|
3597
|
-
import
|
|
3669
|
+
import path34 from "path";
|
|
3598
3670
|
var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
|
|
3599
3671
|
".agents",
|
|
3600
3672
|
".cache",
|
|
@@ -3635,14 +3707,14 @@ var sourceUnderSrcRule = {
|
|
|
3635
3707
|
}
|
|
3636
3708
|
},
|
|
3637
3709
|
create(context) {
|
|
3638
|
-
const filename =
|
|
3710
|
+
const filename = path34.resolve(context.filename);
|
|
3639
3711
|
if (!MODULE_EXTENSION.test(filename)) return {};
|
|
3640
|
-
const relative =
|
|
3712
|
+
const relative = path34.relative(context.cwd, filename).replace(/\\/g, "/");
|
|
3641
3713
|
if (relative === "src" || relative.startsWith("src/")) return {};
|
|
3642
3714
|
const topLevel = relative.split("/")[0] ?? "";
|
|
3643
3715
|
if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
|
|
3644
3716
|
if (!relative.includes("/")) {
|
|
3645
|
-
const basename =
|
|
3717
|
+
const basename = path34.basename(filename);
|
|
3646
3718
|
if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
|
|
3647
3719
|
}
|
|
3648
3720
|
return {
|
|
@@ -3659,7 +3731,7 @@ var sourceUnderSrcRule = {
|
|
|
3659
3731
|
|
|
3660
3732
|
// eslint/rules/zirka-baseline.ts
|
|
3661
3733
|
import fs4 from "fs";
|
|
3662
|
-
import
|
|
3734
|
+
import path35 from "path";
|
|
3663
3735
|
var ESLINT_CONFIG = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
|
|
3664
3736
|
var PRETTIER_CONFIGS = [
|
|
3665
3737
|
"prettier.config.mjs",
|
|
@@ -3678,10 +3750,10 @@ var zirkaBaselineRule = {
|
|
|
3678
3750
|
}
|
|
3679
3751
|
},
|
|
3680
3752
|
create(context) {
|
|
3681
|
-
const filename =
|
|
3682
|
-
const basename =
|
|
3753
|
+
const filename = path35.resolve(context.filename);
|
|
3754
|
+
const basename = path35.basename(filename);
|
|
3683
3755
|
if (!ESLINT_CONFIG.test(basename)) return {};
|
|
3684
|
-
const projectRoot =
|
|
3756
|
+
const projectRoot = path35.dirname(filename);
|
|
3685
3757
|
const report3 = (message) => {
|
|
3686
3758
|
context.report({
|
|
3687
3759
|
node: context.sourceCode.ast,
|
|
@@ -3696,7 +3768,7 @@ var zirkaBaselineRule = {
|
|
|
3696
3768
|
'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
|
|
3697
3769
|
);
|
|
3698
3770
|
}
|
|
3699
|
-
const tsconfigPath =
|
|
3771
|
+
const tsconfigPath = path35.join(projectRoot, "tsconfig.json");
|
|
3700
3772
|
if (!fs4.existsSync(tsconfigPath)) {
|
|
3701
3773
|
report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
|
|
3702
3774
|
} else {
|
|
@@ -3714,13 +3786,13 @@ var zirkaBaselineRule = {
|
|
|
3714
3786
|
report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
|
|
3715
3787
|
}
|
|
3716
3788
|
}
|
|
3717
|
-
const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs4.existsSync(
|
|
3789
|
+
const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs4.existsSync(path35.join(projectRoot, name)));
|
|
3718
3790
|
if (!prettierConfigFile) {
|
|
3719
3791
|
report3(
|
|
3720
3792
|
"No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
|
|
3721
3793
|
);
|
|
3722
3794
|
} else {
|
|
3723
|
-
const content = fs4.readFileSync(
|
|
3795
|
+
const content = fs4.readFileSync(path35.join(projectRoot, prettierConfigFile), "utf8");
|
|
3724
3796
|
if (!content.includes("zirka")) {
|
|
3725
3797
|
report3(
|
|
3726
3798
|
"The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
|
|
@@ -3790,7 +3862,7 @@ var docKindSuffixRule = {
|
|
|
3790
3862
|
};
|
|
3791
3863
|
|
|
3792
3864
|
// eslint/rules/documentation/title-matches-file-name.ts
|
|
3793
|
-
import
|
|
3865
|
+
import path36 from "path";
|
|
3794
3866
|
function toExpectedFileName(title) {
|
|
3795
3867
|
return `${title.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
|
|
3796
3868
|
}
|
|
@@ -3810,7 +3882,7 @@ var titleMatchesFileNameRule = {
|
|
|
3810
3882
|
if (!filename.endsWith(".md")) return;
|
|
3811
3883
|
const title = getTextContent(node).trim();
|
|
3812
3884
|
const expectedFileName = toExpectedFileName(title);
|
|
3813
|
-
const actualFileName =
|
|
3885
|
+
const actualFileName = path36.basename(filename);
|
|
3814
3886
|
if (!title) {
|
|
3815
3887
|
context.report({
|
|
3816
3888
|
node,
|
|
@@ -4217,7 +4289,7 @@ var referenceBlockHeadingsRule = {
|
|
|
4217
4289
|
};
|
|
4218
4290
|
|
|
4219
4291
|
// eslint/rules/documentation/support-document-placement.ts
|
|
4220
|
-
import
|
|
4292
|
+
import path37 from "path";
|
|
4221
4293
|
var supportDocumentPlacementRule = {
|
|
4222
4294
|
meta: {
|
|
4223
4295
|
type: "problem",
|
|
@@ -4231,7 +4303,7 @@ var supportDocumentPlacementRule = {
|
|
|
4231
4303
|
root(node) {
|
|
4232
4304
|
const filename = getFilename(context);
|
|
4233
4305
|
if (!filename.endsWith(".md")) return;
|
|
4234
|
-
const parentFolder =
|
|
4306
|
+
const parentFolder = path37.basename(path37.dirname(filename));
|
|
4235
4307
|
if (filename.endsWith("-rule.md") && parentFolder !== "rules") {
|
|
4236
4308
|
context.report({
|
|
4237
4309
|
node,
|
|
@@ -4274,11 +4346,11 @@ var noTemplatePromptRule = {
|
|
|
4274
4346
|
};
|
|
4275
4347
|
|
|
4276
4348
|
// eslint/rules/documentation/guide-folder-entry-point.ts
|
|
4277
|
-
import
|
|
4349
|
+
import path39 from "path";
|
|
4278
4350
|
|
|
4279
4351
|
// eslint/rules/documentation/project-index.ts
|
|
4280
4352
|
import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
4281
|
-
import
|
|
4353
|
+
import path38 from "path";
|
|
4282
4354
|
var KIND_BY_SUFFIX = [
|
|
4283
4355
|
["-rule.md", "rule"],
|
|
4284
4356
|
["-guide.md", "guide"],
|
|
@@ -4287,7 +4359,7 @@ var KIND_BY_SUFFIX = [
|
|
|
4287
4359
|
];
|
|
4288
4360
|
function listMarkdownFiles(dir) {
|
|
4289
4361
|
return readdirSync3(dir).flatMap((entry) => {
|
|
4290
|
-
const entryPath =
|
|
4362
|
+
const entryPath = path38.join(dir, entry);
|
|
4291
4363
|
if (statSync4(entryPath).isDirectory()) {
|
|
4292
4364
|
return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
|
|
4293
4365
|
}
|
|
@@ -4305,11 +4377,11 @@ function getProjectDocs(docsRoot) {
|
|
|
4305
4377
|
if (cached) return cached;
|
|
4306
4378
|
const files = listMarkdownFiles(docsRoot);
|
|
4307
4379
|
const docs = files.sort((a, b) => a.localeCompare(b)).map((filePath) => {
|
|
4308
|
-
const fileName =
|
|
4380
|
+
const fileName = path38.basename(filePath);
|
|
4309
4381
|
const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
|
|
4310
4382
|
return {
|
|
4311
4383
|
filePath,
|
|
4312
|
-
doc:
|
|
4384
|
+
doc: path38.relative(docsRoot, filePath).split(path38.sep).join("/"),
|
|
4313
4385
|
fileName,
|
|
4314
4386
|
kind,
|
|
4315
4387
|
title: extractTitle(filePath)
|
|
@@ -4319,12 +4391,12 @@ function getProjectDocs(docsRoot) {
|
|
|
4319
4391
|
return docs;
|
|
4320
4392
|
}
|
|
4321
4393
|
function findDocsRoot(filePath) {
|
|
4322
|
-
let dir =
|
|
4394
|
+
let dir = path38.dirname(filePath);
|
|
4323
4395
|
for (; ; ) {
|
|
4324
|
-
if (
|
|
4396
|
+
if (path38.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
|
|
4325
4397
|
return dir;
|
|
4326
4398
|
}
|
|
4327
|
-
const parent =
|
|
4399
|
+
const parent = path38.dirname(dir);
|
|
4328
4400
|
if (parent === dir) return void 0;
|
|
4329
4401
|
dir = parent;
|
|
4330
4402
|
}
|
|
@@ -4348,13 +4420,13 @@ var guideFolderEntryPointRule = {
|
|
|
4348
4420
|
if (!docsRoot) return;
|
|
4349
4421
|
const docs = getProjectDocs(docsRoot);
|
|
4350
4422
|
const guideFolders = new Set(
|
|
4351
|
-
docs.filter((doc) => ["rules", "references"].includes(
|
|
4423
|
+
docs.filter((doc) => ["rules", "references"].includes(path39.basename(path39.dirname(doc.filePath)))).map((doc) => path39.dirname(path39.dirname(doc.filePath))).filter((folder) => path39.resolve(folder) !== path39.resolve(docsRoot))
|
|
4352
4424
|
);
|
|
4353
|
-
const currentDir =
|
|
4425
|
+
const currentDir = path39.dirname(filename);
|
|
4354
4426
|
if (guideFolders.has(currentDir)) {
|
|
4355
|
-
const expectedEntryPoint = `${
|
|
4427
|
+
const expectedEntryPoint = `${path39.basename(currentDir)}.md`;
|
|
4356
4428
|
const hasEntryPoint = docs.some(
|
|
4357
|
-
(doc) => doc.kind === "guide" &&
|
|
4429
|
+
(doc) => doc.kind === "guide" && path39.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
|
|
4358
4430
|
);
|
|
4359
4431
|
if (!hasEntryPoint) {
|
|
4360
4432
|
context.report({
|
|
@@ -4579,7 +4651,7 @@ var noNestedHowToRule = {
|
|
|
4579
4651
|
|
|
4580
4652
|
// eslint/rules/documentation/glossary-term-linking.ts
|
|
4581
4653
|
import { readFileSync as readFileSync6 } from "fs";
|
|
4582
|
-
import
|
|
4654
|
+
import path40 from "path";
|
|
4583
4655
|
function extractGlossaryTerms(filePath) {
|
|
4584
4656
|
const content = readFileSync6(filePath, "utf8");
|
|
4585
4657
|
const terms = [];
|
|
@@ -4633,9 +4705,9 @@ var glossaryTermLinkingRule = {
|
|
|
4633
4705
|
const docsRoot = findDocsRoot(filename);
|
|
4634
4706
|
if (!docsRoot) return;
|
|
4635
4707
|
const docs = getProjectDocs(docsRoot);
|
|
4636
|
-
const guideDir =
|
|
4708
|
+
const guideDir = path40.dirname(filename);
|
|
4637
4709
|
const guideReferences = docs.filter(
|
|
4638
|
-
(doc) => doc.kind === "reference" &&
|
|
4710
|
+
(doc) => doc.kind === "reference" && path40.dirname(doc.filePath) === guideDir
|
|
4639
4711
|
);
|
|
4640
4712
|
if (guideReferences.length === 0) return;
|
|
4641
4713
|
const glossaryTerms = [];
|
|
@@ -4660,7 +4732,7 @@ var glossaryTermLinkingRule = {
|
|
|
4660
4732
|
|
|
4661
4733
|
// eslint/rules/documentation/guide-mentions-documents.ts
|
|
4662
4734
|
import { existsSync } from "fs";
|
|
4663
|
-
import
|
|
4735
|
+
import path41 from "path";
|
|
4664
4736
|
function visitSteps3(node, check) {
|
|
4665
4737
|
if (node.type === "list" && node.ordered) {
|
|
4666
4738
|
for (const child of node.children) check(child);
|
|
@@ -4693,12 +4765,12 @@ var guideMentionsDocumentsRule = {
|
|
|
4693
4765
|
if (!filename.endsWith("-guide.md")) return;
|
|
4694
4766
|
const docsRoot = findDocsRoot(filename);
|
|
4695
4767
|
if (!docsRoot) return;
|
|
4696
|
-
const guideDir =
|
|
4697
|
-
if (
|
|
4768
|
+
const guideDir = path41.dirname(filename);
|
|
4769
|
+
if (path41.basename(filename, ".md") !== path41.basename(guideDir)) return;
|
|
4698
4770
|
const docs = getProjectDocs(docsRoot);
|
|
4699
4771
|
const owned = docs.filter((doc) => {
|
|
4700
|
-
const parent =
|
|
4701
|
-
return parent ===
|
|
4772
|
+
const parent = path41.dirname(doc.filePath);
|
|
4773
|
+
return parent === path41.join(guideDir, "rules") || parent === path41.join(guideDir, "references");
|
|
4702
4774
|
});
|
|
4703
4775
|
const allLinks = [];
|
|
4704
4776
|
collectMarkdownLinks(node, allLinks);
|
|
@@ -4725,7 +4797,7 @@ var guideMentionsDocumentsRule = {
|
|
|
4725
4797
|
for (const link of allLinks) {
|
|
4726
4798
|
const target = linkTarget(link.url);
|
|
4727
4799
|
if (!target.endsWith(".md")) continue;
|
|
4728
|
-
const resolved =
|
|
4800
|
+
const resolved = path41.normalize(path41.join(guideDir, target));
|
|
4729
4801
|
if (!existsSync(resolved)) {
|
|
4730
4802
|
context.report({
|
|
4731
4803
|
node: link,
|
|
@@ -5166,32 +5238,170 @@ var themeVariableNamespaceRule = {
|
|
|
5166
5238
|
}
|
|
5167
5239
|
};
|
|
5168
5240
|
|
|
5169
|
-
// eslint/rules/tailwind/
|
|
5170
|
-
|
|
5241
|
+
// eslint/rules/tailwind/css-entry-point.ts
|
|
5242
|
+
import { statSync as statSync6 } from "fs";
|
|
5243
|
+
import path44 from "path";
|
|
5244
|
+
|
|
5245
|
+
// eslint/rules/tailwind/source-files.ts
|
|
5246
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
5247
|
+
import path42 from "path";
|
|
5248
|
+
var CSS_EXTENSIONS = [".css"];
|
|
5249
|
+
var MODULE_EXTENSIONS3 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
5250
|
+
var SOURCE_EXTENSIONS = [...MODULE_EXTENSIONS3, ...CSS_EXTENSIONS];
|
|
5251
|
+
function findFiles(dir, extensions) {
|
|
5252
|
+
let entries;
|
|
5253
|
+
try {
|
|
5254
|
+
entries = readdirSync4(dir);
|
|
5255
|
+
} catch {
|
|
5256
|
+
return [];
|
|
5257
|
+
}
|
|
5258
|
+
return entries.flatMap((entry) => {
|
|
5259
|
+
if (entry.startsWith(".") || entry === "node_modules") return [];
|
|
5260
|
+
const entryPath = path42.join(dir, entry);
|
|
5261
|
+
let stats;
|
|
5262
|
+
try {
|
|
5263
|
+
stats = statSync5(entryPath);
|
|
5264
|
+
} catch {
|
|
5265
|
+
return [];
|
|
5266
|
+
}
|
|
5267
|
+
if (stats.isDirectory()) return findFiles(entryPath, extensions);
|
|
5268
|
+
return extensions.includes(path42.extname(entry)) ? [entryPath] : [];
|
|
5269
|
+
});
|
|
5270
|
+
}
|
|
5271
|
+
function cachedTextReader() {
|
|
5272
|
+
const texts = /* @__PURE__ */ new Map();
|
|
5273
|
+
return (file) => {
|
|
5274
|
+
let text = texts.get(file);
|
|
5275
|
+
if (text === void 0) {
|
|
5276
|
+
try {
|
|
5277
|
+
text = readFileSync7(file, "utf8");
|
|
5278
|
+
} catch {
|
|
5279
|
+
text = "";
|
|
5280
|
+
}
|
|
5281
|
+
texts.set(file, text);
|
|
5282
|
+
}
|
|
5283
|
+
return text;
|
|
5284
|
+
};
|
|
5285
|
+
}
|
|
5286
|
+
function escapeRegExp(text) {
|
|
5287
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5288
|
+
}
|
|
5289
|
+
|
|
5290
|
+
// eslint/rules/tailwind/stylesheet-graph.ts
|
|
5291
|
+
import path43 from "path";
|
|
5292
|
+
function registersTailwind(text) {
|
|
5293
|
+
return /@import\s+(?:url\(\s*)?["']tailwindcss["']\s*\)?/i.test(text);
|
|
5294
|
+
}
|
|
5295
|
+
function importedSpecifiers(text) {
|
|
5296
|
+
return [...text.matchAll(/@import\s+(?:url\(\s*)?["'](?<spec>[^"']+)["']/gi)].map(
|
|
5297
|
+
(match) => match.groups?.spec ?? ""
|
|
5298
|
+
);
|
|
5299
|
+
}
|
|
5300
|
+
function moduleImports(text, fileName) {
|
|
5301
|
+
const escaped = fileName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5302
|
+
return new RegExp(`(?:import|require)\\s*\\(?\\s*["'][^"']*${escaped}["']`, "i").test(text);
|
|
5303
|
+
}
|
|
5304
|
+
function resolveSpecifier2(fromFile, spec, sourceRoot) {
|
|
5305
|
+
if (spec.startsWith("/")) return path43.resolve(spec);
|
|
5306
|
+
if (spec.startsWith("./") || spec.startsWith("../")) return path43.resolve(path43.dirname(fromFile), spec);
|
|
5307
|
+
if (spec.startsWith("@/")) return path43.resolve(sourceRoot, spec.slice(2));
|
|
5308
|
+
return void 0;
|
|
5309
|
+
}
|
|
5310
|
+
function buildStylesheetGraph(options) {
|
|
5311
|
+
const { cssFiles, sourceRoot, textOf } = options;
|
|
5312
|
+
const cssSet = new Set(cssFiles.map((file) => path43.normalize(file)));
|
|
5313
|
+
const globals = cssFiles.filter((file) => registersTailwind(textOf(file)));
|
|
5314
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
5315
|
+
const queue = [...globals];
|
|
5316
|
+
for (const global of globals) reachable.add(path43.normalize(global));
|
|
5317
|
+
while (queue.length > 0) {
|
|
5318
|
+
const from = queue.shift();
|
|
5319
|
+
if (!from) continue;
|
|
5320
|
+
for (const spec of importedSpecifiers(textOf(from))) {
|
|
5321
|
+
const target = resolveSpecifier2(from, spec, sourceRoot);
|
|
5322
|
+
if (!target) continue;
|
|
5323
|
+
const normalized = path43.normalize(target);
|
|
5324
|
+
if (cssSet.has(normalized) && !reachable.has(normalized)) {
|
|
5325
|
+
reachable.add(normalized);
|
|
5326
|
+
queue.push(normalized);
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5329
|
+
}
|
|
5330
|
+
const directChildren = /* @__PURE__ */ new Set();
|
|
5331
|
+
for (const global of globals) {
|
|
5332
|
+
for (const spec of importedSpecifiers(textOf(global))) {
|
|
5333
|
+
const target = resolveSpecifier2(global, spec, sourceRoot);
|
|
5334
|
+
if (!target) continue;
|
|
5335
|
+
const normalized = path43.normalize(target);
|
|
5336
|
+
if (cssSet.has(normalized)) directChildren.add(normalized);
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
return { globals, reachable, directChildren };
|
|
5340
|
+
}
|
|
5341
|
+
|
|
5342
|
+
// eslint/rules/tailwind/css-entry-point.ts
|
|
5343
|
+
var cssEntryPointRule = {
|
|
5171
5344
|
meta: {
|
|
5172
5345
|
schema: [],
|
|
5173
5346
|
type: "problem",
|
|
5174
5347
|
docs: {
|
|
5175
|
-
description: "Require
|
|
5348
|
+
description: "Require one global entry point; project CSS only in a stylesheet it imports directly."
|
|
5176
5349
|
}
|
|
5177
5350
|
},
|
|
5178
5351
|
create(context) {
|
|
5352
|
+
const sourceRoot = sourceRootOf(context);
|
|
5353
|
+
let cssFiles;
|
|
5354
|
+
let moduleFiles;
|
|
5355
|
+
try {
|
|
5356
|
+
if (!statSync6(sourceRoot).isDirectory()) return {};
|
|
5357
|
+
cssFiles = findFiles(sourceRoot, CSS_EXTENSIONS);
|
|
5358
|
+
moduleFiles = findFiles(sourceRoot, MODULE_EXTENSIONS3);
|
|
5359
|
+
} catch {
|
|
5360
|
+
return {};
|
|
5361
|
+
}
|
|
5362
|
+
const textOf = cachedTextReader();
|
|
5363
|
+
const graph = buildStylesheetGraph({ cssFiles, sourceRoot, textOf });
|
|
5364
|
+
const { globals, reachable, directChildren } = graph;
|
|
5179
5365
|
return {
|
|
5180
5366
|
"StyleSheet:exit"(node) {
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
)
|
|
5184
|
-
|
|
5367
|
+
if (globals.length === 0) return;
|
|
5368
|
+
const current = path44.normalize(path44.resolve(context.filename));
|
|
5369
|
+
if (globals.includes(current)) {
|
|
5370
|
+
if (globals.length > 1) {
|
|
5371
|
+
context.report({
|
|
5372
|
+
node,
|
|
5373
|
+
message: "Only one stylesheet may register Tailwind as the global entry point."
|
|
5374
|
+
});
|
|
5375
|
+
return;
|
|
5376
|
+
}
|
|
5377
|
+
const basename = path44.basename(current);
|
|
5378
|
+
const importCount = moduleFiles.filter((modulePath) => moduleImports(textOf(modulePath), basename)).length;
|
|
5379
|
+
if (importCount !== 1) {
|
|
5380
|
+
context.report({
|
|
5381
|
+
node,
|
|
5382
|
+
message: `The global stylesheet entry point must be imported by exactly one module (the root layout), but it is imported by ${String(importCount)} module(s).`
|
|
5383
|
+
});
|
|
5384
|
+
}
|
|
5385
|
+
return;
|
|
5386
|
+
}
|
|
5185
5387
|
const hasProjectCss = node.children.some((child) => {
|
|
5186
5388
|
if (child.type === "Atrule" && child.name === "import") return false;
|
|
5187
5389
|
if (child.type === "Comment") return false;
|
|
5188
5390
|
return true;
|
|
5189
5391
|
});
|
|
5190
|
-
if (!
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5392
|
+
if (hasProjectCss && !directChildren.has(current)) {
|
|
5393
|
+
context.report({
|
|
5394
|
+
node,
|
|
5395
|
+
message: "Project CSS may only live in a stylesheet the global entry point imports directly; route it through a direct import or into the entry point."
|
|
5396
|
+
});
|
|
5397
|
+
return;
|
|
5398
|
+
}
|
|
5399
|
+
if (!reachable.has(current)) {
|
|
5400
|
+
context.report({
|
|
5401
|
+
node,
|
|
5402
|
+
message: "This stylesheet must be imported by the global stylesheet entry point via @import, so CSS arrives through one door."
|
|
5403
|
+
});
|
|
5404
|
+
}
|
|
5195
5405
|
}
|
|
5196
5406
|
};
|
|
5197
5407
|
}
|
|
@@ -5209,10 +5419,10 @@ var globalStylesheetRule = {
|
|
|
5209
5419
|
create(context) {
|
|
5210
5420
|
return {
|
|
5211
5421
|
"StyleSheet:exit"(node) {
|
|
5212
|
-
const
|
|
5422
|
+
const registersTailwind2 = node.children.some(
|
|
5213
5423
|
(child) => child.type === "Atrule" && child.name === "import" && JSON.stringify(child.prelude).includes("tailwindcss")
|
|
5214
5424
|
);
|
|
5215
|
-
if (!
|
|
5425
|
+
if (!registersTailwind2) {
|
|
5216
5426
|
const hasProjectCss = node.children.some((child) => {
|
|
5217
5427
|
if (child.type === "Atrule" && child.name === "import") return false;
|
|
5218
5428
|
if (child.type === "Comment") return false;
|
|
@@ -5231,32 +5441,9 @@ var globalStylesheetRule = {
|
|
|
5231
5441
|
};
|
|
5232
5442
|
|
|
5233
5443
|
// eslint/rules/tailwind/unused-utility.ts
|
|
5234
|
-
import {
|
|
5235
|
-
import path41 from "path";
|
|
5236
|
-
var SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".css"];
|
|
5237
|
-
function sourceFiles(dir) {
|
|
5238
|
-
let entries;
|
|
5239
|
-
try {
|
|
5240
|
-
entries = readdirSync4(dir);
|
|
5241
|
-
} catch {
|
|
5242
|
-
return [];
|
|
5243
|
-
}
|
|
5244
|
-
return entries.flatMap((entry) => {
|
|
5245
|
-
if (entry.startsWith(".") || entry === "node_modules") return [];
|
|
5246
|
-
const entryPath = path41.join(dir, entry);
|
|
5247
|
-
let stats;
|
|
5248
|
-
try {
|
|
5249
|
-
stats = statSync5(entryPath);
|
|
5250
|
-
} catch {
|
|
5251
|
-
return [];
|
|
5252
|
-
}
|
|
5253
|
-
if (stats.isDirectory()) return sourceFiles(entryPath);
|
|
5254
|
-
return SOURCE_EXTENSIONS.includes(path41.extname(entry)) ? [entryPath] : [];
|
|
5255
|
-
});
|
|
5256
|
-
}
|
|
5444
|
+
import { statSync as statSync7 } from "fs";
|
|
5257
5445
|
function usagePattern(name) {
|
|
5258
|
-
|
|
5259
|
-
return new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`);
|
|
5446
|
+
return new RegExp(`(?<![\\w-])${escapeRegExp(name)}(?![\\w-])`);
|
|
5260
5447
|
}
|
|
5261
5448
|
var unusedUtilityRule = {
|
|
5262
5449
|
meta: {
|
|
@@ -5267,27 +5454,15 @@ var unusedUtilityRule = {
|
|
|
5267
5454
|
}
|
|
5268
5455
|
},
|
|
5269
5456
|
create(context) {
|
|
5270
|
-
const
|
|
5457
|
+
const sourceRoot = sourceRootOf(context);
|
|
5271
5458
|
let files;
|
|
5272
5459
|
try {
|
|
5273
|
-
if (!
|
|
5274
|
-
files =
|
|
5460
|
+
if (!statSync7(sourceRoot).isDirectory()) return {};
|
|
5461
|
+
files = findFiles(sourceRoot, SOURCE_EXTENSIONS);
|
|
5275
5462
|
} catch {
|
|
5276
5463
|
return {};
|
|
5277
5464
|
}
|
|
5278
|
-
const
|
|
5279
|
-
const textOf = (file) => {
|
|
5280
|
-
let text = texts.get(file);
|
|
5281
|
-
if (text === void 0) {
|
|
5282
|
-
try {
|
|
5283
|
-
text = readFileSync7(file, "utf8");
|
|
5284
|
-
} catch {
|
|
5285
|
-
text = "";
|
|
5286
|
-
}
|
|
5287
|
-
texts.set(file, text);
|
|
5288
|
-
}
|
|
5289
|
-
return text;
|
|
5290
|
-
};
|
|
5465
|
+
const textOf = cachedTextReader();
|
|
5291
5466
|
return {
|
|
5292
5467
|
"StyleSheet:exit"(node) {
|
|
5293
5468
|
for (const utility of atrulesNamed(node, "utility")) {
|
|
@@ -5318,7 +5493,7 @@ var tailwindRules = {
|
|
|
5318
5493
|
"custom-utility-apply": customUtilityApplyRule,
|
|
5319
5494
|
"surface-utility": surfaceUtilityRule,
|
|
5320
5495
|
"theme-variable-namespace": themeVariableNamespaceRule,
|
|
5321
|
-
"
|
|
5496
|
+
"css-entry-point": cssEntryPointRule,
|
|
5322
5497
|
"global-stylesheet": globalStylesheetRule,
|
|
5323
5498
|
"unused-utility": unusedUtilityRule
|
|
5324
5499
|
};
|
|
@@ -5462,13 +5637,13 @@ var repoPackageJsonRules = {
|
|
|
5462
5637
|
"no-vulyk-dependency": noVulykDependencyRule,
|
|
5463
5638
|
"exact-version": exactVersionRule
|
|
5464
5639
|
};
|
|
5465
|
-
var
|
|
5640
|
+
var nextjsPackageJsonRules = {
|
|
5466
5641
|
"nextjs-stack": nextjsStackRule
|
|
5467
5642
|
};
|
|
5468
5643
|
|
|
5469
5644
|
// eslint/rules/husky/husky-hook.ts
|
|
5470
5645
|
import { existsSync as existsSync2, readFileSync as readFileSync8 } from "fs";
|
|
5471
|
-
import
|
|
5646
|
+
import path45 from "path";
|
|
5472
5647
|
function memberName4(member) {
|
|
5473
5648
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5474
5649
|
}
|
|
@@ -5487,7 +5662,7 @@ var huskyHookRule = {
|
|
|
5487
5662
|
if (root.type !== "Object") return;
|
|
5488
5663
|
const scriptName = context.filename;
|
|
5489
5664
|
if (!scriptName.endsWith("package.json")) return;
|
|
5490
|
-
const hookPath =
|
|
5665
|
+
const hookPath = path45.join(context.cwd, ".husky", "pre-commit");
|
|
5491
5666
|
if (!existsSync2(hookPath)) {
|
|
5492
5667
|
context.report({
|
|
5493
5668
|
node,
|
|
@@ -5527,7 +5702,7 @@ var huskyRules = {
|
|
|
5527
5702
|
|
|
5528
5703
|
// eslint/rules/vulyk/vulyk-docs.ts
|
|
5529
5704
|
import { existsSync as existsSync3, readFileSync as readFileSync9 } from "fs";
|
|
5530
|
-
import
|
|
5705
|
+
import path46 from "path";
|
|
5531
5706
|
var PASIKA_REPO = "Bredansky/pasika";
|
|
5532
5707
|
var vulykDocsRule = {
|
|
5533
5708
|
meta: {
|
|
@@ -5541,8 +5716,8 @@ var vulykDocsRule = {
|
|
|
5541
5716
|
return {
|
|
5542
5717
|
Document(node) {
|
|
5543
5718
|
if (!context.filename.endsWith("package.json")) return;
|
|
5544
|
-
const projectRoot =
|
|
5545
|
-
const configPath =
|
|
5719
|
+
const projectRoot = path46.dirname(path46.resolve(context.filename));
|
|
5720
|
+
const configPath = path46.join(projectRoot, "vulyk.config.ts");
|
|
5546
5721
|
if (!existsSync3(configPath)) {
|
|
5547
5722
|
context.report({
|
|
5548
5723
|
node,
|
|
@@ -5557,7 +5732,7 @@ var vulykDocsRule = {
|
|
|
5557
5732
|
message: "vulyk.config.ts must track the framework's docs from the pasika repository."
|
|
5558
5733
|
});
|
|
5559
5734
|
}
|
|
5560
|
-
const agentsPath =
|
|
5735
|
+
const agentsPath = path46.join(projectRoot, "AGENTS.md");
|
|
5561
5736
|
if (!existsSync3(agentsPath)) {
|
|
5562
5737
|
context.report({
|
|
5563
5738
|
node,
|
|
@@ -5575,7 +5750,8 @@ var vulykRules = {
|
|
|
5575
5750
|
};
|
|
5576
5751
|
|
|
5577
5752
|
// eslint/index.ts
|
|
5578
|
-
var
|
|
5753
|
+
var nextjsAppRules = {
|
|
5754
|
+
// Framework-agnostic TypeScript rules.
|
|
5579
5755
|
"filename-case": filenameCaseRule,
|
|
5580
5756
|
"import-boundaries": importBoundariesRule,
|
|
5581
5757
|
"named-exports": namedExportsRule,
|
|
@@ -5590,9 +5766,8 @@ var typescriptAppRules = {
|
|
|
5590
5766
|
"type-extraction": typeExtractionRule,
|
|
5591
5767
|
"zod-schema-validation": zodSchemaValidationRule,
|
|
5592
5768
|
"source-under-src": sourceUnderSrcRule,
|
|
5593
|
-
"zirka-baseline": zirkaBaselineRule
|
|
5594
|
-
|
|
5595
|
-
var nextjsAppRules = {
|
|
5769
|
+
"zirka-baseline": zirkaBaselineRule,
|
|
5770
|
+
// Next.js/React application rules.
|
|
5596
5771
|
"component-placement": componentPlacementRule,
|
|
5597
5772
|
"application-structure": applicationStructureRule,
|
|
5598
5773
|
"data-testid-case": dataTestIdCaseRule,
|
|
@@ -5621,58 +5796,56 @@ var nextjsAppRules = {
|
|
|
5621
5796
|
"shared-style-dedup": sharedStyleDedupRule,
|
|
5622
5797
|
"repeated-structure": repeatedStructureRule
|
|
5623
5798
|
};
|
|
5624
|
-
var pasikaRules = {
|
|
5625
|
-
...typescriptAppRules,
|
|
5626
|
-
...nextjsAppRules
|
|
5627
|
-
};
|
|
5628
5799
|
var pasikaPlugin = {
|
|
5629
5800
|
rules: {
|
|
5630
|
-
|
|
5801
|
+
// The shared plugin must register every rule the preset blocks reference,
|
|
5802
|
+
// so all rule sets live here.
|
|
5803
|
+
...nextjsAppRules,
|
|
5631
5804
|
...documentationRules,
|
|
5632
5805
|
...tailwindRules,
|
|
5633
5806
|
...repoPackageJsonRules,
|
|
5634
|
-
...
|
|
5807
|
+
...nextjsPackageJsonRules,
|
|
5635
5808
|
...huskyRules,
|
|
5636
5809
|
...vulykRules
|
|
5637
5810
|
}
|
|
5638
5811
|
};
|
|
5639
|
-
var
|
|
5812
|
+
var jsonLanguage = { languages: { json: jsonPlugin.languages.json } };
|
|
5640
5813
|
function ruleIds(rules2) {
|
|
5641
5814
|
return Object.keys(rules2).map((name) => `pasika/${name}`);
|
|
5642
5815
|
}
|
|
5643
|
-
var typescriptAppRuleIds = ruleIds(typescriptAppRules);
|
|
5644
5816
|
var nextjsAppRuleIds = ruleIds(nextjsAppRules);
|
|
5645
|
-
var pasikaRuleIds = ruleIds(pasikaRules);
|
|
5646
5817
|
var documentationRuleIds = ruleIds(documentationRules);
|
|
5647
5818
|
var tailwindRuleIds = ruleIds(tailwindRules);
|
|
5648
5819
|
var repoPackageJsonRuleIds = ruleIds(repoPackageJsonRules);
|
|
5649
|
-
var
|
|
5820
|
+
var nextjsPackageJsonRuleIds = ruleIds(nextjsPackageJsonRules);
|
|
5650
5821
|
var huskyRuleIds = ruleIds(huskyRules);
|
|
5651
5822
|
var vulykRuleIds = ruleIds(vulykRules);
|
|
5652
5823
|
var allPasikaRuleIds = [
|
|
5653
|
-
...
|
|
5824
|
+
...nextjsAppRuleIds,
|
|
5654
5825
|
...documentationRuleIds,
|
|
5655
5826
|
...tailwindRuleIds,
|
|
5656
5827
|
...repoPackageJsonRuleIds,
|
|
5657
|
-
...
|
|
5828
|
+
...nextjsPackageJsonRuleIds,
|
|
5658
5829
|
...huskyRuleIds,
|
|
5659
5830
|
...vulykRuleIds
|
|
5660
5831
|
];
|
|
5661
|
-
var
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5832
|
+
var typescriptAppLanguageOptions = {
|
|
5833
|
+
parser: tsParser,
|
|
5834
|
+
parserOptions: {
|
|
5835
|
+
ecmaVersion: "latest",
|
|
5836
|
+
sourceType: "module",
|
|
5837
|
+
ecmaFeatures: { jsx: true }
|
|
5838
|
+
}
|
|
5667
5839
|
};
|
|
5668
|
-
var
|
|
5840
|
+
var nextjsAppConfig = {
|
|
5669
5841
|
files: ["src/**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}"],
|
|
5842
|
+
languageOptions: typescriptAppLanguageOptions,
|
|
5670
5843
|
plugins: {
|
|
5671
5844
|
pasika: pasikaPlugin
|
|
5672
5845
|
},
|
|
5673
5846
|
rules: Object.fromEntries(nextjsAppRuleIds.map((id) => [id, "error"]))
|
|
5674
5847
|
};
|
|
5675
|
-
var
|
|
5848
|
+
var tailwindStructureRules = {
|
|
5676
5849
|
files: ["src/**/globals.css"],
|
|
5677
5850
|
plugins: {
|
|
5678
5851
|
css,
|
|
@@ -5681,10 +5854,10 @@ var tailwindGlobalsBlock = {
|
|
|
5681
5854
|
language: "css/css",
|
|
5682
5855
|
languageOptions: { tolerant: true },
|
|
5683
5856
|
rules: Object.fromEntries(
|
|
5684
|
-
tailwindRuleIds.filter((id) => id !== "pasika/
|
|
5857
|
+
tailwindRuleIds.filter((id) => id !== "pasika/css-entry-point").map((id) => [id, "error"])
|
|
5685
5858
|
)
|
|
5686
5859
|
};
|
|
5687
|
-
var
|
|
5860
|
+
var tailwindImportGraph = {
|
|
5688
5861
|
files: ["src/**/*.css"],
|
|
5689
5862
|
plugins: {
|
|
5690
5863
|
css,
|
|
@@ -5692,32 +5865,32 @@ var tailwindAnyCssBlock = {
|
|
|
5692
5865
|
},
|
|
5693
5866
|
language: "css/css",
|
|
5694
5867
|
languageOptions: { tolerant: true },
|
|
5695
|
-
rules: { "pasika/
|
|
5868
|
+
rules: { "pasika/css-entry-point": "error" }
|
|
5696
5869
|
};
|
|
5697
|
-
var
|
|
5870
|
+
var typescriptAppPackageJsonConfig = {
|
|
5698
5871
|
files: ["package.json"],
|
|
5699
5872
|
plugins: {
|
|
5700
|
-
json:
|
|
5873
|
+
json: jsonLanguage,
|
|
5701
5874
|
pasika: pasikaPlugin
|
|
5702
5875
|
},
|
|
5703
5876
|
language: "json/json",
|
|
5704
5877
|
rules: Object.fromEntries([...repoPackageJsonRuleIds, ...huskyRuleIds, ...vulykRuleIds].map((id) => [id, "error"]))
|
|
5705
5878
|
};
|
|
5706
|
-
var
|
|
5879
|
+
var nextjsAppPackageJsonConfig = {
|
|
5707
5880
|
files: ["package.json"],
|
|
5708
5881
|
plugins: {
|
|
5709
|
-
json:
|
|
5882
|
+
json: jsonLanguage,
|
|
5710
5883
|
pasika: pasikaPlugin
|
|
5711
5884
|
},
|
|
5712
5885
|
language: "json/json",
|
|
5713
|
-
rules: Object.fromEntries(
|
|
5886
|
+
rules: Object.fromEntries(nextjsPackageJsonRuleIds.map((id) => [id, "error"]))
|
|
5714
5887
|
};
|
|
5715
|
-
var
|
|
5888
|
+
var zirkaConfig = {
|
|
5716
5889
|
files: ["eslint.config.{ts,mts,cts,js,mjs,cjs}"],
|
|
5717
5890
|
plugins: { pasika: pasikaPlugin },
|
|
5718
5891
|
rules: { "pasika/zirka-baseline": "error" }
|
|
5719
5892
|
};
|
|
5720
|
-
var
|
|
5893
|
+
var documentationConfig = {
|
|
5721
5894
|
files: ["docs/**/*.md"],
|
|
5722
5895
|
ignores: ["**/_*/**"],
|
|
5723
5896
|
plugins: {
|
|
@@ -5727,22 +5900,25 @@ var docsBlock = {
|
|
|
5727
5900
|
language: "markdown/gfm",
|
|
5728
5901
|
rules: Object.fromEntries(documentationRuleIds.map((id) => [id, "error"]))
|
|
5729
5902
|
};
|
|
5730
|
-
var typescriptApp = [
|
|
5903
|
+
var typescriptApp = [
|
|
5904
|
+
typescriptAppPackageJsonConfig,
|
|
5905
|
+
zirkaConfig,
|
|
5906
|
+
documentationConfig
|
|
5907
|
+
];
|
|
5731
5908
|
var nextjsApp = [
|
|
5732
5909
|
...typescriptApp,
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5910
|
+
nextjsAppPackageJsonConfig,
|
|
5911
|
+
nextjsAppConfig,
|
|
5912
|
+
tailwindStructureRules,
|
|
5913
|
+
tailwindImportGraph
|
|
5737
5914
|
];
|
|
5738
5915
|
export {
|
|
5739
5916
|
allPasikaRuleIds,
|
|
5740
5917
|
documentationRules,
|
|
5741
5918
|
huskyRules,
|
|
5742
|
-
nextPackageJsonRules,
|
|
5743
5919
|
nextjsApp,
|
|
5920
|
+
nextjsPackageJsonRules,
|
|
5744
5921
|
pasikaPlugin,
|
|
5745
|
-
pasikaRules,
|
|
5746
5922
|
repoPackageJsonRules,
|
|
5747
5923
|
tailwindRules,
|
|
5748
5924
|
typescriptApp,
|