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