@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.
Files changed (239) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +48 -5
  3. package/RULES.md +201 -98
  4. package/corpus/.oxlintrc.json +6 -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/icon-controls/bad-unnamed-icon-button.tsx +8 -0
  12. package/corpus/icon-controls/good-named-icon-button.tsx +19 -0
  13. package/corpus/manifest.json +3 -0
  14. package/corpus/tissues/bad-awaits-data.tsx +8 -0
  15. package/corpus/tissues/bad-fetches-data.tsx +7 -0
  16. package/corpus/tissues/good-takes-products.tsx +5 -0
  17. package/dist/discover-atoms.d.ts +2 -37
  18. package/dist/discover-atoms.js +1 -104
  19. package/dist/index.d.ts +3 -12
  20. package/dist/index.js +19 -174
  21. package/dist/presets.d.ts +11 -19
  22. package/dist/presets.js +89 -3
  23. package/dist/registry.d.ts +2 -0
  24. package/dist/registry.js +172 -0
  25. package/dist/rules/atom-no-deps.d.ts +1 -0
  26. package/dist/rules/atom-no-deps.js +10 -11
  27. package/dist/rules/boundary-time-is-primitive.d.ts +0 -7
  28. package/dist/rules/cell-must-be-stateful.d.ts +2 -2
  29. package/dist/rules/cell-must-be-stateful.js +24 -23
  30. package/dist/rules/cell-must-not-compose-cell.d.ts +1 -0
  31. package/dist/rules/cell-must-not-compose-cell.js +10 -3
  32. package/dist/rules/cell-no-cross-feature-organelles.d.ts +1 -0
  33. package/dist/rules/cell-no-cross-feature-organelles.js +7 -0
  34. package/dist/rules/cell-no-tissues.d.ts +1 -0
  35. package/dist/rules/cell-no-tissues.js +11 -7
  36. package/dist/rules/cells-folder-index-is-barrel.d.ts +12 -10
  37. package/dist/rules/cells-folder-index-is-barrel.js +66 -34
  38. package/dist/rules/compound-must-be-stateless.d.ts +2 -0
  39. package/dist/rules/compound-must-be-stateless.js +24 -26
  40. package/dist/rules/compound-no-stores.d.ts +1 -0
  41. package/dist/rules/compound-no-stores.js +12 -10
  42. package/dist/rules/constants-in-constants-file.d.ts +2 -8
  43. package/dist/rules/constants-in-constants-file.js +5 -6
  44. package/dist/rules/dialect-through-the-seam.d.ts +0 -13
  45. package/dist/rules/durable-body-reads-through-steps.d.ts +0 -14
  46. package/dist/rules/durable-body-reads-through-steps.js +8 -3
  47. package/dist/rules/effect-hook-naming.d.ts +9 -11
  48. package/dist/rules/effect-hook-naming.js +33 -31
  49. package/dist/rules/emit-declares-attempts.d.ts +0 -17
  50. package/dist/rules/emit-declares-attempts.js +5 -10
  51. package/dist/rules/font-roles-only.d.ts +0 -18
  52. package/dist/rules/font-roles-only.js +19 -22
  53. package/dist/rules/hermetic-e2e-no-wall-clock-waits.d.ts +53 -0
  54. package/dist/rules/hermetic-e2e-no-wall-clock-waits.js +100 -0
  55. package/dist/rules/icon-control-has-name.d.ts +53 -0
  56. package/dist/rules/icon-control-has-name.js +71 -0
  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 +3 -21
  68. package/dist/rules/lib/direction-rule.js +12 -3
  69. package/dist/rules/lib/doors.d.ts +2 -18
  70. package/dist/rules/lib/doors.js +79 -41
  71. package/dist/rules/lib/hooks.d.ts +2 -17
  72. package/dist/rules/lib/hooks.js +14 -9
  73. package/dist/rules/lib/imports.d.ts +2 -23
  74. package/dist/rules/lib/imports.js +7 -2
  75. package/dist/rules/lib/jsx.d.ts +31 -0
  76. package/dist/rules/lib/jsx.js +11 -0
  77. package/dist/rules/lib/landing.d.ts +0 -40
  78. package/dist/rules/lib/literal-call.d.ts +1 -0
  79. package/dist/rules/lib/literal-call.js +2 -0
  80. package/dist/rules/lib/module-text.d.ts +3 -27
  81. package/dist/rules/lib/module-text.js +19 -4
  82. package/dist/rules/lib/mutations.d.ts +0 -14
  83. package/dist/rules/lib/nodes.d.ts +1 -18
  84. package/dist/rules/lib/nodes.js +1 -0
  85. package/dist/rules/lib/objects.d.ts +2 -11
  86. package/dist/rules/lib/objects.js +21 -0
  87. package/dist/rules/lib/options.d.ts +2 -52
  88. package/dist/rules/lib/options.js +44 -18
  89. package/dist/rules/lib/package-imports.d.ts +0 -5
  90. package/dist/rules/lib/package-imports.js +4 -10
  91. package/dist/rules/lib/paths.d.ts +15 -41
  92. package/dist/rules/lib/paths.js +34 -1
  93. package/dist/rules/lib/pattern-match.d.ts +2 -12
  94. package/dist/rules/lib/pattern-match.js +5 -1
  95. package/dist/rules/lib/probe.d.ts +0 -43
  96. package/dist/rules/lib/reexports.d.ts +0 -18
  97. package/dist/rules/lib/render-tracking.d.ts +0 -8
  98. package/dist/rules/lib/shared-features.d.ts +0 -8
  99. package/dist/rules/lib/shared-features.js +2 -9
  100. package/dist/rules/lib/state-sources.d.ts +8 -0
  101. package/dist/rules/lib/state-sources.js +55 -0
  102. package/dist/rules/lib/steps.d.ts +3 -5
  103. package/dist/rules/lib/steps.js +6 -1
  104. package/dist/rules/lib/styling.d.ts +9 -42
  105. package/dist/rules/lib/styling.js +36 -10
  106. package/dist/rules/lib/tiers.d.ts +11 -24
  107. package/dist/rules/lib/tiers.js +41 -1
  108. package/dist/rules/lib/tissue-entrypoint.d.ts +0 -1
  109. package/dist/rules/lib/tissue-entrypoint.js +4 -5
  110. package/dist/rules/lib/tsconfig-paths.d.ts +0 -4
  111. package/dist/rules/lib/tsconfig-paths.js +47 -15
  112. package/dist/rules/lib/type-members.d.ts +6 -0
  113. package/dist/rules/lib/type-members.js +165 -0
  114. package/dist/rules/lib/visitors.d.ts +0 -22
  115. package/dist/rules/lib/workspace-members.d.ts +2 -0
  116. package/dist/rules/lib/workspace-members.js +115 -0
  117. package/dist/rules/list-take-without-order.js +2 -8
  118. package/dist/rules/max-comment-density.d.ts +3 -18
  119. package/dist/rules/max-comment-density.js +3 -3
  120. package/dist/rules/molecule-atoms-only.d.ts +1 -0
  121. package/dist/rules/molecule-atoms-only.js +11 -7
  122. package/dist/rules/molecule-must-compose.d.ts +3 -1
  123. package/dist/rules/molecule-must-compose.js +17 -21
  124. package/dist/rules/mutating-step-declares-retry.d.ts +2 -7
  125. package/dist/rules/mutating-step-declares-retry.js +29 -11
  126. package/dist/rules/named-when-block.d.ts +0 -2
  127. package/dist/rules/named-when-block.js +2 -8
  128. package/dist/rules/next-route-segment-is-thin-delegate.d.ts +4 -19
  129. package/dist/rules/next-route-segment-is-thin-delegate.js +47 -71
  130. package/dist/rules/no-brand-names.d.ts +0 -9
  131. package/dist/rules/no-brand-names.js +1 -1
  132. package/dist/rules/no-card-shaped-div.d.ts +4 -42
  133. package/dist/rules/no-card-shaped-div.js +12 -4
  134. package/dist/rules/no-cross-feature-stores.d.ts +1 -0
  135. package/dist/rules/no-cross-feature-stores.js +9 -5
  136. package/dist/rules/no-d1-transaction.d.ts +2 -5
  137. package/dist/rules/no-d1-transaction.js +7 -0
  138. package/dist/rules/no-date-string-compare.d.ts +2 -0
  139. package/dist/rules/no-date-string-compare.js +14 -8
  140. package/dist/rules/no-duplicate-jsx-patterns.d.ts +2 -0
  141. package/dist/rules/no-duplicate-jsx-patterns.js +16 -1
  142. package/dist/rules/no-generic-utility-package.d.ts +48 -2
  143. package/dist/rules/no-generic-utility-package.js +48 -9
  144. package/dist/rules/no-hook-in-component-disguise.d.ts +27 -13
  145. package/dist/rules/no-hook-in-component-disguise.js +38 -30
  146. package/dist/rules/no-index-count-as-exact.d.ts +0 -3
  147. package/dist/rules/no-inert-hidden-jsx.d.ts +4 -21
  148. package/dist/rules/no-inert-hidden-jsx.js +13 -12
  149. package/dist/rules/no-inline-data-in-jsx.d.ts +2 -0
  150. package/dist/rules/no-inline-data-in-jsx.js +17 -2
  151. package/dist/rules/no-invalid-feature-folders.d.ts +2 -0
  152. package/dist/rules/no-invalid-feature-folders.js +12 -4
  153. package/dist/rules/no-loader-side-effect-in-index-file.d.ts +0 -7
  154. package/dist/rules/no-logic-in-component-files.d.ts +6 -5
  155. package/dist/rules/no-logic-in-component-files.js +52 -31
  156. package/dist/rules/no-mock-db-in-integration.d.ts +0 -15
  157. package/dist/rules/no-mock-db-in-integration.js +3 -4
  158. package/dist/rules/no-orm-outside-db.d.ts +1 -0
  159. package/dist/rules/no-orm-outside-db.js +9 -7
  160. package/dist/rules/no-raw-html-atoms.d.ts +3 -22
  161. package/dist/rules/no-raw-html-atoms.js +5 -17
  162. package/dist/rules/no-raw-sql-outside-allowed.d.ts +2 -0
  163. package/dist/rules/no-raw-sql-outside-allowed.js +10 -15
  164. package/dist/rules/no-react-namespace.d.ts +2 -0
  165. package/dist/rules/no-react-namespace.js +13 -0
  166. package/dist/rules/no-renamed-html-props.d.ts +2 -0
  167. package/dist/rules/no-renamed-html-props.js +11 -2
  168. package/dist/rules/no-render-prop-reader.d.ts +4 -20
  169. package/dist/rules/no-render-prop-reader.js +13 -5
  170. package/dist/rules/no-runtime-dead-affordance.d.ts +3 -26
  171. package/dist/rules/no-runtime-dead-affordance.js +7 -20
  172. package/dist/rules/no-swallowed-catch.js +2 -8
  173. package/dist/rules/no-trivial-wrapper-component.d.ts +4 -19
  174. package/dist/rules/no-trivial-wrapper-component.js +24 -33
  175. package/dist/rules/no-ts-in-bio-folders.d.ts +2 -13
  176. package/dist/rules/no-ts-in-bio-folders.js +9 -7
  177. package/dist/rules/no-type-definitions-in-components.d.ts +2 -0
  178. package/dist/rules/no-type-definitions-in-components.js +12 -1
  179. package/dist/rules/no-unregistered-plugin-dir.d.ts +0 -41
  180. package/dist/rules/no-unregistered-plugin-dir.js +1 -9
  181. package/dist/rules/no-void-port.d.ts +2 -4
  182. package/dist/rules/no-void-port.js +7 -0
  183. package/dist/rules/organelle-dependency.d.ts +1 -0
  184. package/dist/rules/organelle-dependency.js +10 -8
  185. package/dist/rules/organelle-single-source.d.ts +2 -0
  186. package/dist/rules/organelle-single-source.js +58 -9
  187. package/dist/rules/plugin-route-namespaced.d.ts +0 -11
  188. package/dist/rules/plugin-route-namespaced.js +2 -2
  189. package/dist/rules/queries-require-org-scope.d.ts +0 -3
  190. package/dist/rules/queries-require-org-scope.js +3 -10
  191. package/dist/rules/queue-loop-is-the-library.js +3 -2
  192. package/dist/rules/route-no-inline-mutation.d.ts +1 -25
  193. package/dist/rules/route-no-inline-mutation.js +2 -7
  194. package/dist/rules/router-schema-parity.d.ts +0 -9
  195. package/dist/rules/router-schema-parity.js +5 -9
  196. package/dist/rules/service-name-not-reserved.d.ts +0 -3
  197. package/dist/rules/service-name-not-reserved.js +1 -34
  198. package/dist/rules/ssot-no-inline-facts.js +2 -5
  199. package/dist/rules/ssot-no-process-env.js +4 -12
  200. package/dist/rules/step-declares-compensation-or-none.d.ts +2 -1
  201. package/dist/rules/step-declares-compensation-or-none.js +17 -1
  202. package/dist/rules/step-opens-its-own-cell.d.ts +2 -25
  203. package/dist/rules/step-opens-its-own-cell.js +8 -13
  204. package/dist/rules/store-route-scopes-tenant-data.js +8 -26
  205. package/dist/rules/subpath-over-barrel.d.ts +2 -10
  206. package/dist/rules/subpath-over-barrel.js +48 -9
  207. package/dist/rules/subscriber-declares-id.d.ts +0 -2
  208. package/dist/rules/subscriber-declares-id.js +2 -8
  209. package/dist/rules/tables-declare-their-plane.d.ts +0 -2
  210. package/dist/rules/tables-declare-their-plane.js +4 -10
  211. package/dist/rules/tenant-tables-carry-org-id.d.ts +1 -4
  212. package/dist/rules/tenant-tables-carry-org-id.js +5 -12
  213. package/dist/rules/test-no-clock-polling-for-promise-flips.d.ts +2 -0
  214. package/dist/rules/test-no-clock-polling-for-promise-flips.js +18 -1
  215. package/dist/rules/time-through-the-door.d.ts +0 -24
  216. package/dist/rules/time-through-the-door.js +5 -5
  217. package/dist/rules/tissue-must-compose.d.ts +4 -2
  218. package/dist/rules/tissue-must-compose.js +25 -20
  219. package/dist/rules/tissue-no-data-props.d.ts +56 -0
  220. package/dist/rules/tissue-no-data-props.js +114 -32
  221. package/dist/rules/tissue-no-fetch.d.ts +26 -0
  222. package/dist/rules/tissue-no-fetch.js +57 -0
  223. package/dist/rules/tissue-no-hooks.d.ts +20 -22
  224. package/dist/rules/tissue-no-hooks.js +22 -4
  225. package/dist/rules/tissue-no-organelles.d.ts +1 -0
  226. package/dist/rules/tissue-no-organelles.js +9 -1
  227. package/dist/rules/tissue-no-stores.d.ts +1 -0
  228. package/dist/rules/tissue-no-stores.js +9 -1
  229. package/dist/rules/todo-names-its-plan.d.ts +1 -28
  230. package/dist/rules/todo-names-its-plan.js +2 -2
  231. package/dist/rules/tokens-only-colors.d.ts +2 -37
  232. package/dist/rules/tokens-only-colors.js +33 -18
  233. package/dist/rules/unique-query-step-name.d.ts +0 -7
  234. package/dist/rules/unique-query-step-name.js +6 -10
  235. package/dist/rules/worker-handles-are-scoped.d.ts +11 -8
  236. package/dist/rules/worker-handles-are-scoped.js +12 -5
  237. package/dist/rules/zustand-v5-best-practices.d.ts +8 -18
  238. package/dist/rules/zustand-v5-best-practices.js +32 -56
  239. package/package.json +2 -2
