@slip-stream-kit/eslint-plugin 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +325 -11
- package/dist/index.js.map +4 -4
- package/dist/rules/feature-public-api/feature-public-api.d.ts +8 -0
- package/dist/rules/feature-public-api/index.d.ts +1 -0
- package/dist/rules/layer-imports/index.d.ts +1 -0
- package/dist/rules/layer-imports/layer-imports.d.ts +13 -0
- package/dist/rules/no-feature-self-import/index.d.ts +1 -0
- package/dist/rules/no-feature-self-import/no-feature-self-import.d.ts +8 -0
- package/dist/utils/import-target.d.ts +35 -0
- package/package.json +2 -2
- package/readme.md +91 -0
package/dist/index.js
CHANGED
|
@@ -1634,6 +1634,238 @@ var e2eTopLevelDescribe = {
|
|
|
1634
1634
|
}
|
|
1635
1635
|
};
|
|
1636
1636
|
|
|
1637
|
+
// src/utils/import-target.ts
|
|
1638
|
+
import path4 from "node:path";
|
|
1639
|
+
var DEFAULT_ROOT_ALIAS = "#root";
|
|
1640
|
+
var INDEX_FILE = /^index(?:\.[cm]?[jt]sx?)?$/;
|
|
1641
|
+
var literalValue = (source) => {
|
|
1642
|
+
if (source === null || source === void 0) {
|
|
1643
|
+
return null;
|
|
1644
|
+
}
|
|
1645
|
+
const value = source.type === "TSLiteralType" ? source.literal?.value : source.value;
|
|
1646
|
+
return typeof value === "string" ? value : null;
|
|
1647
|
+
};
|
|
1648
|
+
var allSpecifiersAreTypes = (specifiers, key) => {
|
|
1649
|
+
return specifiers !== void 0 && specifiers.length > 0 && specifiers.every((specifier) => {
|
|
1650
|
+
return specifier[key] === "type";
|
|
1651
|
+
});
|
|
1652
|
+
};
|
|
1653
|
+
var readImportSource = (node) => {
|
|
1654
|
+
const candidate = node;
|
|
1655
|
+
const sourceNode = candidate.type === "TSImportType" ? candidate.source ?? candidate.argument : candidate.source;
|
|
1656
|
+
const specifier = literalValue(sourceNode);
|
|
1657
|
+
if (specifier === null || sourceNode === null || sourceNode === void 0) {
|
|
1658
|
+
return null;
|
|
1659
|
+
}
|
|
1660
|
+
const typeOnly = candidate.type === "TSImportType" || candidate.importKind === "type" || candidate.exportKind === "type" || allSpecifiersAreTypes(candidate.specifiers, candidate.type === "ImportDeclaration" ? "importKind" : "exportKind");
|
|
1661
|
+
return { specifier, node: sourceNode, typeOnly };
|
|
1662
|
+
};
|
|
1663
|
+
var srcDirOf = (filename) => {
|
|
1664
|
+
const packageRoot = findPackageRoot(filename);
|
|
1665
|
+
if (packageRoot !== null) {
|
|
1666
|
+
return filename.startsWith(`${packageRoot}/src/`) ? `${packageRoot}/src` : null;
|
|
1667
|
+
}
|
|
1668
|
+
const index = filename.lastIndexOf("/src/");
|
|
1669
|
+
return index >= 0 ? filename.slice(0, index + "/src".length) : null;
|
|
1670
|
+
};
|
|
1671
|
+
var resolveUnderSrc = (filename, srcDir, specifier, rootAlias) => {
|
|
1672
|
+
let absolute;
|
|
1673
|
+
if (specifier === rootAlias || specifier.startsWith(`${rootAlias}/`)) {
|
|
1674
|
+
absolute = path4.posix.join(srcDir, specifier.slice(rootAlias.length));
|
|
1675
|
+
} else if (specifier.startsWith(".")) {
|
|
1676
|
+
absolute = path4.posix.resolve(path4.posix.dirname(filename), specifier);
|
|
1677
|
+
} else {
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
if (!absolute.startsWith(`${srcDir}/`)) {
|
|
1681
|
+
return null;
|
|
1682
|
+
}
|
|
1683
|
+
return absolute.slice(srcDir.length + 1).split("/");
|
|
1684
|
+
};
|
|
1685
|
+
var fileDirsUnderSrc = (filename, srcDir) => {
|
|
1686
|
+
return filename.slice(srcDir.length + 1).split("/").slice(0, -1);
|
|
1687
|
+
};
|
|
1688
|
+
var isFeaturePublicApi = (target) => {
|
|
1689
|
+
return target.length === 2 || target.length === 3 && INDEX_FILE.test(target[2]);
|
|
1690
|
+
};
|
|
1691
|
+
var featureNameOf = (segments) => {
|
|
1692
|
+
return segments[0] === "features" && segments.length >= 2 ? segments[1] : null;
|
|
1693
|
+
};
|
|
1694
|
+
var IMPORT_SELECTORS = [
|
|
1695
|
+
"ImportDeclaration",
|
|
1696
|
+
"ExportNamedDeclaration",
|
|
1697
|
+
"ExportAllDeclaration",
|
|
1698
|
+
"ImportExpression",
|
|
1699
|
+
"TSImportType"
|
|
1700
|
+
];
|
|
1701
|
+
var UNLAYERED_TYPES = /* @__PURE__ */ new Set(["backend", "e2e"]);
|
|
1702
|
+
var isLayeredFile = (filename, ignore) => {
|
|
1703
|
+
if (ignore.length > 0 && matchesAnyGlob(filename, ignore)) {
|
|
1704
|
+
return false;
|
|
1705
|
+
}
|
|
1706
|
+
const packageRoot = findPackageRoot(filename);
|
|
1707
|
+
return packageRoot === null || !UNLAYERED_TYPES.has(resolvePackageType(packageRoot));
|
|
1708
|
+
};
|
|
1709
|
+
|
|
1710
|
+
// src/rules/feature-public-api/feature-public-api.ts
|
|
1711
|
+
var featurePublicApi = {
|
|
1712
|
+
meta: {
|
|
1713
|
+
type: "problem",
|
|
1714
|
+
docs: {
|
|
1715
|
+
description: "Require code outside a feature to import it only through its public API (`features/<name>` / its `index`), whatever the path form.",
|
|
1716
|
+
recommended: true,
|
|
1717
|
+
url: "https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin#feature-public-api"
|
|
1718
|
+
},
|
|
1719
|
+
schema: [
|
|
1720
|
+
{
|
|
1721
|
+
type: "object",
|
|
1722
|
+
properties: {
|
|
1723
|
+
rootAlias: { type: "string", description: "The alias that maps to `src/` (default `#root`)." },
|
|
1724
|
+
ignore: {
|
|
1725
|
+
type: "array",
|
|
1726
|
+
items: { type: "string" },
|
|
1727
|
+
description: "Optional globs; the rule is skipped for matching files."
|
|
1728
|
+
}
|
|
1729
|
+
},
|
|
1730
|
+
additionalProperties: false
|
|
1731
|
+
}
|
|
1732
|
+
],
|
|
1733
|
+
messages: {
|
|
1734
|
+
deepImport: "`{{specifier}}` reaches into the internals of feature `{{feature}}`. Import from its public API `{{publicApi}}` instead, exporting what you need from `features/{{feature}}/index.ts`."
|
|
1735
|
+
}
|
|
1736
|
+
},
|
|
1737
|
+
create(context) {
|
|
1738
|
+
const options = context.options[0] ?? {};
|
|
1739
|
+
const filename = context.filename.split("\\").join("/");
|
|
1740
|
+
const srcDir = srcDirOf(filename);
|
|
1741
|
+
if (srcDir === null || !isLayeredFile(filename, options.ignore ?? [])) {
|
|
1742
|
+
return {};
|
|
1743
|
+
}
|
|
1744
|
+
const rootAlias = options.rootAlias ?? DEFAULT_ROOT_ALIAS;
|
|
1745
|
+
const ownFeature = featureNameOf(fileDirsUnderSrc(filename, srcDir));
|
|
1746
|
+
const check = (node) => {
|
|
1747
|
+
const source = readImportSource(node);
|
|
1748
|
+
const target = source === null ? null : resolveUnderSrc(filename, srcDir, source.specifier, rootAlias);
|
|
1749
|
+
const feature = target === null ? null : featureNameOf(target);
|
|
1750
|
+
if (source === null || feature === null || feature === ownFeature || isFeaturePublicApi(target)) {
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
context.report({
|
|
1754
|
+
node: source.node,
|
|
1755
|
+
messageId: "deepImport",
|
|
1756
|
+
data: { specifier: source.specifier, feature, publicApi: `${rootAlias}/features/${feature}` }
|
|
1757
|
+
});
|
|
1758
|
+
};
|
|
1759
|
+
return Object.fromEntries(
|
|
1760
|
+
IMPORT_SELECTORS.map((selector) => {
|
|
1761
|
+
return [selector, check];
|
|
1762
|
+
})
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
|
|
1767
|
+
// src/rules/layer-imports/layer-imports.ts
|
|
1768
|
+
var TEST_FILE2 = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
1769
|
+
var isTestFile = (filename) => {
|
|
1770
|
+
return filename.includes("/__tests__/") || TEST_FILE2.test(filename);
|
|
1771
|
+
};
|
|
1772
|
+
var DEFAULT_LAYERS = [
|
|
1773
|
+
["lib", "components", "shared", "assets", "styles", "data"],
|
|
1774
|
+
["core", "entities"],
|
|
1775
|
+
["features"],
|
|
1776
|
+
["pages", "routes"],
|
|
1777
|
+
["app", "renderer", "mobile"]
|
|
1778
|
+
];
|
|
1779
|
+
var levelIndex = (layers) => {
|
|
1780
|
+
return new Map(
|
|
1781
|
+
layers.flatMap((level, index) => {
|
|
1782
|
+
return level.map((layer) => {
|
|
1783
|
+
return [layer, index];
|
|
1784
|
+
});
|
|
1785
|
+
})
|
|
1786
|
+
);
|
|
1787
|
+
};
|
|
1788
|
+
var describeOrder = (layers) => {
|
|
1789
|
+
return layers.map((level) => {
|
|
1790
|
+
return level.join(", ");
|
|
1791
|
+
}).join(" \u2192 ");
|
|
1792
|
+
};
|
|
1793
|
+
var layerImports = {
|
|
1794
|
+
meta: {
|
|
1795
|
+
type: "problem",
|
|
1796
|
+
docs: {
|
|
1797
|
+
description: "Allow imports only to the same or a lower layer of `src/` (lib/components \u2192 entities \u2192 features \u2192 pages/routes \u2192 app).",
|
|
1798
|
+
recommended: true,
|
|
1799
|
+
url: "https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin#layer-imports"
|
|
1800
|
+
},
|
|
1801
|
+
schema: [
|
|
1802
|
+
{
|
|
1803
|
+
type: "object",
|
|
1804
|
+
properties: {
|
|
1805
|
+
layers: {
|
|
1806
|
+
type: "array",
|
|
1807
|
+
items: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
1808
|
+
minItems: 1,
|
|
1809
|
+
description: "Folders under `src/`, lowest level first; folders in one inner array share a level."
|
|
1810
|
+
},
|
|
1811
|
+
allowTypeImports: {
|
|
1812
|
+
type: "boolean",
|
|
1813
|
+
description: "Whether type-only imports may point at a higher layer (default true)."
|
|
1814
|
+
},
|
|
1815
|
+
rootAlias: { type: "string", description: "The alias that maps to `src/` (default `#root`)." },
|
|
1816
|
+
ignore: {
|
|
1817
|
+
type: "array",
|
|
1818
|
+
items: { type: "string" },
|
|
1819
|
+
description: "Optional globs; the rule is skipped for matching files."
|
|
1820
|
+
}
|
|
1821
|
+
},
|
|
1822
|
+
additionalProperties: false
|
|
1823
|
+
}
|
|
1824
|
+
],
|
|
1825
|
+
messages: {
|
|
1826
|
+
upward: "`{{fromLayer}}` cannot import `{{specifier}}` from the higher layer `{{toLayer}}`. Layers import only their own level or below: {{order}}. Move the shared code down into a lower layer, or pass it in from above."
|
|
1827
|
+
}
|
|
1828
|
+
},
|
|
1829
|
+
create(context) {
|
|
1830
|
+
const options = context.options[0] ?? {};
|
|
1831
|
+
const filename = context.filename.split("\\").join("/");
|
|
1832
|
+
const srcDir = srcDirOf(filename);
|
|
1833
|
+
if (srcDir === null || isTestFile(filename) || !isLayeredFile(filename, options.ignore ?? [])) {
|
|
1834
|
+
return {};
|
|
1835
|
+
}
|
|
1836
|
+
const layers = options.layers ?? DEFAULT_LAYERS;
|
|
1837
|
+
const levels = levelIndex(layers);
|
|
1838
|
+
const fromLayer = fileDirsUnderSrc(filename, srcDir)[0];
|
|
1839
|
+
const fromLevel = fromLayer === void 0 ? void 0 : levels.get(fromLayer);
|
|
1840
|
+
if (fromLayer === void 0 || fromLevel === void 0) {
|
|
1841
|
+
return {};
|
|
1842
|
+
}
|
|
1843
|
+
const allowTypeImports = options.allowTypeImports ?? true;
|
|
1844
|
+
const rootAlias = options.rootAlias ?? DEFAULT_ROOT_ALIAS;
|
|
1845
|
+
const check = (node) => {
|
|
1846
|
+
const source = readImportSource(node);
|
|
1847
|
+
if (source === null || allowTypeImports && source.typeOnly) {
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
const toLayer = resolveUnderSrc(filename, srcDir, source.specifier, rootAlias)?.[0];
|
|
1851
|
+
const toLevel = toLayer === void 0 ? void 0 : levels.get(toLayer);
|
|
1852
|
+
if (toLevel === void 0 || toLevel <= fromLevel) {
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
context.report({
|
|
1856
|
+
node: source.node,
|
|
1857
|
+
messageId: "upward",
|
|
1858
|
+
data: { fromLayer, toLayer, specifier: source.specifier, order: describeOrder(layers) }
|
|
1859
|
+
});
|
|
1860
|
+
};
|
|
1861
|
+
return Object.fromEntries(
|
|
1862
|
+
IMPORT_SELECTORS.map((selector) => {
|
|
1863
|
+
return [selector, check];
|
|
1864
|
+
})
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
|
|
1637
1869
|
// src/rules/max-components-per-file/max-components-per-file.ts
|
|
1638
1870
|
var DEFAULT_MAX_COMPONENTS = 4;
|
|
1639
1871
|
var ANONYMOUS_NAME2 = "component";
|
|
@@ -2122,6 +2354,72 @@ var maxJsxReturnSize = {
|
|
|
2122
2354
|
}
|
|
2123
2355
|
};
|
|
2124
2356
|
|
|
2357
|
+
// src/rules/no-feature-self-import/no-feature-self-import.ts
|
|
2358
|
+
var CONSUMER_DIRS = /* @__PURE__ */ new Set(["__tests__", "__stories__"]);
|
|
2359
|
+
var noFeatureSelfImport = {
|
|
2360
|
+
meta: {
|
|
2361
|
+
type: "problem",
|
|
2362
|
+
docs: {
|
|
2363
|
+
description: "Forbid a feature from importing itself through its own public API, which closes an import cycle through `index.ts`.",
|
|
2364
|
+
recommended: true,
|
|
2365
|
+
url: "https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin#no-feature-self-import"
|
|
2366
|
+
},
|
|
2367
|
+
schema: [
|
|
2368
|
+
{
|
|
2369
|
+
type: "object",
|
|
2370
|
+
properties: {
|
|
2371
|
+
rootAlias: { type: "string", description: "The alias that maps to `src/` (default `#root`)." },
|
|
2372
|
+
ignore: {
|
|
2373
|
+
type: "array",
|
|
2374
|
+
items: { type: "string" },
|
|
2375
|
+
description: "Optional globs; the rule is skipped for matching files."
|
|
2376
|
+
}
|
|
2377
|
+
},
|
|
2378
|
+
additionalProperties: false
|
|
2379
|
+
}
|
|
2380
|
+
],
|
|
2381
|
+
messages: {
|
|
2382
|
+
selfImport: "`{{specifier}}` imports feature `{{feature}}` through its own public API from inside it, which makes an import cycle via `index.ts`. Import the module that defines it with a relative path instead.",
|
|
2383
|
+
selfTypeImport: "`{{specifier}}` reads types of feature `{{feature}}` through its own public API from inside it. Import them from the module that defines them with a relative path; the public API is for code outside the feature."
|
|
2384
|
+
}
|
|
2385
|
+
},
|
|
2386
|
+
create(context) {
|
|
2387
|
+
const options = context.options[0] ?? {};
|
|
2388
|
+
const filename = context.filename.split("\\").join("/");
|
|
2389
|
+
const srcDir = srcDirOf(filename);
|
|
2390
|
+
if (srcDir === null || !isLayeredFile(filename, options.ignore ?? [])) {
|
|
2391
|
+
return {};
|
|
2392
|
+
}
|
|
2393
|
+
const dirs = fileDirsUnderSrc(filename, srcDir);
|
|
2394
|
+
const ownFeature = featureNameOf(dirs);
|
|
2395
|
+
const isConsumer = dirs.some((dir) => {
|
|
2396
|
+
return CONSUMER_DIRS.has(dir);
|
|
2397
|
+
});
|
|
2398
|
+
if (ownFeature === null || isConsumer) {
|
|
2399
|
+
return {};
|
|
2400
|
+
}
|
|
2401
|
+
const rootAlias = options.rootAlias ?? DEFAULT_ROOT_ALIAS;
|
|
2402
|
+
const check = (node) => {
|
|
2403
|
+
const source = readImportSource(node);
|
|
2404
|
+
const target = source === null ? null : resolveUnderSrc(filename, srcDir, source.specifier, rootAlias);
|
|
2405
|
+
if (source === null || target === null || featureNameOf(target) !== ownFeature || !isFeaturePublicApi(target)) {
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
context.report({
|
|
2409
|
+
node: source.node,
|
|
2410
|
+
// A type-only import is erased at build time, so it closes no runtime cycle.
|
|
2411
|
+
messageId: source.typeOnly ? "selfTypeImport" : "selfImport",
|
|
2412
|
+
data: { specifier: source.specifier, feature: ownFeature }
|
|
2413
|
+
});
|
|
2414
|
+
};
|
|
2415
|
+
return Object.fromEntries(
|
|
2416
|
+
IMPORT_SELECTORS.map((selector) => {
|
|
2417
|
+
return [selector, check];
|
|
2418
|
+
})
|
|
2419
|
+
);
|
|
2420
|
+
}
|
|
2421
|
+
};
|
|
2422
|
+
|
|
2125
2423
|
// src/rules/package-structure/package-structure.ts
|
|
2126
2424
|
var DEFAULT_ENTRIES = {
|
|
2127
2425
|
frontend: {
|
|
@@ -2607,10 +2905,10 @@ var propsTypeReference = {
|
|
|
2607
2905
|
|
|
2608
2906
|
// src/rules/require-component-stories/require-component-stories.ts
|
|
2609
2907
|
import { existsSync } from "node:fs";
|
|
2610
|
-
import
|
|
2908
|
+
import path6 from "node:path";
|
|
2611
2909
|
|
|
2612
2910
|
// src/utils/story-path.ts
|
|
2613
|
-
import
|
|
2911
|
+
import path5 from "node:path";
|
|
2614
2912
|
var DEFAULT_STORY_PATH_OPTIONS = {
|
|
2615
2913
|
storiesDir: "__stories__",
|
|
2616
2914
|
storySuffix: ".stories",
|
|
@@ -2629,10 +2927,10 @@ var parse = (filePath) => {
|
|
|
2629
2927
|
const normalized = toPosix(filePath);
|
|
2630
2928
|
const segments = normalized.split("/");
|
|
2631
2929
|
const basename = segments[segments.length - 1] ?? "";
|
|
2632
|
-
const ext =
|
|
2930
|
+
const ext = path5.posix.extname(basename);
|
|
2633
2931
|
const base = ext ? basename.slice(0, -ext.length) : basename;
|
|
2634
2932
|
return {
|
|
2635
|
-
dir:
|
|
2933
|
+
dir: path5.posix.dirname(normalized),
|
|
2636
2934
|
base,
|
|
2637
2935
|
ext,
|
|
2638
2936
|
parent: segments[segments.length - 2] ?? "",
|
|
@@ -2674,10 +2972,10 @@ var deriveExpectedStoryPaths = (filePath, opts) => {
|
|
|
2674
2972
|
return [];
|
|
2675
2973
|
}
|
|
2676
2974
|
const parsed = parse(filePath);
|
|
2677
|
-
const storyBaseDir = classification.mode === "feature-root" ?
|
|
2678
|
-
const storyDir =
|
|
2975
|
+
const storyBaseDir = classification.mode === "feature-root" ? path5.posix.dirname(parsed.dir) : parsed.dir;
|
|
2976
|
+
const storyDir = path5.posix.join(storyBaseDir, options.storiesDir);
|
|
2679
2977
|
return options.storyExtensions.map((extension) => {
|
|
2680
|
-
return
|
|
2978
|
+
return path5.posix.join(storyDir, `${parsed.base}${options.storySuffix}${extension}`);
|
|
2681
2979
|
});
|
|
2682
2980
|
};
|
|
2683
2981
|
|
|
@@ -2797,7 +3095,7 @@ var requireComponentStories = {
|
|
|
2797
3095
|
node: program,
|
|
2798
3096
|
messageId: "missingStory",
|
|
2799
3097
|
data: {
|
|
2800
|
-
component:
|
|
3098
|
+
component: path6.posix.basename(context.filename.split("\\").join("/")),
|
|
2801
3099
|
expected: candidates[0] ?? ""
|
|
2802
3100
|
}
|
|
2803
3101
|
});
|
|
@@ -3083,7 +3381,7 @@ var requireJsdocExample = {
|
|
|
3083
3381
|
};
|
|
3084
3382
|
|
|
3085
3383
|
// src/rules/test-location/test-location.ts
|
|
3086
|
-
var
|
|
3384
|
+
var TEST_FILE3 = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
3087
3385
|
var SPEC_SUFFIX = /\.spec(\.[cm]?[jt]sx?)$/;
|
|
3088
3386
|
var TESTS_DIR = "__tests__";
|
|
3089
3387
|
var FEATURES_DIR = "features";
|
|
@@ -3116,7 +3414,7 @@ var locationViolation = (dirs, basename) => {
|
|
|
3116
3414
|
};
|
|
3117
3415
|
};
|
|
3118
3416
|
var findViolations2 = (filename, ignore) => {
|
|
3119
|
-
if (!
|
|
3417
|
+
if (!TEST_FILE3.test(filename) || ignore.length > 0 && matchesAnyGlob(filename, ignore)) {
|
|
3120
3418
|
return [];
|
|
3121
3419
|
}
|
|
3122
3420
|
const packageRoot = findPackageRoot(filename);
|
|
@@ -3191,6 +3489,9 @@ var rules = {
|
|
|
3191
3489
|
"require-jsdoc-example": requireJsdocExample,
|
|
3192
3490
|
"package-structure": packageStructure,
|
|
3193
3491
|
"test-location": testLocation,
|
|
3492
|
+
"layer-imports": layerImports,
|
|
3493
|
+
"feature-public-api": featurePublicApi,
|
|
3494
|
+
"no-feature-self-import": noFeatureSelfImport,
|
|
3194
3495
|
"e2e-file-layout": e2eFileLayout,
|
|
3195
3496
|
"e2e-file-order": e2eFileOrder,
|
|
3196
3497
|
"e2e-page-object-member-order": e2ePageObjectMemberOrder,
|
|
@@ -3205,7 +3506,7 @@ var PLUGIN_NAME = "@wl";
|
|
|
3205
3506
|
var plugin = {
|
|
3206
3507
|
meta: {
|
|
3207
3508
|
name: "@wl/eslint-plugin",
|
|
3208
|
-
version: "0.
|
|
3509
|
+
version: "0.18.1"
|
|
3209
3510
|
},
|
|
3210
3511
|
rules,
|
|
3211
3512
|
configs: {}
|
|
@@ -3306,6 +3607,19 @@ plugin.configs.recommended = [
|
|
|
3306
3607
|
[`${PLUGIN_NAME}/test-location`]: ["error", { ignore: ["**/*.e2e.test.*"] }]
|
|
3307
3608
|
}
|
|
3308
3609
|
},
|
|
3610
|
+
// Import-path rules read where a specifier lands under `src/`, so they reach every source extension.
|
|
3611
|
+
// They gate themselves on the package type (backend and e2e packages have no UI layering).
|
|
3612
|
+
{
|
|
3613
|
+
files: ["**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"],
|
|
3614
|
+
plugins: {
|
|
3615
|
+
[PLUGIN_NAME]: plugin
|
|
3616
|
+
},
|
|
3617
|
+
rules: {
|
|
3618
|
+
[`${PLUGIN_NAME}/layer-imports`]: "error",
|
|
3619
|
+
[`${PLUGIN_NAME}/feature-public-api`]: "error",
|
|
3620
|
+
[`${PLUGIN_NAME}/no-feature-self-import`]: "error"
|
|
3621
|
+
}
|
|
3622
|
+
},
|
|
3309
3623
|
// The e2e rules gate themselves on the package type, so a repo-wide glob costs one cached type
|
|
3310
3624
|
// lookup per file elsewhere.
|
|
3311
3625
|
{
|