pasika 0.9.1 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/eslint/pasika/index.d.ts +8 -2
- package/dist/eslint/pasika/index.js +1141 -393
- package/package.json +1 -1
|
@@ -1079,13 +1079,13 @@ function classifyFunction(name, isTsx, hasJsx) {
|
|
|
1079
1079
|
return "function";
|
|
1080
1080
|
}
|
|
1081
1081
|
function classifyValue(name, initializer, isTsx) {
|
|
1082
|
-
const
|
|
1082
|
+
const isFunctionLike2 = initializer !== void 0 && (ts2.isArrowFunction(initializer) || ts2.isFunctionExpression(initializer) || ts2.isFunctionDeclaration(initializer));
|
|
1083
1083
|
if (isHookName(name)) return "hook";
|
|
1084
1084
|
if (isSchemaName(name)) return "schema";
|
|
1085
|
-
if (isTsx && isPascalCase3(name) && (initializer === void 0 || returnsJsx(initializer) ||
|
|
1085
|
+
if (isTsx && isPascalCase3(name) && (initializer === void 0 || returnsJsx(initializer) || isFunctionLike2)) {
|
|
1086
1086
|
return "component";
|
|
1087
1087
|
}
|
|
1088
|
-
if (
|
|
1088
|
+
if (isFunctionLike2) return "function";
|
|
1089
1089
|
return "constant";
|
|
1090
1090
|
}
|
|
1091
1091
|
function parseModule(file) {
|
|
@@ -1121,7 +1121,11 @@ function parseModule(file) {
|
|
|
1121
1121
|
}
|
|
1122
1122
|
if (statement.exportClause && ts2.isNamedExports(statement.exportClause)) {
|
|
1123
1123
|
for (const element of statement.exportClause.elements) {
|
|
1124
|
-
exports.push({
|
|
1124
|
+
exports.push({
|
|
1125
|
+
name: element.name.text,
|
|
1126
|
+
kind: "other",
|
|
1127
|
+
line: lineOf(sourceFile, element)
|
|
1128
|
+
});
|
|
1125
1129
|
}
|
|
1126
1130
|
}
|
|
1127
1131
|
continue;
|
|
@@ -1149,7 +1153,11 @@ function parseModule(file) {
|
|
|
1149
1153
|
continue;
|
|
1150
1154
|
}
|
|
1151
1155
|
if (ts2.isTypeAliasDeclaration(statement) || ts2.isInterfaceDeclaration(statement)) {
|
|
1152
|
-
exports.push({
|
|
1156
|
+
exports.push({
|
|
1157
|
+
name: statement.name.text,
|
|
1158
|
+
kind: "type",
|
|
1159
|
+
line: lineOf(sourceFile, statement)
|
|
1160
|
+
});
|
|
1153
1161
|
}
|
|
1154
1162
|
}
|
|
1155
1163
|
return { file: path6.resolve(file), imports, exports };
|
|
@@ -1382,41 +1390,81 @@ var enforceCnMergeRule = {
|
|
|
1382
1390
|
};
|
|
1383
1391
|
|
|
1384
1392
|
// eslint/rules/cn-helper.ts
|
|
1385
|
-
|
|
1393
|
+
import path9 from "path";
|
|
1394
|
+
var HELPER = "cn";
|
|
1395
|
+
var DOC = "docs/pasika-adoption-guide/rules/cn-helper-rule.md";
|
|
1396
|
+
var ESLINT_CONFIG = /^eslint\.config\.(?:cjs|cts|js|mjs|mts|ts)$/;
|
|
1397
|
+
function isNamed(node, name) {
|
|
1386
1398
|
return node?.type === "Identifier" && node.name === name;
|
|
1387
1399
|
}
|
|
1400
|
+
function isCallTo(node, callee) {
|
|
1401
|
+
return node?.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === callee;
|
|
1402
|
+
}
|
|
1403
|
+
function isComposition(node) {
|
|
1404
|
+
if (!isCallTo(node, "twMerge")) return false;
|
|
1405
|
+
const first = node.arguments[0];
|
|
1406
|
+
return first !== void 0 && first.type !== "SpreadElement" && isCallTo(first, "clsx");
|
|
1407
|
+
}
|
|
1408
|
+
function returnedExpression(body) {
|
|
1409
|
+
if (!body) return void 0;
|
|
1410
|
+
if (body.type !== "BlockStatement") return body;
|
|
1411
|
+
for (const statement of body.body) {
|
|
1412
|
+
if (statement.type === "ReturnStatement") return statement.argument ?? void 0;
|
|
1413
|
+
}
|
|
1414
|
+
return void 0;
|
|
1415
|
+
}
|
|
1416
|
+
function sourceRootFor(context) {
|
|
1417
|
+
return path9.join(path9.dirname(path9.resolve(context.filename)), "src");
|
|
1418
|
+
}
|
|
1419
|
+
function definesHelper(context, name) {
|
|
1420
|
+
const index = getProjectIndex(sourceRootFor(context));
|
|
1421
|
+
if (!index) return true;
|
|
1422
|
+
for (const parsed of index.modules.values()) {
|
|
1423
|
+
if (parsed.exports.some((exported) => exported.name === name)) return true;
|
|
1424
|
+
}
|
|
1425
|
+
return false;
|
|
1426
|
+
}
|
|
1388
1427
|
var cnHelperRule = {
|
|
1389
1428
|
meta: {
|
|
1390
1429
|
schema: [],
|
|
1391
1430
|
type: "problem",
|
|
1392
1431
|
docs: {
|
|
1393
|
-
description:
|
|
1432
|
+
description: `Require a repository to define a ${HELPER} helper that returns twMerge(clsx(...)).`
|
|
1394
1433
|
}
|
|
1395
1434
|
},
|
|
1396
1435
|
create(context) {
|
|
1436
|
+
if (ESLINT_CONFIG.test(path9.basename(context.filename))) {
|
|
1437
|
+
return {
|
|
1438
|
+
Program() {
|
|
1439
|
+
if (definesHelper(context, HELPER)) return;
|
|
1440
|
+
context.report({
|
|
1441
|
+
node: context.sourceCode.ast,
|
|
1442
|
+
loc: { line: 1, column: 0 },
|
|
1443
|
+
message: `A repository must define a ${HELPER} helper. See ${DOC}`
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
const check = (body, reportNode) => {
|
|
1449
|
+
if (isComposition(returnedExpression(body))) return;
|
|
1450
|
+
context.report({
|
|
1451
|
+
node: reportNode,
|
|
1452
|
+
message: `${HELPER} must return twMerge(clsx(...)). See ${DOC}`
|
|
1453
|
+
});
|
|
1454
|
+
};
|
|
1397
1455
|
return {
|
|
1398
1456
|
FunctionDeclaration(node) {
|
|
1399
|
-
if (
|
|
1457
|
+
if (isNamed(node.id, HELPER)) check(node.body, node);
|
|
1400
1458
|
},
|
|
1401
1459
|
VariableDeclarator(node) {
|
|
1402
|
-
if (node.
|
|
1403
|
-
|
|
1404
|
-
|
|
1460
|
+
if (!isNamed(node.id.type === "Identifier" ? node.id : null, HELPER)) return;
|
|
1461
|
+
const init = node.init;
|
|
1462
|
+
if (init?.type !== "ArrowFunctionExpression" && init?.type !== "FunctionExpression") return;
|
|
1463
|
+
check(init.body, node);
|
|
1405
1464
|
}
|
|
1406
1465
|
};
|
|
1407
1466
|
}
|
|
1408
1467
|
};
|
|
1409
|
-
function checkBody(context, body) {
|
|
1410
|
-
const source = context.sourceCode.getText(body);
|
|
1411
|
-
const usesClsx = /\bclsx\b/.test(source);
|
|
1412
|
-
const usesTwMerge = /\btwMerge\b|\btailwind-merge\b/.test(source);
|
|
1413
|
-
if (!usesClsx || !usesTwMerge) {
|
|
1414
|
-
context.report({
|
|
1415
|
-
node: body,
|
|
1416
|
-
message: "cn must be built from clsx and tailwind-merge (e.g. cn = twMerge(clsx(inputs)))."
|
|
1417
|
-
});
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
1468
|
|
|
1421
1469
|
// eslint/rules/enforce-cva-variant-props.ts
|
|
1422
1470
|
var enforceCvaVariantPropsRule = {
|
|
@@ -1482,7 +1530,7 @@ var enforceCvaVariantPropsRule = {
|
|
|
1482
1530
|
};
|
|
1483
1531
|
|
|
1484
1532
|
// eslint/rules/enforce-barrel-exports.ts
|
|
1485
|
-
import
|
|
1533
|
+
import path10 from "path";
|
|
1486
1534
|
import fs from "fs";
|
|
1487
1535
|
function isPascalCase4(str) {
|
|
1488
1536
|
return /^[A-Z][A-Za-z0-9]*$/.test(str);
|
|
@@ -1492,7 +1540,7 @@ function isKebabCase2(str) {
|
|
|
1492
1540
|
}
|
|
1493
1541
|
var SUPPORT_FOLDERS = /* @__PURE__ */ new Set(["types", "schemas", "hooks", "constants", "utils", "config", "locales"]);
|
|
1494
1542
|
function parentComponentName(dirPath, folderName) {
|
|
1495
|
-
const componentFile =
|
|
1543
|
+
const componentFile = path10.join(dirPath, `${folderName}.tsx`);
|
|
1496
1544
|
if (!fs.existsSync(componentFile)) return void 0;
|
|
1497
1545
|
try {
|
|
1498
1546
|
const exports = parseModule(componentFile).exports;
|
|
@@ -1512,11 +1560,11 @@ var enforceBarrelExportsRule = {
|
|
|
1512
1560
|
create(context) {
|
|
1513
1561
|
const filename = context.filename;
|
|
1514
1562
|
if (!filename) return {};
|
|
1515
|
-
const baseName =
|
|
1563
|
+
const baseName = path10.basename(filename);
|
|
1516
1564
|
if (baseName !== "index.ts" && baseName !== "index.cts" && baseName !== "index.mts") return {};
|
|
1517
|
-
const dirPath =
|
|
1518
|
-
const folderName =
|
|
1519
|
-
const parentFolderName =
|
|
1565
|
+
const dirPath = path10.dirname(filename);
|
|
1566
|
+
const folderName = path10.basename(dirPath);
|
|
1567
|
+
const parentFolderName = path10.basename(path10.dirname(dirPath));
|
|
1520
1568
|
if (SUPPORT_FOLDERS.has(folderName)) return {};
|
|
1521
1569
|
if (!isPascalCase4(folderName) && !isKebabCase2(folderName)) return {};
|
|
1522
1570
|
const parentName = parentComponentName(dirPath, folderName);
|
|
@@ -1555,14 +1603,14 @@ var enforceBarrelExportsRule = {
|
|
|
1555
1603
|
|
|
1556
1604
|
// eslint/rules/component-placement.ts
|
|
1557
1605
|
import fs2 from "fs";
|
|
1558
|
-
import
|
|
1606
|
+
import path12 from "path";
|
|
1559
1607
|
|
|
1560
1608
|
// eslint/project/ccf.ts
|
|
1561
|
-
import
|
|
1609
|
+
import path11 from "path";
|
|
1562
1610
|
var SUPPORT_FOLDERS2 = /* @__PURE__ */ new Set(["hooks", "types", "schemas", "constants", "utils"]);
|
|
1563
1611
|
function segmentsOf(file, sourceRoot) {
|
|
1564
|
-
const relative =
|
|
1565
|
-
return relative.startsWith("..") ? [] : relative.split(
|
|
1612
|
+
const relative = path11.relative(sourceRoot, file);
|
|
1613
|
+
return relative.startsWith("..") ? [] : relative.split(path11.sep);
|
|
1566
1614
|
}
|
|
1567
1615
|
function folderSegmentsOf(file, sourceRoot) {
|
|
1568
1616
|
return segmentsOf(file, sourceRoot).slice(0, -1);
|
|
@@ -1570,6 +1618,10 @@ function folderSegmentsOf(file, sourceRoot) {
|
|
|
1570
1618
|
var isUnderApp = (segments) => segments[0] === "app";
|
|
1571
1619
|
var isConfigModule = (segments) => segments[0] === "config";
|
|
1572
1620
|
var isUnderCompositions = (segments) => segments[0] === "compositions";
|
|
1621
|
+
function isPlacingConsumer(consumer, sourceRoot) {
|
|
1622
|
+
const segments = segmentsOf(consumer, sourceRoot);
|
|
1623
|
+
return segments.length > 0 && !isUnderApp(segments);
|
|
1624
|
+
}
|
|
1573
1625
|
function commonPrefix(folders) {
|
|
1574
1626
|
if (folders.length === 0) return [];
|
|
1575
1627
|
const [first = []] = folders;
|
|
@@ -1614,13 +1666,10 @@ function owningFolderOf(consumer, sourceRoot) {
|
|
|
1614
1666
|
var configModuleOf = (segments) => isConfigModule(segments) && segments.length >= 3 ? segments[1] : void 0;
|
|
1615
1667
|
function resolveSupportPlacement(supportFile, supportFolder, index) {
|
|
1616
1668
|
const consumers = [...index.consumers.get(supportFile) ?? []].filter(
|
|
1617
|
-
(consumer) =>
|
|
1669
|
+
(consumer) => isPlacingConsumer(consumer, index.sourceRoot)
|
|
1618
1670
|
);
|
|
1619
1671
|
if (consumers.length === 0) return void 0;
|
|
1620
1672
|
const consumerSegments = consumers.map((consumer) => segmentsOf(consumer, index.sourceRoot));
|
|
1621
|
-
if (consumerSegments.some((segments) => isUnderApp(segments))) {
|
|
1622
|
-
return { countedConsumers: consumers, expectedFolder: [supportFolder], reason: "app-consumer" };
|
|
1623
|
-
}
|
|
1624
1673
|
const configModules = new Set(consumerSegments.map((segments) => configModuleOf(segments)));
|
|
1625
1674
|
const [onlyConfigModule] = [...configModules];
|
|
1626
1675
|
if (configModules.size === 1 && onlyConfigModule !== void 0) {
|
|
@@ -1641,7 +1690,7 @@ function resolveSupportPlacement(supportFile, supportFolder, index) {
|
|
|
1641
1690
|
}
|
|
1642
1691
|
function describeConsumers(consumers, sourceRoot) {
|
|
1643
1692
|
const shown = 3;
|
|
1644
|
-
const names = consumers.map((consumer) =>
|
|
1693
|
+
const names = consumers.map((consumer) => path11.relative(path11.dirname(sourceRoot), consumer).split(path11.sep).join("/")).sort((left, right) => left.localeCompare(right));
|
|
1645
1694
|
if (names.length <= shown) return names.join(", ");
|
|
1646
1695
|
return `${names.slice(0, shown).join(", ")} and ${String(names.length - shown)} more`;
|
|
1647
1696
|
}
|
|
@@ -1658,7 +1707,7 @@ function isNestedInside(expectedFolder, currentFolder, componentFile) {
|
|
|
1658
1707
|
if (!sameFolder(currentFolder.slice(0, -1), expectedFolder)) return false;
|
|
1659
1708
|
const folderName = currentFolder[currentFolder.length - 1];
|
|
1660
1709
|
if (!folderName) return false;
|
|
1661
|
-
return fs2.existsSync(
|
|
1710
|
+
return fs2.existsSync(path12.join(path12.dirname(componentFile), `${folderName}.tsx`));
|
|
1662
1711
|
}
|
|
1663
1712
|
var componentPlacementRule = {
|
|
1664
1713
|
meta: {
|
|
@@ -1674,7 +1723,7 @@ var componentPlacementRule = {
|
|
|
1674
1723
|
const sourceRoot = sourceRootOf(context);
|
|
1675
1724
|
const index = getProjectIndex(sourceRoot);
|
|
1676
1725
|
if (!index) return {};
|
|
1677
|
-
const componentFile =
|
|
1726
|
+
const componentFile = path12.resolve(filename);
|
|
1678
1727
|
const segments = segmentsOf(componentFile, sourceRoot);
|
|
1679
1728
|
if (segments.length === 0) return {};
|
|
1680
1729
|
if (isUnderApp(segments) || isConfigModule(segments)) return {};
|
|
@@ -1708,10 +1757,9 @@ var componentPlacementRule = {
|
|
|
1708
1757
|
};
|
|
1709
1758
|
|
|
1710
1759
|
// eslint/rules/support-file-placement.ts
|
|
1711
|
-
import
|
|
1760
|
+
import path13 from "path";
|
|
1712
1761
|
var CONFIG_OWNED_FOLDERS = /* @__PURE__ */ new Set(["types", "constants"]);
|
|
1713
1762
|
var REASON_TEXT2 = {
|
|
1714
|
-
"app-consumer": "a file under src/app/ imports it, so it belongs to the app-wide support folder",
|
|
1715
1763
|
"config-module": "every file that imports it belongs to that configuration module",
|
|
1716
1764
|
ccf: "that is the closest folder its consumers share",
|
|
1717
1765
|
"across-features": "its consumers span more than one feature, so no feature can own it",
|
|
@@ -1728,7 +1776,7 @@ var supportFilePlacementRule = {
|
|
|
1728
1776
|
},
|
|
1729
1777
|
create(context) {
|
|
1730
1778
|
const sourceRoot = sourceRootOf(context);
|
|
1731
|
-
const supportFile =
|
|
1779
|
+
const supportFile = path13.resolve(context.filename);
|
|
1732
1780
|
const currentFolder = folderSegmentsOf(supportFile, sourceRoot);
|
|
1733
1781
|
const supportFolder = currentFolder[currentFolder.length - 1];
|
|
1734
1782
|
if (supportFolder === void 0 || !SUPPORT_FOLDERS2.has(supportFolder)) return {};
|
|
@@ -1753,7 +1801,7 @@ var supportFilePlacementRule = {
|
|
|
1753
1801
|
|
|
1754
1802
|
// eslint/rules/application-structure.ts
|
|
1755
1803
|
import fs3 from "fs";
|
|
1756
|
-
import
|
|
1804
|
+
import path14 from "path";
|
|
1757
1805
|
var MODULE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
1758
1806
|
var ROUTING_FILES = /* @__PURE__ */ new Set([
|
|
1759
1807
|
"default",
|
|
@@ -1791,7 +1839,7 @@ function report2(context, message) {
|
|
|
1791
1839
|
};
|
|
1792
1840
|
}
|
|
1793
1841
|
function isCodeFile(filename) {
|
|
1794
|
-
return MODULE_EXTENSIONS2.has(
|
|
1842
|
+
return MODULE_EXTENSIONS2.has(path14.extname(filename));
|
|
1795
1843
|
}
|
|
1796
1844
|
function isRootSupportFolder(folder) {
|
|
1797
1845
|
return SUPPORT_FOLDERS2.has(folder);
|
|
@@ -1812,7 +1860,7 @@ function expectedSupportFolder(kinds) {
|
|
|
1812
1860
|
function configModuleRoot(filename, sourceRoot) {
|
|
1813
1861
|
const segments = segmentsOf(filename, sourceRoot);
|
|
1814
1862
|
if (segments[0] !== "config" || segments.length < 3) return void 0;
|
|
1815
|
-
return
|
|
1863
|
+
return path14.join(sourceRoot, "config", segments[1] ?? "");
|
|
1816
1864
|
}
|
|
1817
1865
|
function componentFolderStart(segments) {
|
|
1818
1866
|
if (segments[0] === "features") return 2;
|
|
@@ -1825,12 +1873,12 @@ function componentFolderViolation(segments, sourceRoot) {
|
|
|
1825
1873
|
for (let depth = segments.length - 2; depth >= start; depth -= 1) {
|
|
1826
1874
|
const folder = segments[depth];
|
|
1827
1875
|
if (!folder || SUPPORT_FOLDERS2.has(folder)) continue;
|
|
1828
|
-
const folderPath =
|
|
1876
|
+
const folderPath = path14.join(sourceRoot, ...segments.slice(0, depth + 1));
|
|
1829
1877
|
const label = `src/${segments.slice(0, depth + 1).join("/")}/`;
|
|
1830
|
-
if (!fs3.existsSync(
|
|
1878
|
+
if (!fs3.existsSync(path14.join(folderPath, `${folder}.tsx`))) {
|
|
1831
1879
|
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.`;
|
|
1832
1880
|
}
|
|
1833
|
-
if (!fs3.existsSync(
|
|
1881
|
+
if (!fs3.existsSync(path14.join(folderPath, "index.ts"))) {
|
|
1834
1882
|
return `A component folder must have an index.ts that named-re-exports its component; add index.ts to ${label}.`;
|
|
1835
1883
|
}
|
|
1836
1884
|
}
|
|
@@ -1845,7 +1893,7 @@ var applicationStructureRule = {
|
|
|
1845
1893
|
}
|
|
1846
1894
|
},
|
|
1847
1895
|
create(context) {
|
|
1848
|
-
const filename =
|
|
1896
|
+
const filename = path14.resolve(context.filename);
|
|
1849
1897
|
const sourceRoot = sourceRootOf(context);
|
|
1850
1898
|
const segments = segmentsOf(filename, sourceRoot);
|
|
1851
1899
|
if (segments.length === 0) return {};
|
|
@@ -1876,43 +1924,43 @@ var applicationStructureRule = {
|
|
|
1876
1924
|
);
|
|
1877
1925
|
}
|
|
1878
1926
|
const moduleRoot = configModuleRoot(filename, sourceRoot);
|
|
1879
|
-
if (moduleRoot && !fs3.existsSync(
|
|
1927
|
+
if (moduleRoot && !fs3.existsSync(path14.join(moduleRoot, "index.ts"))) {
|
|
1880
1928
|
return report2(
|
|
1881
1929
|
context,
|
|
1882
|
-
`Add src/config/${
|
|
1930
|
+
`Add src/config/${path14.basename(moduleRoot)}/index.ts as the configuration module entry point.`
|
|
1883
1931
|
);
|
|
1884
1932
|
}
|
|
1885
|
-
if (moduleRoot && segments.length === 3 &&
|
|
1933
|
+
if (moduleRoot && segments.length === 3 && path14.basename(filename) !== "index.ts") {
|
|
1886
1934
|
const kinds2 = exportedKinds(filename);
|
|
1887
1935
|
const expected2 = expectedSupportFolder(kinds2);
|
|
1888
1936
|
if (expected2 !== void 0) {
|
|
1889
1937
|
return report2(
|
|
1890
1938
|
context,
|
|
1891
|
-
`Move this configuration support file into src/config/${
|
|
1939
|
+
`Move this configuration support file into src/config/${path14.basename(moduleRoot)}/${expected2}/.`
|
|
1892
1940
|
);
|
|
1893
1941
|
}
|
|
1894
1942
|
}
|
|
1895
1943
|
}
|
|
1896
1944
|
if (topLevel === "app" && isCodeFile(filename)) {
|
|
1897
|
-
const basename =
|
|
1898
|
-
const currentFolder2 =
|
|
1945
|
+
const basename = path14.basename(filename, path14.extname(filename));
|
|
1946
|
+
const currentFolder2 = path14.basename(path14.dirname(filename));
|
|
1899
1947
|
if (SUPPORT_FOLDERS2.has(currentFolder2)) {
|
|
1900
1948
|
return report2(
|
|
1901
1949
|
context,
|
|
1902
1950
|
"src/app/ may contain routing files and framework assets, but ordinary components and support files must live outside src/app/."
|
|
1903
1951
|
);
|
|
1904
1952
|
}
|
|
1905
|
-
if (!ROUTING_FILES.has(basename) &&
|
|
1953
|
+
if (!ROUTING_FILES.has(basename) && path14.extname(filename) !== ".css") {
|
|
1906
1954
|
return report2(
|
|
1907
1955
|
context,
|
|
1908
1956
|
"src/app/ may contain routing files and framework assets, but ordinary components and support files must live outside src/app/."
|
|
1909
1957
|
);
|
|
1910
1958
|
}
|
|
1911
|
-
if (ROUTING_FILES.has(basename) ||
|
|
1959
|
+
if (ROUTING_FILES.has(basename) || path14.extname(filename) === ".css") return {};
|
|
1912
1960
|
}
|
|
1913
|
-
const currentFolder =
|
|
1961
|
+
const currentFolder = path14.basename(path14.dirname(filename));
|
|
1914
1962
|
const kinds = exportedKinds(filename);
|
|
1915
|
-
const isConfigModuleRoot = topLevel === "config" && segments.length === 3 &&
|
|
1963
|
+
const isConfigModuleRoot = topLevel === "config" && segments.length === 3 && path14.basename(filename) === "index.ts";
|
|
1916
1964
|
if (isConfigModuleRoot) return {};
|
|
1917
1965
|
if (!SUPPORT_FOLDERS2.has(currentFolder)) {
|
|
1918
1966
|
const expected2 = expectedSupportFolder(kinds);
|
|
@@ -1929,7 +1977,7 @@ var applicationStructureRule = {
|
|
|
1929
1977
|
if (kinds.has("component")) {
|
|
1930
1978
|
return report2(
|
|
1931
1979
|
context,
|
|
1932
|
-
`A support folder must not contain a component; move ${
|
|
1980
|
+
`A support folder must not contain a component; move ${path14.basename(filename)} beside ${currentFolder}/.`
|
|
1933
1981
|
);
|
|
1934
1982
|
}
|
|
1935
1983
|
const expected = expectedSupportFolder(kinds);
|
|
@@ -1946,7 +1994,7 @@ var applicationStructureRule = {
|
|
|
1946
1994
|
};
|
|
1947
1995
|
|
|
1948
1996
|
// eslint/rules/named-exports.ts
|
|
1949
|
-
import
|
|
1997
|
+
import path15 from "path";
|
|
1950
1998
|
var FRAMEWORK_DEFAULT_EXPORT_FILES = /* @__PURE__ */ new Set([
|
|
1951
1999
|
// App Router routing files
|
|
1952
2000
|
"default",
|
|
@@ -1968,9 +2016,9 @@ var FRAMEWORK_DEFAULT_EXPORT_FILES = /* @__PURE__ */ new Set([
|
|
|
1968
2016
|
"twitter-image"
|
|
1969
2017
|
]);
|
|
1970
2018
|
function isFrameworkDefaultExportFile(filename) {
|
|
1971
|
-
const normalized = filename.replaceAll(
|
|
2019
|
+
const normalized = filename.replaceAll(path15.sep, "/");
|
|
1972
2020
|
if (!normalized.includes("/src/app/")) return false;
|
|
1973
|
-
const basename =
|
|
2021
|
+
const basename = path15.basename(filename, path15.extname(filename));
|
|
1974
2022
|
return FRAMEWORK_DEFAULT_EXPORT_FILES.has(basename);
|
|
1975
2023
|
}
|
|
1976
2024
|
var namedExportsRule = {
|
|
@@ -1995,7 +2043,7 @@ var namedExportsRule = {
|
|
|
1995
2043
|
};
|
|
1996
2044
|
|
|
1997
2045
|
// eslint/rules/data-testid-case.ts
|
|
1998
|
-
import
|
|
2046
|
+
import path16 from "path";
|
|
1999
2047
|
var NEXT_ROUTING_FILES2 = /* @__PURE__ */ new Set([
|
|
2000
2048
|
"default",
|
|
2001
2049
|
"error",
|
|
@@ -2028,9 +2076,9 @@ var dataTestIdCaseRule = {
|
|
|
2028
2076
|
}
|
|
2029
2077
|
},
|
|
2030
2078
|
create(context) {
|
|
2031
|
-
const filename =
|
|
2079
|
+
const filename = path16.resolve(context.filename);
|
|
2032
2080
|
if (!filename.endsWith(".tsx")) return {};
|
|
2033
|
-
const base =
|
|
2081
|
+
const base = path16.basename(filename, path16.extname(filename));
|
|
2034
2082
|
if (NEXT_ROUTING_FILES2.has(base)) return {};
|
|
2035
2083
|
const text = context.sourceCode.text;
|
|
2036
2084
|
const components = parseComponentInfo(text, filename);
|
|
@@ -2072,7 +2120,7 @@ function toKebabCase(value) {
|
|
|
2072
2120
|
|
|
2073
2121
|
// eslint/rules/support-folder-shape.ts
|
|
2074
2122
|
import fs4 from "fs";
|
|
2075
|
-
import
|
|
2123
|
+
import path17 from "path";
|
|
2076
2124
|
var SUPPORT_FOLDERS3 = /* @__PURE__ */ new Set(["constants", "types", "schemas"]);
|
|
2077
2125
|
var INDEX_NAMES = /* @__PURE__ */ new Set(["index.ts", "index.tsx", "index.mts", "index.cts"]);
|
|
2078
2126
|
var supportFolderShapeRule = {
|
|
@@ -2084,12 +2132,12 @@ var supportFolderShapeRule = {
|
|
|
2084
2132
|
}
|
|
2085
2133
|
},
|
|
2086
2134
|
create(context) {
|
|
2087
|
-
const filename =
|
|
2088
|
-
const baseName =
|
|
2135
|
+
const filename = path17.resolve(context.filename);
|
|
2136
|
+
const baseName = path17.basename(filename);
|
|
2089
2137
|
if (!INDEX_NAMES.has(baseName)) return {};
|
|
2090
|
-
const folder =
|
|
2138
|
+
const folder = path17.basename(path17.dirname(filename));
|
|
2091
2139
|
if (!SUPPORT_FOLDERS3.has(folder)) return {};
|
|
2092
|
-
const directory =
|
|
2140
|
+
const directory = path17.dirname(filename);
|
|
2093
2141
|
let entries;
|
|
2094
2142
|
try {
|
|
2095
2143
|
entries = fs4.readdirSync(directory);
|
|
@@ -2106,7 +2154,7 @@ var supportFolderShapeRule = {
|
|
|
2106
2154
|
const exportPattern = /export\s+(?:\{[^}]*\}|\*[^;]*)\s+from\s+["'](?<specifier>\.[^"']+)["']/g;
|
|
2107
2155
|
for (const match of source.matchAll(exportPattern)) {
|
|
2108
2156
|
const specifier = match.groups?.specifier;
|
|
2109
|
-
if (specifier) exportedFiles.add(
|
|
2157
|
+
if (specifier) exportedFiles.add(path17.basename(specifier));
|
|
2110
2158
|
}
|
|
2111
2159
|
const hasAnyReExport = exportedFiles.size > 0;
|
|
2112
2160
|
const guide = `docs/next-codebase-guide/rules/${folder === "constants" ? "constants" : "types-and-schemas"}-rule.md`;
|
|
@@ -2133,7 +2181,7 @@ var supportFolderShapeRule = {
|
|
|
2133
2181
|
};
|
|
2134
2182
|
|
|
2135
2183
|
// eslint/rules/constant-casing.ts
|
|
2136
|
-
import
|
|
2184
|
+
import path18 from "path";
|
|
2137
2185
|
var NEXTJS_ROUTE_HANDLER_NAMES = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
2138
2186
|
function isScreamingSnakeCase(name) {
|
|
2139
2187
|
return /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/.test(name);
|
|
@@ -2147,9 +2195,9 @@ var constantCasingRule = {
|
|
|
2147
2195
|
}
|
|
2148
2196
|
},
|
|
2149
2197
|
create(context) {
|
|
2150
|
-
const filename =
|
|
2198
|
+
const filename = path18.resolve(context.filename);
|
|
2151
2199
|
const sourceRoot = sourceRootOf(context);
|
|
2152
|
-
if (!filename.startsWith(sourceRoot +
|
|
2200
|
+
if (!filename.startsWith(sourceRoot + path18.sep)) return {};
|
|
2153
2201
|
return {
|
|
2154
2202
|
VariableDeclarator(node) {
|
|
2155
2203
|
if (node.id.type !== "Identifier") return;
|
|
@@ -2171,7 +2219,7 @@ var constantCasingRule = {
|
|
|
2171
2219
|
};
|
|
2172
2220
|
|
|
2173
2221
|
// eslint/rules/prefer-enum.ts
|
|
2174
|
-
import
|
|
2222
|
+
import path19 from "path";
|
|
2175
2223
|
function isConstAssertion(node) {
|
|
2176
2224
|
const typeAnnotation = node.typeAnnotation;
|
|
2177
2225
|
return typeAnnotation?.type === "TSTypeReference" && typeAnnotation.typeName?.type === "Identifier" && typeAnnotation.typeName.name === "const";
|
|
@@ -2190,9 +2238,9 @@ var preferEnumRule = {
|
|
|
2190
2238
|
}
|
|
2191
2239
|
},
|
|
2192
2240
|
create(context) {
|
|
2193
|
-
const filename =
|
|
2241
|
+
const filename = path19.resolve(context.filename);
|
|
2194
2242
|
const sourceRoot = sourceRootOf(context);
|
|
2195
|
-
if (!filename.startsWith(sourceRoot +
|
|
2243
|
+
if (!filename.startsWith(sourceRoot + path19.sep)) return {};
|
|
2196
2244
|
return {
|
|
2197
2245
|
TSAsExpression(node) {
|
|
2198
2246
|
if (!isConstAssertion(node)) return;
|
|
@@ -2210,7 +2258,7 @@ var preferEnumRule = {
|
|
|
2210
2258
|
};
|
|
2211
2259
|
|
|
2212
2260
|
// eslint/rules/import-through-index.ts
|
|
2213
|
-
import
|
|
2261
|
+
import path20 from "path";
|
|
2214
2262
|
var importThroughIndexRule = {
|
|
2215
2263
|
meta: {
|
|
2216
2264
|
schema: [],
|
|
@@ -2220,7 +2268,7 @@ var importThroughIndexRule = {
|
|
|
2220
2268
|
}
|
|
2221
2269
|
},
|
|
2222
2270
|
create(context) {
|
|
2223
|
-
const filename =
|
|
2271
|
+
const filename = path20.resolve(context.filename);
|
|
2224
2272
|
const sourceRoot = sourceRootOf2(context, filename);
|
|
2225
2273
|
return {
|
|
2226
2274
|
Program(node) {
|
|
@@ -2232,7 +2280,7 @@ var importThroughIndexRule = {
|
|
|
2232
2280
|
(segment) => ["constants", "types", "schemas"].includes(segment)
|
|
2233
2281
|
);
|
|
2234
2282
|
const supportFolder = supportFolderIndex >= 0 ? targetSegments[supportFolderIndex] : void 0;
|
|
2235
|
-
if (!supportFolder ||
|
|
2283
|
+
if (!supportFolder || path20.basename(target).startsWith("index.")) continue;
|
|
2236
2284
|
const folderIndex = targetSegments.slice(0, supportFolderIndex + 1);
|
|
2237
2285
|
const expected = `@/${folderIndex.join("/")}`;
|
|
2238
2286
|
context.report({
|
|
@@ -2254,14 +2302,14 @@ function importSpecifiers(source) {
|
|
|
2254
2302
|
return specifiers;
|
|
2255
2303
|
}
|
|
2256
2304
|
function sourceRootOf2(context, filename) {
|
|
2257
|
-
const marker = `${
|
|
2305
|
+
const marker = `${path20.sep}src${path20.sep}`;
|
|
2258
2306
|
const srcIndex = filename.lastIndexOf(marker);
|
|
2259
2307
|
if (srcIndex >= 0) return filename.slice(0, srcIndex + marker.length - 1);
|
|
2260
|
-
return
|
|
2308
|
+
return path20.resolve(context.cwd ?? process.cwd(), "src");
|
|
2261
2309
|
}
|
|
2262
2310
|
|
|
2263
2311
|
// eslint/rules/util-file-name.ts
|
|
2264
|
-
import
|
|
2312
|
+
import path21 from "path";
|
|
2265
2313
|
function toKebabCase2(value) {
|
|
2266
2314
|
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();
|
|
2267
2315
|
}
|
|
@@ -2274,7 +2322,7 @@ var utilFileNameRule = {
|
|
|
2274
2322
|
}
|
|
2275
2323
|
},
|
|
2276
2324
|
create(context) {
|
|
2277
|
-
const filename =
|
|
2325
|
+
const filename = path21.resolve(context.filename);
|
|
2278
2326
|
const segments = filename.replace(/\\/g, "/").split("/");
|
|
2279
2327
|
if (!segments.includes("utils")) return {};
|
|
2280
2328
|
let module;
|
|
@@ -2288,13 +2336,13 @@ var utilFileNameRule = {
|
|
|
2288
2336
|
const functionName = functions[0]?.name;
|
|
2289
2337
|
if (!functionName) return {};
|
|
2290
2338
|
const expected = toKebabCase2(functionName);
|
|
2291
|
-
const actual =
|
|
2339
|
+
const actual = path21.basename(filename, path21.extname(filename));
|
|
2292
2340
|
if (!expected || actual === expected) return {};
|
|
2293
2341
|
return {
|
|
2294
2342
|
Program(node) {
|
|
2295
2343
|
context.report({
|
|
2296
2344
|
node,
|
|
2297
|
-
message: `A utility file exporting ${functionName} must be named ${expected}.${
|
|
2345
|
+
message: `A utility file exporting ${functionName} must be named ${expected}.${path21.extname(filename).slice(1)}.`
|
|
2298
2346
|
});
|
|
2299
2347
|
}
|
|
2300
2348
|
};
|
|
@@ -2302,7 +2350,7 @@ var utilFileNameRule = {
|
|
|
2302
2350
|
};
|
|
2303
2351
|
|
|
2304
2352
|
// eslint/rules/no-util-barrel.ts
|
|
2305
|
-
import
|
|
2353
|
+
import path22 from "path";
|
|
2306
2354
|
var noUtilBarrelRule = {
|
|
2307
2355
|
meta: {
|
|
2308
2356
|
schema: [],
|
|
@@ -2312,7 +2360,7 @@ var noUtilBarrelRule = {
|
|
|
2312
2360
|
}
|
|
2313
2361
|
},
|
|
2314
2362
|
create(context) {
|
|
2315
|
-
const filename =
|
|
2363
|
+
const filename = path22.resolve(context.filename);
|
|
2316
2364
|
const sourceRoot = sourceRootOf3(context, filename);
|
|
2317
2365
|
return {
|
|
2318
2366
|
Program(node) {
|
|
@@ -2321,7 +2369,7 @@ var noUtilBarrelRule = {
|
|
|
2321
2369
|
if (!target) continue;
|
|
2322
2370
|
const segments = target.replace(/\\/g, "/").split("/");
|
|
2323
2371
|
const utilsIndex = segments.lastIndexOf("utils");
|
|
2324
|
-
if (utilsIndex < 0 || !
|
|
2372
|
+
if (utilsIndex < 0 || !path22.basename(target).startsWith("index.")) continue;
|
|
2325
2373
|
context.report({
|
|
2326
2374
|
node,
|
|
2327
2375
|
message: `Import utilities directly instead of through "${specifier}". See docs/next-codebase-guide/rules/utilities-rule.md`
|
|
@@ -2341,10 +2389,10 @@ function importSpecifiers2(source) {
|
|
|
2341
2389
|
return specifiers;
|
|
2342
2390
|
}
|
|
2343
2391
|
function sourceRootOf3(context, filename) {
|
|
2344
|
-
const marker = `${
|
|
2392
|
+
const marker = `${path22.sep}src${path22.sep}`;
|
|
2345
2393
|
const srcIndex = filename.lastIndexOf(marker);
|
|
2346
2394
|
if (srcIndex >= 0) return filename.slice(0, srcIndex + marker.length - 1);
|
|
2347
|
-
return
|
|
2395
|
+
return path22.resolve(context.cwd ?? process.cwd(), "src");
|
|
2348
2396
|
}
|
|
2349
2397
|
|
|
2350
2398
|
// eslint/rules/jsx-hygiene.ts
|
|
@@ -2437,7 +2485,7 @@ var jsxHygieneRule = {
|
|
|
2437
2485
|
};
|
|
2438
2486
|
|
|
2439
2487
|
// eslint/rules/interactive-component.ts
|
|
2440
|
-
import
|
|
2488
|
+
import path23 from "path";
|
|
2441
2489
|
var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
|
|
2442
2490
|
"a",
|
|
2443
2491
|
"button",
|
|
@@ -2592,8 +2640,8 @@ var interactiveComponentRule = {
|
|
|
2592
2640
|
},
|
|
2593
2641
|
create(context) {
|
|
2594
2642
|
if (!context.filename.endsWith(".tsx") && !context.filename.endsWith(".jsx")) return {};
|
|
2595
|
-
const filename =
|
|
2596
|
-
const base =
|
|
2643
|
+
const filename = path23.resolve(context.filename);
|
|
2644
|
+
const base = path23.basename(filename, path23.extname(filename));
|
|
2597
2645
|
if (NEXT_ROUTING_FILES3.has(base)) return {};
|
|
2598
2646
|
return {
|
|
2599
2647
|
JSXElement(node) {
|
|
@@ -2844,12 +2892,12 @@ var cvaBooleanVariantsRule = {
|
|
|
2844
2892
|
};
|
|
2845
2893
|
|
|
2846
2894
|
// eslint/rules/cross-feature-import.ts
|
|
2847
|
-
import
|
|
2895
|
+
import path24 from "path";
|
|
2848
2896
|
var FEATURES_SEGMENT = "features";
|
|
2849
2897
|
function featureNameOf(resolvedPath, sourceRoot) {
|
|
2850
|
-
const relative =
|
|
2898
|
+
const relative = path24.relative(sourceRoot, resolvedPath);
|
|
2851
2899
|
if (relative.startsWith("..")) return void 0;
|
|
2852
|
-
const segments = relative.split(
|
|
2900
|
+
const segments = relative.split(path24.sep);
|
|
2853
2901
|
if (segments[0] !== FEATURES_SEGMENT || segments.length < 2) return void 0;
|
|
2854
2902
|
return segments[1];
|
|
2855
2903
|
}
|
|
@@ -2865,9 +2913,9 @@ var crossFeatureImportRule = {
|
|
|
2865
2913
|
const filename = context.filename;
|
|
2866
2914
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
|
|
2867
2915
|
const sourceRoot = sourceRootOf(context);
|
|
2868
|
-
const fileRelative =
|
|
2916
|
+
const fileRelative = path24.relative(sourceRoot, filename);
|
|
2869
2917
|
if (fileRelative.startsWith("..")) return {};
|
|
2870
|
-
const fileSegments = fileRelative.split(
|
|
2918
|
+
const fileSegments = fileRelative.split(path24.sep);
|
|
2871
2919
|
const isInCompositions = fileSegments[0] === "compositions";
|
|
2872
2920
|
const isInApp = fileSegments[0] === "app";
|
|
2873
2921
|
const isConfig = fileSegments[0] === "config";
|
|
@@ -2881,9 +2929,9 @@ var crossFeatureImportRule = {
|
|
|
2881
2929
|
if (typeof source.value !== "string") return;
|
|
2882
2930
|
let resolved;
|
|
2883
2931
|
if (source.value.startsWith("@/")) {
|
|
2884
|
-
resolved =
|
|
2932
|
+
resolved = path24.resolve(sourceRoot, source.value.slice(2));
|
|
2885
2933
|
} else if (source.value.startsWith(".")) {
|
|
2886
|
-
resolved =
|
|
2934
|
+
resolved = path24.resolve(path24.dirname(filename), source.value);
|
|
2887
2935
|
}
|
|
2888
2936
|
if (!resolved) return;
|
|
2889
2937
|
const feature = featureNameOf(resolved, sourceRoot);
|
|
@@ -2902,7 +2950,7 @@ var crossFeatureImportRule = {
|
|
|
2902
2950
|
};
|
|
2903
2951
|
|
|
2904
2952
|
// eslint/rules/pure-function-extract.ts
|
|
2905
|
-
import
|
|
2953
|
+
import path25 from "path";
|
|
2906
2954
|
var ROUTE_HANDLER_EXPORT_NAMES = /* @__PURE__ */ new Set([
|
|
2907
2955
|
"GET",
|
|
2908
2956
|
"POST",
|
|
@@ -2946,12 +2994,12 @@ var pureFunctionExtractRule = {
|
|
|
2946
2994
|
},
|
|
2947
2995
|
create(context) {
|
|
2948
2996
|
const filename = context.filename;
|
|
2949
|
-
const isRouteFile =
|
|
2997
|
+
const isRouteFile = path25.basename(filename) === "route.ts";
|
|
2950
2998
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx") && !isRouteFile) return {};
|
|
2951
2999
|
const sourceRoot = sourceRootOf(context);
|
|
2952
|
-
const relative =
|
|
3000
|
+
const relative = path25.relative(sourceRoot, filename);
|
|
2953
3001
|
if (relative.startsWith("..")) return {};
|
|
2954
|
-
const segments = relative.split(
|
|
3002
|
+
const segments = relative.split(path25.sep);
|
|
2955
3003
|
if (segments[0] === "utils") return {};
|
|
2956
3004
|
if (segments[0] === "app" && !isRouteFile) return {};
|
|
2957
3005
|
const supportFolders = /* @__PURE__ */ new Set(["hooks", "types", "schemas", "constants", "utils"]);
|
|
@@ -2976,30 +3024,529 @@ var pureFunctionExtractRule = {
|
|
|
2976
3024
|
report3(node, name);
|
|
2977
3025
|
},
|
|
2978
3026
|
VariableDeclarator(node) {
|
|
2979
|
-
if (node.id.type !== "Identifier") return;
|
|
2980
|
-
const name = node.id.name;
|
|
2981
|
-
if (!name) return;
|
|
2982
|
-
const container = node.parent.parent;
|
|
2983
|
-
const exported = container?.type === "ExportNamedDeclaration";
|
|
2984
|
-
const moduleLevel = exported || container?.type === "Program";
|
|
2985
|
-
if (!moduleLevel) return;
|
|
2986
|
-
if (!isRouteFile && !exported) return;
|
|
2987
|
-
if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
|
|
2988
|
-
if (isComponentLikeName(name) || isHookName2(name)) return;
|
|
3027
|
+
if (node.id.type !== "Identifier") return;
|
|
3028
|
+
const name = node.id.name;
|
|
3029
|
+
if (!name) return;
|
|
3030
|
+
const container = node.parent.parent;
|
|
3031
|
+
const exported = container?.type === "ExportNamedDeclaration";
|
|
3032
|
+
const moduleLevel = exported || container?.type === "Program";
|
|
3033
|
+
if (!moduleLevel) return;
|
|
3034
|
+
if (!isRouteFile && !exported) return;
|
|
3035
|
+
if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
|
|
3036
|
+
if (isComponentLikeName(name) || isHookName2(name)) return;
|
|
3037
|
+
const init = node.init;
|
|
3038
|
+
if (!init || init.type !== "ArrowFunctionExpression" && init.type !== "FunctionExpression") {
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
if (init.body.type === "BlockStatement" && hasHookUsage(init.body)) return;
|
|
3042
|
+
report3(node, name);
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
|
|
3048
|
+
// eslint/rules/root-support-placement.ts
|
|
3049
|
+
import path26 from "path";
|
|
3050
|
+
function isSupportFolder(value) {
|
|
3051
|
+
return value === "utils" || value === "types" || value === "schemas" || value === "constants";
|
|
3052
|
+
}
|
|
3053
|
+
var TYPES_AND_SCHEMAS_DOC = "docs/next-codebase-guide/rules/types-and-schemas-rule.md";
|
|
3054
|
+
var KIND_FOR_SUPPORT_FOLDER = {
|
|
3055
|
+
utils: "function",
|
|
3056
|
+
types: "type",
|
|
3057
|
+
schemas: "schema",
|
|
3058
|
+
constants: "constant"
|
|
3059
|
+
};
|
|
3060
|
+
var LABEL_FOR_SUPPORT_FOLDER = {
|
|
3061
|
+
utils: "Function",
|
|
3062
|
+
types: "Type",
|
|
3063
|
+
schemas: "Schema",
|
|
3064
|
+
constants: "Constant"
|
|
3065
|
+
};
|
|
3066
|
+
var DOC_FOR_SUPPORT_FOLDER = {
|
|
3067
|
+
utils: "docs/next-codebase-guide/rules/utilities-rule.md",
|
|
3068
|
+
types: TYPES_AND_SCHEMAS_DOC,
|
|
3069
|
+
schemas: TYPES_AND_SCHEMAS_DOC,
|
|
3070
|
+
constants: "docs/next-codebase-guide/rules/constants-rule.md"
|
|
3071
|
+
};
|
|
3072
|
+
var rootSupportPlacementRule = {
|
|
3073
|
+
meta: {
|
|
3074
|
+
schema: [],
|
|
3075
|
+
type: "problem",
|
|
3076
|
+
docs: {
|
|
3077
|
+
description: "Require a zero-consumer support-folder export to live under a feature folder, not root or elsewhere."
|
|
3078
|
+
}
|
|
3079
|
+
},
|
|
3080
|
+
create(context) {
|
|
3081
|
+
const sourceRoot = sourceRootOf(context);
|
|
3082
|
+
const file = path26.resolve(context.filename);
|
|
3083
|
+
const segments = segmentsOf(file, sourceRoot);
|
|
3084
|
+
const folderSegments = folderSegmentsOf(file, sourceRoot);
|
|
3085
|
+
const supportFolder = folderSegments[folderSegments.length - 1];
|
|
3086
|
+
if (!isSupportFolder(supportFolder)) return {};
|
|
3087
|
+
const expectedKind = KIND_FOR_SUPPORT_FOLDER[supportFolder];
|
|
3088
|
+
const doc = DOC_FOR_SUPPORT_FOLDER[supportFolder];
|
|
3089
|
+
const label = LABEL_FOR_SUPPORT_FOLDER[supportFolder];
|
|
3090
|
+
const isRoot = segments[0] === supportFolder;
|
|
3091
|
+
const isUnderFeature = segments[0] === "features";
|
|
3092
|
+
if (isUnderFeature) return {};
|
|
3093
|
+
const index = getProjectIndex(sourceRoot);
|
|
3094
|
+
if (!index) return {};
|
|
3095
|
+
const module = index.modules.get(file);
|
|
3096
|
+
if (!module) return {};
|
|
3097
|
+
const findings = [];
|
|
3098
|
+
for (const exp of module.exports) {
|
|
3099
|
+
if (exp.kind !== expectedKind) continue;
|
|
3100
|
+
const consumers = [...index.symbolConsumers.get(symbolKey(file, exp.name)) ?? []];
|
|
3101
|
+
if (consumers.length === 0) continue;
|
|
3102
|
+
const outsideApp = consumers.filter((consumer) => isPlacingConsumer(consumer, sourceRoot));
|
|
3103
|
+
if (outsideApp.length > 0) continue;
|
|
3104
|
+
const where = isRoot ? `root src/${supportFolder}/` : `src/${folderSegments.join("/")}/`;
|
|
3105
|
+
findings.push({
|
|
3106
|
+
line: exp.line,
|
|
3107
|
+
message: `${label} "${exp.name}" is consumed only from src/app/, so it has not earned ${where}; move it into the feature it represents (src/features/<feature>/${supportFolder}/). If no existing feature applies, introduce a new feature folder. See ${doc}`
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
if (findings.length === 0) return {};
|
|
3111
|
+
return {
|
|
3112
|
+
Program(node) {
|
|
3113
|
+
for (const finding of findings) {
|
|
3114
|
+
context.report({ node, loc: { line: finding.line, column: 0 }, message: finding.message });
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
};
|
|
3120
|
+
|
|
3121
|
+
// eslint/rules/route-handler-shape.ts
|
|
3122
|
+
import path27 from "path";
|
|
3123
|
+
import ts4 from "typescript";
|
|
3124
|
+
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
3125
|
+
var REQUIRED_BOUNDARY = "withResponse";
|
|
3126
|
+
function isFunctionLike(node) {
|
|
3127
|
+
return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
|
|
3128
|
+
}
|
|
3129
|
+
function findHandler(node) {
|
|
3130
|
+
if (isFunctionLike(node)) return node;
|
|
3131
|
+
if (node.type === "CallExpression") {
|
|
3132
|
+
for (const arg of node.arguments) {
|
|
3133
|
+
if (arg.type === "SpreadElement") continue;
|
|
3134
|
+
const found = findHandler(arg);
|
|
3135
|
+
if (found) return found;
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
return void 0;
|
|
3139
|
+
}
|
|
3140
|
+
function isLoop(node) {
|
|
3141
|
+
return ts4.isForStatement(node) || ts4.isForOfStatement(node) || ts4.isForInStatement(node) || ts4.isWhileStatement(node) || ts4.isDoStatement(node);
|
|
3142
|
+
}
|
|
3143
|
+
function isFunctionBoundary(node) {
|
|
3144
|
+
return ts4.isFunctionExpression(node) || ts4.isArrowFunction(node) || ts4.isFunctionDeclaration(node);
|
|
3145
|
+
}
|
|
3146
|
+
function analyzeHandlerBody(body, sourceText) {
|
|
3147
|
+
const start = body.range?.[0] ?? 0;
|
|
3148
|
+
const end = body.range?.[1] ?? sourceText.length;
|
|
3149
|
+
const sourceFile = ts4.createSourceFile(
|
|
3150
|
+
"route.ts",
|
|
3151
|
+
sourceText.slice(start, end),
|
|
3152
|
+
ts4.ScriptTarget.Latest,
|
|
3153
|
+
true,
|
|
3154
|
+
ts4.ScriptKind.TS
|
|
3155
|
+
);
|
|
3156
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
3157
|
+
const calledNames = /* @__PURE__ */ new Set();
|
|
3158
|
+
const visit = (node) => {
|
|
3159
|
+
if (ts4.isTryStatement(node)) kinds.add("try");
|
|
3160
|
+
if (isLoop(node)) kinds.add("loop");
|
|
3161
|
+
if (ts4.isIfStatement(node)) kinds.add("if");
|
|
3162
|
+
if (ts4.isCallExpression(node) && ts4.isIdentifier(node.expression)) calledNames.add(node.expression.text);
|
|
3163
|
+
if (isFunctionBoundary(node)) return;
|
|
3164
|
+
ts4.forEachChild(node, visit);
|
|
3165
|
+
};
|
|
3166
|
+
visit(sourceFile);
|
|
3167
|
+
return { kinds, calledNames };
|
|
3168
|
+
}
|
|
3169
|
+
var CONTROL_FLOW_MESSAGES = {
|
|
3170
|
+
try: "a try statement",
|
|
3171
|
+
loop: "a loop",
|
|
3172
|
+
if: "an if statement"
|
|
3173
|
+
};
|
|
3174
|
+
function isFunctionLikeInit(node) {
|
|
3175
|
+
return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
|
|
3176
|
+
}
|
|
3177
|
+
function collectLocallyDeclaredNames(programBody) {
|
|
3178
|
+
const names = /* @__PURE__ */ new Set();
|
|
3179
|
+
for (const statement of programBody) {
|
|
3180
|
+
const declaration = statement.type === "ExportNamedDeclaration" && statement.declaration ? statement.declaration : statement;
|
|
3181
|
+
if (declaration.type === "FunctionDeclaration") {
|
|
3182
|
+
names.add(declaration.id.name);
|
|
3183
|
+
}
|
|
3184
|
+
if (declaration.type === "VariableDeclaration") {
|
|
3185
|
+
for (const declarator of declaration.declarations) {
|
|
3186
|
+
if (declarator.id.type === "Identifier" && isFunctionLikeInit(declarator.init)) {
|
|
3187
|
+
names.add(declarator.id.name);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
return names;
|
|
3193
|
+
}
|
|
3194
|
+
var routeHandlerShapeRule = {
|
|
3195
|
+
meta: {
|
|
3196
|
+
schema: [],
|
|
3197
|
+
type: "problem",
|
|
3198
|
+
docs: {
|
|
3199
|
+
description: "Require a route.ts handler to be wrapped in withResponse, with no try, loop, or if of its own, and every function it calls imported rather than declared in route.ts."
|
|
3200
|
+
}
|
|
3201
|
+
},
|
|
3202
|
+
create(context) {
|
|
3203
|
+
if (path27.basename(context.filename) !== "route.ts") return {};
|
|
3204
|
+
const sourceText = context.sourceCode.text;
|
|
3205
|
+
function reportUnwrapped(node, name) {
|
|
3206
|
+
context.report({
|
|
3207
|
+
node,
|
|
3208
|
+
message: `Handler "${name}" must be wrapped in withResponse. See docs/next-codebase-guide/rules/route-handler-rule.md`
|
|
3209
|
+
});
|
|
3210
|
+
}
|
|
3211
|
+
function checkExport(node, name, init) {
|
|
3212
|
+
if (init.type === "Identifier") return;
|
|
3213
|
+
const boundary = init.type === "CallExpression" && init.callee.type === "Identifier" ? init.callee.name : void 0;
|
|
3214
|
+
if (boundary !== REQUIRED_BOUNDARY) {
|
|
3215
|
+
reportUnwrapped(node, name);
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
const handler = findHandler(init);
|
|
3219
|
+
if (!handler?.body) return;
|
|
3220
|
+
const { kinds, calledNames } = analyzeHandlerBody(handler.body, sourceText);
|
|
3221
|
+
for (const kind of kinds) {
|
|
3222
|
+
context.report({
|
|
3223
|
+
node,
|
|
3224
|
+
message: `Handler "${name}" contains ${CONTROL_FLOW_MESSAGES[kind]} of its own; delegate that to a function it calls. See docs/next-codebase-guide/rules/route-handler-rule.md`
|
|
3225
|
+
});
|
|
3226
|
+
}
|
|
3227
|
+
const localNames = collectLocallyDeclaredNames(context.sourceCode.ast.body);
|
|
3228
|
+
for (const calledName of calledNames) {
|
|
3229
|
+
if (localNames.has(calledName)) {
|
|
3230
|
+
context.report({
|
|
3231
|
+
node,
|
|
3232
|
+
message: `Handler "${name}" calls "${calledName}", which is declared in route.ts instead of imported. See docs/next-codebase-guide/rules/route-handler-rule.md`
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
return {
|
|
3238
|
+
FunctionDeclaration(node) {
|
|
3239
|
+
const exported = node.parent?.type === "ExportNamedDeclaration";
|
|
3240
|
+
const name = node.id?.name;
|
|
3241
|
+
if (!exported || !name || !HTTP_METHODS.has(name) || !node.body) return;
|
|
3242
|
+
reportUnwrapped(node, name);
|
|
3243
|
+
},
|
|
3244
|
+
VariableDeclarator(node) {
|
|
3245
|
+
if (node.id.type !== "Identifier") return;
|
|
3246
|
+
const name = node.id.name;
|
|
3247
|
+
const exported = node.parent.parent?.type === "ExportNamedDeclaration";
|
|
3248
|
+
if (!exported || !HTTP_METHODS.has(name) || !node.init) return;
|
|
3249
|
+
checkExport(node, name, node.init);
|
|
3250
|
+
}
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
};
|
|
3254
|
+
|
|
3255
|
+
// eslint/rules/http-error-usage.ts
|
|
3256
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3257
|
+
import path28 from "path";
|
|
3258
|
+
import ts5 from "typescript";
|
|
3259
|
+
var HTTP_ERROR = "HttpError";
|
|
3260
|
+
var ROUTE_FILE = "route.ts";
|
|
3261
|
+
var DOC2 = "docs/next-codebase-guide/rules/route-handler-rule.md";
|
|
3262
|
+
var HERITAGE_DEPTH = 5;
|
|
3263
|
+
var HANDS_ERROR_BACK = `A delegated module must throw the HttpError it reports a failure with, not return it. See ${DOC2}`;
|
|
3264
|
+
var delegatedModuleMessage = (name) => `A delegated module must report a failure by throwing an HttpError, not "${name}". See ${DOC2}`;
|
|
3265
|
+
function constructedErrorName(node) {
|
|
3266
|
+
if (node === void 0) return void 0;
|
|
3267
|
+
if (node.type === "NewExpression") {
|
|
3268
|
+
return node.callee.type === "Identifier" ? node.callee.name : void 0;
|
|
3269
|
+
}
|
|
3270
|
+
if (node.type === "AwaitExpression") return constructedErrorName(node.argument);
|
|
3271
|
+
if (node.type === "ConditionalExpression") {
|
|
3272
|
+
return constructedErrorName(node.consequent) ?? constructedErrorName(node.alternate);
|
|
3273
|
+
}
|
|
3274
|
+
if (node.type === "LogicalExpression") {
|
|
3275
|
+
return constructedErrorName(node.left) ?? constructedErrorName(node.right);
|
|
3276
|
+
}
|
|
3277
|
+
if (node.type !== "CallExpression") return void 0;
|
|
3278
|
+
for (const argument of node.arguments) {
|
|
3279
|
+
const found = constructedErrorName(argument);
|
|
3280
|
+
if (found) return found;
|
|
3281
|
+
}
|
|
3282
|
+
return void 0;
|
|
3283
|
+
}
|
|
3284
|
+
function parentClassOf(file, name) {
|
|
3285
|
+
let text;
|
|
3286
|
+
try {
|
|
3287
|
+
text = readFileSync4(file, "utf8");
|
|
3288
|
+
} catch {
|
|
3289
|
+
return void 0;
|
|
3290
|
+
}
|
|
3291
|
+
const sourceFile = ts5.createSourceFile(file, text, ts5.ScriptTarget.Latest, true, ts5.ScriptKind.TSX);
|
|
3292
|
+
for (const statement of sourceFile.statements) {
|
|
3293
|
+
if (!ts5.isClassDeclaration(statement) || statement.name?.text !== name) continue;
|
|
3294
|
+
const heritage = statement.heritageClauses?.find((clause) => clause.token === ts5.SyntaxKind.ExtendsKeyword);
|
|
3295
|
+
const parent = heritage?.types[0]?.expression;
|
|
3296
|
+
return parent && ts5.isIdentifier(parent) ? parent.text : void 0;
|
|
3297
|
+
}
|
|
3298
|
+
return void 0;
|
|
3299
|
+
}
|
|
3300
|
+
function importedFrom(file, name, sourceRoot, index) {
|
|
3301
|
+
const module = index.modules.get(path28.resolve(file));
|
|
3302
|
+
const moduleImport = module?.imports.find((entry) => entry.names.includes(name));
|
|
3303
|
+
if (!moduleImport) return void 0;
|
|
3304
|
+
return resolveSpecifier(file, moduleImport.specifier, sourceRoot);
|
|
3305
|
+
}
|
|
3306
|
+
function isHttpErrorClass(file, name, sourceRoot, index) {
|
|
3307
|
+
let currentFile = path28.resolve(file);
|
|
3308
|
+
let currentName = name;
|
|
3309
|
+
for (let depth = 0; depth <= HERITAGE_DEPTH; depth += 1) {
|
|
3310
|
+
if (currentName === HTTP_ERROR) return true;
|
|
3311
|
+
const parent = parentClassOf(currentFile, currentName);
|
|
3312
|
+
if (parent) {
|
|
3313
|
+
currentName = parent;
|
|
3314
|
+
continue;
|
|
3315
|
+
}
|
|
3316
|
+
const imported = importedFrom(currentFile, currentName, sourceRoot, index);
|
|
3317
|
+
if (!imported) return false;
|
|
3318
|
+
currentFile = imported;
|
|
3319
|
+
}
|
|
3320
|
+
return false;
|
|
3321
|
+
}
|
|
3322
|
+
var pipelineCache;
|
|
3323
|
+
function pipelineFiles(sourceRoot) {
|
|
3324
|
+
const index = getProjectIndex(sourceRoot);
|
|
3325
|
+
if (!index) return void 0;
|
|
3326
|
+
if (pipelineCache?.index === index) return pipelineCache.files;
|
|
3327
|
+
const files = /* @__PURE__ */ new Set();
|
|
3328
|
+
const queue = [...index.modules.keys()].filter((file) => path28.basename(file) === ROUTE_FILE);
|
|
3329
|
+
while (queue.length > 0) {
|
|
3330
|
+
const file = queue.pop();
|
|
3331
|
+
if (file === void 0 || files.has(file)) continue;
|
|
3332
|
+
files.add(file);
|
|
3333
|
+
for (const moduleImport of index.modules.get(file)?.imports ?? []) {
|
|
3334
|
+
const target = resolveSpecifier(file, moduleImport.specifier, index.sourceRoot);
|
|
3335
|
+
if (target && index.modules.has(target) && !files.has(target)) queue.push(target);
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
pipelineCache = { index, files };
|
|
3339
|
+
return files;
|
|
3340
|
+
}
|
|
3341
|
+
var httpErrorUsageRule = {
|
|
3342
|
+
meta: {
|
|
3343
|
+
schema: [],
|
|
3344
|
+
type: "problem",
|
|
3345
|
+
docs: {
|
|
3346
|
+
description: "Require a delegated module to report a failure by throwing an HttpError, never another error type and never a returned value."
|
|
3347
|
+
}
|
|
3348
|
+
},
|
|
3349
|
+
create(context) {
|
|
3350
|
+
const sourceRoot = sourceRootOf(context);
|
|
3351
|
+
const file = path28.resolve(context.filename);
|
|
3352
|
+
let delegated;
|
|
3353
|
+
function inPipeline() {
|
|
3354
|
+
delegated ??= pipelineFiles(sourceRoot)?.has(file) ?? false;
|
|
3355
|
+
return delegated;
|
|
3356
|
+
}
|
|
3357
|
+
return {
|
|
3358
|
+
ReturnStatement(node) {
|
|
3359
|
+
if (node.argument === null) return;
|
|
3360
|
+
if (!inPipeline()) return;
|
|
3361
|
+
if (constructedErrorName(node.argument) !== HTTP_ERROR) return;
|
|
3362
|
+
context.report({ node, message: HANDS_ERROR_BACK });
|
|
3363
|
+
},
|
|
3364
|
+
ArrowFunctionExpression(node) {
|
|
3365
|
+
if (node.body.type === "BlockStatement") return;
|
|
3366
|
+
if (!inPipeline()) return;
|
|
3367
|
+
if (constructedErrorName(node.body) !== HTTP_ERROR) return;
|
|
3368
|
+
context.report({ node, message: HANDS_ERROR_BACK });
|
|
3369
|
+
},
|
|
3370
|
+
ThrowStatement(node) {
|
|
3371
|
+
const thrown = node.argument;
|
|
3372
|
+
if (thrown.type !== "NewExpression" || thrown.callee.type !== "Identifier") return;
|
|
3373
|
+
const name = thrown.callee.name;
|
|
3374
|
+
if (name === HTTP_ERROR) return;
|
|
3375
|
+
const index = getProjectIndex(sourceRoot);
|
|
3376
|
+
if (!index || !inPipeline()) return;
|
|
3377
|
+
if (isHttpErrorClass(file, name, sourceRoot, index)) return;
|
|
3378
|
+
context.report({ node, message: delegatedModuleMessage(name) });
|
|
3379
|
+
}
|
|
3380
|
+
};
|
|
3381
|
+
}
|
|
3382
|
+
};
|
|
3383
|
+
|
|
3384
|
+
// eslint/rules/with-response-helper.ts
|
|
3385
|
+
import path29 from "path";
|
|
3386
|
+
import ts6 from "typescript";
|
|
3387
|
+
var HELPER2 = "withResponse";
|
|
3388
|
+
var DOC3 = "docs/pasika-adoption-guide/rules/with-response-helper-rule.md";
|
|
3389
|
+
var ESLINT_CONFIG2 = /^eslint\.config\.(?:cjs|cts|js|mjs|mts|ts)$/;
|
|
3390
|
+
var BEAT_KEYS = [
|
|
3391
|
+
"awaitsHandler",
|
|
3392
|
+
"validatesWithSchema",
|
|
3393
|
+
"checksHttpError",
|
|
3394
|
+
"answersWithNullData",
|
|
3395
|
+
"usesErrorStatus",
|
|
3396
|
+
"rethrows"
|
|
3397
|
+
];
|
|
3398
|
+
var BEAT_MESSAGES = {
|
|
3399
|
+
awaitsHandler: "must await the handler",
|
|
3400
|
+
validatesWithSchema: "must validate the handler's returned data through the response schema",
|
|
3401
|
+
checksHttpError: "must check the caught error with instanceof HttpError",
|
|
3402
|
+
answersWithNullData: "must answer a thrown HttpError with { data: null, message }",
|
|
3403
|
+
usesErrorStatus: "must answer at the caught error's status",
|
|
3404
|
+
rethrows: "must rethrow a caught error that is not an HttpError"
|
|
3405
|
+
};
|
|
3406
|
+
function isNamed2(node, name) {
|
|
3407
|
+
return node?.type === "Identifier" && node.name === name;
|
|
3408
|
+
}
|
|
3409
|
+
function sourceRootFor2(context) {
|
|
3410
|
+
return path29.join(path29.dirname(path29.resolve(context.filename)), "src");
|
|
3411
|
+
}
|
|
3412
|
+
function definesHelper2(context, name) {
|
|
3413
|
+
const index = getProjectIndex(sourceRootFor2(context));
|
|
3414
|
+
if (!index) return true;
|
|
3415
|
+
for (const parsed of index.modules.values()) {
|
|
3416
|
+
if (parsed.exports.some((exported) => exported.name === name)) return true;
|
|
3417
|
+
}
|
|
3418
|
+
return false;
|
|
3419
|
+
}
|
|
3420
|
+
function contains(node, check) {
|
|
3421
|
+
let found = false;
|
|
3422
|
+
const visit = (current) => {
|
|
3423
|
+
if (found) return;
|
|
3424
|
+
if (check(current)) {
|
|
3425
|
+
found = true;
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
ts6.forEachChild(current, visit);
|
|
3429
|
+
};
|
|
3430
|
+
visit(node);
|
|
3431
|
+
return found;
|
|
3432
|
+
}
|
|
3433
|
+
function findTryStatement(node) {
|
|
3434
|
+
let found;
|
|
3435
|
+
const visit = (current) => {
|
|
3436
|
+
if (found) return;
|
|
3437
|
+
if (ts6.isTryStatement(current)) {
|
|
3438
|
+
found = current;
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
ts6.forEachChild(current, visit);
|
|
3442
|
+
};
|
|
3443
|
+
visit(node);
|
|
3444
|
+
return found;
|
|
3445
|
+
}
|
|
3446
|
+
function awaitsHandlerCall(handlerName) {
|
|
3447
|
+
return (node) => ts6.isAwaitExpression(node) && ts6.isCallExpression(node.expression) && ts6.isIdentifier(node.expression.expression) && node.expression.expression.text === handlerName;
|
|
3448
|
+
}
|
|
3449
|
+
function parsesThroughSchema(schemaName) {
|
|
3450
|
+
return (node) => ts6.isCallExpression(node) && ts6.isPropertyAccessExpression(node.expression) && ts6.isIdentifier(node.expression.expression) && node.expression.expression.text === schemaName && (node.expression.name.text === "parse" || node.expression.name.text === "safeParse");
|
|
3451
|
+
}
|
|
3452
|
+
function checksHttpError(node) {
|
|
3453
|
+
return ts6.isBinaryExpression(node) && node.operatorToken.kind === ts6.SyntaxKind.InstanceOfKeyword && ts6.isIdentifier(node.right) && node.right.text === "HttpError";
|
|
3454
|
+
}
|
|
3455
|
+
function answersWithNullData(node) {
|
|
3456
|
+
if (!ts6.isCallExpression(node) || !ts6.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "json") {
|
|
3457
|
+
return false;
|
|
3458
|
+
}
|
|
3459
|
+
return node.arguments.some((argument) => {
|
|
3460
|
+
if (!ts6.isObjectLiteralExpression(argument)) return false;
|
|
3461
|
+
return argument.properties.some((property) => {
|
|
3462
|
+
if (!ts6.isPropertyAssignment(property)) return false;
|
|
3463
|
+
const name = ts6.isIdentifier(property.name) ? property.name.text : void 0;
|
|
3464
|
+
return name === "data" && property.initializer.kind === ts6.SyntaxKind.NullKeyword;
|
|
3465
|
+
});
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3468
|
+
function readsErrorStatus(errorName) {
|
|
3469
|
+
return (node) => ts6.isPropertyAccessExpression(node) && node.name.text === "status" && ts6.isIdentifier(node.expression) && node.expression.text === errorName;
|
|
3470
|
+
}
|
|
3471
|
+
function collectBeats(text, schemaName, handlerName) {
|
|
3472
|
+
const beats = {
|
|
3473
|
+
awaitsHandler: false,
|
|
3474
|
+
validatesWithSchema: false,
|
|
3475
|
+
checksHttpError: false,
|
|
3476
|
+
answersWithNullData: false,
|
|
3477
|
+
usesErrorStatus: false,
|
|
3478
|
+
rethrows: false
|
|
3479
|
+
};
|
|
3480
|
+
const sourceFile = ts6.createSourceFile("with-response.ts", text, ts6.ScriptTarget.Latest, true, ts6.ScriptKind.TS);
|
|
3481
|
+
const tryStatement = findTryStatement(sourceFile);
|
|
3482
|
+
if (!tryStatement) return beats;
|
|
3483
|
+
beats.awaitsHandler = contains(tryStatement.tryBlock, awaitsHandlerCall(handlerName));
|
|
3484
|
+
beats.validatesWithSchema = contains(tryStatement.tryBlock, parsesThroughSchema(schemaName));
|
|
3485
|
+
const catchClause = tryStatement.catchClause;
|
|
3486
|
+
if (!catchClause) return beats;
|
|
3487
|
+
const errorName = catchClause.variableDeclaration?.name;
|
|
3488
|
+
const caughtName = errorName && ts6.isIdentifier(errorName) ? errorName.text : void 0;
|
|
3489
|
+
beats.checksHttpError = contains(catchClause.block, checksHttpError);
|
|
3490
|
+
beats.answersWithNullData = contains(catchClause.block, answersWithNullData);
|
|
3491
|
+
beats.usesErrorStatus = contains(catchClause.block, readsErrorStatus(caughtName));
|
|
3492
|
+
beats.rethrows = contains(catchClause.block, ts6.isThrowStatement);
|
|
3493
|
+
return beats;
|
|
3494
|
+
}
|
|
3495
|
+
function parameterNames(node) {
|
|
3496
|
+
if (node.type !== "FunctionDeclaration" && node.type !== "FunctionExpression" && node.type !== "ArrowFunctionExpression") {
|
|
3497
|
+
return [];
|
|
3498
|
+
}
|
|
3499
|
+
return node.params.map((parameter) => parameter.type === "Identifier" ? parameter.name : void 0);
|
|
3500
|
+
}
|
|
3501
|
+
var withResponseHelperRule = {
|
|
3502
|
+
meta: {
|
|
3503
|
+
schema: [],
|
|
3504
|
+
type: "problem",
|
|
3505
|
+
docs: {
|
|
3506
|
+
description: `Require a repository to define a ${HELPER2} helper that validates the handler's data and maps a thrown HttpError to a response.`
|
|
3507
|
+
}
|
|
3508
|
+
},
|
|
3509
|
+
create(context) {
|
|
3510
|
+
if (ESLINT_CONFIG2.test(path29.basename(context.filename))) {
|
|
3511
|
+
return {
|
|
3512
|
+
Program() {
|
|
3513
|
+
if (definesHelper2(context, HELPER2)) return;
|
|
3514
|
+
context.report({
|
|
3515
|
+
node: context.sourceCode.ast,
|
|
3516
|
+
loc: { line: 1, column: 0 },
|
|
3517
|
+
message: `A repository must define a ${HELPER2} helper. See ${DOC3}`
|
|
3518
|
+
});
|
|
3519
|
+
}
|
|
3520
|
+
};
|
|
3521
|
+
}
|
|
3522
|
+
const check = (node, parameters) => {
|
|
3523
|
+
const [start, end] = node.range ?? [0, context.sourceCode.text.length];
|
|
3524
|
+
const beats = collectBeats(context.sourceCode.text.slice(start, end), parameters[0], parameters[1]);
|
|
3525
|
+
for (const beat of BEAT_KEYS) {
|
|
3526
|
+
if (beats[beat]) continue;
|
|
3527
|
+
context.report({
|
|
3528
|
+
node,
|
|
3529
|
+
message: `${HELPER2} ${BEAT_MESSAGES[beat]}. See ${DOC3}`
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
};
|
|
3533
|
+
return {
|
|
3534
|
+
FunctionDeclaration(node) {
|
|
3535
|
+
if (isNamed2(node.id, HELPER2)) check(node, parameterNames(node));
|
|
3536
|
+
},
|
|
3537
|
+
VariableDeclarator(node) {
|
|
3538
|
+
if (!isNamed2(node.id.type === "Identifier" ? node.id : null, HELPER2)) return;
|
|
2989
3539
|
const init = node.init;
|
|
2990
|
-
if (
|
|
2991
|
-
|
|
2992
|
-
}
|
|
2993
|
-
if (init.body.type === "BlockStatement" && hasHookUsage(init.body)) return;
|
|
2994
|
-
report3(node, name);
|
|
3540
|
+
if (init?.type !== "ArrowFunctionExpression" && init?.type !== "FunctionExpression") return;
|
|
3541
|
+
check(init, parameterNames(init));
|
|
2995
3542
|
}
|
|
2996
3543
|
};
|
|
2997
3544
|
}
|
|
2998
3545
|
};
|
|
2999
3546
|
|
|
3000
3547
|
// eslint/rules/hook-complexity.ts
|
|
3001
|
-
import
|
|
3002
|
-
import
|
|
3548
|
+
import path30 from "path";
|
|
3549
|
+
import ts7 from "typescript";
|
|
3003
3550
|
var REACT_HOOKS = /* @__PURE__ */ new Set([
|
|
3004
3551
|
"useState",
|
|
3005
3552
|
"useEffect",
|
|
@@ -3017,28 +3564,67 @@ var REACT_HOOKS = /* @__PURE__ */ new Set([
|
|
|
3017
3564
|
"useSyncExternalStore",
|
|
3018
3565
|
"useInsertionEffect"
|
|
3019
3566
|
]);
|
|
3567
|
+
var SUBSCRIPTION_METHODS = /* @__PURE__ */ new Set(["on", "off", "addEventListener", "removeEventListener"]);
|
|
3568
|
+
var STORAGE_OBJECTS = /* @__PURE__ */ new Set(["localStorage", "sessionStorage", "indexedDB"]);
|
|
3569
|
+
var DOM_METHODS = /* @__PURE__ */ new Set(["focus", "blur", "scrollIntoView", "click"]);
|
|
3570
|
+
var DOM_PROPERTIES = /* @__PURE__ */ new Set(["classList"]);
|
|
3571
|
+
var DOM_CONSTRUCTORS = /* @__PURE__ */ new Set(["MutationObserver", "ResizeObserver", "IntersectionObserver"]);
|
|
3572
|
+
var LIFECYCLE_METHODS = /* @__PURE__ */ new Set(["load", "destroy", "dispose", "close", "cleanup", "unmount"]);
|
|
3020
3573
|
function isHookName3(name) {
|
|
3021
3574
|
return /^use[A-Z]/.test(name);
|
|
3022
3575
|
}
|
|
3023
|
-
function
|
|
3576
|
+
function calledMethodName(node) {
|
|
3577
|
+
return ts7.isPropertyAccessExpression(node.expression) ? node.expression.name.text : void 0;
|
|
3578
|
+
}
|
|
3579
|
+
function calledOnObjectName(node) {
|
|
3580
|
+
if (!ts7.isPropertyAccessExpression(node.expression)) return void 0;
|
|
3581
|
+
const object = node.expression.expression;
|
|
3582
|
+
return ts7.isIdentifier(object) ? object.text : void 0;
|
|
3583
|
+
}
|
|
3584
|
+
function calledHookName(node) {
|
|
3585
|
+
return ts7.isCallExpression(node) && ts7.isIdentifier(node.expression) && REACT_HOOKS.has(node.expression.text) ? node.expression.text : void 0;
|
|
3586
|
+
}
|
|
3587
|
+
function sideEffectCategoryOf(node) {
|
|
3588
|
+
if (ts7.isAwaitExpression(node)) return "externalIO";
|
|
3589
|
+
if (ts7.isNewExpression(node) && ts7.isIdentifier(node.expression) && DOM_CONSTRUCTORS.has(node.expression.text)) {
|
|
3590
|
+
return "domManipulation";
|
|
3591
|
+
}
|
|
3592
|
+
if (ts7.isPropertyAccessExpression(node) && DOM_PROPERTIES.has(node.name.text)) {
|
|
3593
|
+
return "domManipulation";
|
|
3594
|
+
}
|
|
3595
|
+
if (ts7.isCallExpression(node)) {
|
|
3596
|
+
if (ts7.isIdentifier(node.expression) && node.expression.text === "fetch") return "externalIO";
|
|
3597
|
+
const method = calledMethodName(node);
|
|
3598
|
+
if (method && SUBSCRIPTION_METHODS.has(method)) return "subscription";
|
|
3599
|
+
if (method && LIFECYCLE_METHODS.has(method)) return "lifecycle";
|
|
3600
|
+
if (method && DOM_METHODS.has(method)) return "domManipulation";
|
|
3601
|
+
const object = calledOnObjectName(node);
|
|
3602
|
+
if (object && STORAGE_OBJECTS.has(object)) return "externalIO";
|
|
3603
|
+
}
|
|
3604
|
+
return void 0;
|
|
3605
|
+
}
|
|
3606
|
+
function computeExtractionScore(body, sourceText) {
|
|
3024
3607
|
const start = body.range?.[0] ?? 0;
|
|
3025
3608
|
const end = body.range?.[1] ?? sourceText.length;
|
|
3026
|
-
const sourceFile =
|
|
3609
|
+
const sourceFile = ts7.createSourceFile(
|
|
3027
3610
|
"hook.ts",
|
|
3028
3611
|
sourceText.slice(start, end),
|
|
3029
|
-
|
|
3612
|
+
ts7.ScriptTarget.Latest,
|
|
3030
3613
|
true,
|
|
3031
|
-
|
|
3614
|
+
ts7.ScriptKind.TS
|
|
3032
3615
|
);
|
|
3033
|
-
const
|
|
3616
|
+
const hookNames = /* @__PURE__ */ new Set();
|
|
3617
|
+
const sideEffectCategories = /* @__PURE__ */ new Set();
|
|
3034
3618
|
const visit = (node) => {
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3619
|
+
const hookName = calledHookName(node);
|
|
3620
|
+
if (hookName) hookNames.add(hookName);
|
|
3621
|
+
const sideEffect = sideEffectCategoryOf(node);
|
|
3622
|
+
if (sideEffect) sideEffectCategories.add(sideEffect);
|
|
3623
|
+
ts7.forEachChild(node, visit);
|
|
3039
3624
|
};
|
|
3040
3625
|
visit(sourceFile);
|
|
3041
|
-
|
|
3626
|
+
const hookDiversityPoint = hookNames.size >= 2 ? 1 : 0;
|
|
3627
|
+
return hookDiversityPoint + sideEffectCategories.size;
|
|
3042
3628
|
}
|
|
3043
3629
|
var hookComplexityRule = {
|
|
3044
3630
|
meta: {
|
|
@@ -3051,26 +3637,26 @@ var hookComplexityRule = {
|
|
|
3051
3637
|
create(context) {
|
|
3052
3638
|
const filename = context.filename;
|
|
3053
3639
|
const sourceRoot = sourceRootOf(context);
|
|
3054
|
-
const relative =
|
|
3640
|
+
const relative = path30.relative(sourceRoot, filename);
|
|
3055
3641
|
if (relative.startsWith("..")) return {};
|
|
3056
|
-
const segments = relative.split(
|
|
3642
|
+
const segments = relative.split(path30.sep);
|
|
3057
3643
|
const sourceText = context.sourceCode.text;
|
|
3058
3644
|
function checkHook(node, name, body, exported) {
|
|
3059
3645
|
if (!exported) return;
|
|
3060
3646
|
if (!name || !isHookName3(name)) return;
|
|
3061
3647
|
if (!body) return;
|
|
3062
|
-
const
|
|
3648
|
+
const score = computeExtractionScore(body, sourceText);
|
|
3063
3649
|
const parentFolder = segments.length >= 2 ? segments[segments.length - 2] : void 0;
|
|
3064
3650
|
const inSupportFolder = parentFolder === "hooks";
|
|
3065
|
-
if (
|
|
3651
|
+
if (score >= 2 && !inSupportFolder) {
|
|
3066
3652
|
context.report({
|
|
3067
3653
|
node,
|
|
3068
|
-
message: `Hook "${name}" has ${String(
|
|
3654
|
+
message: `Hook "${name}" has an extraction score of ${String(score)} and must be extracted to a hooks/ folder. See docs/next-codebase-guide/rules/hook-extraction-rule.md`
|
|
3069
3655
|
});
|
|
3070
|
-
} else if (
|
|
3656
|
+
} else if (score < 2 && inSupportFolder) {
|
|
3071
3657
|
context.report({
|
|
3072
3658
|
node,
|
|
3073
|
-
message: `Hook "${name}" has
|
|
3659
|
+
message: `Hook "${name}" has an extraction score below two and must stay inline in its consumer file. See docs/next-codebase-guide/rules/hook-extraction-rule.md`
|
|
3074
3660
|
});
|
|
3075
3661
|
}
|
|
3076
3662
|
}
|
|
@@ -3095,9 +3681,9 @@ var hookComplexityRule = {
|
|
|
3095
3681
|
};
|
|
3096
3682
|
|
|
3097
3683
|
// eslint/rules/locale-dotted-path.ts
|
|
3098
|
-
import
|
|
3684
|
+
import path31 from "path";
|
|
3099
3685
|
function isInLocalesDir(filename) {
|
|
3100
|
-
const segments =
|
|
3686
|
+
const segments = path31.resolve(filename).split(path31.sep);
|
|
3101
3687
|
const srcIdx = segments.lastIndexOf("src");
|
|
3102
3688
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
3103
3689
|
}
|
|
@@ -3146,9 +3732,9 @@ var localeDottedPathRule = {
|
|
|
3146
3732
|
};
|
|
3147
3733
|
|
|
3148
3734
|
// eslint/rules/locales-location.ts
|
|
3149
|
-
import
|
|
3735
|
+
import path32 from "path";
|
|
3150
3736
|
function isLocalesFile(filename) {
|
|
3151
|
-
const segments =
|
|
3737
|
+
const segments = path32.resolve(filename).split(path32.sep);
|
|
3152
3738
|
const srcIdx = segments.lastIndexOf("src");
|
|
3153
3739
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
3154
3740
|
}
|
|
@@ -3173,7 +3759,7 @@ var localesLocationRule = {
|
|
|
3173
3759
|
create(context) {
|
|
3174
3760
|
if (isLocalesFile(context.filename) || isTestFile(context.filename)) return {};
|
|
3175
3761
|
const filename = context.filename;
|
|
3176
|
-
const segments =
|
|
3762
|
+
const segments = path32.resolve(filename).split(path32.sep);
|
|
3177
3763
|
const srcIdx = segments.lastIndexOf("src");
|
|
3178
3764
|
if (srcIdx === -1) return {};
|
|
3179
3765
|
const folder = segments[srcIdx + 1];
|
|
@@ -3197,7 +3783,7 @@ var localesLocationRule = {
|
|
|
3197
3783
|
};
|
|
3198
3784
|
|
|
3199
3785
|
// eslint/rules/hook-extraction.ts
|
|
3200
|
-
import
|
|
3786
|
+
import path33 from "path";
|
|
3201
3787
|
var hookExtractionRule = {
|
|
3202
3788
|
meta: {
|
|
3203
3789
|
schema: [],
|
|
@@ -3208,7 +3794,7 @@ var hookExtractionRule = {
|
|
|
3208
3794
|
},
|
|
3209
3795
|
create(context) {
|
|
3210
3796
|
const sourceRoot = sourceRootOf(context);
|
|
3211
|
-
const file =
|
|
3797
|
+
const file = path33.resolve(context.filename);
|
|
3212
3798
|
const segments = segmentsOf(file, sourceRoot);
|
|
3213
3799
|
if (segments.length === 0) return {};
|
|
3214
3800
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3234,7 +3820,7 @@ var hookExtractionRule = {
|
|
|
3234
3820
|
};
|
|
3235
3821
|
|
|
3236
3822
|
// eslint/rules/value-extraction.ts
|
|
3237
|
-
import
|
|
3823
|
+
import path34 from "path";
|
|
3238
3824
|
var valueExtractionRule = {
|
|
3239
3825
|
meta: {
|
|
3240
3826
|
schema: [],
|
|
@@ -3245,7 +3831,7 @@ var valueExtractionRule = {
|
|
|
3245
3831
|
},
|
|
3246
3832
|
create(context) {
|
|
3247
3833
|
const sourceRoot = sourceRootOf(context);
|
|
3248
|
-
const file =
|
|
3834
|
+
const file = path34.resolve(context.filename);
|
|
3249
3835
|
const segments = segmentsOf(file, sourceRoot);
|
|
3250
3836
|
if (segments.length === 0 || segments[0] !== "app") return {};
|
|
3251
3837
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3266,7 +3852,7 @@ var valueExtractionRule = {
|
|
|
3266
3852
|
};
|
|
3267
3853
|
|
|
3268
3854
|
// eslint/rules/config-extraction.ts
|
|
3269
|
-
import
|
|
3855
|
+
import path35 from "path";
|
|
3270
3856
|
var configExtractionRule = {
|
|
3271
3857
|
meta: {
|
|
3272
3858
|
schema: [],
|
|
@@ -3277,7 +3863,7 @@ var configExtractionRule = {
|
|
|
3277
3863
|
},
|
|
3278
3864
|
create(context) {
|
|
3279
3865
|
const sourceRoot = sourceRootOf(context);
|
|
3280
|
-
const file =
|
|
3866
|
+
const file = path35.resolve(context.filename);
|
|
3281
3867
|
const segments = segmentsOf(file, sourceRoot);
|
|
3282
3868
|
if (segments.length < 3 || segments[0] !== "config") return {};
|
|
3283
3869
|
if (SUPPORT_FOLDERS2.has(segments[2] ?? "")) return {};
|
|
@@ -3315,7 +3901,7 @@ var configExtractionRule = {
|
|
|
3315
3901
|
};
|
|
3316
3902
|
|
|
3317
3903
|
// eslint/rules/component-nesting.ts
|
|
3318
|
-
import
|
|
3904
|
+
import path36 from "path";
|
|
3319
3905
|
var componentNestingRule = {
|
|
3320
3906
|
meta: {
|
|
3321
3907
|
schema: [],
|
|
@@ -3326,7 +3912,7 @@ var componentNestingRule = {
|
|
|
3326
3912
|
},
|
|
3327
3913
|
create(context) {
|
|
3328
3914
|
const sourceRoot = sourceRootOf(context);
|
|
3329
|
-
const file =
|
|
3915
|
+
const file = path36.resolve(context.filename);
|
|
3330
3916
|
const segments = segmentsOf(file, sourceRoot);
|
|
3331
3917
|
if (segments.length !== 4 || segments[0] !== "features") return {};
|
|
3332
3918
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3359,7 +3945,7 @@ var componentNestingRule = {
|
|
|
3359
3945
|
};
|
|
3360
3946
|
|
|
3361
3947
|
// eslint/rules/stay-flat.ts
|
|
3362
|
-
import
|
|
3948
|
+
import path37 from "path";
|
|
3363
3949
|
var stayFlatRule = {
|
|
3364
3950
|
meta: {
|
|
3365
3951
|
schema: [],
|
|
@@ -3370,7 +3956,7 @@ var stayFlatRule = {
|
|
|
3370
3956
|
},
|
|
3371
3957
|
create(context) {
|
|
3372
3958
|
const sourceRoot = sourceRootOf(context);
|
|
3373
|
-
const file =
|
|
3959
|
+
const file = path37.resolve(context.filename);
|
|
3374
3960
|
const segments = segmentsOf(file, sourceRoot);
|
|
3375
3961
|
if (segments.length !== 3 || segments[0] !== "features") return {};
|
|
3376
3962
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3410,7 +3996,7 @@ var stayFlatRule = {
|
|
|
3410
3996
|
};
|
|
3411
3997
|
|
|
3412
3998
|
// eslint/rules/type-extraction.ts
|
|
3413
|
-
import
|
|
3999
|
+
import path38 from "path";
|
|
3414
4000
|
var typeExtractionRule = {
|
|
3415
4001
|
meta: {
|
|
3416
4002
|
schema: [],
|
|
@@ -3421,7 +4007,7 @@ var typeExtractionRule = {
|
|
|
3421
4007
|
},
|
|
3422
4008
|
create(context) {
|
|
3423
4009
|
const sourceRoot = sourceRootOf(context);
|
|
3424
|
-
const file =
|
|
4010
|
+
const file = path38.resolve(context.filename);
|
|
3425
4011
|
const segments = segmentsOf(file, sourceRoot);
|
|
3426
4012
|
if (segments.length === 0) return {};
|
|
3427
4013
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3467,9 +4053,9 @@ var typeExtractionRule = {
|
|
|
3467
4053
|
};
|
|
3468
4054
|
|
|
3469
4055
|
// eslint/rules/locale-placement.ts
|
|
3470
|
-
import
|
|
3471
|
-
import { readFileSync as
|
|
3472
|
-
import
|
|
4056
|
+
import path39 from "path";
|
|
4057
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
4058
|
+
import ts8 from "typescript";
|
|
3473
4059
|
var LOCALE_ACCESS = /\blocales\.(?<key>[A-Za-z_$][\w$]*)/g;
|
|
3474
4060
|
var camelCase = (name) => name.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase());
|
|
3475
4061
|
var FORCED_TOP_LEVEL = /* @__PURE__ */ new Set(["app", "shared", "compositions", "config"]);
|
|
@@ -3477,26 +4063,26 @@ function forcesTopLevel(segments) {
|
|
|
3477
4063
|
return FORCED_TOP_LEVEL.has(segments[0] ?? "") || SUPPORT_FOLDERS2.has(segments[0] ?? "");
|
|
3478
4064
|
}
|
|
3479
4065
|
function localePlacement(text) {
|
|
3480
|
-
const sourceFile =
|
|
4066
|
+
const sourceFile = ts8.createSourceFile("locales.ts", text, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TS);
|
|
3481
4067
|
for (const statement of sourceFile.statements) {
|
|
3482
|
-
if (!
|
|
3483
|
-
const isExported2 = (
|
|
3484
|
-
(modifier) => modifier.kind ===
|
|
4068
|
+
if (!ts8.isVariableStatement(statement)) continue;
|
|
4069
|
+
const isExported2 = (ts8.getModifiers(statement) ?? []).some(
|
|
4070
|
+
(modifier) => modifier.kind === ts8.SyntaxKind.ExportKeyword
|
|
3485
4071
|
);
|
|
3486
4072
|
if (!isExported2) continue;
|
|
3487
4073
|
for (const declaration of statement.declarationList.declarations) {
|
|
3488
|
-
if (!
|
|
3489
|
-
if (!declaration.initializer || !
|
|
4074
|
+
if (!ts8.isIdentifier(declaration.name) || declaration.name.text !== "locales") continue;
|
|
4075
|
+
if (!declaration.initializer || !ts8.isObjectLiteralExpression(declaration.initializer)) continue;
|
|
3490
4076
|
const placement = /* @__PURE__ */ new Map();
|
|
3491
4077
|
for (const property of declaration.initializer.properties) {
|
|
3492
|
-
if (!
|
|
4078
|
+
if (!ts8.isPropertyAssignment(property)) continue;
|
|
3493
4079
|
let name;
|
|
3494
|
-
if (
|
|
3495
|
-
else if (
|
|
4080
|
+
if (ts8.isIdentifier(property.name)) name = property.name.text;
|
|
4081
|
+
else if (ts8.isStringLiteral(property.name)) name = property.name.text;
|
|
3496
4082
|
if (name === void 0) continue;
|
|
3497
4083
|
const line = sourceFile.getLineAndCharacterOfPosition(property.getStart(sourceFile)).line + 1;
|
|
3498
4084
|
placement.set(name, {
|
|
3499
|
-
kind:
|
|
4085
|
+
kind: ts8.isObjectLiteralExpression(property.initializer) ? "nested" : "top",
|
|
3500
4086
|
line
|
|
3501
4087
|
});
|
|
3502
4088
|
}
|
|
@@ -3515,7 +4101,7 @@ var localePlacementRule = {
|
|
|
3515
4101
|
},
|
|
3516
4102
|
create(context) {
|
|
3517
4103
|
const sourceRoot = sourceRootOf(context);
|
|
3518
|
-
const file =
|
|
4104
|
+
const file = path39.resolve(context.filename);
|
|
3519
4105
|
const segments = segmentsOf(file, sourceRoot);
|
|
3520
4106
|
if (segments.length === 0) return {};
|
|
3521
4107
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3525,7 +4111,7 @@ var localePlacementRule = {
|
|
|
3525
4111
|
return candidateSegments.length === 2 && candidateSegments[0] === "locales" && candidateSegments[1]?.startsWith("index.");
|
|
3526
4112
|
});
|
|
3527
4113
|
if (!localesFile || file !== localesFile) return {};
|
|
3528
|
-
const placement = localePlacement(
|
|
4114
|
+
const placement = localePlacement(readFileSync5(localesFile, "utf8"));
|
|
3529
4115
|
if (!placement) return {};
|
|
3530
4116
|
const keyReaders = /* @__PURE__ */ new Map();
|
|
3531
4117
|
const keyFeatures = /* @__PURE__ */ new Map();
|
|
@@ -3537,7 +4123,7 @@ var localePlacementRule = {
|
|
|
3537
4123
|
);
|
|
3538
4124
|
if (!importsLocales) continue;
|
|
3539
4125
|
const candidateSegments = segmentsOf(candidateFile, sourceRoot);
|
|
3540
|
-
for (const match of
|
|
4126
|
+
for (const match of readFileSync5(candidateFile, "utf8").matchAll(LOCALE_ACCESS)) {
|
|
3541
4127
|
const key = match.groups?.key;
|
|
3542
4128
|
if (key === void 0) continue;
|
|
3543
4129
|
const readers = keyReaders.get(key) ?? /* @__PURE__ */ new Set();
|
|
@@ -3593,39 +4179,39 @@ var localePlacementRule = {
|
|
|
3593
4179
|
};
|
|
3594
4180
|
|
|
3595
4181
|
// eslint/rules/sole-state-owner.ts
|
|
3596
|
-
import
|
|
3597
|
-
import
|
|
4182
|
+
import path40 from "path";
|
|
4183
|
+
import ts9 from "typescript";
|
|
3598
4184
|
function findStateHooks(node) {
|
|
3599
4185
|
const hooks = [];
|
|
3600
4186
|
const visit = (child) => {
|
|
3601
|
-
if (
|
|
3602
|
-
if (
|
|
4187
|
+
if (ts9.isCallExpression(child) && ts9.isIdentifier(child.expression) && child.expression.text === "useState") {
|
|
4188
|
+
if (ts9.isVariableDeclaration(child.parent)) {
|
|
3603
4189
|
const { name } = child.parent;
|
|
3604
|
-
if (
|
|
4190
|
+
if (ts9.isArrayBindingPattern(name) && name.elements.length >= 2) {
|
|
3605
4191
|
const value = name.elements[0];
|
|
3606
4192
|
const updater = name.elements[1];
|
|
3607
|
-
if (value && updater &&
|
|
4193
|
+
if (value && updater && ts9.isBindingElement(value) && ts9.isBindingElement(updater)) {
|
|
3608
4194
|
const valueName = value.name;
|
|
3609
4195
|
const updaterName = updater.name;
|
|
3610
|
-
if (
|
|
4196
|
+
if (ts9.isIdentifier(valueName) && ts9.isIdentifier(updaterName)) {
|
|
3611
4197
|
hooks.push({ value: valueName.text, updater: updaterName.text });
|
|
3612
4198
|
}
|
|
3613
4199
|
}
|
|
3614
4200
|
}
|
|
3615
4201
|
}
|
|
3616
4202
|
}
|
|
3617
|
-
|
|
4203
|
+
ts9.forEachChild(child, visit);
|
|
3618
4204
|
};
|
|
3619
4205
|
visit(node);
|
|
3620
4206
|
return hooks;
|
|
3621
4207
|
}
|
|
3622
4208
|
function isHookUsage(node, hook) {
|
|
3623
|
-
if (
|
|
4209
|
+
if (ts9.isCallExpression(node) && ts9.isIdentifier(node.expression) && node.expression.text === hook.updater) {
|
|
3624
4210
|
return "updater";
|
|
3625
4211
|
}
|
|
3626
|
-
if (
|
|
4212
|
+
if (ts9.isIdentifier(node) && node.text === hook.value) {
|
|
3627
4213
|
const parent = node.parent;
|
|
3628
|
-
if (
|
|
4214
|
+
if (ts9.isBindingElement(parent) || ts9.isPropertyAccessExpression(parent) || ts9.isShorthandPropertyAssignment(parent)) {
|
|
3629
4215
|
return void 0;
|
|
3630
4216
|
}
|
|
3631
4217
|
return "value";
|
|
@@ -3635,16 +4221,16 @@ function isHookUsage(node, hook) {
|
|
|
3635
4221
|
function topLevelJsxChildren(initial) {
|
|
3636
4222
|
if (!initial) return void 0;
|
|
3637
4223
|
let expression = initial;
|
|
3638
|
-
while (
|
|
3639
|
-
if (
|
|
4224
|
+
while (ts9.isParenthesizedExpression(expression)) expression = expression.expression;
|
|
4225
|
+
if (ts9.isJsxFragment(expression)) {
|
|
3640
4226
|
const children = expression.children.filter(
|
|
3641
|
-
(c) => !
|
|
4227
|
+
(c) => !ts9.isJsxText(c) && !ts9.isJsxSpreadAttribute(c)
|
|
3642
4228
|
);
|
|
3643
4229
|
return { root: expression, children };
|
|
3644
4230
|
}
|
|
3645
|
-
if (
|
|
4231
|
+
if (ts9.isJsxElement(expression)) {
|
|
3646
4232
|
const children = expression.children.filter(
|
|
3647
|
-
(c) => !
|
|
4233
|
+
(c) => !ts9.isJsxText(c) && !ts9.isJsxSpreadAttribute(c)
|
|
3648
4234
|
);
|
|
3649
4235
|
return { root: expression, children };
|
|
3650
4236
|
}
|
|
@@ -3658,8 +4244,8 @@ function collectUsesIn(child, hook) {
|
|
|
3658
4244
|
positions.push(node);
|
|
3659
4245
|
count += 1;
|
|
3660
4246
|
}
|
|
3661
|
-
if (
|
|
3662
|
-
|
|
4247
|
+
if (ts9.isFunctionDeclaration(node) || ts9.isClassDeclaration(node)) return;
|
|
4248
|
+
ts9.forEachChild(node, visit);
|
|
3663
4249
|
};
|
|
3664
4250
|
visit(child);
|
|
3665
4251
|
return { positions, count };
|
|
@@ -3673,7 +4259,7 @@ var soleStateOwnerRule = {
|
|
|
3673
4259
|
}
|
|
3674
4260
|
},
|
|
3675
4261
|
create(context) {
|
|
3676
|
-
const filename =
|
|
4262
|
+
const filename = path40.resolve(context.filename);
|
|
3677
4263
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
|
|
3678
4264
|
const text = context.sourceCode.text;
|
|
3679
4265
|
const components = parseComponentInfo(text, filename);
|
|
@@ -3698,18 +4284,18 @@ var soleStateOwnerRule = {
|
|
|
3698
4284
|
}
|
|
3699
4285
|
};
|
|
3700
4286
|
function analyzeSoleOwner(declaration, hook) {
|
|
3701
|
-
const body =
|
|
3702
|
-
if (!body || !
|
|
4287
|
+
const body = ts9.isFunctionDeclaration(declaration) ? declaration.body : void 0;
|
|
4288
|
+
if (!body || !ts9.isBlock(body)) return void 0;
|
|
3703
4289
|
const returns = [];
|
|
3704
4290
|
const visit = (node) => {
|
|
3705
|
-
if (node !== body && (
|
|
3706
|
-
if (
|
|
3707
|
-
|
|
4291
|
+
if (node !== body && (ts9.isFunctionLike(node) || ts9.isClassLike(node))) return;
|
|
4292
|
+
if (ts9.isReturnStatement(node)) returns.push(node);
|
|
4293
|
+
ts9.forEachChild(node, visit);
|
|
3708
4294
|
};
|
|
3709
4295
|
visit(body);
|
|
3710
4296
|
if (returns.length !== 1) return void 0;
|
|
3711
4297
|
const single = returns[0];
|
|
3712
|
-
if (!single?.expression || !
|
|
4298
|
+
if (!single?.expression || !ts9.isExpression(single.expression)) return void 0;
|
|
3713
4299
|
const returnExpression = single.expression;
|
|
3714
4300
|
const root = topLevelJsxChildren(returnExpression);
|
|
3715
4301
|
if (!root || root.children.length === 0) return void 0;
|
|
@@ -3732,11 +4318,11 @@ function usesOutsideJsx(declaration, hook, children) {
|
|
|
3732
4318
|
let outside = false;
|
|
3733
4319
|
const visit = (node) => {
|
|
3734
4320
|
if (outside) return;
|
|
3735
|
-
if (node !== declaration && (
|
|
4321
|
+
if (node !== declaration && (ts9.isFunctionDeclaration(node) || ts9.isClassDeclaration(node))) return;
|
|
3736
4322
|
if (isHookUsage(node, hook)) {
|
|
3737
4323
|
let current = node;
|
|
3738
4324
|
let isInJsxChild = false;
|
|
3739
|
-
while (!
|
|
4325
|
+
while (!ts9.isSourceFile(current)) {
|
|
3740
4326
|
if (children.includes(current)) {
|
|
3741
4327
|
isInJsxChild = true;
|
|
3742
4328
|
break;
|
|
@@ -3745,14 +4331,14 @@ function usesOutsideJsx(declaration, hook, children) {
|
|
|
3745
4331
|
}
|
|
3746
4332
|
if (!isInJsxChild) outside = true;
|
|
3747
4333
|
}
|
|
3748
|
-
|
|
4334
|
+
ts9.forEachChild(node, visit);
|
|
3749
4335
|
};
|
|
3750
4336
|
visit(declaration);
|
|
3751
4337
|
return outside;
|
|
3752
4338
|
}
|
|
3753
4339
|
|
|
3754
4340
|
// eslint/rules/locale-key-shape.ts
|
|
3755
|
-
import
|
|
4341
|
+
import path41 from "path";
|
|
3756
4342
|
var MAX_KEY_LENGTH = 30;
|
|
3757
4343
|
var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
|
|
3758
4344
|
"Button",
|
|
@@ -3802,7 +4388,7 @@ var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
|
|
|
3802
4388
|
var CAMEL_CASE = /^[a-z][a-zA-Z0-9]*$/;
|
|
3803
4389
|
var ENGLISH = /^[A-Za-z0-9_]*$/;
|
|
3804
4390
|
function isLocalesFile2(filename) {
|
|
3805
|
-
const segments =
|
|
4391
|
+
const segments = path41.resolve(filename).split(path41.sep);
|
|
3806
4392
|
const srcIdx = segments.lastIndexOf("src");
|
|
3807
4393
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
3808
4394
|
}
|
|
@@ -3873,8 +4459,8 @@ var localeKeyShapeRule = {
|
|
|
3873
4459
|
};
|
|
3874
4460
|
|
|
3875
4461
|
// eslint/rules/shared-style-dedup.ts
|
|
3876
|
-
import
|
|
3877
|
-
import { readFileSync as
|
|
4462
|
+
import path42 from "path";
|
|
4463
|
+
import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
3878
4464
|
var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
|
|
3879
4465
|
var comboCache;
|
|
3880
4466
|
function combosFor(index) {
|
|
@@ -3888,7 +4474,7 @@ function combosFor(index) {
|
|
|
3888
4474
|
}
|
|
3889
4475
|
const combos = /* @__PURE__ */ new Map();
|
|
3890
4476
|
for (const file of files) {
|
|
3891
|
-
const text =
|
|
4477
|
+
const text = readFileSync6(file, "utf8");
|
|
3892
4478
|
for (const match of text.matchAll(CLASS_NAME)) {
|
|
3893
4479
|
const classes = (match.groups?.classes ?? "").split(/\s+/).filter(Boolean);
|
|
3894
4480
|
if (classes.length < 2) continue;
|
|
@@ -3911,7 +4497,7 @@ var sharedStyleDedupRule = {
|
|
|
3911
4497
|
},
|
|
3912
4498
|
create(context) {
|
|
3913
4499
|
const sourceRoot = sourceRootOf(context);
|
|
3914
|
-
const file =
|
|
4500
|
+
const file = path42.resolve(context.filename);
|
|
3915
4501
|
const segments = segmentsOf(file, sourceRoot);
|
|
3916
4502
|
if (segments.length === 0) return {};
|
|
3917
4503
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -4115,7 +4701,7 @@ var zodSchemaValidationRule = {
|
|
|
4115
4701
|
};
|
|
4116
4702
|
|
|
4117
4703
|
// eslint/rules/schema-casing.ts
|
|
4118
|
-
import
|
|
4704
|
+
import path43 from "path";
|
|
4119
4705
|
function isCamelCase(name) {
|
|
4120
4706
|
return /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
4121
4707
|
}
|
|
@@ -4143,9 +4729,9 @@ var schemaCasingRule = {
|
|
|
4143
4729
|
}
|
|
4144
4730
|
},
|
|
4145
4731
|
create(context) {
|
|
4146
|
-
const filename =
|
|
4732
|
+
const filename = path43.resolve(context.filename);
|
|
4147
4733
|
const sourceRoot = sourceRootOf(context);
|
|
4148
|
-
if (!filename.startsWith(sourceRoot +
|
|
4734
|
+
if (!filename.startsWith(sourceRoot + path43.sep)) return {};
|
|
4149
4735
|
let zodLocalName;
|
|
4150
4736
|
return {
|
|
4151
4737
|
ImportDeclaration(node) {
|
|
@@ -4177,7 +4763,7 @@ var schemaCasingRule = {
|
|
|
4177
4763
|
};
|
|
4178
4764
|
|
|
4179
4765
|
// eslint/rules/component-casing.ts
|
|
4180
|
-
import
|
|
4766
|
+
import path44 from "path";
|
|
4181
4767
|
function isPascalCase6(name) {
|
|
4182
4768
|
return /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
4183
4769
|
}
|
|
@@ -4190,10 +4776,10 @@ var componentCasingRule = {
|
|
|
4190
4776
|
}
|
|
4191
4777
|
},
|
|
4192
4778
|
create(context) {
|
|
4193
|
-
const filename =
|
|
4779
|
+
const filename = path44.resolve(context.filename);
|
|
4194
4780
|
const sourceRoot = sourceRootOf(context);
|
|
4195
|
-
if (!filename.startsWith(sourceRoot +
|
|
4196
|
-
if (
|
|
4781
|
+
if (!filename.startsWith(sourceRoot + path44.sep)) return {};
|
|
4782
|
+
if (path44.extname(filename) !== ".tsx") return {};
|
|
4197
4783
|
return {
|
|
4198
4784
|
Program(node) {
|
|
4199
4785
|
for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
|
|
@@ -4210,7 +4796,7 @@ var componentCasingRule = {
|
|
|
4210
4796
|
};
|
|
4211
4797
|
|
|
4212
4798
|
// eslint/rules/source-under-src.ts
|
|
4213
|
-
import
|
|
4799
|
+
import path45 from "path";
|
|
4214
4800
|
var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
|
|
4215
4801
|
".agents",
|
|
4216
4802
|
".cache",
|
|
@@ -4251,14 +4837,14 @@ var sourceUnderSrcRule = {
|
|
|
4251
4837
|
}
|
|
4252
4838
|
},
|
|
4253
4839
|
create(context) {
|
|
4254
|
-
const filename =
|
|
4840
|
+
const filename = path45.resolve(context.filename);
|
|
4255
4841
|
if (!MODULE_EXTENSION.test(filename)) return {};
|
|
4256
|
-
const relative =
|
|
4842
|
+
const relative = path45.relative(context.cwd, filename).replace(/\\/g, "/");
|
|
4257
4843
|
if (relative === "src" || relative.startsWith("src/")) return {};
|
|
4258
4844
|
const topLevel = relative.split("/")[0] ?? "";
|
|
4259
4845
|
if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
|
|
4260
4846
|
if (!relative.includes("/")) {
|
|
4261
|
-
const basename =
|
|
4847
|
+
const basename = path45.basename(filename);
|
|
4262
4848
|
if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
|
|
4263
4849
|
}
|
|
4264
4850
|
return {
|
|
@@ -4275,8 +4861,8 @@ var sourceUnderSrcRule = {
|
|
|
4275
4861
|
|
|
4276
4862
|
// eslint/rules/zirka-baseline.ts
|
|
4277
4863
|
import fs5 from "fs";
|
|
4278
|
-
import
|
|
4279
|
-
var
|
|
4864
|
+
import path46 from "path";
|
|
4865
|
+
var ESLINT_CONFIG3 = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
|
|
4280
4866
|
var PRETTIER_CONFIGS = [
|
|
4281
4867
|
"prettier.config.mjs",
|
|
4282
4868
|
"prettier.config.cjs",
|
|
@@ -4294,10 +4880,10 @@ var zirkaBaselineRule = {
|
|
|
4294
4880
|
}
|
|
4295
4881
|
},
|
|
4296
4882
|
create(context) {
|
|
4297
|
-
const filename =
|
|
4298
|
-
const basename =
|
|
4299
|
-
if (!
|
|
4300
|
-
const projectRoot =
|
|
4883
|
+
const filename = path46.resolve(context.filename);
|
|
4884
|
+
const basename = path46.basename(filename);
|
|
4885
|
+
if (!ESLINT_CONFIG3.test(basename)) return {};
|
|
4886
|
+
const projectRoot = path46.dirname(filename);
|
|
4301
4887
|
const report3 = (message) => {
|
|
4302
4888
|
context.report({
|
|
4303
4889
|
node: context.sourceCode.ast,
|
|
@@ -4312,7 +4898,7 @@ var zirkaBaselineRule = {
|
|
|
4312
4898
|
'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
|
|
4313
4899
|
);
|
|
4314
4900
|
}
|
|
4315
|
-
const tsconfigPath =
|
|
4901
|
+
const tsconfigPath = path46.join(projectRoot, "tsconfig.json");
|
|
4316
4902
|
if (!fs5.existsSync(tsconfigPath)) {
|
|
4317
4903
|
report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
|
|
4318
4904
|
} else {
|
|
@@ -4330,13 +4916,13 @@ var zirkaBaselineRule = {
|
|
|
4330
4916
|
report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
|
|
4331
4917
|
}
|
|
4332
4918
|
}
|
|
4333
|
-
const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(
|
|
4919
|
+
const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(path46.join(projectRoot, name)));
|
|
4334
4920
|
if (!prettierConfigFile) {
|
|
4335
4921
|
report3(
|
|
4336
4922
|
"No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
|
|
4337
4923
|
);
|
|
4338
4924
|
} else {
|
|
4339
|
-
const content = fs5.readFileSync(
|
|
4925
|
+
const content = fs5.readFileSync(path46.join(projectRoot, prettierConfigFile), "utf8");
|
|
4340
4926
|
if (!content.includes("zirka")) {
|
|
4341
4927
|
report3(
|
|
4342
4928
|
"The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
|
|
@@ -4378,6 +4964,15 @@ function getTextContent(node) {
|
|
|
4378
4964
|
function getLine(node) {
|
|
4379
4965
|
return node.position?.start.line ?? 0;
|
|
4380
4966
|
}
|
|
4967
|
+
function linkTarget(url) {
|
|
4968
|
+
return url.split("#")[0] ?? url;
|
|
4969
|
+
}
|
|
4970
|
+
function isDocLink(url) {
|
|
4971
|
+
return linkTarget(url).endsWith(".md");
|
|
4972
|
+
}
|
|
4973
|
+
function headingAnchor(text) {
|
|
4974
|
+
return text.trim().toLowerCase().replaceAll(/[^\p{L}\p{N}\s-]/gu, "").replaceAll(/\s+/g, "-");
|
|
4975
|
+
}
|
|
4381
4976
|
|
|
4382
4977
|
// eslint/rules/documentation/doc-kind-suffix.ts
|
|
4383
4978
|
var docKindSuffixRule = {
|
|
@@ -4406,7 +5001,7 @@ var docKindSuffixRule = {
|
|
|
4406
5001
|
};
|
|
4407
5002
|
|
|
4408
5003
|
// eslint/rules/documentation/title-matches-file-name.ts
|
|
4409
|
-
import
|
|
5004
|
+
import path47 from "path";
|
|
4410
5005
|
function toExpectedFileName(title) {
|
|
4411
5006
|
return `${title.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
|
|
4412
5007
|
}
|
|
@@ -4426,7 +5021,7 @@ var titleMatchesFileNameRule = {
|
|
|
4426
5021
|
if (!filename.endsWith(".md")) return;
|
|
4427
5022
|
const title = getTextContent(node).trim();
|
|
4428
5023
|
const expectedFileName = toExpectedFileName(title);
|
|
4429
|
-
const actualFileName =
|
|
5024
|
+
const actualFileName = path47.basename(filename);
|
|
4430
5025
|
if (!title) {
|
|
4431
5026
|
context.report({
|
|
4432
5027
|
node,
|
|
@@ -4581,7 +5176,7 @@ var guideStepSingleSentenceRule = {
|
|
|
4581
5176
|
|
|
4582
5177
|
// eslint/rules/documentation/guide-step-single-link.ts
|
|
4583
5178
|
function countDocLinks(node) {
|
|
4584
|
-
if (node.type === "link" && node.url
|
|
5179
|
+
if (node.type === "link" && isDocLink(node.url)) return 1;
|
|
4585
5180
|
if ("children" in node) {
|
|
4586
5181
|
return node.children.reduce((sum, child) => sum + countDocLinks(child), 0);
|
|
4587
5182
|
}
|
|
@@ -4799,7 +5394,7 @@ var noCrossDocumentLinkRule = {
|
|
|
4799
5394
|
const filename = getFilename(context);
|
|
4800
5395
|
const kind = linkedKind(filename);
|
|
4801
5396
|
if (!kind) return;
|
|
4802
|
-
if (node.url
|
|
5397
|
+
if (isDocLink(node.url)) {
|
|
4803
5398
|
context.report({
|
|
4804
5399
|
node,
|
|
4805
5400
|
message: `${kind} links another document: ${node.url}`
|
|
@@ -4867,22 +5462,46 @@ var referenceBlockHeadingsRule = {
|
|
|
4867
5462
|
}
|
|
4868
5463
|
};
|
|
4869
5464
|
|
|
5465
|
+
// eslint/rules/documentation/reference-max-heading-depth.ts
|
|
5466
|
+
var referenceMaxHeadingDepthRule = {
|
|
5467
|
+
meta: {
|
|
5468
|
+
type: "problem",
|
|
5469
|
+
docs: {
|
|
5470
|
+
description: "Reference max heading depth rule.",
|
|
5471
|
+
recommended: true
|
|
5472
|
+
}
|
|
5473
|
+
},
|
|
5474
|
+
create(context) {
|
|
5475
|
+
return {
|
|
5476
|
+
heading(node) {
|
|
5477
|
+
const filename = getFilename(context);
|
|
5478
|
+
if (!filename.endsWith("-reference.md")) return;
|
|
5479
|
+
if (node.depth <= 2) return;
|
|
5480
|
+
context.report({
|
|
5481
|
+
node,
|
|
5482
|
+
message: "reference heading is deeper than level 2; flatten it into the section it would nest under"
|
|
5483
|
+
});
|
|
5484
|
+
}
|
|
5485
|
+
};
|
|
5486
|
+
}
|
|
5487
|
+
};
|
|
5488
|
+
|
|
4870
5489
|
// eslint/rules/documentation/support-document-placement.ts
|
|
4871
5490
|
import { existsSync as existsSync2 } from "fs";
|
|
4872
|
-
import
|
|
5491
|
+
import path48 from "path";
|
|
4873
5492
|
function checkPlacement(filename, kind) {
|
|
4874
|
-
const parentFolder =
|
|
5493
|
+
const parentFolder = path48.basename(path48.dirname(filename));
|
|
4875
5494
|
const expectedParent = `${kind}s`;
|
|
4876
5495
|
if (parentFolder !== expectedParent) {
|
|
4877
5496
|
return `${kind} lives in "${parentFolder}/" instead of "${expectedParent}/"`;
|
|
4878
5497
|
}
|
|
4879
|
-
const guideFolderPath =
|
|
4880
|
-
const guideFolder =
|
|
5498
|
+
const guideFolderPath = path48.dirname(path48.dirname(filename));
|
|
5499
|
+
const guideFolder = path48.basename(guideFolderPath);
|
|
4881
5500
|
if (!guideFolder.endsWith("-guide")) {
|
|
4882
5501
|
return `${kind} owner folder "${guideFolder}/" does not use the "*-guide/" suffix`;
|
|
4883
5502
|
}
|
|
4884
5503
|
const entryPoint = `${guideFolder}.md`;
|
|
4885
|
-
if (!existsSync2(
|
|
5504
|
+
if (!existsSync2(path48.join(guideFolderPath, entryPoint))) {
|
|
4886
5505
|
return `${kind} owner folder "${guideFolder}/" has no "${entryPoint}" entry point`;
|
|
4887
5506
|
}
|
|
4888
5507
|
return void 0;
|
|
@@ -4941,11 +5560,11 @@ var noTemplatePromptRule = {
|
|
|
4941
5560
|
};
|
|
4942
5561
|
|
|
4943
5562
|
// eslint/rules/documentation/guide-folder-entry-point.ts
|
|
4944
|
-
import
|
|
5563
|
+
import path50 from "path";
|
|
4945
5564
|
|
|
4946
5565
|
// eslint/rules/documentation/project-index.ts
|
|
4947
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
4948
|
-
import
|
|
5566
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
5567
|
+
import path49 from "path";
|
|
4949
5568
|
var KIND_BY_SUFFIX = [
|
|
4950
5569
|
["-rule.md", "rule"],
|
|
4951
5570
|
["-guide.md", "guide"],
|
|
@@ -4954,7 +5573,7 @@ var KIND_BY_SUFFIX = [
|
|
|
4954
5573
|
];
|
|
4955
5574
|
function listMarkdownFiles(dir) {
|
|
4956
5575
|
return readdirSync3(dir).flatMap((entry) => {
|
|
4957
|
-
const entryPath =
|
|
5576
|
+
const entryPath = path49.join(dir, entry);
|
|
4958
5577
|
if (statSync4(entryPath).isDirectory()) {
|
|
4959
5578
|
return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
|
|
4960
5579
|
}
|
|
@@ -4962,7 +5581,7 @@ function listMarkdownFiles(dir) {
|
|
|
4962
5581
|
});
|
|
4963
5582
|
}
|
|
4964
5583
|
function extractTitle(filePath) {
|
|
4965
|
-
const content =
|
|
5584
|
+
const content = readFileSync7(filePath, "utf8");
|
|
4966
5585
|
const match = /^# (?<title>.+)$/m.exec(content);
|
|
4967
5586
|
return match?.groups?.title?.trim() ?? "";
|
|
4968
5587
|
}
|
|
@@ -4972,11 +5591,11 @@ function getProjectDocs(docsRoot) {
|
|
|
4972
5591
|
if (cached) return cached;
|
|
4973
5592
|
const files = listMarkdownFiles(docsRoot);
|
|
4974
5593
|
const docs = files.sort((a, b) => a.localeCompare(b)).map((filePath) => {
|
|
4975
|
-
const fileName =
|
|
5594
|
+
const fileName = path49.basename(filePath);
|
|
4976
5595
|
const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
|
|
4977
5596
|
return {
|
|
4978
5597
|
filePath,
|
|
4979
|
-
doc:
|
|
5598
|
+
doc: path49.relative(docsRoot, filePath).split(path49.sep).join("/"),
|
|
4980
5599
|
fileName,
|
|
4981
5600
|
kind,
|
|
4982
5601
|
title: extractTitle(filePath)
|
|
@@ -4986,12 +5605,12 @@ function getProjectDocs(docsRoot) {
|
|
|
4986
5605
|
return docs;
|
|
4987
5606
|
}
|
|
4988
5607
|
function findDocsRoot(filePath) {
|
|
4989
|
-
let dir =
|
|
5608
|
+
let dir = path49.dirname(filePath);
|
|
4990
5609
|
for (; ; ) {
|
|
4991
|
-
if (
|
|
5610
|
+
if (path49.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
|
|
4992
5611
|
return dir;
|
|
4993
5612
|
}
|
|
4994
|
-
const parent =
|
|
5613
|
+
const parent = path49.dirname(dir);
|
|
4995
5614
|
if (parent === dir) return void 0;
|
|
4996
5615
|
dir = parent;
|
|
4997
5616
|
}
|
|
@@ -5015,13 +5634,13 @@ var guideFolderEntryPointRule = {
|
|
|
5015
5634
|
if (!docsRoot) return;
|
|
5016
5635
|
const docs = getProjectDocs(docsRoot);
|
|
5017
5636
|
const guideFolders = new Set(
|
|
5018
|
-
docs.filter((doc) => ["rules", "references"].includes(
|
|
5637
|
+
docs.filter((doc) => ["rules", "references"].includes(path50.basename(path50.dirname(doc.filePath)))).map((doc) => path50.dirname(path50.dirname(doc.filePath))).filter((folder) => path50.resolve(folder) !== path50.resolve(docsRoot))
|
|
5019
5638
|
);
|
|
5020
|
-
const currentDir =
|
|
5639
|
+
const currentDir = path50.dirname(filename);
|
|
5021
5640
|
if (guideFolders.has(currentDir)) {
|
|
5022
|
-
const expectedEntryPoint = `${
|
|
5641
|
+
const expectedEntryPoint = `${path50.basename(currentDir)}.md`;
|
|
5023
5642
|
const hasEntryPoint = docs.some(
|
|
5024
|
-
(doc) => doc.kind === "guide" &&
|
|
5643
|
+
(doc) => doc.kind === "guide" && path50.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
|
|
5025
5644
|
);
|
|
5026
5645
|
if (!hasEntryPoint) {
|
|
5027
5646
|
context.report({
|
|
@@ -5165,6 +5784,8 @@ var policySubjectHeadingsRule = {
|
|
|
5165
5784
|
};
|
|
5166
5785
|
|
|
5167
5786
|
// eslint/rules/documentation/guide-link-anchors.ts
|
|
5787
|
+
import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
|
|
5788
|
+
import path51 from "path";
|
|
5168
5789
|
function visitSteps2(node, check) {
|
|
5169
5790
|
if (node.type === "list" && node.ordered) {
|
|
5170
5791
|
for (const child of node.children) check(child);
|
|
@@ -5174,17 +5795,52 @@ function visitSteps2(node, check) {
|
|
|
5174
5795
|
}
|
|
5175
5796
|
}
|
|
5176
5797
|
function collectGuideLinks(node) {
|
|
5177
|
-
if (node.type === "link" && node.url.endsWith("-guide.md")) return [node];
|
|
5798
|
+
if (node.type === "link" && linkTarget(node.url).endsWith("-guide.md")) return [node];
|
|
5178
5799
|
if ("children" in node) {
|
|
5179
5800
|
return node.children.flatMap(collectGuideLinks);
|
|
5180
5801
|
}
|
|
5181
5802
|
return [];
|
|
5182
5803
|
}
|
|
5804
|
+
function collectLinks(node, out) {
|
|
5805
|
+
if (node.type === "link") out.push(node);
|
|
5806
|
+
if ("children" in node) {
|
|
5807
|
+
for (const child of node.children) collectLinks(child, out);
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
var anchorsByFile = /* @__PURE__ */ new Map();
|
|
5811
|
+
function documentAnchors(filePath) {
|
|
5812
|
+
const cached = anchorsByFile.get(filePath);
|
|
5813
|
+
if (cached) return cached;
|
|
5814
|
+
const anchors = /* @__PURE__ */ new Set();
|
|
5815
|
+
anchorsByFile.set(filePath, anchors);
|
|
5816
|
+
let content;
|
|
5817
|
+
try {
|
|
5818
|
+
content = readFileSync8(filePath, "utf8");
|
|
5819
|
+
} catch {
|
|
5820
|
+
return anchors;
|
|
5821
|
+
}
|
|
5822
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5823
|
+
let fenced = false;
|
|
5824
|
+
for (const line of content.split("\n")) {
|
|
5825
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
5826
|
+
fenced = !fenced;
|
|
5827
|
+
continue;
|
|
5828
|
+
}
|
|
5829
|
+
if (fenced) continue;
|
|
5830
|
+
const heading = /^#{1,6}\s+(?<text>.+)$/.exec(line)?.groups?.text;
|
|
5831
|
+
if (!heading) continue;
|
|
5832
|
+
const base = headingAnchor(heading);
|
|
5833
|
+
const count = seen.get(base) ?? 0;
|
|
5834
|
+
seen.set(base, count + 1);
|
|
5835
|
+
anchors.add(count === 0 ? base : `${base}-${String(count)}`);
|
|
5836
|
+
}
|
|
5837
|
+
return anchors;
|
|
5838
|
+
}
|
|
5183
5839
|
var guideLinkAnchorsRule = {
|
|
5184
5840
|
meta: {
|
|
5185
5841
|
type: "problem",
|
|
5186
5842
|
docs: {
|
|
5187
|
-
description: "A step that links another Guide must link directly to a How To section.",
|
|
5843
|
+
description: "A step that links another Guide must link directly to a How To section, and a link that carries an anchor must point at a heading the linked document has.",
|
|
5188
5844
|
recommended: true
|
|
5189
5845
|
}
|
|
5190
5846
|
},
|
|
@@ -5203,6 +5859,19 @@ var guideLinkAnchorsRule = {
|
|
|
5203
5859
|
}
|
|
5204
5860
|
}
|
|
5205
5861
|
});
|
|
5862
|
+
const links = [];
|
|
5863
|
+
collectLinks(node, links);
|
|
5864
|
+
for (const link of links) {
|
|
5865
|
+
const [target = "", fragment = ""] = link.url.split("#");
|
|
5866
|
+
if (!fragment || !isDocLink(link.url)) continue;
|
|
5867
|
+
const targetPath = path51.resolve(path51.dirname(filename), target);
|
|
5868
|
+
if (!existsSync3(targetPath)) continue;
|
|
5869
|
+
if (documentAnchors(targetPath).has(fragment.toLowerCase())) continue;
|
|
5870
|
+
context.report({
|
|
5871
|
+
node: link,
|
|
5872
|
+
message: `link ${link.url} points at a heading the document does not have`
|
|
5873
|
+
});
|
|
5874
|
+
}
|
|
5206
5875
|
}
|
|
5207
5876
|
};
|
|
5208
5877
|
}
|
|
@@ -5245,41 +5914,82 @@ var noNestedHowToRule = {
|
|
|
5245
5914
|
};
|
|
5246
5915
|
|
|
5247
5916
|
// eslint/rules/documentation/glossary-term-linking.ts
|
|
5248
|
-
import { readFileSync as
|
|
5249
|
-
import
|
|
5917
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
5918
|
+
import path52 from "path";
|
|
5919
|
+
function isGlossary(filePath) {
|
|
5920
|
+
return path52.basename(filePath).includes("glossary");
|
|
5921
|
+
}
|
|
5922
|
+
function termNames(cell) {
|
|
5923
|
+
const abbreviation = /\((?<abbreviation>[^()]+)\)/.exec(cell)?.groups?.abbreviation?.trim();
|
|
5924
|
+
return abbreviation ? [cell, abbreviation] : [cell];
|
|
5925
|
+
}
|
|
5926
|
+
function extractTableTerms(content) {
|
|
5927
|
+
const terms = [];
|
|
5928
|
+
const rowPattern = /^\|(?<cell>[^|]*)\|/gm;
|
|
5929
|
+
for (const match of content.matchAll(rowPattern)) {
|
|
5930
|
+
const cell = match.groups?.cell?.trim() ?? "";
|
|
5931
|
+
if (!cell || /^:?-+:?$/.test(cell) || cell.toLowerCase() === "term") continue;
|
|
5932
|
+
terms.push({ term: cell, names: termNames(cell) });
|
|
5933
|
+
}
|
|
5934
|
+
return terms;
|
|
5935
|
+
}
|
|
5250
5936
|
function extractGlossaryTerms(filePath) {
|
|
5251
|
-
|
|
5937
|
+
if (!isGlossary(filePath)) return [];
|
|
5938
|
+
const content = readFileSync9(filePath, "utf8");
|
|
5939
|
+
const tableTerms = extractTableTerms(content);
|
|
5940
|
+
if (tableTerms.length > 0) return tableTerms;
|
|
5252
5941
|
const terms = [];
|
|
5253
5942
|
const headingPattern = /^## (?<term>.+)$/gm;
|
|
5254
|
-
|
|
5255
|
-
while ((match = headingPattern.exec(content)) !== null) {
|
|
5943
|
+
for (const match of content.matchAll(headingPattern)) {
|
|
5256
5944
|
const term = match.groups?.term?.trim();
|
|
5257
|
-
if (term) terms.push(term);
|
|
5945
|
+
if (term) terms.push({ term, names: termNames(term) });
|
|
5258
5946
|
}
|
|
5259
5947
|
return terms;
|
|
5260
5948
|
}
|
|
5261
|
-
function
|
|
5262
|
-
const
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
collectDocLinks(item, firstStepLinks);
|
|
5269
|
-
}
|
|
5949
|
+
function extractBlockHeadings(filePath) {
|
|
5950
|
+
const headings = [];
|
|
5951
|
+
let fenced = false;
|
|
5952
|
+
for (const line of readFileSync9(filePath, "utf8").split("\n")) {
|
|
5953
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
5954
|
+
fenced = !fenced;
|
|
5955
|
+
continue;
|
|
5270
5956
|
}
|
|
5957
|
+
if (fenced) continue;
|
|
5958
|
+
const heading = /^##\s+(?<text>.+)$/.exec(line)?.groups?.text;
|
|
5959
|
+
if (heading) headings.push(heading.trim());
|
|
5271
5960
|
}
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5961
|
+
return headings;
|
|
5962
|
+
}
|
|
5963
|
+
function normalize(text) {
|
|
5964
|
+
return text.replaceAll("`", "").toLowerCase();
|
|
5965
|
+
}
|
|
5966
|
+
function collectSections(node) {
|
|
5967
|
+
const sections = [];
|
|
5968
|
+
const children = node.children;
|
|
5969
|
+
for (const [index, child] of children.entries()) {
|
|
5970
|
+
if (child.type !== "heading" || child.depth !== 2) continue;
|
|
5971
|
+
const title = getTextContent(child).trim();
|
|
5972
|
+
if (!/^How To \S/.test(title)) continue;
|
|
5973
|
+
const end = children.findIndex(
|
|
5974
|
+
(sibling, siblingIndex) => siblingIndex > index && sibling.type === "heading" && sibling.depth === 2
|
|
5975
|
+
);
|
|
5976
|
+
const section = { heading: child, title, stepTexts: [], firstStepLinks: [] };
|
|
5977
|
+
collectSteps(children.slice(index + 1, end === -1 ? children.length : end), section);
|
|
5978
|
+
sections.push(section);
|
|
5979
|
+
}
|
|
5980
|
+
return sections;
|
|
5981
|
+
}
|
|
5982
|
+
function collectSteps(content, section) {
|
|
5983
|
+
const lists = content.filter((node) => node.type === "list");
|
|
5984
|
+
const paragraphs = content.filter((node) => node.type === "paragraph");
|
|
5985
|
+
const steps = lists.length > 0 ? lists.flatMap((list) => list.children) : paragraphs.slice(content[0]?.type === "paragraph" ? 1 : 0);
|
|
5986
|
+
for (const step of steps) {
|
|
5987
|
+
section.stepTexts.push(getTextContent(step));
|
|
5988
|
+
if (section.stepTexts.length === 1) collectDocLinks(step, section.firstStepLinks);
|
|
5278
5989
|
}
|
|
5279
|
-
return { texts, firstStepLinks };
|
|
5280
5990
|
}
|
|
5281
5991
|
function collectDocLinks(node, out) {
|
|
5282
|
-
if (node.type === "link" && node.url
|
|
5992
|
+
if (node.type === "link" && isDocLink(node.url)) out.push(node.url);
|
|
5283
5993
|
if ("children" in node) {
|
|
5284
5994
|
for (const child of node.children) collectDocLinks(child, out);
|
|
5285
5995
|
}
|
|
@@ -5288,7 +5998,7 @@ var glossaryTermLinkingRule = {
|
|
|
5288
5998
|
meta: {
|
|
5289
5999
|
type: "problem",
|
|
5290
6000
|
docs: {
|
|
5291
|
-
description: "A
|
|
6001
|
+
description: "A How To section whose steps use glossary terms must link that Reference from the first step, and every block of a shared glossary must be read by one of them.",
|
|
5292
6002
|
recommended: true
|
|
5293
6003
|
}
|
|
5294
6004
|
},
|
|
@@ -5300,25 +6010,55 @@ var glossaryTermLinkingRule = {
|
|
|
5300
6010
|
const docsRoot = findDocsRoot(filename);
|
|
5301
6011
|
if (!docsRoot) return;
|
|
5302
6012
|
const docs = getProjectDocs(docsRoot);
|
|
5303
|
-
const guideDir =
|
|
6013
|
+
const guideDir = path52.dirname(filename);
|
|
6014
|
+
const referencesDir = path52.join(guideDir, "references");
|
|
5304
6015
|
const guideReferences = docs.filter(
|
|
5305
|
-
(doc) => doc.kind === "reference" &&
|
|
6016
|
+
(doc) => doc.kind === "reference" && path52.dirname(doc.filePath) === referencesDir
|
|
5306
6017
|
);
|
|
5307
|
-
|
|
6018
|
+
const glossaryReferences = guideReferences.filter((doc) => isGlossary(doc.filePath));
|
|
6019
|
+
if (glossaryReferences.length === 0) return;
|
|
5308
6020
|
const glossaryTerms = [];
|
|
5309
|
-
for (const ref of
|
|
6021
|
+
for (const ref of glossaryReferences) {
|
|
5310
6022
|
glossaryTerms.push(...extractGlossaryTerms(ref.filePath));
|
|
5311
6023
|
}
|
|
5312
6024
|
if (glossaryTerms.length === 0) return;
|
|
5313
|
-
const
|
|
5314
|
-
const
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
6025
|
+
const sections = collectSections(node);
|
|
6026
|
+
for (const ref of glossaryReferences) {
|
|
6027
|
+
for (const heading of extractBlockHeadings(ref.filePath)) {
|
|
6028
|
+
const anchor = headingAnchor(heading);
|
|
6029
|
+
const anchored = sections.some(
|
|
6030
|
+
(section) => section.firstStepLinks.some((link) => {
|
|
6031
|
+
const fragment = link.split("#")[1] ?? "";
|
|
6032
|
+
if (fragment.toLowerCase() !== anchor) return false;
|
|
6033
|
+
return path52.resolve(guideDir, linkTarget(link)) === path52.resolve(ref.filePath);
|
|
6034
|
+
})
|
|
6035
|
+
);
|
|
6036
|
+
if (!anchored) {
|
|
6037
|
+
context.report({
|
|
6038
|
+
node,
|
|
6039
|
+
message: `shared glossary block "${heading}" is not read by any How To section's first step`
|
|
6040
|
+
});
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
6044
|
+
for (const section of sections) {
|
|
6045
|
+
const normalizedStepTexts = section.stepTexts.map(normalize);
|
|
6046
|
+
const usedTerms = glossaryTerms.filter(
|
|
6047
|
+
(entry) => entry.names.some((name) => {
|
|
6048
|
+
const needle = normalize(name);
|
|
6049
|
+
return normalizedStepTexts.some((text) => text.includes(needle));
|
|
6050
|
+
})
|
|
6051
|
+
).map((entry) => entry.term);
|
|
6052
|
+
if (usedTerms.length === 0) continue;
|
|
6053
|
+
const hasGlossaryLink = section.firstStepLinks.some(
|
|
6054
|
+
(link) => glossaryReferences.some((ref) => link.includes(ref.fileName))
|
|
6055
|
+
);
|
|
6056
|
+
if (!hasGlossaryLink) {
|
|
6057
|
+
context.report({
|
|
6058
|
+
node: section.heading,
|
|
6059
|
+
message: `guide section "${section.title}" uses glossary terms (${usedTerms.join(", ")}) but its first step does not link the glossary reference`
|
|
6060
|
+
});
|
|
6061
|
+
}
|
|
5322
6062
|
}
|
|
5323
6063
|
}
|
|
5324
6064
|
};
|
|
@@ -5326,8 +6066,8 @@ var glossaryTermLinkingRule = {
|
|
|
5326
6066
|
};
|
|
5327
6067
|
|
|
5328
6068
|
// eslint/rules/documentation/guide-mentions-documents.ts
|
|
5329
|
-
import { existsSync as
|
|
5330
|
-
import
|
|
6069
|
+
import { existsSync as existsSync4 } from "fs";
|
|
6070
|
+
import path53 from "path";
|
|
5331
6071
|
function visitSteps3(node, check) {
|
|
5332
6072
|
if (node.type === "list" && node.ordered) {
|
|
5333
6073
|
for (const child of node.children) check(child);
|
|
@@ -5337,14 +6077,11 @@ function visitSteps3(node, check) {
|
|
|
5337
6077
|
}
|
|
5338
6078
|
}
|
|
5339
6079
|
function collectMarkdownLinks(node, out) {
|
|
5340
|
-
if (node.type === "link" && node.url
|
|
6080
|
+
if (node.type === "link" && isDocLink(node.url)) out.push(node);
|
|
5341
6081
|
if ("children" in node) {
|
|
5342
6082
|
for (const child of node.children) collectMarkdownLinks(child, out);
|
|
5343
6083
|
}
|
|
5344
6084
|
}
|
|
5345
|
-
function linkTarget(url) {
|
|
5346
|
-
return url.split("#")[0] ?? url;
|
|
5347
|
-
}
|
|
5348
6085
|
var guideMentionsDocumentsRule = {
|
|
5349
6086
|
meta: {
|
|
5350
6087
|
type: "problem",
|
|
@@ -5360,12 +6097,12 @@ var guideMentionsDocumentsRule = {
|
|
|
5360
6097
|
if (!filename.endsWith("-guide.md")) return;
|
|
5361
6098
|
const docsRoot = findDocsRoot(filename);
|
|
5362
6099
|
if (!docsRoot) return;
|
|
5363
|
-
const guideDir =
|
|
5364
|
-
if (
|
|
6100
|
+
const guideDir = path53.dirname(filename);
|
|
6101
|
+
if (path53.basename(filename, ".md") !== path53.basename(guideDir)) return;
|
|
5365
6102
|
const docs = getProjectDocs(docsRoot);
|
|
5366
6103
|
const owned = docs.filter((doc) => {
|
|
5367
|
-
const parent =
|
|
5368
|
-
return parent ===
|
|
6104
|
+
const parent = path53.dirname(doc.filePath);
|
|
6105
|
+
return parent === path53.join(guideDir, "rules") || parent === path53.join(guideDir, "references");
|
|
5369
6106
|
});
|
|
5370
6107
|
const allLinks = [];
|
|
5371
6108
|
collectMarkdownLinks(node, allLinks);
|
|
@@ -5373,7 +6110,7 @@ var guideMentionsDocumentsRule = {
|
|
|
5373
6110
|
visitSteps3(node, (item) => {
|
|
5374
6111
|
collectMarkdownLinks(item, stepLinks);
|
|
5375
6112
|
});
|
|
5376
|
-
const mentionsOf = (links, fileName) => links.some((link) => link.url.split("/").pop() === fileName);
|
|
6113
|
+
const mentionsOf = (links, fileName) => links.some((link) => linkTarget(link.url).split("/").pop() === fileName);
|
|
5377
6114
|
for (const doc of owned) {
|
|
5378
6115
|
if (doc.kind === "rule") {
|
|
5379
6116
|
if (!mentionsOf(stepLinks, doc.fileName)) {
|
|
@@ -5392,8 +6129,8 @@ var guideMentionsDocumentsRule = {
|
|
|
5392
6129
|
for (const link of allLinks) {
|
|
5393
6130
|
const target = linkTarget(link.url);
|
|
5394
6131
|
if (!target.endsWith(".md")) continue;
|
|
5395
|
-
const resolved =
|
|
5396
|
-
if (!
|
|
6132
|
+
const resolved = path53.normalize(path53.join(guideDir, target));
|
|
6133
|
+
if (!existsSync4(resolved)) {
|
|
5397
6134
|
context.report({
|
|
5398
6135
|
node: link,
|
|
5399
6136
|
message: `Guide links a document that does not exist: ${link.url}`
|
|
@@ -5470,6 +6207,7 @@ var documentationRules = {
|
|
|
5470
6207
|
"no-cross-document-link": noCrossDocumentLinkRule,
|
|
5471
6208
|
"reference-no-rfc-vocabulary": referenceNoRfcVocabularyRule,
|
|
5472
6209
|
"reference-block-headings": referenceBlockHeadingsRule,
|
|
6210
|
+
"reference-max-heading-depth": referenceMaxHeadingDepthRule,
|
|
5473
6211
|
"support-document-placement": supportDocumentPlacementRule,
|
|
5474
6212
|
"no-template-prompt": noTemplatePromptRule,
|
|
5475
6213
|
"guide-folder-entry-point": guideFolderEntryPointRule,
|
|
@@ -5942,11 +6680,11 @@ var themeVariableNamespaceRule = {
|
|
|
5942
6680
|
|
|
5943
6681
|
// eslint/rules/tailwind/css-entry-point.ts
|
|
5944
6682
|
import { statSync as statSync6 } from "fs";
|
|
5945
|
-
import
|
|
6683
|
+
import path56 from "path";
|
|
5946
6684
|
|
|
5947
6685
|
// eslint/rules/tailwind/source-files.ts
|
|
5948
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
5949
|
-
import
|
|
6686
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync5 } from "fs";
|
|
6687
|
+
import path54 from "path";
|
|
5950
6688
|
var CSS_EXTENSIONS = [".css"];
|
|
5951
6689
|
var MODULE_EXTENSIONS3 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
5952
6690
|
var SOURCE_EXTENSIONS = [...MODULE_EXTENSIONS3, ...CSS_EXTENSIONS];
|
|
@@ -5959,7 +6697,7 @@ function findFiles(dir, extensions) {
|
|
|
5959
6697
|
}
|
|
5960
6698
|
return entries.flatMap((entry) => {
|
|
5961
6699
|
if (entry.startsWith(".") || entry === "node_modules") return [];
|
|
5962
|
-
const entryPath =
|
|
6700
|
+
const entryPath = path54.join(dir, entry);
|
|
5963
6701
|
let stats;
|
|
5964
6702
|
try {
|
|
5965
6703
|
stats = statSync5(entryPath);
|
|
@@ -5967,7 +6705,7 @@ function findFiles(dir, extensions) {
|
|
|
5967
6705
|
return [];
|
|
5968
6706
|
}
|
|
5969
6707
|
if (stats.isDirectory()) return findFiles(entryPath, extensions);
|
|
5970
|
-
return extensions.includes(
|
|
6708
|
+
return extensions.includes(path54.extname(entry)) ? [entryPath] : [];
|
|
5971
6709
|
});
|
|
5972
6710
|
}
|
|
5973
6711
|
function cachedTextReader() {
|
|
@@ -5976,7 +6714,7 @@ function cachedTextReader() {
|
|
|
5976
6714
|
let text = texts.get(file);
|
|
5977
6715
|
if (text === void 0) {
|
|
5978
6716
|
try {
|
|
5979
|
-
text =
|
|
6717
|
+
text = readFileSync10(file, "utf8");
|
|
5980
6718
|
} catch {
|
|
5981
6719
|
text = "";
|
|
5982
6720
|
}
|
|
@@ -5990,7 +6728,7 @@ function escapeRegExp(text) {
|
|
|
5990
6728
|
}
|
|
5991
6729
|
|
|
5992
6730
|
// eslint/rules/tailwind/stylesheet-graph.ts
|
|
5993
|
-
import
|
|
6731
|
+
import path55 from "path";
|
|
5994
6732
|
function registersTailwind(text) {
|
|
5995
6733
|
return /@import\s+(?:url\(\s*)?["']tailwindcss["']\s*\)?/i.test(text);
|
|
5996
6734
|
}
|
|
@@ -6004,25 +6742,25 @@ function moduleImports(text, fileName) {
|
|
|
6004
6742
|
return new RegExp(`(?:import|require)\\s*\\(?\\s*["'][^"']*${escaped}["']`, "i").test(text);
|
|
6005
6743
|
}
|
|
6006
6744
|
function resolveSpecifier2(fromFile, spec, sourceRoot) {
|
|
6007
|
-
if (spec.startsWith("/")) return
|
|
6008
|
-
if (spec.startsWith("./") || spec.startsWith("../")) return
|
|
6009
|
-
if (spec.startsWith("@/")) return
|
|
6745
|
+
if (spec.startsWith("/")) return path55.resolve(spec);
|
|
6746
|
+
if (spec.startsWith("./") || spec.startsWith("../")) return path55.resolve(path55.dirname(fromFile), spec);
|
|
6747
|
+
if (spec.startsWith("@/")) return path55.resolve(sourceRoot, spec.slice(2));
|
|
6010
6748
|
return void 0;
|
|
6011
6749
|
}
|
|
6012
6750
|
function buildStylesheetGraph(options) {
|
|
6013
6751
|
const { cssFiles, sourceRoot, textOf } = options;
|
|
6014
|
-
const cssSet = new Set(cssFiles.map((file) =>
|
|
6752
|
+
const cssSet = new Set(cssFiles.map((file) => path55.normalize(file)));
|
|
6015
6753
|
const globals = cssFiles.filter((file) => registersTailwind(textOf(file)));
|
|
6016
6754
|
const reachable = /* @__PURE__ */ new Set();
|
|
6017
6755
|
const queue = [...globals];
|
|
6018
|
-
for (const global of globals) reachable.add(
|
|
6756
|
+
for (const global of globals) reachable.add(path55.normalize(global));
|
|
6019
6757
|
while (queue.length > 0) {
|
|
6020
6758
|
const from = queue.shift();
|
|
6021
6759
|
if (!from) continue;
|
|
6022
6760
|
for (const spec of importedSpecifiers(textOf(from))) {
|
|
6023
6761
|
const target = resolveSpecifier2(from, spec, sourceRoot);
|
|
6024
6762
|
if (!target) continue;
|
|
6025
|
-
const normalized =
|
|
6763
|
+
const normalized = path55.normalize(target);
|
|
6026
6764
|
if (cssSet.has(normalized) && !reachable.has(normalized)) {
|
|
6027
6765
|
reachable.add(normalized);
|
|
6028
6766
|
queue.push(normalized);
|
|
@@ -6034,7 +6772,7 @@ function buildStylesheetGraph(options) {
|
|
|
6034
6772
|
for (const spec of importedSpecifiers(textOf(global))) {
|
|
6035
6773
|
const target = resolveSpecifier2(global, spec, sourceRoot);
|
|
6036
6774
|
if (!target) continue;
|
|
6037
|
-
const normalized =
|
|
6775
|
+
const normalized = path55.normalize(target);
|
|
6038
6776
|
if (cssSet.has(normalized)) directChildren.add(normalized);
|
|
6039
6777
|
}
|
|
6040
6778
|
}
|
|
@@ -6067,7 +6805,7 @@ var cssEntryPointRule = {
|
|
|
6067
6805
|
return {
|
|
6068
6806
|
"StyleSheet:exit"(node) {
|
|
6069
6807
|
if (globals.length === 0) return;
|
|
6070
|
-
const current =
|
|
6808
|
+
const current = path56.normalize(path56.resolve(context.filename));
|
|
6071
6809
|
if (globals.includes(current)) {
|
|
6072
6810
|
if (globals.length > 1) {
|
|
6073
6811
|
context.report({
|
|
@@ -6076,7 +6814,7 @@ var cssEntryPointRule = {
|
|
|
6076
6814
|
});
|
|
6077
6815
|
return;
|
|
6078
6816
|
}
|
|
6079
|
-
const basename =
|
|
6817
|
+
const basename = path56.basename(current);
|
|
6080
6818
|
const importCount = moduleFiles.filter((modulePath) => moduleImports(textOf(modulePath), basename)).length;
|
|
6081
6819
|
if (importCount !== 1) {
|
|
6082
6820
|
context.report({
|
|
@@ -6404,8 +7142,8 @@ var nextjsStackRule = {
|
|
|
6404
7142
|
};
|
|
6405
7143
|
|
|
6406
7144
|
// eslint/rules/package-json/vitest-coverage.ts
|
|
6407
|
-
import { existsSync as
|
|
6408
|
-
import
|
|
7145
|
+
import { existsSync as existsSync5, readFileSync as readFileSync11 } from "fs";
|
|
7146
|
+
import path57 from "path";
|
|
6409
7147
|
function memberName4(member) {
|
|
6410
7148
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
6411
7149
|
}
|
|
@@ -6474,7 +7212,7 @@ var vitestCoverageRule = {
|
|
|
6474
7212
|
message: 'package.json must declare a "test:unit:coverage" script that runs Vitest with coverage.'
|
|
6475
7213
|
});
|
|
6476
7214
|
}
|
|
6477
|
-
const configName = VITEST_CONFIG_NAMES.find((name) =>
|
|
7215
|
+
const configName = VITEST_CONFIG_NAMES.find((name) => existsSync5(path57.join(context.cwd, name)));
|
|
6478
7216
|
if (!configName) {
|
|
6479
7217
|
context.report({
|
|
6480
7218
|
node,
|
|
@@ -6482,7 +7220,7 @@ var vitestCoverageRule = {
|
|
|
6482
7220
|
});
|
|
6483
7221
|
return;
|
|
6484
7222
|
}
|
|
6485
|
-
const content =
|
|
7223
|
+
const content = readFileSync11(path57.join(context.cwd, configName), "utf8");
|
|
6486
7224
|
for (const metric of THRESHOLD_METRICS) {
|
|
6487
7225
|
if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
|
|
6488
7226
|
context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
|
|
@@ -6528,8 +7266,8 @@ var nextjsPackageJsonRules = {
|
|
|
6528
7266
|
};
|
|
6529
7267
|
|
|
6530
7268
|
// eslint/rules/husky/husky-hook.ts
|
|
6531
|
-
import { existsSync as
|
|
6532
|
-
import
|
|
7269
|
+
import { existsSync as existsSync6, readFileSync as readFileSync12 } from "fs";
|
|
7270
|
+
import path58 from "path";
|
|
6533
7271
|
var VITEST_CONFIG_NAMES2 = [
|
|
6534
7272
|
"vitest.config.ts",
|
|
6535
7273
|
"vitest.config.mts",
|
|
@@ -6555,15 +7293,15 @@ var huskyHookRule = {
|
|
|
6555
7293
|
const root = node.body;
|
|
6556
7294
|
if (root.type !== "Object") return;
|
|
6557
7295
|
if (!context.filename.endsWith("package.json")) return;
|
|
6558
|
-
const hookPath =
|
|
6559
|
-
if (!
|
|
7296
|
+
const hookPath = path58.join(context.cwd, ".husky", "pre-commit");
|
|
7297
|
+
if (!existsSync6(hookPath)) {
|
|
6560
7298
|
context.report({
|
|
6561
7299
|
node,
|
|
6562
7300
|
message: "No .husky/pre-commit hook found. Configure husky to run checks before commits."
|
|
6563
7301
|
});
|
|
6564
7302
|
return;
|
|
6565
7303
|
}
|
|
6566
|
-
const content =
|
|
7304
|
+
const content = readFileSync12(hookPath, "utf8");
|
|
6567
7305
|
const scripts = root.members.find((member) => memberName5(member) === "scripts");
|
|
6568
7306
|
const scriptNames = new Set(scripts?.value.type === "Object" ? scripts.value.members.map(memberName5) : []);
|
|
6569
7307
|
const requireNamedScript = (name) => {
|
|
@@ -6579,7 +7317,7 @@ var huskyHookRule = {
|
|
|
6579
7317
|
}
|
|
6580
7318
|
requireNamedScript("typecheck");
|
|
6581
7319
|
requireNamedScript("test:unit:coverage");
|
|
6582
|
-
const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) =>
|
|
7320
|
+
const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) => existsSync6(path58.join(context.cwd, name)));
|
|
6583
7321
|
if (vitestConfigName !== void 0) {
|
|
6584
7322
|
const coverageIndex = content.indexOf("npm run test:unit:coverage");
|
|
6585
7323
|
const localAddIndex = content.indexOf(`git add ${vitestConfigName}`);
|
|
@@ -6593,8 +7331,8 @@ var huskyHookRule = {
|
|
|
6593
7331
|
if (!content.includes("libyear --limit-major-individual=1")) {
|
|
6594
7332
|
context.report({ node, message: ".husky/pre-commit must run npx libyear --limit-major-individual=1." });
|
|
6595
7333
|
}
|
|
6596
|
-
const suppressionsPath =
|
|
6597
|
-
if (
|
|
7334
|
+
const suppressionsPath = path58.join(context.cwd, "eslint-suppressions.json");
|
|
7335
|
+
if (existsSync6(suppressionsPath)) {
|
|
6598
7336
|
requireNamedScript("lint:prune");
|
|
6599
7337
|
const pruneIndex = content.indexOf("npm run lint:prune");
|
|
6600
7338
|
const localAddIndex = content.indexOf("git add eslint-suppressions.json");
|
|
@@ -6624,73 +7362,33 @@ var huskyRules = {
|
|
|
6624
7362
|
"husky-hook": huskyHookRule
|
|
6625
7363
|
};
|
|
6626
7364
|
|
|
6627
|
-
// eslint/rules/vulyk/
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
}
|
|
6631
|
-
function dependency(root, sectionName) {
|
|
6632
|
-
if (root.type !== "Object") return void 0;
|
|
6633
|
-
const section = root.members.find((member) => memberName6(member) === sectionName);
|
|
6634
|
-
if (section?.value.type !== "Object") return void 0;
|
|
6635
|
-
return section.value.members.find((member) => memberName6(member) === "vulyk");
|
|
6636
|
-
}
|
|
6637
|
-
var vulykDependencyRule = {
|
|
6638
|
-
meta: {
|
|
6639
|
-
schema: [],
|
|
6640
|
-
type: "problem",
|
|
6641
|
-
docs: {
|
|
6642
|
-
description: "Require vulyk in devDependencies so its typed config and CLI use the pinned package."
|
|
6643
|
-
}
|
|
6644
|
-
},
|
|
6645
|
-
create(context) {
|
|
6646
|
-
return {
|
|
6647
|
-
Document(node) {
|
|
6648
|
-
const runtimeDependency = dependency(node.body, "dependencies");
|
|
6649
|
-
const developmentDependency = dependency(node.body, "devDependencies");
|
|
6650
|
-
if (runtimeDependency) {
|
|
6651
|
-
context.report({
|
|
6652
|
-
node: runtimeDependency,
|
|
6653
|
-
message: "vulyk must be listed in devDependencies, not dependencies."
|
|
6654
|
-
});
|
|
6655
|
-
}
|
|
6656
|
-
if (!developmentDependency && !runtimeDependency) {
|
|
6657
|
-
context.report({
|
|
6658
|
-
node,
|
|
6659
|
-
message: "vulyk must be listed in package.json as a devDependency."
|
|
6660
|
-
});
|
|
6661
|
-
}
|
|
6662
|
-
}
|
|
6663
|
-
};
|
|
6664
|
-
}
|
|
6665
|
-
};
|
|
6666
|
-
|
|
6667
|
-
// eslint/rules/vulyk/vulyk-docs.ts
|
|
6668
|
-
import { existsSync as existsSync6, readFileSync as readFileSync11 } from "fs";
|
|
6669
|
-
import path53 from "path";
|
|
7365
|
+
// eslint/rules/vulyk/tracked-docs.ts
|
|
7366
|
+
import { existsSync as existsSync7, readFileSync as readFileSync13 } from "fs";
|
|
7367
|
+
import path59 from "path";
|
|
6670
7368
|
var PASIKA_REPO = "Bredansky/pasika";
|
|
6671
|
-
var
|
|
7369
|
+
var BASE_REQUIRED_TRACKED_DOCS = [
|
|
6672
7370
|
{ name: "documentation-guide", path: "docs/documentation-guide" },
|
|
6673
7371
|
{ name: "pasika-adoption-guide", path: "docs/pasika-adoption-guide" },
|
|
6674
7372
|
{ name: "repository-policy", path: "docs/repository-policy.md" }
|
|
6675
7373
|
];
|
|
6676
|
-
var
|
|
7374
|
+
var NEXTJS_REQUIRED_TRACKED_DOCS = [
|
|
6677
7375
|
{ name: "next-codebase-guide", path: "docs/next-codebase-guide" },
|
|
6678
7376
|
{ name: "next-tailwind-guide", path: "docs/next-tailwind-guide" }
|
|
6679
7377
|
];
|
|
6680
|
-
function
|
|
7378
|
+
function memberName6(member) {
|
|
6681
7379
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
6682
7380
|
}
|
|
6683
7381
|
function hasDependency(root, name) {
|
|
6684
|
-
const section = root.members.find((member) =>
|
|
7382
|
+
const section = root.members.find((member) => memberName6(member) === "dependencies");
|
|
6685
7383
|
if (section?.value.type !== "Object") return false;
|
|
6686
|
-
return section.value.members.some((member) =>
|
|
7384
|
+
return section.value.members.some((member) => memberName6(member) === name);
|
|
6687
7385
|
}
|
|
6688
|
-
var
|
|
7386
|
+
var trackedDocsRule = {
|
|
6689
7387
|
meta: {
|
|
6690
7388
|
schema: [],
|
|
6691
7389
|
type: "problem",
|
|
6692
7390
|
docs: {
|
|
6693
|
-
description: "Require vulyk.config.ts to track the framework's required docs from pasika and the generated AGENTS.md."
|
|
7391
|
+
description: "Require vulyk.config.ts to track the framework's required tracked docs from pasika and the generated AGENTS.md."
|
|
6694
7392
|
}
|
|
6695
7393
|
},
|
|
6696
7394
|
create(context) {
|
|
@@ -6699,34 +7397,34 @@ var vulykDocsRule = {
|
|
|
6699
7397
|
if (!context.filename.endsWith("package.json")) return;
|
|
6700
7398
|
const root = node.body;
|
|
6701
7399
|
if (root.type !== "Object") return;
|
|
6702
|
-
const projectRoot =
|
|
6703
|
-
const configPath =
|
|
6704
|
-
if (!
|
|
7400
|
+
const projectRoot = path59.dirname(path59.resolve(context.filename));
|
|
7401
|
+
const configPath = path59.join(projectRoot, "vulyk.config.ts");
|
|
7402
|
+
if (!existsSync7(configPath)) {
|
|
6705
7403
|
context.report({
|
|
6706
7404
|
node,
|
|
6707
|
-
message: "No vulyk.config.ts found. Run npx vulyk init to create one that tracks the framework's docs."
|
|
7405
|
+
message: "No vulyk.config.ts found. Run npx vulyk init to create one that tracks the framework's required tracked docs."
|
|
6708
7406
|
});
|
|
6709
7407
|
return;
|
|
6710
7408
|
}
|
|
6711
|
-
const config =
|
|
7409
|
+
const config = readFileSync13(configPath, "utf8");
|
|
6712
7410
|
if (!config.includes(PASIKA_REPO)) {
|
|
6713
7411
|
context.report({
|
|
6714
7412
|
node,
|
|
6715
|
-
message: "vulyk.config.ts must track the framework's docs from the pasika repository."
|
|
7413
|
+
message: "vulyk.config.ts must track the framework's required tracked docs from the pasika repository."
|
|
6716
7414
|
});
|
|
6717
7415
|
return;
|
|
6718
7416
|
}
|
|
6719
|
-
const
|
|
6720
|
-
for (const
|
|
6721
|
-
if (!config.includes(
|
|
7417
|
+
const requiredTrackedDocs = hasDependency(root, "next") ? [...BASE_REQUIRED_TRACKED_DOCS, ...NEXTJS_REQUIRED_TRACKED_DOCS] : BASE_REQUIRED_TRACKED_DOCS;
|
|
7418
|
+
for (const trackedDoc of requiredTrackedDocs) {
|
|
7419
|
+
if (!config.includes(trackedDoc.path)) {
|
|
6722
7420
|
context.report({
|
|
6723
7421
|
node,
|
|
6724
|
-
message: `vulyk.config.ts must track the framework's ${
|
|
7422
|
+
message: `vulyk.config.ts must track the framework's ${trackedDoc.name} tracked docs from pasika (${PASIKA_REPO}/${trackedDoc.path}).`
|
|
6725
7423
|
});
|
|
6726
7424
|
}
|
|
6727
7425
|
}
|
|
6728
|
-
const agentsPath =
|
|
6729
|
-
if (!
|
|
7426
|
+
const agentsPath = path59.join(projectRoot, "AGENTS.md");
|
|
7427
|
+
if (!existsSync7(agentsPath)) {
|
|
6730
7428
|
context.report({
|
|
6731
7429
|
node,
|
|
6732
7430
|
message: "No AGENTS.md found. Run npx vulyk agents to generate the agent file that routes to the tracked docs."
|
|
@@ -6737,10 +7435,50 @@ var vulykDocsRule = {
|
|
|
6737
7435
|
}
|
|
6738
7436
|
};
|
|
6739
7437
|
|
|
7438
|
+
// eslint/rules/vulyk/vulyk-dependency.ts
|
|
7439
|
+
function memberName7(member) {
|
|
7440
|
+
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
7441
|
+
}
|
|
7442
|
+
function dependency(root, sectionName) {
|
|
7443
|
+
if (root.type !== "Object") return void 0;
|
|
7444
|
+
const section = root.members.find((member) => memberName7(member) === sectionName);
|
|
7445
|
+
if (section?.value.type !== "Object") return void 0;
|
|
7446
|
+
return section.value.members.find((member) => memberName7(member) === "vulyk");
|
|
7447
|
+
}
|
|
7448
|
+
var vulykDependencyRule = {
|
|
7449
|
+
meta: {
|
|
7450
|
+
schema: [],
|
|
7451
|
+
type: "problem",
|
|
7452
|
+
docs: {
|
|
7453
|
+
description: "Require vulyk in devDependencies so its typed config and CLI use the pinned package."
|
|
7454
|
+
}
|
|
7455
|
+
},
|
|
7456
|
+
create(context) {
|
|
7457
|
+
return {
|
|
7458
|
+
Document(node) {
|
|
7459
|
+
const runtimeDependency = dependency(node.body, "dependencies");
|
|
7460
|
+
const developmentDependency = dependency(node.body, "devDependencies");
|
|
7461
|
+
if (runtimeDependency) {
|
|
7462
|
+
context.report({
|
|
7463
|
+
node: runtimeDependency,
|
|
7464
|
+
message: "vulyk must be listed in devDependencies, not dependencies."
|
|
7465
|
+
});
|
|
7466
|
+
}
|
|
7467
|
+
if (!developmentDependency && !runtimeDependency) {
|
|
7468
|
+
context.report({
|
|
7469
|
+
node,
|
|
7470
|
+
message: "vulyk must be listed in package.json as a devDependency."
|
|
7471
|
+
});
|
|
7472
|
+
}
|
|
7473
|
+
}
|
|
7474
|
+
};
|
|
7475
|
+
}
|
|
7476
|
+
};
|
|
7477
|
+
|
|
6740
7478
|
// eslint/rules/vulyk/index.ts
|
|
6741
7479
|
var vulykRules = {
|
|
6742
7480
|
"vulyk-dependency": vulykDependencyRule,
|
|
6743
|
-
"
|
|
7481
|
+
"tracked-docs": trackedDocsRule
|
|
6744
7482
|
};
|
|
6745
7483
|
|
|
6746
7484
|
// eslint/index.ts
|
|
@@ -6782,6 +7520,10 @@ var pasikaNextjsAppRules = {
|
|
|
6782
7520
|
"cva-boolean-variants": cvaBooleanVariantsRule,
|
|
6783
7521
|
"cross-feature-import": crossFeatureImportRule,
|
|
6784
7522
|
"pure-function-extract": pureFunctionExtractRule,
|
|
7523
|
+
"root-support-placement": rootSupportPlacementRule,
|
|
7524
|
+
"route-handler-shape": routeHandlerShapeRule,
|
|
7525
|
+
"http-error-usage": httpErrorUsageRule,
|
|
7526
|
+
"with-response-helper": withResponseHelperRule,
|
|
6785
7527
|
"hook-complexity": hookComplexityRule,
|
|
6786
7528
|
"locale-dotted-path": localeDottedPathRule,
|
|
6787
7529
|
"locales-location": localesLocationRule,
|
|
@@ -6886,6 +7628,11 @@ var zirkaConfig = {
|
|
|
6886
7628
|
plugins: { pasika: pasikaPlugin },
|
|
6887
7629
|
rules: { "pasika/zirka-baseline": "error" }
|
|
6888
7630
|
};
|
|
7631
|
+
var nextjsHelperConfig = {
|
|
7632
|
+
files: ["eslint.config.{ts,mts,cts,js,mjs,cjs}"],
|
|
7633
|
+
plugins: { pasika: pasikaPlugin },
|
|
7634
|
+
rules: { "pasika/cn-helper": "error", "pasika/with-response-helper": "error" }
|
|
7635
|
+
};
|
|
6889
7636
|
var documentationConfig = {
|
|
6890
7637
|
files: ["docs/**/*.md"],
|
|
6891
7638
|
// vulyk-generated agent files are not authored docs: with per-directory
|
|
@@ -6915,6 +7662,7 @@ var pasikaNextjsAppWithDiagnostic = (preset) => {
|
|
|
6915
7662
|
var pasikaNextjsApp = pasikaNextjsAppWithDiagnostic([
|
|
6916
7663
|
...pasikaApp,
|
|
6917
7664
|
pasikaNextjsAppPackageJsonConfig,
|
|
7665
|
+
nextjsHelperConfig,
|
|
6918
7666
|
pasikaNextjsAppConfig,
|
|
6919
7667
|
tailwindStructureRules,
|
|
6920
7668
|
tailwindImportGraph
|