pasika 0.9.1 → 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 +1139 -388
- 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,7 @@ var crossFeatureImportRule = {
|
|
|
2902
2950
|
};
|
|
2903
2951
|
|
|
2904
2952
|
// eslint/rules/pure-function-extract.ts
|
|
2905
|
-
import
|
|
2953
|
+
import path25 from "path";
|
|
2906
2954
|
var ROUTE_HANDLER_EXPORT_NAMES = /* @__PURE__ */ new Set([
|
|
2907
2955
|
"GET",
|
|
2908
2956
|
"POST",
|
|
@@ -2946,12 +2994,12 @@ var pureFunctionExtractRule = {
|
|
|
2946
2994
|
},
|
|
2947
2995
|
create(context) {
|
|
2948
2996
|
const filename = context.filename;
|
|
2949
|
-
const isRouteFile =
|
|
2997
|
+
const isRouteFile = path25.basename(filename) === "route.ts";
|
|
2950
2998
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx") && !isRouteFile) return {};
|
|
2951
2999
|
const sourceRoot = sourceRootOf(context);
|
|
2952
|
-
const relative =
|
|
3000
|
+
const relative = path25.relative(sourceRoot, filename);
|
|
2953
3001
|
if (relative.startsWith("..")) return {};
|
|
2954
|
-
const segments = relative.split(
|
|
3002
|
+
const segments = relative.split(path25.sep);
|
|
2955
3003
|
if (segments[0] === "utils") return {};
|
|
2956
3004
|
if (segments[0] === "app" && !isRouteFile) return {};
|
|
2957
3005
|
const supportFolders = /* @__PURE__ */ new Set(["hooks", "types", "schemas", "constants", "utils"]);
|
|
@@ -2976,30 +3024,532 @@ var pureFunctionExtractRule = {
|
|
|
2976
3024
|
report3(node, name);
|
|
2977
3025
|
},
|
|
2978
3026
|
VariableDeclarator(node) {
|
|
2979
|
-
if (node.id.type !== "Identifier") return;
|
|
2980
|
-
const name = node.id.name;
|
|
2981
|
-
if (!name) return;
|
|
2982
|
-
const container = node.parent.parent;
|
|
2983
|
-
const exported = container?.type === "ExportNamedDeclaration";
|
|
2984
|
-
const moduleLevel = exported || container?.type === "Program";
|
|
2985
|
-
if (!moduleLevel) return;
|
|
2986
|
-
if (!isRouteFile && !exported) return;
|
|
2987
|
-
if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
|
|
2988
|
-
if (isComponentLikeName(name) || isHookName2(name)) return;
|
|
3027
|
+
if (node.id.type !== "Identifier") return;
|
|
3028
|
+
const name = node.id.name;
|
|
3029
|
+
if (!name) return;
|
|
3030
|
+
const container = node.parent.parent;
|
|
3031
|
+
const exported = container?.type === "ExportNamedDeclaration";
|
|
3032
|
+
const moduleLevel = exported || container?.type === "Program";
|
|
3033
|
+
if (!moduleLevel) return;
|
|
3034
|
+
if (!isRouteFile && !exported) return;
|
|
3035
|
+
if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
|
|
3036
|
+
if (isComponentLikeName(name) || isHookName2(name)) return;
|
|
3037
|
+
const init = node.init;
|
|
3038
|
+
if (!init || init.type !== "ArrowFunctionExpression" && init.type !== "FunctionExpression") {
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
if (init.body.type === "BlockStatement" && hasHookUsage(init.body)) return;
|
|
3042
|
+
report3(node, name);
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
|
|
3048
|
+
// eslint/rules/root-support-placement.ts
|
|
3049
|
+
import path26 from "path";
|
|
3050
|
+
function isSupportFolder(value) {
|
|
3051
|
+
return value === "utils" || value === "types" || value === "schemas" || value === "constants";
|
|
3052
|
+
}
|
|
3053
|
+
var TYPES_AND_SCHEMAS_DOC = "docs/next-codebase-guide/rules/types-and-schemas-rule.md";
|
|
3054
|
+
var KIND_FOR_SUPPORT_FOLDER = {
|
|
3055
|
+
utils: "function",
|
|
3056
|
+
types: "type",
|
|
3057
|
+
schemas: "schema",
|
|
3058
|
+
constants: "constant"
|
|
3059
|
+
};
|
|
3060
|
+
var LABEL_FOR_SUPPORT_FOLDER = {
|
|
3061
|
+
utils: "Function",
|
|
3062
|
+
types: "Type",
|
|
3063
|
+
schemas: "Schema",
|
|
3064
|
+
constants: "Constant"
|
|
3065
|
+
};
|
|
3066
|
+
var DOC_FOR_SUPPORT_FOLDER = {
|
|
3067
|
+
utils: "docs/next-codebase-guide/rules/utilities-rule.md",
|
|
3068
|
+
types: TYPES_AND_SCHEMAS_DOC,
|
|
3069
|
+
schemas: TYPES_AND_SCHEMAS_DOC,
|
|
3070
|
+
constants: "docs/next-codebase-guide/rules/constants-rule.md"
|
|
3071
|
+
};
|
|
3072
|
+
var rootSupportPlacementRule = {
|
|
3073
|
+
meta: {
|
|
3074
|
+
schema: [],
|
|
3075
|
+
type: "problem",
|
|
3076
|
+
docs: {
|
|
3077
|
+
description: "Require a zero-consumer support-folder export to live under a feature folder, not root or elsewhere."
|
|
3078
|
+
}
|
|
3079
|
+
},
|
|
3080
|
+
create(context) {
|
|
3081
|
+
const sourceRoot = sourceRootOf(context);
|
|
3082
|
+
const file = path26.resolve(context.filename);
|
|
3083
|
+
const segments = segmentsOf(file, sourceRoot);
|
|
3084
|
+
const folderSegments = folderSegmentsOf(file, sourceRoot);
|
|
3085
|
+
const supportFolder = folderSegments[folderSegments.length - 1];
|
|
3086
|
+
if (!isSupportFolder(supportFolder)) return {};
|
|
3087
|
+
const expectedKind = KIND_FOR_SUPPORT_FOLDER[supportFolder];
|
|
3088
|
+
const doc = DOC_FOR_SUPPORT_FOLDER[supportFolder];
|
|
3089
|
+
const label = LABEL_FOR_SUPPORT_FOLDER[supportFolder];
|
|
3090
|
+
const isRoot = segments[0] === supportFolder;
|
|
3091
|
+
const isUnderFeature = segments[0] === "features";
|
|
3092
|
+
if (isUnderFeature) return {};
|
|
3093
|
+
const index = getProjectIndex(sourceRoot);
|
|
3094
|
+
if (!index) return {};
|
|
3095
|
+
const module = index.modules.get(file);
|
|
3096
|
+
if (!module) return {};
|
|
3097
|
+
const findings = [];
|
|
3098
|
+
for (const exp of module.exports) {
|
|
3099
|
+
if (exp.kind !== expectedKind) continue;
|
|
3100
|
+
const consumers = [...index.symbolConsumers.get(symbolKey(file, exp.name)) ?? []];
|
|
3101
|
+
if (consumers.length === 0) continue;
|
|
3102
|
+
const 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));
|
|
3539
|
+
},
|
|
3540
|
+
VariableDeclarator(node) {
|
|
3541
|
+
if (!isNamed2(node.id.type === "Identifier" ? node.id : null, HELPER2)) return;
|
|
2989
3542
|
const init = node.init;
|
|
2990
|
-
if (
|
|
2991
|
-
|
|
2992
|
-
}
|
|
2993
|
-
if (init.body.type === "BlockStatement" && hasHookUsage(init.body)) return;
|
|
2994
|
-
report3(node, name);
|
|
3543
|
+
if (init?.type !== "ArrowFunctionExpression" && init?.type !== "FunctionExpression") return;
|
|
3544
|
+
check(init, parameterNames(init));
|
|
2995
3545
|
}
|
|
2996
3546
|
};
|
|
2997
3547
|
}
|
|
2998
3548
|
};
|
|
2999
3549
|
|
|
3000
3550
|
// eslint/rules/hook-complexity.ts
|
|
3001
|
-
import
|
|
3002
|
-
import
|
|
3551
|
+
import path30 from "path";
|
|
3552
|
+
import ts7 from "typescript";
|
|
3003
3553
|
var REACT_HOOKS = /* @__PURE__ */ new Set([
|
|
3004
3554
|
"useState",
|
|
3005
3555
|
"useEffect",
|
|
@@ -3017,28 +3567,67 @@ var REACT_HOOKS = /* @__PURE__ */ new Set([
|
|
|
3017
3567
|
"useSyncExternalStore",
|
|
3018
3568
|
"useInsertionEffect"
|
|
3019
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"]);
|
|
3020
3576
|
function isHookName3(name) {
|
|
3021
3577
|
return /^use[A-Z]/.test(name);
|
|
3022
3578
|
}
|
|
3023
|
-
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) {
|
|
3024
3610
|
const start = body.range?.[0] ?? 0;
|
|
3025
3611
|
const end = body.range?.[1] ?? sourceText.length;
|
|
3026
|
-
const sourceFile =
|
|
3612
|
+
const sourceFile = ts7.createSourceFile(
|
|
3027
3613
|
"hook.ts",
|
|
3028
3614
|
sourceText.slice(start, end),
|
|
3029
|
-
|
|
3615
|
+
ts7.ScriptTarget.Latest,
|
|
3030
3616
|
true,
|
|
3031
|
-
|
|
3617
|
+
ts7.ScriptKind.TS
|
|
3032
3618
|
);
|
|
3033
|
-
const
|
|
3619
|
+
const hookNames = /* @__PURE__ */ new Set();
|
|
3620
|
+
const sideEffectCategories = /* @__PURE__ */ new Set();
|
|
3034
3621
|
const visit = (node) => {
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
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);
|
|
3039
3627
|
};
|
|
3040
3628
|
visit(sourceFile);
|
|
3041
|
-
|
|
3629
|
+
const hookDiversityPoint = hookNames.size >= 2 ? 1 : 0;
|
|
3630
|
+
return hookDiversityPoint + sideEffectCategories.size;
|
|
3042
3631
|
}
|
|
3043
3632
|
var hookComplexityRule = {
|
|
3044
3633
|
meta: {
|
|
@@ -3051,26 +3640,26 @@ var hookComplexityRule = {
|
|
|
3051
3640
|
create(context) {
|
|
3052
3641
|
const filename = context.filename;
|
|
3053
3642
|
const sourceRoot = sourceRootOf(context);
|
|
3054
|
-
const relative =
|
|
3643
|
+
const relative = path30.relative(sourceRoot, filename);
|
|
3055
3644
|
if (relative.startsWith("..")) return {};
|
|
3056
|
-
const segments = relative.split(
|
|
3645
|
+
const segments = relative.split(path30.sep);
|
|
3057
3646
|
const sourceText = context.sourceCode.text;
|
|
3058
3647
|
function checkHook(node, name, body, exported) {
|
|
3059
3648
|
if (!exported) return;
|
|
3060
3649
|
if (!name || !isHookName3(name)) return;
|
|
3061
3650
|
if (!body) return;
|
|
3062
|
-
const
|
|
3651
|
+
const score = computeExtractionScore(body, sourceText);
|
|
3063
3652
|
const parentFolder = segments.length >= 2 ? segments[segments.length - 2] : void 0;
|
|
3064
3653
|
const inSupportFolder = parentFolder === "hooks";
|
|
3065
|
-
if (
|
|
3654
|
+
if (score >= 2 && !inSupportFolder) {
|
|
3066
3655
|
context.report({
|
|
3067
3656
|
node,
|
|
3068
|
-
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`
|
|
3069
3658
|
});
|
|
3070
|
-
} else if (
|
|
3659
|
+
} else if (score < 2 && inSupportFolder) {
|
|
3071
3660
|
context.report({
|
|
3072
3661
|
node,
|
|
3073
|
-
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`
|
|
3074
3663
|
});
|
|
3075
3664
|
}
|
|
3076
3665
|
}
|
|
@@ -3095,9 +3684,9 @@ var hookComplexityRule = {
|
|
|
3095
3684
|
};
|
|
3096
3685
|
|
|
3097
3686
|
// eslint/rules/locale-dotted-path.ts
|
|
3098
|
-
import
|
|
3687
|
+
import path31 from "path";
|
|
3099
3688
|
function isInLocalesDir(filename) {
|
|
3100
|
-
const segments =
|
|
3689
|
+
const segments = path31.resolve(filename).split(path31.sep);
|
|
3101
3690
|
const srcIdx = segments.lastIndexOf("src");
|
|
3102
3691
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
3103
3692
|
}
|
|
@@ -3146,9 +3735,9 @@ var localeDottedPathRule = {
|
|
|
3146
3735
|
};
|
|
3147
3736
|
|
|
3148
3737
|
// eslint/rules/locales-location.ts
|
|
3149
|
-
import
|
|
3738
|
+
import path32 from "path";
|
|
3150
3739
|
function isLocalesFile(filename) {
|
|
3151
|
-
const segments =
|
|
3740
|
+
const segments = path32.resolve(filename).split(path32.sep);
|
|
3152
3741
|
const srcIdx = segments.lastIndexOf("src");
|
|
3153
3742
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
3154
3743
|
}
|
|
@@ -3173,7 +3762,7 @@ var localesLocationRule = {
|
|
|
3173
3762
|
create(context) {
|
|
3174
3763
|
if (isLocalesFile(context.filename) || isTestFile(context.filename)) return {};
|
|
3175
3764
|
const filename = context.filename;
|
|
3176
|
-
const segments =
|
|
3765
|
+
const segments = path32.resolve(filename).split(path32.sep);
|
|
3177
3766
|
const srcIdx = segments.lastIndexOf("src");
|
|
3178
3767
|
if (srcIdx === -1) return {};
|
|
3179
3768
|
const folder = segments[srcIdx + 1];
|
|
@@ -3197,7 +3786,7 @@ var localesLocationRule = {
|
|
|
3197
3786
|
};
|
|
3198
3787
|
|
|
3199
3788
|
// eslint/rules/hook-extraction.ts
|
|
3200
|
-
import
|
|
3789
|
+
import path33 from "path";
|
|
3201
3790
|
var hookExtractionRule = {
|
|
3202
3791
|
meta: {
|
|
3203
3792
|
schema: [],
|
|
@@ -3208,7 +3797,7 @@ var hookExtractionRule = {
|
|
|
3208
3797
|
},
|
|
3209
3798
|
create(context) {
|
|
3210
3799
|
const sourceRoot = sourceRootOf(context);
|
|
3211
|
-
const file =
|
|
3800
|
+
const file = path33.resolve(context.filename);
|
|
3212
3801
|
const segments = segmentsOf(file, sourceRoot);
|
|
3213
3802
|
if (segments.length === 0) return {};
|
|
3214
3803
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3234,7 +3823,7 @@ var hookExtractionRule = {
|
|
|
3234
3823
|
};
|
|
3235
3824
|
|
|
3236
3825
|
// eslint/rules/value-extraction.ts
|
|
3237
|
-
import
|
|
3826
|
+
import path34 from "path";
|
|
3238
3827
|
var valueExtractionRule = {
|
|
3239
3828
|
meta: {
|
|
3240
3829
|
schema: [],
|
|
@@ -3245,7 +3834,7 @@ var valueExtractionRule = {
|
|
|
3245
3834
|
},
|
|
3246
3835
|
create(context) {
|
|
3247
3836
|
const sourceRoot = sourceRootOf(context);
|
|
3248
|
-
const file =
|
|
3837
|
+
const file = path34.resolve(context.filename);
|
|
3249
3838
|
const segments = segmentsOf(file, sourceRoot);
|
|
3250
3839
|
if (segments.length === 0 || segments[0] !== "app") return {};
|
|
3251
3840
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3266,7 +3855,7 @@ var valueExtractionRule = {
|
|
|
3266
3855
|
};
|
|
3267
3856
|
|
|
3268
3857
|
// eslint/rules/config-extraction.ts
|
|
3269
|
-
import
|
|
3858
|
+
import path35 from "path";
|
|
3270
3859
|
var configExtractionRule = {
|
|
3271
3860
|
meta: {
|
|
3272
3861
|
schema: [],
|
|
@@ -3277,7 +3866,7 @@ var configExtractionRule = {
|
|
|
3277
3866
|
},
|
|
3278
3867
|
create(context) {
|
|
3279
3868
|
const sourceRoot = sourceRootOf(context);
|
|
3280
|
-
const file =
|
|
3869
|
+
const file = path35.resolve(context.filename);
|
|
3281
3870
|
const segments = segmentsOf(file, sourceRoot);
|
|
3282
3871
|
if (segments.length < 3 || segments[0] !== "config") return {};
|
|
3283
3872
|
if (SUPPORT_FOLDERS2.has(segments[2] ?? "")) return {};
|
|
@@ -3315,7 +3904,7 @@ var configExtractionRule = {
|
|
|
3315
3904
|
};
|
|
3316
3905
|
|
|
3317
3906
|
// eslint/rules/component-nesting.ts
|
|
3318
|
-
import
|
|
3907
|
+
import path36 from "path";
|
|
3319
3908
|
var componentNestingRule = {
|
|
3320
3909
|
meta: {
|
|
3321
3910
|
schema: [],
|
|
@@ -3326,7 +3915,7 @@ var componentNestingRule = {
|
|
|
3326
3915
|
},
|
|
3327
3916
|
create(context) {
|
|
3328
3917
|
const sourceRoot = sourceRootOf(context);
|
|
3329
|
-
const file =
|
|
3918
|
+
const file = path36.resolve(context.filename);
|
|
3330
3919
|
const segments = segmentsOf(file, sourceRoot);
|
|
3331
3920
|
if (segments.length !== 4 || segments[0] !== "features") return {};
|
|
3332
3921
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3359,7 +3948,7 @@ var componentNestingRule = {
|
|
|
3359
3948
|
};
|
|
3360
3949
|
|
|
3361
3950
|
// eslint/rules/stay-flat.ts
|
|
3362
|
-
import
|
|
3951
|
+
import path37 from "path";
|
|
3363
3952
|
var stayFlatRule = {
|
|
3364
3953
|
meta: {
|
|
3365
3954
|
schema: [],
|
|
@@ -3370,7 +3959,7 @@ var stayFlatRule = {
|
|
|
3370
3959
|
},
|
|
3371
3960
|
create(context) {
|
|
3372
3961
|
const sourceRoot = sourceRootOf(context);
|
|
3373
|
-
const file =
|
|
3962
|
+
const file = path37.resolve(context.filename);
|
|
3374
3963
|
const segments = segmentsOf(file, sourceRoot);
|
|
3375
3964
|
if (segments.length !== 3 || segments[0] !== "features") return {};
|
|
3376
3965
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3410,7 +3999,7 @@ var stayFlatRule = {
|
|
|
3410
3999
|
};
|
|
3411
4000
|
|
|
3412
4001
|
// eslint/rules/type-extraction.ts
|
|
3413
|
-
import
|
|
4002
|
+
import path38 from "path";
|
|
3414
4003
|
var typeExtractionRule = {
|
|
3415
4004
|
meta: {
|
|
3416
4005
|
schema: [],
|
|
@@ -3421,7 +4010,7 @@ var typeExtractionRule = {
|
|
|
3421
4010
|
},
|
|
3422
4011
|
create(context) {
|
|
3423
4012
|
const sourceRoot = sourceRootOf(context);
|
|
3424
|
-
const file =
|
|
4013
|
+
const file = path38.resolve(context.filename);
|
|
3425
4014
|
const segments = segmentsOf(file, sourceRoot);
|
|
3426
4015
|
if (segments.length === 0) return {};
|
|
3427
4016
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3467,9 +4056,9 @@ var typeExtractionRule = {
|
|
|
3467
4056
|
};
|
|
3468
4057
|
|
|
3469
4058
|
// eslint/rules/locale-placement.ts
|
|
3470
|
-
import
|
|
3471
|
-
import { readFileSync as
|
|
3472
|
-
import
|
|
4059
|
+
import path39 from "path";
|
|
4060
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
4061
|
+
import ts8 from "typescript";
|
|
3473
4062
|
var LOCALE_ACCESS = /\blocales\.(?<key>[A-Za-z_$][\w$]*)/g;
|
|
3474
4063
|
var camelCase = (name) => name.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase());
|
|
3475
4064
|
var FORCED_TOP_LEVEL = /* @__PURE__ */ new Set(["app", "shared", "compositions", "config"]);
|
|
@@ -3477,26 +4066,26 @@ function forcesTopLevel(segments) {
|
|
|
3477
4066
|
return FORCED_TOP_LEVEL.has(segments[0] ?? "") || SUPPORT_FOLDERS2.has(segments[0] ?? "");
|
|
3478
4067
|
}
|
|
3479
4068
|
function localePlacement(text) {
|
|
3480
|
-
const sourceFile =
|
|
4069
|
+
const sourceFile = ts8.createSourceFile("locales.ts", text, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TS);
|
|
3481
4070
|
for (const statement of sourceFile.statements) {
|
|
3482
|
-
if (!
|
|
3483
|
-
const isExported2 = (
|
|
3484
|
-
(modifier) => modifier.kind ===
|
|
4071
|
+
if (!ts8.isVariableStatement(statement)) continue;
|
|
4072
|
+
const isExported2 = (ts8.getModifiers(statement) ?? []).some(
|
|
4073
|
+
(modifier) => modifier.kind === ts8.SyntaxKind.ExportKeyword
|
|
3485
4074
|
);
|
|
3486
4075
|
if (!isExported2) continue;
|
|
3487
4076
|
for (const declaration of statement.declarationList.declarations) {
|
|
3488
|
-
if (!
|
|
3489
|
-
if (!declaration.initializer || !
|
|
4077
|
+
if (!ts8.isIdentifier(declaration.name) || declaration.name.text !== "locales") continue;
|
|
4078
|
+
if (!declaration.initializer || !ts8.isObjectLiteralExpression(declaration.initializer)) continue;
|
|
3490
4079
|
const placement = /* @__PURE__ */ new Map();
|
|
3491
4080
|
for (const property of declaration.initializer.properties) {
|
|
3492
|
-
if (!
|
|
4081
|
+
if (!ts8.isPropertyAssignment(property)) continue;
|
|
3493
4082
|
let name;
|
|
3494
|
-
if (
|
|
3495
|
-
else if (
|
|
4083
|
+
if (ts8.isIdentifier(property.name)) name = property.name.text;
|
|
4084
|
+
else if (ts8.isStringLiteral(property.name)) name = property.name.text;
|
|
3496
4085
|
if (name === void 0) continue;
|
|
3497
4086
|
const line = sourceFile.getLineAndCharacterOfPosition(property.getStart(sourceFile)).line + 1;
|
|
3498
4087
|
placement.set(name, {
|
|
3499
|
-
kind:
|
|
4088
|
+
kind: ts8.isObjectLiteralExpression(property.initializer) ? "nested" : "top",
|
|
3500
4089
|
line
|
|
3501
4090
|
});
|
|
3502
4091
|
}
|
|
@@ -3515,7 +4104,7 @@ var localePlacementRule = {
|
|
|
3515
4104
|
},
|
|
3516
4105
|
create(context) {
|
|
3517
4106
|
const sourceRoot = sourceRootOf(context);
|
|
3518
|
-
const file =
|
|
4107
|
+
const file = path39.resolve(context.filename);
|
|
3519
4108
|
const segments = segmentsOf(file, sourceRoot);
|
|
3520
4109
|
if (segments.length === 0) return {};
|
|
3521
4110
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -3525,7 +4114,7 @@ var localePlacementRule = {
|
|
|
3525
4114
|
return candidateSegments.length === 2 && candidateSegments[0] === "locales" && candidateSegments[1]?.startsWith("index.");
|
|
3526
4115
|
});
|
|
3527
4116
|
if (!localesFile || file !== localesFile) return {};
|
|
3528
|
-
const placement = localePlacement(
|
|
4117
|
+
const placement = localePlacement(readFileSync5(localesFile, "utf8"));
|
|
3529
4118
|
if (!placement) return {};
|
|
3530
4119
|
const keyReaders = /* @__PURE__ */ new Map();
|
|
3531
4120
|
const keyFeatures = /* @__PURE__ */ new Map();
|
|
@@ -3537,7 +4126,7 @@ var localePlacementRule = {
|
|
|
3537
4126
|
);
|
|
3538
4127
|
if (!importsLocales) continue;
|
|
3539
4128
|
const candidateSegments = segmentsOf(candidateFile, sourceRoot);
|
|
3540
|
-
for (const match of
|
|
4129
|
+
for (const match of readFileSync5(candidateFile, "utf8").matchAll(LOCALE_ACCESS)) {
|
|
3541
4130
|
const key = match.groups?.key;
|
|
3542
4131
|
if (key === void 0) continue;
|
|
3543
4132
|
const readers = keyReaders.get(key) ?? /* @__PURE__ */ new Set();
|
|
@@ -3593,39 +4182,39 @@ var localePlacementRule = {
|
|
|
3593
4182
|
};
|
|
3594
4183
|
|
|
3595
4184
|
// eslint/rules/sole-state-owner.ts
|
|
3596
|
-
import
|
|
3597
|
-
import
|
|
4185
|
+
import path40 from "path";
|
|
4186
|
+
import ts9 from "typescript";
|
|
3598
4187
|
function findStateHooks(node) {
|
|
3599
4188
|
const hooks = [];
|
|
3600
4189
|
const visit = (child) => {
|
|
3601
|
-
if (
|
|
3602
|
-
if (
|
|
4190
|
+
if (ts9.isCallExpression(child) && ts9.isIdentifier(child.expression) && child.expression.text === "useState") {
|
|
4191
|
+
if (ts9.isVariableDeclaration(child.parent)) {
|
|
3603
4192
|
const { name } = child.parent;
|
|
3604
|
-
if (
|
|
4193
|
+
if (ts9.isArrayBindingPattern(name) && name.elements.length >= 2) {
|
|
3605
4194
|
const value = name.elements[0];
|
|
3606
4195
|
const updater = name.elements[1];
|
|
3607
|
-
if (value && updater &&
|
|
4196
|
+
if (value && updater && ts9.isBindingElement(value) && ts9.isBindingElement(updater)) {
|
|
3608
4197
|
const valueName = value.name;
|
|
3609
4198
|
const updaterName = updater.name;
|
|
3610
|
-
if (
|
|
4199
|
+
if (ts9.isIdentifier(valueName) && ts9.isIdentifier(updaterName)) {
|
|
3611
4200
|
hooks.push({ value: valueName.text, updater: updaterName.text });
|
|
3612
4201
|
}
|
|
3613
4202
|
}
|
|
3614
4203
|
}
|
|
3615
4204
|
}
|
|
3616
4205
|
}
|
|
3617
|
-
|
|
4206
|
+
ts9.forEachChild(child, visit);
|
|
3618
4207
|
};
|
|
3619
4208
|
visit(node);
|
|
3620
4209
|
return hooks;
|
|
3621
4210
|
}
|
|
3622
4211
|
function isHookUsage(node, hook) {
|
|
3623
|
-
if (
|
|
4212
|
+
if (ts9.isCallExpression(node) && ts9.isIdentifier(node.expression) && node.expression.text === hook.updater) {
|
|
3624
4213
|
return "updater";
|
|
3625
4214
|
}
|
|
3626
|
-
if (
|
|
4215
|
+
if (ts9.isIdentifier(node) && node.text === hook.value) {
|
|
3627
4216
|
const parent = node.parent;
|
|
3628
|
-
if (
|
|
4217
|
+
if (ts9.isBindingElement(parent) || ts9.isPropertyAccessExpression(parent) || ts9.isShorthandPropertyAssignment(parent)) {
|
|
3629
4218
|
return void 0;
|
|
3630
4219
|
}
|
|
3631
4220
|
return "value";
|
|
@@ -3635,16 +4224,16 @@ function isHookUsage(node, hook) {
|
|
|
3635
4224
|
function topLevelJsxChildren(initial) {
|
|
3636
4225
|
if (!initial) return void 0;
|
|
3637
4226
|
let expression = initial;
|
|
3638
|
-
while (
|
|
3639
|
-
if (
|
|
4227
|
+
while (ts9.isParenthesizedExpression(expression)) expression = expression.expression;
|
|
4228
|
+
if (ts9.isJsxFragment(expression)) {
|
|
3640
4229
|
const children = expression.children.filter(
|
|
3641
|
-
(c) => !
|
|
4230
|
+
(c) => !ts9.isJsxText(c) && !ts9.isJsxSpreadAttribute(c)
|
|
3642
4231
|
);
|
|
3643
4232
|
return { root: expression, children };
|
|
3644
4233
|
}
|
|
3645
|
-
if (
|
|
4234
|
+
if (ts9.isJsxElement(expression)) {
|
|
3646
4235
|
const children = expression.children.filter(
|
|
3647
|
-
(c) => !
|
|
4236
|
+
(c) => !ts9.isJsxText(c) && !ts9.isJsxSpreadAttribute(c)
|
|
3648
4237
|
);
|
|
3649
4238
|
return { root: expression, children };
|
|
3650
4239
|
}
|
|
@@ -3658,8 +4247,8 @@ function collectUsesIn(child, hook) {
|
|
|
3658
4247
|
positions.push(node);
|
|
3659
4248
|
count += 1;
|
|
3660
4249
|
}
|
|
3661
|
-
if (
|
|
3662
|
-
|
|
4250
|
+
if (ts9.isFunctionDeclaration(node) || ts9.isClassDeclaration(node)) return;
|
|
4251
|
+
ts9.forEachChild(node, visit);
|
|
3663
4252
|
};
|
|
3664
4253
|
visit(child);
|
|
3665
4254
|
return { positions, count };
|
|
@@ -3673,7 +4262,7 @@ var soleStateOwnerRule = {
|
|
|
3673
4262
|
}
|
|
3674
4263
|
},
|
|
3675
4264
|
create(context) {
|
|
3676
|
-
const filename =
|
|
4265
|
+
const filename = path40.resolve(context.filename);
|
|
3677
4266
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
|
|
3678
4267
|
const text = context.sourceCode.text;
|
|
3679
4268
|
const components = parseComponentInfo(text, filename);
|
|
@@ -3698,18 +4287,18 @@ var soleStateOwnerRule = {
|
|
|
3698
4287
|
}
|
|
3699
4288
|
};
|
|
3700
4289
|
function analyzeSoleOwner(declaration, hook) {
|
|
3701
|
-
const body =
|
|
3702
|
-
if (!body || !
|
|
4290
|
+
const body = ts9.isFunctionDeclaration(declaration) ? declaration.body : void 0;
|
|
4291
|
+
if (!body || !ts9.isBlock(body)) return void 0;
|
|
3703
4292
|
const returns = [];
|
|
3704
4293
|
const visit = (node) => {
|
|
3705
|
-
if (node !== body && (
|
|
3706
|
-
if (
|
|
3707
|
-
|
|
4294
|
+
if (node !== body && (ts9.isFunctionLike(node) || ts9.isClassLike(node))) return;
|
|
4295
|
+
if (ts9.isReturnStatement(node)) returns.push(node);
|
|
4296
|
+
ts9.forEachChild(node, visit);
|
|
3708
4297
|
};
|
|
3709
4298
|
visit(body);
|
|
3710
4299
|
if (returns.length !== 1) return void 0;
|
|
3711
4300
|
const single = returns[0];
|
|
3712
|
-
if (!single?.expression || !
|
|
4301
|
+
if (!single?.expression || !ts9.isExpression(single.expression)) return void 0;
|
|
3713
4302
|
const returnExpression = single.expression;
|
|
3714
4303
|
const root = topLevelJsxChildren(returnExpression);
|
|
3715
4304
|
if (!root || root.children.length === 0) return void 0;
|
|
@@ -3732,11 +4321,11 @@ function usesOutsideJsx(declaration, hook, children) {
|
|
|
3732
4321
|
let outside = false;
|
|
3733
4322
|
const visit = (node) => {
|
|
3734
4323
|
if (outside) return;
|
|
3735
|
-
if (node !== declaration && (
|
|
4324
|
+
if (node !== declaration && (ts9.isFunctionDeclaration(node) || ts9.isClassDeclaration(node))) return;
|
|
3736
4325
|
if (isHookUsage(node, hook)) {
|
|
3737
4326
|
let current = node;
|
|
3738
4327
|
let isInJsxChild = false;
|
|
3739
|
-
while (!
|
|
4328
|
+
while (!ts9.isSourceFile(current)) {
|
|
3740
4329
|
if (children.includes(current)) {
|
|
3741
4330
|
isInJsxChild = true;
|
|
3742
4331
|
break;
|
|
@@ -3745,14 +4334,14 @@ function usesOutsideJsx(declaration, hook, children) {
|
|
|
3745
4334
|
}
|
|
3746
4335
|
if (!isInJsxChild) outside = true;
|
|
3747
4336
|
}
|
|
3748
|
-
|
|
4337
|
+
ts9.forEachChild(node, visit);
|
|
3749
4338
|
};
|
|
3750
4339
|
visit(declaration);
|
|
3751
4340
|
return outside;
|
|
3752
4341
|
}
|
|
3753
4342
|
|
|
3754
4343
|
// eslint/rules/locale-key-shape.ts
|
|
3755
|
-
import
|
|
4344
|
+
import path41 from "path";
|
|
3756
4345
|
var MAX_KEY_LENGTH = 30;
|
|
3757
4346
|
var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
|
|
3758
4347
|
"Button",
|
|
@@ -3802,7 +4391,7 @@ var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
|
|
|
3802
4391
|
var CAMEL_CASE = /^[a-z][a-zA-Z0-9]*$/;
|
|
3803
4392
|
var ENGLISH = /^[A-Za-z0-9_]*$/;
|
|
3804
4393
|
function isLocalesFile2(filename) {
|
|
3805
|
-
const segments =
|
|
4394
|
+
const segments = path41.resolve(filename).split(path41.sep);
|
|
3806
4395
|
const srcIdx = segments.lastIndexOf("src");
|
|
3807
4396
|
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
3808
4397
|
}
|
|
@@ -3873,8 +4462,8 @@ var localeKeyShapeRule = {
|
|
|
3873
4462
|
};
|
|
3874
4463
|
|
|
3875
4464
|
// eslint/rules/shared-style-dedup.ts
|
|
3876
|
-
import
|
|
3877
|
-
import { readFileSync as
|
|
4465
|
+
import path42 from "path";
|
|
4466
|
+
import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
3878
4467
|
var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
|
|
3879
4468
|
var comboCache;
|
|
3880
4469
|
function combosFor(index) {
|
|
@@ -3888,7 +4477,7 @@ function combosFor(index) {
|
|
|
3888
4477
|
}
|
|
3889
4478
|
const combos = /* @__PURE__ */ new Map();
|
|
3890
4479
|
for (const file of files) {
|
|
3891
|
-
const text =
|
|
4480
|
+
const text = readFileSync6(file, "utf8");
|
|
3892
4481
|
for (const match of text.matchAll(CLASS_NAME)) {
|
|
3893
4482
|
const classes = (match.groups?.classes ?? "").split(/\s+/).filter(Boolean);
|
|
3894
4483
|
if (classes.length < 2) continue;
|
|
@@ -3911,7 +4500,7 @@ var sharedStyleDedupRule = {
|
|
|
3911
4500
|
},
|
|
3912
4501
|
create(context) {
|
|
3913
4502
|
const sourceRoot = sourceRootOf(context);
|
|
3914
|
-
const file =
|
|
4503
|
+
const file = path42.resolve(context.filename);
|
|
3915
4504
|
const segments = segmentsOf(file, sourceRoot);
|
|
3916
4505
|
if (segments.length === 0) return {};
|
|
3917
4506
|
const index = getProjectIndex(sourceRoot);
|
|
@@ -4115,7 +4704,7 @@ var zodSchemaValidationRule = {
|
|
|
4115
4704
|
};
|
|
4116
4705
|
|
|
4117
4706
|
// eslint/rules/schema-casing.ts
|
|
4118
|
-
import
|
|
4707
|
+
import path43 from "path";
|
|
4119
4708
|
function isCamelCase(name) {
|
|
4120
4709
|
return /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
4121
4710
|
}
|
|
@@ -4143,9 +4732,9 @@ var schemaCasingRule = {
|
|
|
4143
4732
|
}
|
|
4144
4733
|
},
|
|
4145
4734
|
create(context) {
|
|
4146
|
-
const filename =
|
|
4735
|
+
const filename = path43.resolve(context.filename);
|
|
4147
4736
|
const sourceRoot = sourceRootOf(context);
|
|
4148
|
-
if (!filename.startsWith(sourceRoot +
|
|
4737
|
+
if (!filename.startsWith(sourceRoot + path43.sep)) return {};
|
|
4149
4738
|
let zodLocalName;
|
|
4150
4739
|
return {
|
|
4151
4740
|
ImportDeclaration(node) {
|
|
@@ -4177,7 +4766,7 @@ var schemaCasingRule = {
|
|
|
4177
4766
|
};
|
|
4178
4767
|
|
|
4179
4768
|
// eslint/rules/component-casing.ts
|
|
4180
|
-
import
|
|
4769
|
+
import path44 from "path";
|
|
4181
4770
|
function isPascalCase6(name) {
|
|
4182
4771
|
return /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
4183
4772
|
}
|
|
@@ -4190,10 +4779,10 @@ var componentCasingRule = {
|
|
|
4190
4779
|
}
|
|
4191
4780
|
},
|
|
4192
4781
|
create(context) {
|
|
4193
|
-
const filename =
|
|
4782
|
+
const filename = path44.resolve(context.filename);
|
|
4194
4783
|
const sourceRoot = sourceRootOf(context);
|
|
4195
|
-
if (!filename.startsWith(sourceRoot +
|
|
4196
|
-
if (
|
|
4784
|
+
if (!filename.startsWith(sourceRoot + path44.sep)) return {};
|
|
4785
|
+
if (path44.extname(filename) !== ".tsx") return {};
|
|
4197
4786
|
return {
|
|
4198
4787
|
Program(node) {
|
|
4199
4788
|
for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
|
|
@@ -4210,7 +4799,7 @@ var componentCasingRule = {
|
|
|
4210
4799
|
};
|
|
4211
4800
|
|
|
4212
4801
|
// eslint/rules/source-under-src.ts
|
|
4213
|
-
import
|
|
4802
|
+
import path45 from "path";
|
|
4214
4803
|
var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
|
|
4215
4804
|
".agents",
|
|
4216
4805
|
".cache",
|
|
@@ -4251,14 +4840,14 @@ var sourceUnderSrcRule = {
|
|
|
4251
4840
|
}
|
|
4252
4841
|
},
|
|
4253
4842
|
create(context) {
|
|
4254
|
-
const filename =
|
|
4843
|
+
const filename = path45.resolve(context.filename);
|
|
4255
4844
|
if (!MODULE_EXTENSION.test(filename)) return {};
|
|
4256
|
-
const relative =
|
|
4845
|
+
const relative = path45.relative(context.cwd, filename).replace(/\\/g, "/");
|
|
4257
4846
|
if (relative === "src" || relative.startsWith("src/")) return {};
|
|
4258
4847
|
const topLevel = relative.split("/")[0] ?? "";
|
|
4259
4848
|
if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
|
|
4260
4849
|
if (!relative.includes("/")) {
|
|
4261
|
-
const basename =
|
|
4850
|
+
const basename = path45.basename(filename);
|
|
4262
4851
|
if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
|
|
4263
4852
|
}
|
|
4264
4853
|
return {
|
|
@@ -4275,8 +4864,8 @@ var sourceUnderSrcRule = {
|
|
|
4275
4864
|
|
|
4276
4865
|
// eslint/rules/zirka-baseline.ts
|
|
4277
4866
|
import fs5 from "fs";
|
|
4278
|
-
import
|
|
4279
|
-
var
|
|
4867
|
+
import path46 from "path";
|
|
4868
|
+
var ESLINT_CONFIG3 = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
|
|
4280
4869
|
var PRETTIER_CONFIGS = [
|
|
4281
4870
|
"prettier.config.mjs",
|
|
4282
4871
|
"prettier.config.cjs",
|
|
@@ -4294,10 +4883,10 @@ var zirkaBaselineRule = {
|
|
|
4294
4883
|
}
|
|
4295
4884
|
},
|
|
4296
4885
|
create(context) {
|
|
4297
|
-
const filename =
|
|
4298
|
-
const basename =
|
|
4299
|
-
if (!
|
|
4300
|
-
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);
|
|
4301
4890
|
const report3 = (message) => {
|
|
4302
4891
|
context.report({
|
|
4303
4892
|
node: context.sourceCode.ast,
|
|
@@ -4312,7 +4901,7 @@ var zirkaBaselineRule = {
|
|
|
4312
4901
|
'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
|
|
4313
4902
|
);
|
|
4314
4903
|
}
|
|
4315
|
-
const tsconfigPath =
|
|
4904
|
+
const tsconfigPath = path46.join(projectRoot, "tsconfig.json");
|
|
4316
4905
|
if (!fs5.existsSync(tsconfigPath)) {
|
|
4317
4906
|
report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
|
|
4318
4907
|
} else {
|
|
@@ -4330,13 +4919,13 @@ var zirkaBaselineRule = {
|
|
|
4330
4919
|
report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
|
|
4331
4920
|
}
|
|
4332
4921
|
}
|
|
4333
|
-
const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(
|
|
4922
|
+
const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(path46.join(projectRoot, name)));
|
|
4334
4923
|
if (!prettierConfigFile) {
|
|
4335
4924
|
report3(
|
|
4336
4925
|
"No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
|
|
4337
4926
|
);
|
|
4338
4927
|
} else {
|
|
4339
|
-
const content = fs5.readFileSync(
|
|
4928
|
+
const content = fs5.readFileSync(path46.join(projectRoot, prettierConfigFile), "utf8");
|
|
4340
4929
|
if (!content.includes("zirka")) {
|
|
4341
4930
|
report3(
|
|
4342
4931
|
"The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
|
|
@@ -4378,6 +4967,15 @@ function getTextContent(node) {
|
|
|
4378
4967
|
function getLine(node) {
|
|
4379
4968
|
return node.position?.start.line ?? 0;
|
|
4380
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
|
+
}
|
|
4381
4979
|
|
|
4382
4980
|
// eslint/rules/documentation/doc-kind-suffix.ts
|
|
4383
4981
|
var docKindSuffixRule = {
|
|
@@ -4406,7 +5004,7 @@ var docKindSuffixRule = {
|
|
|
4406
5004
|
};
|
|
4407
5005
|
|
|
4408
5006
|
// eslint/rules/documentation/title-matches-file-name.ts
|
|
4409
|
-
import
|
|
5007
|
+
import path47 from "path";
|
|
4410
5008
|
function toExpectedFileName(title) {
|
|
4411
5009
|
return `${title.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
|
|
4412
5010
|
}
|
|
@@ -4426,7 +5024,7 @@ var titleMatchesFileNameRule = {
|
|
|
4426
5024
|
if (!filename.endsWith(".md")) return;
|
|
4427
5025
|
const title = getTextContent(node).trim();
|
|
4428
5026
|
const expectedFileName = toExpectedFileName(title);
|
|
4429
|
-
const actualFileName =
|
|
5027
|
+
const actualFileName = path47.basename(filename);
|
|
4430
5028
|
if (!title) {
|
|
4431
5029
|
context.report({
|
|
4432
5030
|
node,
|
|
@@ -4581,7 +5179,7 @@ var guideStepSingleSentenceRule = {
|
|
|
4581
5179
|
|
|
4582
5180
|
// eslint/rules/documentation/guide-step-single-link.ts
|
|
4583
5181
|
function countDocLinks(node) {
|
|
4584
|
-
if (node.type === "link" && node.url
|
|
5182
|
+
if (node.type === "link" && isDocLink(node.url)) return 1;
|
|
4585
5183
|
if ("children" in node) {
|
|
4586
5184
|
return node.children.reduce((sum, child) => sum + countDocLinks(child), 0);
|
|
4587
5185
|
}
|
|
@@ -4799,7 +5397,7 @@ var noCrossDocumentLinkRule = {
|
|
|
4799
5397
|
const filename = getFilename(context);
|
|
4800
5398
|
const kind = linkedKind(filename);
|
|
4801
5399
|
if (!kind) return;
|
|
4802
|
-
if (node.url
|
|
5400
|
+
if (isDocLink(node.url)) {
|
|
4803
5401
|
context.report({
|
|
4804
5402
|
node,
|
|
4805
5403
|
message: `${kind} links another document: ${node.url}`
|
|
@@ -4867,22 +5465,46 @@ var referenceBlockHeadingsRule = {
|
|
|
4867
5465
|
}
|
|
4868
5466
|
};
|
|
4869
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
|
+
|
|
4870
5492
|
// eslint/rules/documentation/support-document-placement.ts
|
|
4871
5493
|
import { existsSync as existsSync2 } from "fs";
|
|
4872
|
-
import
|
|
5494
|
+
import path48 from "path";
|
|
4873
5495
|
function checkPlacement(filename, kind) {
|
|
4874
|
-
const parentFolder =
|
|
5496
|
+
const parentFolder = path48.basename(path48.dirname(filename));
|
|
4875
5497
|
const expectedParent = `${kind}s`;
|
|
4876
5498
|
if (parentFolder !== expectedParent) {
|
|
4877
5499
|
return `${kind} lives in "${parentFolder}/" instead of "${expectedParent}/"`;
|
|
4878
5500
|
}
|
|
4879
|
-
const guideFolderPath =
|
|
4880
|
-
const guideFolder =
|
|
5501
|
+
const guideFolderPath = path48.dirname(path48.dirname(filename));
|
|
5502
|
+
const guideFolder = path48.basename(guideFolderPath);
|
|
4881
5503
|
if (!guideFolder.endsWith("-guide")) {
|
|
4882
5504
|
return `${kind} owner folder "${guideFolder}/" does not use the "*-guide/" suffix`;
|
|
4883
5505
|
}
|
|
4884
5506
|
const entryPoint = `${guideFolder}.md`;
|
|
4885
|
-
if (!existsSync2(
|
|
5507
|
+
if (!existsSync2(path48.join(guideFolderPath, entryPoint))) {
|
|
4886
5508
|
return `${kind} owner folder "${guideFolder}/" has no "${entryPoint}" entry point`;
|
|
4887
5509
|
}
|
|
4888
5510
|
return void 0;
|
|
@@ -4941,11 +5563,11 @@ var noTemplatePromptRule = {
|
|
|
4941
5563
|
};
|
|
4942
5564
|
|
|
4943
5565
|
// eslint/rules/documentation/guide-folder-entry-point.ts
|
|
4944
|
-
import
|
|
5566
|
+
import path50 from "path";
|
|
4945
5567
|
|
|
4946
5568
|
// eslint/rules/documentation/project-index.ts
|
|
4947
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
4948
|
-
import
|
|
5569
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
5570
|
+
import path49 from "path";
|
|
4949
5571
|
var KIND_BY_SUFFIX = [
|
|
4950
5572
|
["-rule.md", "rule"],
|
|
4951
5573
|
["-guide.md", "guide"],
|
|
@@ -4954,7 +5576,7 @@ var KIND_BY_SUFFIX = [
|
|
|
4954
5576
|
];
|
|
4955
5577
|
function listMarkdownFiles(dir) {
|
|
4956
5578
|
return readdirSync3(dir).flatMap((entry) => {
|
|
4957
|
-
const entryPath =
|
|
5579
|
+
const entryPath = path49.join(dir, entry);
|
|
4958
5580
|
if (statSync4(entryPath).isDirectory()) {
|
|
4959
5581
|
return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
|
|
4960
5582
|
}
|
|
@@ -4962,7 +5584,7 @@ function listMarkdownFiles(dir) {
|
|
|
4962
5584
|
});
|
|
4963
5585
|
}
|
|
4964
5586
|
function extractTitle(filePath) {
|
|
4965
|
-
const content =
|
|
5587
|
+
const content = readFileSync7(filePath, "utf8");
|
|
4966
5588
|
const match = /^# (?<title>.+)$/m.exec(content);
|
|
4967
5589
|
return match?.groups?.title?.trim() ?? "";
|
|
4968
5590
|
}
|
|
@@ -4972,11 +5594,11 @@ function getProjectDocs(docsRoot) {
|
|
|
4972
5594
|
if (cached) return cached;
|
|
4973
5595
|
const files = listMarkdownFiles(docsRoot);
|
|
4974
5596
|
const docs = files.sort((a, b) => a.localeCompare(b)).map((filePath) => {
|
|
4975
|
-
const fileName =
|
|
5597
|
+
const fileName = path49.basename(filePath);
|
|
4976
5598
|
const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
|
|
4977
5599
|
return {
|
|
4978
5600
|
filePath,
|
|
4979
|
-
doc:
|
|
5601
|
+
doc: path49.relative(docsRoot, filePath).split(path49.sep).join("/"),
|
|
4980
5602
|
fileName,
|
|
4981
5603
|
kind,
|
|
4982
5604
|
title: extractTitle(filePath)
|
|
@@ -4986,12 +5608,12 @@ function getProjectDocs(docsRoot) {
|
|
|
4986
5608
|
return docs;
|
|
4987
5609
|
}
|
|
4988
5610
|
function findDocsRoot(filePath) {
|
|
4989
|
-
let dir =
|
|
5611
|
+
let dir = path49.dirname(filePath);
|
|
4990
5612
|
for (; ; ) {
|
|
4991
|
-
if (
|
|
5613
|
+
if (path49.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
|
|
4992
5614
|
return dir;
|
|
4993
5615
|
}
|
|
4994
|
-
const parent =
|
|
5616
|
+
const parent = path49.dirname(dir);
|
|
4995
5617
|
if (parent === dir) return void 0;
|
|
4996
5618
|
dir = parent;
|
|
4997
5619
|
}
|
|
@@ -5015,13 +5637,13 @@ var guideFolderEntryPointRule = {
|
|
|
5015
5637
|
if (!docsRoot) return;
|
|
5016
5638
|
const docs = getProjectDocs(docsRoot);
|
|
5017
5639
|
const guideFolders = new Set(
|
|
5018
|
-
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))
|
|
5019
5641
|
);
|
|
5020
|
-
const currentDir =
|
|
5642
|
+
const currentDir = path50.dirname(filename);
|
|
5021
5643
|
if (guideFolders.has(currentDir)) {
|
|
5022
|
-
const expectedEntryPoint = `${
|
|
5644
|
+
const expectedEntryPoint = `${path50.basename(currentDir)}.md`;
|
|
5023
5645
|
const hasEntryPoint = docs.some(
|
|
5024
|
-
(doc) => doc.kind === "guide" &&
|
|
5646
|
+
(doc) => doc.kind === "guide" && path50.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
|
|
5025
5647
|
);
|
|
5026
5648
|
if (!hasEntryPoint) {
|
|
5027
5649
|
context.report({
|
|
@@ -5165,6 +5787,8 @@ var policySubjectHeadingsRule = {
|
|
|
5165
5787
|
};
|
|
5166
5788
|
|
|
5167
5789
|
// eslint/rules/documentation/guide-link-anchors.ts
|
|
5790
|
+
import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
|
|
5791
|
+
import path51 from "path";
|
|
5168
5792
|
function visitSteps2(node, check) {
|
|
5169
5793
|
if (node.type === "list" && node.ordered) {
|
|
5170
5794
|
for (const child of node.children) check(child);
|
|
@@ -5174,17 +5798,52 @@ function visitSteps2(node, check) {
|
|
|
5174
5798
|
}
|
|
5175
5799
|
}
|
|
5176
5800
|
function collectGuideLinks(node) {
|
|
5177
|
-
if (node.type === "link" && node.url.endsWith("-guide.md")) return [node];
|
|
5801
|
+
if (node.type === "link" && linkTarget(node.url).endsWith("-guide.md")) return [node];
|
|
5178
5802
|
if ("children" in node) {
|
|
5179
5803
|
return node.children.flatMap(collectGuideLinks);
|
|
5180
5804
|
}
|
|
5181
5805
|
return [];
|
|
5182
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
|
+
}
|
|
5183
5842
|
var guideLinkAnchorsRule = {
|
|
5184
5843
|
meta: {
|
|
5185
5844
|
type: "problem",
|
|
5186
5845
|
docs: {
|
|
5187
|
-
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.",
|
|
5188
5847
|
recommended: true
|
|
5189
5848
|
}
|
|
5190
5849
|
},
|
|
@@ -5203,6 +5862,19 @@ var guideLinkAnchorsRule = {
|
|
|
5203
5862
|
}
|
|
5204
5863
|
}
|
|
5205
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
|
+
}
|
|
5206
5878
|
}
|
|
5207
5879
|
};
|
|
5208
5880
|
}
|
|
@@ -5245,41 +5917,82 @@ var noNestedHowToRule = {
|
|
|
5245
5917
|
};
|
|
5246
5918
|
|
|
5247
5919
|
// eslint/rules/documentation/glossary-term-linking.ts
|
|
5248
|
-
import { readFileSync as
|
|
5249
|
-
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
|
+
}
|
|
5250
5939
|
function extractGlossaryTerms(filePath) {
|
|
5251
|
-
|
|
5940
|
+
if (!isGlossary(filePath)) return [];
|
|
5941
|
+
const content = readFileSync9(filePath, "utf8");
|
|
5942
|
+
const tableTerms = extractTableTerms(content);
|
|
5943
|
+
if (tableTerms.length > 0) return tableTerms;
|
|
5252
5944
|
const terms = [];
|
|
5253
5945
|
const headingPattern = /^## (?<term>.+)$/gm;
|
|
5254
|
-
|
|
5255
|
-
while ((match = headingPattern.exec(content)) !== null) {
|
|
5946
|
+
for (const match of content.matchAll(headingPattern)) {
|
|
5256
5947
|
const term = match.groups?.term?.trim();
|
|
5257
|
-
if (term) terms.push(term);
|
|
5948
|
+
if (term) terms.push({ term, names: termNames(term) });
|
|
5258
5949
|
}
|
|
5259
5950
|
return terms;
|
|
5260
5951
|
}
|
|
5261
|
-
function
|
|
5262
|
-
const
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
collectDocLinks(item, firstStepLinks);
|
|
5269
|
-
}
|
|
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;
|
|
5270
5959
|
}
|
|
5960
|
+
if (fenced) continue;
|
|
5961
|
+
const heading = /^##\s+(?<text>.+)$/.exec(line)?.groups?.text;
|
|
5962
|
+
if (heading) headings.push(heading.trim());
|
|
5271
5963
|
}
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
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);
|
|
5278
5992
|
}
|
|
5279
|
-
return { texts, firstStepLinks };
|
|
5280
5993
|
}
|
|
5281
5994
|
function collectDocLinks(node, out) {
|
|
5282
|
-
if (node.type === "link" && node.url
|
|
5995
|
+
if (node.type === "link" && isDocLink(node.url)) out.push(node.url);
|
|
5283
5996
|
if ("children" in node) {
|
|
5284
5997
|
for (const child of node.children) collectDocLinks(child, out);
|
|
5285
5998
|
}
|
|
@@ -5288,7 +6001,7 @@ var glossaryTermLinkingRule = {
|
|
|
5288
6001
|
meta: {
|
|
5289
6002
|
type: "problem",
|
|
5290
6003
|
docs: {
|
|
5291
|
-
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.",
|
|
5292
6005
|
recommended: true
|
|
5293
6006
|
}
|
|
5294
6007
|
},
|
|
@@ -5300,25 +6013,55 @@ var glossaryTermLinkingRule = {
|
|
|
5300
6013
|
const docsRoot = findDocsRoot(filename);
|
|
5301
6014
|
if (!docsRoot) return;
|
|
5302
6015
|
const docs = getProjectDocs(docsRoot);
|
|
5303
|
-
const guideDir =
|
|
6016
|
+
const guideDir = path52.dirname(filename);
|
|
6017
|
+
const referencesDir = path52.join(guideDir, "references");
|
|
5304
6018
|
const guideReferences = docs.filter(
|
|
5305
|
-
(doc) => doc.kind === "reference" &&
|
|
6019
|
+
(doc) => doc.kind === "reference" && path52.dirname(doc.filePath) === referencesDir
|
|
5306
6020
|
);
|
|
5307
|
-
|
|
6021
|
+
const glossaryReferences = guideReferences.filter((doc) => isGlossary(doc.filePath));
|
|
6022
|
+
if (glossaryReferences.length === 0) return;
|
|
5308
6023
|
const glossaryTerms = [];
|
|
5309
|
-
for (const ref of
|
|
6024
|
+
for (const ref of glossaryReferences) {
|
|
5310
6025
|
glossaryTerms.push(...extractGlossaryTerms(ref.filePath));
|
|
5311
6026
|
}
|
|
5312
6027
|
if (glossaryTerms.length === 0) return;
|
|
5313
|
-
const
|
|
5314
|
-
const
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
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
|
+
}
|
|
5322
6065
|
}
|
|
5323
6066
|
}
|
|
5324
6067
|
};
|
|
@@ -5326,8 +6069,8 @@ var glossaryTermLinkingRule = {
|
|
|
5326
6069
|
};
|
|
5327
6070
|
|
|
5328
6071
|
// eslint/rules/documentation/guide-mentions-documents.ts
|
|
5329
|
-
import { existsSync as
|
|
5330
|
-
import
|
|
6072
|
+
import { existsSync as existsSync4 } from "fs";
|
|
6073
|
+
import path53 from "path";
|
|
5331
6074
|
function visitSteps3(node, check) {
|
|
5332
6075
|
if (node.type === "list" && node.ordered) {
|
|
5333
6076
|
for (const child of node.children) check(child);
|
|
@@ -5337,14 +6080,11 @@ function visitSteps3(node, check) {
|
|
|
5337
6080
|
}
|
|
5338
6081
|
}
|
|
5339
6082
|
function collectMarkdownLinks(node, out) {
|
|
5340
|
-
if (node.type === "link" && node.url
|
|
6083
|
+
if (node.type === "link" && isDocLink(node.url)) out.push(node);
|
|
5341
6084
|
if ("children" in node) {
|
|
5342
6085
|
for (const child of node.children) collectMarkdownLinks(child, out);
|
|
5343
6086
|
}
|
|
5344
6087
|
}
|
|
5345
|
-
function linkTarget(url) {
|
|
5346
|
-
return url.split("#")[0] ?? url;
|
|
5347
|
-
}
|
|
5348
6088
|
var guideMentionsDocumentsRule = {
|
|
5349
6089
|
meta: {
|
|
5350
6090
|
type: "problem",
|
|
@@ -5360,12 +6100,12 @@ var guideMentionsDocumentsRule = {
|
|
|
5360
6100
|
if (!filename.endsWith("-guide.md")) return;
|
|
5361
6101
|
const docsRoot = findDocsRoot(filename);
|
|
5362
6102
|
if (!docsRoot) return;
|
|
5363
|
-
const guideDir =
|
|
5364
|
-
if (
|
|
6103
|
+
const guideDir = path53.dirname(filename);
|
|
6104
|
+
if (path53.basename(filename, ".md") !== path53.basename(guideDir)) return;
|
|
5365
6105
|
const docs = getProjectDocs(docsRoot);
|
|
5366
6106
|
const owned = docs.filter((doc) => {
|
|
5367
|
-
const parent =
|
|
5368
|
-
return parent ===
|
|
6107
|
+
const parent = path53.dirname(doc.filePath);
|
|
6108
|
+
return parent === path53.join(guideDir, "rules") || parent === path53.join(guideDir, "references");
|
|
5369
6109
|
});
|
|
5370
6110
|
const allLinks = [];
|
|
5371
6111
|
collectMarkdownLinks(node, allLinks);
|
|
@@ -5373,7 +6113,7 @@ var guideMentionsDocumentsRule = {
|
|
|
5373
6113
|
visitSteps3(node, (item) => {
|
|
5374
6114
|
collectMarkdownLinks(item, stepLinks);
|
|
5375
6115
|
});
|
|
5376
|
-
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);
|
|
5377
6117
|
for (const doc of owned) {
|
|
5378
6118
|
if (doc.kind === "rule") {
|
|
5379
6119
|
if (!mentionsOf(stepLinks, doc.fileName)) {
|
|
@@ -5392,8 +6132,8 @@ var guideMentionsDocumentsRule = {
|
|
|
5392
6132
|
for (const link of allLinks) {
|
|
5393
6133
|
const target = linkTarget(link.url);
|
|
5394
6134
|
if (!target.endsWith(".md")) continue;
|
|
5395
|
-
const resolved =
|
|
5396
|
-
if (!
|
|
6135
|
+
const resolved = path53.normalize(path53.join(guideDir, target));
|
|
6136
|
+
if (!existsSync4(resolved)) {
|
|
5397
6137
|
context.report({
|
|
5398
6138
|
node: link,
|
|
5399
6139
|
message: `Guide links a document that does not exist: ${link.url}`
|
|
@@ -5470,6 +6210,7 @@ var documentationRules = {
|
|
|
5470
6210
|
"no-cross-document-link": noCrossDocumentLinkRule,
|
|
5471
6211
|
"reference-no-rfc-vocabulary": referenceNoRfcVocabularyRule,
|
|
5472
6212
|
"reference-block-headings": referenceBlockHeadingsRule,
|
|
6213
|
+
"reference-max-heading-depth": referenceMaxHeadingDepthRule,
|
|
5473
6214
|
"support-document-placement": supportDocumentPlacementRule,
|
|
5474
6215
|
"no-template-prompt": noTemplatePromptRule,
|
|
5475
6216
|
"guide-folder-entry-point": guideFolderEntryPointRule,
|
|
@@ -5942,11 +6683,11 @@ var themeVariableNamespaceRule = {
|
|
|
5942
6683
|
|
|
5943
6684
|
// eslint/rules/tailwind/css-entry-point.ts
|
|
5944
6685
|
import { statSync as statSync6 } from "fs";
|
|
5945
|
-
import
|
|
6686
|
+
import path56 from "path";
|
|
5946
6687
|
|
|
5947
6688
|
// eslint/rules/tailwind/source-files.ts
|
|
5948
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
5949
|
-
import
|
|
6689
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync5 } from "fs";
|
|
6690
|
+
import path54 from "path";
|
|
5950
6691
|
var CSS_EXTENSIONS = [".css"];
|
|
5951
6692
|
var MODULE_EXTENSIONS3 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
5952
6693
|
var SOURCE_EXTENSIONS = [...MODULE_EXTENSIONS3, ...CSS_EXTENSIONS];
|
|
@@ -5959,7 +6700,7 @@ function findFiles(dir, extensions) {
|
|
|
5959
6700
|
}
|
|
5960
6701
|
return entries.flatMap((entry) => {
|
|
5961
6702
|
if (entry.startsWith(".") || entry === "node_modules") return [];
|
|
5962
|
-
const entryPath =
|
|
6703
|
+
const entryPath = path54.join(dir, entry);
|
|
5963
6704
|
let stats;
|
|
5964
6705
|
try {
|
|
5965
6706
|
stats = statSync5(entryPath);
|
|
@@ -5967,7 +6708,7 @@ function findFiles(dir, extensions) {
|
|
|
5967
6708
|
return [];
|
|
5968
6709
|
}
|
|
5969
6710
|
if (stats.isDirectory()) return findFiles(entryPath, extensions);
|
|
5970
|
-
return extensions.includes(
|
|
6711
|
+
return extensions.includes(path54.extname(entry)) ? [entryPath] : [];
|
|
5971
6712
|
});
|
|
5972
6713
|
}
|
|
5973
6714
|
function cachedTextReader() {
|
|
@@ -5976,7 +6717,7 @@ function cachedTextReader() {
|
|
|
5976
6717
|
let text = texts.get(file);
|
|
5977
6718
|
if (text === void 0) {
|
|
5978
6719
|
try {
|
|
5979
|
-
text =
|
|
6720
|
+
text = readFileSync10(file, "utf8");
|
|
5980
6721
|
} catch {
|
|
5981
6722
|
text = "";
|
|
5982
6723
|
}
|
|
@@ -5990,7 +6731,7 @@ function escapeRegExp(text) {
|
|
|
5990
6731
|
}
|
|
5991
6732
|
|
|
5992
6733
|
// eslint/rules/tailwind/stylesheet-graph.ts
|
|
5993
|
-
import
|
|
6734
|
+
import path55 from "path";
|
|
5994
6735
|
function registersTailwind(text) {
|
|
5995
6736
|
return /@import\s+(?:url\(\s*)?["']tailwindcss["']\s*\)?/i.test(text);
|
|
5996
6737
|
}
|
|
@@ -6004,25 +6745,25 @@ function moduleImports(text, fileName) {
|
|
|
6004
6745
|
return new RegExp(`(?:import|require)\\s*\\(?\\s*["'][^"']*${escaped}["']`, "i").test(text);
|
|
6005
6746
|
}
|
|
6006
6747
|
function resolveSpecifier2(fromFile, spec, sourceRoot) {
|
|
6007
|
-
if (spec.startsWith("/")) return
|
|
6008
|
-
if (spec.startsWith("./") || spec.startsWith("../")) return
|
|
6009
|
-
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));
|
|
6010
6751
|
return void 0;
|
|
6011
6752
|
}
|
|
6012
6753
|
function buildStylesheetGraph(options) {
|
|
6013
6754
|
const { cssFiles, sourceRoot, textOf } = options;
|
|
6014
|
-
const cssSet = new Set(cssFiles.map((file) =>
|
|
6755
|
+
const cssSet = new Set(cssFiles.map((file) => path55.normalize(file)));
|
|
6015
6756
|
const globals = cssFiles.filter((file) => registersTailwind(textOf(file)));
|
|
6016
6757
|
const reachable = /* @__PURE__ */ new Set();
|
|
6017
6758
|
const queue = [...globals];
|
|
6018
|
-
for (const global of globals) reachable.add(
|
|
6759
|
+
for (const global of globals) reachable.add(path55.normalize(global));
|
|
6019
6760
|
while (queue.length > 0) {
|
|
6020
6761
|
const from = queue.shift();
|
|
6021
6762
|
if (!from) continue;
|
|
6022
6763
|
for (const spec of importedSpecifiers(textOf(from))) {
|
|
6023
6764
|
const target = resolveSpecifier2(from, spec, sourceRoot);
|
|
6024
6765
|
if (!target) continue;
|
|
6025
|
-
const normalized =
|
|
6766
|
+
const normalized = path55.normalize(target);
|
|
6026
6767
|
if (cssSet.has(normalized) && !reachable.has(normalized)) {
|
|
6027
6768
|
reachable.add(normalized);
|
|
6028
6769
|
queue.push(normalized);
|
|
@@ -6034,7 +6775,7 @@ function buildStylesheetGraph(options) {
|
|
|
6034
6775
|
for (const spec of importedSpecifiers(textOf(global))) {
|
|
6035
6776
|
const target = resolveSpecifier2(global, spec, sourceRoot);
|
|
6036
6777
|
if (!target) continue;
|
|
6037
|
-
const normalized =
|
|
6778
|
+
const normalized = path55.normalize(target);
|
|
6038
6779
|
if (cssSet.has(normalized)) directChildren.add(normalized);
|
|
6039
6780
|
}
|
|
6040
6781
|
}
|
|
@@ -6067,7 +6808,7 @@ var cssEntryPointRule = {
|
|
|
6067
6808
|
return {
|
|
6068
6809
|
"StyleSheet:exit"(node) {
|
|
6069
6810
|
if (globals.length === 0) return;
|
|
6070
|
-
const current =
|
|
6811
|
+
const current = path56.normalize(path56.resolve(context.filename));
|
|
6071
6812
|
if (globals.includes(current)) {
|
|
6072
6813
|
if (globals.length > 1) {
|
|
6073
6814
|
context.report({
|
|
@@ -6076,7 +6817,7 @@ var cssEntryPointRule = {
|
|
|
6076
6817
|
});
|
|
6077
6818
|
return;
|
|
6078
6819
|
}
|
|
6079
|
-
const basename =
|
|
6820
|
+
const basename = path56.basename(current);
|
|
6080
6821
|
const importCount = moduleFiles.filter((modulePath) => moduleImports(textOf(modulePath), basename)).length;
|
|
6081
6822
|
if (importCount !== 1) {
|
|
6082
6823
|
context.report({
|
|
@@ -6404,8 +7145,8 @@ var nextjsStackRule = {
|
|
|
6404
7145
|
};
|
|
6405
7146
|
|
|
6406
7147
|
// eslint/rules/package-json/vitest-coverage.ts
|
|
6407
|
-
import { existsSync as
|
|
6408
|
-
import
|
|
7148
|
+
import { existsSync as existsSync5, readFileSync as readFileSync11 } from "fs";
|
|
7149
|
+
import path57 from "path";
|
|
6409
7150
|
function memberName4(member) {
|
|
6410
7151
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
6411
7152
|
}
|
|
@@ -6474,7 +7215,7 @@ var vitestCoverageRule = {
|
|
|
6474
7215
|
message: 'package.json must declare a "test:unit:coverage" script that runs Vitest with coverage.'
|
|
6475
7216
|
});
|
|
6476
7217
|
}
|
|
6477
|
-
const configName = VITEST_CONFIG_NAMES.find((name) =>
|
|
7218
|
+
const configName = VITEST_CONFIG_NAMES.find((name) => existsSync5(path57.join(context.cwd, name)));
|
|
6478
7219
|
if (!configName) {
|
|
6479
7220
|
context.report({
|
|
6480
7221
|
node,
|
|
@@ -6482,7 +7223,7 @@ var vitestCoverageRule = {
|
|
|
6482
7223
|
});
|
|
6483
7224
|
return;
|
|
6484
7225
|
}
|
|
6485
|
-
const content =
|
|
7226
|
+
const content = readFileSync11(path57.join(context.cwd, configName), "utf8");
|
|
6486
7227
|
for (const metric of THRESHOLD_METRICS) {
|
|
6487
7228
|
if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
|
|
6488
7229
|
context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
|
|
@@ -6528,8 +7269,8 @@ var nextjsPackageJsonRules = {
|
|
|
6528
7269
|
};
|
|
6529
7270
|
|
|
6530
7271
|
// eslint/rules/husky/husky-hook.ts
|
|
6531
|
-
import { existsSync as
|
|
6532
|
-
import
|
|
7272
|
+
import { existsSync as existsSync6, readFileSync as readFileSync12 } from "fs";
|
|
7273
|
+
import path58 from "path";
|
|
6533
7274
|
var VITEST_CONFIG_NAMES2 = [
|
|
6534
7275
|
"vitest.config.ts",
|
|
6535
7276
|
"vitest.config.mts",
|
|
@@ -6555,15 +7296,15 @@ var huskyHookRule = {
|
|
|
6555
7296
|
const root = node.body;
|
|
6556
7297
|
if (root.type !== "Object") return;
|
|
6557
7298
|
if (!context.filename.endsWith("package.json")) return;
|
|
6558
|
-
const hookPath =
|
|
6559
|
-
if (!
|
|
7299
|
+
const hookPath = path58.join(context.cwd, ".husky", "pre-commit");
|
|
7300
|
+
if (!existsSync6(hookPath)) {
|
|
6560
7301
|
context.report({
|
|
6561
7302
|
node,
|
|
6562
7303
|
message: "No .husky/pre-commit hook found. Configure husky to run checks before commits."
|
|
6563
7304
|
});
|
|
6564
7305
|
return;
|
|
6565
7306
|
}
|
|
6566
|
-
const content =
|
|
7307
|
+
const content = readFileSync12(hookPath, "utf8");
|
|
6567
7308
|
const scripts = root.members.find((member) => memberName5(member) === "scripts");
|
|
6568
7309
|
const scriptNames = new Set(scripts?.value.type === "Object" ? scripts.value.members.map(memberName5) : []);
|
|
6569
7310
|
const requireNamedScript = (name) => {
|
|
@@ -6579,7 +7320,7 @@ var huskyHookRule = {
|
|
|
6579
7320
|
}
|
|
6580
7321
|
requireNamedScript("typecheck");
|
|
6581
7322
|
requireNamedScript("test:unit:coverage");
|
|
6582
|
-
const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) =>
|
|
7323
|
+
const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) => existsSync6(path58.join(context.cwd, name)));
|
|
6583
7324
|
if (vitestConfigName !== void 0) {
|
|
6584
7325
|
const coverageIndex = content.indexOf("npm run test:unit:coverage");
|
|
6585
7326
|
const localAddIndex = content.indexOf(`git add ${vitestConfigName}`);
|
|
@@ -6593,8 +7334,8 @@ var huskyHookRule = {
|
|
|
6593
7334
|
if (!content.includes("libyear --limit-major-individual=1")) {
|
|
6594
7335
|
context.report({ node, message: ".husky/pre-commit must run npx libyear --limit-major-individual=1." });
|
|
6595
7336
|
}
|
|
6596
|
-
const suppressionsPath =
|
|
6597
|
-
if (
|
|
7337
|
+
const suppressionsPath = path58.join(context.cwd, "eslint-suppressions.json");
|
|
7338
|
+
if (existsSync6(suppressionsPath)) {
|
|
6598
7339
|
requireNamedScript("lint:prune");
|
|
6599
7340
|
const pruneIndex = content.indexOf("npm run lint:prune");
|
|
6600
7341
|
const localAddIndex = content.indexOf("git add eslint-suppressions.json");
|
|
@@ -6624,73 +7365,33 @@ var huskyRules = {
|
|
|
6624
7365
|
"husky-hook": huskyHookRule
|
|
6625
7366
|
};
|
|
6626
7367
|
|
|
6627
|
-
// eslint/rules/vulyk/
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
}
|
|
6631
|
-
function dependency(root, sectionName) {
|
|
6632
|
-
if (root.type !== "Object") return void 0;
|
|
6633
|
-
const section = root.members.find((member) => memberName6(member) === sectionName);
|
|
6634
|
-
if (section?.value.type !== "Object") return void 0;
|
|
6635
|
-
return section.value.members.find((member) => memberName6(member) === "vulyk");
|
|
6636
|
-
}
|
|
6637
|
-
var vulykDependencyRule = {
|
|
6638
|
-
meta: {
|
|
6639
|
-
schema: [],
|
|
6640
|
-
type: "problem",
|
|
6641
|
-
docs: {
|
|
6642
|
-
description: "Require vulyk in devDependencies so its typed config and CLI use the pinned package."
|
|
6643
|
-
}
|
|
6644
|
-
},
|
|
6645
|
-
create(context) {
|
|
6646
|
-
return {
|
|
6647
|
-
Document(node) {
|
|
6648
|
-
const runtimeDependency = dependency(node.body, "dependencies");
|
|
6649
|
-
const developmentDependency = dependency(node.body, "devDependencies");
|
|
6650
|
-
if (runtimeDependency) {
|
|
6651
|
-
context.report({
|
|
6652
|
-
node: runtimeDependency,
|
|
6653
|
-
message: "vulyk must be listed in devDependencies, not dependencies."
|
|
6654
|
-
});
|
|
6655
|
-
}
|
|
6656
|
-
if (!developmentDependency && !runtimeDependency) {
|
|
6657
|
-
context.report({
|
|
6658
|
-
node,
|
|
6659
|
-
message: "vulyk must be listed in package.json as a devDependency."
|
|
6660
|
-
});
|
|
6661
|
-
}
|
|
6662
|
-
}
|
|
6663
|
-
};
|
|
6664
|
-
}
|
|
6665
|
-
};
|
|
6666
|
-
|
|
6667
|
-
// eslint/rules/vulyk/vulyk-docs.ts
|
|
6668
|
-
import { existsSync as existsSync6, readFileSync as readFileSync11 } from "fs";
|
|
6669
|
-
import path53 from "path";
|
|
7368
|
+
// eslint/rules/vulyk/tracked-docs.ts
|
|
7369
|
+
import { existsSync as existsSync7, readFileSync as readFileSync13 } from "fs";
|
|
7370
|
+
import path59 from "path";
|
|
6670
7371
|
var PASIKA_REPO = "Bredansky/pasika";
|
|
6671
|
-
var
|
|
7372
|
+
var BASE_REQUIRED_TRACKED_DOCS = [
|
|
6672
7373
|
{ name: "documentation-guide", path: "docs/documentation-guide" },
|
|
6673
7374
|
{ name: "pasika-adoption-guide", path: "docs/pasika-adoption-guide" },
|
|
6674
7375
|
{ name: "repository-policy", path: "docs/repository-policy.md" }
|
|
6675
7376
|
];
|
|
6676
|
-
var
|
|
7377
|
+
var NEXTJS_REQUIRED_TRACKED_DOCS = [
|
|
6677
7378
|
{ name: "next-codebase-guide", path: "docs/next-codebase-guide" },
|
|
6678
7379
|
{ name: "next-tailwind-guide", path: "docs/next-tailwind-guide" }
|
|
6679
7380
|
];
|
|
6680
|
-
function
|
|
7381
|
+
function memberName6(member) {
|
|
6681
7382
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
6682
7383
|
}
|
|
6683
7384
|
function hasDependency(root, name) {
|
|
6684
|
-
const section = root.members.find((member) =>
|
|
7385
|
+
const section = root.members.find((member) => memberName6(member) === "dependencies");
|
|
6685
7386
|
if (section?.value.type !== "Object") return false;
|
|
6686
|
-
return section.value.members.some((member) =>
|
|
7387
|
+
return section.value.members.some((member) => memberName6(member) === name);
|
|
6687
7388
|
}
|
|
6688
|
-
var
|
|
7389
|
+
var trackedDocsRule = {
|
|
6689
7390
|
meta: {
|
|
6690
7391
|
schema: [],
|
|
6691
7392
|
type: "problem",
|
|
6692
7393
|
docs: {
|
|
6693
|
-
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."
|
|
6694
7395
|
}
|
|
6695
7396
|
},
|
|
6696
7397
|
create(context) {
|
|
@@ -6699,34 +7400,34 @@ var vulykDocsRule = {
|
|
|
6699
7400
|
if (!context.filename.endsWith("package.json")) return;
|
|
6700
7401
|
const root = node.body;
|
|
6701
7402
|
if (root.type !== "Object") return;
|
|
6702
|
-
const projectRoot =
|
|
6703
|
-
const configPath =
|
|
6704
|
-
if (!
|
|
7403
|
+
const projectRoot = path59.dirname(path59.resolve(context.filename));
|
|
7404
|
+
const configPath = path59.join(projectRoot, "vulyk.config.ts");
|
|
7405
|
+
if (!existsSync7(configPath)) {
|
|
6705
7406
|
context.report({
|
|
6706
7407
|
node,
|
|
6707
|
-
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."
|
|
6708
7409
|
});
|
|
6709
7410
|
return;
|
|
6710
7411
|
}
|
|
6711
|
-
const config =
|
|
7412
|
+
const config = readFileSync13(configPath, "utf8");
|
|
6712
7413
|
if (!config.includes(PASIKA_REPO)) {
|
|
6713
7414
|
context.report({
|
|
6714
7415
|
node,
|
|
6715
|
-
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."
|
|
6716
7417
|
});
|
|
6717
7418
|
return;
|
|
6718
7419
|
}
|
|
6719
|
-
const
|
|
6720
|
-
for (const
|
|
6721
|
-
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)) {
|
|
6722
7423
|
context.report({
|
|
6723
7424
|
node,
|
|
6724
|
-
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}).`
|
|
6725
7426
|
});
|
|
6726
7427
|
}
|
|
6727
7428
|
}
|
|
6728
|
-
const agentsPath =
|
|
6729
|
-
if (!
|
|
7429
|
+
const agentsPath = path59.join(projectRoot, "AGENTS.md");
|
|
7430
|
+
if (!existsSync7(agentsPath)) {
|
|
6730
7431
|
context.report({
|
|
6731
7432
|
node,
|
|
6732
7433
|
message: "No AGENTS.md found. Run npx vulyk agents to generate the agent file that routes to the tracked docs."
|
|
@@ -6737,10 +7438,50 @@ var vulykDocsRule = {
|
|
|
6737
7438
|
}
|
|
6738
7439
|
};
|
|
6739
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
|
+
|
|
6740
7481
|
// eslint/rules/vulyk/index.ts
|
|
6741
7482
|
var vulykRules = {
|
|
6742
7483
|
"vulyk-dependency": vulykDependencyRule,
|
|
6743
|
-
"
|
|
7484
|
+
"tracked-docs": trackedDocsRule
|
|
6744
7485
|
};
|
|
6745
7486
|
|
|
6746
7487
|
// eslint/index.ts
|
|
@@ -6782,6 +7523,10 @@ var pasikaNextjsAppRules = {
|
|
|
6782
7523
|
"cva-boolean-variants": cvaBooleanVariantsRule,
|
|
6783
7524
|
"cross-feature-import": crossFeatureImportRule,
|
|
6784
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,
|
|
6785
7530
|
"hook-complexity": hookComplexityRule,
|
|
6786
7531
|
"locale-dotted-path": localeDottedPathRule,
|
|
6787
7532
|
"locales-location": localesLocationRule,
|
|
@@ -6886,6 +7631,11 @@ var zirkaConfig = {
|
|
|
6886
7631
|
plugins: { pasika: pasikaPlugin },
|
|
6887
7632
|
rules: { "pasika/zirka-baseline": "error" }
|
|
6888
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
|
+
};
|
|
6889
7639
|
var documentationConfig = {
|
|
6890
7640
|
files: ["docs/**/*.md"],
|
|
6891
7641
|
// vulyk-generated agent files are not authored docs: with per-directory
|
|
@@ -6915,6 +7665,7 @@ var pasikaNextjsAppWithDiagnostic = (preset) => {
|
|
|
6915
7665
|
var pasikaNextjsApp = pasikaNextjsAppWithDiagnostic([
|
|
6916
7666
|
...pasikaApp,
|
|
6917
7667
|
pasikaNextjsAppPackageJsonConfig,
|
|
7668
|
+
nextjsHelperConfig,
|
|
6918
7669
|
pasikaNextjsAppConfig,
|
|
6919
7670
|
tailwindStructureRules,
|
|
6920
7671
|
tailwindImportGraph
|