@starci/eslint-canon-fe 9.3.0 → 9.4.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 (177) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +5 -1
  3. package/dist/brand-values.d.ts +28 -0
  4. package/dist/brand-values.js +172 -0
  5. package/dist/button-intent.d.ts +11 -0
  6. package/dist/button-intent.js +361 -0
  7. package/dist/callback-shape.d.ts +7 -0
  8. package/dist/callback-shape.js +396 -0
  9. package/dist/class-names.d.ts +19 -0
  10. package/dist/class-names.js +170 -0
  11. package/dist/client-boundary.d.ts +42 -0
  12. package/dist/client-boundary.js +404 -0
  13. package/dist/comments.d.ts +47 -0
  14. package/dist/comments.js +156 -0
  15. package/dist/control-height.d.ts +7 -0
  16. package/dist/control-height.js +83 -0
  17. package/dist/drawing.d.ts +31 -0
  18. package/dist/drawing.js +376 -0
  19. package/dist/env-owner.d.ts +22 -0
  20. package/dist/env-owner.js +291 -0
  21. package/dist/fe-stack.d.ts +10 -0
  22. package/dist/fe-stack.js +73 -0
  23. package/dist/file-layout.d.ts +71 -0
  24. package/dist/file-layout.js +465 -0
  25. package/dist/formatting.d.ts +25 -0
  26. package/dist/formatting.js +104 -0
  27. package/dist/function-budget.d.ts +9 -0
  28. package/dist/function-budget.js +48 -0
  29. package/dist/grammar-shim.d.ts +5 -0
  30. package/dist/grammar-shim.js +126 -0
  31. package/dist/hooks-folder.d.ts +34 -0
  32. package/dist/hooks-folder.js +320 -0
  33. package/dist/hygiene.d.ts +39 -0
  34. package/dist/hygiene.js +540 -0
  35. package/dist/index.d.ts +105 -0
  36. package/dist/index.js +310 -0
  37. package/{landmark.mjs → dist/landmark.d.ts} +4 -6
  38. package/dist/landmark.js +6 -0
  39. package/dist/lib/ast.d.ts +36 -0
  40. package/dist/lib/ast.js +94 -0
  41. package/dist/lib/bindings.d.ts +84 -0
  42. package/dist/lib/bindings.js +168 -0
  43. package/dist/lib/class-text.d.ts +3 -0
  44. package/dist/lib/class-text.js +30 -0
  45. package/dist/lib/config.d.ts +81 -0
  46. package/dist/lib/config.js +141 -0
  47. package/dist/lib/drawing-scope.d.ts +5 -0
  48. package/{lib/drawing-scope.mjs → dist/lib/drawing-scope.js} +6 -11
  49. package/dist/lib/eslint-boundary.d.ts +23 -0
  50. package/dist/lib/eslint-boundary.js +6 -0
  51. package/dist/lib/fe-stack.d.ts +49 -0
  52. package/dist/lib/fe-stack.js +98 -0
  53. package/dist/lib/hfs-view.d.ts +74 -0
  54. package/dist/lib/hfs-view.js +6 -0
  55. package/dist/lib/hfs.d.ts +34 -0
  56. package/dist/lib/hfs.js +72 -0
  57. package/dist/lib/next.d.ts +4 -0
  58. package/dist/lib/next.js +10 -0
  59. package/dist/lib/own-hook-origin.d.ts +17 -0
  60. package/dist/lib/own-hook-origin.js +52 -0
  61. package/dist/lib/package-root.d.ts +4 -0
  62. package/dist/lib/package-root.js +10 -0
  63. package/dist/lib/path.d.ts +2 -0
  64. package/{lib/path.mjs → dist/lib/path.js} +1 -1
  65. package/dist/lib/platform.d.ts +26 -0
  66. package/{lib/platform.mjs → dist/lib/platform.js} +38 -35
  67. package/dist/lib/program-for.d.ts +5 -0
  68. package/dist/lib/program-for.js +23 -0
  69. package/dist/lib/pure-owner-helper.d.ts +10 -0
  70. package/dist/lib/pure-owner-helper.js +156 -0
  71. package/dist/lib/react.d.ts +16 -0
  72. package/dist/lib/react.js +22 -0
  73. package/dist/lib/redux-hook-origin.d.ts +4 -0
  74. package/dist/lib/redux-hook-origin.js +50 -0
  75. package/dist/lib/rule.d.ts +28 -0
  76. package/dist/lib/rule.js +5 -0
  77. package/dist/lib/scope.d.ts +88 -0
  78. package/{lib/scope.mjs → dist/lib/scope.js} +83 -93
  79. package/dist/lib/typed-class-values.d.ts +13 -0
  80. package/dist/lib/typed-class-values.js +92 -0
  81. package/dist/lib/types.d.ts +44 -0
  82. package/dist/lib/types.js +64 -0
  83. package/dist/lib/why.d.ts +350 -0
  84. package/dist/lib/why.js +350 -0
  85. package/dist/lint-escape-hatch.d.ts +24 -0
  86. package/{lint-escape-hatch.mjs → dist/lint-escape-hatch.js} +38 -45
  87. package/dist/lists.d.ts +31 -0
  88. package/dist/lists.js +202 -0
  89. package/dist/loading.d.ts +34 -0
  90. package/dist/loading.js +157 -0
  91. package/dist/naming.d.ts +33 -0
  92. package/dist/naming.js +176 -0
  93. package/dist/native-controls.d.ts +24 -0
  94. package/dist/native-controls.js +105 -0
  95. package/dist/next-conventions.d.ts +48 -0
  96. package/dist/next-conventions.js +452 -0
  97. package/dist/project-graph.d.ts +5 -0
  98. package/{project-graph.mjs → dist/project-graph.js} +8 -12
  99. package/dist/props-and-slots.d.ts +19 -0
  100. package/dist/props-and-slots.js +320 -0
  101. package/dist/served-locale.d.ts +38 -0
  102. package/dist/served-locale.js +263 -0
  103. package/dist/shape-slot.d.ts +18 -0
  104. package/dist/shape-slot.js +324 -0
  105. package/dist/size-and-state-budget.d.ts +46 -0
  106. package/dist/size-and-state-budget.js +424 -0
  107. package/dist/size-growth.d.ts +24 -0
  108. package/dist/size-growth.js +64 -0
  109. package/dist/sonar-parity.d.ts +5 -0
  110. package/{sonar-parity.mjs → dist/sonar-parity.js} +11 -7
  111. package/dist/surface-rules.d.ts +18 -0
  112. package/dist/surface-rules.js +491 -0
  113. package/dist/swr-family.d.ts +15 -0
  114. package/dist/swr-family.js +419 -0
  115. package/dist/the-split.d.ts +27 -0
  116. package/dist/the-split.js +157 -0
  117. package/dist/tokens.d.ts +9 -0
  118. package/dist/tokens.js +35 -0
  119. package/dist/translation.d.ts +51 -0
  120. package/dist/translation.js +403 -0
  121. package/dist/transport.d.ts +73 -0
  122. package/dist/transport.js +885 -0
  123. package/dist/type-safety.d.ts +43 -0
  124. package/dist/type-safety.js +139 -0
  125. package/dist/ui-rulings.d.ts +11 -0
  126. package/dist/ui-rulings.js +121 -0
  127. package/package.json +31 -9
  128. package/runtime/knowledge/hfs/canon-pins.yaml +17 -10
  129. package/brand-values.mjs +0 -172
  130. package/button-intent.mjs +0 -320
  131. package/callback-shape.mjs +0 -332
  132. package/class-names.mjs +0 -149
  133. package/client-boundary.mjs +0 -400
  134. package/comments.mjs +0 -159
  135. package/control-height.mjs +0 -77
  136. package/drawing.mjs +0 -321
  137. package/env-owner.mjs +0 -244
  138. package/fe-stack.mjs +0 -86
  139. package/file-layout.mjs +0 -472
  140. package/formatting.mjs +0 -110
  141. package/function-budget.mjs +0 -49
  142. package/grammar-shim.mjs +0 -123
  143. package/hooks-folder.mjs +0 -300
  144. package/hygiene.mjs +0 -513
  145. package/index.mjs +0 -317
  146. package/lib/ast.mjs +0 -97
  147. package/lib/bindings.mjs +0 -159
  148. package/lib/class-text.mjs +0 -29
  149. package/lib/config.mjs +0 -142
  150. package/lib/fe-stack.mjs +0 -77
  151. package/lib/hfs.mjs +0 -49
  152. package/lib/next.mjs +0 -6
  153. package/lib/own-hook-origin.mjs +0 -53
  154. package/lib/program-for.mjs +0 -22
  155. package/lib/pure-owner-helper.mjs +0 -148
  156. package/lib/react.mjs +0 -28
  157. package/lib/redux-hook-origin.mjs +0 -44
  158. package/lib/typed-class-values.mjs +0 -81
  159. package/lib/types.mjs +0 -63
  160. package/lib/why.mjs +0 -352
  161. package/lists.mjs +0 -193
  162. package/loading.mjs +0 -156
  163. package/naming.mjs +0 -185
  164. package/native-controls.mjs +0 -108
  165. package/next-conventions.mjs +0 -447
  166. package/props-and-slots.mjs +0 -276
  167. package/served-locale.mjs +0 -243
  168. package/size-and-state-budget.mjs +0 -376
  169. package/size-growth.mjs +0 -62
  170. package/surface-rules.mjs +0 -443
  171. package/swr-family.mjs +0 -326
  172. package/the-split.mjs +0 -155
  173. package/tokens.mjs +0 -39
  174. package/translation.mjs +0 -392
  175. package/transport.mjs +0 -830
  176. package/type-safety.mjs +0 -147
  177. package/ui-rulings.mjs +0 -134
