pasika 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/eslint/pasika/index.d.ts +8 -2
- package/dist/eslint/pasika/index.js +1168 -392
- 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);
|
|
@@ -1641,7 +1689,7 @@ function resolveSupportPlacement(supportFile, supportFolder, index) {
|
|
|
1641
1689
|
}
|
|
1642
1690
|
function describeConsumers(consumers, sourceRoot) {
|
|
1643
1691
|
const shown = 3;
|
|
1644
|
-
const names = consumers.map((consumer) =>
|
|
1692
|
+
const names = consumers.map((consumer) => path11.relative(path11.dirname(sourceRoot), consumer).split(path11.sep).join("/")).sort((left, right) => left.localeCompare(right));
|
|
1645
1693
|
if (names.length <= shown) return names.join(", ");
|
|
1646
1694
|
return `${names.slice(0, shown).join(", ")} and ${String(names.length - shown)} more`;
|
|
1647
1695
|
}
|
|
@@ -1658,7 +1706,7 @@ function isNestedInside(expectedFolder, currentFolder, componentFile) {
|
|
|
1658
1706
|
if (!sameFolder(currentFolder.slice(0, -1), expectedFolder)) return false;
|
|
1659
1707
|
const folderName = currentFolder[currentFolder.length - 1];
|
|
1660
1708
|
if (!folderName) return false;
|
|
1661
|
-
return fs2.existsSync(
|
|
1709
|
+
return fs2.existsSync(path12.join(path12.dirname(componentFile), `${folderName}.tsx`));
|
|
1662
1710
|
}
|
|
1663
1711
|
var componentPlacementRule = {
|
|
1664
1712
|
meta: {
|
|
@@ -1674,7 +1722,7 @@ var componentPlacementRule = {
|
|
|
1674
1722
|
const sourceRoot = sourceRootOf(context);
|
|
1675
1723
|
const index = getProjectIndex(sourceRoot);
|
|
1676
1724
|
if (!index) return {};
|
|
1677
|
-
const componentFile =
|
|
1725
|
+
const componentFile = path12.resolve(filename);
|
|
1678
1726
|
const segments = segmentsOf(componentFile, sourceRoot);
|
|
1679
1727
|
if (segments.length === 0) return {};
|
|
1680
1728
|
if (isUnderApp(segments) || isConfigModule(segments)) return {};
|
|
@@ -1708,7 +1756,7 @@ var componentPlacementRule = {
|
|
|
1708
1756
|
};
|
|
1709
1757
|
|
|
1710
1758
|
// eslint/rules/support-file-placement.ts
|
|
1711
|
-
import
|
|
1759
|
+
import path13 from "path";
|
|
1712
1760
|
var CONFIG_OWNED_FOLDERS = /* @__PURE__ */ new Set(["types", "constants"]);
|
|
1713
1761
|
var REASON_TEXT2 = {
|
|
1714
1762
|
"app-consumer": "a file under src/app/ imports it, so it belongs to the app-wide support folder",
|
|
@@ -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,24 @@ var crossFeatureImportRule = {
|
|
|
2902
2950
|
};
|
|
2903
2951
|
|
|
2904
2952
|
// eslint/rules/pure-function-extract.ts
|
|
2905
|
-
import
|
|
2953
|
+
import path25 from "path";
|
|
2954
|
+
var ROUTE_HANDLER_EXPORT_NAMES = /* @__PURE__ */ new Set([
|
|
2955
|
+
"GET",
|
|
2956
|
+
"POST",
|
|
2957
|
+
"PUT",
|
|
2958
|
+
"PATCH",
|
|
2959
|
+
"DELETE",
|
|
2960
|
+
"HEAD",
|
|
2961
|
+
"OPTIONS",
|
|
2962
|
+
"dynamic",
|
|
2963
|
+
"dynamicParams",
|
|
2964
|
+
"revalidate",
|
|
2965
|
+
"fetchCache",
|
|
2966
|
+
"runtime",
|
|
2967
|
+
"preferredRegion",
|
|
2968
|
+
"maxDuration",
|
|
2969
|
+
"generateStaticParams"
|
|
2970
|
+
]);
|
|
2906
2971
|
function isComponentLikeName(name) {
|
|
2907
2972
|
return /^[A-Z]/.test(name);
|
|
2908
2973
|
}
|
|
@@ -2929,13 +2994,14 @@ var pureFunctionExtractRule = {
|
|
|
2929
2994
|
},
|
|
2930
2995
|
create(context) {
|
|
2931
2996
|
const filename = context.filename;
|
|
2932
|
-
|
|
2997
|
+
const isRouteFile = path25.basename(filename) === "route.ts";
|
|
2998
|
+
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx") && !isRouteFile) return {};
|
|
2933
2999
|
const sourceRoot = sourceRootOf(context);
|
|
2934
|
-
const relative =
|
|
3000
|
+
const relative = path25.relative(sourceRoot, filename);
|
|
2935
3001
|
if (relative.startsWith("..")) return {};
|
|
2936
|
-
const segments = relative.split(
|
|
3002
|
+
const segments = relative.split(path25.sep);
|
|
2937
3003
|
if (segments[0] === "utils") return {};
|
|
2938
|
-
if (segments[0] === "app") return {};
|
|
3004
|
+
if (segments[0] === "app" && !isRouteFile) return {};
|
|
2939
3005
|
const supportFolders = /* @__PURE__ */ new Set(["hooks", "types", "schemas", "constants", "utils"]);
|
|
2940
3006
|
if (segments.length >= 2 && supportFolders.has(segments[segments.length - 1] ?? "")) return {};
|
|
2941
3007
|
function report3(node, name) {
|
|
@@ -2946,35 +3012,544 @@ var pureFunctionExtractRule = {
|
|
|
2946
3012
|
}
|
|
2947
3013
|
return {
|
|
2948
3014
|
FunctionDeclaration(node) {
|
|
2949
|
-
const exported = node.parent?.type === "ExportNamedDeclaration";
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
if (!
|
|
2953
|
-
|
|
2954
|
-
if (!
|
|
2955
|
-
|
|
3015
|
+
const exported = node.parent?.type === "ExportNamedDeclaration";
|
|
3016
|
+
const moduleLevel = exported || node.parent?.type === "Program";
|
|
3017
|
+
if (!moduleLevel) return;
|
|
3018
|
+
if (!isRouteFile && !exported) return;
|
|
3019
|
+
const name = node.id?.name;
|
|
3020
|
+
if (!name) return;
|
|
3021
|
+
if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
|
|
3022
|
+
if (isComponentLikeName(name) || isHookName2(name)) return;
|
|
3023
|
+
if (!node.body || hasHookUsage(node.body)) return;
|
|
3024
|
+
report3(node, name);
|
|
3025
|
+
},
|
|
3026
|
+
VariableDeclarator(node) {
|
|
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 real = consumers.filter((consumer) => {
|
|
3103
|
+
const consumerSegments = segmentsOf(consumer, sourceRoot);
|
|
3104
|
+
return !isUnderApp(consumerSegments) && !isConfigModule(consumerSegments);
|
|
3105
|
+
});
|
|
3106
|
+
if (real.length > 0) continue;
|
|
3107
|
+
const where = isRoot ? `root src/${supportFolder}/` : `src/${folderSegments.join("/")}/`;
|
|
3108
|
+
findings.push({
|
|
3109
|
+
line: exp.line,
|
|
3110
|
+
message: `${label} "${exp.name}" has no consumer outside src/app/ or a configuration module, 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}`
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
if (findings.length === 0) return {};
|
|
3114
|
+
return {
|
|
3115
|
+
Program(node) {
|
|
3116
|
+
for (const finding of findings) {
|
|
3117
|
+
context.report({ node, loc: { line: finding.line, column: 0 }, message: finding.message });
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
3123
|
+
|
|
3124
|
+
// eslint/rules/route-handler-shape.ts
|
|
3125
|
+
import path27 from "path";
|
|
3126
|
+
import ts4 from "typescript";
|
|
3127
|
+
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
3128
|
+
var REQUIRED_BOUNDARY = "withResponse";
|
|
3129
|
+
function isFunctionLike(node) {
|
|
3130
|
+
return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
|
|
3131
|
+
}
|
|
3132
|
+
function findHandler(node) {
|
|
3133
|
+
if (isFunctionLike(node)) return node;
|
|
3134
|
+
if (node.type === "CallExpression") {
|
|
3135
|
+
for (const arg of node.arguments) {
|
|
3136
|
+
if (arg.type === "SpreadElement") continue;
|
|
3137
|
+
const found = findHandler(arg);
|
|
3138
|
+
if (found) return found;
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
return void 0;
|
|
3142
|
+
}
|
|
3143
|
+
function isLoop(node) {
|
|
3144
|
+
return ts4.isForStatement(node) || ts4.isForOfStatement(node) || ts4.isForInStatement(node) || ts4.isWhileStatement(node) || ts4.isDoStatement(node);
|
|
3145
|
+
}
|
|
3146
|
+
function isFunctionBoundary(node) {
|
|
3147
|
+
return ts4.isFunctionExpression(node) || ts4.isArrowFunction(node) || ts4.isFunctionDeclaration(node);
|
|
3148
|
+
}
|
|
3149
|
+
function analyzeHandlerBody(body, sourceText) {
|
|
3150
|
+
const start = body.range?.[0] ?? 0;
|
|
3151
|
+
const end = body.range?.[1] ?? sourceText.length;
|
|
3152
|
+
const sourceFile = ts4.createSourceFile(
|
|
3153
|
+
"route.ts",
|
|
3154
|
+
sourceText.slice(start, end),
|
|
3155
|
+
ts4.ScriptTarget.Latest,
|
|
3156
|
+
true,
|
|
3157
|
+
ts4.ScriptKind.TS
|
|
3158
|
+
);
|
|
3159
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
3160
|
+
const calledNames = /* @__PURE__ */ new Set();
|
|
3161
|
+
const visit = (node) => {
|
|
3162
|
+
if (ts4.isTryStatement(node)) kinds.add("try");
|
|
3163
|
+
if (isLoop(node)) kinds.add("loop");
|
|
3164
|
+
if (ts4.isIfStatement(node)) kinds.add("if");
|
|
3165
|
+
if (ts4.isCallExpression(node) && ts4.isIdentifier(node.expression)) calledNames.add(node.expression.text);
|
|
3166
|
+
if (isFunctionBoundary(node)) return;
|
|
3167
|
+
ts4.forEachChild(node, visit);
|
|
3168
|
+
};
|
|
3169
|
+
visit(sourceFile);
|
|
3170
|
+
return { kinds, calledNames };
|
|
3171
|
+
}
|
|
3172
|
+
var CONTROL_FLOW_MESSAGES = {
|
|
3173
|
+
try: "a try statement",
|
|
3174
|
+
loop: "a loop",
|
|
3175
|
+
if: "an if statement"
|
|
3176
|
+
};
|
|
3177
|
+
function isFunctionLikeInit(node) {
|
|
3178
|
+
return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
|
|
3179
|
+
}
|
|
3180
|
+
function collectLocallyDeclaredNames(programBody) {
|
|
3181
|
+
const names = /* @__PURE__ */ new Set();
|
|
3182
|
+
for (const statement of programBody) {
|
|
3183
|
+
const declaration = statement.type === "ExportNamedDeclaration" && statement.declaration ? statement.declaration : statement;
|
|
3184
|
+
if (declaration.type === "FunctionDeclaration") {
|
|
3185
|
+
names.add(declaration.id.name);
|
|
3186
|
+
}
|
|
3187
|
+
if (declaration.type === "VariableDeclaration") {
|
|
3188
|
+
for (const declarator of declaration.declarations) {
|
|
3189
|
+
if (declarator.id.type === "Identifier" && isFunctionLikeInit(declarator.init)) {
|
|
3190
|
+
names.add(declarator.id.name);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
return names;
|
|
3196
|
+
}
|
|
3197
|
+
var routeHandlerShapeRule = {
|
|
3198
|
+
meta: {
|
|
3199
|
+
schema: [],
|
|
3200
|
+
type: "problem",
|
|
3201
|
+
docs: {
|
|
3202
|
+
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."
|
|
3203
|
+
}
|
|
3204
|
+
},
|
|
3205
|
+
create(context) {
|
|
3206
|
+
if (path27.basename(context.filename) !== "route.ts") return {};
|
|
3207
|
+
const sourceText = context.sourceCode.text;
|
|
3208
|
+
function reportUnwrapped(node, name) {
|
|
3209
|
+
context.report({
|
|
3210
|
+
node,
|
|
3211
|
+
message: `Handler "${name}" must be wrapped in withResponse. See docs/next-codebase-guide/rules/route-handler-rule.md`
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
function checkExport(node, name, init) {
|
|
3215
|
+
if (init.type === "Identifier") return;
|
|
3216
|
+
const boundary = init.type === "CallExpression" && init.callee.type === "Identifier" ? init.callee.name : void 0;
|
|
3217
|
+
if (boundary !== REQUIRED_BOUNDARY) {
|
|
3218
|
+
reportUnwrapped(node, name);
|
|
3219
|
+
return;
|
|
3220
|
+
}
|
|
3221
|
+
const handler = findHandler(init);
|
|
3222
|
+
if (!handler?.body) return;
|
|
3223
|
+
const { kinds, calledNames } = analyzeHandlerBody(handler.body, sourceText);
|
|
3224
|
+
for (const kind of kinds) {
|
|
3225
|
+
context.report({
|
|
3226
|
+
node,
|
|
3227
|
+
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`
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
const localNames = collectLocallyDeclaredNames(context.sourceCode.ast.body);
|
|
3231
|
+
for (const calledName of calledNames) {
|
|
3232
|
+
if (localNames.has(calledName)) {
|
|
3233
|
+
context.report({
|
|
3234
|
+
node,
|
|
3235
|
+
message: `Handler "${name}" calls "${calledName}", which is declared in route.ts instead of imported. See docs/next-codebase-guide/rules/route-handler-rule.md`
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
return {
|
|
3241
|
+
FunctionDeclaration(node) {
|
|
3242
|
+
const exported = node.parent?.type === "ExportNamedDeclaration";
|
|
3243
|
+
const name = node.id?.name;
|
|
3244
|
+
if (!exported || !name || !HTTP_METHODS.has(name) || !node.body) return;
|
|
3245
|
+
reportUnwrapped(node, name);
|
|
3246
|
+
},
|
|
3247
|
+
VariableDeclarator(node) {
|
|
3248
|
+
if (node.id.type !== "Identifier") return;
|
|
3249
|
+
const name = node.id.name;
|
|
3250
|
+
const exported = node.parent.parent?.type === "ExportNamedDeclaration";
|
|
3251
|
+
if (!exported || !HTTP_METHODS.has(name) || !node.init) return;
|
|
3252
|
+
checkExport(node, name, node.init);
|
|
3253
|
+
}
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
};
|
|
3257
|
+
|
|
3258
|
+
// eslint/rules/http-error-usage.ts
|
|
3259
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3260
|
+
import path28 from "path";
|
|
3261
|
+
import ts5 from "typescript";
|
|
3262
|
+
var HTTP_ERROR = "HttpError";
|
|
3263
|
+
var ROUTE_FILE = "route.ts";
|
|
3264
|
+
var DOC2 = "docs/next-codebase-guide/rules/route-handler-rule.md";
|
|
3265
|
+
var HERITAGE_DEPTH = 5;
|
|
3266
|
+
var HANDS_ERROR_BACK = `A delegated module must throw the HttpError it reports a failure with, not return it. See ${DOC2}`;
|
|
3267
|
+
var delegatedModuleMessage = (name) => `A delegated module must report a failure by throwing an HttpError, not "${name}". See ${DOC2}`;
|
|
3268
|
+
function constructedErrorName(node) {
|
|
3269
|
+
if (node === void 0) return void 0;
|
|
3270
|
+
if (node.type === "NewExpression") {
|
|
3271
|
+
return node.callee.type === "Identifier" ? node.callee.name : void 0;
|
|
3272
|
+
}
|
|
3273
|
+
if (node.type === "AwaitExpression") return constructedErrorName(node.argument);
|
|
3274
|
+
if (node.type === "ConditionalExpression") {
|
|
3275
|
+
return constructedErrorName(node.consequent) ?? constructedErrorName(node.alternate);
|
|
3276
|
+
}
|
|
3277
|
+
if (node.type === "LogicalExpression") {
|
|
3278
|
+
return constructedErrorName(node.left) ?? constructedErrorName(node.right);
|
|
3279
|
+
}
|
|
3280
|
+
if (node.type !== "CallExpression") return void 0;
|
|
3281
|
+
for (const argument of node.arguments) {
|
|
3282
|
+
const found = constructedErrorName(argument);
|
|
3283
|
+
if (found) return found;
|
|
3284
|
+
}
|
|
3285
|
+
return void 0;
|
|
3286
|
+
}
|
|
3287
|
+
function parentClassOf(file, name) {
|
|
3288
|
+
let text;
|
|
3289
|
+
try {
|
|
3290
|
+
text = readFileSync4(file, "utf8");
|
|
3291
|
+
} catch {
|
|
3292
|
+
return void 0;
|
|
3293
|
+
}
|
|
3294
|
+
const sourceFile = ts5.createSourceFile(file, text, ts5.ScriptTarget.Latest, true, ts5.ScriptKind.TSX);
|
|
3295
|
+
for (const statement of sourceFile.statements) {
|
|
3296
|
+
if (!ts5.isClassDeclaration(statement) || statement.name?.text !== name) continue;
|
|
3297
|
+
const heritage = statement.heritageClauses?.find((clause) => clause.token === ts5.SyntaxKind.ExtendsKeyword);
|
|
3298
|
+
const parent = heritage?.types[0]?.expression;
|
|
3299
|
+
return parent && ts5.isIdentifier(parent) ? parent.text : void 0;
|
|
3300
|
+
}
|
|
3301
|
+
return void 0;
|
|
3302
|
+
}
|
|
3303
|
+
function importedFrom(file, name, sourceRoot, index) {
|
|
3304
|
+
const module = index.modules.get(path28.resolve(file));
|
|
3305
|
+
const moduleImport = module?.imports.find((entry) => entry.names.includes(name));
|
|
3306
|
+
if (!moduleImport) return void 0;
|
|
3307
|
+
return resolveSpecifier(file, moduleImport.specifier, sourceRoot);
|
|
3308
|
+
}
|
|
3309
|
+
function isHttpErrorClass(file, name, sourceRoot, index) {
|
|
3310
|
+
let currentFile = path28.resolve(file);
|
|
3311
|
+
let currentName = name;
|
|
3312
|
+
for (let depth = 0; depth <= HERITAGE_DEPTH; depth += 1) {
|
|
3313
|
+
if (currentName === HTTP_ERROR) return true;
|
|
3314
|
+
const parent = parentClassOf(currentFile, currentName);
|
|
3315
|
+
if (parent) {
|
|
3316
|
+
currentName = parent;
|
|
3317
|
+
continue;
|
|
3318
|
+
}
|
|
3319
|
+
const imported = importedFrom(currentFile, currentName, sourceRoot, index);
|
|
3320
|
+
if (!imported) return false;
|
|
3321
|
+
currentFile = imported;
|
|
3322
|
+
}
|
|
3323
|
+
return false;
|
|
3324
|
+
}
|
|
3325
|
+
var pipelineCache;
|
|
3326
|
+
function pipelineFiles(sourceRoot) {
|
|
3327
|
+
const index = getProjectIndex(sourceRoot);
|
|
3328
|
+
if (!index) return void 0;
|
|
3329
|
+
if (pipelineCache?.index === index) return pipelineCache.files;
|
|
3330
|
+
const files = /* @__PURE__ */ new Set();
|
|
3331
|
+
const queue = [...index.modules.keys()].filter((file) => path28.basename(file) === ROUTE_FILE);
|
|
3332
|
+
while (queue.length > 0) {
|
|
3333
|
+
const file = queue.pop();
|
|
3334
|
+
if (file === void 0 || files.has(file)) continue;
|
|
3335
|
+
files.add(file);
|
|
3336
|
+
for (const moduleImport of index.modules.get(file)?.imports ?? []) {
|
|
3337
|
+
const target = resolveSpecifier(file, moduleImport.specifier, index.sourceRoot);
|
|
3338
|
+
if (target && index.modules.has(target) && !files.has(target)) queue.push(target);
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
pipelineCache = { index, files };
|
|
3342
|
+
return files;
|
|
3343
|
+
}
|
|
3344
|
+
var httpErrorUsageRule = {
|
|
3345
|
+
meta: {
|
|
3346
|
+
schema: [],
|
|
3347
|
+
type: "problem",
|
|
3348
|
+
docs: {
|
|
3349
|
+
description: "Require a delegated module to report a failure by throwing an HttpError, never another error type and never a returned value."
|
|
3350
|
+
}
|
|
3351
|
+
},
|
|
3352
|
+
create(context) {
|
|
3353
|
+
const sourceRoot = sourceRootOf(context);
|
|
3354
|
+
const file = path28.resolve(context.filename);
|
|
3355
|
+
let delegated;
|
|
3356
|
+
function inPipeline() {
|
|
3357
|
+
delegated ??= pipelineFiles(sourceRoot)?.has(file) ?? false;
|
|
3358
|
+
return delegated;
|
|
3359
|
+
}
|
|
3360
|
+
return {
|
|
3361
|
+
ReturnStatement(node) {
|
|
3362
|
+
if (node.argument === null) return;
|
|
3363
|
+
if (!inPipeline()) return;
|
|
3364
|
+
if (constructedErrorName(node.argument) !== HTTP_ERROR) return;
|
|
3365
|
+
context.report({ node, message: HANDS_ERROR_BACK });
|
|
3366
|
+
},
|
|
3367
|
+
ArrowFunctionExpression(node) {
|
|
3368
|
+
if (node.body.type === "BlockStatement") return;
|
|
3369
|
+
if (!inPipeline()) return;
|
|
3370
|
+
if (constructedErrorName(node.body) !== HTTP_ERROR) return;
|
|
3371
|
+
context.report({ node, message: HANDS_ERROR_BACK });
|
|
3372
|
+
},
|
|
3373
|
+
ThrowStatement(node) {
|
|
3374
|
+
const thrown = node.argument;
|
|
3375
|
+
if (thrown.type !== "NewExpression" || thrown.callee.type !== "Identifier") return;
|
|
3376
|
+
const name = thrown.callee.name;
|
|
3377
|
+
if (name === HTTP_ERROR) return;
|
|
3378
|
+
const index = getProjectIndex(sourceRoot);
|
|
3379
|
+
if (!index || !inPipeline()) return;
|
|
3380
|
+
if (isHttpErrorClass(file, name, sourceRoot, index)) return;
|
|
3381
|
+
context.report({ node, message: delegatedModuleMessage(name) });
|
|
3382
|
+
}
|
|
3383
|
+
};
|
|
3384
|
+
}
|
|
3385
|
+
};
|
|
3386
|
+
|
|
3387
|
+
// eslint/rules/with-response-helper.ts
|
|
3388
|
+
import path29 from "path";
|
|
3389
|
+
import ts6 from "typescript";
|
|
3390
|
+
var HELPER2 = "withResponse";
|
|
3391
|
+
var DOC3 = "docs/pasika-adoption-guide/rules/with-response-helper-rule.md";
|
|
3392
|
+
var ESLINT_CONFIG2 = /^eslint\.config\.(?:cjs|cts|js|mjs|mts|ts)$/;
|
|
3393
|
+
var BEAT_KEYS = [
|
|
3394
|
+
"awaitsHandler",
|
|
3395
|
+
"validatesWithSchema",
|
|
3396
|
+
"checksHttpError",
|
|
3397
|
+
"answersWithNullData",
|
|
3398
|
+
"usesErrorStatus",
|
|
3399
|
+
"rethrows"
|
|
3400
|
+
];
|
|
3401
|
+
var BEAT_MESSAGES = {
|
|
3402
|
+
awaitsHandler: "must await the handler",
|
|
3403
|
+
validatesWithSchema: "must validate the handler's returned data through the response schema",
|
|
3404
|
+
checksHttpError: "must check the caught error with instanceof HttpError",
|
|
3405
|
+
answersWithNullData: "must answer a thrown HttpError with { data: null, message }",
|
|
3406
|
+
usesErrorStatus: "must answer at the caught error's status",
|
|
3407
|
+
rethrows: "must rethrow a caught error that is not an HttpError"
|
|
3408
|
+
};
|
|
3409
|
+
function isNamed2(node, name) {
|
|
3410
|
+
return node?.type === "Identifier" && node.name === name;
|
|
3411
|
+
}
|
|
3412
|
+
function sourceRootFor2(context) {
|
|
3413
|
+
return path29.join(path29.dirname(path29.resolve(context.filename)), "src");
|
|
3414
|
+
}
|
|
3415
|
+
function definesHelper2(context, name) {
|
|
3416
|
+
const index = getProjectIndex(sourceRootFor2(context));
|
|
3417
|
+
if (!index) return true;
|
|
3418
|
+
for (const parsed of index.modules.values()) {
|
|
3419
|
+
if (parsed.exports.some((exported) => exported.name === name)) return true;
|
|
3420
|
+
}
|
|
3421
|
+
return false;
|
|
3422
|
+
}
|
|
3423
|
+
function contains(node, check) {
|
|
3424
|
+
let found = false;
|
|
3425
|
+
const visit = (current) => {
|
|
3426
|
+
if (found) return;
|
|
3427
|
+
if (check(current)) {
|
|
3428
|
+
found = true;
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
ts6.forEachChild(current, visit);
|
|
3432
|
+
};
|
|
3433
|
+
visit(node);
|
|
3434
|
+
return found;
|
|
3435
|
+
}
|
|
3436
|
+
function findTryStatement(node) {
|
|
3437
|
+
let found;
|
|
3438
|
+
const visit = (current) => {
|
|
3439
|
+
if (found) return;
|
|
3440
|
+
if (ts6.isTryStatement(current)) {
|
|
3441
|
+
found = current;
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
ts6.forEachChild(current, visit);
|
|
3445
|
+
};
|
|
3446
|
+
visit(node);
|
|
3447
|
+
return found;
|
|
3448
|
+
}
|
|
3449
|
+
function awaitsHandlerCall(handlerName) {
|
|
3450
|
+
return (node) => ts6.isAwaitExpression(node) && ts6.isCallExpression(node.expression) && ts6.isIdentifier(node.expression.expression) && node.expression.expression.text === handlerName;
|
|
3451
|
+
}
|
|
3452
|
+
function parsesThroughSchema(schemaName) {
|
|
3453
|
+
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");
|
|
3454
|
+
}
|
|
3455
|
+
function checksHttpError(node) {
|
|
3456
|
+
return ts6.isBinaryExpression(node) && node.operatorToken.kind === ts6.SyntaxKind.InstanceOfKeyword && ts6.isIdentifier(node.right) && node.right.text === "HttpError";
|
|
3457
|
+
}
|
|
3458
|
+
function answersWithNullData(node) {
|
|
3459
|
+
if (!ts6.isCallExpression(node) || !ts6.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "json") {
|
|
3460
|
+
return false;
|
|
3461
|
+
}
|
|
3462
|
+
return node.arguments.some((argument) => {
|
|
3463
|
+
if (!ts6.isObjectLiteralExpression(argument)) return false;
|
|
3464
|
+
return argument.properties.some((property) => {
|
|
3465
|
+
if (!ts6.isPropertyAssignment(property)) return false;
|
|
3466
|
+
const name = ts6.isIdentifier(property.name) ? property.name.text : void 0;
|
|
3467
|
+
return name === "data" && property.initializer.kind === ts6.SyntaxKind.NullKeyword;
|
|
3468
|
+
});
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
3471
|
+
function readsErrorStatus(errorName) {
|
|
3472
|
+
return (node) => ts6.isPropertyAccessExpression(node) && node.name.text === "status" && ts6.isIdentifier(node.expression) && node.expression.text === errorName;
|
|
3473
|
+
}
|
|
3474
|
+
function collectBeats(text, schemaName, handlerName) {
|
|
3475
|
+
const beats = {
|
|
3476
|
+
awaitsHandler: false,
|
|
3477
|
+
validatesWithSchema: false,
|
|
3478
|
+
checksHttpError: false,
|
|
3479
|
+
answersWithNullData: false,
|
|
3480
|
+
usesErrorStatus: false,
|
|
3481
|
+
rethrows: false
|
|
3482
|
+
};
|
|
3483
|
+
const sourceFile = ts6.createSourceFile("with-response.ts", text, ts6.ScriptTarget.Latest, true, ts6.ScriptKind.TS);
|
|
3484
|
+
const tryStatement = findTryStatement(sourceFile);
|
|
3485
|
+
if (!tryStatement) return beats;
|
|
3486
|
+
beats.awaitsHandler = contains(tryStatement.tryBlock, awaitsHandlerCall(handlerName));
|
|
3487
|
+
beats.validatesWithSchema = contains(tryStatement.tryBlock, parsesThroughSchema(schemaName));
|
|
3488
|
+
const catchClause = tryStatement.catchClause;
|
|
3489
|
+
if (!catchClause) return beats;
|
|
3490
|
+
const errorName = catchClause.variableDeclaration?.name;
|
|
3491
|
+
const caughtName = errorName && ts6.isIdentifier(errorName) ? errorName.text : void 0;
|
|
3492
|
+
beats.checksHttpError = contains(catchClause.block, checksHttpError);
|
|
3493
|
+
beats.answersWithNullData = contains(catchClause.block, answersWithNullData);
|
|
3494
|
+
beats.usesErrorStatus = contains(catchClause.block, readsErrorStatus(caughtName));
|
|
3495
|
+
beats.rethrows = contains(catchClause.block, ts6.isThrowStatement);
|
|
3496
|
+
return beats;
|
|
3497
|
+
}
|
|
3498
|
+
function parameterNames(node) {
|
|
3499
|
+
if (node.type !== "FunctionDeclaration" && node.type !== "FunctionExpression" && node.type !== "ArrowFunctionExpression") {
|
|
3500
|
+
return [];
|
|
3501
|
+
}
|
|
3502
|
+
return node.params.map((parameter) => parameter.type === "Identifier" ? parameter.name : void 0);
|
|
3503
|
+
}
|
|
3504
|
+
var withResponseHelperRule = {
|
|
3505
|
+
meta: {
|
|
3506
|
+
schema: [],
|
|
3507
|
+
type: "problem",
|
|
3508
|
+
docs: {
|
|
3509
|
+
description: `Require a repository to define a ${HELPER2} helper that validates the handler's data and maps a thrown HttpError to a response.`
|
|
3510
|
+
}
|
|
3511
|
+
},
|
|
3512
|
+
create(context) {
|
|
3513
|
+
if (ESLINT_CONFIG2.test(path29.basename(context.filename))) {
|
|
3514
|
+
return {
|
|
3515
|
+
Program() {
|
|
3516
|
+
if (definesHelper2(context, HELPER2)) return;
|
|
3517
|
+
context.report({
|
|
3518
|
+
node: context.sourceCode.ast,
|
|
3519
|
+
loc: { line: 1, column: 0 },
|
|
3520
|
+
message: `A repository must define a ${HELPER2} helper. See ${DOC3}`
|
|
3521
|
+
});
|
|
3522
|
+
}
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
const check = (node, parameters) => {
|
|
3526
|
+
const [start, end] = node.range ?? [0, context.sourceCode.text.length];
|
|
3527
|
+
const beats = collectBeats(context.sourceCode.text.slice(start, end), parameters[0], parameters[1]);
|
|
3528
|
+
for (const beat of BEAT_KEYS) {
|
|
3529
|
+
if (beats[beat]) continue;
|
|
3530
|
+
context.report({
|
|
3531
|
+
node,
|
|
3532
|
+
message: `${HELPER2} ${BEAT_MESSAGES[beat]}. See ${DOC3}`
|
|
3533
|
+
});
|
|
3534
|
+
}
|
|
3535
|
+
};
|
|
3536
|
+
return {
|
|
3537
|
+
FunctionDeclaration(node) {
|
|
3538
|
+
if (isNamed2(node.id, HELPER2)) check(node, parameterNames(node));
|
|
2956
3539
|
},
|
|
2957
3540
|
VariableDeclarator(node) {
|
|
2958
|
-
if (node.id.type
|
|
2959
|
-
const name = node.id.name;
|
|
2960
|
-
if (!name) return;
|
|
2961
|
-
const exported = node.parent.parent?.type === "ExportNamedDeclaration";
|
|
2962
|
-
if (!exported) return;
|
|
2963
|
-
if (isComponentLikeName(name) || isHookName2(name)) return;
|
|
3541
|
+
if (!isNamed2(node.id.type === "Identifier" ? node.id : null, HELPER2)) return;
|
|
2964
3542
|
const init = node.init;
|
|
2965
|
-
if (
|
|
2966
|
-
|
|
2967
|
-
}
|
|
2968
|
-
if (init.body.type === "BlockStatement" && hasHookUsage(init.body)) return;
|
|
2969
|
-
report3(node, name);
|
|
3543
|
+
if (init?.type !== "ArrowFunctionExpression" && init?.type !== "FunctionExpression") return;
|
|
3544
|
+
check(init, parameterNames(init));
|
|
2970
3545
|
}
|
|
2971
3546
|
};
|
|
2972
3547
|
}
|
|
2973
3548
|
};
|
|
2974
3549
|
|
|
2975
3550
|
// eslint/rules/hook-complexity.ts
|
|
2976
|
-
import
|
|
2977
|
-
import
|
|
3551
|
+
import path30 from "path";
|
|
3552
|
+
import ts7 from "typescript";
|
|
2978
3553
|
var REACT_HOOKS = /* @__PURE__ */ new Set([
|
|
2979
3554
|
"useState",
|
|
2980
3555
|
"useEffect",
|
|
@@ -2992,28 +3567,67 @@ var REACT_HOOKS = /* @__PURE__ */ new Set([
|
|
|
2992
3567
|
"useSyncExternalStore",
|
|
2993
3568
|
"useInsertionEffect"
|
|
2994
3569
|
]);
|
|
3570
|
+
var SUBSCRIPTION_METHODS = /* @__PURE__ */ new Set(["on", "off", "addEventListener", "removeEventListener"]);
|
|
3571
|
+
var STORAGE_OBJECTS = /* @__PURE__ */ new Set(["localStorage", "sessionStorage", "indexedDB"]);
|
|
3572
|
+
var DOM_METHODS = /* @__PURE__ */ new Set(["focus", "blur", "scrollIntoView", "click"]);
|
|
3573
|
+
var DOM_PROPERTIES = /* @__PURE__ */ new Set(["classList"]);
|
|
3574
|
+
var DOM_CONSTRUCTORS = /* @__PURE__ */ new Set(["MutationObserver", "ResizeObserver", "IntersectionObserver"]);
|
|
3575
|
+
var LIFECYCLE_METHODS = /* @__PURE__ */ new Set(["load", "destroy", "dispose", "close", "cleanup", "unmount"]);
|
|
2995
3576
|
function isHookName3(name) {
|
|
2996
3577
|
return /^use[A-Z]/.test(name);
|
|
2997
3578
|
}
|
|
2998
|
-
function
|
|
3579
|
+
function calledMethodName(node) {
|
|
3580
|
+
return ts7.isPropertyAccessExpression(node.expression) ? node.expression.name.text : void 0;
|
|
3581
|
+
}
|
|
3582
|
+
function calledOnObjectName(node) {
|
|
3583
|
+
if (!ts7.isPropertyAccessExpression(node.expression)) return void 0;
|
|
3584
|
+
const object = node.expression.expression;
|
|
3585
|
+
return ts7.isIdentifier(object) ? object.text : void 0;
|
|
3586
|
+
}
|
|
3587
|
+
function calledHookName(node) {
|
|
3588
|
+
return ts7.isCallExpression(node) && ts7.isIdentifier(node.expression) && REACT_HOOKS.has(node.expression.text) ? node.expression.text : void 0;
|
|
3589
|
+
}
|
|
3590
|
+
function sideEffectCategoryOf(node) {
|
|
3591
|
+
if (ts7.isAwaitExpression(node)) return "externalIO";
|
|
3592
|
+
if (ts7.isNewExpression(node) && ts7.isIdentifier(node.expression) && DOM_CONSTRUCTORS.has(node.expression.text)) {
|
|
3593
|
+
return "domManipulation";
|
|
3594
|
+
}
|
|
3595
|
+
if (ts7.isPropertyAccessExpression(node) && DOM_PROPERTIES.has(node.name.text)) {
|
|
3596
|
+
return "domManipulation";
|
|
3597
|
+
}
|
|
3598
|
+
if (ts7.isCallExpression(node)) {
|
|
3599
|
+
if (ts7.isIdentifier(node.expression) && node.expression.text === "fetch") return "externalIO";
|
|
3600
|
+
const method = calledMethodName(node);
|
|
3601
|
+
if (method && SUBSCRIPTION_METHODS.has(method)) return "subscription";
|
|
3602
|
+
if (method && LIFECYCLE_METHODS.has(method)) return "lifecycle";
|
|
3603
|
+
if (method && DOM_METHODS.has(method)) return "domManipulation";
|
|
3604
|
+
const object = calledOnObjectName(node);
|
|
3605
|
+
if (object && STORAGE_OBJECTS.has(object)) return "externalIO";
|
|
3606
|
+
}
|
|
3607
|
+
return void 0;
|
|
3608
|
+
}
|
|
3609
|
+
function computeExtractionScore(body, sourceText) {
|
|
2999
3610
|
const start = body.range?.[0] ?? 0;
|
|
3000
3611
|
const end = body.range?.[1] ?? sourceText.length;
|
|
3001
|
-
const sourceFile =
|
|
3612
|
+
const sourceFile = ts7.createSourceFile(
|
|
3002
3613
|
"hook.ts",
|
|
3003
3614
|
sourceText.slice(start, end),
|
|
3004
|
-
|
|
3615
|
+
ts7.ScriptTarget.Latest,
|
|
3005
3616
|
true,
|
|
3006
|
-
|
|
3617
|
+
ts7.ScriptKind.TS
|
|
3007
3618
|
);
|
|
3008
|
-
const
|
|
3619
|
+
const hookNames = /* @__PURE__ */ new Set();
|
|
3620
|
+
const sideEffectCategories = /* @__PURE__ */ new Set();
|
|
3009
3621
|
const visit = (node) => {
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3622
|
+
const hookName = calledHookName(node);
|
|
3623
|
+
if (hookName) hookNames.add(hookName);
|
|
3624
|
+
const sideEffect = sideEffectCategoryOf(node);
|
|
3625
|
+
if (sideEffect) sideEffectCategories.add(sideEffect);
|
|
3626
|
+
ts7.forEachChild(node, visit);
|
|
3014
3627
|
};
|
|
3015
3628
|
visit(sourceFile);
|
|
3016
|
-
|
|
3629
|
+
const hookDiversityPoint = hookNames.size >= 2 ? 1 : 0;
|
|
3630
|
+
return hookDiversityPoint + sideEffectCategories.size;
|
|
3017
3631
|
}
|
|
3018
3632
|
var hookComplexityRule = {
|
|
3019
3633
|
meta: {
|
|
@@ -3026,26 +3640,26 @@ var hookComplexityRule = {
|
|
|
3026
3640
|
create(context) {
|
|
3027
3641
|
const filename = context.filename;
|
|
3028
3642
|
const sourceRoot = sourceRootOf(context);
|
|
3029
|
-
const relative =
|
|
3643
|
+
const relative = path30.relative(sourceRoot, filename);
|
|
3030
3644
|
if (relative.startsWith("..")) return {};
|
|
3031
|
-
const segments = relative.split(
|
|
3645
|
+
const segments = relative.split(path30.sep);
|
|
3032
3646
|
const sourceText = context.sourceCode.text;
|
|
3033
3647
|
function checkHook(node, name, body, exported) {
|
|
3034
3648
|
if (!exported) return;
|
|
3035
3649
|
if (!name || !isHookName3(name)) return;
|
|
3036
3650
|
if (!body) return;
|
|
3037
|
-
const
|
|
3651
|
+
const score = computeExtractionScore(body, sourceText);
|
|
3038
3652
|
const parentFolder = segments.length >= 2 ? segments[segments.length - 2] : void 0;
|
|
3039
3653
|
const inSupportFolder = parentFolder === "hooks";
|
|
3040
|
-
if (
|
|
3654
|
+
if (score >= 2 && !inSupportFolder) {
|
|
3041
3655
|
context.report({
|
|
3042
3656
|
node,
|
|
3043
|
-
message: `Hook "${name}" has ${String(
|
|
3657
|
+
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`
|
|
3044
3658
|
});
|
|
3045
|
-
} else if (
|
|
3659
|
+
} else if (score < 2 && inSupportFolder) {
|
|
3046
3660
|
context.report({
|
|
3047
3661
|
node,
|
|
3048
|
-
message: `Hook "${name}" has
|
|
3662
|
+
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`
|
|
3049
3663
|
});
|
|
3050
3664
|
}
|
|
3051
3665
|
}
|
|
@@ -3070,9 +3684,9 @@ var hookComplexityRule = {
|
|
|
3070
3684
|
};
|
|
3071
3685
|
|
|
3072
3686
|
// eslint/rules/locale-dotted-path.ts
|
|
3073
|
-
import
|
|
3687
|
+
import path31 from "path";
|
|
3074
3688
|
function isInLocalesDir(filename) {
|
|
3075
|
-
const segments =
|
|
3689
|
+
const segments = path31.resolve(filename).split(path31.sep);
|
|
3076
3690
|
const srcIdx = segments.lastIndexOf("src");
|
|
3077
3691
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
3078
3692
|
}
|
|
@@ -3121,9 +3735,9 @@ var localeDottedPathRule = {
|
|
|
3121
3735
|
};
|
|
3122
3736
|
|
|
3123
3737
|
// eslint/rules/locales-location.ts
|
|
3124
|
-
import
|
|
3738
|
+
import path32 from "path";
|
|
3125
3739
|
function isLocalesFile(filename) {
|
|
3126
|
-
const segments =
|
|
3740
|
+
const segments = path32.resolve(filename).split(path32.sep);
|
|
3127
3741
|
const srcIdx = segments.lastIndexOf("src");
|
|
3128
3742
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
3129
3743
|
}
|
|
@@ -3148,7 +3762,7 @@ var localesLocationRule = {
|
|
|
3148
3762
|
create(context) {
|
|
3149
3763
|
if (isLocalesFile(context.filename) || isTestFile(context.filename)) return {};
|
|
3150
3764
|
const filename = context.filename;
|
|
3151
|
-
const segments =
|
|
3765
|
+
const segments = path32.resolve(filename).split(path32.sep);
|
|
3152
3766
|
const srcIdx = segments.lastIndexOf("src");
|
|
3153
3767
|
if (srcIdx === -1) return {};
|
|
3154
3768
|
const folder = segments[srcIdx + 1];
|
|
@@ -3172,7 +3786,7 @@ var localesLocationRule = {
|
|
|
3172
3786
|
};
|
|
3173
3787
|
|
|
3174
3788
|
// eslint/rules/hook-extraction.ts
|
|
3175
|
-
import
|
|
3789
|
+
import path33 from "path";
|
|
3176
3790
|
var hookExtractionRule = {
|
|
3177
3791
|
meta: {
|
|
3178
3792
|
schema: [],
|
|
@@ -3183,7 +3797,7 @@ var hookExtractionRule = {
|
|
|
3183
3797
|
},
|
|
3184
3798
|
create(context) {
|
|
3185
3799
|
const sourceRoot = sourceRootOf(context);
|
|
3186
|
-
const file =
|
|
3800
|
+
const file = path33.resolve(context.filename);
|
|
3187
3801
|
const segments = segmentsOf(file, sourceRoot);
|
|
3188
3802
|
if (segments.length === 0) return {};
|
|
3189
3803
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3209,7 +3823,7 @@ var hookExtractionRule = {
|
|
|
3209
3823
|
};
|
|
3210
3824
|
|
|
3211
3825
|
// eslint/rules/value-extraction.ts
|
|
3212
|
-
import
|
|
3826
|
+
import path34 from "path";
|
|
3213
3827
|
var valueExtractionRule = {
|
|
3214
3828
|
meta: {
|
|
3215
3829
|
schema: [],
|
|
@@ -3220,7 +3834,7 @@ var valueExtractionRule = {
|
|
|
3220
3834
|
},
|
|
3221
3835
|
create(context) {
|
|
3222
3836
|
const sourceRoot = sourceRootOf(context);
|
|
3223
|
-
const file =
|
|
3837
|
+
const file = path34.resolve(context.filename);
|
|
3224
3838
|
const segments = segmentsOf(file, sourceRoot);
|
|
3225
3839
|
if (segments.length === 0 || segments[0] !== "app") return {};
|
|
3226
3840
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3241,7 +3855,7 @@ var valueExtractionRule = {
|
|
|
3241
3855
|
};
|
|
3242
3856
|
|
|
3243
3857
|
// eslint/rules/config-extraction.ts
|
|
3244
|
-
import
|
|
3858
|
+
import path35 from "path";
|
|
3245
3859
|
var configExtractionRule = {
|
|
3246
3860
|
meta: {
|
|
3247
3861
|
schema: [],
|
|
@@ -3252,7 +3866,7 @@ var configExtractionRule = {
|
|
|
3252
3866
|
},
|
|
3253
3867
|
create(context) {
|
|
3254
3868
|
const sourceRoot = sourceRootOf(context);
|
|
3255
|
-
const file =
|
|
3869
|
+
const file = path35.resolve(context.filename);
|
|
3256
3870
|
const segments = segmentsOf(file, sourceRoot);
|
|
3257
3871
|
if (segments.length < 3 || segments[0] !== "config") return {};
|
|
3258
3872
|
if (SUPPORT_FOLDERS2.has(segments[2] ?? "")) return {};
|
|
@@ -3290,7 +3904,7 @@ var configExtractionRule = {
|
|
|
3290
3904
|
};
|
|
3291
3905
|
|
|
3292
3906
|
// eslint/rules/component-nesting.ts
|
|
3293
|
-
import
|
|
3907
|
+
import path36 from "path";
|
|
3294
3908
|
var componentNestingRule = {
|
|
3295
3909
|
meta: {
|
|
3296
3910
|
schema: [],
|
|
@@ -3301,7 +3915,7 @@ var componentNestingRule = {
|
|
|
3301
3915
|
},
|
|
3302
3916
|
create(context) {
|
|
3303
3917
|
const sourceRoot = sourceRootOf(context);
|
|
3304
|
-
const file =
|
|
3918
|
+
const file = path36.resolve(context.filename);
|
|
3305
3919
|
const segments = segmentsOf(file, sourceRoot);
|
|
3306
3920
|
if (segments.length !== 4 || segments[0] !== "features") return {};
|
|
3307
3921
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3334,7 +3948,7 @@ var componentNestingRule = {
|
|
|
3334
3948
|
};
|
|
3335
3949
|
|
|
3336
3950
|
// eslint/rules/stay-flat.ts
|
|
3337
|
-
import
|
|
3951
|
+
import path37 from "path";
|
|
3338
3952
|
var stayFlatRule = {
|
|
3339
3953
|
meta: {
|
|
3340
3954
|
schema: [],
|
|
@@ -3345,7 +3959,7 @@ var stayFlatRule = {
|
|
|
3345
3959
|
},
|
|
3346
3960
|
create(context) {
|
|
3347
3961
|
const sourceRoot = sourceRootOf(context);
|
|
3348
|
-
const file =
|
|
3962
|
+
const file = path37.resolve(context.filename);
|
|
3349
3963
|
const segments = segmentsOf(file, sourceRoot);
|
|
3350
3964
|
if (segments.length !== 3 || segments[0] !== "features") return {};
|
|
3351
3965
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3385,7 +3999,7 @@ var stayFlatRule = {
|
|
|
3385
3999
|
};
|
|
3386
4000
|
|
|
3387
4001
|
// eslint/rules/type-extraction.ts
|
|
3388
|
-
import
|
|
4002
|
+
import path38 from "path";
|
|
3389
4003
|
var typeExtractionRule = {
|
|
3390
4004
|
meta: {
|
|
3391
4005
|
schema: [],
|
|
@@ -3396,7 +4010,7 @@ var typeExtractionRule = {
|
|
|
3396
4010
|
},
|
|
3397
4011
|
create(context) {
|
|
3398
4012
|
const sourceRoot = sourceRootOf(context);
|
|
3399
|
-
const file =
|
|
4013
|
+
const file = path38.resolve(context.filename);
|
|
3400
4014
|
const segments = segmentsOf(file, sourceRoot);
|
|
3401
4015
|
if (segments.length === 0) return {};
|
|
3402
4016
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3442,9 +4056,9 @@ var typeExtractionRule = {
|
|
|
3442
4056
|
};
|
|
3443
4057
|
|
|
3444
4058
|
// eslint/rules/locale-placement.ts
|
|
3445
|
-
import
|
|
3446
|
-
import { readFileSync as
|
|
3447
|
-
import
|
|
4059
|
+
import path39 from "path";
|
|
4060
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
4061
|
+
import ts8 from "typescript";
|
|
3448
4062
|
var LOCALE_ACCESS = /\blocales\.(?<key>[A-Za-z_$][\w$]*)/g;
|
|
3449
4063
|
var camelCase = (name) => name.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase());
|
|
3450
4064
|
var FORCED_TOP_LEVEL = /* @__PURE__ */ new Set(["app", "shared", "compositions", "config"]);
|
|
@@ -3452,26 +4066,26 @@ function forcesTopLevel(segments) {
|
|
|
3452
4066
|
return FORCED_TOP_LEVEL.has(segments[0] ?? "") || SUPPORT_FOLDERS2.has(segments[0] ?? "");
|
|
3453
4067
|
}
|
|
3454
4068
|
function localePlacement(text) {
|
|
3455
|
-
const sourceFile =
|
|
4069
|
+
const sourceFile = ts8.createSourceFile("locales.ts", text, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TS);
|
|
3456
4070
|
for (const statement of sourceFile.statements) {
|
|
3457
|
-
if (!
|
|
3458
|
-
const isExported2 = (
|
|
3459
|
-
(modifier) => modifier.kind ===
|
|
4071
|
+
if (!ts8.isVariableStatement(statement)) continue;
|
|
4072
|
+
const isExported2 = (ts8.getModifiers(statement) ?? []).some(
|
|
4073
|
+
(modifier) => modifier.kind === ts8.SyntaxKind.ExportKeyword
|
|
3460
4074
|
);
|
|
3461
4075
|
if (!isExported2) continue;
|
|
3462
4076
|
for (const declaration of statement.declarationList.declarations) {
|
|
3463
|
-
if (!
|
|
3464
|
-
if (!declaration.initializer || !
|
|
4077
|
+
if (!ts8.isIdentifier(declaration.name) || declaration.name.text !== "locales") continue;
|
|
4078
|
+
if (!declaration.initializer || !ts8.isObjectLiteralExpression(declaration.initializer)) continue;
|
|
3465
4079
|
const placement = /* @__PURE__ */ new Map();
|
|
3466
4080
|
for (const property of declaration.initializer.properties) {
|
|
3467
|
-
if (!
|
|
4081
|
+
if (!ts8.isPropertyAssignment(property)) continue;
|
|
3468
4082
|
let name;
|
|
3469
|
-
if (
|
|
3470
|
-
else if (
|
|
4083
|
+
if (ts8.isIdentifier(property.name)) name = property.name.text;
|
|
4084
|
+
else if (ts8.isStringLiteral(property.name)) name = property.name.text;
|
|
3471
4085
|
if (name === void 0) continue;
|
|
3472
4086
|
const line = sourceFile.getLineAndCharacterOfPosition(property.getStart(sourceFile)).line + 1;
|
|
3473
4087
|
placement.set(name, {
|
|
3474
|
-
kind:
|
|
4088
|
+
kind: ts8.isObjectLiteralExpression(property.initializer) ? "nested" : "top",
|
|
3475
4089
|
line
|
|
3476
4090
|
});
|
|
3477
4091
|
}
|
|
@@ -3490,7 +4104,7 @@ var localePlacementRule = {
|
|
|
3490
4104
|
},
|
|
3491
4105
|
create(context) {
|
|
3492
4106
|
const sourceRoot = sourceRootOf(context);
|
|
3493
|
-
const file =
|
|
4107
|
+
const file = path39.resolve(context.filename);
|
|
3494
4108
|
const segments = segmentsOf(file, sourceRoot);
|
|
3495
4109
|
if (segments.length === 0) return {};
|
|
3496
4110
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3500,7 +4114,7 @@ var localePlacementRule = {
|
|
|
3500
4114
|
return candidateSegments.length === 2 && candidateSegments[0] === "locales" && candidateSegments[1]?.startsWith("index.");
|
|
3501
4115
|
});
|
|
3502
4116
|
if (!localesFile || file !== localesFile) return {};
|
|
3503
|
-
const placement = localePlacement(
|
|
4117
|
+
const placement = localePlacement(readFileSync5(localesFile, "utf8"));
|
|
3504
4118
|
if (!placement) return {};
|
|
3505
4119
|
const keyReaders = /* @__PURE__ */ new Map();
|
|
3506
4120
|
const keyFeatures = /* @__PURE__ */ new Map();
|
|
@@ -3512,7 +4126,7 @@ var localePlacementRule = {
|
|
|
3512
4126
|
);
|
|
3513
4127
|
if (!importsLocales) continue;
|
|
3514
4128
|
const candidateSegments = segmentsOf(candidateFile, sourceRoot);
|
|
3515
|
-
for (const match of
|
|
4129
|
+
for (const match of readFileSync5(candidateFile, "utf8").matchAll(LOCALE_ACCESS)) {
|
|
3516
4130
|
const key = match.groups?.key;
|
|
3517
4131
|
if (key === void 0) continue;
|
|
3518
4132
|
const readers = keyReaders.get(key) ?? /* @__PURE__ */ new Set();
|
|
@@ -3568,39 +4182,39 @@ var localePlacementRule = {
|
|
|
3568
4182
|
};
|
|
3569
4183
|
|
|
3570
4184
|
// eslint/rules/sole-state-owner.ts
|
|
3571
|
-
import
|
|
3572
|
-
import
|
|
4185
|
+
import path40 from "path";
|
|
4186
|
+
import ts9 from "typescript";
|
|
3573
4187
|
function findStateHooks(node) {
|
|
3574
4188
|
const hooks = [];
|
|
3575
4189
|
const visit = (child) => {
|
|
3576
|
-
if (
|
|
3577
|
-
if (
|
|
4190
|
+
if (ts9.isCallExpression(child) && ts9.isIdentifier(child.expression) && child.expression.text === "useState") {
|
|
4191
|
+
if (ts9.isVariableDeclaration(child.parent)) {
|
|
3578
4192
|
const { name } = child.parent;
|
|
3579
|
-
if (
|
|
4193
|
+
if (ts9.isArrayBindingPattern(name) && name.elements.length >= 2) {
|
|
3580
4194
|
const value = name.elements[0];
|
|
3581
4195
|
const updater = name.elements[1];
|
|
3582
|
-
if (value && updater &&
|
|
4196
|
+
if (value && updater && ts9.isBindingElement(value) && ts9.isBindingElement(updater)) {
|
|
3583
4197
|
const valueName = value.name;
|
|
3584
4198
|
const updaterName = updater.name;
|
|
3585
|
-
if (
|
|
4199
|
+
if (ts9.isIdentifier(valueName) && ts9.isIdentifier(updaterName)) {
|
|
3586
4200
|
hooks.push({ value: valueName.text, updater: updaterName.text });
|
|
3587
4201
|
}
|
|
3588
4202
|
}
|
|
3589
4203
|
}
|
|
3590
4204
|
}
|
|
3591
4205
|
}
|
|
3592
|
-
|
|
4206
|
+
ts9.forEachChild(child, visit);
|
|
3593
4207
|
};
|
|
3594
4208
|
visit(node);
|
|
3595
4209
|
return hooks;
|
|
3596
4210
|
}
|
|
3597
4211
|
function isHookUsage(node, hook) {
|
|
3598
|
-
if (
|
|
4212
|
+
if (ts9.isCallExpression(node) && ts9.isIdentifier(node.expression) && node.expression.text === hook.updater) {
|
|
3599
4213
|
return "updater";
|
|
3600
4214
|
}
|
|
3601
|
-
if (
|
|
4215
|
+
if (ts9.isIdentifier(node) && node.text === hook.value) {
|
|
3602
4216
|
const parent = node.parent;
|
|
3603
|
-
if (
|
|
4217
|
+
if (ts9.isBindingElement(parent) || ts9.isPropertyAccessExpression(parent) || ts9.isShorthandPropertyAssignment(parent)) {
|
|
3604
4218
|
return void 0;
|
|
3605
4219
|
}
|
|
3606
4220
|
return "value";
|
|
@@ -3610,16 +4224,16 @@ function isHookUsage(node, hook) {
|
|
|
3610
4224
|
function topLevelJsxChildren(initial) {
|
|
3611
4225
|
if (!initial) return void 0;
|
|
3612
4226
|
let expression = initial;
|
|
3613
|
-
while (
|
|
3614
|
-
if (
|
|
4227
|
+
while (ts9.isParenthesizedExpression(expression)) expression = expression.expression;
|
|
4228
|
+
if (ts9.isJsxFragment(expression)) {
|
|
3615
4229
|
const children = expression.children.filter(
|
|
3616
|
-
(c) => !
|
|
4230
|
+
(c) => !ts9.isJsxText(c) && !ts9.isJsxSpreadAttribute(c)
|
|
3617
4231
|
);
|
|
3618
4232
|
return { root: expression, children };
|
|
3619
4233
|
}
|
|
3620
|
-
if (
|
|
4234
|
+
if (ts9.isJsxElement(expression)) {
|
|
3621
4235
|
const children = expression.children.filter(
|
|
3622
|
-
(c) => !
|
|
4236
|
+
(c) => !ts9.isJsxText(c) && !ts9.isJsxSpreadAttribute(c)
|
|
3623
4237
|
);
|
|
3624
4238
|
return { root: expression, children };
|
|
3625
4239
|
}
|
|
@@ -3633,8 +4247,8 @@ function collectUsesIn(child, hook) {
|
|
|
3633
4247
|
positions.push(node);
|
|
3634
4248
|
count += 1;
|
|
3635
4249
|
}
|
|
3636
|
-
if (
|
|
3637
|
-
|
|
4250
|
+
if (ts9.isFunctionDeclaration(node) || ts9.isClassDeclaration(node)) return;
|
|
4251
|
+
ts9.forEachChild(node, visit);
|
|
3638
4252
|
};
|
|
3639
4253
|
visit(child);
|
|
3640
4254
|
return { positions, count };
|
|
@@ -3648,7 +4262,7 @@ var soleStateOwnerRule = {
|
|
|
3648
4262
|
}
|
|
3649
4263
|
},
|
|
3650
4264
|
create(context) {
|
|
3651
|
-
const filename =
|
|
4265
|
+
const filename = path40.resolve(context.filename);
|
|
3652
4266
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
|
|
3653
4267
|
const text = context.sourceCode.text;
|
|
3654
4268
|
const components = parseComponentInfo(text, filename);
|
|
@@ -3673,18 +4287,18 @@ var soleStateOwnerRule = {
|
|
|
3673
4287
|
}
|
|
3674
4288
|
};
|
|
3675
4289
|
function analyzeSoleOwner(declaration, hook) {
|
|
3676
|
-
const body =
|
|
3677
|
-
if (!body || !
|
|
4290
|
+
const body = ts9.isFunctionDeclaration(declaration) ? declaration.body : void 0;
|
|
4291
|
+
if (!body || !ts9.isBlock(body)) return void 0;
|
|
3678
4292
|
const returns = [];
|
|
3679
4293
|
const visit = (node) => {
|
|
3680
|
-
if (node !== body && (
|
|
3681
|
-
if (
|
|
3682
|
-
|
|
4294
|
+
if (node !== body && (ts9.isFunctionLike(node) || ts9.isClassLike(node))) return;
|
|
4295
|
+
if (ts9.isReturnStatement(node)) returns.push(node);
|
|
4296
|
+
ts9.forEachChild(node, visit);
|
|
3683
4297
|
};
|
|
3684
4298
|
visit(body);
|
|
3685
4299
|
if (returns.length !== 1) return void 0;
|
|
3686
4300
|
const single = returns[0];
|
|
3687
|
-
if (!single?.expression || !
|
|
4301
|
+
if (!single?.expression || !ts9.isExpression(single.expression)) return void 0;
|
|
3688
4302
|
const returnExpression = single.expression;
|
|
3689
4303
|
const root = topLevelJsxChildren(returnExpression);
|
|
3690
4304
|
if (!root || root.children.length === 0) return void 0;
|
|
@@ -3707,11 +4321,11 @@ function usesOutsideJsx(declaration, hook, children) {
|
|
|
3707
4321
|
let outside = false;
|
|
3708
4322
|
const visit = (node) => {
|
|
3709
4323
|
if (outside) return;
|
|
3710
|
-
if (node !== declaration && (
|
|
4324
|
+
if (node !== declaration && (ts9.isFunctionDeclaration(node) || ts9.isClassDeclaration(node))) return;
|
|
3711
4325
|
if (isHookUsage(node, hook)) {
|
|
3712
4326
|
let current = node;
|
|
3713
4327
|
let isInJsxChild = false;
|
|
3714
|
-
while (!
|
|
4328
|
+
while (!ts9.isSourceFile(current)) {
|
|
3715
4329
|
if (children.includes(current)) {
|
|
3716
4330
|
isInJsxChild = true;
|
|
3717
4331
|
break;
|
|
@@ -3720,14 +4334,14 @@ function usesOutsideJsx(declaration, hook, children) {
|
|
|
3720
4334
|
}
|
|
3721
4335
|
if (!isInJsxChild) outside = true;
|
|
3722
4336
|
}
|
|
3723
|
-
|
|
4337
|
+
ts9.forEachChild(node, visit);
|
|
3724
4338
|
};
|
|
3725
4339
|
visit(declaration);
|
|
3726
4340
|
return outside;
|
|
3727
4341
|
}
|
|
3728
4342
|
|
|
3729
4343
|
// eslint/rules/locale-key-shape.ts
|
|
3730
|
-
import
|
|
4344
|
+
import path41 from "path";
|
|
3731
4345
|
var MAX_KEY_LENGTH = 30;
|
|
3732
4346
|
var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
|
|
3733
4347
|
"Button",
|
|
@@ -3777,7 +4391,7 @@ var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
|
|
|
3777
4391
|
var CAMEL_CASE = /^[a-z][a-zA-Z0-9]*$/;
|
|
3778
4392
|
var ENGLISH = /^[A-Za-z0-9_]*$/;
|
|
3779
4393
|
function isLocalesFile2(filename) {
|
|
3780
|
-
const segments =
|
|
4394
|
+
const segments = path41.resolve(filename).split(path41.sep);
|
|
3781
4395
|
const srcIdx = segments.lastIndexOf("src");
|
|
3782
4396
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
3783
4397
|
}
|
|
@@ -3848,8 +4462,8 @@ var localeKeyShapeRule = {
|
|
|
3848
4462
|
};
|
|
3849
4463
|
|
|
3850
4464
|
// eslint/rules/shared-style-dedup.ts
|
|
3851
|
-
import
|
|
3852
|
-
import { readFileSync as
|
|
4465
|
+
import path42 from "path";
|
|
4466
|
+
import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
3853
4467
|
var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
|
|
3854
4468
|
var comboCache;
|
|
3855
4469
|
function combosFor(index) {
|
|
@@ -3863,7 +4477,7 @@ function combosFor(index) {
|
|
|
3863
4477
|
}
|
|
3864
4478
|
const combos = /* @__PURE__ */ new Map();
|
|
3865
4479
|
for (const file of files) {
|
|
3866
|
-
const text =
|
|
4480
|
+
const text = readFileSync6(file, "utf8");
|
|
3867
4481
|
for (const match of text.matchAll(CLASS_NAME)) {
|
|
3868
4482
|
const classes = (match.groups?.classes ?? "").split(/\s+/).filter(Boolean);
|
|
3869
4483
|
if (classes.length < 2) continue;
|
|
@@ -3886,7 +4500,7 @@ var sharedStyleDedupRule = {
|
|
|
3886
4500
|
},
|
|
3887
4501
|
create(context) {
|
|
3888
4502
|
const sourceRoot = sourceRootOf(context);
|
|
3889
|
-
const file =
|
|
4503
|
+
const file = path42.resolve(context.filename);
|
|
3890
4504
|
const segments = segmentsOf(file, sourceRoot);
|
|
3891
4505
|
if (segments.length === 0) return {};
|
|
3892
4506
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -4090,7 +4704,7 @@ var zodSchemaValidationRule = {
|
|
|
4090
4704
|
};
|
|
4091
4705
|
|
|
4092
4706
|
// eslint/rules/schema-casing.ts
|
|
4093
|
-
import
|
|
4707
|
+
import path43 from "path";
|
|
4094
4708
|
function isCamelCase(name) {
|
|
4095
4709
|
return /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
4096
4710
|
}
|
|
@@ -4118,9 +4732,9 @@ var schemaCasingRule = {
|
|
|
4118
4732
|
}
|
|
4119
4733
|
},
|
|
4120
4734
|
create(context) {
|
|
4121
|
-
const filename =
|
|
4735
|
+
const filename = path43.resolve(context.filename);
|
|
4122
4736
|
const sourceRoot = sourceRootOf(context);
|
|
4123
|
-
if (!filename.startsWith(sourceRoot +
|
|
4737
|
+
if (!filename.startsWith(sourceRoot + path43.sep)) return {};
|
|
4124
4738
|
let zodLocalName;
|
|
4125
4739
|
return {
|
|
4126
4740
|
ImportDeclaration(node) {
|
|
@@ -4152,7 +4766,7 @@ var schemaCasingRule = {
|
|
|
4152
4766
|
};
|
|
4153
4767
|
|
|
4154
4768
|
// eslint/rules/component-casing.ts
|
|
4155
|
-
import
|
|
4769
|
+
import path44 from "path";
|
|
4156
4770
|
function isPascalCase6(name) {
|
|
4157
4771
|
return /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
4158
4772
|
}
|
|
@@ -4165,10 +4779,10 @@ var componentCasingRule = {
|
|
|
4165
4779
|
}
|
|
4166
4780
|
},
|
|
4167
4781
|
create(context) {
|
|
4168
|
-
const filename =
|
|
4782
|
+
const filename = path44.resolve(context.filename);
|
|
4169
4783
|
const sourceRoot = sourceRootOf(context);
|
|
4170
|
-
if (!filename.startsWith(sourceRoot +
|
|
4171
|
-
if (
|
|
4784
|
+
if (!filename.startsWith(sourceRoot + path44.sep)) return {};
|
|
4785
|
+
if (path44.extname(filename) !== ".tsx") return {};
|
|
4172
4786
|
return {
|
|
4173
4787
|
Program(node) {
|
|
4174
4788
|
for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
|
|
@@ -4185,7 +4799,7 @@ var componentCasingRule = {
|
|
|
4185
4799
|
};
|
|
4186
4800
|
|
|
4187
4801
|
// eslint/rules/source-under-src.ts
|
|
4188
|
-
import
|
|
4802
|
+
import path45 from "path";
|
|
4189
4803
|
var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
|
|
4190
4804
|
".agents",
|
|
4191
4805
|
".cache",
|
|
@@ -4226,14 +4840,14 @@ var sourceUnderSrcRule = {
|
|
|
4226
4840
|
}
|
|
4227
4841
|
},
|
|
4228
4842
|
create(context) {
|
|
4229
|
-
const filename =
|
|
4843
|
+
const filename = path45.resolve(context.filename);
|
|
4230
4844
|
if (!MODULE_EXTENSION.test(filename)) return {};
|
|
4231
|
-
const relative =
|
|
4845
|
+
const relative = path45.relative(context.cwd, filename).replace(/\\/g, "/");
|
|
4232
4846
|
if (relative === "src" || relative.startsWith("src/")) return {};
|
|
4233
4847
|
const topLevel = relative.split("/")[0] ?? "";
|
|
4234
4848
|
if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
|
|
4235
4849
|
if (!relative.includes("/")) {
|
|
4236
|
-
const basename =
|
|
4850
|
+
const basename = path45.basename(filename);
|
|
4237
4851
|
if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
|
|
4238
4852
|
}
|
|
4239
4853
|
return {
|
|
@@ -4250,8 +4864,8 @@ var sourceUnderSrcRule = {
|
|
|
4250
4864
|
|
|
4251
4865
|
// eslint/rules/zirka-baseline.ts
|
|
4252
4866
|
import fs5 from "fs";
|
|
4253
|
-
import
|
|
4254
|
-
var
|
|
4867
|
+
import path46 from "path";
|
|
4868
|
+
var ESLINT_CONFIG3 = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
|
|
4255
4869
|
var PRETTIER_CONFIGS = [
|
|
4256
4870
|
"prettier.config.mjs",
|
|
4257
4871
|
"prettier.config.cjs",
|
|
@@ -4269,10 +4883,10 @@ var zirkaBaselineRule = {
|
|
|
4269
4883
|
}
|
|
4270
4884
|
},
|
|
4271
4885
|
create(context) {
|
|
4272
|
-
const filename =
|
|
4273
|
-
const basename =
|
|
4274
|
-
if (!
|
|
4275
|
-
const projectRoot =
|
|
4886
|
+
const filename = path46.resolve(context.filename);
|
|
4887
|
+
const basename = path46.basename(filename);
|
|
4888
|
+
if (!ESLINT_CONFIG3.test(basename)) return {};
|
|
4889
|
+
const projectRoot = path46.dirname(filename);
|
|
4276
4890
|
const report3 = (message) => {
|
|
4277
4891
|
context.report({
|
|
4278
4892
|
node: context.sourceCode.ast,
|
|
@@ -4287,7 +4901,7 @@ var zirkaBaselineRule = {
|
|
|
4287
4901
|
'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
|
|
4288
4902
|
);
|
|
4289
4903
|
}
|
|
4290
|
-
const tsconfigPath =
|
|
4904
|
+
const tsconfigPath = path46.join(projectRoot, "tsconfig.json");
|
|
4291
4905
|
if (!fs5.existsSync(tsconfigPath)) {
|
|
4292
4906
|
report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
|
|
4293
4907
|
} else {
|
|
@@ -4305,13 +4919,13 @@ var zirkaBaselineRule = {
|
|
|
4305
4919
|
report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
|
|
4306
4920
|
}
|
|
4307
4921
|
}
|
|
4308
|
-
const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(
|
|
4922
|
+
const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(path46.join(projectRoot, name)));
|
|
4309
4923
|
if (!prettierConfigFile) {
|
|
4310
4924
|
report3(
|
|
4311
4925
|
"No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
|
|
4312
4926
|
);
|
|
4313
4927
|
} else {
|
|
4314
|
-
const content = fs5.readFileSync(
|
|
4928
|
+
const content = fs5.readFileSync(path46.join(projectRoot, prettierConfigFile), "utf8");
|
|
4315
4929
|
if (!content.includes("zirka")) {
|
|
4316
4930
|
report3(
|
|
4317
4931
|
"The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
|
|
@@ -4353,6 +4967,15 @@ function getTextContent(node) {
|
|
|
4353
4967
|
function getLine(node) {
|
|
4354
4968
|
return node.position?.start.line ?? 0;
|
|
4355
4969
|
}
|
|
4970
|
+
function linkTarget(url) {
|
|
4971
|
+
return url.split("#")[0] ?? url;
|
|
4972
|
+
}
|
|
4973
|
+
function isDocLink(url) {
|
|
4974
|
+
return linkTarget(url).endsWith(".md");
|
|
4975
|
+
}
|
|
4976
|
+
function headingAnchor(text) {
|
|
4977
|
+
return text.trim().toLowerCase().replaceAll(/[^\p{L}\p{N}\s-]/gu, "").replaceAll(/\s+/g, "-");
|
|
4978
|
+
}
|
|
4356
4979
|
|
|
4357
4980
|
// eslint/rules/documentation/doc-kind-suffix.ts
|
|
4358
4981
|
var docKindSuffixRule = {
|
|
@@ -4381,7 +5004,7 @@ var docKindSuffixRule = {
|
|
|
4381
5004
|
};
|
|
4382
5005
|
|
|
4383
5006
|
// eslint/rules/documentation/title-matches-file-name.ts
|
|
4384
|
-
import
|
|
5007
|
+
import path47 from "path";
|
|
4385
5008
|
function toExpectedFileName(title) {
|
|
4386
5009
|
return `${title.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
|
|
4387
5010
|
}
|
|
@@ -4401,7 +5024,7 @@ var titleMatchesFileNameRule = {
|
|
|
4401
5024
|
if (!filename.endsWith(".md")) return;
|
|
4402
5025
|
const title = getTextContent(node).trim();
|
|
4403
5026
|
const expectedFileName = toExpectedFileName(title);
|
|
4404
|
-
const actualFileName =
|
|
5027
|
+
const actualFileName = path47.basename(filename);
|
|
4405
5028
|
if (!title) {
|
|
4406
5029
|
context.report({
|
|
4407
5030
|
node,
|
|
@@ -4556,7 +5179,7 @@ var guideStepSingleSentenceRule = {
|
|
|
4556
5179
|
|
|
4557
5180
|
// eslint/rules/documentation/guide-step-single-link.ts
|
|
4558
5181
|
function countDocLinks(node) {
|
|
4559
|
-
if (node.type === "link" && node.url
|
|
5182
|
+
if (node.type === "link" && isDocLink(node.url)) return 1;
|
|
4560
5183
|
if ("children" in node) {
|
|
4561
5184
|
return node.children.reduce((sum, child) => sum + countDocLinks(child), 0);
|
|
4562
5185
|
}
|
|
@@ -4774,7 +5397,7 @@ var noCrossDocumentLinkRule = {
|
|
|
4774
5397
|
const filename = getFilename(context);
|
|
4775
5398
|
const kind = linkedKind(filename);
|
|
4776
5399
|
if (!kind) return;
|
|
4777
|
-
if (node.url
|
|
5400
|
+
if (isDocLink(node.url)) {
|
|
4778
5401
|
context.report({
|
|
4779
5402
|
node,
|
|
4780
5403
|
message: `${kind} links another document: ${node.url}`
|
|
@@ -4842,22 +5465,46 @@ var referenceBlockHeadingsRule = {
|
|
|
4842
5465
|
}
|
|
4843
5466
|
};
|
|
4844
5467
|
|
|
5468
|
+
// eslint/rules/documentation/reference-max-heading-depth.ts
|
|
5469
|
+
var referenceMaxHeadingDepthRule = {
|
|
5470
|
+
meta: {
|
|
5471
|
+
type: "problem",
|
|
5472
|
+
docs: {
|
|
5473
|
+
description: "Reference max heading depth rule.",
|
|
5474
|
+
recommended: true
|
|
5475
|
+
}
|
|
5476
|
+
},
|
|
5477
|
+
create(context) {
|
|
5478
|
+
return {
|
|
5479
|
+
heading(node) {
|
|
5480
|
+
const filename = getFilename(context);
|
|
5481
|
+
if (!filename.endsWith("-reference.md")) return;
|
|
5482
|
+
if (node.depth <= 2) return;
|
|
5483
|
+
context.report({
|
|
5484
|
+
node,
|
|
5485
|
+
message: "reference heading is deeper than level 2; flatten it into the section it would nest under"
|
|
5486
|
+
});
|
|
5487
|
+
}
|
|
5488
|
+
};
|
|
5489
|
+
}
|
|
5490
|
+
};
|
|
5491
|
+
|
|
4845
5492
|
// eslint/rules/documentation/support-document-placement.ts
|
|
4846
5493
|
import { existsSync as existsSync2 } from "fs";
|
|
4847
|
-
import
|
|
5494
|
+
import path48 from "path";
|
|
4848
5495
|
function checkPlacement(filename, kind) {
|
|
4849
|
-
const parentFolder =
|
|
5496
|
+
const parentFolder = path48.basename(path48.dirname(filename));
|
|
4850
5497
|
const expectedParent = `${kind}s`;
|
|
4851
5498
|
if (parentFolder !== expectedParent) {
|
|
4852
5499
|
return `${kind} lives in "${parentFolder}/" instead of "${expectedParent}/"`;
|
|
4853
5500
|
}
|
|
4854
|
-
const guideFolderPath =
|
|
4855
|
-
const guideFolder =
|
|
5501
|
+
const guideFolderPath = path48.dirname(path48.dirname(filename));
|
|
5502
|
+
const guideFolder = path48.basename(guideFolderPath);
|
|
4856
5503
|
if (!guideFolder.endsWith("-guide")) {
|
|
4857
5504
|
return `${kind} owner folder "${guideFolder}/" does not use the "*-guide/" suffix`;
|
|
4858
5505
|
}
|
|
4859
5506
|
const entryPoint = `${guideFolder}.md`;
|
|
4860
|
-
if (!existsSync2(
|
|
5507
|
+
if (!existsSync2(path48.join(guideFolderPath, entryPoint))) {
|
|
4861
5508
|
return `${kind} owner folder "${guideFolder}/" has no "${entryPoint}" entry point`;
|
|
4862
5509
|
}
|
|
4863
5510
|
return void 0;
|
|
@@ -4916,11 +5563,11 @@ var noTemplatePromptRule = {
|
|
|
4916
5563
|
};
|
|
4917
5564
|
|
|
4918
5565
|
// eslint/rules/documentation/guide-folder-entry-point.ts
|
|
4919
|
-
import
|
|
5566
|
+
import path50 from "path";
|
|
4920
5567
|
|
|
4921
5568
|
// eslint/rules/documentation/project-index.ts
|
|
4922
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
4923
|
-
import
|
|
5569
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
5570
|
+
import path49 from "path";
|
|
4924
5571
|
var KIND_BY_SUFFIX = [
|
|
4925
5572
|
["-rule.md", "rule"],
|
|
4926
5573
|
["-guide.md", "guide"],
|
|
@@ -4929,7 +5576,7 @@ var KIND_BY_SUFFIX = [
|
|
|
4929
5576
|
];
|
|
4930
5577
|
function listMarkdownFiles(dir) {
|
|
4931
5578
|
return readdirSync3(dir).flatMap((entry) => {
|
|
4932
|
-
const entryPath =
|
|
5579
|
+
const entryPath = path49.join(dir, entry);
|
|
4933
5580
|
if (statSync4(entryPath).isDirectory()) {
|
|
4934
5581
|
return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
|
|
4935
5582
|
}
|
|
@@ -4937,7 +5584,7 @@ function listMarkdownFiles(dir) {
|
|
|
4937
5584
|
});
|
|
4938
5585
|
}
|
|
4939
5586
|
function extractTitle(filePath) {
|
|
4940
|
-
const content =
|
|
5587
|
+
const content = readFileSync7(filePath, "utf8");
|
|
4941
5588
|
const match = /^# (?<title>.+)$/m.exec(content);
|
|
4942
5589
|
return match?.groups?.title?.trim() ?? "";
|
|
4943
5590
|
}
|
|
@@ -4947,11 +5594,11 @@ function getProjectDocs(docsRoot) {
|
|
|
4947
5594
|
if (cached) return cached;
|
|
4948
5595
|
const files = listMarkdownFiles(docsRoot);
|
|
4949
5596
|
const docs = files.sort((a, b) => a.localeCompare(b)).map((filePath) => {
|
|
4950
|
-
const fileName =
|
|
5597
|
+
const fileName = path49.basename(filePath);
|
|
4951
5598
|
const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
|
|
4952
5599
|
return {
|
|
4953
5600
|
filePath,
|
|
4954
|
-
doc:
|
|
5601
|
+
doc: path49.relative(docsRoot, filePath).split(path49.sep).join("/"),
|
|
4955
5602
|
fileName,
|
|
4956
5603
|
kind,
|
|
4957
5604
|
title: extractTitle(filePath)
|
|
@@ -4961,12 +5608,12 @@ function getProjectDocs(docsRoot) {
|
|
|
4961
5608
|
return docs;
|
|
4962
5609
|
}
|
|
4963
5610
|
function findDocsRoot(filePath) {
|
|
4964
|
-
let dir =
|
|
5611
|
+
let dir = path49.dirname(filePath);
|
|
4965
5612
|
for (; ; ) {
|
|
4966
|
-
if (
|
|
5613
|
+
if (path49.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
|
|
4967
5614
|
return dir;
|
|
4968
5615
|
}
|
|
4969
|
-
const parent =
|
|
5616
|
+
const parent = path49.dirname(dir);
|
|
4970
5617
|
if (parent === dir) return void 0;
|
|
4971
5618
|
dir = parent;
|
|
4972
5619
|
}
|
|
@@ -4990,13 +5637,13 @@ var guideFolderEntryPointRule = {
|
|
|
4990
5637
|
if (!docsRoot) return;
|
|
4991
5638
|
const docs = getProjectDocs(docsRoot);
|
|
4992
5639
|
const guideFolders = new Set(
|
|
4993
|
-
docs.filter((doc) => ["rules", "references"].includes(
|
|
5640
|
+
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))
|
|
4994
5641
|
);
|
|
4995
|
-
const currentDir =
|
|
5642
|
+
const currentDir = path50.dirname(filename);
|
|
4996
5643
|
if (guideFolders.has(currentDir)) {
|
|
4997
|
-
const expectedEntryPoint = `${
|
|
5644
|
+
const expectedEntryPoint = `${path50.basename(currentDir)}.md`;
|
|
4998
5645
|
const hasEntryPoint = docs.some(
|
|
4999
|
-
(doc) => doc.kind === "guide" &&
|
|
5646
|
+
(doc) => doc.kind === "guide" && path50.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
|
|
5000
5647
|
);
|
|
5001
5648
|
if (!hasEntryPoint) {
|
|
5002
5649
|
context.report({
|
|
@@ -5140,6 +5787,8 @@ var policySubjectHeadingsRule = {
|
|
|
5140
5787
|
};
|
|
5141
5788
|
|
|
5142
5789
|
// eslint/rules/documentation/guide-link-anchors.ts
|
|
5790
|
+
import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
|
|
5791
|
+
import path51 from "path";
|
|
5143
5792
|
function visitSteps2(node, check) {
|
|
5144
5793
|
if (node.type === "list" && node.ordered) {
|
|
5145
5794
|
for (const child of node.children) check(child);
|
|
@@ -5149,17 +5798,52 @@ function visitSteps2(node, check) {
|
|
|
5149
5798
|
}
|
|
5150
5799
|
}
|
|
5151
5800
|
function collectGuideLinks(node) {
|
|
5152
|
-
if (node.type === "link" && node.url.endsWith("-guide.md")) return [node];
|
|
5801
|
+
if (node.type === "link" && linkTarget(node.url).endsWith("-guide.md")) return [node];
|
|
5153
5802
|
if ("children" in node) {
|
|
5154
5803
|
return node.children.flatMap(collectGuideLinks);
|
|
5155
5804
|
}
|
|
5156
5805
|
return [];
|
|
5157
5806
|
}
|
|
5807
|
+
function collectLinks(node, out) {
|
|
5808
|
+
if (node.type === "link") out.push(node);
|
|
5809
|
+
if ("children" in node) {
|
|
5810
|
+
for (const child of node.children) collectLinks(child, out);
|
|
5811
|
+
}
|
|
5812
|
+
}
|
|
5813
|
+
var anchorsByFile = /* @__PURE__ */ new Map();
|
|
5814
|
+
function documentAnchors(filePath) {
|
|
5815
|
+
const cached = anchorsByFile.get(filePath);
|
|
5816
|
+
if (cached) return cached;
|
|
5817
|
+
const anchors = /* @__PURE__ */ new Set();
|
|
5818
|
+
anchorsByFile.set(filePath, anchors);
|
|
5819
|
+
let content;
|
|
5820
|
+
try {
|
|
5821
|
+
content = readFileSync8(filePath, "utf8");
|
|
5822
|
+
} catch {
|
|
5823
|
+
return anchors;
|
|
5824
|
+
}
|
|
5825
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5826
|
+
let fenced = false;
|
|
5827
|
+
for (const line of content.split("\n")) {
|
|
5828
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
5829
|
+
fenced = !fenced;
|
|
5830
|
+
continue;
|
|
5831
|
+
}
|
|
5832
|
+
if (fenced) continue;
|
|
5833
|
+
const heading = /^#{1,6}\s+(?<text>.+)$/.exec(line)?.groups?.text;
|
|
5834
|
+
if (!heading) continue;
|
|
5835
|
+
const base = headingAnchor(heading);
|
|
5836
|
+
const count = seen.get(base) ?? 0;
|
|
5837
|
+
seen.set(base, count + 1);
|
|
5838
|
+
anchors.add(count === 0 ? base : `${base}-${String(count)}`);
|
|
5839
|
+
}
|
|
5840
|
+
return anchors;
|
|
5841
|
+
}
|
|
5158
5842
|
var guideLinkAnchorsRule = {
|
|
5159
5843
|
meta: {
|
|
5160
5844
|
type: "problem",
|
|
5161
5845
|
docs: {
|
|
5162
|
-
description: "A step that links another Guide must link directly to a How To section.",
|
|
5846
|
+
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.",
|
|
5163
5847
|
recommended: true
|
|
5164
5848
|
}
|
|
5165
5849
|
},
|
|
@@ -5178,6 +5862,19 @@ var guideLinkAnchorsRule = {
|
|
|
5178
5862
|
}
|
|
5179
5863
|
}
|
|
5180
5864
|
});
|
|
5865
|
+
const links = [];
|
|
5866
|
+
collectLinks(node, links);
|
|
5867
|
+
for (const link of links) {
|
|
5868
|
+
const [target = "", fragment = ""] = link.url.split("#");
|
|
5869
|
+
if (!fragment || !isDocLink(link.url)) continue;
|
|
5870
|
+
const targetPath = path51.resolve(path51.dirname(filename), target);
|
|
5871
|
+
if (!existsSync3(targetPath)) continue;
|
|
5872
|
+
if (documentAnchors(targetPath).has(fragment.toLowerCase())) continue;
|
|
5873
|
+
context.report({
|
|
5874
|
+
node: link,
|
|
5875
|
+
message: `link ${link.url} points at a heading the document does not have`
|
|
5876
|
+
});
|
|
5877
|
+
}
|
|
5181
5878
|
}
|
|
5182
5879
|
};
|
|
5183
5880
|
}
|
|
@@ -5220,41 +5917,82 @@ var noNestedHowToRule = {
|
|
|
5220
5917
|
};
|
|
5221
5918
|
|
|
5222
5919
|
// eslint/rules/documentation/glossary-term-linking.ts
|
|
5223
|
-
import { readFileSync as
|
|
5224
|
-
import
|
|
5920
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
5921
|
+
import path52 from "path";
|
|
5922
|
+
function isGlossary(filePath) {
|
|
5923
|
+
return path52.basename(filePath).includes("glossary");
|
|
5924
|
+
}
|
|
5925
|
+
function termNames(cell) {
|
|
5926
|
+
const abbreviation = /\((?<abbreviation>[^()]+)\)/.exec(cell)?.groups?.abbreviation?.trim();
|
|
5927
|
+
return abbreviation ? [cell, abbreviation] : [cell];
|
|
5928
|
+
}
|
|
5929
|
+
function extractTableTerms(content) {
|
|
5930
|
+
const terms = [];
|
|
5931
|
+
const rowPattern = /^\|(?<cell>[^|]*)\|/gm;
|
|
5932
|
+
for (const match of content.matchAll(rowPattern)) {
|
|
5933
|
+
const cell = match.groups?.cell?.trim() ?? "";
|
|
5934
|
+
if (!cell || /^:?-+:?$/.test(cell) || cell.toLowerCase() === "term") continue;
|
|
5935
|
+
terms.push({ term: cell, names: termNames(cell) });
|
|
5936
|
+
}
|
|
5937
|
+
return terms;
|
|
5938
|
+
}
|
|
5225
5939
|
function extractGlossaryTerms(filePath) {
|
|
5226
|
-
|
|
5940
|
+
if (!isGlossary(filePath)) return [];
|
|
5941
|
+
const content = readFileSync9(filePath, "utf8");
|
|
5942
|
+
const tableTerms = extractTableTerms(content);
|
|
5943
|
+
if (tableTerms.length > 0) return tableTerms;
|
|
5227
5944
|
const terms = [];
|
|
5228
5945
|
const headingPattern = /^## (?<term>.+)$/gm;
|
|
5229
|
-
|
|
5230
|
-
while ((match = headingPattern.exec(content)) !== null) {
|
|
5946
|
+
for (const match of content.matchAll(headingPattern)) {
|
|
5231
5947
|
const term = match.groups?.term?.trim();
|
|
5232
|
-
if (term) terms.push(term);
|
|
5948
|
+
if (term) terms.push({ term, names: termNames(term) });
|
|
5233
5949
|
}
|
|
5234
5950
|
return terms;
|
|
5235
5951
|
}
|
|
5236
|
-
function
|
|
5237
|
-
const
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
collectDocLinks(item, firstStepLinks);
|
|
5244
|
-
}
|
|
5952
|
+
function extractBlockHeadings(filePath) {
|
|
5953
|
+
const headings = [];
|
|
5954
|
+
let fenced = false;
|
|
5955
|
+
for (const line of readFileSync9(filePath, "utf8").split("\n")) {
|
|
5956
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
5957
|
+
fenced = !fenced;
|
|
5958
|
+
continue;
|
|
5245
5959
|
}
|
|
5960
|
+
if (fenced) continue;
|
|
5961
|
+
const heading = /^##\s+(?<text>.+)$/.exec(line)?.groups?.text;
|
|
5962
|
+
if (heading) headings.push(heading.trim());
|
|
5246
5963
|
}
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5964
|
+
return headings;
|
|
5965
|
+
}
|
|
5966
|
+
function normalize(text) {
|
|
5967
|
+
return text.replaceAll("`", "").toLowerCase();
|
|
5968
|
+
}
|
|
5969
|
+
function collectSections(node) {
|
|
5970
|
+
const sections = [];
|
|
5971
|
+
const children = node.children;
|
|
5972
|
+
for (const [index, child] of children.entries()) {
|
|
5973
|
+
if (child.type !== "heading" || child.depth !== 2) continue;
|
|
5974
|
+
const title = getTextContent(child).trim();
|
|
5975
|
+
if (!/^How To \S/.test(title)) continue;
|
|
5976
|
+
const end = children.findIndex(
|
|
5977
|
+
(sibling, siblingIndex) => siblingIndex > index && sibling.type === "heading" && sibling.depth === 2
|
|
5978
|
+
);
|
|
5979
|
+
const section = { heading: child, title, stepTexts: [], firstStepLinks: [] };
|
|
5980
|
+
collectSteps(children.slice(index + 1, end === -1 ? children.length : end), section);
|
|
5981
|
+
sections.push(section);
|
|
5982
|
+
}
|
|
5983
|
+
return sections;
|
|
5984
|
+
}
|
|
5985
|
+
function collectSteps(content, section) {
|
|
5986
|
+
const lists = content.filter((node) => node.type === "list");
|
|
5987
|
+
const paragraphs = content.filter((node) => node.type === "paragraph");
|
|
5988
|
+
const steps = lists.length > 0 ? lists.flatMap((list) => list.children) : paragraphs.slice(content[0]?.type === "paragraph" ? 1 : 0);
|
|
5989
|
+
for (const step of steps) {
|
|
5990
|
+
section.stepTexts.push(getTextContent(step));
|
|
5991
|
+
if (section.stepTexts.length === 1) collectDocLinks(step, section.firstStepLinks);
|
|
5253
5992
|
}
|
|
5254
|
-
return { texts, firstStepLinks };
|
|
5255
5993
|
}
|
|
5256
5994
|
function collectDocLinks(node, out) {
|
|
5257
|
-
if (node.type === "link" && node.url
|
|
5995
|
+
if (node.type === "link" && isDocLink(node.url)) out.push(node.url);
|
|
5258
5996
|
if ("children" in node) {
|
|
5259
5997
|
for (const child of node.children) collectDocLinks(child, out);
|
|
5260
5998
|
}
|
|
@@ -5263,7 +6001,7 @@ var glossaryTermLinkingRule = {
|
|
|
5263
6001
|
meta: {
|
|
5264
6002
|
type: "problem",
|
|
5265
6003
|
docs: {
|
|
5266
|
-
description: "A
|
|
6004
|
+
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.",
|
|
5267
6005
|
recommended: true
|
|
5268
6006
|
}
|
|
5269
6007
|
},
|
|
@@ -5275,25 +6013,55 @@ var glossaryTermLinkingRule = {
|
|
|
5275
6013
|
const docsRoot = findDocsRoot(filename);
|
|
5276
6014
|
if (!docsRoot) return;
|
|
5277
6015
|
const docs = getProjectDocs(docsRoot);
|
|
5278
|
-
const guideDir =
|
|
6016
|
+
const guideDir = path52.dirname(filename);
|
|
6017
|
+
const referencesDir = path52.join(guideDir, "references");
|
|
5279
6018
|
const guideReferences = docs.filter(
|
|
5280
|
-
(doc) => doc.kind === "reference" &&
|
|
6019
|
+
(doc) => doc.kind === "reference" && path52.dirname(doc.filePath) === referencesDir
|
|
5281
6020
|
);
|
|
5282
|
-
|
|
6021
|
+
const glossaryReferences = guideReferences.filter((doc) => isGlossary(doc.filePath));
|
|
6022
|
+
if (glossaryReferences.length === 0) return;
|
|
5283
6023
|
const glossaryTerms = [];
|
|
5284
|
-
for (const ref of
|
|
6024
|
+
for (const ref of glossaryReferences) {
|
|
5285
6025
|
glossaryTerms.push(...extractGlossaryTerms(ref.filePath));
|
|
5286
6026
|
}
|
|
5287
6027
|
if (glossaryTerms.length === 0) return;
|
|
5288
|
-
const
|
|
5289
|
-
const
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
6028
|
+
const sections = collectSections(node);
|
|
6029
|
+
for (const ref of glossaryReferences) {
|
|
6030
|
+
for (const heading of extractBlockHeadings(ref.filePath)) {
|
|
6031
|
+
const anchor = headingAnchor(heading);
|
|
6032
|
+
const anchored = sections.some(
|
|
6033
|
+
(section) => section.firstStepLinks.some((link) => {
|
|
6034
|
+
const fragment = link.split("#")[1] ?? "";
|
|
6035
|
+
if (fragment.toLowerCase() !== anchor) return false;
|
|
6036
|
+
return path52.resolve(guideDir, linkTarget(link)) === path52.resolve(ref.filePath);
|
|
6037
|
+
})
|
|
6038
|
+
);
|
|
6039
|
+
if (!anchored) {
|
|
6040
|
+
context.report({
|
|
6041
|
+
node,
|
|
6042
|
+
message: `shared glossary block "${heading}" is not read by any How To section's first step`
|
|
6043
|
+
});
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
6047
|
+
for (const section of sections) {
|
|
6048
|
+
const normalizedStepTexts = section.stepTexts.map(normalize);
|
|
6049
|
+
const usedTerms = glossaryTerms.filter(
|
|
6050
|
+
(entry) => entry.names.some((name) => {
|
|
6051
|
+
const needle = normalize(name);
|
|
6052
|
+
return normalizedStepTexts.some((text) => text.includes(needle));
|
|
6053
|
+
})
|
|
6054
|
+
).map((entry) => entry.term);
|
|
6055
|
+
if (usedTerms.length === 0) continue;
|
|
6056
|
+
const hasGlossaryLink = section.firstStepLinks.some(
|
|
6057
|
+
(link) => glossaryReferences.some((ref) => link.includes(ref.fileName))
|
|
6058
|
+
);
|
|
6059
|
+
if (!hasGlossaryLink) {
|
|
6060
|
+
context.report({
|
|
6061
|
+
node: section.heading,
|
|
6062
|
+
message: `guide section "${section.title}" uses glossary terms (${usedTerms.join(", ")}) but its first step does not link the glossary reference`
|
|
6063
|
+
});
|
|
6064
|
+
}
|
|
5297
6065
|
}
|
|
5298
6066
|
}
|
|
5299
6067
|
};
|
|
@@ -5301,8 +6069,8 @@ var glossaryTermLinkingRule = {
|
|
|
5301
6069
|
};
|
|
5302
6070
|
|
|
5303
6071
|
// eslint/rules/documentation/guide-mentions-documents.ts
|
|
5304
|
-
import { existsSync as
|
|
5305
|
-
import
|
|
6072
|
+
import { existsSync as existsSync4 } from "fs";
|
|
6073
|
+
import path53 from "path";
|
|
5306
6074
|
function visitSteps3(node, check) {
|
|
5307
6075
|
if (node.type === "list" && node.ordered) {
|
|
5308
6076
|
for (const child of node.children) check(child);
|
|
@@ -5312,14 +6080,11 @@ function visitSteps3(node, check) {
|
|
|
5312
6080
|
}
|
|
5313
6081
|
}
|
|
5314
6082
|
function collectMarkdownLinks(node, out) {
|
|
5315
|
-
if (node.type === "link" && node.url
|
|
6083
|
+
if (node.type === "link" && isDocLink(node.url)) out.push(node);
|
|
5316
6084
|
if ("children" in node) {
|
|
5317
6085
|
for (const child of node.children) collectMarkdownLinks(child, out);
|
|
5318
6086
|
}
|
|
5319
6087
|
}
|
|
5320
|
-
function linkTarget(url) {
|
|
5321
|
-
return url.split("#")[0] ?? url;
|
|
5322
|
-
}
|
|
5323
6088
|
var guideMentionsDocumentsRule = {
|
|
5324
6089
|
meta: {
|
|
5325
6090
|
type: "problem",
|
|
@@ -5335,12 +6100,12 @@ var guideMentionsDocumentsRule = {
|
|
|
5335
6100
|
if (!filename.endsWith("-guide.md")) return;
|
|
5336
6101
|
const docsRoot = findDocsRoot(filename);
|
|
5337
6102
|
if (!docsRoot) return;
|
|
5338
|
-
const guideDir =
|
|
5339
|
-
if (
|
|
6103
|
+
const guideDir = path53.dirname(filename);
|
|
6104
|
+
if (path53.basename(filename, ".md") !== path53.basename(guideDir)) return;
|
|
5340
6105
|
const docs = getProjectDocs(docsRoot);
|
|
5341
6106
|
const owned = docs.filter((doc) => {
|
|
5342
|
-
const parent =
|
|
5343
|
-
return parent ===
|
|
6107
|
+
const parent = path53.dirname(doc.filePath);
|
|
6108
|
+
return parent === path53.join(guideDir, "rules") || parent === path53.join(guideDir, "references");
|
|
5344
6109
|
});
|
|
5345
6110
|
const allLinks = [];
|
|
5346
6111
|
collectMarkdownLinks(node, allLinks);
|
|
@@ -5348,7 +6113,7 @@ var guideMentionsDocumentsRule = {
|
|
|
5348
6113
|
visitSteps3(node, (item) => {
|
|
5349
6114
|
collectMarkdownLinks(item, stepLinks);
|
|
5350
6115
|
});
|
|
5351
|
-
const mentionsOf = (links, fileName) => links.some((link) => link.url.split("/").pop() === fileName);
|
|
6116
|
+
const mentionsOf = (links, fileName) => links.some((link) => linkTarget(link.url).split("/").pop() === fileName);
|
|
5352
6117
|
for (const doc of owned) {
|
|
5353
6118
|
if (doc.kind === "rule") {
|
|
5354
6119
|
if (!mentionsOf(stepLinks, doc.fileName)) {
|
|
@@ -5367,8 +6132,8 @@ var guideMentionsDocumentsRule = {
|
|
|
5367
6132
|
for (const link of allLinks) {
|
|
5368
6133
|
const target = linkTarget(link.url);
|
|
5369
6134
|
if (!target.endsWith(".md")) continue;
|
|
5370
|
-
const resolved =
|
|
5371
|
-
if (!
|
|
6135
|
+
const resolved = path53.normalize(path53.join(guideDir, target));
|
|
6136
|
+
if (!existsSync4(resolved)) {
|
|
5372
6137
|
context.report({
|
|
5373
6138
|
node: link,
|
|
5374
6139
|
message: `Guide links a document that does not exist: ${link.url}`
|
|
@@ -5445,6 +6210,7 @@ var documentationRules = {
|
|
|
5445
6210
|
"no-cross-document-link": noCrossDocumentLinkRule,
|
|
5446
6211
|
"reference-no-rfc-vocabulary": referenceNoRfcVocabularyRule,
|
|
5447
6212
|
"reference-block-headings": referenceBlockHeadingsRule,
|
|
6213
|
+
"reference-max-heading-depth": referenceMaxHeadingDepthRule,
|
|
5448
6214
|
"support-document-placement": supportDocumentPlacementRule,
|
|
5449
6215
|
"no-template-prompt": noTemplatePromptRule,
|
|
5450
6216
|
"guide-folder-entry-point": guideFolderEntryPointRule,
|
|
@@ -5917,11 +6683,11 @@ var themeVariableNamespaceRule = {
|
|
|
5917
6683
|
|
|
5918
6684
|
// eslint/rules/tailwind/css-entry-point.ts
|
|
5919
6685
|
import { statSync as statSync6 } from "fs";
|
|
5920
|
-
import
|
|
6686
|
+
import path56 from "path";
|
|
5921
6687
|
|
|
5922
6688
|
// eslint/rules/tailwind/source-files.ts
|
|
5923
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
5924
|
-
import
|
|
6689
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync5 } from "fs";
|
|
6690
|
+
import path54 from "path";
|
|
5925
6691
|
var CSS_EXTENSIONS = [".css"];
|
|
5926
6692
|
var MODULE_EXTENSIONS3 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
5927
6693
|
var SOURCE_EXTENSIONS = [...MODULE_EXTENSIONS3, ...CSS_EXTENSIONS];
|
|
@@ -5934,7 +6700,7 @@ function findFiles(dir, extensions) {
|
|
|
5934
6700
|
}
|
|
5935
6701
|
return entries.flatMap((entry) => {
|
|
5936
6702
|
if (entry.startsWith(".") || entry === "node_modules") return [];
|
|
5937
|
-
const entryPath =
|
|
6703
|
+
const entryPath = path54.join(dir, entry);
|
|
5938
6704
|
let stats;
|
|
5939
6705
|
try {
|
|
5940
6706
|
stats = statSync5(entryPath);
|
|
@@ -5942,7 +6708,7 @@ function findFiles(dir, extensions) {
|
|
|
5942
6708
|
return [];
|
|
5943
6709
|
}
|
|
5944
6710
|
if (stats.isDirectory()) return findFiles(entryPath, extensions);
|
|
5945
|
-
return extensions.includes(
|
|
6711
|
+
return extensions.includes(path54.extname(entry)) ? [entryPath] : [];
|
|
5946
6712
|
});
|
|
5947
6713
|
}
|
|
5948
6714
|
function cachedTextReader() {
|
|
@@ -5951,7 +6717,7 @@ function cachedTextReader() {
|
|
|
5951
6717
|
let text = texts.get(file);
|
|
5952
6718
|
if (text === void 0) {
|
|
5953
6719
|
try {
|
|
5954
|
-
text =
|
|
6720
|
+
text = readFileSync10(file, "utf8");
|
|
5955
6721
|
} catch {
|
|
5956
6722
|
text = "";
|
|
5957
6723
|
}
|
|
@@ -5965,7 +6731,7 @@ function escapeRegExp(text) {
|
|
|
5965
6731
|
}
|
|
5966
6732
|
|
|
5967
6733
|
// eslint/rules/tailwind/stylesheet-graph.ts
|
|
5968
|
-
import
|
|
6734
|
+
import path55 from "path";
|
|
5969
6735
|
function registersTailwind(text) {
|
|
5970
6736
|
return /@import\s+(?:url\(\s*)?["']tailwindcss["']\s*\)?/i.test(text);
|
|
5971
6737
|
}
|
|
@@ -5979,25 +6745,25 @@ function moduleImports(text, fileName) {
|
|
|
5979
6745
|
return new RegExp(`(?:import|require)\\s*\\(?\\s*["'][^"']*${escaped}["']`, "i").test(text);
|
|
5980
6746
|
}
|
|
5981
6747
|
function resolveSpecifier2(fromFile, spec, sourceRoot) {
|
|
5982
|
-
if (spec.startsWith("/")) return
|
|
5983
|
-
if (spec.startsWith("./") || spec.startsWith("../")) return
|
|
5984
|
-
if (spec.startsWith("@/")) return
|
|
6748
|
+
if (spec.startsWith("/")) return path55.resolve(spec);
|
|
6749
|
+
if (spec.startsWith("./") || spec.startsWith("../")) return path55.resolve(path55.dirname(fromFile), spec);
|
|
6750
|
+
if (spec.startsWith("@/")) return path55.resolve(sourceRoot, spec.slice(2));
|
|
5985
6751
|
return void 0;
|
|
5986
6752
|
}
|
|
5987
6753
|
function buildStylesheetGraph(options) {
|
|
5988
6754
|
const { cssFiles, sourceRoot, textOf } = options;
|
|
5989
|
-
const cssSet = new Set(cssFiles.map((file) =>
|
|
6755
|
+
const cssSet = new Set(cssFiles.map((file) => path55.normalize(file)));
|
|
5990
6756
|
const globals = cssFiles.filter((file) => registersTailwind(textOf(file)));
|
|
5991
6757
|
const reachable = /* @__PURE__ */ new Set();
|
|
5992
6758
|
const queue = [...globals];
|
|
5993
|
-
for (const global of globals) reachable.add(
|
|
6759
|
+
for (const global of globals) reachable.add(path55.normalize(global));
|
|
5994
6760
|
while (queue.length > 0) {
|
|
5995
6761
|
const from = queue.shift();
|
|
5996
6762
|
if (!from) continue;
|
|
5997
6763
|
for (const spec of importedSpecifiers(textOf(from))) {
|
|
5998
6764
|
const target = resolveSpecifier2(from, spec, sourceRoot);
|
|
5999
6765
|
if (!target) continue;
|
|
6000
|
-
const normalized =
|
|
6766
|
+
const normalized = path55.normalize(target);
|
|
6001
6767
|
if (cssSet.has(normalized) && !reachable.has(normalized)) {
|
|
6002
6768
|
reachable.add(normalized);
|
|
6003
6769
|
queue.push(normalized);
|
|
@@ -6009,7 +6775,7 @@ function buildStylesheetGraph(options) {
|
|
|
6009
6775
|
for (const spec of importedSpecifiers(textOf(global))) {
|
|
6010
6776
|
const target = resolveSpecifier2(global, spec, sourceRoot);
|
|
6011
6777
|
if (!target) continue;
|
|
6012
|
-
const normalized =
|
|
6778
|
+
const normalized = path55.normalize(target);
|
|
6013
6779
|
if (cssSet.has(normalized)) directChildren.add(normalized);
|
|
6014
6780
|
}
|
|
6015
6781
|
}
|
|
@@ -6042,7 +6808,7 @@ var cssEntryPointRule = {
|
|
|
6042
6808
|
return {
|
|
6043
6809
|
"StyleSheet:exit"(node) {
|
|
6044
6810
|
if (globals.length === 0) return;
|
|
6045
|
-
const current =
|
|
6811
|
+
const current = path56.normalize(path56.resolve(context.filename));
|
|
6046
6812
|
if (globals.includes(current)) {
|
|
6047
6813
|
if (globals.length > 1) {
|
|
6048
6814
|
context.report({
|
|
@@ -6051,7 +6817,7 @@ var cssEntryPointRule = {
|
|
|
6051
6817
|
});
|
|
6052
6818
|
return;
|
|
6053
6819
|
}
|
|
6054
|
-
const basename =
|
|
6820
|
+
const basename = path56.basename(current);
|
|
6055
6821
|
const importCount = moduleFiles.filter((modulePath) => moduleImports(textOf(modulePath), basename)).length;
|
|
6056
6822
|
if (importCount !== 1) {
|
|
6057
6823
|
context.report({
|
|
@@ -6379,8 +7145,8 @@ var nextjsStackRule = {
|
|
|
6379
7145
|
};
|
|
6380
7146
|
|
|
6381
7147
|
// eslint/rules/package-json/vitest-coverage.ts
|
|
6382
|
-
import { existsSync as
|
|
6383
|
-
import
|
|
7148
|
+
import { existsSync as existsSync5, readFileSync as readFileSync11 } from "fs";
|
|
7149
|
+
import path57 from "path";
|
|
6384
7150
|
function memberName4(member) {
|
|
6385
7151
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
6386
7152
|
}
|
|
@@ -6449,7 +7215,7 @@ var vitestCoverageRule = {
|
|
|
6449
7215
|
message: 'package.json must declare a "test:unit:coverage" script that runs Vitest with coverage.'
|
|
6450
7216
|
});
|
|
6451
7217
|
}
|
|
6452
|
-
const configName = VITEST_CONFIG_NAMES.find((name) =>
|
|
7218
|
+
const configName = VITEST_CONFIG_NAMES.find((name) => existsSync5(path57.join(context.cwd, name)));
|
|
6453
7219
|
if (!configName) {
|
|
6454
7220
|
context.report({
|
|
6455
7221
|
node,
|
|
@@ -6457,7 +7223,7 @@ var vitestCoverageRule = {
|
|
|
6457
7223
|
});
|
|
6458
7224
|
return;
|
|
6459
7225
|
}
|
|
6460
|
-
const content =
|
|
7226
|
+
const content = readFileSync11(path57.join(context.cwd, configName), "utf8");
|
|
6461
7227
|
for (const metric of THRESHOLD_METRICS) {
|
|
6462
7228
|
if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
|
|
6463
7229
|
context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
|
|
@@ -6503,8 +7269,8 @@ var nextjsPackageJsonRules = {
|
|
|
6503
7269
|
};
|
|
6504
7270
|
|
|
6505
7271
|
// eslint/rules/husky/husky-hook.ts
|
|
6506
|
-
import { existsSync as
|
|
6507
|
-
import
|
|
7272
|
+
import { existsSync as existsSync6, readFileSync as readFileSync12 } from "fs";
|
|
7273
|
+
import path58 from "path";
|
|
6508
7274
|
var VITEST_CONFIG_NAMES2 = [
|
|
6509
7275
|
"vitest.config.ts",
|
|
6510
7276
|
"vitest.config.mts",
|
|
@@ -6530,15 +7296,15 @@ var huskyHookRule = {
|
|
|
6530
7296
|
const root = node.body;
|
|
6531
7297
|
if (root.type !== "Object") return;
|
|
6532
7298
|
if (!context.filename.endsWith("package.json")) return;
|
|
6533
|
-
const hookPath =
|
|
6534
|
-
if (!
|
|
7299
|
+
const hookPath = path58.join(context.cwd, ".husky", "pre-commit");
|
|
7300
|
+
if (!existsSync6(hookPath)) {
|
|
6535
7301
|
context.report({
|
|
6536
7302
|
node,
|
|
6537
7303
|
message: "No .husky/pre-commit hook found. Configure husky to run checks before commits."
|
|
6538
7304
|
});
|
|
6539
7305
|
return;
|
|
6540
7306
|
}
|
|
6541
|
-
const content =
|
|
7307
|
+
const content = readFileSync12(hookPath, "utf8");
|
|
6542
7308
|
const scripts = root.members.find((member) => memberName5(member) === "scripts");
|
|
6543
7309
|
const scriptNames = new Set(scripts?.value.type === "Object" ? scripts.value.members.map(memberName5) : []);
|
|
6544
7310
|
const requireNamedScript = (name) => {
|
|
@@ -6554,7 +7320,7 @@ var huskyHookRule = {
|
|
|
6554
7320
|
}
|
|
6555
7321
|
requireNamedScript("typecheck");
|
|
6556
7322
|
requireNamedScript("test:unit:coverage");
|
|
6557
|
-
const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) =>
|
|
7323
|
+
const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) => existsSync6(path58.join(context.cwd, name)));
|
|
6558
7324
|
if (vitestConfigName !== void 0) {
|
|
6559
7325
|
const coverageIndex = content.indexOf("npm run test:unit:coverage");
|
|
6560
7326
|
const localAddIndex = content.indexOf(`git add ${vitestConfigName}`);
|
|
@@ -6568,8 +7334,8 @@ var huskyHookRule = {
|
|
|
6568
7334
|
if (!content.includes("libyear --limit-major-individual=1")) {
|
|
6569
7335
|
context.report({ node, message: ".husky/pre-commit must run npx libyear --limit-major-individual=1." });
|
|
6570
7336
|
}
|
|
6571
|
-
const suppressionsPath =
|
|
6572
|
-
if (
|
|
7337
|
+
const suppressionsPath = path58.join(context.cwd, "eslint-suppressions.json");
|
|
7338
|
+
if (existsSync6(suppressionsPath)) {
|
|
6573
7339
|
requireNamedScript("lint:prune");
|
|
6574
7340
|
const pruneIndex = content.indexOf("npm run lint:prune");
|
|
6575
7341
|
const localAddIndex = content.indexOf("git add eslint-suppressions.json");
|
|
@@ -6599,73 +7365,33 @@ var huskyRules = {
|
|
|
6599
7365
|
"husky-hook": huskyHookRule
|
|
6600
7366
|
};
|
|
6601
7367
|
|
|
6602
|
-
// eslint/rules/vulyk/
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
}
|
|
6606
|
-
function dependency(root, sectionName) {
|
|
6607
|
-
if (root.type !== "Object") return void 0;
|
|
6608
|
-
const section = root.members.find((member) => memberName6(member) === sectionName);
|
|
6609
|
-
if (section?.value.type !== "Object") return void 0;
|
|
6610
|
-
return section.value.members.find((member) => memberName6(member) === "vulyk");
|
|
6611
|
-
}
|
|
6612
|
-
var vulykDependencyRule = {
|
|
6613
|
-
meta: {
|
|
6614
|
-
schema: [],
|
|
6615
|
-
type: "problem",
|
|
6616
|
-
docs: {
|
|
6617
|
-
description: "Require vulyk in devDependencies so its typed config and CLI use the pinned package."
|
|
6618
|
-
}
|
|
6619
|
-
},
|
|
6620
|
-
create(context) {
|
|
6621
|
-
return {
|
|
6622
|
-
Document(node) {
|
|
6623
|
-
const runtimeDependency = dependency(node.body, "dependencies");
|
|
6624
|
-
const developmentDependency = dependency(node.body, "devDependencies");
|
|
6625
|
-
if (runtimeDependency) {
|
|
6626
|
-
context.report({
|
|
6627
|
-
node: runtimeDependency,
|
|
6628
|
-
message: "vulyk must be listed in devDependencies, not dependencies."
|
|
6629
|
-
});
|
|
6630
|
-
}
|
|
6631
|
-
if (!developmentDependency && !runtimeDependency) {
|
|
6632
|
-
context.report({
|
|
6633
|
-
node,
|
|
6634
|
-
message: "vulyk must be listed in package.json as a devDependency."
|
|
6635
|
-
});
|
|
6636
|
-
}
|
|
6637
|
-
}
|
|
6638
|
-
};
|
|
6639
|
-
}
|
|
6640
|
-
};
|
|
6641
|
-
|
|
6642
|
-
// eslint/rules/vulyk/vulyk-docs.ts
|
|
6643
|
-
import { existsSync as existsSync6, readFileSync as readFileSync11 } from "fs";
|
|
6644
|
-
import path53 from "path";
|
|
7368
|
+
// eslint/rules/vulyk/tracked-docs.ts
|
|
7369
|
+
import { existsSync as existsSync7, readFileSync as readFileSync13 } from "fs";
|
|
7370
|
+
import path59 from "path";
|
|
6645
7371
|
var PASIKA_REPO = "Bredansky/pasika";
|
|
6646
|
-
var
|
|
7372
|
+
var BASE_REQUIRED_TRACKED_DOCS = [
|
|
6647
7373
|
{ name: "documentation-guide", path: "docs/documentation-guide" },
|
|
6648
7374
|
{ name: "pasika-adoption-guide", path: "docs/pasika-adoption-guide" },
|
|
6649
7375
|
{ name: "repository-policy", path: "docs/repository-policy.md" }
|
|
6650
7376
|
];
|
|
6651
|
-
var
|
|
7377
|
+
var NEXTJS_REQUIRED_TRACKED_DOCS = [
|
|
6652
7378
|
{ name: "next-codebase-guide", path: "docs/next-codebase-guide" },
|
|
6653
7379
|
{ name: "next-tailwind-guide", path: "docs/next-tailwind-guide" }
|
|
6654
7380
|
];
|
|
6655
|
-
function
|
|
7381
|
+
function memberName6(member) {
|
|
6656
7382
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
6657
7383
|
}
|
|
6658
7384
|
function hasDependency(root, name) {
|
|
6659
|
-
const section = root.members.find((member) =>
|
|
7385
|
+
const section = root.members.find((member) => memberName6(member) === "dependencies");
|
|
6660
7386
|
if (section?.value.type !== "Object") return false;
|
|
6661
|
-
return section.value.members.some((member) =>
|
|
7387
|
+
return section.value.members.some((member) => memberName6(member) === name);
|
|
6662
7388
|
}
|
|
6663
|
-
var
|
|
7389
|
+
var trackedDocsRule = {
|
|
6664
7390
|
meta: {
|
|
6665
7391
|
schema: [],
|
|
6666
7392
|
type: "problem",
|
|
6667
7393
|
docs: {
|
|
6668
|
-
description: "Require vulyk.config.ts to track the framework's required docs from pasika and the generated AGENTS.md."
|
|
7394
|
+
description: "Require vulyk.config.ts to track the framework's required tracked docs from pasika and the generated AGENTS.md."
|
|
6669
7395
|
}
|
|
6670
7396
|
},
|
|
6671
7397
|
create(context) {
|
|
@@ -6674,34 +7400,34 @@ var vulykDocsRule = {
|
|
|
6674
7400
|
if (!context.filename.endsWith("package.json")) return;
|
|
6675
7401
|
const root = node.body;
|
|
6676
7402
|
if (root.type !== "Object") return;
|
|
6677
|
-
const projectRoot =
|
|
6678
|
-
const configPath =
|
|
6679
|
-
if (!
|
|
7403
|
+
const projectRoot = path59.dirname(path59.resolve(context.filename));
|
|
7404
|
+
const configPath = path59.join(projectRoot, "vulyk.config.ts");
|
|
7405
|
+
if (!existsSync7(configPath)) {
|
|
6680
7406
|
context.report({
|
|
6681
7407
|
node,
|
|
6682
|
-
message: "No vulyk.config.ts found. Run npx vulyk init to create one that tracks the framework's docs."
|
|
7408
|
+
message: "No vulyk.config.ts found. Run npx vulyk init to create one that tracks the framework's required tracked docs."
|
|
6683
7409
|
});
|
|
6684
7410
|
return;
|
|
6685
7411
|
}
|
|
6686
|
-
const config =
|
|
7412
|
+
const config = readFileSync13(configPath, "utf8");
|
|
6687
7413
|
if (!config.includes(PASIKA_REPO)) {
|
|
6688
7414
|
context.report({
|
|
6689
7415
|
node,
|
|
6690
|
-
message: "vulyk.config.ts must track the framework's docs from the pasika repository."
|
|
7416
|
+
message: "vulyk.config.ts must track the framework's required tracked docs from the pasika repository."
|
|
6691
7417
|
});
|
|
6692
7418
|
return;
|
|
6693
7419
|
}
|
|
6694
|
-
const
|
|
6695
|
-
for (const
|
|
6696
|
-
if (!config.includes(
|
|
7420
|
+
const requiredTrackedDocs = hasDependency(root, "next") ? [...BASE_REQUIRED_TRACKED_DOCS, ...NEXTJS_REQUIRED_TRACKED_DOCS] : BASE_REQUIRED_TRACKED_DOCS;
|
|
7421
|
+
for (const trackedDoc of requiredTrackedDocs) {
|
|
7422
|
+
if (!config.includes(trackedDoc.path)) {
|
|
6697
7423
|
context.report({
|
|
6698
7424
|
node,
|
|
6699
|
-
message: `vulyk.config.ts must track the framework's ${
|
|
7425
|
+
message: `vulyk.config.ts must track the framework's ${trackedDoc.name} tracked docs from pasika (${PASIKA_REPO}/${trackedDoc.path}).`
|
|
6700
7426
|
});
|
|
6701
7427
|
}
|
|
6702
7428
|
}
|
|
6703
|
-
const agentsPath =
|
|
6704
|
-
if (!
|
|
7429
|
+
const agentsPath = path59.join(projectRoot, "AGENTS.md");
|
|
7430
|
+
if (!existsSync7(agentsPath)) {
|
|
6705
7431
|
context.report({
|
|
6706
7432
|
node,
|
|
6707
7433
|
message: "No AGENTS.md found. Run npx vulyk agents to generate the agent file that routes to the tracked docs."
|
|
@@ -6712,10 +7438,50 @@ var vulykDocsRule = {
|
|
|
6712
7438
|
}
|
|
6713
7439
|
};
|
|
6714
7440
|
|
|
7441
|
+
// eslint/rules/vulyk/vulyk-dependency.ts
|
|
7442
|
+
function memberName7(member) {
|
|
7443
|
+
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
7444
|
+
}
|
|
7445
|
+
function dependency(root, sectionName) {
|
|
7446
|
+
if (root.type !== "Object") return void 0;
|
|
7447
|
+
const section = root.members.find((member) => memberName7(member) === sectionName);
|
|
7448
|
+
if (section?.value.type !== "Object") return void 0;
|
|
7449
|
+
return section.value.members.find((member) => memberName7(member) === "vulyk");
|
|
7450
|
+
}
|
|
7451
|
+
var vulykDependencyRule = {
|
|
7452
|
+
meta: {
|
|
7453
|
+
schema: [],
|
|
7454
|
+
type: "problem",
|
|
7455
|
+
docs: {
|
|
7456
|
+
description: "Require vulyk in devDependencies so its typed config and CLI use the pinned package."
|
|
7457
|
+
}
|
|
7458
|
+
},
|
|
7459
|
+
create(context) {
|
|
7460
|
+
return {
|
|
7461
|
+
Document(node) {
|
|
7462
|
+
const runtimeDependency = dependency(node.body, "dependencies");
|
|
7463
|
+
const developmentDependency = dependency(node.body, "devDependencies");
|
|
7464
|
+
if (runtimeDependency) {
|
|
7465
|
+
context.report({
|
|
7466
|
+
node: runtimeDependency,
|
|
7467
|
+
message: "vulyk must be listed in devDependencies, not dependencies."
|
|
7468
|
+
});
|
|
7469
|
+
}
|
|
7470
|
+
if (!developmentDependency && !runtimeDependency) {
|
|
7471
|
+
context.report({
|
|
7472
|
+
node,
|
|
7473
|
+
message: "vulyk must be listed in package.json as a devDependency."
|
|
7474
|
+
});
|
|
7475
|
+
}
|
|
7476
|
+
}
|
|
7477
|
+
};
|
|
7478
|
+
}
|
|
7479
|
+
};
|
|
7480
|
+
|
|
6715
7481
|
// eslint/rules/vulyk/index.ts
|
|
6716
7482
|
var vulykRules = {
|
|
6717
7483
|
"vulyk-dependency": vulykDependencyRule,
|
|
6718
|
-
"
|
|
7484
|
+
"tracked-docs": trackedDocsRule
|
|
6719
7485
|
};
|
|
6720
7486
|
|
|
6721
7487
|
// eslint/index.ts
|
|
@@ -6757,6 +7523,10 @@ var pasikaNextjsAppRules = {
|
|
|
6757
7523
|
"cva-boolean-variants": cvaBooleanVariantsRule,
|
|
6758
7524
|
"cross-feature-import": crossFeatureImportRule,
|
|
6759
7525
|
"pure-function-extract": pureFunctionExtractRule,
|
|
7526
|
+
"root-support-placement": rootSupportPlacementRule,
|
|
7527
|
+
"route-handler-shape": routeHandlerShapeRule,
|
|
7528
|
+
"http-error-usage": httpErrorUsageRule,
|
|
7529
|
+
"with-response-helper": withResponseHelperRule,
|
|
6760
7530
|
"hook-complexity": hookComplexityRule,
|
|
6761
7531
|
"locale-dotted-path": localeDottedPathRule,
|
|
6762
7532
|
"locales-location": localesLocationRule,
|
|
@@ -6861,6 +7631,11 @@ var zirkaConfig = {
|
|
|
6861
7631
|
plugins: { pasika: pasikaPlugin },
|
|
6862
7632
|
rules: { "pasika/zirka-baseline": "error" }
|
|
6863
7633
|
};
|
|
7634
|
+
var nextjsHelperConfig = {
|
|
7635
|
+
files: ["eslint.config.{ts,mts,cts,js,mjs,cjs}"],
|
|
7636
|
+
plugins: { pasika: pasikaPlugin },
|
|
7637
|
+
rules: { "pasika/cn-helper": "error", "pasika/with-response-helper": "error" }
|
|
7638
|
+
};
|
|
6864
7639
|
var documentationConfig = {
|
|
6865
7640
|
files: ["docs/**/*.md"],
|
|
6866
7641
|
// vulyk-generated agent files are not authored docs: with per-directory
|
|
@@ -6890,6 +7665,7 @@ var pasikaNextjsAppWithDiagnostic = (preset) => {
|
|
|
6890
7665
|
var pasikaNextjsApp = pasikaNextjsAppWithDiagnostic([
|
|
6891
7666
|
...pasikaApp,
|
|
6892
7667
|
pasikaNextjsAppPackageJsonConfig,
|
|
7668
|
+
nextjsHelperConfig,
|
|
6893
7669
|
pasikaNextjsAppConfig,
|
|
6894
7670
|
tailwindStructureRules,
|
|
6895
7671
|
tailwindImportGraph
|