oxlint-plugin-arch 0.10.0 → 0.11.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/CHANGELOG.md +14 -0
- package/dist/index.js +1 -1
- package/dist/presets/index.js +24 -8
- package/dist/presets/next-react-modules-preset.js +24 -8
- package/dist/presets/react-modules-preset.js +24 -8
- package/dist/presets/tanstack-start-react-modules-preset.js +24 -8
- package/dist/rules/module-policy/index.js +41 -1
- package/dist/rules/module-policy/no-unknown.d.ts +6 -0
- package/dist/types/preset.types.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
- `modules/no-unknown` allows `unknown` on the narrowed parameter of a type guard (`function isAppError(error: unknown): error is AppError`) and on the error parameter of a catch callback (`catch: (error: unknown) => E`, `.catch((error: unknown) => …)`). Other uses still fail. This applies to every root.
|
|
6
|
+
- `arch/no-trivial-functions` no longer reports type guards with a predicate return type as pass-through wrappers. Banned names and structurally generic guards are still reported.
|
|
7
|
+
- Package schemas: construction is allowed only in `types/**`. Package `*.types.ts` files outside `types/` are reported again (fix: move them to `src/types/`), matching `arch/no-restricted-files`.
|
|
8
|
+
- Hook filenames accept a React Native platform suffix: `use-chat-draft.web.ts`, `.native.ts`, `.ios.ts` and `.android.ts`. This applies to web roots and packages.
|
|
9
|
+
- A UI kit outside every web root, resolved from `shadcn.ui`, gets the Tailwind checks. Its stylesheet is `tailwind.cssEntryPointsByRoot` for the kit's package folder or architecture root, otherwise the one stylesheet the web roots share. With several web stylesheets and no entry, the preset throws and names `cssEntryPointsByRoot`.
|
|
10
|
+
|
|
11
|
+
| Fix | Rules |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| Auto-fix (`--fix`) | `tailwindcss/*` sort, canonical, shorthand and whitespace rules on a kit package |
|
|
14
|
+
| Move | `arch/no-local-schema-construction` in package `*.types.ts` outside `types/` (to `src/types/`) |
|
|
15
|
+
| Reshape | `tailwindcss/no-conflicting-classes`, `tailwindcss/no-deprecated-classes` on a kit package |
|
|
16
|
+
|
|
3
17
|
## 0.10.0
|
|
4
18
|
|
|
5
19
|
**Breaking:** `packages` roots now get the app rules. Until now they only got the shared baseline (formatting, imports, stock TypeScript/JavaScript rules and `modules/import-boundaries`), so expect new errors in package `src` folders.
|
package/dist/index.js
CHANGED
|
@@ -4089,7 +4089,7 @@ var noTrivialFunctions = defineRule32({
|
|
|
4089
4089
|
}
|
|
4090
4090
|
seen.add(key);
|
|
4091
4091
|
checkHelper(item.node);
|
|
4092
|
-
if (reported.has(item.node.start))
|
|
4092
|
+
if (reported.has(item.node.start) || item.node.returnType?.typeAnnotation.type === "TSTypePredicate")
|
|
4093
4093
|
continue;
|
|
4094
4094
|
const expression = trivialExpression(item.node);
|
|
4095
4095
|
const trivial2 = mode === "legacy" ? declarationsIsTrivialFunction(item.node) : expression ? trivialIsForwarder(item.node, expression) : declarationsIsTrivialFunction(item.node);
|
package/dist/presets/index.js
CHANGED
|
@@ -747,7 +747,6 @@ function presetTypesConfig(context) {
|
|
|
747
747
|
"arch/no-local-schema-construction": ["error", { packages: ["zod"], namespaces: ["z"] }]
|
|
748
748
|
}, [
|
|
749
749
|
...presetScopes([...schemaOwners, ...packageSources], "types/**"),
|
|
750
|
-
...presetScopes(packageSources, "**/*.types.ts"),
|
|
751
750
|
...colocatedTypes,
|
|
752
751
|
...ui
|
|
753
752
|
]),
|
|
@@ -957,8 +956,8 @@ function presetModulesConfig(context) {
|
|
|
957
956
|
"arch/filename-match": [
|
|
958
957
|
"error",
|
|
959
958
|
{
|
|
960
|
-
pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*[.]ts$",
|
|
961
|
-
message: "Hooks use use-domain.ts filenames."
|
|
959
|
+
pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*(?:[.](?:web|native|ios|android))?[.]ts$",
|
|
960
|
+
message: "Hooks use use-domain.ts filenames, with an optional .web, .native, .ios or .android platform suffix."
|
|
962
961
|
}
|
|
963
962
|
],
|
|
964
963
|
"arch/export-file-prefix": "error",
|
|
@@ -1109,9 +1108,8 @@ function presetTestsConfig(context) {
|
|
|
1109
1108
|
|
|
1110
1109
|
// src/configs/react.ts
|
|
1111
1110
|
function presetReactConfig(context) {
|
|
1112
|
-
const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles,
|
|
1111
|
+
const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles, webUiRoots, ui, testFiles, components, css } = context;
|
|
1113
1112
|
const policies = {};
|
|
1114
|
-
const webUi = uiRoots.filter((path) => web.some((root) => root === "." || path === root || path.startsWith(root + "/")));
|
|
1115
1113
|
if (reactFiles.length) {
|
|
1116
1114
|
policies.react = [
|
|
1117
1115
|
presetOverride(reactFiles, {
|
|
@@ -1221,7 +1219,7 @@ function presetReactConfig(context) {
|
|
|
1221
1219
|
},
|
|
1222
1220
|
{
|
|
1223
1221
|
token: "createContext",
|
|
1224
|
-
allowPathPatterns: [...presetScopes(web, "context/"), ...
|
|
1222
|
+
allowPathPatterns: [...presetScopes(web, "context/"), ...webUiRoots.map((path) => `${path}/`)]
|
|
1225
1223
|
}
|
|
1226
1224
|
]
|
|
1227
1225
|
}
|
|
@@ -1291,7 +1289,7 @@ function presetReactConfig(context) {
|
|
|
1291
1289
|
presetOverride(webFiles, {
|
|
1292
1290
|
"shadcn/no-unknown-classes": ["error", { allow: [] }]
|
|
1293
1291
|
}),
|
|
1294
|
-
presetOverride(presetScopes(
|
|
1292
|
+
presetOverride(presetScopes(webUiRoots, "sonner.tsx"), {
|
|
1295
1293
|
"shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
|
|
1296
1294
|
})
|
|
1297
1295
|
];
|
|
@@ -1576,6 +1574,7 @@ function presetResolveContext(options) {
|
|
|
1576
1574
|
throw new Error("shadcn.componentImportSources was renamed to shadcn.components.");
|
|
1577
1575
|
const uiRoots = shadcn.ui === undefined ? web.map((path) => posix2.join(path, "components/ui")) : resolveUi(shadcn.ui);
|
|
1578
1576
|
const ui = presetScopes(uiRoots, "**");
|
|
1577
|
+
const webUiRoots = uiRoots.filter((path) => web.some((webRoot) => webRoot === "." || path === webRoot || path.startsWith(webRoot + "/")));
|
|
1579
1578
|
const testFiles = ["**/{test,tests,e2e}/**/*.{ts,tsx,mts,cts}", "**/*.{test,spec}.{ts,tsx,mts,cts}"];
|
|
1580
1579
|
const safetyFiles = [...appFiles, ...testFiles];
|
|
1581
1580
|
const components = presetScopes([...web, ...packageSources], "components/**/*.tsx");
|
|
@@ -1596,7 +1595,7 @@ function presetResolveContext(options) {
|
|
|
1596
1595
|
posix2.normalize(path.replaceAll("\\", "/")).replace(/\/$/, ""),
|
|
1597
1596
|
entry
|
|
1598
1597
|
]));
|
|
1599
|
-
const
|
|
1598
|
+
const webCss = tailwind === false ? {} : Object.fromEntries(entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) => {
|
|
1600
1599
|
const sourcePath = source(path, entry);
|
|
1601
1600
|
const fallback = entry.framework === "next" ? "app/globals.css" : "application/styles.css";
|
|
1602
1601
|
return [
|
|
@@ -1604,6 +1603,22 @@ function presetResolveContext(options) {
|
|
|
1604
1603
|
resolve3(root, entryPoints[path] ?? tailwind?.cssEntryPoint ?? posix2.join(sourcePath, fallback))
|
|
1605
1604
|
];
|
|
1606
1605
|
}));
|
|
1606
|
+
const webStylesheets = [...new Set(Object.values(webCss))];
|
|
1607
|
+
const kitCss = Object.fromEntries((tailwind === false ? [] : uiRoots.filter((path) => !webUiRoots.includes(path))).map((path) => {
|
|
1608
|
+
const owners = packages.flatMap((packageRoot) => {
|
|
1609
|
+
if (path !== packageRoot && !path.startsWith(packageRoot + "/"))
|
|
1610
|
+
return [];
|
|
1611
|
+
const child = path.slice(packageRoot.length + 1).split("/")[0];
|
|
1612
|
+
return isPackage(packageRoot) || !child ? [packageRoot] : [posix2.join(packageRoot, child), packageRoot];
|
|
1613
|
+
});
|
|
1614
|
+
const entry = owners.map((owner) => entryPoints[owner]).find(Boolean);
|
|
1615
|
+
if (entry)
|
|
1616
|
+
return [path, resolve3(root, entry)];
|
|
1617
|
+
if (webStylesheets.length === 1)
|
|
1618
|
+
return [path, webStylesheets[0]];
|
|
1619
|
+
throw new Error(`The UI kit ${path} needs a Tailwind stylesheet: set tailwind.cssEntryPointsByRoot for ${owners[0] ?? path}.`);
|
|
1620
|
+
}));
|
|
1621
|
+
const css = { ...webCss, ...kitCss };
|
|
1607
1622
|
return {
|
|
1608
1623
|
options,
|
|
1609
1624
|
root,
|
|
@@ -1628,6 +1643,7 @@ function presetResolveContext(options) {
|
|
|
1628
1643
|
utilities,
|
|
1629
1644
|
types,
|
|
1630
1645
|
uiRoots,
|
|
1646
|
+
webUiRoots,
|
|
1631
1647
|
ui,
|
|
1632
1648
|
generated,
|
|
1633
1649
|
testFiles,
|
|
@@ -747,7 +747,6 @@ function presetTypesConfig(context) {
|
|
|
747
747
|
"arch/no-local-schema-construction": ["error", { packages: ["zod"], namespaces: ["z"] }]
|
|
748
748
|
}, [
|
|
749
749
|
...presetScopes([...schemaOwners, ...packageSources], "types/**"),
|
|
750
|
-
...presetScopes(packageSources, "**/*.types.ts"),
|
|
751
750
|
...colocatedTypes,
|
|
752
751
|
...ui
|
|
753
752
|
]),
|
|
@@ -957,8 +956,8 @@ function presetModulesConfig(context) {
|
|
|
957
956
|
"arch/filename-match": [
|
|
958
957
|
"error",
|
|
959
958
|
{
|
|
960
|
-
pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*[.]ts$",
|
|
961
|
-
message: "Hooks use use-domain.ts filenames."
|
|
959
|
+
pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*(?:[.](?:web|native|ios|android))?[.]ts$",
|
|
960
|
+
message: "Hooks use use-domain.ts filenames, with an optional .web, .native, .ios or .android platform suffix."
|
|
962
961
|
}
|
|
963
962
|
],
|
|
964
963
|
"arch/export-file-prefix": "error",
|
|
@@ -1109,9 +1108,8 @@ function presetTestsConfig(context) {
|
|
|
1109
1108
|
|
|
1110
1109
|
// src/configs/react.ts
|
|
1111
1110
|
function presetReactConfig(context) {
|
|
1112
|
-
const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles,
|
|
1111
|
+
const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles, webUiRoots, ui, testFiles, components, css } = context;
|
|
1113
1112
|
const policies = {};
|
|
1114
|
-
const webUi = uiRoots.filter((path) => web.some((root) => root === "." || path === root || path.startsWith(root + "/")));
|
|
1115
1113
|
if (reactFiles.length) {
|
|
1116
1114
|
policies.react = [
|
|
1117
1115
|
presetOverride(reactFiles, {
|
|
@@ -1221,7 +1219,7 @@ function presetReactConfig(context) {
|
|
|
1221
1219
|
},
|
|
1222
1220
|
{
|
|
1223
1221
|
token: "createContext",
|
|
1224
|
-
allowPathPatterns: [...presetScopes(web, "context/"), ...
|
|
1222
|
+
allowPathPatterns: [...presetScopes(web, "context/"), ...webUiRoots.map((path) => `${path}/`)]
|
|
1225
1223
|
}
|
|
1226
1224
|
]
|
|
1227
1225
|
}
|
|
@@ -1291,7 +1289,7 @@ function presetReactConfig(context) {
|
|
|
1291
1289
|
presetOverride(webFiles, {
|
|
1292
1290
|
"shadcn/no-unknown-classes": ["error", { allow: [] }]
|
|
1293
1291
|
}),
|
|
1294
|
-
presetOverride(presetScopes(
|
|
1292
|
+
presetOverride(presetScopes(webUiRoots, "sonner.tsx"), {
|
|
1295
1293
|
"shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
|
|
1296
1294
|
})
|
|
1297
1295
|
];
|
|
@@ -1576,6 +1574,7 @@ function presetResolveContext(options) {
|
|
|
1576
1574
|
throw new Error("shadcn.componentImportSources was renamed to shadcn.components.");
|
|
1577
1575
|
const uiRoots = shadcn.ui === undefined ? web.map((path) => posix2.join(path, "components/ui")) : resolveUi(shadcn.ui);
|
|
1578
1576
|
const ui = presetScopes(uiRoots, "**");
|
|
1577
|
+
const webUiRoots = uiRoots.filter((path) => web.some((webRoot) => webRoot === "." || path === webRoot || path.startsWith(webRoot + "/")));
|
|
1579
1578
|
const testFiles = ["**/{test,tests,e2e}/**/*.{ts,tsx,mts,cts}", "**/*.{test,spec}.{ts,tsx,mts,cts}"];
|
|
1580
1579
|
const safetyFiles = [...appFiles, ...testFiles];
|
|
1581
1580
|
const components = presetScopes([...web, ...packageSources], "components/**/*.tsx");
|
|
@@ -1596,7 +1595,7 @@ function presetResolveContext(options) {
|
|
|
1596
1595
|
posix2.normalize(path.replaceAll("\\", "/")).replace(/\/$/, ""),
|
|
1597
1596
|
entry
|
|
1598
1597
|
]));
|
|
1599
|
-
const
|
|
1598
|
+
const webCss = tailwind === false ? {} : Object.fromEntries(entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) => {
|
|
1600
1599
|
const sourcePath = source(path, entry);
|
|
1601
1600
|
const fallback = entry.framework === "next" ? "app/globals.css" : "application/styles.css";
|
|
1602
1601
|
return [
|
|
@@ -1604,6 +1603,22 @@ function presetResolveContext(options) {
|
|
|
1604
1603
|
resolve3(root, entryPoints[path] ?? tailwind?.cssEntryPoint ?? posix2.join(sourcePath, fallback))
|
|
1605
1604
|
];
|
|
1606
1605
|
}));
|
|
1606
|
+
const webStylesheets = [...new Set(Object.values(webCss))];
|
|
1607
|
+
const kitCss = Object.fromEntries((tailwind === false ? [] : uiRoots.filter((path) => !webUiRoots.includes(path))).map((path) => {
|
|
1608
|
+
const owners = packages.flatMap((packageRoot) => {
|
|
1609
|
+
if (path !== packageRoot && !path.startsWith(packageRoot + "/"))
|
|
1610
|
+
return [];
|
|
1611
|
+
const child = path.slice(packageRoot.length + 1).split("/")[0];
|
|
1612
|
+
return isPackage(packageRoot) || !child ? [packageRoot] : [posix2.join(packageRoot, child), packageRoot];
|
|
1613
|
+
});
|
|
1614
|
+
const entry = owners.map((owner) => entryPoints[owner]).find(Boolean);
|
|
1615
|
+
if (entry)
|
|
1616
|
+
return [path, resolve3(root, entry)];
|
|
1617
|
+
if (webStylesheets.length === 1)
|
|
1618
|
+
return [path, webStylesheets[0]];
|
|
1619
|
+
throw new Error(`The UI kit ${path} needs a Tailwind stylesheet: set tailwind.cssEntryPointsByRoot for ${owners[0] ?? path}.`);
|
|
1620
|
+
}));
|
|
1621
|
+
const css = { ...webCss, ...kitCss };
|
|
1607
1622
|
return {
|
|
1608
1623
|
options,
|
|
1609
1624
|
root,
|
|
@@ -1628,6 +1643,7 @@ function presetResolveContext(options) {
|
|
|
1628
1643
|
utilities,
|
|
1629
1644
|
types,
|
|
1630
1645
|
uiRoots,
|
|
1646
|
+
webUiRoots,
|
|
1631
1647
|
ui,
|
|
1632
1648
|
generated,
|
|
1633
1649
|
testFiles,
|
|
@@ -747,7 +747,6 @@ function presetTypesConfig(context) {
|
|
|
747
747
|
"arch/no-local-schema-construction": ["error", { packages: ["zod"], namespaces: ["z"] }]
|
|
748
748
|
}, [
|
|
749
749
|
...presetScopes([...schemaOwners, ...packageSources], "types/**"),
|
|
750
|
-
...presetScopes(packageSources, "**/*.types.ts"),
|
|
751
750
|
...colocatedTypes,
|
|
752
751
|
...ui
|
|
753
752
|
]),
|
|
@@ -957,8 +956,8 @@ function presetModulesConfig(context) {
|
|
|
957
956
|
"arch/filename-match": [
|
|
958
957
|
"error",
|
|
959
958
|
{
|
|
960
|
-
pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*[.]ts$",
|
|
961
|
-
message: "Hooks use use-domain.ts filenames."
|
|
959
|
+
pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*(?:[.](?:web|native|ios|android))?[.]ts$",
|
|
960
|
+
message: "Hooks use use-domain.ts filenames, with an optional .web, .native, .ios or .android platform suffix."
|
|
962
961
|
}
|
|
963
962
|
],
|
|
964
963
|
"arch/export-file-prefix": "error",
|
|
@@ -1109,9 +1108,8 @@ function presetTestsConfig(context) {
|
|
|
1109
1108
|
|
|
1110
1109
|
// src/configs/react.ts
|
|
1111
1110
|
function presetReactConfig(context) {
|
|
1112
|
-
const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles,
|
|
1111
|
+
const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles, webUiRoots, ui, testFiles, components, css } = context;
|
|
1113
1112
|
const policies = {};
|
|
1114
|
-
const webUi = uiRoots.filter((path) => web.some((root) => root === "." || path === root || path.startsWith(root + "/")));
|
|
1115
1113
|
if (reactFiles.length) {
|
|
1116
1114
|
policies.react = [
|
|
1117
1115
|
presetOverride(reactFiles, {
|
|
@@ -1221,7 +1219,7 @@ function presetReactConfig(context) {
|
|
|
1221
1219
|
},
|
|
1222
1220
|
{
|
|
1223
1221
|
token: "createContext",
|
|
1224
|
-
allowPathPatterns: [...presetScopes(web, "context/"), ...
|
|
1222
|
+
allowPathPatterns: [...presetScopes(web, "context/"), ...webUiRoots.map((path) => `${path}/`)]
|
|
1225
1223
|
}
|
|
1226
1224
|
]
|
|
1227
1225
|
}
|
|
@@ -1291,7 +1289,7 @@ function presetReactConfig(context) {
|
|
|
1291
1289
|
presetOverride(webFiles, {
|
|
1292
1290
|
"shadcn/no-unknown-classes": ["error", { allow: [] }]
|
|
1293
1291
|
}),
|
|
1294
|
-
presetOverride(presetScopes(
|
|
1292
|
+
presetOverride(presetScopes(webUiRoots, "sonner.tsx"), {
|
|
1295
1293
|
"shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
|
|
1296
1294
|
})
|
|
1297
1295
|
];
|
|
@@ -1576,6 +1574,7 @@ function presetResolveContext(options) {
|
|
|
1576
1574
|
throw new Error("shadcn.componentImportSources was renamed to shadcn.components.");
|
|
1577
1575
|
const uiRoots = shadcn.ui === undefined ? web.map((path) => posix2.join(path, "components/ui")) : resolveUi(shadcn.ui);
|
|
1578
1576
|
const ui = presetScopes(uiRoots, "**");
|
|
1577
|
+
const webUiRoots = uiRoots.filter((path) => web.some((webRoot) => webRoot === "." || path === webRoot || path.startsWith(webRoot + "/")));
|
|
1579
1578
|
const testFiles = ["**/{test,tests,e2e}/**/*.{ts,tsx,mts,cts}", "**/*.{test,spec}.{ts,tsx,mts,cts}"];
|
|
1580
1579
|
const safetyFiles = [...appFiles, ...testFiles];
|
|
1581
1580
|
const components = presetScopes([...web, ...packageSources], "components/**/*.tsx");
|
|
@@ -1596,7 +1595,7 @@ function presetResolveContext(options) {
|
|
|
1596
1595
|
posix2.normalize(path.replaceAll("\\", "/")).replace(/\/$/, ""),
|
|
1597
1596
|
entry
|
|
1598
1597
|
]));
|
|
1599
|
-
const
|
|
1598
|
+
const webCss = tailwind === false ? {} : Object.fromEntries(entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) => {
|
|
1600
1599
|
const sourcePath = source(path, entry);
|
|
1601
1600
|
const fallback = entry.framework === "next" ? "app/globals.css" : "application/styles.css";
|
|
1602
1601
|
return [
|
|
@@ -1604,6 +1603,22 @@ function presetResolveContext(options) {
|
|
|
1604
1603
|
resolve3(root, entryPoints[path] ?? tailwind?.cssEntryPoint ?? posix2.join(sourcePath, fallback))
|
|
1605
1604
|
];
|
|
1606
1605
|
}));
|
|
1606
|
+
const webStylesheets = [...new Set(Object.values(webCss))];
|
|
1607
|
+
const kitCss = Object.fromEntries((tailwind === false ? [] : uiRoots.filter((path) => !webUiRoots.includes(path))).map((path) => {
|
|
1608
|
+
const owners = packages.flatMap((packageRoot) => {
|
|
1609
|
+
if (path !== packageRoot && !path.startsWith(packageRoot + "/"))
|
|
1610
|
+
return [];
|
|
1611
|
+
const child = path.slice(packageRoot.length + 1).split("/")[0];
|
|
1612
|
+
return isPackage(packageRoot) || !child ? [packageRoot] : [posix2.join(packageRoot, child), packageRoot];
|
|
1613
|
+
});
|
|
1614
|
+
const entry = owners.map((owner) => entryPoints[owner]).find(Boolean);
|
|
1615
|
+
if (entry)
|
|
1616
|
+
return [path, resolve3(root, entry)];
|
|
1617
|
+
if (webStylesheets.length === 1)
|
|
1618
|
+
return [path, webStylesheets[0]];
|
|
1619
|
+
throw new Error(`The UI kit ${path} needs a Tailwind stylesheet: set tailwind.cssEntryPointsByRoot for ${owners[0] ?? path}.`);
|
|
1620
|
+
}));
|
|
1621
|
+
const css = { ...webCss, ...kitCss };
|
|
1607
1622
|
return {
|
|
1608
1623
|
options,
|
|
1609
1624
|
root,
|
|
@@ -1628,6 +1643,7 @@ function presetResolveContext(options) {
|
|
|
1628
1643
|
utilities,
|
|
1629
1644
|
types,
|
|
1630
1645
|
uiRoots,
|
|
1646
|
+
webUiRoots,
|
|
1631
1647
|
ui,
|
|
1632
1648
|
generated,
|
|
1633
1649
|
testFiles,
|
|
@@ -747,7 +747,6 @@ function presetTypesConfig(context) {
|
|
|
747
747
|
"arch/no-local-schema-construction": ["error", { packages: ["zod"], namespaces: ["z"] }]
|
|
748
748
|
}, [
|
|
749
749
|
...presetScopes([...schemaOwners, ...packageSources], "types/**"),
|
|
750
|
-
...presetScopes(packageSources, "**/*.types.ts"),
|
|
751
750
|
...colocatedTypes,
|
|
752
751
|
...ui
|
|
753
752
|
]),
|
|
@@ -957,8 +956,8 @@ function presetModulesConfig(context) {
|
|
|
957
956
|
"arch/filename-match": [
|
|
958
957
|
"error",
|
|
959
958
|
{
|
|
960
|
-
pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*[.]ts$",
|
|
961
|
-
message: "Hooks use use-domain.ts filenames."
|
|
959
|
+
pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*(?:[.](?:web|native|ios|android))?[.]ts$",
|
|
960
|
+
message: "Hooks use use-domain.ts filenames, with an optional .web, .native, .ios or .android platform suffix."
|
|
962
961
|
}
|
|
963
962
|
],
|
|
964
963
|
"arch/export-file-prefix": "error",
|
|
@@ -1109,9 +1108,8 @@ function presetTestsConfig(context) {
|
|
|
1109
1108
|
|
|
1110
1109
|
// src/configs/react.ts
|
|
1111
1110
|
function presetReactConfig(context) {
|
|
1112
|
-
const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles,
|
|
1111
|
+
const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles, webUiRoots, ui, testFiles, components, css } = context;
|
|
1113
1112
|
const policies = {};
|
|
1114
|
-
const webUi = uiRoots.filter((path) => web.some((root) => root === "." || path === root || path.startsWith(root + "/")));
|
|
1115
1113
|
if (reactFiles.length) {
|
|
1116
1114
|
policies.react = [
|
|
1117
1115
|
presetOverride(reactFiles, {
|
|
@@ -1221,7 +1219,7 @@ function presetReactConfig(context) {
|
|
|
1221
1219
|
},
|
|
1222
1220
|
{
|
|
1223
1221
|
token: "createContext",
|
|
1224
|
-
allowPathPatterns: [...presetScopes(web, "context/"), ...
|
|
1222
|
+
allowPathPatterns: [...presetScopes(web, "context/"), ...webUiRoots.map((path) => `${path}/`)]
|
|
1225
1223
|
}
|
|
1226
1224
|
]
|
|
1227
1225
|
}
|
|
@@ -1291,7 +1289,7 @@ function presetReactConfig(context) {
|
|
|
1291
1289
|
presetOverride(webFiles, {
|
|
1292
1290
|
"shadcn/no-unknown-classes": ["error", { allow: [] }]
|
|
1293
1291
|
}),
|
|
1294
|
-
presetOverride(presetScopes(
|
|
1292
|
+
presetOverride(presetScopes(webUiRoots, "sonner.tsx"), {
|
|
1295
1293
|
"shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
|
|
1296
1294
|
})
|
|
1297
1295
|
];
|
|
@@ -1576,6 +1574,7 @@ function presetResolveContext(options) {
|
|
|
1576
1574
|
throw new Error("shadcn.componentImportSources was renamed to shadcn.components.");
|
|
1577
1575
|
const uiRoots = shadcn.ui === undefined ? web.map((path) => posix2.join(path, "components/ui")) : resolveUi(shadcn.ui);
|
|
1578
1576
|
const ui = presetScopes(uiRoots, "**");
|
|
1577
|
+
const webUiRoots = uiRoots.filter((path) => web.some((webRoot) => webRoot === "." || path === webRoot || path.startsWith(webRoot + "/")));
|
|
1579
1578
|
const testFiles = ["**/{test,tests,e2e}/**/*.{ts,tsx,mts,cts}", "**/*.{test,spec}.{ts,tsx,mts,cts}"];
|
|
1580
1579
|
const safetyFiles = [...appFiles, ...testFiles];
|
|
1581
1580
|
const components = presetScopes([...web, ...packageSources], "components/**/*.tsx");
|
|
@@ -1596,7 +1595,7 @@ function presetResolveContext(options) {
|
|
|
1596
1595
|
posix2.normalize(path.replaceAll("\\", "/")).replace(/\/$/, ""),
|
|
1597
1596
|
entry
|
|
1598
1597
|
]));
|
|
1599
|
-
const
|
|
1598
|
+
const webCss = tailwind === false ? {} : Object.fromEntries(entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) => {
|
|
1600
1599
|
const sourcePath = source(path, entry);
|
|
1601
1600
|
const fallback = entry.framework === "next" ? "app/globals.css" : "application/styles.css";
|
|
1602
1601
|
return [
|
|
@@ -1604,6 +1603,22 @@ function presetResolveContext(options) {
|
|
|
1604
1603
|
resolve3(root, entryPoints[path] ?? tailwind?.cssEntryPoint ?? posix2.join(sourcePath, fallback))
|
|
1605
1604
|
];
|
|
1606
1605
|
}));
|
|
1606
|
+
const webStylesheets = [...new Set(Object.values(webCss))];
|
|
1607
|
+
const kitCss = Object.fromEntries((tailwind === false ? [] : uiRoots.filter((path) => !webUiRoots.includes(path))).map((path) => {
|
|
1608
|
+
const owners = packages.flatMap((packageRoot) => {
|
|
1609
|
+
if (path !== packageRoot && !path.startsWith(packageRoot + "/"))
|
|
1610
|
+
return [];
|
|
1611
|
+
const child = path.slice(packageRoot.length + 1).split("/")[0];
|
|
1612
|
+
return isPackage(packageRoot) || !child ? [packageRoot] : [posix2.join(packageRoot, child), packageRoot];
|
|
1613
|
+
});
|
|
1614
|
+
const entry = owners.map((owner) => entryPoints[owner]).find(Boolean);
|
|
1615
|
+
if (entry)
|
|
1616
|
+
return [path, resolve3(root, entry)];
|
|
1617
|
+
if (webStylesheets.length === 1)
|
|
1618
|
+
return [path, webStylesheets[0]];
|
|
1619
|
+
throw new Error(`The UI kit ${path} needs a Tailwind stylesheet: set tailwind.cssEntryPointsByRoot for ${owners[0] ?? path}.`);
|
|
1620
|
+
}));
|
|
1621
|
+
const css = { ...webCss, ...kitCss };
|
|
1607
1622
|
return {
|
|
1608
1623
|
options,
|
|
1609
1624
|
root,
|
|
@@ -1628,6 +1643,7 @@ function presetResolveContext(options) {
|
|
|
1628
1643
|
utilities,
|
|
1629
1644
|
types,
|
|
1630
1645
|
uiRoots,
|
|
1646
|
+
webUiRoots,
|
|
1631
1647
|
ui,
|
|
1632
1648
|
generated,
|
|
1633
1649
|
testFiles,
|
|
@@ -2195,6 +2195,46 @@ var nextRuntime = defineRule11({
|
|
|
2195
2195
|
}
|
|
2196
2196
|
});
|
|
2197
2197
|
|
|
2198
|
+
// src/rules/module-policy/no-unknown.ts
|
|
2199
|
+
import { defineRule as defineRule12 } from "@oxlint/plugins";
|
|
2200
|
+
var noUnknown = defineRule12({
|
|
2201
|
+
meta: { type: "problem", schema: [], messages: { forbidden: "Use an owned domain type rather than unknown." } },
|
|
2202
|
+
createOnce(context) {
|
|
2203
|
+
function catchName(node) {
|
|
2204
|
+
const key = "key" in node && !(("computed" in node) && node.computed) ? node.key : undefined;
|
|
2205
|
+
return key?.type === "Identifier" && key.name === "catch";
|
|
2206
|
+
}
|
|
2207
|
+
function allowed(node) {
|
|
2208
|
+
const annotation = node.parent;
|
|
2209
|
+
const parameter = annotation?.type === "TSTypeAnnotation" ? annotation.parent : undefined;
|
|
2210
|
+
if (parameter?.type !== "Identifier")
|
|
2211
|
+
return false;
|
|
2212
|
+
const owner = parameter.parent;
|
|
2213
|
+
if (!owner || !("params" in owner) || !owner.params.some((entry) => entry === parameter))
|
|
2214
|
+
return false;
|
|
2215
|
+
const predicate = "returnType" in owner ? owner.returnType?.typeAnnotation : undefined;
|
|
2216
|
+
if (predicate?.type === "TSTypePredicate" && predicate.parameterName.type === "Identifier")
|
|
2217
|
+
return predicate.parameterName.name === parameter.name;
|
|
2218
|
+
if (owner.params[0] !== parameter)
|
|
2219
|
+
return false;
|
|
2220
|
+
const holder = owner.parent;
|
|
2221
|
+
if (!holder)
|
|
2222
|
+
return false;
|
|
2223
|
+
if (catchName(owner) || catchName(holder))
|
|
2224
|
+
return true;
|
|
2225
|
+
if (holder.type === "TSTypeAnnotation" && holder.parent && catchName(holder.parent))
|
|
2226
|
+
return true;
|
|
2227
|
+
return holder.type === "CallExpression" && holder.arguments.some((entry) => entry === owner) && holder.callee.type === "MemberExpression" && !holder.callee.computed && holder.callee.property.type === "Identifier" && holder.callee.property.name === "catch";
|
|
2228
|
+
}
|
|
2229
|
+
return {
|
|
2230
|
+
TSUnknownKeyword(node) {
|
|
2231
|
+
if (!allowed(node))
|
|
2232
|
+
context.report({ node, messageId: "forbidden" });
|
|
2233
|
+
}
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
});
|
|
2237
|
+
|
|
2198
2238
|
// src/rules/module-policy/index.ts
|
|
2199
2239
|
var module_policy_default = eslintCompatPlugin({
|
|
2200
2240
|
meta: { name: "modules" },
|
|
@@ -2209,7 +2249,7 @@ var module_policy_default = eslintCompatPlugin({
|
|
|
2209
2249
|
"import-boundaries": importBoundaries,
|
|
2210
2250
|
"tanstack-runtime": tanstackRuntime,
|
|
2211
2251
|
"local-type-alias": syntaxRule("TSTypeAliasDeclaration", "Local component/hook contracts use interfaces named for their owner. Derived or reusable aliases belong in domain types; do not substitute an empty interface."),
|
|
2212
|
-
"no-unknown":
|
|
2252
|
+
"no-unknown": noUnknown,
|
|
2213
2253
|
memoization,
|
|
2214
2254
|
"next-runtime": nextRuntime,
|
|
2215
2255
|
"pascal-interface": syntaxRule("TSInterfaceDeclaration:not([id.name=/^[A-Z][a-zA-Z0-9]*$/])", "Use PascalCase for interfaces."),
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rejects the `unknown` type except on the narrowed parameter of a type guard or the error parameter of a catch callback.
|
|
3
|
+
*
|
|
4
|
+
* Example: `type Value = unknown` fails; `function isUser(value: unknown): value is User` passes.
|
|
5
|
+
*/
|
|
6
|
+
export declare const noUnknown: import("@oxlint/plugins").Rule;
|
|
@@ -175,6 +175,8 @@ export interface PresetContext {
|
|
|
175
175
|
types: string[];
|
|
176
176
|
/** UI kit folders resolved from `shadcn.ui`. */
|
|
177
177
|
uiRoots: string[];
|
|
178
|
+
/** UI kit folders inside a web source. */
|
|
179
|
+
webUiRoots: string[];
|
|
178
180
|
ui: string[];
|
|
179
181
|
generated: string[];
|
|
180
182
|
testFiles: string[];
|