@geonosis/oxlint-plugin-biological-architecture 3.1.0 → 3.3.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 +57 -0
- package/README.md +75 -6
- package/RULES.md +202 -101
- package/corpus/.oxlintrc.json +5 -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/manifest.json +2 -0
- package/corpus/medusa/queries/bad-silent-cap.ts +11 -0
- package/corpus/medusa/queries/good-paged-through.ts +19 -0
- package/corpus/medusa/queries/integration-tests/any-row.ts +10 -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 +21 -176
- package/dist/presets.d.ts +11 -19
- package/dist/presets.js +94 -4
- 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.js +7 -1
- 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 +4 -21
- package/dist/rules/lib/direction-rule.js +25 -4
- package/dist/rules/lib/doors.d.ts +2 -18
- package/dist/rules/lib/doors.js +81 -41
- package/dist/rules/lib/fresh.d.ts +5 -0
- package/dist/rules/lib/fresh.js +15 -0
- 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 +3 -52
- package/dist/rules/lib/options.js +49 -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/run-files.d.ts +18 -0
- package/dist/rules/lib/run-files.js +144 -0
- 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/token-layer.d.ts +19 -0
- package/dist/rules/lib/token-layer.js +170 -0
- 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 +113 -0
- package/dist/rules/list-take-without-order.d.ts +24 -0
- package/dist/rules/list-take-without-order.js +293 -16
- 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 +2 -0
- 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 +3 -1
- 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 +2 -9
- 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 +10 -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 +3 -26
- 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 +3 -10
- package/dist/rules/subpath-over-barrel.js +55 -10
- 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 +7 -1
- package/dist/rules/test-no-clock-polling-for-promise-flips.js +21 -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 +57 -1
- 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 +21 -23
- 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 +8 -46
- package/dist/rules/tokens-only-colors.js +64 -20
- 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/dist/timing.d.ts +18 -0
- package/dist/timing.js +75 -0
- package/package.json +2 -2
|
@@ -2,17 +2,9 @@ import { type RuleContextWithOptions } from './lib/context.js';
|
|
|
2
2
|
import { type Probe } from './lib/probe.js';
|
|
3
3
|
import { type OpeningElement } from './lib/styling.js';
|
|
4
4
|
interface Options {
|
|
5
|
-
/** Paths where a family literal is the point — the theme file, an `@font-face` sheet. */
|
|
6
5
|
allow?: string[];
|
|
7
|
-
/**
|
|
8
|
-
* The modules this rule does not judge, each written out in full — the enumeration a pattern
|
|
9
|
-
* cannot be. A glob grants its exemption to wherever it stops and nobody counts what accumulates
|
|
10
|
-
* on the far side; a list of module names is a number a reader can hold.
|
|
11
|
-
*/
|
|
12
6
|
exemptModules?: string[];
|
|
13
|
-
/** How a themed family variable is spelled. Default `--font-`. */
|
|
14
7
|
prefix?: string;
|
|
15
|
-
/** The role names this repo's contract declares. */
|
|
16
8
|
roles?: string[];
|
|
17
9
|
}
|
|
18
10
|
declare const fontRolesOnly: {
|
|
@@ -23,11 +15,6 @@ declare const fontRolesOnly: {
|
|
|
23
15
|
};
|
|
24
16
|
fixShape: string;
|
|
25
17
|
meta: {
|
|
26
|
-
/**
|
|
27
|
-
* #139: which option grants by PATTERN and which one ENUMERATES. Declared on the rule so the
|
|
28
|
-
* doctor needs no list of its own — a rule that gains a path grant and forgets to say so is a
|
|
29
|
-
* grant nothing counts, which is the defect this pair exists to end.
|
|
30
|
-
*/
|
|
31
18
|
grants: {
|
|
32
19
|
modules: string;
|
|
33
20
|
paths: string;
|
|
@@ -68,11 +55,6 @@ declare const fontRolesOnly: {
|
|
|
68
55
|
}[];
|
|
69
56
|
type: 'problem';
|
|
70
57
|
};
|
|
71
|
-
/**
|
|
72
|
-
* A family literal, which fires whatever the declared roles are — the question `exercised` asks
|
|
73
|
-
* is whether the rule RUNS here, and a probe built out of an undeclared role name would be
|
|
74
|
-
* defeated by a repo that happened to declare one spelled the same.
|
|
75
|
-
*/
|
|
76
58
|
probe(): Probe;
|
|
77
59
|
};
|
|
78
60
|
export default fontRolesOnly;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/rules/font-roles-only.ts
|
|
2
2
|
import { requireOption } from "./lib/options.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isExempt, normalise } from "./lib/paths.js";
|
|
4
4
|
import { optionsBag, STRING, STRINGS } from "./lib/schema.js";
|
|
5
5
|
import {
|
|
6
6
|
arbitraryValuesOf,
|
|
@@ -11,38 +11,35 @@ import {
|
|
|
11
11
|
var WEIGHT = /^\d+(?:\.\d+)?$/;
|
|
12
12
|
var fontRolesOnly = {
|
|
13
13
|
create(context) {
|
|
14
|
-
const {
|
|
14
|
+
const { prefix = "--font-", roles, ...exempt } = context.options?.[0] ?? {};
|
|
15
15
|
const declared = requireOption("font-roles-only", roles, "roles");
|
|
16
|
-
|
|
17
|
-
if (exemptModules.some((module) => isModule(filename, module)))
|
|
18
|
-
return {};
|
|
19
|
-
if (allow.some((path) => filename.includes(path)))
|
|
16
|
+
if (isExempt(normalise(context.filename), exempt))
|
|
20
17
|
return {};
|
|
18
|
+
const judge = (value, node) => {
|
|
19
|
+
const variable = variableOf(value);
|
|
20
|
+
if (variable === undefined) {
|
|
21
|
+
context.report({ data: { value }, messageId: "familyLiteral", node });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!variable.startsWith(prefix))
|
|
25
|
+
return;
|
|
26
|
+
const role = variable.slice(prefix.length);
|
|
27
|
+
if (!declared.includes(role)) {
|
|
28
|
+
context.report({ data: { role, value }, messageId: "undeclaredRole", node });
|
|
29
|
+
}
|
|
30
|
+
};
|
|
21
31
|
return {
|
|
22
32
|
JSXOpeningElement(node) {
|
|
23
|
-
const judge = (value) => {
|
|
24
|
-
const variable = variableOf(value);
|
|
25
|
-
if (variable === undefined) {
|
|
26
|
-
context.report({ data: { value }, messageId: "familyLiteral", node });
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (!variable.startsWith(prefix))
|
|
30
|
-
return;
|
|
31
|
-
const role = variable.slice(prefix.length);
|
|
32
|
-
if (!declared.includes(role)) {
|
|
33
|
-
context.report({ data: { role, value }, messageId: "undeclaredRole", node });
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
33
|
for (const { property, value } of styleEntriesOf(node)) {
|
|
37
34
|
if (property === "fontFamily" || property === "font")
|
|
38
|
-
judge(value);
|
|
35
|
+
judge(value, node);
|
|
39
36
|
}
|
|
40
37
|
const className = classNameOf(node);
|
|
41
38
|
if (className === undefined)
|
|
42
39
|
return;
|
|
43
40
|
for (const [utility, inner] of arbitraryValuesOf(className)) {
|
|
44
|
-
if (utility
|
|
45
|
-
judge(inner);
|
|
41
|
+
if (utility === "font" && !WEIGHT.test(inner))
|
|
42
|
+
judge(inner, node);
|
|
46
43
|
}
|
|
47
44
|
}
|
|
48
45
|
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type RuleContextWithRawOptions } from './lib/context.js';
|
|
2
|
+
import { type AnyNode } from './lib/nodes.js';
|
|
3
|
+
import { type Probe } from './lib/probe.js';
|
|
4
|
+
declare const hermeticE2eNoWallClockWaits: {
|
|
5
|
+
create(context: RuleContextWithRawOptions): {
|
|
6
|
+
CallExpression?: undefined;
|
|
7
|
+
} | {
|
|
8
|
+
CallExpression(node: AnyNode): void;
|
|
9
|
+
};
|
|
10
|
+
fixShape: string;
|
|
11
|
+
meta: {
|
|
12
|
+
docs: {
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
messages: {
|
|
16
|
+
clockWait: string;
|
|
17
|
+
listResponseWait: string;
|
|
18
|
+
};
|
|
19
|
+
schema: {
|
|
20
|
+
additionalProperties: boolean;
|
|
21
|
+
properties: {
|
|
22
|
+
clockWaits: {
|
|
23
|
+
items: {
|
|
24
|
+
type: 'string';
|
|
25
|
+
};
|
|
26
|
+
type: 'array';
|
|
27
|
+
};
|
|
28
|
+
listEndpoints: {
|
|
29
|
+
items: {
|
|
30
|
+
type: 'string';
|
|
31
|
+
};
|
|
32
|
+
type: 'array';
|
|
33
|
+
};
|
|
34
|
+
networkWaits: {
|
|
35
|
+
items: {
|
|
36
|
+
type: 'string';
|
|
37
|
+
};
|
|
38
|
+
type: 'array';
|
|
39
|
+
};
|
|
40
|
+
specs: {
|
|
41
|
+
items: {
|
|
42
|
+
type: 'string';
|
|
43
|
+
};
|
|
44
|
+
type: 'array';
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
type: 'object';
|
|
48
|
+
}[];
|
|
49
|
+
type: 'problem';
|
|
50
|
+
};
|
|
51
|
+
probe(options: readonly unknown[]): Probe;
|
|
52
|
+
};
|
|
53
|
+
export default hermeticE2eNoWallClockWaits;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/rules/hermetic-e2e-no-wall-clock-waits.ts
|
|
2
|
+
import { calleeNameOf, propertyNameOf, walkNode } from "./lib/nodes.js";
|
|
3
|
+
import { normalise } from "./lib/paths.js";
|
|
4
|
+
import { bagOf, firstOf, pathMatching } from "./lib/probe.js";
|
|
5
|
+
import { optionsBag, STRINGS } from "./lib/schema.js";
|
|
6
|
+
var SPECS = [String.raw`(?:^|/)e2e[^/]*/.*\.(?:spec|test)\.[cm]?[jt]sx?$`];
|
|
7
|
+
var CLOCK_WAITS = ["setTimeout", "waitForTimeout"];
|
|
8
|
+
var NETWORK_WAITS = ["waitForResponse"];
|
|
9
|
+
var calledName = (node) => {
|
|
10
|
+
const callee = node.callee;
|
|
11
|
+
return calleeNameOf(node) ?? (callee === undefined ? null : propertyNameOf(callee));
|
|
12
|
+
};
|
|
13
|
+
var textsIn = (nodes) => {
|
|
14
|
+
const found = [];
|
|
15
|
+
walkNode(nodes, (node) => {
|
|
16
|
+
if (node.type === "Literal") {
|
|
17
|
+
const regex = node.regex;
|
|
18
|
+
if (typeof regex?.pattern === "string")
|
|
19
|
+
found.push(regex.pattern);
|
|
20
|
+
else if (typeof node.value === "string")
|
|
21
|
+
found.push(node.value);
|
|
22
|
+
}
|
|
23
|
+
if (node.type === "TemplateLiteral") {
|
|
24
|
+
for (const quasi of node.quasis ?? []) {
|
|
25
|
+
found.push(quasi.value?.raw ?? "");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return found;
|
|
30
|
+
};
|
|
31
|
+
var hermeticE2eNoWallClockWaits = {
|
|
32
|
+
create(context) {
|
|
33
|
+
const options = bagOf(context.options);
|
|
34
|
+
const specs = options.specs ?? SPECS;
|
|
35
|
+
if (!specs.some((pattern) => new RegExp(pattern).test(normalise(context.filename))))
|
|
36
|
+
return {};
|
|
37
|
+
const clockWaits = new Set(options.clockWaits ?? CLOCK_WAITS);
|
|
38
|
+
const networkWaits = new Set(options.networkWaits ?? NETWORK_WAITS);
|
|
39
|
+
const endpoints = (options.listEndpoints ?? []).map((pattern) => new RegExp(pattern));
|
|
40
|
+
return {
|
|
41
|
+
CallExpression(node) {
|
|
42
|
+
const call = calledName(node);
|
|
43
|
+
if (call === null)
|
|
44
|
+
return;
|
|
45
|
+
if (clockWaits.has(call)) {
|
|
46
|
+
context.report({ data: { call }, messageId: "clockWait", node });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!networkWaits.has(call) || endpoints.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
const endpoint = textsIn(node.arguments).find((text) => endpoints.some((pattern) => pattern.test(text)));
|
|
52
|
+
if (endpoint !== undefined) {
|
|
53
|
+
context.report({ data: { call, endpoint }, messageId: "listResponseWait", node });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
fixShape: `An end-to-end spec waits for the state it asserts on, never for a time and never for the network
|
|
59
|
+
event that merely precedes it. \`page.waitForTimeout(500)\` or a promise sleep guesses how long the
|
|
60
|
+
page needs; the guess passes on a fast machine and flakes on a slow one. \`waitForResponse\` on a LIST
|
|
61
|
+
endpoint is the same guess with a better alibi: the response arriving says the request finished, not
|
|
62
|
+
that the page merged the rows into what the user sees. Assert on the page instead — a web-first
|
|
63
|
+
assertion on the element the data produces retries until it holds: \`await
|
|
64
|
+
expect(page.getByRole('row', { name })).toBeVisible()\`. The specs are the files \`specs\` names
|
|
65
|
+
(default: spec files under an \`e2e*\` directory), the calls that sleep are \`clockWaits\` (default
|
|
66
|
+
\`waitForTimeout\`, \`setTimeout\`), and the list endpoints whose response may not stand in for the
|
|
67
|
+
page are the regexes in \`listEndpoints\`, with \`networkWaits\` naming the calls that wait on one
|
|
68
|
+
(default \`waitForResponse\`); with no \`listEndpoints\` the response waits are not judged.`,
|
|
69
|
+
meta: {
|
|
70
|
+
docs: {
|
|
71
|
+
description: 'In an end-to-end spec, a fixed-time wait (`waitForTimeout`, a `setTimeout` sleep) guesses how long the page needs, and a `waitForResponse` on a list endpoint stands in for "the live data was merged" when only the page can say so. Assert on the page state instead. The spec files, the sleeping calls, the waiting calls and the list endpoints are options; the response wait is judged only for endpoints the repo names.'
|
|
72
|
+
},
|
|
73
|
+
messages: {
|
|
74
|
+
clockWait: "`{{call}}` waits for a time, not for a state: it passes when the machine is fast and flakes when it is slow. Wait for the state the next step needs — a web-first assertion on the element it produces retries until it holds.",
|
|
75
|
+
listResponseWait: "`{{call}}` on a list endpoint ({{endpoint}}) treats the response arriving as the data being shown, and the page merges it later: a race. Assert on what the page renders — a web-first assertion on the element the data produces retries until it holds."
|
|
76
|
+
},
|
|
77
|
+
schema: optionsBag({
|
|
78
|
+
clockWaits: STRINGS,
|
|
79
|
+
listEndpoints: STRINGS,
|
|
80
|
+
networkWaits: STRINGS,
|
|
81
|
+
specs: STRINGS
|
|
82
|
+
}),
|
|
83
|
+
type: "problem"
|
|
84
|
+
},
|
|
85
|
+
probe(options) {
|
|
86
|
+
const bag = bagOf(options);
|
|
87
|
+
const call = firstOf(bag.clockWaits, ["waitForTimeout"]);
|
|
88
|
+
return {
|
|
89
|
+
path: pathMatching(firstOf(bag.specs, SPECS), "geonosis-probe.spec.ts"),
|
|
90
|
+
source: `export const geonosisProbe = async (page) => {
|
|
91
|
+
await page.${call}(500)
|
|
92
|
+
}
|
|
93
|
+
`
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var hermetic_e2e_no_wall_clock_waits_default = hermeticE2eNoWallClockWaits;
|
|
98
|
+
export {
|
|
99
|
+
hermetic_e2e_no_wall_clock_waits_default as default
|
|
100
|
+
};
|
|
@@ -4,8 +4,10 @@ import { optionsBag, STRING, STRINGS } from "./lib/schema.js";
|
|
|
4
4
|
var NATIVE_CONTROLS = ["button", "a"];
|
|
5
5
|
var ICONS = "Icon$";
|
|
6
6
|
var NAMED_BY = new Set(["aria-label", "aria-labelledby", "title"]);
|
|
7
|
+
var ICON_NAMED_BY = new Set(["aria-label", "aria-labelledby"]);
|
|
7
8
|
var nameOf = (node) => node === undefined ? "" : node.type === "JSXMemberExpression" ? `${nameOf(node.object)}.${nameOf(node.property)}` : node.name ?? "";
|
|
8
9
|
var said = (child) => child.type !== "JSXText" || (child.value ?? "").trim() !== "";
|
|
10
|
+
var namesItself = (icon) => icon.type === "JSXElement" && ((icon.openingElement?.attributes ?? []).some((one) => one.type === "JSXAttribute" && ICON_NAMED_BY.has(nameOf(one.name))) || nameOf(icon.openingElement?.name) === "svg" && (icon.children ?? []).some((one) => nameOf(one.openingElement?.name) === "title"));
|
|
9
11
|
var iconControlHasName = {
|
|
10
12
|
create(context) {
|
|
11
13
|
const options = bagOf(context.options);
|
|
@@ -21,6 +23,8 @@ var iconControlHasName = {
|
|
|
21
23
|
return;
|
|
22
24
|
}
|
|
23
25
|
const shown = (node.children ?? []).filter(said);
|
|
26
|
+
if (shown.some(namesItself))
|
|
27
|
+
return;
|
|
24
28
|
const icons = shown.map((one) => one.type === "JSXElement" ? nameOf(one.openingElement?.name) : "");
|
|
25
29
|
if (icons.length === 0 || !icons.every((name) => name === "svg" || icon.test(name)))
|
|
26
30
|
return;
|
|
@@ -38,7 +42,9 @@ app's own words — a label prop the caller passes, never a string the component
|
|
|
38
42
|
beside the icon, visible or \`sr-only\`. \`<button>\` and \`<a>\` are read by default; \`controls\` names the
|
|
39
43
|
repo's control atoms, and \`icons\` is the pattern an icon component's name matches (\`Icon$\` by default);
|
|
40
44
|
an inline \`<svg>\` is always one.
|
|
41
|
-
|
|
45
|
+
An icon that carries its own \`aria-label\` or \`aria-labelledby\` (an icon component must forward it to its
|
|
46
|
+
svg), or an \`<svg>\` holding a \`<title>\`, names the control; a \`title\` attribute on an icon does not. A
|
|
47
|
+
spread may carry the name and is left alone.`,
|
|
42
48
|
meta: {
|
|
43
49
|
docs: {
|
|
44
50
|
description: "A button or link whose only content is an icon component, with no `aria-label`, `aria-labelledby` or `title`, has no accessible name — a screen reader announces a control that says nothing."
|
|
@@ -1,30 +1,14 @@
|
|
|
1
1
|
import { type RuleContextWithOptions } from './lib/context.js';
|
|
2
2
|
import { type Probe } from './lib/probe.js';
|
|
3
3
|
interface Layer {
|
|
4
|
-
/** Layer names this one may import. A layer may always import itself; this lists the others. */
|
|
5
4
|
mayImport?: string[];
|
|
6
5
|
name: string;
|
|
7
|
-
/** Regex sources, tested against the normalised filename and against an import's source. */
|
|
8
6
|
paths: string[];
|
|
9
|
-
/** This layer's own answer to the rule-wide `typeOnly`, for the reason written there. */
|
|
10
7
|
typeOnly?: 'count' | 'skip';
|
|
11
8
|
}
|
|
12
9
|
interface Options {
|
|
13
|
-
/**
|
|
14
|
-
* Whether a bare package specifier or a `node:` builtin counts as crossing a wall. It does not by
|
|
15
|
-
* default: a layer that may import no other layer still runs on a machine with a filesystem, and
|
|
16
|
-
* a rule that called `node:path` a breach would be unusable in every repo that has one.
|
|
17
|
-
*/
|
|
18
10
|
externals?: 'allow' | 'deny';
|
|
19
11
|
layers?: Layer[];
|
|
20
|
-
/**
|
|
21
|
-
* D-034's named exception, and the default every layer keeps unless it says otherwise. A type the
|
|
22
|
-
* bundler erases is not a dependency, and three packages in this repo borrow one across a wall on
|
|
23
|
-
* purpose so their runtime graph stays clean. `count` is for walls that are about KNOWLEDGE
|
|
24
|
-
* rather than weight — a package floor, where `import type { Document }` in a foundation is the
|
|
25
|
-
* upward reach the floors exist to forbid, compiled away or not. One repo has both kinds, so a
|
|
26
|
-
* layer may override this for its own files.
|
|
27
|
-
*/
|
|
28
12
|
typeOnly?: 'count' | 'skip';
|
|
29
13
|
}
|
|
30
14
|
declare const layerWalls: {
|
|
@@ -83,15 +67,6 @@ declare const layerWalls: {
|
|
|
83
67
|
}[];
|
|
84
68
|
type: 'problem';
|
|
85
69
|
};
|
|
86
|
-
/**
|
|
87
|
-
* A file in one declared layer importing a specifier that lands in another it may not reach.
|
|
88
|
-
*
|
|
89
|
-
* Both halves come out of the same regex sources the walls are written in — the filename from the
|
|
90
|
-
* layer it must sit in, the import from the layer it must not reach — and both are re-tested
|
|
91
|
-
* against the patterns they were built from. A pair the inversion cannot express is a sentence
|
|
92
|
-
* rather than a silent zero: the walls are a repo's own vocabulary, so there is no fixture
|
|
93
|
-
* anywhere that could stand in for them.
|
|
94
|
-
*/
|
|
95
70
|
probe(options: readonly unknown[]): Probe;
|
|
96
71
|
};
|
|
97
72
|
export default layerWalls;
|
|
@@ -8,25 +8,19 @@ import { onImportSource } from "./lib/visitors.js";
|
|
|
8
8
|
var isExternal = (source) => !isRelative(source) && !source.startsWith("/");
|
|
9
9
|
var PROBE_TARGET = "geonosis-probe-target.ts";
|
|
10
10
|
var PROBE_WALL = "geonosis-probe-wall.ts";
|
|
11
|
-
var
|
|
11
|
+
var invert = (pattern, leaf) => {
|
|
12
12
|
try {
|
|
13
|
-
pathMatching(pattern, leaf);
|
|
14
|
-
return true;
|
|
13
|
+
return pathMatching(pattern, leaf);
|
|
15
14
|
} catch {
|
|
16
|
-
return
|
|
15
|
+
return;
|
|
17
16
|
}
|
|
18
17
|
};
|
|
19
18
|
var placeIn = (paths, tried) => {
|
|
20
19
|
for (const pattern of paths) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
candidate = pathMatching(pattern, PROBE_WALL);
|
|
24
|
-
} catch {
|
|
25
|
-
tried.push(pattern);
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
if (new RegExp(pattern).test(`/repo-root/${candidate}`))
|
|
20
|
+
const candidate = invert(pattern, PROBE_WALL);
|
|
21
|
+
if (candidate !== undefined && new RegExp(pattern).test(`/repo-root/${candidate}`)) {
|
|
29
22
|
return candidate;
|
|
23
|
+
}
|
|
30
24
|
tried.push(pattern);
|
|
31
25
|
}
|
|
32
26
|
return;
|
|
@@ -113,7 +107,7 @@ rule reports that once a run and judges nothing: walls nobody declared are not w
|
|
|
113
107
|
const tried = [];
|
|
114
108
|
for (const here of layers) {
|
|
115
109
|
const mayImport = new Set(here.mayImport ?? []);
|
|
116
|
-
const targetPath = layers.filter((other) => other.name !== here.name && !mayImport.has(other.name)).flatMap((other) => other.paths).find((one) =>
|
|
110
|
+
const targetPath = layers.filter((other) => other.name !== here.name && !mayImport.has(other.name)).flatMap((other) => other.paths).find((one) => invert(one, PROBE_TARGET) !== undefined);
|
|
117
111
|
const path = placeIn(here.paths, tried);
|
|
118
112
|
if (targetPath === undefined || path === undefined)
|
|
119
113
|
continue;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface Restriction {
|
|
2
|
+
atom: string;
|
|
3
|
+
element: string;
|
|
4
|
+
importPath: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const ATOM_MAP_SCHEMA: {
|
|
7
|
+
items: {
|
|
8
|
+
properties: {
|
|
9
|
+
atom: {
|
|
10
|
+
type: 'string';
|
|
11
|
+
};
|
|
12
|
+
element: {
|
|
13
|
+
type: 'string';
|
|
14
|
+
};
|
|
15
|
+
importPath: {
|
|
16
|
+
type: 'string';
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
required: string[];
|
|
20
|
+
type: 'object';
|
|
21
|
+
};
|
|
22
|
+
type: 'array';
|
|
23
|
+
};
|
|
24
|
+
export declare const atomMapOf: (options: readonly unknown[]) => Restriction[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/rules/lib/atom-map.ts
|
|
2
|
+
import { STRING } from "./schema.js";
|
|
3
|
+
var ATOM_MAP_SCHEMA = {
|
|
4
|
+
items: {
|
|
5
|
+
properties: { atom: STRING, element: STRING, importPath: STRING },
|
|
6
|
+
required: ["element", "atom", "importPath"],
|
|
7
|
+
type: "object"
|
|
8
|
+
},
|
|
9
|
+
type: "array"
|
|
10
|
+
};
|
|
11
|
+
var isRestriction = (value) => typeof value === "object" && value !== null && ("atom" in value) && typeof value.atom === "string" && ("element" in value) && typeof value.element === "string" && ("importPath" in value) && typeof value.importPath === "string";
|
|
12
|
+
var atomMapOf = (options) => Array.isArray(options[0]) ? options[0].filter(isRestriction) : [];
|
|
13
|
+
export {
|
|
14
|
+
ATOM_MAP_SCHEMA,
|
|
15
|
+
atomMapOf
|
|
16
|
+
};
|
|
@@ -1,23 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What counts as a colour written out.
|
|
3
|
-
*
|
|
4
|
-
* A CSS fact, not a repo fact, so it lives in code rather than in an option (law 6 is about a
|
|
5
|
-
* repo's brands, features and tables — the notations CSS accepts for a colour are the same
|
|
6
|
-
* everywhere). Every one of them is here because a brand escapes through all of them equally: the
|
|
7
|
-
* component that hardcodes `oklch(0.91 0.17 102)` stops following the theme exactly as completely
|
|
8
|
-
* as the one that hardcodes `#ffe849`.
|
|
9
|
-
*/
|
|
10
|
-
/** A whole value that is one written-out colour: `#ff0000`, `rgb(1 2 3)`, `tan`. */
|
|
11
1
|
export declare const isColorLiteral: (value: string) => boolean;
|
|
12
|
-
/**
|
|
13
|
-
* A colour written out ANYWHERE inside a value — for the shorthands.
|
|
14
|
-
*
|
|
15
|
-
* `border: '1px solid #ff0000'` and `boxShadow: '0 1px 2px rgba(0,0,0,.2)'` hardcode a colour just
|
|
16
|
-
* as thoroughly as `color` does, and a check that only looked at whole values would pass both.
|
|
17
|
-
* Named colours are deliberately NOT searched for here: `1px solid tan` is real and so is a
|
|
18
|
-
* `transition` naming a property, and a substring search over 148 English words inside a free-form
|
|
19
|
-
* value is how a rule earns its reputation for false positives.
|
|
20
|
-
*/
|
|
21
2
|
export declare const containsColorLiteral: (value: string) => boolean;
|
|
22
3
|
export declare const isColorProperty: (property: string) => boolean;
|
|
23
4
|
export declare const isColorShorthand: (property: string) => boolean;
|