@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
@@ -1,5 +1,7 @@
1
1
  // src/rules/tissue-no-hooks.ts
2
- import { hookCalledBy } from "./lib/hooks.js";
2
+ import { hookCalledBy, hookNameOf } from "./lib/hooks.js";
3
+ import { sourceOf } from "./lib/imports.js";
4
+ import { optionsBag, STRINGS } from "./lib/schema.js";
3
5
  import { tissueEntrypointOf } from "./lib/tissue-entrypoint.js";
4
6
  var FORBIDDEN_HOOKS = new Set([
5
7
  "useCallback",
@@ -16,11 +18,14 @@ var tissueNoHooks = {
16
18
  const { basename: shortName, isEntrypoint } = tissueEntrypointOf(context.filename);
17
19
  if (!isEntrypoint)
18
20
  return {};
21
+ const reads = new Set((context.options?.[0]?.contextHooks ?? []).filter((one) => !FORBIDDEN_HOOKS.has(one)));
19
22
  return {
20
23
  CallExpression(node) {
21
24
  const name = hookCalledBy(node);
22
25
  if (name === null)
23
26
  return;
27
+ if (reads.has(hookNameOf(node) ?? ""))
28
+ return;
24
29
  context.report({
25
30
  data: { file: shortName, hook: name },
26
31
  messageId: "called",
@@ -30,7 +35,7 @@ var tissueNoHooks = {
30
35
  ImportDeclaration(node) {
31
36
  if (node.importKind === "type")
32
37
  return;
33
- if (node.source.value !== "react")
38
+ if (sourceOf(node) !== "react")
34
39
  return;
35
40
  const specifiers = node.specifiers ?? [];
36
41
  for (const spec of specifiers) {
@@ -52,7 +57,9 @@ var tissueNoHooks = {
52
57
  },
53
58
  fixShape: `A tissue holds no state: no \`useState\`, no \`useEffect\`, and no call to ANY \`use*()\` — including a
54
59
  hook defined in \`lib/\` that reads a store behind your back. If you need the hook, the file is a cell:
55
- move it to \`cells/\`, or push the call into a cell the tissue arranges. Private siblings
60
+ move it to \`cells/\`, or push the call into a cell the tissue arranges. A hook that only reads the
61
+ framework's context (\`useTranslations\`, \`usePathname\`) is named in \`contextHooks\`, a list that is
62
+ empty until the repo writes it and never excuses \`useState\` and its kin. Private siblings
56
63
  (\`.parts.tsx\`, \`.sections.tsx\`) are not the tissue entrypoint and may hold local UI state.`,
57
64
  meta: {
58
65
  docs: {
@@ -62,8 +69,19 @@ move it to \`cells/\`, or push the call into a cell the tissue arranges. Private
62
69
  called: 'Tissue "{{file}}" calls "{{hook}}". A hook call is state, wherever the hook is defined — a rule that only reads imports never sees the state a `lib/` hook reaches through. Move this file to cells/, or push the call into a cell it arranges.',
63
70
  forbidden: 'Tissue "{{file}}" imports "{{hook}}" from react. Tissues are pure arrangements of cells and cannot hold state. State lives only at organelle/cell levels. Either push this logic into a cell, or if this file is truly doing cell work, move it to the cells/ tier.'
64
71
  },
65
- schema: [],
72
+ schema: optionsBag({ contextHooks: STRINGS }),
66
73
  type: "problem"
74
+ },
75
+ probe() {
76
+ return {
77
+ path: "src/tissues/geonosis-probe.tsx",
78
+ source: `declare const useGeonosisThing: () => number
79
+
80
+ export function GeonosisProbe() {
81
+ return <div>{useGeonosisThing()}</div>
82
+ }
83
+ `
84
+ };
67
85
  }
68
86
  };
69
87
  var tissue_no_hooks_default = tissueNoHooks;
@@ -9,6 +9,7 @@ declare const tissueNoOrganelles: {
9
9
  }) => void;
10
10
  }): import('./lib/visitors.js').Visitor;
11
11
  fixShape: string;
12
+ probe?: ((options: readonly unknown[]) => import('./lib/probe.js').Probe | import('./lib/probe.js').ProbeTree) | undefined;
12
13
  meta: {
13
14
  docs: {
14
15
  description: string;
@@ -1,14 +1,22 @@
1
1
  // src/rules/tissue-no-organelles.ts
2
2
  import { directionRule } from "./lib/direction-rule.js";
3
+ import { inTier } from "./lib/tiers.js";
3
4
  var tissueNoOrganelles = directionRule({
4
5
  description: "Tissues cannot directly import organelles. In biology, tissues arrange cells; organelles live INSIDE cells. To reach an organelle from a tissue, go through a cell. This forces tissues to stay at the cell-arrangement level and keeps state encapsulated in the cells that own their organelles.",
5
6
  fixShape: `A tissue never imports an organelle. Organelles live INSIDE cells; go through a cell that hosts the
6
7
  organelle and let the tissue arrange the cell. Reaching past the cell is how state gets into a layer
7
8
  that is supposed to have none.`,
8
- forbidden: (target) => /\/organelles\//.test(target.path),
9
+ forbidden: (target) => inTier(target.path, "organelles"),
9
10
  messages: {
10
11
  forbidden: 'Tissue "{{file}}" cannot import organelle "{{source}}". Tissues arrange cells; organelles live inside cells. Host this organelle inside a cell, then compose the cell from this tissue.'
11
12
  },
13
+ probe: () => ({
14
+ path: "src/tissues/geonosis-probe.tsx",
15
+ source: `import { up } from '../organelles/geonosis-up'
16
+
17
+ export const GeonosisProbe = up
18
+ `
19
+ }),
12
20
  scope: "tissues"
13
21
  });
14
22
  var tissue_no_organelles_default = tissueNoOrganelles;
@@ -9,6 +9,7 @@ declare const tissueNoStores: {
9
9
  }) => void;
10
10
  }): import('./lib/visitors.js').Visitor;
11
11
  fixShape: string;
12
+ probe?: ((options: readonly unknown[]) => import('./lib/probe.js').Probe | import('./lib/probe.js').ProbeTree) | undefined;
12
13
  meta: {
13
14
  docs: {
14
15
  description: string;
@@ -1,14 +1,22 @@
1
1
  // src/rules/tissue-no-stores.ts
2
2
  import { directionRule } from "./lib/direction-rule.js";
3
+ import { STORES_FOLDER } from "./lib/tiers.js";
3
4
  var tissueNoStores = directionRule({
4
5
  description: "Tissues cannot import from stores. Tissues arrange cells but must not directly access state. State lives at organelle and cell levels.",
5
6
  fixShape: `A tissue reads no store. State lives at the organelle and cell tiers only — a tissue that needs store
6
7
  data is a cell wearing the wrong folder. Move it to \`cells/\`, or have a cell read the store and let
7
8
  the tissue arrange that cell.`,
8
- forbidden: (target) => /(?:^|\/)stores\//.test(target.path),
9
+ forbidden: (target) => STORES_FOLDER.test(target.path),
9
10
  messages: {
10
11
  forbidden: 'Tissue "{{file}}" cannot import from "{{source}}". Tissues arrange cells but must not directly access stores. Push the store read into a constituent cell.'
11
12
  },
13
+ probe: () => ({
14
+ path: "src/tissues/geonosis-probe.tsx",
15
+ source: `import { up } from '../stores/geonosis-up'
16
+
17
+ export const GeonosisProbe = up
18
+ `
19
+ }),
12
20
  scope: "tissues"
13
21
  });
14
22
  var tissue_no_stores_default = tissueNoStores;
@@ -1,31 +1,9 @@
1
+ import { type SourceCode } from './lib/comments.js';
1
2
  import { type Probe } from './lib/probe.js';
2
3
  interface Options {
3
- /** What a plan id looks like, as a regex source. `\d{3,}` is the NNN a plan file is named for. */
4
4
  id?: string;
5
- /**
6
- * The words this repo treats as debt markers. Matched case-sensitively, on a word boundary and
7
- * followed by `(`, `:`, whitespace or the end of the comment — the shapes a marker is written in.
8
- */
9
5
  markers?: string[];
10
6
  }
11
- type Comment = {
12
- loc: {
13
- end: {
14
- line: number;
15
- };
16
- start: {
17
- line: number;
18
- };
19
- };
20
- type: string;
21
- value: string;
22
- };
23
- type SourceCode = {
24
- ast?: {
25
- comments?: Comment[];
26
- };
27
- getAllComments?: () => Comment[];
28
- };
29
7
  declare const todoNamesItsPlan: {
30
8
  create(context: {
31
9
  filename: string;
@@ -65,11 +43,6 @@ declare const todoNamesItsPlan: {
65
43
  }[];
66
44
  type: 'problem';
67
45
  };
68
- /**
69
- * The repo's OWN first marker, in a comment, citing nothing. It has to be the configured word:
70
- * a probe hardcoding one spelling would report the rule as unreachable in every repo that spells
71
- * its debt with another, which is the whole class of finding probes exist to end.
72
- */
73
46
  probe(options: readonly unknown[]): Probe;
74
47
  };
75
48
  export default todoNamesItsPlan;
@@ -1,10 +1,10 @@
1
1
  // src/rules/todo-names-its-plan.ts
2
+ import { commentsOf } from "./lib/comments.js";
2
3
  import { requireOption } from "./lib/options.js";
4
+ import { escapeForRegex } from "./lib/pattern-match.js";
3
5
  import { bagOf, firstOf } from "./lib/probe.js";
4
6
  import { optionsBag, STRING, STRINGS } from "./lib/schema.js";
5
7
  var ID_DEFAULT = String.raw`\d{3,}`;
6
- var commentsOf = (sourceCode) => sourceCode?.getAllComments?.() ?? sourceCode?.ast?.comments ?? [];
7
- var escapeForRegex = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
8
  var todoNamesItsPlan = {
9
9
  create(context) {
10
10
  const { id = ID_DEFAULT, markers = [] } = context.options?.[0] ?? {};
@@ -3,52 +3,22 @@ import type { AnyNode } from './lib/nodes.js';
3
3
  import { type Probe } from './lib/probe.js';
4
4
  import { type OpeningElement } from './lib/styling.js';
5
5
  interface Options {
6
- /** Paths where a literal is the point — the theme file, the palette, the token layer. */
7
6
  allow?: string[];
8
- /**
9
- * The modules this rule does not judge, each written out in full — the enumeration a pattern
10
- * cannot be. A glob grants its exemption to wherever it stops and nobody counts what accumulates
11
- * on the far side; a list of module names is a number a reader can hold.
12
- */
13
7
  exemptModules?: string[];
14
- /** The prefixes a themed custom property starts with. One string or several. */
15
8
  tokenPrefix?: string | string[];
16
9
  }
17
10
  declare const tokensOnlyColors: {
18
11
  create(context: RuleContextWithOptions<Options>): {
19
12
  JSXOpeningElement?: undefined;
20
- /**
21
- * A colour that arrives as a DEFAULT — `({ color = '#090909' })` — is never written in a JSX
22
- * attribute, so the visitor above cannot see it. It reaches the element through a prop and
23
- * paints in the brand it was typed for, from the one place nobody looked: seventy-two icons
24
- * in one kit carried the same hex this way, under this rule, at "error", for its whole life.
25
- * A default is a value the component CHOOSES, which is exactly what a token is for.
26
- *
27
- * A bare word is left alone. `variant = 'primary'` and `size = 'small'` are defaults too, and
28
- * a rule that read every English word as a colour would be turned off within the week.
29
- */
13
+ Property?: undefined;
30
14
  AssignmentPattern?: undefined;
31
15
  } | {
32
16
  JSXOpeningElement(node: OpeningElement): void;
33
- /**
34
- * A colour that arrives as a DEFAULT — `({ color = '#090909' })` — is never written in a JSX
35
- * attribute, so the visitor above cannot see it. It reaches the element through a prop and
36
- * paints in the brand it was typed for, from the one place nobody looked: seventy-two icons
37
- * in one kit carried the same hex this way, under this rule, at "error", for its whole life.
38
- * A default is a value the component CHOOSES, which is exactly what a token is for.
39
- *
40
- * A bare word is left alone. `variant = 'primary'` and `size = 'small'` are defaults too, and
41
- * a rule that read every English word as a colour would be turned off within the week.
42
- */
17
+ Property(node: AnyNode): void;
43
18
  AssignmentPattern(node: AnyNode): void;
44
19
  };
45
20
  fixShape: string;
46
21
  meta: {
47
- /**
48
- * #139: which option grants by PATTERN and which one ENUMERATES. Declared on the rule so the
49
- * doctor needs no list of its own — a rule that gains a path grant and forgets to say so is a
50
- * grant nothing counts, which is the defect this pair exists to end.
51
- */
52
22
  grants: {
53
23
  modules: string;
54
24
  paths: string;
@@ -90,11 +60,6 @@ declare const tokensOnlyColors: {
90
60
  }[];
91
61
  type: 'problem';
92
62
  };
93
- /**
94
- * A named colour in an inline style, which fires under any token prefix — the question is whether
95
- * the rule RUNS in this repo, and a probe spelled out of the prefix would be defeated by a repo
96
- * that happened to declare a token of the same name.
97
- */
98
63
  probe(): Probe;
99
64
  };
100
65
  export default tokensOnlyColors;
@@ -6,20 +6,23 @@ import {
6
6
  isColorShorthand,
7
7
  isColorUtility
8
8
  } from "./lib/colors.js";
9
- import { requireOption } from "./lib/options.js";
10
- import { isModule, normalise } from "./lib/paths.js";
9
+ import { DEFAULT_TEST_EXEMPT, requireOption } from "./lib/options.js";
10
+ import { anyOf, isExempt, normalise } from "./lib/paths.js";
11
11
  import { optionsBag, STRINGS } from "./lib/schema.js";
12
12
  import {
13
13
  arbitraryValuesOf,
14
14
  classNameOf,
15
+ keyOf,
15
16
  localVariablesOf,
16
17
  styleEntriesOf,
18
+ styleObjectOf,
17
19
  variableOf
18
20
  } from "./lib/styling.js";
19
21
  var defaultedName = (node) => {
20
22
  const left = node.left;
21
23
  return left?.type === "Identifier" ? left.name ?? "default" : "default";
22
24
  };
25
+ var isNamedOnly = (value) => /^[a-z]+$/i.test(value.trim());
23
26
  var tokenOffence = (value, prefixes, local) => {
24
27
  const variable = variableOf(value);
25
28
  if (variable !== undefined) {
@@ -31,16 +34,27 @@ var tokenOffence = (value, prefixes, local) => {
31
34
  };
32
35
  var tokensOnlyColors = {
33
36
  create(context) {
34
- const { allow = [], exemptModules = [], tokenPrefix } = context.options?.[0] ?? {};
37
+ const { tokenPrefix, ...exempt } = context.options?.[0] ?? {};
35
38
  const prefixes = [requireOption("tokens-only-colors", tokenPrefix, "tokenPrefix")].flat();
36
- requireOption("tokens-only-colors", prefixes, "tokenPrefix");
37
39
  const filename = normalise(context.filename);
38
- if (exemptModules.some((module) => isModule(filename, module)))
39
- return {};
40
- if (allow.some((path) => filename.includes(path)))
40
+ if (isExempt(filename, exempt))
41
41
  return {};
42
+ const readByAnElement = new WeakSet;
43
+ const reportDefault = (node, property, value) => {
44
+ const offence = tokenOffence(value, prefixes, new Set);
45
+ if (offence === undefined || offence === "colorLiteral" && isNamedOnly(value))
46
+ return;
47
+ if (offence === "foreignVariable" && !isColorProperty(property))
48
+ return;
49
+ context.report({ data: { property, value }, messageId: offence, node });
50
+ };
51
+ const isTest = anyOf([...DEFAULT_TEST_EXEMPT, "__fixtures__/", "__visual__/"], filename);
42
52
  return {
43
53
  JSXOpeningElement(node) {
54
+ const styled = styleObjectOf(node);
55
+ for (const entry of styled?.properties ?? []) {
56
+ readByAnElement.add(entry);
57
+ }
44
58
  const report = (messageId, data) => context.report({ data, messageId, node });
45
59
  const local = localVariablesOf(node);
46
60
  for (const { property, value } of styleEntriesOf(node)) {
@@ -68,20 +82,22 @@ var tokensOnlyColors = {
68
82
  }
69
83
  }
70
84
  },
85
+ Property(node) {
86
+ if (isTest || readByAnElement.has(node))
87
+ return;
88
+ const value = node.value;
89
+ if (value?.type !== "Literal" || typeof value.value !== "string")
90
+ return;
91
+ const name = keyOf(node);
92
+ if (name === undefined)
93
+ return;
94
+ reportDefault(node, name, value.value);
95
+ },
71
96
  AssignmentPattern(node) {
72
97
  const right = node.right;
73
98
  if (right?.type !== "Literal" || typeof right.value !== "string")
74
99
  return;
75
- const offence = tokenOffence(right.value, prefixes, new Set);
76
- if (offence === undefined || offence === "colorLiteral" && isNamedOnly(right.value))
77
- return;
78
- if (offence === "foreignVariable" && !isColorProperty(defaultedName(node)))
79
- return;
80
- context.report({
81
- data: { property: defaultedName(node), value: right.value },
82
- messageId: offence,
83
- node
84
- });
100
+ reportDefault(node, defaultedName(node), right.value);
85
101
  }
86
102
  };
87
103
  },
@@ -122,7 +138,6 @@ exactly like a clean tree.`,
122
138
  };
123
139
  }
124
140
  };
125
- var isNamedOnly = (value) => /^[a-z]+$/i.test(value.trim());
126
141
  var tokens_only_colors_default = tokensOnlyColors;
127
142
  export {
128
143
  tokens_only_colors_default as default
@@ -2,9 +2,7 @@ import { type RuleContextWithOptions } from './lib/context.js';
2
2
  import { type AnyNode } from './lib/nodes.js';
3
3
  import { type Probe } from './lib/probe.js';
4
4
  interface Options {
5
- /** The call names that read through the query graph. No default: see the note above. */
6
5
  queryStepCalls?: string[];
7
- /** The call names that define a workflow. Default: Medusa's `createWorkflow`. */
8
6
  workflowFactories?: string[];
9
7
  }
10
8
  declare const uniqueQueryStepName: {
@@ -40,11 +38,6 @@ declare const uniqueQueryStepName: {
40
38
  }[];
41
39
  type: 'problem';
42
40
  };
43
- /**
44
- * Two query steps in one body, one named and one not — which is one finding rather than two. A
45
- * body with a single un-named step collides with nothing and is deliberately exempt, so a probe
46
- * built from one would fire nothing and read as a rule that cannot be exercised.
47
- */
48
41
  probe(options: readonly unknown[]): Probe;
49
42
  };
50
43
  export default uniqueQueryStepName;
@@ -1,19 +1,15 @@
1
1
  // src/rules/unique-query-step-name.ts
2
- import { calleeNameOf, walkNode } from "./lib/nodes.js";
2
+ import { walkNode } from "./lib/nodes.js";
3
3
  import { propertiesOf, stringOf, valueOf } from "./lib/objects.js";
4
4
  import { requireOption } from "./lib/options.js";
5
5
  import { bagOf, firstOf } from "./lib/probe.js";
6
6
  import { optionsBag, STRINGS } from "./lib/schema.js";
7
+ import { DEFAULT_WORKFLOW_FACTORIES, isStepCall, workflowFactoriesOf } from "./lib/steps.js";
7
8
  var RULE = "unique-query-step-name";
8
9
  var ACCEPTS = "{ queryStepCalls: string[] (required), workflowFactories: string[] }";
9
- var DEFAULT_WORKFLOW_FACTORIES = ["createWorkflow"];
10
10
  var CONFIG = "config";
11
11
  var NAME = "name";
12
12
  var COLLIDES_FROM = 2;
13
- var isCallOf = (node, names) => {
14
- const callee = calleeNameOf(node);
15
- return callee !== null && names.has(callee);
16
- };
17
13
  var ABSENT = { kind: "absent" };
18
14
  var UNREADABLE = { kind: "unreadable" };
19
15
  var chainedName = (node, steps) => {
@@ -26,7 +22,7 @@ var chainedName = (node, steps) => {
26
22
  if (property?.type !== "Identifier" || property.name !== CONFIG)
27
23
  return null;
28
24
  const inner = callee.object;
29
- if (inner === undefined || !isCallOf(inner, steps))
25
+ if (inner === undefined || !isStepCall(inner, steps))
30
26
  return null;
31
27
  const [first] = node.arguments ?? [];
32
28
  const properties = propertiesOf(first);
@@ -47,18 +43,18 @@ var stepsIn = (body, calls) => {
47
43
  });
48
44
  const steps = [];
49
45
  walkNode(body, (node) => {
50
- if (isCallOf(node, calls))
46
+ if (isStepCall(node, calls))
51
47
  steps.push({ name: names.get(node) ?? ABSENT, node });
52
48
  });
53
49
  return steps;
54
50
  };
55
51
  var uniqueQueryStepName = {
56
52
  create(context) {
57
- const factories = new Set(requireOption(RULE, context.options?.[0]?.workflowFactories ?? DEFAULT_WORKFLOW_FACTORIES, "workflowFactories"));
53
+ const factories = workflowFactoriesOf(RULE, context.options?.[0]?.workflowFactories);
58
54
  const calls = new Set(requireOption(RULE, context.options?.[0]?.queryStepCalls, "queryStepCalls", ACCEPTS));
59
55
  return {
60
56
  CallExpression(node) {
61
- if (!isCallOf(node, factories))
57
+ if (!isStepCall(node, factories))
62
58
  return;
63
59
  const steps = stepsIn(node.arguments ?? [], calls);
64
60
  if (steps.length < COLLIDES_FROM)
@@ -1,26 +1,30 @@
1
1
  import { type RuleContextWithOptions } from './lib/context.js';
2
2
  import { type Probe } from './lib/probe.js';
3
3
  interface Options {
4
- /** Regex source, tested against the normalised filename: the tree that holds the cell workers. */
5
4
  within?: string;
6
- /** The calls that open a scoped handle — `withCells`, `withConnection`, whatever this repo has. */
7
5
  wrapper?: string[];
8
6
  }
7
+ interface Callee {
8
+ computed?: boolean;
9
+ name?: string;
10
+ property?: {
11
+ name?: string;
12
+ type?: string;
13
+ };
14
+ type?: string;
15
+ }
9
16
  declare const cellHandlesAreScoped: {
10
17
  create(context: RuleContextWithOptions<Options>): {
18
+ ExportDefaultDeclaration?: undefined;
11
19
  CallExpression?: undefined;
12
20
  'Program:exit'?: undefined;
13
- ExportDefaultDeclaration?: undefined;
14
21
  ClassDeclaration?: undefined;
15
22
  } | {
16
23
  ExportDefaultDeclaration(node: {
17
24
  declaration?: unknown;
18
25
  }): void;
19
26
  CallExpression(node: {
20
- callee?: {
21
- type?: string;
22
- name?: string;
23
- };
27
+ callee?: Callee;
24
28
  }): void;
25
29
  ClassDeclaration(node: {
26
30
  superClass?: {
@@ -59,7 +63,6 @@ declare const cellHandlesAreScoped: {
59
63
  }[];
60
64
  type: 'problem';
61
65
  };
62
- /** A module handler inside the configured tree that never opens a scope — the smallest breach. */
63
66
  probe(options: readonly unknown[]): Probe;
64
67
  };
65
68
  export default cellHandlesAreScoped;
@@ -12,6 +12,14 @@ var entrypointMakers = new Set([
12
12
  "entrypointFor",
13
13
  "createWorkflowEntrypoint"
14
14
  ]);
15
+ var calleeName = (callee) => {
16
+ if (callee?.type === "Identifier")
17
+ return callee.name;
18
+ if (callee?.type === "MemberExpression" && callee.computed !== true) {
19
+ return callee.property?.type === "Identifier" ? callee.property.name : undefined;
20
+ }
21
+ return;
22
+ };
15
23
  var cellHandlesAreScoped = {
16
24
  create(context) {
17
25
  const within = requireOption(RULE, context.options?.[0]?.within, "within", ACCEPTS);
@@ -33,11 +41,10 @@ var cellHandlesAreScoped = {
33
41
  handlerNode = node;
34
42
  },
35
43
  CallExpression(node) {
36
- if (node.callee?.type !== "Identifier")
37
- return;
38
- if (node.callee.name !== undefined && scopedBy.has(node.callee.name))
44
+ const called = calleeName(node.callee);
45
+ if (called !== undefined && scopedBy.has(called))
39
46
  scoped = true;
40
- if (node.callee.name === "createWorkflowEntrypoint") {
47
+ if (node.callee?.type === "Identifier" && node.callee.name === "createWorkflowEntrypoint") {
41
48
  handler = "the workflow entrypoint";
42
49
  handlerNode = node;
43
50
  }
@@ -64,7 +71,7 @@ var cellHandlesAreScoped = {
64
71
  fixShape: `A worker that opens a database-cell handle wraps every invocation in the repo's scope wrapper — the
65
72
  module handlers (\`fetch\`, \`queue\`, \`scheduled\`, \`email\`) and the workflow entrypoint alike — so the
66
73
  handle is closed when the invocation ends: \`withCells(() => …, (closing) => ctx.waitUntil(closing))\`.
67
- A handler that declares itself and never calls it opens a pool per call and never closes one. Name
74
+ The call may be a member — \`cellOf(env).withConnection(…)\` — as well as a bare name. A handler that declares itself and never calls it opens a pool per call and never closes one. Name
68
75
  the wrapper in \`wrapper\` and the tree the cell workers live in in \`within\`; enabled without either
69
76
  the rule reports that once a run and judges nothing.`,
70
77
  meta: {
@@ -1,4 +1,5 @@
1
1
  import { type RuleContext } from './lib/context.js';
2
+ import { type Probe } from './lib/probe.js';
2
3
  interface CallExpression {
3
4
  arguments: {
4
5
  callee?: {
@@ -17,24 +18,12 @@ interface CallExpression {
17
18
  };
18
19
  type: 'CallExpression';
19
20
  }
20
- /**
21
- * Zustand v5 best practices — single rule enforcing all patterns:
22
- *
23
- * 1. REQUIRE SELECTOR: useStore() without args subscribes to entire state.
24
- * Fix: useStore((s) => s.field) or useStore(useShallow((s) => ({ ... })))
25
- *
26
- * 2. SINGLE CALL PER STORE: multiple useStore((s) => s.x) calls create
27
- * separate subscriptions. Fix: consolidate with useShallow.
28
- *
29
- * 3. useShallow BYPASS: calls wrapped in useShallow are always allowed,
30
- * even if there are multiple (edge case for different selector shapes).
31
- */
32
21
  declare const zustandV5BestPractices: {
33
22
  create(context: RuleContext): {
34
- ArrowFunctionExpression(node: unknown): void;
35
- 'ArrowFunctionExpression:exit'(): void;
23
+ ArrowFunctionExpression: () => void;
24
+ 'ArrowFunctionExpression:exit': () => void;
36
25
  CallExpression(node: CallExpression): void;
37
- FunctionDeclaration(node: unknown): void;
26
+ FunctionDeclaration: () => void;
38
27
  ImportDeclaration(node: {
39
28
  source: {
40
29
  value: string;
@@ -45,9 +34,9 @@ declare const zustandV5BestPractices: {
45
34
  };
46
35
  }[];
47
36
  }): void;
48
- 'FunctionDeclaration:exit'(): void;
49
- FunctionExpression(node: unknown): void;
50
- 'FunctionExpression:exit'(): void;
37
+ 'FunctionDeclaration:exit': () => void;
38
+ FunctionExpression: () => void;
39
+ 'FunctionExpression:exit': () => void;
51
40
  };
52
41
  fixShape: string;
53
42
  meta: {
@@ -61,5 +50,6 @@ declare const zustandV5BestPractices: {
61
50
  schema: never[];
62
51
  type: 'problem';
63
52
  };
53
+ probe(): Probe;
64
54
  };
65
55
  export default zustandV5BestPractices;