@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.
Files changed (248) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +75 -6
  3. package/RULES.md +202 -101
  4. package/corpus/.oxlintrc.json +5 -0
  5. package/corpus/data/bad-dynamic-orm.ts +5 -0
  6. package/corpus/data/bad-type-only-orm.ts +3 -0
  7. package/corpus/e2e/bad-list-response.spec.ts +5 -0
  8. package/corpus/e2e/bad-timeout.spec.ts +5 -0
  9. package/corpus/e2e/good-detail-response.spec.ts +5 -0
  10. package/corpus/e2e/good-state.spec.ts +5 -0
  11. package/corpus/manifest.json +2 -0
  12. package/corpus/medusa/queries/bad-silent-cap.ts +11 -0
  13. package/corpus/medusa/queries/good-paged-through.ts +19 -0
  14. package/corpus/medusa/queries/integration-tests/any-row.ts +10 -0
  15. package/corpus/tissues/bad-awaits-data.tsx +8 -0
  16. package/corpus/tissues/bad-fetches-data.tsx +7 -0
  17. package/corpus/tissues/good-takes-products.tsx +5 -0
  18. package/dist/discover-atoms.d.ts +2 -37
  19. package/dist/discover-atoms.js +1 -104
  20. package/dist/index.d.ts +3 -12
  21. package/dist/index.js +21 -176
  22. package/dist/presets.d.ts +11 -19
  23. package/dist/presets.js +94 -4
  24. package/dist/registry.d.ts +2 -0
  25. package/dist/registry.js +172 -0
  26. package/dist/rules/atom-no-deps.d.ts +1 -0
  27. package/dist/rules/atom-no-deps.js +10 -11
  28. package/dist/rules/boundary-time-is-primitive.d.ts +0 -7
  29. package/dist/rules/cell-must-be-stateful.d.ts +2 -2
  30. package/dist/rules/cell-must-be-stateful.js +24 -23
  31. package/dist/rules/cell-must-not-compose-cell.d.ts +1 -0
  32. package/dist/rules/cell-must-not-compose-cell.js +10 -3
  33. package/dist/rules/cell-no-cross-feature-organelles.d.ts +1 -0
  34. package/dist/rules/cell-no-cross-feature-organelles.js +7 -0
  35. package/dist/rules/cell-no-tissues.d.ts +1 -0
  36. package/dist/rules/cell-no-tissues.js +11 -7
  37. package/dist/rules/cells-folder-index-is-barrel.d.ts +12 -10
  38. package/dist/rules/cells-folder-index-is-barrel.js +66 -34
  39. package/dist/rules/compound-must-be-stateless.d.ts +2 -0
  40. package/dist/rules/compound-must-be-stateless.js +24 -26
  41. package/dist/rules/compound-no-stores.d.ts +1 -0
  42. package/dist/rules/compound-no-stores.js +12 -10
  43. package/dist/rules/constants-in-constants-file.d.ts +2 -8
  44. package/dist/rules/constants-in-constants-file.js +5 -6
  45. package/dist/rules/dialect-through-the-seam.d.ts +0 -13
  46. package/dist/rules/durable-body-reads-through-steps.d.ts +0 -14
  47. package/dist/rules/durable-body-reads-through-steps.js +8 -3
  48. package/dist/rules/effect-hook-naming.d.ts +9 -11
  49. package/dist/rules/effect-hook-naming.js +33 -31
  50. package/dist/rules/emit-declares-attempts.d.ts +0 -17
  51. package/dist/rules/emit-declares-attempts.js +5 -10
  52. package/dist/rules/font-roles-only.d.ts +0 -18
  53. package/dist/rules/font-roles-only.js +19 -22
  54. package/dist/rules/hermetic-e2e-no-wall-clock-waits.d.ts +53 -0
  55. package/dist/rules/hermetic-e2e-no-wall-clock-waits.js +100 -0
  56. package/dist/rules/icon-control-has-name.js +7 -1
  57. package/dist/rules/layer-walls.d.ts +0 -25
  58. package/dist/rules/layer-walls.js +7 -13
  59. package/dist/rules/lib/atom-map.d.ts +24 -0
  60. package/dist/rules/lib/atom-map.js +16 -0
  61. package/dist/rules/lib/cells-barrel.d.ts +0 -1
  62. package/dist/rules/lib/colors.d.ts +0 -19
  63. package/dist/rules/lib/colors.js +10 -243
  64. package/dist/rules/lib/comments.d.ts +19 -0
  65. package/dist/rules/lib/comments.js +5 -0
  66. package/dist/rules/lib/context.d.ts +0 -10
  67. package/dist/rules/lib/direction-rule.d.ts +4 -21
  68. package/dist/rules/lib/direction-rule.js +25 -4
  69. package/dist/rules/lib/doors.d.ts +2 -18
  70. package/dist/rules/lib/doors.js +81 -41
  71. package/dist/rules/lib/fresh.d.ts +5 -0
  72. package/dist/rules/lib/fresh.js +15 -0
  73. package/dist/rules/lib/hooks.d.ts +2 -17
  74. package/dist/rules/lib/hooks.js +14 -9
  75. package/dist/rules/lib/imports.d.ts +2 -23
  76. package/dist/rules/lib/imports.js +7 -2
  77. package/dist/rules/lib/jsx.d.ts +31 -0
  78. package/dist/rules/lib/jsx.js +11 -0
  79. package/dist/rules/lib/landing.d.ts +0 -40
  80. package/dist/rules/lib/literal-call.d.ts +1 -0
  81. package/dist/rules/lib/literal-call.js +2 -0
  82. package/dist/rules/lib/module-text.d.ts +3 -27
  83. package/dist/rules/lib/module-text.js +19 -4
  84. package/dist/rules/lib/mutations.d.ts +0 -14
  85. package/dist/rules/lib/nodes.d.ts +1 -18
  86. package/dist/rules/lib/nodes.js +1 -0
  87. package/dist/rules/lib/objects.d.ts +2 -11
  88. package/dist/rules/lib/objects.js +21 -0
  89. package/dist/rules/lib/options.d.ts +3 -52
  90. package/dist/rules/lib/options.js +49 -18
  91. package/dist/rules/lib/package-imports.d.ts +0 -5
  92. package/dist/rules/lib/package-imports.js +4 -10
  93. package/dist/rules/lib/paths.d.ts +15 -41
  94. package/dist/rules/lib/paths.js +34 -1
  95. package/dist/rules/lib/pattern-match.d.ts +2 -12
  96. package/dist/rules/lib/pattern-match.js +5 -1
  97. package/dist/rules/lib/probe.d.ts +0 -43
  98. package/dist/rules/lib/reexports.d.ts +0 -18
  99. package/dist/rules/lib/render-tracking.d.ts +0 -8
  100. package/dist/rules/lib/run-files.d.ts +18 -0
  101. package/dist/rules/lib/run-files.js +144 -0
  102. package/dist/rules/lib/shared-features.d.ts +0 -8
  103. package/dist/rules/lib/shared-features.js +2 -9
  104. package/dist/rules/lib/state-sources.d.ts +8 -0
  105. package/dist/rules/lib/state-sources.js +55 -0
  106. package/dist/rules/lib/steps.d.ts +3 -5
  107. package/dist/rules/lib/steps.js +6 -1
  108. package/dist/rules/lib/styling.d.ts +9 -42
  109. package/dist/rules/lib/styling.js +36 -10
  110. package/dist/rules/lib/tiers.d.ts +11 -24
  111. package/dist/rules/lib/tiers.js +41 -1
  112. package/dist/rules/lib/tissue-entrypoint.d.ts +0 -1
  113. package/dist/rules/lib/tissue-entrypoint.js +4 -5
  114. package/dist/rules/lib/token-layer.d.ts +19 -0
  115. package/dist/rules/lib/token-layer.js +170 -0
  116. package/dist/rules/lib/tsconfig-paths.d.ts +0 -4
  117. package/dist/rules/lib/tsconfig-paths.js +47 -15
  118. package/dist/rules/lib/type-members.d.ts +6 -0
  119. package/dist/rules/lib/type-members.js +165 -0
  120. package/dist/rules/lib/visitors.d.ts +0 -22
  121. package/dist/rules/lib/workspace-members.d.ts +2 -0
  122. package/dist/rules/lib/workspace-members.js +113 -0
  123. package/dist/rules/list-take-without-order.d.ts +24 -0
  124. package/dist/rules/list-take-without-order.js +293 -16
  125. package/dist/rules/max-comment-density.d.ts +3 -18
  126. package/dist/rules/max-comment-density.js +3 -3
  127. package/dist/rules/molecule-atoms-only.d.ts +1 -0
  128. package/dist/rules/molecule-atoms-only.js +11 -7
  129. package/dist/rules/molecule-must-compose.d.ts +2 -0
  130. package/dist/rules/molecule-must-compose.js +17 -21
  131. package/dist/rules/mutating-step-declares-retry.d.ts +2 -7
  132. package/dist/rules/mutating-step-declares-retry.js +29 -11
  133. package/dist/rules/named-when-block.d.ts +0 -2
  134. package/dist/rules/named-when-block.js +2 -8
  135. package/dist/rules/next-route-segment-is-thin-delegate.d.ts +4 -19
  136. package/dist/rules/next-route-segment-is-thin-delegate.js +47 -71
  137. package/dist/rules/no-brand-names.d.ts +0 -9
  138. package/dist/rules/no-brand-names.js +1 -1
  139. package/dist/rules/no-card-shaped-div.d.ts +4 -42
  140. package/dist/rules/no-card-shaped-div.js +12 -4
  141. package/dist/rules/no-cross-feature-stores.d.ts +1 -0
  142. package/dist/rules/no-cross-feature-stores.js +9 -5
  143. package/dist/rules/no-d1-transaction.d.ts +2 -5
  144. package/dist/rules/no-d1-transaction.js +7 -0
  145. package/dist/rules/no-date-string-compare.d.ts +2 -0
  146. package/dist/rules/no-date-string-compare.js +14 -8
  147. package/dist/rules/no-duplicate-jsx-patterns.d.ts +3 -1
  148. package/dist/rules/no-duplicate-jsx-patterns.js +16 -1
  149. package/dist/rules/no-generic-utility-package.d.ts +48 -2
  150. package/dist/rules/no-generic-utility-package.js +48 -9
  151. package/dist/rules/no-hook-in-component-disguise.d.ts +27 -13
  152. package/dist/rules/no-hook-in-component-disguise.js +38 -30
  153. package/dist/rules/no-index-count-as-exact.d.ts +0 -3
  154. package/dist/rules/no-inert-hidden-jsx.d.ts +4 -21
  155. package/dist/rules/no-inert-hidden-jsx.js +13 -12
  156. package/dist/rules/no-inline-data-in-jsx.d.ts +2 -0
  157. package/dist/rules/no-inline-data-in-jsx.js +17 -2
  158. package/dist/rules/no-invalid-feature-folders.d.ts +2 -0
  159. package/dist/rules/no-invalid-feature-folders.js +12 -4
  160. package/dist/rules/no-loader-side-effect-in-index-file.d.ts +2 -9
  161. package/dist/rules/no-logic-in-component-files.d.ts +6 -5
  162. package/dist/rules/no-logic-in-component-files.js +52 -31
  163. package/dist/rules/no-mock-db-in-integration.d.ts +0 -15
  164. package/dist/rules/no-mock-db-in-integration.js +3 -4
  165. package/dist/rules/no-orm-outside-db.d.ts +1 -0
  166. package/dist/rules/no-orm-outside-db.js +10 -7
  167. package/dist/rules/no-raw-html-atoms.d.ts +3 -22
  168. package/dist/rules/no-raw-html-atoms.js +5 -17
  169. package/dist/rules/no-raw-sql-outside-allowed.d.ts +2 -0
  170. package/dist/rules/no-raw-sql-outside-allowed.js +10 -15
  171. package/dist/rules/no-react-namespace.d.ts +2 -0
  172. package/dist/rules/no-react-namespace.js +13 -0
  173. package/dist/rules/no-renamed-html-props.d.ts +2 -0
  174. package/dist/rules/no-renamed-html-props.js +11 -2
  175. package/dist/rules/no-render-prop-reader.d.ts +4 -20
  176. package/dist/rules/no-render-prop-reader.js +13 -5
  177. package/dist/rules/no-runtime-dead-affordance.d.ts +3 -26
  178. package/dist/rules/no-runtime-dead-affordance.js +7 -20
  179. package/dist/rules/no-swallowed-catch.js +2 -8
  180. package/dist/rules/no-trivial-wrapper-component.d.ts +4 -19
  181. package/dist/rules/no-trivial-wrapper-component.js +24 -33
  182. package/dist/rules/no-ts-in-bio-folders.d.ts +2 -13
  183. package/dist/rules/no-ts-in-bio-folders.js +9 -7
  184. package/dist/rules/no-type-definitions-in-components.d.ts +2 -0
  185. package/dist/rules/no-type-definitions-in-components.js +12 -1
  186. package/dist/rules/no-unregistered-plugin-dir.d.ts +0 -41
  187. package/dist/rules/no-unregistered-plugin-dir.js +1 -9
  188. package/dist/rules/no-void-port.d.ts +2 -4
  189. package/dist/rules/no-void-port.js +7 -0
  190. package/dist/rules/organelle-dependency.d.ts +1 -0
  191. package/dist/rules/organelle-dependency.js +10 -8
  192. package/dist/rules/organelle-single-source.d.ts +2 -0
  193. package/dist/rules/organelle-single-source.js +58 -9
  194. package/dist/rules/plugin-route-namespaced.d.ts +0 -11
  195. package/dist/rules/plugin-route-namespaced.js +2 -2
  196. package/dist/rules/queries-require-org-scope.d.ts +0 -3
  197. package/dist/rules/queries-require-org-scope.js +3 -10
  198. package/dist/rules/queue-loop-is-the-library.js +3 -2
  199. package/dist/rules/route-no-inline-mutation.d.ts +1 -25
  200. package/dist/rules/route-no-inline-mutation.js +2 -7
  201. package/dist/rules/router-schema-parity.d.ts +0 -9
  202. package/dist/rules/router-schema-parity.js +5 -9
  203. package/dist/rules/service-name-not-reserved.d.ts +0 -3
  204. package/dist/rules/service-name-not-reserved.js +1 -34
  205. package/dist/rules/ssot-no-inline-facts.js +2 -5
  206. package/dist/rules/ssot-no-process-env.js +4 -12
  207. package/dist/rules/step-declares-compensation-or-none.d.ts +2 -1
  208. package/dist/rules/step-declares-compensation-or-none.js +17 -1
  209. package/dist/rules/step-opens-its-own-cell.d.ts +3 -26
  210. package/dist/rules/step-opens-its-own-cell.js +8 -13
  211. package/dist/rules/store-route-scopes-tenant-data.js +8 -26
  212. package/dist/rules/subpath-over-barrel.d.ts +3 -10
  213. package/dist/rules/subpath-over-barrel.js +55 -10
  214. package/dist/rules/subscriber-declares-id.d.ts +0 -2
  215. package/dist/rules/subscriber-declares-id.js +2 -8
  216. package/dist/rules/tables-declare-their-plane.d.ts +0 -2
  217. package/dist/rules/tables-declare-their-plane.js +4 -10
  218. package/dist/rules/tenant-tables-carry-org-id.d.ts +1 -4
  219. package/dist/rules/tenant-tables-carry-org-id.js +5 -12
  220. package/dist/rules/test-no-clock-polling-for-promise-flips.d.ts +7 -1
  221. package/dist/rules/test-no-clock-polling-for-promise-flips.js +21 -1
  222. package/dist/rules/time-through-the-door.d.ts +0 -24
  223. package/dist/rules/time-through-the-door.js +5 -5
  224. package/dist/rules/tissue-must-compose.d.ts +4 -2
  225. package/dist/rules/tissue-must-compose.js +25 -20
  226. package/dist/rules/tissue-no-data-props.d.ts +57 -1
  227. package/dist/rules/tissue-no-data-props.js +114 -32
  228. package/dist/rules/tissue-no-fetch.d.ts +26 -0
  229. package/dist/rules/tissue-no-fetch.js +57 -0
  230. package/dist/rules/tissue-no-hooks.d.ts +21 -23
  231. package/dist/rules/tissue-no-hooks.js +22 -4
  232. package/dist/rules/tissue-no-organelles.d.ts +1 -0
  233. package/dist/rules/tissue-no-organelles.js +9 -1
  234. package/dist/rules/tissue-no-stores.d.ts +1 -0
  235. package/dist/rules/tissue-no-stores.js +9 -1
  236. package/dist/rules/todo-names-its-plan.d.ts +1 -28
  237. package/dist/rules/todo-names-its-plan.js +2 -2
  238. package/dist/rules/tokens-only-colors.d.ts +8 -46
  239. package/dist/rules/tokens-only-colors.js +64 -20
  240. package/dist/rules/unique-query-step-name.d.ts +0 -7
  241. package/dist/rules/unique-query-step-name.js +6 -10
  242. package/dist/rules/worker-handles-are-scoped.d.ts +11 -8
  243. package/dist/rules/worker-handles-are-scoped.js +12 -5
  244. package/dist/rules/zustand-v5-best-practices.d.ts +8 -18
  245. package/dist/rules/zustand-v5-best-practices.js +32 -56
  246. package/dist/timing.d.ts +18 -0
  247. package/dist/timing.js +75 -0
  248. 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 { isModule, normalise } from "./lib/paths.js";
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 { allow = [], exemptModules = [], prefix = "--font-", roles } = context.options?.[0] ?? {};
14
+ const { prefix = "--font-", roles, ...exempt } = context.options?.[0] ?? {};
15
15
  const declared = requireOption("font-roles-only", roles, "roles");
16
- const filename = normalise(context.filename);
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.replace(/^.*:/, "") === "font" && !WEIGHT.test(inner))
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
- A spread may carry the name and is left alone.`,
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 inverts = (pattern, leaf) => {
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 false;
15
+ return;
17
16
  }
18
17
  };
19
18
  var placeIn = (paths, tried) => {
20
19
  for (const pattern of paths) {
21
- let candidate;
22
- try {
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) => inverts(one, PROBE_TARGET));
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,2 +1 @@
1
- /** Takes a path already normalised to forward slashes. */
2
1
  export declare const isCellsBarrel: (path: string) => boolean;
@@ -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;