package/env-owner.mjs DELETED
@@ -1,244 +0,0 @@
1
- /**
2
- * The rules that hold `env-owner.md` (HFS R49, `FE_ENV_OWNER`).
3
- *
4
- * TWO FACTS, ONE OWNER. The environment is read in exactly one place, `modules/config`, and what it
5
- * reads has no default that stands in for a real deployment value. They are one law because they
6
- * fail together: an app with nine copies of `process.env.API_URL ?? "http://localhost:3068"` had
7
- * nine readers and nine silent defaults, so a production build with the variable missing talked to
8
- * a developer's laptop and looked healthy.
9
- *
10
- * The rules cannot see whether `modules/config` throws when a variable is missing in production -
11
- * that is a behaviour, and the module's own spec is where it is proven. What they hold is the
12
- * shape that makes it provable: one reader, and no literal fallback next to it.
13
- */
14
-
15
- import { isConfigModule } from "./lib/scope.mjs"
16
-
17
- /** `process.env`, or `import.meta.env`. */
18
- const isEnvObject = (node) => {
19
- if (!node || node.type !== "MemberExpression" || node.computed) return false
20
- const name = node.property && node.property.name
21
- if (name !== "env") return false
22
- const object = node.object
23
- if (object.type === "Identifier" && object.name === "process") return true
24
- return (
25
- object.type === "MetaProperty" && object.meta.name === "import" && object.property.name === "meta"
26
- )
27
- }
28
-
29
- /** The variable name of `process.env.NAME` / `process.env["NAME"]`, or null. */
30
- const envVariableName = (node) => {
31
- if (!node || node.type !== "MemberExpression" || !isEnvObject(node.object)) return null
32
- if (!node.computed && node.property.type === "Identifier") return node.property.name
33
- if (node.computed && node.property.type === "Literal" && typeof node.property.value === "string") {
34
- return node.property.value
35
- }
36
- return null
37
- }
38
-
39
- /** Static text of a literal or an expression-free template, else null. */
40
- const staticText = (node) => {
41
- if (!node) return null
42
- if (node.type === "Literal" && typeof node.value === "string") return node.value
43
- if (node.type === "TemplateLiteral") {
44
- return node.quasis.map((quasi) => quasi.value.cooked ?? "").join("")
45
- }
46
- return null
47
- }
48
-
49
- /** An absolute or protocol-relative URL: what a fallback must never carry. */
50
- const URL_TEXT = /^(?:https?:)?\/\/\S+/i
51
-
52
- /** Variable names whose value is a secret or an address: a default for one is a hole in a deployment. */
53
- const SENSITIVE_NAME = /(?:SECRET|TOKEN|PASSWORD|PASSWD|API_?KEY|PRIVATE_?KEY|URL|URI|HOST|ENDPOINT|ORIGIN)$/i
54
-
55
- /** Type assertions and optional chains preserve the identity of the wrapped value. */
56
- const unwrapped = (node) => {
57
- while (node && ["TSAsExpression", "TSTypeAssertion", "TSNonNullExpression", "TSSatisfiesExpression", "ChainExpression"].includes(node.type)) node = node.expression
58
- return node
59
- }
60
-
61
- /** The binding of this identifier, rather than every variable with the same spelling. */
62
- const bindingOf = (sourceCode, identifier) => {
63
- for (let scope = sourceCode.getScope(identifier); scope; scope = scope.upper) {
64
- const variable = scope.set.get(identifier.name)
65
- if (variable) return variable
66
- }
67
- return null
68
- }
69
-
70
- /** Prior writes in the use's execution scope or its parents; a closure can capture a later initializer. */
71
- const writeBefore = (sourceCode, variable, node) => {
72
- const scopes = new Set()
73
- for (let scope = sourceCode.getScope(node); scope; scope = scope.upper) scopes.add(scope.variableScope)
74
- const writes = variable.references.filter((reference) => reference.isWrite()
75
- && scopes.has(reference.from.variableScope)
76
- && !(reference.writeExpr && reference.writeExpr.range[0] <= node.range[0] && node.range[1] <= reference.writeExpr.range[1]))
77
- return writes
78
- .filter((reference) => reference.identifier.range[0] < node.range[0])
79
- .sort((left, right) => right.identifier.range[0] - left.identifier.range[0])[0]
80
- ?? writes.find((reference) => reference.init)
81
- }
82
-
83
- /** Property extraction between a write's source and its destructured binding. */
84
- const propertyPath = (identifier) => {
85
- const properties = []
86
- for (let node = identifier; node.parent; node = node.parent) {
87
- const parent = node.parent
88
- if (parent.type === "Property" && parent.value === node) properties.unshift(parent)
89
- else if (parent.type !== "ObjectPattern" && !(parent.type === "AssignmentPattern" && parent.left === node)) break
90
- }
91
- return properties
92
- }
93
-
94
- /** Known environment containers and constant computed keys, followed through local aliases. */
95
- const environmentIdentity = (sourceCode) => {
96
- const textOf = (node, seen = new Set()) => {
97
- node = unwrapped(node)
98
- if (!node || seen.has(node)) return null
99
- if (node.type === "Literal" && typeof node.value === "string") return node.value
100
- if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0].value.cooked
101
- if (node.type !== "Identifier") return null
102
- const variable = bindingOf(sourceCode, node)
103
- if (!variable) return null
104
- const write = writeBefore(sourceCode, variable, node)
105
- if (!write?.writeExpr || propertyPath(write.identifier).length > 0) return null
106
- return textOf(write.writeExpr, new Set([...seen, node]))
107
- }
108
- const keyOf = (node) => node.computed ? textOf(node.key ?? node.property) : (node.key ?? node.property).name ?? (node.key ?? node.property).value
109
- const propertyKind = (kind, key) => {
110
- if (kind === "global" && key === "process") return "process"
111
- if ((kind === "process" || kind === "meta") && key === "env") return "env"
112
- return null
113
- }
114
- const kindOf = (node, seen = new Set()) => {
115
- node = unwrapped(node)
116
- if (!node || seen.has(node)) return null
117
- if (node.type === "MetaProperty" && node.meta.name === "import" && node.property.name === "meta") return "meta"
118
- if (node.type === "MemberExpression") return propertyKind(kindOf(node.object, new Set([...seen, node])), keyOf(node))
119
- if (node.type !== "Identifier") return null
120
- const variable = bindingOf(sourceCode, node)
121
- if (!variable || variable.defs.length === 0) {
122
- if (node.name === "process") return "process"
123
- return ["globalThis", "global"].includes(node.name) ? "global" : null
124
- }
125
- const imported = variable.defs.find((definition) => definition.type === "ImportBinding" && ["process", "node:process"].includes(definition.parent.source.value))
126
- if (imported) {
127
- if (imported.parent.importKind === "type" || imported.node.importKind === "type") return null
128
- if (imported.node.type !== "ImportSpecifier") return "process"
129
- const name = imported.node.imported.name ?? imported.node.imported.value
130
- return name === "default" ? "process" : name === "env" ? "env" : null
131
- }
132
- const write = writeBefore(sourceCode, variable, node)
133
- if (!write?.writeExpr) return null
134
- let kind = kindOf(write.writeExpr, new Set([...seen, node]))
135
- for (const property of propertyPath(write.identifier)) kind = propertyKind(kind, keyOf(property))
136
- return kind
137
- }
138
- const readsEnv = (pattern, kind) => {
139
- if (pattern.type === "AssignmentPattern") return readsEnv(pattern.left, kind)
140
- if (pattern.type !== "ObjectPattern") return false
141
- return pattern.properties.some((property) => {
142
- if (property.type !== "Property") return false
143
- const valueKind = propertyKind(kind, keyOf(property))
144
- return valueKind === "env" || readsEnv(property.value, valueKind)
145
- })
146
- }
147
- return { kindOf, readsEnv }
148
- }
149
-
150
- // -- FE-ENV-1 --------------------------------------------------------------------------------------
151
-
152
- /** Only `modules/config` reads the environment. */
153
- export const noEnvOutsideConfig = {
154
- meta: {
155
- type: "problem",
156
- docs: { description: "`process.env` and `import.meta.env` are read only in `modules/config`." },
157
- schema: [],
158
- messages: {
159
- env:
160
- "This file reads the environment. Only `modules/config` may: it is the one place that can fail loudly when a production variable is missing. Read the value from the config module's typed export instead.",
161
- },
162
- },
163
- create(context) {
164
- if (isConfigModule(context)) return {}
165
- const members = []
166
- const destructures = []
167
- return {
168
- ImportDeclaration(node) {
169
- if (node.importKind === "type" || !["process", "node:process"].includes(node.source.value)) return
170
- for (const specifier of node.specifiers) {
171
- if (specifier.type === "ImportSpecifier" && specifier.importKind !== "type" && (specifier.imported.name ?? specifier.imported.value) === "env") context.report({ node: specifier, messageId: "env" })
172
- }
173
- },
174
- MemberExpression(node) {
175
- members.push(node)
176
- },
177
- VariableDeclarator(node) {
178
- if (node.id.type === "ObjectPattern" && node.init) destructures.push({ node, pattern: node.id, value: node.init })
179
- },
180
- AssignmentExpression(node) {
181
- if (node.operator === "=" && node.left.type === "ObjectPattern") destructures.push({ node, pattern: node.left, value: node.right })
182
- },
183
- AssignmentPattern(node) {
184
- if (node.left.type === "ObjectPattern") destructures.push({ node, pattern: node.left, value: node.right })
185
- },
186
- "Program:exit"() {
187
- const { kindOf, readsEnv } = environmentIdentity(context.sourceCode ?? context.getSourceCode())
188
- for (const node of members) {
189
- if (kindOf(node) === "env") context.report({ node, messageId: "env" })
190
- }
191
- for (const { node, pattern, value } of destructures) {
192
- if (readsEnv(pattern, kindOf(value))) context.report({ node, messageId: "env" })
193
- }
194
- },
195
- }
196
- },
197
- }
198
-
199
- // -- FE-ENV-2 --------------------------------------------------------------------------------------
200
-
201
- /** A fallback never stands in for a deployment address or secret. */
202
- export const noHardcodedEndpointFallback = {
203
- meta: {
204
- type: "problem",
205
- docs: { description: "No URL literal as a fallback, and no literal default for an address or secret variable." },
206
- schema: [],
207
- messages: {
208
- url:
209
- "A URL literal used as a fallback (`?? \"http://...\"`, `|| \"http://...\"` or a parameter default). A missing variable then silently points production at a developer's machine. Read the address from `modules/config`, which throws when it is absent.",
210
- variable:
211
- "`{{name}}` is an address or secret variable with a literal default. A default here makes a misconfigured deployment look healthy. Remove the default and let `modules/config` fail loudly when it is missing.",
212
- },
213
- },
214
- create(context) {
215
- return {
216
- LogicalExpression(node) {
217
- if (node.operator !== "??" && node.operator !== "||") return
218
- const text = staticText(node.right)
219
- if (text === null) return
220
- if (URL_TEXT.test(text)) {
221
- context.report({ node: node.right, messageId: "url" })
222
- return
223
- }
224
- const name = envVariableName(node.left)
225
- if (name && text !== "" && SENSITIVE_NAME.test(name)) {
226
- context.report({ node: node.right, messageId: "variable", data: { name } })
227
- }
228
- },
229
- AssignmentPattern(node) {
230
- const text = staticText(node.right)
231
- if (text !== null && URL_TEXT.test(text)) context.report({ node: node.right, messageId: "url" })
232
- },
233
- }
234
- },
235
- }
236
-
237
- /** The rules this law contributes to the plugin. */
238
- export const rules = {
239
- "no-env-outside-config": noEnvOutsideConfig,
240
- "no-hardcoded-endpoint-fallback": noHardcodedEndpointFallback,
241
- }
242
-
243
- /** Both are exact syntactic shapes, and both are errors. */
244
- export const recommended = Object.fromEntries(Object.keys(rules).map((name) => [`starci-fe/${name}`, "error"]))
package/fe-stack.mjs DELETED
@@ -1,86 +0,0 @@
1
- /**
2
- * The rule that holds the front-end stack (`knowledge/hfs/fe-stack.yaml`, failure code `FE_STACK_FORBIDDEN`).
3
- *
4
- * ONE LIBRARY PER CONCERN. Every common front-end concern - GraphQL, REST, data hooks, toast, forms, state, realtime, i18n, UI,
5
- * icons, motion, dates, numbers, storage, charts, markdown, code - has one canonical library, the same for the lite and the full
6
- * edition (owner ruling 2026-10-13). A second library for a concern is a second set of conventions a reader has to know and a
7
- * second dependency a release has to keep current, and it always arrives one import at a time. The catalog names, per concern,
8
- * the alternatives a front end never imports; this rule refuses exactly those, read from the catalog, so the list lives once.
9
- *
10
- * SUPABASE IS AUTH ONLY. With a Supabase connection the front end holds the Supabase Auth client and nothing else of Supabase
11
- * (the data path is GraphQL to the back end): `@supabase/*` is imported only by `modules/auth` (slot `fe.modules.auth`).
12
- *
13
- * WHAT IS LEFT TO SIBLINGS. A canonical library imported in the wrong place (Apollo outside `modules/api`, axios outside its REST
14
- * calls, socket.io-client outside `hooks/socketio`) and `fetch` outside the API layer are `fetch-only-in-api-client`'s findings;
15
- * a glyph library outside the icon leaf is the icon law's. Each import is judged by one rule.
16
- */
17
- import { forbiddingConcern, packageNameOf } from "./lib/fe-stack.mjs"
18
- import { globalReferences, inSlot, isProductSource } from "./lib/scope.mjs"
19
-
20
- /** The package scope only the identity-provider client may import. */
21
- const SUPABASE_SCOPE = "@supabase/"
22
-
23
- /**
24
- * No import (static, re-export, dynamic or `require`) of a package the fe-stack catalog forbids, and no `@supabase/*` outside
25
- * `modules/auth`. Type-only imports count: a type import still makes the package a dependency of the front end.
26
- */
27
- export const feStackImport = {
28
- meta: {
29
- type: "problem",
30
- docs: { description: "A front end imports no library the fe-stack catalog forbids, and `@supabase/*` only in `modules/auth`." },
31
- schema: [],
32
- messages: {
33
- forbidden:
34
- "`{{name}}` is a forbidden alternative for the {{concern}} concern of the front-end stack. The stack has one library per concern, the same in every app and both editions: use {{canonical}} ({{pattern}}). {{why}}",
35
- supabase:
36
- "`{{name}}` is imported outside `modules/auth`. Supabase on the front end is the auth client only, held by `modules/auth` (the session, the access token the Apollo link attaches, sign-in and sign-out); data is read and written through the GraphQL operations of `modules/api`, never through Supabase from the browser.",
37
- },
38
- },
39
- create(context) {
40
- if (!isProductSource(context)) return {}
41
- const authOwner = inSlot(context, "fe.modules.auth")
42
- const judge = (node, source) => {
43
- const name = packageNameOf(source)
44
- if (name === null) return
45
- const concern = forbiddingConcern(source)
46
- if (concern) {
47
- const named = concern.packages?.length > 0 ? concern.packages : concern.home ?? []
48
- const canonical = named.map((entry) => `\`${entry}\``).join(", ")
49
- context.report({ node, messageId: "forbidden", data: { name, concern: concern.id, canonical, pattern: concern.pattern ?? "", why: concern.why ?? "" } })
50
- return
51
- }
52
- if (name.startsWith(SUPABASE_SCOPE) && !authOwner) context.report({ node, messageId: "supabase", data: { name } })
53
- }
54
- return {
55
- ImportDeclaration(node) {
56
- judge(node, node.source.value)
57
- },
58
- ExportNamedDeclaration(node) {
59
- if (node.source) judge(node, node.source.value)
60
- },
61
- ExportAllDeclaration(node) {
62
- judge(node, node.source.value)
63
- },
64
- ImportExpression(node) {
65
- if (node.source.type === "Literal") judge(node, node.source.value)
66
- },
67
- TSImportEqualsDeclaration(node) {
68
- // `import x = require("moment")`
69
- if (node.moduleReference?.type === "TSExternalModuleReference" && node.moduleReference.expression?.type === "Literal") judge(node, node.moduleReference.expression.value)
70
- },
71
- CallExpression(node) {
72
- // `require("moment")` where `require` is the CommonJS global.
73
- if (node.callee.type !== "Identifier" || node.callee.name !== "require" || node.arguments[0]?.type !== "Literal") return
74
- if (globalReferences(context, new Set(["require"])).includes(node.callee)) judge(node, node.arguments[0].value)
75
- },
76
- }
77
- },
78
- }
79
-
80
- /** The rules this law contributes to the plugin. */
81
- export const rules = {
82
- "fe-stack-import": feStackImport,
83
- }
84
-
85
- /** Every rule is an error: a second library for a concern is the defect, not a style. */
86
- export const recommended = Object.fromEntries(Object.keys(rules).map((name) => [`starci-fe/${name}`, "error"]))