@@ -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
+ };
@@ -0,0 +1,53 @@
1
+ import type { RuleContextWithRawOptions } from './lib/context.js';
2
+ import { type Probe } from './lib/probe.js';
3
+ type Name = {
4
+ name?: string;
5
+ object?: Name;
6
+ property?: Name;
7
+ type: string;
8
+ };
9
+ type Attribute = {
10
+ name?: Name;
11
+ type: string;
12
+ };
13
+ type Child = {
14
+ children?: Child[];
15
+ openingElement?: {
16
+ attributes?: Attribute[];
17
+ name: Name;
18
+ };
19
+ type: string;
20
+ value?: string;
21
+ };
22
+ declare const iconControlHasName: {
23
+ create(context: RuleContextWithRawOptions): {
24
+ JSXElement(node: Child): void;
25
+ };
26
+ fixShape: string;
27
+ meta: {
28
+ docs: {
29
+ description: string;
30
+ };
31
+ messages: {
32
+ unnamed: string;
33
+ };
34
+ schema: {
35
+ additionalProperties: boolean;
36
+ properties: {
37
+ controls: {
38
+ items: {
39
+ type: 'string';
40
+ };
41
+ type: 'array';
42
+ };
43
+ icons: {
44
+ type: 'string';
45
+ };
46
+ };
47
+ type: 'object';
48
+ }[];
49
+ type: 'problem';
50
+ };
51
+ probe(options: readonly unknown[]): Probe;
52
+ };
53
+ export default iconControlHasName;
@@ -0,0 +1,71 @@
1
+ // src/rules/icon-control-has-name.ts
2
+ import { bagOf, firstOf } from "./lib/probe.js";
3
+ import { optionsBag, STRING, STRINGS } from "./lib/schema.js";
4
+ var NATIVE_CONTROLS = ["button", "a"];
5
+ var ICONS = "Icon$";
6
+ var NAMED_BY = new Set(["aria-label", "aria-labelledby", "title"]);
7
+ var ICON_NAMED_BY = new Set(["aria-label", "aria-labelledby"]);
8
+ var nameOf = (node) => node === undefined ? "" : node.type === "JSXMemberExpression" ? `${nameOf(node.object)}.${nameOf(node.property)}` : node.name ?? "";
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"));
11
+ var iconControlHasName = {
12
+ create(context) {
13
+ const options = bagOf(context.options);
14
+ const controls = new Set([...NATIVE_CONTROLS, ...options.controls ?? []]);
15
+ const icon = new RegExp(options.icons ?? ICONS);
16
+ return {
17
+ JSXElement(node) {
18
+ const control = nameOf(node.openingElement?.name);
19
+ if (!controls.has(control))
20
+ return;
21
+ const attributes = node.openingElement?.attributes ?? [];
22
+ if (attributes.some((one) => one.type !== "JSXAttribute" || NAMED_BY.has(nameOf(one.name)))) {
23
+ return;
24
+ }
25
+ const shown = (node.children ?? []).filter(said);
26
+ if (shown.some(namesItself))
27
+ return;
28
+ const icons = shown.map((one) => one.type === "JSXElement" ? nameOf(one.openingElement?.name) : "");
29
+ if (icons.length === 0 || !icons.every((name) => name === "svg" || icon.test(name)))
30
+ return;
31
+ context.report({
32
+ data: { control, icons: icons.join(", ") },
33
+ messageId: "unnamed",
34
+ node
35
+ });
36
+ }
37
+ };
38
+ },
39
+ fixShape: `A control whose only content is an icon has no name: the icon is decorative, so a screen reader
40
+ announces "button" and nothing else. Give it \`aria-label\` (or \`aria-labelledby\`, or \`title\`) in the
41
+ app's own words — a label prop the caller passes, never a string the component invents — or put text
42
+ beside the icon, visible or \`sr-only\`. \`<button>\` and \`<a>\` are read by default; \`controls\` names the
43
+ repo's control atoms, and \`icons\` is the pattern an icon component's name matches (\`Icon$\` by default);
44
+ an inline \`<svg>\` is always one.
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.`,
48
+ meta: {
49
+ docs: {
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."
51
+ },
52
+ messages: {
53
+ unnamed: "<{{control}}> shows only {{icons}} and has no name a screen reader can read. Give it aria-label in the words the app passes in, or text beside the icon."
54
+ },
55
+ schema: optionsBag({ controls: STRINGS, icons: STRING }),
56
+ type: "problem"
57
+ },
58
+ probe(options) {
59
+ const bag = bagOf(options);
60
+ const control = firstOf(bag.controls, NATIVE_CONTROLS);
61
+ return {
62
+ path: "molecules/geonosis-probe.tsx",
63
+ source: `export const Probe = () => <${control}><svg /></${control}>
64
+ `
65
+ };
66
+ }
67
+ };
68
+ var icon_control_has_name_default = iconControlHasName;
69
+ export {
70
+ icon_control_has_name_default as default
71
+ };
@@ -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;
@@ -1,257 +1,24 @@
1
1
  // src/rules/lib/colors.ts
