@starci/eslint-canon-fe 9.2.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.
- package/CHANGELOG.md +14 -0
- package/README.md +5 -1
- package/dist/brand-values.d.ts +28 -0
- package/dist/brand-values.js +172 -0
- package/dist/button-intent.d.ts +11 -0
- package/dist/button-intent.js +361 -0
- package/dist/callback-shape.d.ts +7 -0
- package/dist/callback-shape.js +396 -0
- package/dist/class-names.d.ts +19 -0
- package/dist/class-names.js +170 -0
- package/dist/client-boundary.d.ts +42 -0
- package/dist/client-boundary.js +404 -0
- package/dist/comments.d.ts +47 -0
- package/dist/comments.js +156 -0
- package/dist/control-height.d.ts +7 -0
- package/dist/control-height.js +83 -0
- package/dist/drawing.d.ts +31 -0
- package/dist/drawing.js +376 -0
- package/dist/env-owner.d.ts +22 -0
- package/dist/env-owner.js +291 -0
- package/dist/fe-stack.d.ts +10 -0
- package/dist/fe-stack.js +73 -0
- package/dist/file-layout.d.ts +71 -0
- package/dist/file-layout.js +465 -0
- package/dist/formatting.d.ts +25 -0
- package/dist/formatting.js +104 -0
- package/dist/function-budget.d.ts +9 -0
- package/dist/function-budget.js +48 -0
- package/dist/grammar-shim.d.ts +5 -0
- package/dist/grammar-shim.js +126 -0
- package/dist/hooks-folder.d.ts +34 -0
- package/dist/hooks-folder.js +320 -0
- package/dist/hygiene.d.ts +39 -0
- package/dist/hygiene.js +540 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +310 -0
- package/{landmark.mjs → dist/landmark.d.ts} +4 -6
- package/dist/landmark.js +6 -0
- package/dist/lib/ast.d.ts +36 -0
- package/dist/lib/ast.js +94 -0
- package/dist/lib/bindings.d.ts +84 -0
- package/dist/lib/bindings.js +168 -0
- package/dist/lib/class-text.d.ts +3 -0
- package/dist/lib/class-text.js +30 -0
- package/dist/lib/config.d.ts +81 -0
- package/dist/lib/config.js +141 -0
- package/dist/lib/drawing-scope.d.ts +5 -0
- package/{lib/drawing-scope.mjs → dist/lib/drawing-scope.js} +6 -11
- package/dist/lib/eslint-boundary.d.ts +23 -0
- package/dist/lib/eslint-boundary.js +6 -0
- package/dist/lib/fe-stack.d.ts +49 -0
- package/dist/lib/fe-stack.js +98 -0
- package/dist/lib/hfs-view.d.ts +74 -0
- package/dist/lib/hfs-view.js +6 -0
- package/dist/lib/hfs.d.ts +34 -0
- package/dist/lib/hfs.js +72 -0
- package/dist/lib/next.d.ts +4 -0
- package/dist/lib/next.js +10 -0
- package/dist/lib/own-hook-origin.d.ts +17 -0
- package/dist/lib/own-hook-origin.js +52 -0
- package/dist/lib/package-root.d.ts +4 -0
- package/dist/lib/package-root.js +10 -0
- package/dist/lib/path.d.ts +2 -0
- package/{lib/path.mjs → dist/lib/path.js} +1 -1
- package/dist/lib/platform.d.ts +26 -0
- package/{lib/platform.mjs → dist/lib/platform.js} +38 -35
- package/dist/lib/program-for.d.ts +5 -0
- package/dist/lib/program-for.js +23 -0
- package/dist/lib/pure-owner-helper.d.ts +10 -0
- package/dist/lib/pure-owner-helper.js +156 -0
- package/dist/lib/react.d.ts +16 -0
- package/dist/lib/react.js +22 -0
- package/dist/lib/redux-hook-origin.d.ts +4 -0
- package/dist/lib/redux-hook-origin.js +50 -0
- package/dist/lib/rule.d.ts +28 -0
- package/dist/lib/rule.js +5 -0
- package/dist/lib/scope.d.ts +88 -0
- package/{lib/scope.mjs → dist/lib/scope.js} +83 -93
- package/dist/lib/typed-class-values.d.ts +13 -0
- package/dist/lib/typed-class-values.js +92 -0
- package/dist/lib/types.d.ts +44 -0
- package/dist/lib/types.js +64 -0
- package/dist/lib/why.d.ts +350 -0
- package/dist/lib/why.js +350 -0
- package/dist/lint-escape-hatch.d.ts +24 -0
- package/{lint-escape-hatch.mjs → dist/lint-escape-hatch.js} +38 -45
- package/dist/lists.d.ts +31 -0
- package/dist/lists.js +202 -0
- package/dist/loading.d.ts +34 -0
- package/dist/loading.js +157 -0
- package/dist/naming.d.ts +33 -0
- package/dist/naming.js +176 -0
- package/dist/native-controls.d.ts +24 -0
- package/dist/native-controls.js +105 -0
- package/dist/next-conventions.d.ts +48 -0
- package/dist/next-conventions.js +452 -0
- package/dist/project-graph.d.ts +5 -0
- package/{project-graph.mjs → dist/project-graph.js} +8 -12
- package/dist/props-and-slots.d.ts +19 -0
- package/dist/props-and-slots.js +320 -0
- package/dist/served-locale.d.ts +38 -0
- package/dist/served-locale.js +263 -0
- package/dist/shape-slot.d.ts +18 -0
- package/dist/shape-slot.js +324 -0
- package/dist/size-and-state-budget.d.ts +46 -0
- package/dist/size-and-state-budget.js +424 -0
- package/dist/size-growth.d.ts +24 -0
- package/dist/size-growth.js +64 -0
- package/dist/sonar-parity.d.ts +5 -0
- package/{sonar-parity.mjs → dist/sonar-parity.js} +11 -7
- package/dist/surface-rules.d.ts +18 -0
- package/dist/surface-rules.js +491 -0
- package/dist/swr-family.d.ts +15 -0
- package/dist/swr-family.js +419 -0
- package/dist/the-split.d.ts +27 -0
- package/dist/the-split.js +157 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.js +35 -0
- package/dist/translation.d.ts +51 -0
- package/dist/translation.js +403 -0
- package/dist/transport.d.ts +73 -0
- package/dist/transport.js +885 -0
- package/dist/type-safety.d.ts +43 -0
- package/dist/type-safety.js +139 -0
- package/dist/ui-rulings.d.ts +11 -0
- package/dist/ui-rulings.js +121 -0
- package/package.json +31 -9
- package/runtime/knowledge/hfs/canon-pins.yaml +22 -14
- package/runtime/scripts/lib/package-at.mjs +5 -5
- package/brand-values.mjs +0 -172
- package/button-intent.mjs +0 -320
- package/callback-shape.mjs +0 -332
- package/class-names.mjs +0 -149
- package/client-boundary.mjs +0 -400
- package/comments.mjs +0 -159
- package/control-height.mjs +0 -77
- package/drawing.mjs +0 -315
- package/env-owner.mjs +0 -244
- package/fe-stack.mjs +0 -86
- package/file-layout.mjs +0 -472
- package/formatting.mjs +0 -110
- package/function-budget.mjs +0 -49
- package/grammar-shim.mjs +0 -123
- package/hooks-folder.mjs +0 -300
- package/hygiene.mjs +0 -513
- package/index.mjs +0 -317
- package/lib/ast.mjs +0 -97
- package/lib/bindings.mjs +0 -159
- package/lib/class-text.mjs +0 -29
- package/lib/config.mjs +0 -142
- package/lib/fe-stack.mjs +0 -77
- package/lib/hfs.mjs +0 -49
- package/lib/next.mjs +0 -6
- package/lib/own-hook-origin.mjs +0 -53
- package/lib/program-for.mjs +0 -22
- package/lib/pure-owner-helper.mjs +0 -148
- package/lib/react.mjs +0 -28
- package/lib/redux-hook-origin.mjs +0 -44
- package/lib/typed-class-values.mjs +0 -81
- package/lib/types.mjs +0 -63
- package/lib/why.mjs +0 -352
- package/lists.mjs +0 -193
- package/loading.mjs +0 -156
- package/naming.mjs +0 -185
- package/native-controls.mjs +0 -108
- package/next-conventions.mjs +0 -447
- package/props-and-slots.mjs +0 -276
- package/served-locale.mjs +0 -243
- package/size-and-state-budget.mjs +0 -376
- package/size-growth.mjs +0 -62
- package/surface-rules.mjs +0 -443
- package/swr-family.mjs +0 -326
- package/the-split.mjs +0 -155
- package/tokens.mjs +0 -39
- package/translation.mjs +0 -392
- package/transport.mjs +0 -830
- package/type-safety.mjs +0 -147
- package/ui-rulings.mjs +0 -134
package/index.mjs
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The universal frontend plugin: every applicable law's rules, gathered once.
|
|
3
|
-
*
|
|
4
|
-
* The public factory gathers every universal rule at error. A managed repository config composes
|
|
5
|
-
* it with the selected Grammar-family factory instead of registering individual rules. Family
|
|
6
|
-
* packages consume the public generic infrastructure exports; each rule implementation has one owner.
|
|
7
|
-
*
|
|
8
|
-
* Published IDs identify their owning law. Namespace migrations are explicit in the removed-vocabulary
|
|
9
|
-
* catalog; compatibility aliases cannot create a second owner for a rule.
|
|
10
|
-
*/
|
|
11
|
-
import { createRequire } from "node:module"
|
|
12
|
-
import { join } from "node:path"
|
|
13
|
-
import { pathToFileURL } from "node:url"
|
|
14
|
-
import { recommended as brandValuesRecommended, rules as brandValuesRules } from "./brand-values.mjs"
|
|
15
|
-
import { recommended as buttonIntentRecommended, rules as buttonIntentRules } from "./button-intent.mjs"
|
|
16
|
-
import { recommended as controlHeightRecommended, rules as controlHeightRules } from "./control-height.mjs"
|
|
17
|
-
import { recommended as clientBoundaryRecommended, rules as clientBoundaryRules } from "./client-boundary.mjs"
|
|
18
|
-
import { recommended as commentsRecommended, rules as commentsRules } from "./comments.mjs"
|
|
19
|
-
import { recommended as classNamesRecommended, rules as classNamesRules } from "./class-names.mjs"
|
|
20
|
-
import { recommended as envOwnerRecommended, rules as envOwnerRules } from "./env-owner.mjs"
|
|
21
|
-
import { recommended as feStackRecommended, rules as feStackRules } from "./fe-stack.mjs"
|
|
22
|
-
import { recommended as fileLayoutRecommended, rules as fileLayoutRules } from "./file-layout.mjs"
|
|
23
|
-
import { recommended as formattingRecommended, rules as formattingRules } from "./formatting.mjs"
|
|
24
|
-
import { recommended as grammarShimRecommended, rules as grammarShimRules } from "./grammar-shim.mjs"
|
|
25
|
-
import { recommended as hooksFolderRecommended, rules as hooksFolderRules } from "./hooks-folder.mjs"
|
|
26
|
-
import { recommended as hygieneRecommended, rules as hygieneRules } from "./hygiene.mjs"
|
|
27
|
-
import { recommended as landmarkRecommended, rules as landmarkRules } from "./landmark.mjs"
|
|
28
|
-
import { recommended as listsRecommended, rules as listsRules } from "./lists.mjs"
|
|
29
|
-
import { recommended as loadingRecommended, rules as loadingRules } from "./loading.mjs"
|
|
30
|
-
import { recommended as lintEscapeRecommended, rules as lintEscapeRules } from "./lint-escape-hatch.mjs"
|
|
31
|
-
import { recommended as namingRecommended, rules as namingRules } from "./naming.mjs"
|
|
32
|
-
import { recommended as nativeControlsRecommended, rules as nativeControlsRules } from "./native-controls.mjs"
|
|
33
|
-
import { recommended as nextConventionsRecommended, rules as nextConventionsRules } from "./next-conventions.mjs"
|
|
34
|
-
import { recommended as propsRecommended, rules as propsRules } from "./props-and-slots.mjs"
|
|
35
|
-
import { recommended as servedLocaleRecommended, rules as servedLocaleRules } from "./served-locale.mjs"
|
|
36
|
-
import { recommended as drawingRecommended, rules as drawingRules } from "./drawing.mjs"
|
|
37
|
-
import { recommended as sizeBudgetRecommended, rules as sizeBudgetRules } from "./size-and-state-budget.mjs"
|
|
38
|
-
import { recommended as sizeGrowthRecommended, rules as sizeGrowthRules } from "./size-growth.mjs"
|
|
39
|
-
import { recommended as surfaceRulesRecommended, rules as surfaceRules } from "./surface-rules.mjs"
|
|
40
|
-
import { recommended as splitRecommended, rules as splitRules } from "./the-split.mjs"
|
|
41
|
-
import { recommended as tokensRecommended, rules as tokensRules } from "./tokens.mjs"
|
|
42
|
-
import { recommended as transportRecommended, rules as transportRules } from "./transport.mjs"
|
|
43
|
-
import { recommended as translationRecommended, rules as translationRules } from "./translation.mjs"
|
|
44
|
-
import { recommended as typeSafetyRecommended, rules as typeSafetyRules } from "./type-safety.mjs"
|
|
45
|
-
import { recommended as projectGraphRecommended, rules as projectGraphRules } from "./project-graph.mjs"
|
|
46
|
-
import { recommended as sonarParityRecommended, rules as sonarParityRules } from "./sonar-parity.mjs"
|
|
47
|
-
import { buildFeConfig, buildPlainSourceConfig } from "./lib/config.mjs"
|
|
48
|
-
import { why } from "./lib/why.mjs"
|
|
49
|
-
import { recommended as uiRulingsRecommended, rules as uiRulingsRules } from "./ui-rulings.mjs"
|
|
50
|
-
|
|
51
|
-
/** Each law's contribution, kept separate so a duplicate name is detectable rather than silent. */
|
|
52
|
-
const CONTRIBUTIONS = [
|
|
53
|
-
{ law: "ui-rulings", rules: uiRulingsRules, recommended: uiRulingsRecommended },
|
|
54
|
-
{ law: "project-graph", rules: projectGraphRules, recommended: projectGraphRecommended },
|
|
55
|
-
{ law: "brand-values", rules: brandValuesRules, recommended: brandValuesRecommended },
|
|
56
|
-
{ law: "button-intent", rules: buttonIntentRules, recommended: buttonIntentRecommended },
|
|
57
|
-
{ law: "control-height", rules: controlHeightRules, recommended: controlHeightRecommended },
|
|
58
|
-
{ law: "client-boundary", rules: clientBoundaryRules, recommended: clientBoundaryRecommended },
|
|
59
|
-
{ law: "comments", rules: commentsRules, recommended: commentsRecommended },
|
|
60
|
-
{
|
|
61
|
-
law: "class-names",
|
|
62
|
-
rules: classNamesRules,
|
|
63
|
-
recommended: classNamesRecommended,
|
|
64
|
-
},
|
|
65
|
-
{ law: "env-owner", rules: envOwnerRules, recommended: envOwnerRecommended },
|
|
66
|
-
{ law: "fe-stack", rules: feStackRules, recommended: feStackRecommended },
|
|
67
|
-
{ law: "file-layout", rules: fileLayoutRules, recommended: fileLayoutRecommended },
|
|
68
|
-
{ law: "formatting", rules: formattingRules, recommended: formattingRecommended },
|
|
69
|
-
{ law: "grammar-shim", rules: grammarShimRules, recommended: grammarShimRecommended },
|
|
70
|
-
{ law: "hooks-folder", rules: hooksFolderRules, recommended: hooksFolderRecommended },
|
|
71
|
-
{ law: "hygiene", rules: hygieneRules, recommended: hygieneRecommended },
|
|
72
|
-
{ law: "landmark", rules: landmarkRules, recommended: landmarkRecommended },
|
|
73
|
-
{ law: "lists", rules: listsRules, recommended: listsRecommended },
|
|
74
|
-
{ law: "loading", rules: loadingRules, recommended: loadingRecommended },
|
|
75
|
-
{ law: "lint-escape-hatch", rules: lintEscapeRules, recommended: lintEscapeRecommended },
|
|
76
|
-
{ law: "naming", rules: namingRules, recommended: namingRecommended },
|
|
77
|
-
{ law: "native-controls", rules: nativeControlsRules, recommended: nativeControlsRecommended },
|
|
78
|
-
{ law: "next-conventions", rules: nextConventionsRules, recommended: nextConventionsRecommended },
|
|
79
|
-
{ law: "props-and-slots", rules: propsRules, recommended: propsRecommended },
|
|
80
|
-
{ law: "served-locale", rules: servedLocaleRules, recommended: servedLocaleRecommended },
|
|
81
|
-
{ law: "drawing", rules: drawingRules, recommended: drawingRecommended },
|
|
82
|
-
{ law: "size-and-state-budget", rules: sizeBudgetRules, recommended: sizeBudgetRecommended },
|
|
83
|
-
{ law: "size-growth", rules: sizeGrowthRules, recommended: sizeGrowthRecommended },
|
|
84
|
-
{ law: "surface-rules", rules: surfaceRules, recommended: surfaceRulesRecommended },
|
|
85
|
-
{ law: "the-split", rules: splitRules, recommended: splitRecommended },
|
|
86
|
-
{ law: "tokens", rules: tokensRules, recommended: tokensRecommended },
|
|
87
|
-
{ law: "translation", rules: translationRules, recommended: translationRecommended },
|
|
88
|
-
{ law: "transport", rules: transportRules, recommended: transportRecommended },
|
|
89
|
-
{ law: "type-safety", rules: typeSafetyRules, recommended: typeSafetyRecommended },
|
|
90
|
-
{ law: "sonar-parity", rules: sonarParityRules, recommended: sonarParityRecommended },
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
/** Every gathered law. */
|
|
94
|
-
export const lawOwners = CONTRIBUTIONS.map((entry) => entry.law)
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Which law declares each rule.
|
|
98
|
-
*
|
|
99
|
-
* Exported because a failing rule is a question about a LAW, and the shortest path from a build log
|
|
100
|
-
* to the document that explains it is this map. It is also what the twin test walks to prove no two
|
|
101
|
-
* laws claim one name.
|
|
102
|
-
*/
|
|
103
|
-
export const ruleOwners = Object.fromEntries(
|
|
104
|
-
CONTRIBUTIONS.flatMap((entry) => Object.keys(entry.rules).map((name) => [name, entry.law])),
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Every (law, rule name) pair as DECLARED, before any collapsing.
|
|
109
|
-
*
|
|
110
|
-
* `ruleOwners` above and `rules` below are both built with `Object.fromEntries`, which silently
|
|
111
|
-
* keeps the last writer when two laws declare one name. A guard that walks either of them cannot
|
|
112
|
-
* see a collision - the duplicate is gone before it looks, so every name appears exactly once BY
|
|
113
|
-
* CONSTRUCTION and the check passes forever while being blind.
|
|
114
|
-
*
|
|
115
|
-
* That is not hypothetical. The back-end twin had this exact shape, three rules were declared by two
|
|
116
|
-
* laws each, one copy was discarded on import, and its guard reported green throughout. This axis
|
|
117
|
-
* has no collision today; publishing the raw declarations is what keeps that a FACT rather than an
|
|
118
|
-
* assumption nobody can test.
|
|
119
|
-
*/
|
|
120
|
-
export const ruleDeclarations = CONTRIBUTIONS.flatMap((entry) =>
|
|
121
|
-
Object.keys(entry.rules).map((name) => ({ law: entry.law, name })),
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
/** Every rule this canon publishes, keyed by its published name. */
|
|
125
|
-
export const rules = Object.fromEntries(
|
|
126
|
-
CONTRIBUTIONS.flatMap((entry) => Object.entries(entry.rules)),
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* The levels this canon asks for, as the plugin's own opinion.
|
|
131
|
-
*
|
|
132
|
-
* Every FE canon rule is an error. Existing debt is fixed before adoption; lowering architecture
|
|
133
|
-
* to warn teaches every later author that the boundary is optional and is not a supported rollout.
|
|
134
|
-
*/
|
|
135
|
-
export const recommended = Object.fromEntries(
|
|
136
|
-
CONTRIBUTIONS.flatMap((entry) => Object.entries(entry.recommended)),
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
/** Flat-config options that make inline directives ineffective rather than merely forbidden. */
|
|
140
|
-
export { linterOptions } from "./lib/config.mjs"
|
|
141
|
-
export { loadHfs } from "./lib/hfs.mjs"
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* The Vietnamese why of each rule that carries a catalogue code: `{ code, vi, fixVi }`.
|
|
145
|
-
*
|
|
146
|
-
* Read by the harness, which quotes `vi` to the owner and files the finding under `code`.
|
|
147
|
-
*/
|
|
148
|
-
export { why }
|
|
149
|
-
|
|
150
|
-
/** The plugin object, shaped the way a flat config expects it. */
|
|
151
|
-
const plugin = {
|
|
152
|
-
meta: { name: "eslint-plugin-starci-fe" },
|
|
153
|
-
rules,
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export default plugin
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* The React Hooks plugin, from wherever the consuming repository installed it.
|
|
160
|
-
*
|
|
161
|
-
* A peer dependency is resolved from the package that asks for it, and a package linked by path
|
|
162
|
-
* (`file:`) is resolved from its REAL location - which has no `node_modules` of the repository that
|
|
163
|
-
* linked it. So the plugin is looked up from this package first and from the working directory
|
|
164
|
-
* second; a repository that installed `eslint-plugin-react-hooks` beside its `eslint.config.mjs`
|
|
165
|
-
* is found either way, and one that did not gets a message naming the install rather than a
|
|
166
|
-
* resolver stack trace.
|
|
167
|
-
*/
|
|
168
|
-
const loadReactHooks = () => {
|
|
169
|
-
for (const base of [import.meta.url, pathToFileURL(join(process.cwd(), "noop.js")).href]) {
|
|
170
|
-
try {
|
|
171
|
-
return createRequire(base)("eslint-plugin-react-hooks")
|
|
172
|
-
} catch {
|
|
173
|
-
// try the next place
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
throw new Error("starciFeConfig needs eslint-plugin-react-hooks 7 or newer: npm i -D eslint-plugin-react-hooks")
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/** The typescript-eslint plugin whose type-aware rules the factory borrows (Sonar S1874, S6551 and S7503); a dependency of this package. */
|
|
180
|
-
const loadTsPlugin = () => createRequire(import.meta.url)("@typescript-eslint/eslint-plugin")
|
|
181
|
-
|
|
182
|
-
/** The Sonar way plugins (scripts/lib/sonar-way.mjs); dependencies of this package, pinned exactly. */
|
|
183
|
-
const loadSonarPlugins = () => {
|
|
184
|
-
const require = createRequire(import.meta.url)
|
|
185
|
-
const unicorn = require("eslint-plugin-unicorn")
|
|
186
|
-
return { sonarjs: require("eslint-plugin-sonarjs"), unicorn: unicorn.default ?? unicorn }
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* The React Hooks rules of a plugin, all at error.
|
|
191
|
-
*
|
|
192
|
-
* The plugin's own recommended set is the definition of "the hooks rules" - it carries the classic
|
|
193
|
-
* pair and the compiler-derived ones (`set-state-in-effect`, `refs`, `purity`, `immutability`) - and
|
|
194
|
-
* it ships several of them at `warn`. A warning is the level a team learns to scroll past, so the
|
|
195
|
-
* canon states every one as `error`. Four rules are named on purpose: a plugin too old to carry them
|
|
196
|
-
* would otherwise be adopted silently with the rules that catch effect-driven state and ref reads
|
|
197
|
-
* during render missing.
|
|
198
|
-
*
|
|
199
|
-
* @param {object} reactHooks - The plugin object.
|
|
200
|
-
* @returns {Record<string, "error">} Every recommended hooks rule at error.
|
|
201
|
-
*/
|
|
202
|
-
export const reactHooksRules = (reactHooks) => {
|
|
203
|
-
const configured = reactHooks.configs?.flat?.recommended?.rules ?? {}
|
|
204
|
-
for (const required of [
|
|
205
|
-
"react-hooks/set-state-in-effect",
|
|
206
|
-
"react-hooks/refs",
|
|
207
|
-
"react-hooks/rules-of-hooks",
|
|
208
|
-
"react-hooks/exhaustive-deps",
|
|
209
|
-
]) {
|
|
210
|
-
if (!(required in configured)) {
|
|
211
|
-
throw new Error(`eslint-plugin-react-hooks does not carry ${required}; install version 7 or newer`)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return Object.fromEntries(Object.keys(configured).map((name) => [name, "error"]))
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* The universal portion of a frontend's flat config, composed with its selected Grammar-family factory.
|
|
219
|
-
*
|
|
220
|
-
* It owns nothing the repository could choose: which files are linted comes from the HFS profile (every app's and every
|
|
221
|
-
* workspace package's `src/`), not which rules are on, not their level, not whether an inline comment may
|
|
222
|
-
* switch one off. NO RULE IS OFF - the factory throws rather than emit a block in which a published rule is missing or
|
|
223
|
-
* below error, so a rule added to this package reaches every repository or the build of this package fails.
|
|
224
|
-
*
|
|
225
|
-
* @param {{ hfs: object }} input - The HFS view of the repository (`loadHfs(import.meta.url)`).
|
|
226
|
-
* @returns {object[]} The flat config: ignores and the typed source block.
|
|
227
|
-
*/
|
|
228
|
-
export const starciFeConfig = ({ hfs } = {}) => {
|
|
229
|
-
const published = Object.keys(rules).map((name) => `starci-fe/${name}`)
|
|
230
|
-
const notRequested = published.filter((name) => recommended[name] !== "error")
|
|
231
|
-
if (notRequested.length > 0) {
|
|
232
|
-
throw new Error(`starciFeConfig: these published rules are not requested at error: ${notRequested.join(", ")}`)
|
|
233
|
-
}
|
|
234
|
-
const reactHooks = loadReactHooks()
|
|
235
|
-
return buildFeConfig({
|
|
236
|
-
hfs,
|
|
237
|
-
plugin,
|
|
238
|
-
reactHooks,
|
|
239
|
-
tsPlugin: loadTsPlugin(),
|
|
240
|
-
sonar: loadSonarPlugins(),
|
|
241
|
-
source: { ...recommended, ...reactHooksRules(reactHooks) },
|
|
242
|
-
})
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* The flat config of a TypeScript front-end tree that is no HFS app (a runtime package, the harness UI):
|
|
247
|
-
* `export default starciPlainSourceConfig({ root: import.meta.dirname })`. It lints the tree's `src/` with types, with the inline-directive fence, the
|
|
248
|
-
* React Hooks rules, the type-aware rules and the Sonar way rules, and with every canon rule that needs no slot view, all at `error`.
|
|
249
|
-
*
|
|
250
|
-
* @param {{ root: string }} input - The absolute directory holding the tree's `tsconfig.json` and `src/`.
|
|
251
|
-
* @returns {object[]} The flat config: ignores, the typed source block and the Sonar way block.
|
|
252
|
-
*/
|
|
253
|
-
export const starciPlainSourceConfig = ({ root } = {}) => {
|
|
254
|
-
const reactHooks = loadReactHooks()
|
|
255
|
-
return buildPlainSourceConfig({ root, plugin, hooks: reactHooksRules(reactHooks), reactHooks, tsPlugin: loadTsPlugin(), sonar: loadSonarPlugins() })
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* The flat config of a TypeScript front-end tree that adopts the typed half of the canon first: the inline-directive fence, typed linting, the
|
|
260
|
-
* React Hooks rules, the type-aware typescript-eslint rules and the Sonar way rules over the tree's `src/`, all at `error`. It is
|
|
261
|
-
* `starciPlainSourceConfig` without the canon's own product-structure rules (`SLOT_FREE_RULES`: jsdoc on exports, arrow exports, English-only
|
|
262
|
-
* source, grammar controls, ...), for a tree that has not been brought to them yet; a tree that has uses `starciPlainSourceConfig`.
|
|
263
|
-
*
|
|
264
|
-
* @param {{ root: string }} input - The absolute directory holding the tree's `tsconfig.json` and `src/`.
|
|
265
|
-
* @returns {object[]} The flat config: ignores, the typed source block and the Sonar way block.
|
|
266
|
-
*/
|
|
267
|
-
export const starciTypedSourceConfig = ({ root } = {}) => {
|
|
268
|
-
const reactHooks = loadReactHooks()
|
|
269
|
-
return buildPlainSourceConfig({ root, plugin, hooks: reactHooksRules(reactHooks), reactHooks, tsPlugin: loadTsPlugin(), sonar: loadSonarPlugins(), canon: false })
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// A21 append-only registration: keep the already exported plugin and recommendation live.
|
|
273
|
-
import { recommended as functionBudgetRecommended, rules as functionBudgetRules } from "./function-budget.mjs"
|
|
274
|
-
CONTRIBUTIONS.push({ law: "function-budget", rules: functionBudgetRules, recommended: functionBudgetRecommended })
|
|
275
|
-
lawOwners.push("function-budget")
|
|
276
|
-
ruleDeclarations.push(...Object.keys(functionBudgetRules).map((name) => ({ law: "function-budget", name })))
|
|
277
|
-
Object.assign(ruleOwners, Object.fromEntries(Object.keys(functionBudgetRules).map((name) => [name, "function-budget"])))
|
|
278
|
-
Object.assign(rules, functionBudgetRules)
|
|
279
|
-
Object.assign(recommended, functionBudgetRecommended)
|
|
280
|
-
// A14 modules are also forwarded by the existing hooks-folder and transport contributions so
|
|
281
|
-
// source-only catalog discovery can find them. The appended registration gives each module
|
|
282
|
-
// ownership of those same rule instances without declaring either rule twice.
|
|
283
|
-
import { recommended as swrFamilyRecommended, rules as swrFamilyRules } from "./swr-family.mjs"
|
|
284
|
-
import { recommended as callbackShapeRecommended, rules as callbackShapeRules } from "./callback-shape.mjs"
|
|
285
|
-
|
|
286
|
-
const A14_CONTRIBUTIONS = [
|
|
287
|
-
{ law: "swr-family", rules: swrFamilyRules, recommended: swrFamilyRecommended },
|
|
288
|
-
{ law: "callback-shape", rules: callbackShapeRules, recommended: callbackShapeRecommended },
|
|
289
|
-
]
|
|
290
|
-
|
|
291
|
-
for (const addition of A14_CONTRIBUTIONS) {
|
|
292
|
-
const names = new Set(Object.keys(addition.rules))
|
|
293
|
-
for (const contribution of CONTRIBUTIONS) {
|
|
294
|
-
for (const name of names) {
|
|
295
|
-
if (name in contribution.rules && contribution.rules[name] !== addition.rules[name]) {
|
|
296
|
-
throw new Error("A14 registration: two laws publish different rules named " + name)
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
// Replace contribution views only; the law module's exported maps retain their forwarding
|
|
300
|
-
// entries for the source-only catalog reader.
|
|
301
|
-
contribution.rules = Object.fromEntries(Object.entries(contribution.rules).filter(([name]) => !names.has(name)))
|
|
302
|
-
contribution.recommended = Object.fromEntries(
|
|
303
|
-
Object.entries(contribution.recommended).filter(([name]) => !names.has(name.replace(/^starci-fe\//, ""))),
|
|
304
|
-
)
|
|
305
|
-
}
|
|
306
|
-
CONTRIBUTIONS.push(addition)
|
|
307
|
-
lawOwners.push(addition.law)
|
|
308
|
-
for (const name of names) {
|
|
309
|
-
const declarations = ruleDeclarations.filter((entry) => entry.name === name)
|
|
310
|
-
if (declarations.length > 1) throw new Error("A14 registration: duplicate declarations for " + name)
|
|
311
|
-
if (declarations.length === 1) declarations[0].law = addition.law
|
|
312
|
-
else ruleDeclarations.push({ law: addition.law, name })
|
|
313
|
-
ruleOwners[name] = addition.law
|
|
314
|
-
}
|
|
315
|
-
Object.assign(rules, addition.rules)
|
|
316
|
-
Object.assign(recommended, addition.recommended)
|
|
317
|
-
}
|
package/lib/ast.mjs
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Small AST readers shared by the laws that look at JSX attributes, calls and effect callbacks.
|
|
3
|
-
*
|
|
4
|
-
* ONE PLACE, because each reader answers "what does this node say" and two copies answer it
|
|
5
|
-
* slightly differently: a rule that treats `` `/a` `` as a string and a sibling that does not
|
|
6
|
-
* disagree about which hrefs are hardcoded.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** The JSX attribute called `name` on an opening element, or null. */
|
|
10
|
-
export const attribute = (opening, name) =>
|
|
11
|
-
opening.attributes.find(
|
|
12
|
-
(entry) => entry.type === "JSXAttribute" && entry.name.type === "JSXIdentifier" && entry.name.name === name,
|
|
13
|
-
) ?? null
|
|
14
|
-
|
|
15
|
-
/** The expression an attribute carries: a literal, or the inside of `{...}`; null for a bare attribute. */
|
|
16
|
-
export const attributeValue = (entry) => {
|
|
17
|
-
if (!entry || !entry.value) return null
|
|
18
|
-
return entry.value.type === "JSXExpressionContainer" ? entry.value.expression : entry.value
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** The text of a string literal or of a template with no substitution; null for anything else. */
|
|
22
|
-
export const stringOf = (node) => {
|
|
23
|
-
if (!node) return null
|
|
24
|
-
if (node.type === "Literal" && typeof node.value === "string") return node.value
|
|
25
|
-
if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0]?.value.cooked ?? ""
|
|
26
|
-
return null
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** The leading text of a string literal or template (up to the first substitution); null for anything else. */
|
|
30
|
-
export const leadingTextOf = (node) => {
|
|
31
|
-
if (!node) return null
|
|
32
|
-
if (node.type === "TemplateLiteral") return node.quasis[0]?.value.cooked ?? ""
|
|
33
|
-
return stringOf(node)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** `a.b.c` for a chain of plain member accesses ending in an identifier; null for anything computed. */
|
|
37
|
-
export const calleeName = (node) => {
|
|
38
|
-
if (!node) return null
|
|
39
|
-
if (node.type === "Identifier") return node.name
|
|
40
|
-
if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
|
|
41
|
-
const head = calleeName(node.object)
|
|
42
|
-
return head === null ? null : `${head}.${node.property.name}`
|
|
43
|
-
}
|
|
44
|
-
return null
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** The name of a JSX element for a plain tag or a component; null for a member or namespaced name. */
|
|
48
|
-
export const tagName = (opening) => (opening.name.type === "JSXIdentifier" ? opening.name.name : null)
|
|
49
|
-
|
|
50
|
-
/** True for a `className` / `class` JSX attribute. */
|
|
51
|
-
export const isClassAttribute = (node) =>
|
|
52
|
-
node.type === "JSXAttribute" && node.name && (node.name.name === "className" || node.name.name === "class")
|
|
53
|
-
|
|
54
|
-
/** Static string carried by a JSX attribute or by a module constant holding a class string. */
|
|
55
|
-
export const staticText = (value) => {
|
|
56
|
-
if (!value) return null
|
|
57
|
-
if (value.type === "Literal" && typeof value.value === "string") return value.value
|
|
58
|
-
if (value.type === "TemplateLiteral" && value.expressions.length === 0) {
|
|
59
|
-
return value.quasis.map((quasi) => quasi.value.cooked).join(" ")
|
|
60
|
-
}
|
|
61
|
-
if (value.type === "JSXExpressionContainer") return staticText(value.expression)
|
|
62
|
-
return null
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** True for a function expression or an arrow function. */
|
|
66
|
-
export const isFunction = (node) => node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression"
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Every node under `root`, depth first, through the visitor keys of the parser in use.
|
|
70
|
-
* `enter` may return `false` to skip a node's children.
|
|
71
|
-
*/
|
|
72
|
-
export const walk = (sourceCode, root, enter) => {
|
|
73
|
-
const keys = sourceCode.visitorKeys
|
|
74
|
-
const visit = (node) => {
|
|
75
|
-
if (enter(node) === false) return
|
|
76
|
-
for (const key of keys[node.type] ?? []) {
|
|
77
|
-
const child = node[key]
|
|
78
|
-
if (Array.isArray(child)) {
|
|
79
|
-
for (const item of child) if (item && typeof item.type === "string") visit(item)
|
|
80
|
-
} else if (child && typeof child.type === "string") {
|
|
81
|
-
visit(child)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
visit(root)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** True when an identifier called `name` appears anywhere under `root`. */
|
|
89
|
-
export const mentions = (sourceCode, root, name) => {
|
|
90
|
-
let found = false
|
|
91
|
-
walk(sourceCode, root, (node) => {
|
|
92
|
-
if (found) return false
|
|
93
|
-
if (node.type === "Identifier" && node.name === name) found = true
|
|
94
|
-
return undefined
|
|
95
|
-
})
|
|
96
|
-
return found
|
|
97
|
-
}
|
package/lib/bindings.mjs
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bindings of the front-end canon: what an identifier resolves to through the scope manager, which module an import
|
|
3
|
-
* comes from, which function a name stands for, and a stable KEY for an expression that names a place (a variable, a
|
|
4
|
-
* member path, `this.x`). A rule that must decide "is this the same handle / target / listener" compares keys, never
|
|
5
|
-
* spellings: two `id` variables in different scopes have different keys, `timerRef.current` has one key wherever it is
|
|
6
|
-
* written.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { stringOf } from "./ast.mjs"
|
|
10
|
-
|
|
11
|
-
/** The globals that all name the browser's global object. */
|
|
12
|
-
const GLOBAL_OBJECT = new Set(["window", "globalThis", "self"])
|
|
13
|
-
|
|
14
|
-
/** Wrappers that change a node's type, not its identity. */
|
|
15
|
-
const TRANSPARENT = new Set(["TSAsExpression", "TSNonNullExpression", "TSSatisfiesExpression", "TSTypeAssertion", "ChainExpression", "ParenthesizedExpression"])
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* The node without the wrappers that only change its type (`x as T`, `x!`, `x?.y`).
|
|
19
|
-
*
|
|
20
|
-
* @param {object} node - An ESTree node.
|
|
21
|
-
* @returns {object} The inner node.
|
|
22
|
-
*/
|
|
23
|
-
export const unwrap = (node) => {
|
|
24
|
-
let current = node
|
|
25
|
-
while (current && TRANSPARENT.has(current.type)) current = current.expression
|
|
26
|
-
return current
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* The node's parent with the type-only wrappers skipped upward.
|
|
31
|
-
*
|
|
32
|
-
* @param {object} node - An ESTree node.
|
|
33
|
-
* @returns {object|null} The first parent that is not a type-only wrapper.
|
|
34
|
-
*/
|
|
35
|
-
export const parentOf = (node) => {
|
|
36
|
-
let current = node.parent
|
|
37
|
-
while (current && TRANSPARENT.has(current.type)) current = current.parent
|
|
38
|
-
return current ?? null
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** True for a function expression, an arrow function or a function declaration. */
|
|
42
|
-
export const isFn = (node) =>
|
|
43
|
-
Boolean(node) && (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration")
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* The scope-manager variable an identifier resolves to, or null for a global.
|
|
47
|
-
*
|
|
48
|
-
* @param {object} context - The ESLint rule context.
|
|
49
|
-
* @param {object} identifier - An ESTree identifier.
|
|
50
|
-
* @returns {object|null} The variable.
|
|
51
|
-
*/
|
|
52
|
-
export const variableOf = (context, identifier) => {
|
|
53
|
-
for (let scope = context.sourceCode.getScope(identifier); scope; scope = scope.upper) {
|
|
54
|
-
const variable = scope.set.get(identifier.name)
|
|
55
|
-
if (variable) return variable
|
|
56
|
-
}
|
|
57
|
-
return null
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Where an identifier was imported from.
|
|
62
|
-
*
|
|
63
|
-
* @param {object} context - The ESLint rule context.
|
|
64
|
-
* @param {object} identifier - An ESTree identifier.
|
|
65
|
-
* @returns {{ source: string, imported: string|null }|null} The module specifier and the imported name (`null` for a default or namespace import), or null when the identifier is not an import.
|
|
66
|
-
*/
|
|
67
|
-
export const importOf = (context, identifier) => {
|
|
68
|
-
const variable = variableOf(context, identifier)
|
|
69
|
-
const definition = variable?.defs.find((entry) => entry.type === "ImportBinding")
|
|
70
|
-
if (!definition) return null
|
|
71
|
-
const specifier = definition.node
|
|
72
|
-
const source = String(definition.parent.source.value)
|
|
73
|
-
if (specifier.type === "ImportSpecifier") return { source, imported: specifier.imported.name ?? String(specifier.imported.value) }
|
|
74
|
-
return { source, imported: null }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** The property path a destructured binding selects from its initializer; null for an unselected or rest binding. */
|
|
78
|
-
export const patternPath = (pattern, name) => {
|
|
79
|
-
if (pattern.type === "Identifier") return pattern.name === name ? [] : null
|
|
80
|
-
if (pattern.type === "AssignmentPattern") return patternPath(pattern.left, name)
|
|
81
|
-
if (pattern.type !== "ObjectPattern") return null
|
|
82
|
-
for (const property of pattern.properties) {
|
|
83
|
-
if (property.type !== "Property") continue
|
|
84
|
-
const nested = patternPath(property.value, name)
|
|
85
|
-
if (nested !== null) return [property.computed ? stringOf(property.key) : property.key.name ?? property.key.value, ...nested]
|
|
86
|
-
}
|
|
87
|
-
return null
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* The function an expression stands for: the function itself, or the function a same-file variable or declaration holds.
|
|
92
|
-
*
|
|
93
|
-
* @param {object} context - The ESLint rule context.
|
|
94
|
-
* @param {object} node - An ESTree expression.
|
|
95
|
-
* @returns {object|null} The function node, or null when it cannot be resolved in this file.
|
|
96
|
-
*/
|
|
97
|
-
export const functionOf = (context, node) => {
|
|
98
|
-
const inner = unwrap(node)
|
|
99
|
-
if (!inner) return null
|
|
100
|
-
if (isFn(inner)) return inner
|
|
101
|
-
if (inner.type !== "Identifier") return null
|
|
102
|
-
const variable = variableOf(context, inner)
|
|
103
|
-
for (const definition of variable?.defs ?? []) {
|
|
104
|
-
if (definition.type === "FunctionName" && definition.node.type === "FunctionDeclaration") return definition.node
|
|
105
|
-
if (definition.type === "Variable" && definition.node.id.type === "Identifier") {
|
|
106
|
-
const init = unwrap(definition.node.init)
|
|
107
|
-
if (isFn(init)) return init
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return null
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** A stable numeric id per scope-manager variable, so keys compare by binding and not by spelling. */
|
|
114
|
-
const VARIABLE_IDS = new WeakMap()
|
|
115
|
-
let nextVariableId = 1
|
|
116
|
-
const idOf = (variable) => {
|
|
117
|
-
if (!VARIABLE_IDS.has(variable)) VARIABLE_IDS.set(variable, nextVariableId++)
|
|
118
|
-
return VARIABLE_IDS.get(variable)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* The key of an expression that names a place: `v12` for a variable, `v12.current` for a member path, `this.timer` for
|
|
123
|
-
* a member of `this`, `g:window` for the browser's global object under any of its names. Anything else has no key.
|
|
124
|
-
*
|
|
125
|
-
* @param {object} context - The ESLint rule context.
|
|
126
|
-
* @param {object} node - An ESTree expression.
|
|
127
|
-
* @returns {string|null} The key.
|
|
128
|
-
*/
|
|
129
|
-
export const keyOf = (context, node) => {
|
|
130
|
-
const inner = unwrap(node)
|
|
131
|
-
if (!inner) return null
|
|
132
|
-
if (inner.type === "Identifier") {
|
|
133
|
-
const variable = variableOf(context, inner)
|
|
134
|
-
if (variable && variable.defs.length > 0) return `v${idOf(variable)}`
|
|
135
|
-
return GLOBAL_OBJECT.has(inner.name) ? "g:window" : `g:${inner.name}`
|
|
136
|
-
}
|
|
137
|
-
if (inner.type === "ThisExpression") return "this"
|
|
138
|
-
if (inner.type === "MemberExpression") {
|
|
139
|
-
const head = keyOf(context, inner.object)
|
|
140
|
-
if (head === null) return null
|
|
141
|
-
if (!inner.computed && inner.property.type === "Identifier") return `${head}.${inner.property.name}`
|
|
142
|
-
if (inner.computed && inner.property.type === "Literal") return `${head}[${String(inner.property.value)}]`
|
|
143
|
-
return null
|
|
144
|
-
}
|
|
145
|
-
return null
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* The receiver and the member name of a call written as `receiver.name(...)`, or the bare name of `name(...)`.
|
|
150
|
-
*
|
|
151
|
-
* @param {object} call - An ESTree call expression.
|
|
152
|
-
* @returns {{ receiver: object|null, name: string }|null} The parts, or null for a computed or exotic callee.
|
|
153
|
-
*/
|
|
154
|
-
export const calleeParts = (call) => {
|
|
155
|
-
const callee = unwrap(call.callee)
|
|
156
|
-
if (callee.type === "Identifier") return { receiver: null, name: callee.name }
|
|
157
|
-
if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") return { receiver: callee.object, name: callee.property.name }
|
|
158
|
-
return null
|
|
159
|
-
}
|
package/lib/class-text.mjs
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/** Shared static class-string readers for token discipline and Grammar scale checks. */
|
|
2
|
-
import { isClassAttribute, staticText } from "./ast.mjs"
|
|
3
|
-
import { isProductSource } from "./scope.mjs"
|
|
4
|
-
|
|
5
|
-
/** Static strings in JSX, constants and arrays, preserving the authored class order. */
|
|
6
|
-
const classText = (value) => {
|
|
7
|
-
if (value?.type === "JSXExpressionContainer") return classText(value.expression)
|
|
8
|
-
if (value?.type === "ArrayExpression") {
|
|
9
|
-
const members = value.elements.map((element) => classText(element)).filter(Boolean)
|
|
10
|
-
return members.length > 0 ? members.join(" ") : null
|
|
11
|
-
}
|
|
12
|
-
return staticText(value)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Visit class strings in product JSX, declarations and named class arrays. */
|
|
16
|
-
export const classTextVisitors = (context, report) => {
|
|
17
|
-
if (!isProductSource(context)) return {}
|
|
18
|
-
return {
|
|
19
|
-
JSXAttribute(node) {
|
|
20
|
-
if (isClassAttribute(node)) report(node, classText(node.value))
|
|
21
|
-
},
|
|
22
|
-
VariableDeclarator(node) {
|
|
23
|
-
report(node, classText(node.init))
|
|
24
|
-
},
|
|
25
|
-
Property(node) {
|
|
26
|
-
if (!node.computed && node.key.type === "Identifier" && node.key.name === "classes") report(node, classText(node.value))
|
|
27
|
-
},
|
|
28
|
-
}
|
|
29
|
-
}
|