@geonosis/oxlint-plugin-biological-architecture 3.0.0 → 3.2.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 +50 -0
- package/README.md +48 -5
- package/RULES.md +201 -98
- package/corpus/.oxlintrc.json +6 -0
- package/corpus/data/bad-dynamic-orm.ts +5 -0
- package/corpus/data/bad-type-only-orm.ts +3 -0
- package/corpus/e2e/bad-list-response.spec.ts +5 -0
- package/corpus/e2e/bad-timeout.spec.ts +5 -0
- package/corpus/e2e/good-detail-response.spec.ts +5 -0
- package/corpus/e2e/good-state.spec.ts +5 -0
- package/corpus/icon-controls/bad-unnamed-icon-button.tsx +8 -0
- package/corpus/icon-controls/good-named-icon-button.tsx +19 -0
- package/corpus/manifest.json +3 -0
- package/corpus/tissues/bad-awaits-data.tsx +8 -0
- package/corpus/tissues/bad-fetches-data.tsx +7 -0
- package/corpus/tissues/good-takes-products.tsx +5 -0
- package/dist/discover-atoms.d.ts +2 -37
- package/dist/discover-atoms.js +1 -104
- package/dist/index.d.ts +3 -12
- package/dist/index.js +19 -174
- package/dist/presets.d.ts +11 -19
- package/dist/presets.js +89 -3
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +172 -0
- package/dist/rules/atom-no-deps.d.ts +1 -0
- package/dist/rules/atom-no-deps.js +10 -11
- package/dist/rules/boundary-time-is-primitive.d.ts +0 -7
- package/dist/rules/cell-must-be-stateful.d.ts +2 -2
- package/dist/rules/cell-must-be-stateful.js +24 -23
- package/dist/rules/cell-must-not-compose-cell.d.ts +1 -0
- package/dist/rules/cell-must-not-compose-cell.js +10 -3
- package/dist/rules/cell-no-cross-feature-organelles.d.ts +1 -0
- package/dist/rules/cell-no-cross-feature-organelles.js +7 -0
- package/dist/rules/cell-no-tissues.d.ts +1 -0
- package/dist/rules/cell-no-tissues.js +11 -7
- package/dist/rules/cells-folder-index-is-barrel.d.ts +12 -10
- package/dist/rules/cells-folder-index-is-barrel.js +66 -34
- package/dist/rules/compound-must-be-stateless.d.ts +2 -0
- package/dist/rules/compound-must-be-stateless.js +24 -26
- package/dist/rules/compound-no-stores.d.ts +1 -0
- package/dist/rules/compound-no-stores.js +12 -10
- package/dist/rules/constants-in-constants-file.d.ts +2 -8
- package/dist/rules/constants-in-constants-file.js +5 -6
- package/dist/rules/dialect-through-the-seam.d.ts +0 -13
- package/dist/rules/durable-body-reads-through-steps.d.ts +0 -14
- package/dist/rules/durable-body-reads-through-steps.js +8 -3
- package/dist/rules/effect-hook-naming.d.ts +9 -11
- package/dist/rules/effect-hook-naming.js +33 -31
- package/dist/rules/emit-declares-attempts.d.ts +0 -17
- package/dist/rules/emit-declares-attempts.js +5 -10
- package/dist/rules/font-roles-only.d.ts +0 -18
- package/dist/rules/font-roles-only.js +19 -22
- package/dist/rules/hermetic-e2e-no-wall-clock-waits.d.ts +53 -0
- package/dist/rules/hermetic-e2e-no-wall-clock-waits.js +100 -0
- package/dist/rules/icon-control-has-name.d.ts +53 -0
- package/dist/rules/icon-control-has-name.js +71 -0
- package/dist/rules/layer-walls.d.ts +0 -25
- package/dist/rules/layer-walls.js +7 -13
- package/dist/rules/lib/atom-map.d.ts +24 -0
- package/dist/rules/lib/atom-map.js +16 -0
- package/dist/rules/lib/cells-barrel.d.ts +0 -1
- package/dist/rules/lib/colors.d.ts +0 -19
- package/dist/rules/lib/colors.js +10 -243
- package/dist/rules/lib/comments.d.ts +19 -0
- package/dist/rules/lib/comments.js +5 -0
- package/dist/rules/lib/context.d.ts +0 -10
- package/dist/rules/lib/direction-rule.d.ts +3 -21
- package/dist/rules/lib/direction-rule.js +12 -3
- package/dist/rules/lib/doors.d.ts +2 -18
- package/dist/rules/lib/doors.js +79 -41
- package/dist/rules/lib/hooks.d.ts +2 -17
- package/dist/rules/lib/hooks.js +14 -9
- package/dist/rules/lib/imports.d.ts +2 -23
- package/dist/rules/lib/imports.js +7 -2
- package/dist/rules/lib/jsx.d.ts +31 -0
- package/dist/rules/lib/jsx.js +11 -0
- package/dist/rules/lib/landing.d.ts +0 -40
- package/dist/rules/lib/literal-call.d.ts +1 -0
- package/dist/rules/lib/literal-call.js +2 -0
- package/dist/rules/lib/module-text.d.ts +3 -27
- package/dist/rules/lib/module-text.js +19 -4
- package/dist/rules/lib/mutations.d.ts +0 -14
- package/dist/rules/lib/nodes.d.ts +1 -18
- package/dist/rules/lib/nodes.js +1 -0
- package/dist/rules/lib/objects.d.ts +2 -11
- package/dist/rules/lib/objects.js +21 -0
- package/dist/rules/lib/options.d.ts +2 -52
- package/dist/rules/lib/options.js +44 -18
- package/dist/rules/lib/package-imports.d.ts +0 -5
- package/dist/rules/lib/package-imports.js +4 -10
- package/dist/rules/lib/paths.d.ts +15 -41
- package/dist/rules/lib/paths.js +34 -1
- package/dist/rules/lib/pattern-match.d.ts +2 -12
- package/dist/rules/lib/pattern-match.js +5 -1
- package/dist/rules/lib/probe.d.ts +0 -43
- package/dist/rules/lib/reexports.d.ts +0 -18
- package/dist/rules/lib/render-tracking.d.ts +0 -8
- package/dist/rules/lib/shared-features.d.ts +0 -8
- package/dist/rules/lib/shared-features.js +2 -9
- package/dist/rules/lib/state-sources.d.ts +8 -0
- package/dist/rules/lib/state-sources.js +55 -0
- package/dist/rules/lib/steps.d.ts +3 -5
- package/dist/rules/lib/steps.js +6 -1
- package/dist/rules/lib/styling.d.ts +9 -42
- package/dist/rules/lib/styling.js +36 -10
- package/dist/rules/lib/tiers.d.ts +11 -24
- package/dist/rules/lib/tiers.js +41 -1
- package/dist/rules/lib/tissue-entrypoint.d.ts +0 -1
- package/dist/rules/lib/tissue-entrypoint.js +4 -5
- package/dist/rules/lib/tsconfig-paths.d.ts +0 -4
- package/dist/rules/lib/tsconfig-paths.js +47 -15
- package/dist/rules/lib/type-members.d.ts +6 -0
- package/dist/rules/lib/type-members.js +165 -0
- package/dist/rules/lib/visitors.d.ts +0 -22
- package/dist/rules/lib/workspace-members.d.ts +2 -0
- package/dist/rules/lib/workspace-members.js +115 -0
- package/dist/rules/list-take-without-order.js +2 -8
- package/dist/rules/max-comment-density.d.ts +3 -18
- package/dist/rules/max-comment-density.js +3 -3
- package/dist/rules/molecule-atoms-only.d.ts +1 -0
- package/dist/rules/molecule-atoms-only.js +11 -7
- package/dist/rules/molecule-must-compose.d.ts +3 -1
- package/dist/rules/molecule-must-compose.js +17 -21
- package/dist/rules/mutating-step-declares-retry.d.ts +2 -7
- package/dist/rules/mutating-step-declares-retry.js +29 -11
- package/dist/rules/named-when-block.d.ts +0 -2
- package/dist/rules/named-when-block.js +2 -8
- package/dist/rules/next-route-segment-is-thin-delegate.d.ts +4 -19
- package/dist/rules/next-route-segment-is-thin-delegate.js +47 -71
- package/dist/rules/no-brand-names.d.ts +0 -9
- package/dist/rules/no-brand-names.js +1 -1
- package/dist/rules/no-card-shaped-div.d.ts +4 -42
- package/dist/rules/no-card-shaped-div.js +12 -4
- package/dist/rules/no-cross-feature-stores.d.ts +1 -0
- package/dist/rules/no-cross-feature-stores.js +9 -5
- package/dist/rules/no-d1-transaction.d.ts +2 -5
- package/dist/rules/no-d1-transaction.js +7 -0
- package/dist/rules/no-date-string-compare.d.ts +2 -0
- package/dist/rules/no-date-string-compare.js +14 -8
- package/dist/rules/no-duplicate-jsx-patterns.d.ts +2 -0
- package/dist/rules/no-duplicate-jsx-patterns.js +16 -1
- package/dist/rules/no-generic-utility-package.d.ts +48 -2
- package/dist/rules/no-generic-utility-package.js +48 -9
- package/dist/rules/no-hook-in-component-disguise.d.ts +27 -13
- package/dist/rules/no-hook-in-component-disguise.js +38 -30
- package/dist/rules/no-index-count-as-exact.d.ts +0 -3
- package/dist/rules/no-inert-hidden-jsx.d.ts +4 -21
- package/dist/rules/no-inert-hidden-jsx.js +13 -12
- package/dist/rules/no-inline-data-in-jsx.d.ts +2 -0
- package/dist/rules/no-inline-data-in-jsx.js +17 -2
- package/dist/rules/no-invalid-feature-folders.d.ts +2 -0
- package/dist/rules/no-invalid-feature-folders.js +12 -4
- package/dist/rules/no-loader-side-effect-in-index-file.d.ts +0 -7
- package/dist/rules/no-logic-in-component-files.d.ts +6 -5
- package/dist/rules/no-logic-in-component-files.js +52 -31
- package/dist/rules/no-mock-db-in-integration.d.ts +0 -15
- package/dist/rules/no-mock-db-in-integration.js +3 -4
- package/dist/rules/no-orm-outside-db.d.ts +1 -0
- package/dist/rules/no-orm-outside-db.js +9 -7
- package/dist/rules/no-raw-html-atoms.d.ts +3 -22
- package/dist/rules/no-raw-html-atoms.js +5 -17
- package/dist/rules/no-raw-sql-outside-allowed.d.ts +2 -0
- package/dist/rules/no-raw-sql-outside-allowed.js +10 -15
- package/dist/rules/no-react-namespace.d.ts +2 -0
- package/dist/rules/no-react-namespace.js +13 -0
- package/dist/rules/no-renamed-html-props.d.ts +2 -0
- package/dist/rules/no-renamed-html-props.js +11 -2
- package/dist/rules/no-render-prop-reader.d.ts +4 -20
- package/dist/rules/no-render-prop-reader.js +13 -5
- package/dist/rules/no-runtime-dead-affordance.d.ts +3 -26
- package/dist/rules/no-runtime-dead-affordance.js +7 -20
- package/dist/rules/no-swallowed-catch.js +2 -8
- package/dist/rules/no-trivial-wrapper-component.d.ts +4 -19
- package/dist/rules/no-trivial-wrapper-component.js +24 -33
- package/dist/rules/no-ts-in-bio-folders.d.ts +2 -13
- package/dist/rules/no-ts-in-bio-folders.js +9 -7
- package/dist/rules/no-type-definitions-in-components.d.ts +2 -0
- package/dist/rules/no-type-definitions-in-components.js +12 -1
- package/dist/rules/no-unregistered-plugin-dir.d.ts +0 -41
- package/dist/rules/no-unregistered-plugin-dir.js +1 -9
- package/dist/rules/no-void-port.d.ts +2 -4
- package/dist/rules/no-void-port.js +7 -0
- package/dist/rules/organelle-dependency.d.ts +1 -0
- package/dist/rules/organelle-dependency.js +10 -8
- package/dist/rules/organelle-single-source.d.ts +2 -0
- package/dist/rules/organelle-single-source.js +58 -9
- package/dist/rules/plugin-route-namespaced.d.ts +0 -11
- package/dist/rules/plugin-route-namespaced.js +2 -2
- package/dist/rules/queries-require-org-scope.d.ts +0 -3
- package/dist/rules/queries-require-org-scope.js +3 -10
- package/dist/rules/queue-loop-is-the-library.js +3 -2
- package/dist/rules/route-no-inline-mutation.d.ts +1 -25
- package/dist/rules/route-no-inline-mutation.js +2 -7
- package/dist/rules/router-schema-parity.d.ts +0 -9
- package/dist/rules/router-schema-parity.js +5 -9
- package/dist/rules/service-name-not-reserved.d.ts +0 -3
- package/dist/rules/service-name-not-reserved.js +1 -34
- package/dist/rules/ssot-no-inline-facts.js +2 -5
- package/dist/rules/ssot-no-process-env.js +4 -12
- package/dist/rules/step-declares-compensation-or-none.d.ts +2 -1
- package/dist/rules/step-declares-compensation-or-none.js +17 -1
- package/dist/rules/step-opens-its-own-cell.d.ts +2 -25
- package/dist/rules/step-opens-its-own-cell.js +8 -13
- package/dist/rules/store-route-scopes-tenant-data.js +8 -26
- package/dist/rules/subpath-over-barrel.d.ts +2 -10
- package/dist/rules/subpath-over-barrel.js +48 -9
- package/dist/rules/subscriber-declares-id.d.ts +0 -2
- package/dist/rules/subscriber-declares-id.js +2 -8
- package/dist/rules/tables-declare-their-plane.d.ts +0 -2
- package/dist/rules/tables-declare-their-plane.js +4 -10
- package/dist/rules/tenant-tables-carry-org-id.d.ts +1 -4
- package/dist/rules/tenant-tables-carry-org-id.js +5 -12
- package/dist/rules/test-no-clock-polling-for-promise-flips.d.ts +2 -0
- package/dist/rules/test-no-clock-polling-for-promise-flips.js +18 -1
- package/dist/rules/time-through-the-door.d.ts +0 -24
- package/dist/rules/time-through-the-door.js +5 -5
- package/dist/rules/tissue-must-compose.d.ts +4 -2
- package/dist/rules/tissue-must-compose.js +25 -20
- package/dist/rules/tissue-no-data-props.d.ts +56 -0
- package/dist/rules/tissue-no-data-props.js +114 -32
- package/dist/rules/tissue-no-fetch.d.ts +26 -0
- package/dist/rules/tissue-no-fetch.js +57 -0
- package/dist/rules/tissue-no-hooks.d.ts +20 -22
- package/dist/rules/tissue-no-hooks.js +22 -4
- package/dist/rules/tissue-no-organelles.d.ts +1 -0
- package/dist/rules/tissue-no-organelles.js +9 -1
- package/dist/rules/tissue-no-stores.d.ts +1 -0
- package/dist/rules/tissue-no-stores.js +9 -1
- package/dist/rules/todo-names-its-plan.d.ts +1 -28
- package/dist/rules/todo-names-its-plan.js +2 -2
- package/dist/rules/tokens-only-colors.d.ts +2 -37
- package/dist/rules/tokens-only-colors.js +33 -18
- package/dist/rules/unique-query-step-name.d.ts +0 -7
- package/dist/rules/unique-query-step-name.js +6 -10
- package/dist/rules/worker-handles-are-scoped.d.ts +11 -8
- package/dist/rules/worker-handles-are-scoped.js +12 -5
- package/dist/rules/zustand-v5-best-practices.d.ts +8 -18
- package/dist/rules/zustand-v5-best-practices.js +32 -56
- package/package.json +2 -2
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// src/rules/next-route-segment-is-thin-delegate.ts
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
import { sourceOf } from "./lib/imports.js";
|
|
3
|
+
import { isCapitalised, meaningfulChildren } from "./lib/jsx.js";
|
|
4
|
+
import { FUNCTION_BOUNDARY_TYPES, walkOwnFunctionBody } from "./lib/nodes.js";
|
|
5
|
+
import {
|
|
6
|
+
fileOf,
|
|
7
|
+
isTestFile,
|
|
8
|
+
normalise,
|
|
9
|
+
ROUTE_BODY_BASENAMES,
|
|
10
|
+
ROUTE_FALLBACK_BASENAMES
|
|
11
|
+
} from "./lib/paths.js";
|
|
12
|
+
import { APP_FOLDER, folderOf, TIERS } from "./lib/tiers.js";
|
|
13
13
|
function collectReturnStatements(body) {
|
|
14
14
|
const out = [];
|
|
15
15
|
walkOwnFunctionBody(body, (node) => {
|
|
@@ -41,47 +41,42 @@ function describeReturn(arg) {
|
|
|
41
41
|
return a.type ?? "unknown";
|
|
42
42
|
}
|
|
43
43
|
function bioFolderOf(source) {
|
|
44
|
-
|
|
45
|
-
return m?.[1] ?? "unknown";
|
|
44
|
+
return folderOf(source, [...TIERS, "lib", "stores"]) ?? "unknown";
|
|
46
45
|
}
|
|
47
46
|
function isTissueSource(source) {
|
|
48
47
|
if (!source)
|
|
49
48
|
return false;
|
|
50
|
-
return
|
|
49
|
+
return folderOf(source, ["tissues"]) !== undefined;
|
|
51
50
|
}
|
|
52
51
|
var nextRouteSegmentIsThinDelegate = {
|
|
53
52
|
create(context) {
|
|
54
53
|
const normalized = normalise(context.filename);
|
|
55
54
|
if (isTestFile(normalized))
|
|
56
55
|
return {};
|
|
57
|
-
if (
|
|
56
|
+
if (!APP_FOLDER.test(normalized))
|
|
58
57
|
return {};
|
|
59
|
-
const basename = normalized
|
|
60
|
-
if (!
|
|
58
|
+
const basename = fileOf(normalized);
|
|
59
|
+
if (!ROUTE_BODY_BASENAMES.has(basename))
|
|
61
60
|
return {};
|
|
62
|
-
if (
|
|
61
|
+
if (ROUTE_FALLBACK_BASENAMES.has(basename))
|
|
63
62
|
return {};
|
|
64
63
|
const importSources = new Map;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
messageId: "delegateMustBeTissue",
|
|
82
|
-
node: ret
|
|
83
|
-
});
|
|
84
|
-
}
|
|
64
|
+
const delegate = (tag, found, ret) => {
|
|
65
|
+
const source = tag === null ? undefined : importSources.get(tag);
|
|
66
|
+
if (tag === null || source === undefined || !isCapitalised(tag)) {
|
|
67
|
+
context.report({
|
|
68
|
+
data: { file: basename, found },
|
|
69
|
+
messageId: "mustBeThinDelegate",
|
|
70
|
+
node: ret
|
|
71
|
+
});
|
|
72
|
+
} else if (!isTissueSource(source)) {
|
|
73
|
+
context.report({
|
|
74
|
+
data: { bio: bioFolderOf(source), file: basename, source, tag },
|
|
75
|
+
messageId: "delegateMustBeTissue",
|
|
76
|
+
node: ret
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
85
80
|
function checkReturn(arg, ret) {
|
|
86
81
|
if (!arg || typeof arg !== "object")
|
|
87
82
|
return;
|
|
@@ -89,28 +84,11 @@ var nextRouteSegmentIsThinDelegate = {
|
|
|
89
84
|
if (a.type === "Literal" && a.value === null)
|
|
90
85
|
return;
|
|
91
86
|
if (a.type === "JSXElement") {
|
|
92
|
-
|
|
93
|
-
if (!isImportedPascalCase(tag)) {
|
|
94
|
-
context.report({
|
|
95
|
-
data: { file: basename, found: describeReturn(arg) },
|
|
96
|
-
messageId: "mustBeThinDelegate",
|
|
97
|
-
node: ret
|
|
98
|
-
});
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const source = importSources.get(tag);
|
|
102
|
-
if (!isTissueSource(source)) {
|
|
103
|
-
reportNonTissueDelegate(tag, ret);
|
|
104
|
-
}
|
|
87
|
+
delegate(tagOf(arg), describeReturn(arg), ret);
|
|
105
88
|
return;
|
|
106
89
|
}
|
|
107
90
|
if (a.type === "JSXFragment") {
|
|
108
|
-
const
|
|
109
|
-
const meaningful = kids.filter((c) => {
|
|
110
|
-
if (c.type === "JSXText")
|
|
111
|
-
return (c.value ?? "").trim().length > 0;
|
|
112
|
-
return true;
|
|
113
|
-
});
|
|
91
|
+
const meaningful = meaningfulChildren(arg.children);
|
|
114
92
|
if (meaningful.length !== 1) {
|
|
115
93
|
context.report({
|
|
116
94
|
data: { count: String(meaningful.length), file: basename },
|
|
@@ -129,18 +107,7 @@ var nextRouteSegmentIsThinDelegate = {
|
|
|
129
107
|
return;
|
|
130
108
|
}
|
|
131
109
|
const tagName = child.openingElement?.name?.type === "JSXIdentifier" ? child.openingElement.name.name ?? null : null;
|
|
132
|
-
|
|
133
|
-
context.report({
|
|
134
|
-
data: { file: basename, found: `fragment><${tagName ?? "?"}/></fragment` },
|
|
135
|
-
messageId: "mustBeThinDelegate",
|
|
136
|
-
node: ret
|
|
137
|
-
});
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const source = importSources.get(tagName);
|
|
141
|
-
if (!isTissueSource(source)) {
|
|
142
|
-
reportNonTissueDelegate(tagName, ret);
|
|
143
|
-
}
|
|
110
|
+
delegate(tagName, `fragment><${tagName ?? "?"}/></fragment`, ret);
|
|
144
111
|
return;
|
|
145
112
|
}
|
|
146
113
|
context.report({
|
|
@@ -152,7 +119,7 @@ var nextRouteSegmentIsThinDelegate = {
|
|
|
152
119
|
return {
|
|
153
120
|
ExportDefaultDeclaration(node) {
|
|
154
121
|
const decl = node.declaration;
|
|
155
|
-
if (!decl || !
|
|
122
|
+
if (!decl || !FUNCTION_BOUNDARY_TYPES.has(decl.type))
|
|
156
123
|
return;
|
|
157
124
|
if (decl.body !== undefined && decl.body.type !== "BlockStatement") {
|
|
158
125
|
checkReturn(decl.body, decl.body);
|
|
@@ -162,7 +129,7 @@ var nextRouteSegmentIsThinDelegate = {
|
|
|
162
129
|
checkReturn(ret.argument, ret);
|
|
163
130
|
},
|
|
164
131
|
ImportDeclaration(node) {
|
|
165
|
-
const source = node
|
|
132
|
+
const source = sourceOf(node) ?? "";
|
|
166
133
|
for (const spec of node.specifiers ?? []) {
|
|
167
134
|
const local = spec.local?.name;
|
|
168
135
|
if (local)
|
|
@@ -186,6 +153,15 @@ returns is a tissue — a route that renders a cell directly wraps it in a tissu
|
|
|
186
153
|
},
|
|
187
154
|
schema: [],
|
|
188
155
|
type: "problem"
|
|
156
|
+
},
|
|
157
|
+
probe() {
|
|
158
|
+
return {
|
|
159
|
+
path: "src/app/geonosis-probe/page.tsx",
|
|
160
|
+
source: `export default function Page() {
|
|
161
|
+
return <div>inline markup</div>
|
|
162
|
+
}
|
|
163
|
+
`
|
|
164
|
+
};
|
|
189
165
|
}
|
|
190
166
|
};
|
|
191
167
|
var next_route_segment_is_thin_delegate_default = nextRouteSegmentIsThinDelegate;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { type Probe } from './lib/probe.js';
|
|
2
2
|
interface Options {
|
|
3
|
-
/** Substrings stripped before the scan — a real demo-asset host, so a CDN URL never trips. */
|
|
4
3
|
allowedSubstrings?: string[];
|
|
5
|
-
/** Brand names, matched case-insensitively on a word boundary. */
|
|
6
4
|
brands?: string[];
|
|
7
5
|
}
|
|
8
6
|
declare const noBrandNames: {
|
|
@@ -52,13 +50,6 @@ declare const noBrandNames: {
|
|
|
52
50
|
}[];
|
|
53
51
|
type: 'problem';
|
|
54
52
|
};
|
|
55
|
-
/**
|
|
56
|
-
* The brand list is a Medusa storefront's and the corpus says `acme`, so this rule reads as
|
|
57
|
-
* firing nowhere in the repo whose brands it was configured with — one of the two findings that
|
|
58
|
-
* opened this. The brand goes on a line of its own so no `allowedSubstrings` entry can strip it
|
|
59
|
-
* away: a CDN host is stripped before the scan, and a probe hiding inside one would prove the
|
|
60
|
-
* opposite of what it claims.
|
|
61
|
-
*/
|
|
62
53
|
probe(options: readonly unknown[]): Probe;
|
|
63
54
|
};
|
|
64
55
|
export default noBrandNames;
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { requireOption } from "./lib/options.js";
|
|
4
4
|
import { normalise } from "./lib/paths.js";
|
|
5
|
+
import { escapeForRegex } from "./lib/pattern-match.js";
|
|
5
6
|
import { bagOf } from "./lib/probe.js";
|
|
6
7
|
import { optionsBag, STRINGS } from "./lib/schema.js";
|
|
7
8
|
var EXEMPT_PATH = /\/examples\//;
|
|
8
|
-
var escapeForRegex = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9
9
|
var noBrandNames = {
|
|
10
10
|
create(context) {
|
|
11
11
|
const { allowedSubstrings = [], brands = [] } = context.options?.[0] ?? {};
|
|
@@ -1,44 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
operator: string;
|
|
4
|
-
right: ClassNameExpression | null;
|
|
5
|
-
type: 'BinaryExpression';
|
|
6
|
-
}
|
|
7
|
-
type ClassNameExpression = {
|
|
8
|
-
type: string;
|
|
9
|
-
} | BinaryExpression | JSXExpressionContainer | Literal | TemplateLiteral;
|
|
10
|
-
interface JSXAttribute {
|
|
11
|
-
name?: {
|
|
12
|
-
name?: string;
|
|
13
|
-
type: string;
|
|
14
|
-
};
|
|
15
|
-
type: string;
|
|
16
|
-
value?: ClassNameExpression | null;
|
|
17
|
-
}
|
|
18
|
-
interface JSXExpressionContainer {
|
|
19
|
-
expression: ClassNameExpression | null;
|
|
20
|
-
type: 'JSXExpressionContainer';
|
|
21
|
-
}
|
|
22
|
-
interface JSXOpeningElement {
|
|
23
|
-
attributes: JSXAttribute[];
|
|
24
|
-
name: {
|
|
25
|
-
name?: string;
|
|
26
|
-
type: string;
|
|
27
|
-
};
|
|
28
|
-
type: 'JSXOpeningElement';
|
|
29
|
-
}
|
|
30
|
-
interface Literal {
|
|
31
|
-
type: 'Literal';
|
|
32
|
-
value: unknown;
|
|
33
|
-
}
|
|
34
|
-
interface TemplateLiteral {
|
|
35
|
-
quasis: {
|
|
36
|
-
value?: {
|
|
37
|
-
raw?: string;
|
|
38
|
-
};
|
|
39
|
-
}[];
|
|
40
|
-
type: 'TemplateLiteral';
|
|
41
|
-
}
|
|
1
|
+
import { type JsxOpeningElement } from './lib/jsx.js';
|
|
2
|
+
import { type Probe } from './lib/probe.js';
|
|
42
3
|
declare const noCardShapedDiv: {
|
|
43
4
|
create(context: {
|
|
44
5
|
filename: string;
|
|
@@ -50,7 +11,7 @@ declare const noCardShapedDiv: {
|
|
|
50
11
|
}): {
|
|
51
12
|
JSXOpeningElement?: undefined;
|
|
52
13
|
} | {
|
|
53
|
-
JSXOpeningElement(node:
|
|
14
|
+
JSXOpeningElement(node: JsxOpeningElement): void;
|
|
54
15
|
};
|
|
55
16
|
fixShape: string;
|
|
56
17
|
meta: {
|
|
@@ -63,5 +24,6 @@ declare const noCardShapedDiv: {
|
|
|
63
24
|
schema: never[];
|
|
64
25
|
type: 'problem';
|
|
65
26
|
};
|
|
27
|
+
probe(): Probe;
|
|
66
28
|
};
|
|
67
29
|
export default noCardShapedDiv;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/rules/no-card-shaped-div.ts
|
|
2
|
+
import { attributeNamed, tagNameOf } from "./lib/jsx.js";
|
|
2
3
|
import { normalise } from "./lib/paths.js";
|
|
3
|
-
|
|
4
|
+
import { ABOVE_ATOMS_FOLDER } from "./lib/tiers.js";
|
|
4
5
|
var MODIFIER_PREFIX = "(?:(?:dark|print|sm|md|lg|xl|2xl|first|last|odd|even):)?";
|
|
5
6
|
var BORDER_PATTERN = new RegExp(`(?:^|\\s)${MODIFIER_PREFIX}border(?:\\s|$|-[1-9]\\d*(?:\\s|$))`);
|
|
6
7
|
var BG_PATTERN = new RegExp(`(?:^|\\s)${MODIFIER_PREFIX}bg-(?:white|black|container|page|neutral-\\d+|gray-\\d+|zinc-\\d+|stone-\\d+|slate-\\d+)`);
|
|
@@ -35,13 +36,13 @@ function isCardLikeClassName(className) {
|
|
|
35
36
|
var noCardShapedDiv = {
|
|
36
37
|
create(context) {
|
|
37
38
|
const filename = normalise(context.filename);
|
|
38
|
-
if (!
|
|
39
|
+
if (!ABOVE_ATOMS_FOLDER.test(filename))
|
|
39
40
|
return {};
|
|
40
41
|
return {
|
|
41
42
|
JSXOpeningElement(node) {
|
|
42
|
-
if (node
|
|
43
|
+
if (tagNameOf(node) !== "div")
|
|
43
44
|
return;
|
|
44
|
-
const classAttr = node
|
|
45
|
+
const classAttr = attributeNamed(node, "className");
|
|
45
46
|
if (!classAttr)
|
|
46
47
|
return;
|
|
47
48
|
const cls = extractClassNameString(classAttr.value ?? null);
|
|
@@ -65,6 +66,13 @@ styling into an atom that owns it.`,
|
|
|
65
66
|
},
|
|
66
67
|
schema: [],
|
|
67
68
|
type: "problem"
|
|
69
|
+
},
|
|
70
|
+
probe() {
|
|
71
|
+
return {
|
|
72
|
+
path: "src/cells/geonosis-probe.tsx",
|
|
73
|
+
source: `export const GeonosisProbe = () => <div className="border bg-white" />
|
|
74
|
+
`
|
|
75
|
+
};
|
|
68
76
|
}
|
|
69
77
|
};
|
|
70
78
|
var no_card_shaped_div_default = noCardShapedDiv;
|
|
@@ -9,6 +9,7 @@ declare const noCrossFeatureStores: {
|
|
|
9
9
|
}) => void;
|
|
10
10
|
}): import('./lib/visitors.js').Visitor;
|
|
11
11
|
fixShape: string;
|
|
12
|
+
probe?: ((options: readonly unknown[]) => import('./lib/probe.js').Probe | import('./lib/probe.js').ProbeTree) | undefined;
|
|
12
13
|
meta: {
|
|
13
14
|
docs: {
|
|
14
15
|
description: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/rules/no-cross-feature-stores.ts
|
|
2
2
|
import { directionRule } from "./lib/direction-rule.js";
|
|
3
|
+
import { crossesFeatureBoundary } from "./lib/shared-features.js";
|
|
3
4
|
var BIO_FILE_PATTERN = /(?:^|\/)features\/([^/]+)\/(cells|organelles)\/.*\.(tsx?|ts)$/;
|
|
4
5
|
var STORE_IMPORT_PATTERN = /(?:^|\/|@\/)features\/([^/]+)\/stores\//;
|
|
5
|
-
var SHARED_FEATURE = "shared";
|
|
6
6
|
var storeFeatureOf = (source) => STORE_IMPORT_PATTERN.exec(source)?.[1];
|
|
7
7
|
var noCrossFeatureStores = directionRule({
|
|
8
8
|
data: (target, importer) => ({
|
|
@@ -15,14 +15,18 @@ var noCrossFeatureStores = directionRule({
|
|
|
15
15
|
fixShape: `A cell or organelle in \`features/X/\` reads only \`features/X/stores/*\` and \`features/shared/stores/*\`.
|
|
16
16
|
Another feature's store is a hidden coupling: let the data flow through the parent tissue as props
|
|
17
17
|
instead. Type-only imports are exempt; \`export … from\` is not.`,
|
|
18
|
-
forbidden: (target, importer) => {
|
|
19
|
-
const importedFeature = storeFeatureOf(target.path);
|
|
20
|
-
return importedFeature !== undefined && importedFeature !== importer.feature && importedFeature !== SHARED_FEATURE;
|
|
21
|
-
},
|
|
18
|
+
forbidden: (target, importer) => crossesFeatureBoundary(storeFeatureOf(target.path), importer.feature, {}),
|
|
22
19
|
messages: {
|
|
23
20
|
crossFeatureStore: `"{{file}}" in feature "{{ownFeature}}" imports from another feature's store: "{{source}}" (feature: "{{importedFeature}}"). Cross-feature data must flow through the parent as props. Only features/{{ownFeature}}/stores/* and features/shared/stores/* are allowed here.`
|
|
24
21
|
},
|
|
25
22
|
messageId: "crossFeatureStore",
|
|
23
|
+
probe: () => ({
|
|
24
|
+
path: "src/features/geonosis/cells/geonosis-probe.tsx",
|
|
25
|
+
source: `import { other } from '@/features/geonosis-neighbour/stores/other'
|
|
26
|
+
|
|
27
|
+
export const GeonosisProbe = other
|
|
28
|
+
`
|
|
29
|
+
}),
|
|
26
30
|
scope: (importer) => BIO_FILE_PATTERN.test(importer.path)
|
|
27
31
|
});
|
|
28
32
|
var no_cross_feature_stores_default = noCrossFeatureStores;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { type RuleContextWithOptions } from './lib/context.js';
|
|
2
|
+
import type { Probe } from './lib/probe.js';
|
|
2
3
|
interface Options {
|
|
3
|
-
/**
|
|
4
|
-
* Regex sources over the normalised filename: files inside `transacts` whose handle is still D1
|
|
5
|
-
* — the control plane's own modules, its schema.
|
|
6
|
-
*/
|
|
7
4
|
except?: string | string[];
|
|
8
|
-
/** Regex sources over the normalised filename: the files whose database transacts (a Postgres cell). */
|
|
9
5
|
transacts?: string | string[];
|
|
10
6
|
}
|
|
11
7
|
type Node = {
|
|
@@ -65,5 +61,6 @@ declare const noD1Transaction: {
|
|
|
65
61
|
}[];
|
|
66
62
|
type: 'problem';
|
|
67
63
|
};
|
|
64
|
+
probe(): Probe;
|
|
68
65
|
};
|
|
69
66
|
export default noD1Transaction;
|
|
@@ -82,6 +82,13 @@ judged until the repo says which of its handles may transact.`,
|
|
|
82
82
|
transacts: PATTERNS
|
|
83
83
|
}),
|
|
84
84
|
type: "problem"
|
|
85
|
+
},
|
|
86
|
+
probe() {
|
|
87
|
+
return {
|
|
88
|
+
path: "geonosis-probe-d1.ts",
|
|
89
|
+
source: `export const geonosisProbe = (db) => db.transaction(async () => 1)
|
|
90
|
+
`
|
|
91
|
+
};
|
|
85
92
|
}
|
|
86
93
|
};
|
|
87
94
|
var no_d1_transaction_default = noD1Transaction;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type RuleContextWithRawOptions } from './lib/context.js';
|
|
2
2
|
import { type AnyNode } from './lib/nodes.js';
|
|
3
|
+
import { type Probe } from './lib/probe.js';
|
|
3
4
|
declare const noDateStringCompare: {
|
|
4
5
|
create(context: RuleContextWithRawOptions): {
|
|
5
6
|
BinaryExpression(node: AnyNode): void;
|
|
@@ -24,5 +25,6 @@ declare const noDateStringCompare: {
|
|
|
24
25
|
}[];
|
|
25
26
|
type: 'problem';
|
|
26
27
|
};
|
|
28
|
+
probe(options: readonly unknown[]): Probe;
|
|
27
29
|
};
|
|
28
30
|
export default noDateStringCompare;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/rules/no-date-string-compare.ts
|
|
2
2
|
import { calleeNameOf, propertyNameOf } from "./lib/nodes.js";
|
|
3
3
|
import { bagOf } from "./lib/probe.js";
|
|
4
|
-
import { STRING } from "./lib/schema.js";
|
|
4
|
+
import { optionsBag, STRING } from "./lib/schema.js";
|
|
5
5
|
var FIELD_PATTERN = "(?:_at|[a-z]At)$";
|
|
6
6
|
var RELATIONAL = new Set(["<", "<=", ">", ">="]);
|
|
7
7
|
var COMPARE = "localeCompare";
|
|
@@ -84,14 +84,20 @@ and a template with copy in it are both left alone. Which names hold a timestamp
|
|
|
84
84
|
messages: {
|
|
85
85
|
textOrder: 'Ordering "{{field}}" by {{ordering}} on a string compares the TEXT of a timestamp: a Date stringifies to "Fri Aug 30 2026 …", so this sorts by weekday name. Compare `.getTime()` or `Date.parse()`, or order on the ISO string the API returned.'
|
|
86
86
|
},
|
|
87
|
-
schema:
|
|
88
|
-
{
|
|
89
|
-
additionalProperties: false,
|
|
90
|
-
properties: { fieldPattern: STRING },
|
|
91
|
-
type: "object"
|
|
92
|
-
}
|
|
93
|
-
],
|
|
87
|
+
schema: optionsBag({ fieldPattern: STRING }),
|
|
94
88
|
type: "problem"
|
|
89
|
+
},
|
|
90
|
+
probe(options) {
|
|
91
|
+
const field = "created_at";
|
|
92
|
+
const pattern = bagOf(options).fieldPattern ?? FIELD_PATTERN;
|
|
93
|
+
if (!new RegExp(pattern).test(field)) {
|
|
94
|
+
throw new Error(`no probe can be built: \`${field}\` is not a field the pattern ${pattern} holds a time in`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
path: "geonosis-probe-order.ts",
|
|
98
|
+
source: `export const geonosisProbe = (left, right) => String(left.${field}).localeCompare(String(right.${field}))
|
|
99
|
+
`
|
|
100
|
+
};
|
|
95
101
|
}
|
|
96
102
|
};
|
|
97
103
|
var no_date_string_compare_default = noDateStringCompare;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type RuleContext } from './lib/context.js';
|
|
2
|
+
import { type Probe } from './lib/probe.js';
|
|
2
3
|
interface JSXNode {
|
|
3
4
|
attributes: {
|
|
4
5
|
name?: {
|
|
@@ -51,5 +52,6 @@ declare const noDuplicateJsxPatterns: {
|
|
|
51
52
|
schema: never[];
|
|
52
53
|
type: 'problem';
|
|
53
54
|
};
|
|
55
|
+
probe(): Probe;
|
|
54
56
|
};
|
|
55
57
|
export default noDuplicateJsxPatterns;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/rules/no-duplicate-jsx-patterns.ts
|
|
2
2
|
import { normalise } from "./lib/paths.js";
|
|
3
|
-
|
|
3
|
+
import { COMPONENT_FILE_PATTERN } from "./lib/tiers.js";
|
|
4
4
|
var MIN_CLASS_LENGTH = 30;
|
|
5
5
|
var MIN_DUPLICATES = 3;
|
|
6
6
|
var ITERATORS = new Set(["map", "flatMap", "forEach", "filter", "reduce"]);
|
|
@@ -95,6 +95,21 @@ their text are one component with a prop.`,
|
|
|
95
95
|
},
|
|
96
96
|
schema: [],
|
|
97
97
|
type: "problem"
|
|
98
|
+
},
|
|
99
|
+
probe() {
|
|
100
|
+
return {
|
|
101
|
+
path: "src/cells/geonosis-probe.tsx",
|
|
102
|
+
source: `export function GeonosisProbe() {
|
|
103
|
+
return (
|
|
104
|
+
<div>
|
|
105
|
+
<p className="mb-[4px] text-[12px] leading-[16px] font-bold">a</p>
|
|
106
|
+
<p className="mb-[4px] text-[12px] leading-[16px] font-bold">b</p>
|
|
107
|
+
<p className="mb-[4px] text-[12px] leading-[16px] font-bold">c</p>
|
|
108
|
+
</div>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
`
|
|
112
|
+
};
|
|
98
113
|
}
|
|
99
114
|
};
|
|
100
115
|
var no_duplicate_jsx_patterns_default = noDuplicateJsxPatterns;
|
|
@@ -1,8 +1,35 @@
|
|
|
1
1
|
import { type RuleContextWithRawOptions } from './lib/context.js';
|
|
2
2
|
import { type ProbeTree } from './lib/probe.js';
|
|
3
|
+
interface Declared {
|
|
4
|
+
declarations?: {
|
|
5
|
+
id?: {
|
|
6
|
+
name?: string;
|
|
7
|
+
};
|
|
8
|
+
}[];
|
|
9
|
+
id?: {
|
|
10
|
+
name?: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
interface Statement {
|
|
14
|
+
declaration?: Declared | null;
|
|
15
|
+
exported?: {
|
|
16
|
+
name?: string;
|
|
17
|
+
value?: string;
|
|
18
|
+
} | null;
|
|
19
|
+
source?: unknown;
|
|
20
|
+
specifiers?: {
|
|
21
|
+
exported?: {
|
|
22
|
+
name?: string;
|
|
23
|
+
value?: string;
|
|
24
|
+
};
|
|
25
|
+
}[];
|
|
26
|
+
type?: string;
|
|
27
|
+
}
|
|
3
28
|
declare const noGenericUtilityPackage: {
|
|
4
29
|
create(context: RuleContextWithRawOptions): {
|
|
5
|
-
Program(node:
|
|
30
|
+
Program(node: {
|
|
31
|
+
body?: Statement[];
|
|
32
|
+
}): void;
|
|
6
33
|
};
|
|
7
34
|
fixShape: string;
|
|
8
35
|
meta: {
|
|
@@ -10,11 +37,31 @@ declare const noGenericUtilityPackage: {
|
|
|
10
37
|
description: string;
|
|
11
38
|
};
|
|
12
39
|
messages: {
|
|
40
|
+
beyondTheDoor: string;
|
|
13
41
|
generic: string;
|
|
14
42
|
};
|
|
15
43
|
schema: {
|
|
16
44
|
additionalProperties: boolean;
|
|
17
45
|
properties: {
|
|
46
|
+
allow: {
|
|
47
|
+
items: {
|
|
48
|
+
additionalProperties: boolean;
|
|
49
|
+
properties: {
|
|
50
|
+
exports: {
|
|
51
|
+
items: {
|
|
52
|
+
type: 'string';
|
|
53
|
+
};
|
|
54
|
+
type: 'array';
|
|
55
|
+
};
|
|
56
|
+
package: {
|
|
57
|
+
type: 'string';
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
required: string[];
|
|
61
|
+
type: 'object';
|
|
62
|
+
};
|
|
63
|
+
type: 'array';
|
|
64
|
+
};
|
|
18
65
|
entries: {
|
|
19
66
|
items: {
|
|
20
67
|
type: 'string';
|
|
@@ -32,7 +79,6 @@ declare const noGenericUtilityPackage: {
|
|
|
32
79
|
}[];
|
|
33
80
|
type: 'problem';
|
|
34
81
|
};
|
|
35
|
-
/** The manifest AND the entry: the name is in the manifest, and the finding lands on the entry. */
|
|
36
82
|
probe(options: readonly unknown[]): ProbeTree;
|
|
37
83
|
};
|
|
38
84
|
export default noGenericUtilityPackage;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/rules/no-generic-utility-package.ts
|
|
2
|
-
import {
|
|
2
|
+
import { readManifest } from "./lib/module-text.js";
|
|
3
3
|
import { normalise } from "./lib/paths.js";
|
|
4
4
|
import { bagOf } from "./lib/probe.js";
|
|
5
|
-
import { optionsBag, STRINGS } from "./lib/schema.js";
|
|
5
|
+
import { optionsBag, STRING, STRINGS } from "./lib/schema.js";
|
|
6
6
|
var NAMES = ["common", "helpers", "misc", "shared", "utils"];
|
|
7
7
|
var ENTRIES = [
|
|
8
8
|
"index.ts",
|
|
@@ -15,19 +15,41 @@ var ENTRIES = [
|
|
|
15
15
|
"src/index.jsx"
|
|
16
16
|
];
|
|
17
17
|
var MANIFEST = "package.json";
|
|
18
|
-
var
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return null;
|
|
18
|
+
var exportedNames = (body) => body.flatMap((statement) => {
|
|
19
|
+
if (statement.type === "ExportDefaultDeclaration")
|
|
20
|
+
return ["default"];
|
|
21
|
+
if (statement.type === "ExportAllDeclaration") {
|
|
22
|
+
return [statement.exported?.name ?? statement.exported?.value ?? "*"];
|
|
24
23
|
}
|
|
24
|
+
if (statement.type !== "ExportNamedDeclaration")
|
|
25
|
+
return [];
|
|
26
|
+
const declared = statement.declaration;
|
|
27
|
+
const fromDeclaration = [
|
|
28
|
+
declared?.id?.name,
|
|
29
|
+
...(declared?.declarations ?? []).map((one) => one.id?.name)
|
|
30
|
+
];
|
|
31
|
+
const fromSpecifiers = (statement.specifiers ?? []).map((one) => one.exported?.name ?? one.exported?.value);
|
|
32
|
+
return [...fromDeclaration, ...fromSpecifiers].filter((one) => one !== undefined);
|
|
33
|
+
});
|
|
34
|
+
var doorSchema = {
|
|
35
|
+
items: {
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
properties: { exports: STRINGS, package: STRING },
|
|
38
|
+
required: ["package", "exports"],
|
|
39
|
+
type: "object"
|
|
40
|
+
},
|
|
41
|
+
type: "array"
|
|
42
|
+
};
|
|
43
|
+
var nameOf = (root) => {
|
|
44
|
+
const name = readManifest(`${root}/${MANIFEST}`)?.name;
|
|
45
|
+
return typeof name === "string" ? name : null;
|
|
25
46
|
};
|
|
26
47
|
var noGenericUtilityPackage = {
|
|
27
48
|
create(context) {
|
|
28
49
|
const options = bagOf(context.options);
|
|
29
50
|
const names = new Set(options.names ?? NAMES);
|
|
30
51
|
const entries = options.entries ?? ENTRIES;
|
|
52
|
+
const allowed = options.allow ?? [];
|
|
31
53
|
const filename = normalise(context.filename);
|
|
32
54
|
return {
|
|
33
55
|
Program(node) {
|
|
@@ -40,6 +62,18 @@ var noGenericUtilityPackage = {
|
|
|
40
62
|
const last = name.split("/").at(-1) ?? "";
|
|
41
63
|
if (!names.has(last))
|
|
42
64
|
return;
|
|
65
|
+
const door = allowed.find((one) => one.package === name);
|
|
66
|
+
if (door !== undefined) {
|
|
67
|
+
const extra = exportedNames(node.body ?? []).filter((one) => !door.exports.includes(one));
|
|
68
|
+
if (extra.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
context.report({
|
|
71
|
+
data: { extra: extra.join(", "), name, allowed: door.exports.join(", ") },
|
|
72
|
+
messageId: "beyondTheDoor",
|
|
73
|
+
node
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
43
77
|
context.report({ data: { generic: last, name }, messageId: "generic", node });
|
|
44
78
|
}
|
|
45
79
|
};
|
|
@@ -49,15 +83,20 @@ about what is inside, so nothing can be refused entry and everything arrives: we
|
|
|
49
83
|
object, a time door. Split it by what each export is FOR and give each half a name that would make a
|
|
50
84
|
wrong import obviously wrong — the formatting goes to the feature's own lib, the constants go beside
|
|
51
85
|
their owner, a door gets its own package named after the thing it is a door to. Delete the rest. The
|
|
52
|
-
anti-names are the \`names\` option and the default is the universal list, never a repo's vocabulary
|
|
86
|
+
anti-names are the \`names\` option and the default is the universal list, never a repo's vocabulary.
|
|
87
|
+
One package may keep a generic name for one reason: \`allow: [{ package, exports }]\` names the door it
|
|
88
|
+
survives to hold, and the package is clean while its entry exports nothing else — a second export is
|
|
89
|
+
the finding. A star re-export cannot be vouched for and counts as one.`,
|
|
53
90
|
meta: {
|
|
54
91
|
docs: {
|
|
55
92
|
description: "A workspace package named `utils` / `common` / `helpers` / `shared` / `misc` is named for nothing, so it accretes everything. Reported once, at the package entry, from the name in its own manifest."
|
|
56
93
|
},
|
|
57
94
|
messages: {
|
|
95
|
+
beyondTheDoor: 'The package "{{name}}" is allowed to hold {{allowed}} and nothing else, and it also exports {{extra}}. Move each of those to the package named for what it does.',
|
|
58
96
|
generic: 'The package "{{name}}" is named for nothing: "{{generic}}" says what is inside to no one, so nothing can be refused entry and everything arrives. Split it by what each export is for and name each half after the thing it does.'
|
|
59
97
|
},
|
|
60
98
|
schema: optionsBag({
|
|
99
|
+
allow: doorSchema,
|
|
61
100
|
entries: STRINGS,
|
|
62
101
|
names: STRINGS
|
|
63
102
|
}),
|