2
- var FUNCTIONS = [
3
- "color",
4
- "color-mix",
5
- "hsl",
6
- "hsla",
7
- "hwb",
8
- "lab",
9
- "lch",
10
- "oklab",
11
- "oklch",
12
- "rgb",
13
- "rgba"
14
- ];
2
+ var words = (list) => new Set(list.split(" "));
15
3
  var HEX = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
16
- var FUNCTION_CALL = new RegExp(`\\b(?:${FUNCTIONS.join("|")})\\(`, "i");
4
+ var FUNCTION_NAMES = "color|color-mix|hsl|hsla|hwb|lab|lch|oklab|oklch|rgb|rgba";
5
+ var FUNCTION_CALL = new RegExp(`\\b(?:${FUNCTION_NAMES})\\(`, "i");
6
+ var FUNCTION_START = new RegExp(`^(?:${FUNCTION_NAMES})\\(`, "i");
17
7
  var HEX_ANYWHERE = /(?:^|[\s(,])#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i;
18
- var NAMED = new Set([
19
- "aliceblue",
20
- "antiquewhite",
21
- "aqua",
22
- "aquamarine",
23
- "azure",
24
- "beige",
25
- "bisque",
26
- "black",
27
- "blanchedalmond",
28
- "blue",
29
- "blueviolet",
30
- "brown",
31
- "burlywood",
32
- "cadetblue",
33
- "chartreuse",
34
- "chocolate",
35
- "coral",
36
- "cornflowerblue",
37
- "cornsilk",
38
- "crimson",
39
- "cyan",
40
- "darkblue",
41
- "darkcyan",
42
- "darkgoldenrod",
43
- "darkgray",
44
- "darkgreen",
45
- "darkgrey",
46
- "darkkhaki",
47
- "darkmagenta",
48
- "darkolivegreen",
49
- "darkorange",
50
- "darkorchid",
51
- "darkred",
52
- "darksalmon",
53
- "darkseagreen",
54
- "darkslateblue",
55
- "darkslategray",
56
- "darkslategrey",
57
- "darkturquoise",
58
- "darkviolet",
59
- "deeppink",
60
- "deepskyblue",
61
- "dimgray",
62
- "dimgrey",
63
- "dodgerblue",
64
- "firebrick",
65
- "floralwhite",
66
- "forestgreen",
67
- "fuchsia",
68
- "gainsboro",
69
- "ghostwhite",
70
- "gold",
71
- "goldenrod",
72
- "gray",
73
- "green",
74
- "greenyellow",
75
- "grey",
76
- "honeydew",
77
- "hotpink",
78
- "indianred",
79
- "indigo",
80
- "ivory",
81
- "khaki",
82
- "lavender",
83
- "lavenderblush",
84
- "lawngreen",
85
- "lemonchiffon",
86
- "lightblue",
87
- "lightcoral",
88
- "lightcyan",
89
- "lightgoldenrodyellow",
90
- "lightgray",
91
- "lightgreen",
92
- "lightgrey",
93
- "lightpink",
94
- "lightsalmon",
95
- "lightseagreen",
96
- "lightskyblue",
97
- "lightslategray",
98
- "lightslategrey",
99
- "lightsteelblue",
100
- "lightyellow",
101
- "lime",
102
- "limegreen",
103
- "linen",
104
- "magenta",
105
- "maroon",
106
- "mediumaquamarine",
107
- "mediumblue",
108
- "mediumorchid",
109
- "mediumpurple",
110
- "mediumseagreen",
111
- "mediumslateblue",
112
- "mediumspringgreen",
113
- "mediumturquoise",
114
- "mediumvioletred",
115
- "midnightblue",
116
- "mintcream",
117
- "mistyrose",
118
- "moccasin",
119
- "navajowhite",
120
- "navy",
121
- "oldlace",
122
- "olive",
123
- "olivedrab",
124
- "orange",
125
- "orangered",
126
- "orchid",
127
- "palegoldenrod",
128
- "palegreen",
129
- "paleturquoise",
130
- "palevioletred",
131
- "papayawhip",
132
- "peachpuff",
133
- "peru",
134
- "pink",
135
- "plum",
136
- "powderblue",
137
- "purple",
138
- "rebeccapurple",
139
- "red",
140
- "rosybrown",
141
- "royalblue",
142
- "saddlebrown",
143
- "salmon",
144
- "sandybrown",
145
- "seagreen",
146
- "seashell",
147
- "sienna",
148
- "silver",
149
- "skyblue",
150
- "slateblue",
151
- "slategray",
152
- "slategrey",
153
- "snow",
154
- "springgreen",
155
- "steelblue",
156
- "tan",
157
- "teal",
158
- "thistle",
159
- "tomato",
160
- "turquoise",
161
- "violet",
162
- "wheat",
163
- "white",
164
- "whitesmoke",
165
- "yellow",
166
- "yellowgreen"
167
- ]);
8
+ var NAMED = words("aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen");
168
9
  var isColorLiteral = (value) => {
169
10
  const trimmed = value.trim();
170
11
  if (trimmed === "")
171
12
  return false;
172
- if (HEX.test(trimmed))
173
- return true;
174
- if (NAMED.has(trimmed.toLowerCase()))
175
- return true;
176
- return new RegExp(`^(?:${FUNCTIONS.join("|")})\\(`, "i").test(trimmed);
13
+ return HEX.test(trimmed) || NAMED.has(trimmed.toLowerCase()) || FUNCTION_START.test(trimmed);
177
14
  };
178
15
  var containsColorLiteral = (value) => HEX_ANYWHERE.test(value) || FUNCTION_CALL.test(value);
179
- var COLOR_PROPERTIES = new Set([
180
- "accentColor",
181
- "backgroundColor",
182
- "borderBlockColor",
183
- "borderBlockEndColor",
184
- "borderBlockStartColor",
185
- "borderBottomColor",
186
- "borderColor",
187
- "borderInlineColor",
188
- "borderInlineEndColor",
189
- "borderInlineStartColor",
190
- "borderLeftColor",
191
- "borderRightColor",
192
- "borderTopColor",
193
- "caretColor",
194
- "color",
195
- "columnRuleColor",
196
- "fill",
197
- "floodColor",
198
- "lightingColor",
199
- "outlineColor",
200
- "stopColor",
201
- "stroke",
202
- "textDecorationColor",
203
- "textEmphasisColor",
204
- "WebkitTextFillColor",
205
- "WebkitTextStrokeColor",
206
- "webkitTextFillColor",
207
- "webkitTextStrokeColor"
208
- ]);
209
- var COLOR_SHORTHANDS = new Set([
210
- "background",
211
- "border",
212
- "borderBottom",
213
- "borderLeft",
214
- "borderRight",
215
- "borderTop",
216
- "boxShadow",
217
- "columnRule",
218
- "filter",
219
- "outline",
220
- "textDecoration",
221
- "textShadow"
222
- ]);
16
+ var COLOR_PROPERTIES = words("accentColor backgroundColor borderBlockColor borderBlockEndColor borderBlockStartColor borderBottomColor borderColor borderInlineColor borderInlineEndColor borderInlineStartColor borderLeftColor borderRightColor borderTopColor caretColor color columnRuleColor fill floodColor lightingColor outlineColor stopColor stroke textDecorationColor textEmphasisColor WebkitTextFillColor WebkitTextStrokeColor webkitTextFillColor webkitTextStrokeColor");
17
+ var COLOR_SHORTHANDS = words("background border borderBottom borderLeft borderRight borderTop boxShadow columnRule filter outline textDecoration textShadow");
223
18
  var isColorProperty = (property) => COLOR_PROPERTIES.has(property);
224
19
  var isColorShorthand = (property) => COLOR_SHORTHANDS.has(property);
225
- var COLOR_UTILITIES = new Set([
226
- "accent",
227
- "bg",
228
- "border",
229
- "border-b",
230
- "border-e",
231
- "border-l",
232
- "border-r",
233
- "border-s",
234
- "border-t",
235
- "border-x",
236
- "border-y",
237
- "caret",
238
- "decoration",
239
- "divide",
240
- "fill",
241
- "from",
242
- "inset-ring",
243
- "inset-shadow",
244
- "outline",
245
- "placeholder",
246
- "ring",
247
- "ring-offset",
248
- "shadow",
249
- "stroke",
250
- "text",
251
- "to",
252
- "via"
253
- ]);
254
- var isColorUtility = (utility) => COLOR_UTILITIES.has(utility.replace(/^.*:/, ""));
20
+ var COLOR_UTILITIES = words("accent bg border border-b border-e border-l border-r border-s border-t border-x border-y caret decoration divide fill from inset-ring inset-shadow outline placeholder ring ring-offset shadow stroke text to via");
21
+ var isColorUtility = (utility) => COLOR_UTILITIES.has(utility);
255
22
  export {
256
23
  containsColorLiteral,
257
24
  isColorLiteral,
@@ -0,0 +1,19 @@
1
+ export type Comment = {
2
+ loc: {
3
+ end: {
4
+ line: number;
5
+ };
6
+ start: {
7
+ line: number;
8
+ };
9
+ };
10
+ type: string;
11
+ value: string;
12
+ };
13
+ export type SourceCode = {
14
+ ast?: {
15
+ comments?: Comment[];
16
+ };
17
+ getAllComments?: () => Comment[];
18
+ };
19
+ export declare const commentsOf: (sourceCode: SourceCode | undefined) => Comment[];
@@ -0,0 +1,5 @@
1
+ // src/rules/lib/comments.ts
2
+ var commentsOf = (sourceCode) => sourceCode?.getAllComments?.() ?? sourceCode?.ast?.comments ?? [];
3
+ export {
4
+ commentsOf
5
+ };