@invariant-ui/cli 0.0.0-stage → 0.1.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 (196) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +248 -2
  3. package/dist/agent-kit/apply.js +232 -0
  4. package/dist/agent-kit/guidance.js +236 -0
  5. package/dist/agent-kit/index.js +5 -0
  6. package/dist/agent-kit/integrations.js +121 -0
  7. package/dist/agent-kit/plugin.js +115 -0
  8. package/dist/artifact-generator/index.js +75 -0
  9. package/dist/browser-verify/driver.js +2 -0
  10. package/dist/browser-verify/index.js +4 -0
  11. package/dist/browser-verify/playwright.js +82 -0
  12. package/dist/browser-verify/runner.js +175 -0
  13. package/dist/browser-verify/spec.js +103 -0
  14. package/dist/cli/args.js +77 -0
  15. package/dist/cli/baseline.js +64 -0
  16. package/dist/cli/cli.js +121 -0
  17. package/dist/cli/commands-browser.js +104 -0
  18. package/dist/cli/commands-discover.js +312 -0
  19. package/dist/cli/commands-enforcement.js +35 -0
  20. package/dist/cli/commands-governance.js +179 -0
  21. package/dist/cli/commands-hook.js +269 -0
  22. package/dist/cli/commands-patterns.js +324 -0
  23. package/dist/cli/commands-setup.js +485 -0
  24. package/dist/cli/commands-validate.js +157 -0
  25. package/dist/cli/commands.js +517 -0
  26. package/dist/cli/config.js +197 -0
  27. package/dist/cli/diff.js +66 -0
  28. package/dist/cli/enforcement-log.js +83 -0
  29. package/dist/cli/enforcement-report.js +96 -0
  30. package/dist/cli/errors.js +10 -0
  31. package/dist/cli/format.js +22 -0
  32. package/dist/cli/index.js +5 -0
  33. package/dist/cli/io.js +2 -0
  34. package/dist/cli/main.js +31 -0
  35. package/dist/cli/pipeline.js +262 -0
  36. package/dist/cli/publish.js +77 -0
  37. package/dist/cli/report.js +127 -0
  38. package/dist/cli/starter-rules.js +57 -0
  39. package/dist/cli/store.js +60 -0
  40. package/dist/cli/version.js +5 -0
  41. package/dist/cli/watch.js +68 -0
  42. package/dist/cli/workflow.js +52 -0
  43. package/dist/compiler-angular/adapter.js +115 -0
  44. package/dist/compiler-angular/analyze.js +222 -0
  45. package/dist/compiler-angular/classes.js +446 -0
  46. package/dist/compiler-angular/declared.js +285 -0
  47. package/dist/compiler-angular/emit.js +183 -0
  48. package/dist/compiler-angular/index.js +4 -0
  49. package/dist/compiler-angular/template.js +142 -0
  50. package/dist/compiler-core/adapter.js +2 -0
  51. package/dist/compiler-core/adapters/inventory.js +312 -0
  52. package/dist/compiler-core/compile.js +148 -0
  53. package/dist/compiler-core/component-facts.js +123 -0
  54. package/dist/compiler-core/discovery/docs.js +66 -0
  55. package/dist/compiler-core/discovery/entries.js +270 -0
  56. package/dist/compiler-core/discovery/exports.js +207 -0
  57. package/dist/compiler-core/discovery/graph.js +59 -0
  58. package/dist/compiler-core/discovery/index.js +103 -0
  59. package/dist/compiler-core/discovery/packages.js +148 -0
  60. package/dist/compiler-core/discovery/schema.js +144 -0
  61. package/dist/compiler-core/discovery/storybook.js +232 -0
  62. package/dist/compiler-core/discovery/support.js +82 -0
  63. package/dist/compiler-core/discovery/tokens.js +186 -0
  64. package/dist/compiler-core/discovery/tooling.js +146 -0
  65. package/dist/compiler-core/fs.js +89 -0
  66. package/dist/compiler-core/glob.js +3 -0
  67. package/dist/compiler-core/index.js +16 -0
  68. package/dist/compiler-core/ir-builder.js +68 -0
  69. package/dist/compiler-core/json-schema.js +11 -0
  70. package/dist/compiler-core/testing/index.js +81 -0
  71. package/dist/compiler-core/ts/ast.js +166 -0
  72. package/dist/compiler-core/ts/members.js +163 -0
  73. package/dist/compiler-core/ts/program.js +81 -0
  74. package/dist/compiler-core/ts/static-eval.js +145 -0
  75. package/dist/compiler-core/ts/tsconfig-paths.js +60 -0
  76. package/dist/compiler-core/walk.js +46 -0
  77. package/dist/compiler-ir/index.js +4 -0
  78. package/dist/compiler-ir/ir.js +67 -0
  79. package/dist/compiler-ir/json-schema.js +6 -0
  80. package/dist/compiler-ir/validate.js +116 -0
  81. package/dist/compiler-react/adapter.js +147 -0
  82. package/dist/compiler-react/analyze.js +199 -0
  83. package/dist/compiler-react/ast.js +105 -0
  84. package/dist/compiler-react/behavior.js +75 -0
  85. package/dist/compiler-react/detect.js +558 -0
  86. package/dist/compiler-react/emit.js +188 -0
  87. package/dist/compiler-react/index.js +2 -0
  88. package/dist/compiler-react/program.js +58 -0
  89. package/dist/compiler-react/props.js +157 -0
  90. package/dist/compiler-react/styling.js +138 -0
  91. package/dist/compiler-react/usage.js +116 -0
  92. package/dist/compiler-react/variants.js +111 -0
  93. package/dist/compiler-storybook/adapter.js +253 -0
  94. package/dist/compiler-storybook/csf.js +521 -0
  95. package/dist/compiler-storybook/emit.js +202 -0
  96. package/dist/compiler-storybook/index-json.js +41 -0
  97. package/dist/compiler-storybook/index.js +5 -0
  98. package/dist/compiler-storybook/mdx.js +120 -0
  99. package/dist/compiler-storybook/static-eval.js +145 -0
  100. package/dist/compiler-tokens/adapter.js +158 -0
  101. package/dist/compiler-tokens/css.js +59 -0
  102. package/dist/compiler-tokens/index.js +5 -0
  103. package/dist/compiler-tokens/json-lines.js +106 -0
  104. package/dist/compiler-tokens/json-tokens.js +52 -0
  105. package/dist/compiler-vue/adapter.js +107 -0
  106. package/dist/compiler-vue/analyze.js +344 -0
  107. package/dist/compiler-vue/declared.js +343 -0
  108. package/dist/compiler-vue/emit.js +129 -0
  109. package/dist/compiler-vue/index.js +5 -0
  110. package/dist/compiler-vue/script.js +472 -0
  111. package/dist/compiler-vue/sfc.js +44 -0
  112. package/dist/compiler-vue/template.js +73 -0
  113. package/dist/contract/archetype-checks.js +153 -0
  114. package/dist/contract/components.js +102 -0
  115. package/dist/contract/conflicts.js +39 -0
  116. package/dist/contract/contract.js +46 -0
  117. package/dist/contract/evidence.js +84 -0
  118. package/dist/contract/fact-path.js +39 -0
  119. package/dist/contract/facts.js +62 -0
  120. package/dist/contract/index.js +16 -0
  121. package/dist/contract/integrity.js +21 -0
  122. package/dist/contract/json-schema.js +11 -0
  123. package/dist/contract/knowledge.js +95 -0
  124. package/dist/contract/pattern-checks.js +254 -0
  125. package/dist/contract/patterns.js +225 -0
  126. package/dist/contract/primitives.js +81 -0
  127. package/dist/contract/sources.js +30 -0
  128. package/dist/contract/tokens.js +41 -0
  129. package/dist/contract/validate.js +253 -0
  130. package/dist/contract-query/archetype.js +188 -0
  131. package/dist/contract-query/index.js +6 -0
  132. package/dist/contract-query/patterns.js +298 -0
  133. package/dist/contract-query/scan.js +164 -0
  134. package/dist/contract-query/search.js +132 -0
  135. package/dist/contract-query/system.js +148 -0
  136. package/dist/contract-query/validate-code.js +239 -0
  137. package/dist/contract-query/validate-usage.js +150 -0
  138. package/dist/contract-query/views.js +288 -0
  139. package/dist/governance/decisions.js +229 -0
  140. package/dist/governance/index.js +5 -0
  141. package/dist/governance/patterns-file.js +156 -0
  142. package/dist/governance/patterns.js +510 -0
  143. package/dist/governance/policies.js +148 -0
  144. package/dist/mcp/index.js +7 -0
  145. package/dist/mcp/loader.js +70 -0
  146. package/dist/mcp/protocol.js +27 -0
  147. package/dist/mcp/resources.js +138 -0
  148. package/dist/mcp/server.js +177 -0
  149. package/dist/mcp/stdio.js +28 -0
  150. package/dist/mcp/tools.js +267 -0
  151. package/dist/reconciliation/code.js +62 -0
  152. package/dist/reconciliation/components.js +401 -0
  153. package/dist/reconciliation/conflicts.js +126 -0
  154. package/dist/reconciliation/facts.js +41 -0
  155. package/dist/reconciliation/index.js +7 -0
  156. package/dist/reconciliation/reconcile.js +134 -0
  157. package/dist/reconciliation/resolve.js +158 -0
  158. package/dist/reconciliation/review.js +81 -0
  159. package/dist/reconciliation/storybook.js +370 -0
  160. package/dist/reconciliation/tokens.js +179 -0
  161. package/dist/reconciliation/util.js +55 -0
  162. package/dist/semantic-engine/additive.js +51 -0
  163. package/dist/semantic-engine/compounds.js +91 -0
  164. package/dist/semantic-engine/documents.js +157 -0
  165. package/dist/semantic-engine/enrich.js +42 -0
  166. package/dist/semantic-engine/index.js +7 -0
  167. package/dist/semantic-engine/infer.js +38 -0
  168. package/dist/semantic-engine/tokens.js +120 -0
  169. package/dist/shared/canonical.js +65 -0
  170. package/dist/shared/diagnostic.js +78 -0
  171. package/dist/shared/glob.js +98 -0
  172. package/dist/shared/index.js +4 -0
  173. package/dist/shared/json.js +2 -0
  174. package/dist/validation/card.js +50 -0
  175. package/dist/validation/discovery/arrangements.js +162 -0
  176. package/dist/validation/discovery/discover.js +70 -0
  177. package/dist/validation/discovery/index.js +8 -0
  178. package/dist/validation/discovery/model.js +127 -0
  179. package/dist/validation/discovery/promote.js +24 -0
  180. package/dist/validation/discovery/reviews.js +63 -0
  181. package/dist/validation/discovery/shapes.js +47 -0
  182. package/dist/validation/discovery/similar-files.js +146 -0
  183. package/dist/validation/discovery/util.js +47 -0
  184. package/dist/validation/index.js +11 -0
  185. package/dist/validation/interaction-facts.js +341 -0
  186. package/dist/validation/interaction-workflows.js +633 -0
  187. package/dist/validation/literals.js +251 -0
  188. package/dist/validation/pattern-rules.js +608 -0
  189. package/dist/validation/policy.js +241 -0
  190. package/dist/validation/report.js +163 -0
  191. package/dist/validation/rules.js +269 -0
  192. package/dist/validation/scan.js +295 -0
  193. package/dist/validation/text.js +34 -0
  194. package/dist/validation/validate-code.js +513 -0
  195. package/dist/validation/validate-usage.js +196 -0
  196. package/package.json +38 -4
@@ -0,0 +1,188 @@
1
+ import { excerpt, lineOf } from "../compiler-core/index.js";
2
+ export const CAPABILITIES = [
3
+ "name",
4
+ "description",
5
+ "lifecycle",
6
+ "deprecated",
7
+ "implementations.*",
8
+ "props.*",
9
+ "slots.*",
10
+ "variants.*",
11
+ "accessibility.*",
12
+ "examples.*",
13
+ "styling.*",
14
+ "usage.*",
15
+ "composition.*"
16
+ ];
17
+ /** Writes one component's facts. Prose from JSDoc is `authored`; everything derived from syntax or types is `extracted`. */
18
+ export function emitComponent(builder, c, pkg) {
19
+ const ref = builder.entity(c.ref, "component", c.displayName);
20
+ const at = (node, method = "compiler-api") => {
21
+ // Project files are loaded under a virtual root, so a node's path is its project-relative path.
22
+ const file = node.getSourceFile().fileName.replace(/^\//, "");
23
+ return builder.evidence({
24
+ sourceRef: `${file}#${c.candidate.name}`,
25
+ location: { type: "file", path: file, line: lineOf(node) },
26
+ method,
27
+ original: excerpt(node)
28
+ });
29
+ };
30
+ const declEv = at(c.candidate.decl);
31
+ const implEv = at(c.candidate.impl ?? c.candidate.decl);
32
+ const put = (path, value, evidence, origin = "extracted") => builder.fact(ref, path, value, evidence, { origin });
33
+ put("name", c.displayName, declEv);
34
+ if (c.docs.description)
35
+ put("description", c.docs.description, declEv, "authored");
36
+ if (c.docs.deprecated) {
37
+ put("lifecycle", "deprecated", declEv, "authored");
38
+ put("deprecated", c.docs.deprecated, declEv, "authored");
39
+ }
40
+ c.docs.examples.forEach((code, index) => {
41
+ const id = `example-${index + 1}`;
42
+ put(`examples.${id}.language`, c.file.split(".").pop() ?? "tsx", declEv, "authored");
43
+ put(`examples.${id}.code`, code, declEv, "authored");
44
+ });
45
+ // Implementation
46
+ const primary = [...c.exports].sort((a, b) => (a.subpath === "." ? 0 : 1) - (b.subpath === "." ? 0 : 1) ||
47
+ (a.name === "default" ? 1 : 0) - (b.name === "default" ? 1 : 0) ||
48
+ (a.name < b.name ? -1 : 1))[0];
49
+ const impl = "implementations.react";
50
+ put(`${impl}.framework`, "react", declEv);
51
+ if (pkg.name)
52
+ put(`${impl}.packageName`, pkg.name, declEv);
53
+ if (primary) {
54
+ put(`${impl}.exportName`, primary.name, declEv);
55
+ if (pkg.name)
56
+ put(`${impl}.importPath`, primary.subpath === "." ? pkg.name : `${pkg.name}/${primary.subpath.replace(/^\.\//, "")}`, declEv);
57
+ }
58
+ const ext = `${impl}.extensions.react`;
59
+ put(`${ext}.kind`, c.candidate.kind, declEv);
60
+ put(`${ext}.file`, c.file, declEv);
61
+ // Neutral link key: other sources identify a component by (file, local declaration name).
62
+ put(`${ext}.localName`, c.candidate.name, declEv);
63
+ put(`${ext}.exports`, c.exports, declEv);
64
+ put(`${ext}.forwardRef`, c.candidate.wrappers.includes("forwardRef"), declEv);
65
+ put(`${ext}.memo`, c.candidate.wrappers.includes("memo"), declEv);
66
+ if (c.candidate.wrappers.length > 0)
67
+ put(`${ext}.wrappers`, c.candidate.wrappers, declEv);
68
+ if (c.candidate.refTypeText)
69
+ put(`${ext}.refType`, c.candidate.refTypeText, declEv);
70
+ if (c.candidate.reactDisplayName)
71
+ put(`${ext}.displayName`, c.candidate.reactDisplayName, declEv);
72
+ if (c.candidate.styled)
73
+ put(`${ext}.styledLibrary`, c.candidate.styled.library, declEv);
74
+ if (c.props.generics.length > 0)
75
+ put(`${ext}.generics`, c.props.generics, declEv);
76
+ if (c.props.inheritsFrom.length > 0)
77
+ put(`${ext}.inheritedPropsFrom`, c.props.inheritsFrom, declEv);
78
+ put(`${ext}.propsComplete`, c.props.complete, declEv);
79
+ if (c.compoundParent)
80
+ put(`${ext}.compoundParent`, c.compoundParent, declEv);
81
+ // Props
82
+ for (const p of c.props.props) {
83
+ const base = `props.${p.name}`;
84
+ const ev = at(p.declaration.node);
85
+ put(`${base}.type.text`, p.typeText, ev);
86
+ put(`${base}.type.kind`, p.kind, ev);
87
+ put(`${base}.required`, p.required, ev);
88
+ if (p.default !== undefined)
89
+ put(`${base}.default`, p.default, [ev, implEv]);
90
+ if (p.defaultExpression !== undefined)
91
+ put(`${base}.extensions.react.defaultExpression`, p.defaultExpression, implEv);
92
+ if (p.allowedValues)
93
+ put(`${base}.allowedValues`, p.allowedValues, ev);
94
+ if (p.description)
95
+ put(`${base}.description`, p.description, ev, "authored");
96
+ if (p.deprecated)
97
+ put(`${base}.deprecated`, p.deprecated, ev, "authored");
98
+ put(`${base}.extensions.react.role`, p.role, ev);
99
+ if (p.inheritedFrom)
100
+ put(`${base}.extensions.react.inheritedFrom`, p.inheritedFrom, ev);
101
+ }
102
+ // Variants
103
+ for (const v of c.variants) {
104
+ const ev = v.node ? at(v.node, v.source === "cva" ? "ast" : "compiler-api") : declEv;
105
+ put(`variants.${v.name}.values`, v.values, ev);
106
+ if (v.default !== undefined)
107
+ put(`variants.${v.name}.default`, v.default, ev);
108
+ put(`variants.${v.name}.extensions.react.source`, v.source, ev);
109
+ if (v.via)
110
+ put(`variants.${v.name}.extensions.react.via`, v.via, ev);
111
+ }
112
+ // Slots
113
+ for (const s of c.slots) {
114
+ put(`slots.${s.name}.declared`, true, declEv);
115
+ put(`slots.${s.name}.required`, s.required, declEv);
116
+ put(`slots.${s.name}.extensions.react.prop`, s.prop, declEv);
117
+ put(`slots.${s.name}.extensions.react.typeText`, s.typeText, declEv);
118
+ if (s.renderProp)
119
+ put(`slots.${s.name}.extensions.react.renderProp`, true, declEv);
120
+ }
121
+ // Accessibility
122
+ if (c.accessibility) {
123
+ if (c.accessibility.role)
124
+ put("accessibility.role", c.accessibility.role, implEv);
125
+ if (c.accessibility.attributes.length > 0)
126
+ put("accessibility.attributes", c.accessibility.attributes, implEv);
127
+ }
128
+ // Styling
129
+ if (c.styling) {
130
+ const s = c.styling;
131
+ if (s.cssModules.length > 0)
132
+ put("styling.cssModules", s.cssModules.map((m) => m.path), implEv);
133
+ const moduleEvidence = s.cssModules.flatMap((m) => m.variables.map((v) => builder.evidence({
134
+ sourceRef: `${m.path}#${v.name}`,
135
+ location: { type: "file", path: m.path, line: v.line },
136
+ method: "structured-parse",
137
+ original: `var(${v.name})`
138
+ })));
139
+ const variables = [
140
+ ...new Set([...s.inlineVariables, ...s.cssModules.flatMap((m) => m.variables.map((v) => v.name))])
141
+ ].sort();
142
+ if (variables.length > 0)
143
+ put("styling.cssVariables", variables, [implEv, ...moduleEvidence]);
144
+ if (s.classNames.length > 0)
145
+ put("styling.classNames", s.classNames, implEv);
146
+ if (s.tailwind && s.classNames.length > 0)
147
+ put("styling.tailwind", true, implEv);
148
+ if (s.libraries.length > 0)
149
+ put("styling.libraries", s.libraries, implEv);
150
+ }
151
+ // Composition and usage
152
+ if (c.renders.length > 0)
153
+ put("composition.renders", c.renders, implEv);
154
+ if (c.subcomponents.length > 0)
155
+ put("composition.subcomponents", c.subcomponents, declEv);
156
+ if (c.usage) {
157
+ const u = c.usage;
158
+ const samples = u.samples.map((s) => builder.evidence({
159
+ sourceRef: `${s.file}#usage`,
160
+ location: { type: "file", path: s.file, line: s.line },
161
+ method: "compiler-api",
162
+ original: s.text
163
+ }));
164
+ put("usage.sites", u.sites, samples);
165
+ // Variant names count as declared (cva variants reach the component through VariantProps).
166
+ const declared = new Set([...c.props.props.map((p) => p.name), ...c.variants.map((v) => v.name)]);
167
+ for (const [name, stat] of [...u.props].sort(([a], [b]) => (a < b ? -1 : 1))) {
168
+ if (!/^[A-Za-z0-9_:-]+$/.test(name))
169
+ continue;
170
+ put(`usage.props.${name}.count`, stat.count, samples);
171
+ if (stat.values.length > 0)
172
+ put(`usage.props.${name}.observedValues`, stat.values, samples);
173
+ if (stat.dynamic > 0)
174
+ put(`usage.props.${name}.dynamicCount`, stat.dynamic, samples);
175
+ // When the props set is incomplete, absence from it proves nothing, so `declared: false` is not claimed.
176
+ if (declared.has(name) || c.props.complete)
177
+ put(`usage.props.${name}.declared`, declared.has(name), samples);
178
+ }
179
+ if (u.undeclared.size > 0)
180
+ put("usage.undeclaredProps", [...u.undeclared].sort(), samples);
181
+ const combos = [...u.combinations]
182
+ .map(([key, count]) => ({ props: key === "" ? [] : key.split(","), count }))
183
+ .sort((a, b) => b.count - a.count || (a.props.join() < b.props.join() ? -1 : 1))
184
+ .slice(0, 10);
185
+ put("usage.combinations", combos, samples);
186
+ }
187
+ }
188
+ //# sourceMappingURL=emit.js.map
@@ -0,0 +1,2 @@
1
+ export { ADAPTER_ID, ADAPTER_VERSION, reactAdapter } from "./adapter.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,58 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import ts from "typescript";
3
+ /**
4
+ * Builds a type-checking program over the supplied project files only. Nothing is read from
5
+ * `node_modules` and no customer code runs: third-party types (including `@types/react`) are not
6
+ * loaded, so references to them stay unresolved and are reported as such rather than guessed.
7
+ * TypeScript's own `lib.es2022.d.ts` is read so built-in utility types like `Omit` resolve.
8
+ */
9
+ export function createAnalysis(files) {
10
+ const virtual = new Map([...files].map(([rel, text]) => [`/${rel}`, text]));
11
+ const options = {
12
+ target: ts.ScriptTarget.ES2022,
13
+ module: ts.ModuleKind.ESNext,
14
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
15
+ jsx: ts.JsxEmit.ReactJSX,
16
+ lib: ["lib.es2022.d.ts"],
17
+ types: [],
18
+ strict: true,
19
+ allowJs: true,
20
+ checkJs: false,
21
+ noEmit: true,
22
+ skipLibCheck: true,
23
+ resolveJsonModule: true,
24
+ allowArbitraryExtensions: true
25
+ };
26
+ const libDir = ts.getDefaultLibFilePath(options).replace(/[^/\\]+$/, "");
27
+ const isLib = (file) => file.startsWith(libDir);
28
+ const read = (file) => {
29
+ const text = virtual.get(file);
30
+ if (text !== undefined)
31
+ return text;
32
+ return isLib(file) && existsSync(file) ? readFileSync(file, "utf8") : undefined;
33
+ };
34
+ const host = {
35
+ getSourceFile: (file, languageVersion) => {
36
+ const text = read(file);
37
+ return text === undefined ? undefined : ts.createSourceFile(file, text, languageVersion, true);
38
+ },
39
+ getDefaultLibFileName: (o) => ts.getDefaultLibFilePath(o),
40
+ writeFile: () => { },
41
+ getCurrentDirectory: () => "/",
42
+ getDirectories: () => [],
43
+ fileExists: (file) => read(file) !== undefined,
44
+ readFile: read,
45
+ getCanonicalFileName: (file) => file,
46
+ useCaseSensitiveFileNames: () => true,
47
+ getNewLine: () => "\n"
48
+ };
49
+ const roots = [...virtual.keys()].filter((f) => /\.(?:[cm]?[jt]sx?)$/.test(f));
50
+ const program = ts.createProgram({ rootNames: roots, options, host });
51
+ return {
52
+ program,
53
+ checker: program.getTypeChecker(),
54
+ relativePath: (fileName) => (virtual.has(fileName) ? fileName.slice(1) : undefined),
55
+ sourceFile: (rel) => program.getSourceFile(`/${rel}`)
56
+ };
57
+ }
58
+ //# sourceMappingURL=program.js.map
@@ -0,0 +1,157 @@
1
+ import { collapse, describeMember, externalBases, hasModifier, isProjectNode, literalValue, topLevelAssignments, unwrap } from "../compiler-core/index.js";
2
+ import ts from "typescript";
3
+ const RENDERABLE_OR_COMPONENT_TYPE = /^(?:(?:React\.)?(?:ReactNode|ReactElement|ReactChild|ReactFragment|JSX\.Element)(?:<.*>)?|(?:React\.)?ComponentType\b.*)$/;
4
+ const RENDER_FUNCTION = /=>\s*(?:React\.)?(?:ReactNode|ReactElement|JSX\.Element)\b/;
5
+ const PROP_NAME = /^[A-Za-z0-9_:-]+$/;
6
+ export function extractProps(analysis, candidate) {
7
+ const { checker } = analysis;
8
+ const notes = [];
9
+ const typeNode = candidate.propsTypeNode ?? firstParameterType(candidate);
10
+ const generics = candidate.impl && ts.isFunctionLike(candidate.impl)
11
+ ? (candidate.impl.typeParameters?.map((p) => p.name.text) ?? [])
12
+ : [];
13
+ if (!typeNode) {
14
+ if (candidate.kind === "lazy")
15
+ notes.push("lazy component: props are defined by the dynamically imported module");
16
+ else if (candidate.kind === "styled")
17
+ notes.push("styled component without a props type argument");
18
+ else
19
+ notes.push("no props type could be determined from syntax");
20
+ return { props: [], inheritsFrom: [], generics, complete: false, skipped: [], notes };
21
+ }
22
+ const type = checker.getTypeFromTypeNode(typeNode);
23
+ const inheritsFrom = [...new Set(externalBases(analysis, typeNode))].sort();
24
+ const defaults = collectDefaults(candidate);
25
+ const rootName = declaredTypeName(typeNode);
26
+ const props = [];
27
+ const skipped = [];
28
+ // One member that does not resolve (a type from a package that is not installed) turns the whole intersection into
29
+ // `any`; reading the members one by one keeps the props of the rest.
30
+ const parts = ts.isIntersectionTypeNode(typeNode) && (type.flags & ts.TypeFlags.Any) !== 0
31
+ ? typeNode.types.map((n) => checker.getTypeFromTypeNode(n))
32
+ : [type];
33
+ const seen = new Set();
34
+ for (const part of parts) {
35
+ if ((part.flags & ts.TypeFlags.Any) !== 0)
36
+ continue;
37
+ for (const symbol of checker.getPropertiesOfType(part)) {
38
+ if (seen.has(symbol.name))
39
+ continue;
40
+ seen.add(symbol.name);
41
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
42
+ if (!declaration || !isProjectNode(analysis, declaration))
43
+ continue;
44
+ if (!PROP_NAME.test(symbol.name)) {
45
+ skipped.push(symbol.name);
46
+ continue;
47
+ }
48
+ props.push(describeProp({ analysis, symbol, declaration, rootName, defaults, candidate }));
49
+ }
50
+ }
51
+ props.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
52
+ const resolvedAny = (type.flags & ts.TypeFlags.Any) !== 0;
53
+ if (resolvedAny)
54
+ notes.push("props type did not resolve");
55
+ if (candidate.propsUnknown && props.length === 0)
56
+ notes.push("props type did not resolve");
57
+ return {
58
+ props,
59
+ inheritsFrom,
60
+ generics,
61
+ complete: !candidate.openProps &&
62
+ inheritsFrom.length === 0 &&
63
+ skipped.length === 0 &&
64
+ !resolvedAny &&
65
+ !(candidate.propsUnknown && props.length === 0),
66
+ skipped,
67
+ notes
68
+ };
69
+ }
70
+ function describeProp(input) {
71
+ const { analysis, symbol, declaration, rootName, defaults } = input;
72
+ const member = describeMember(analysis, symbol, declaration, { renderable: RENDERABLE_OR_COMPONENT_TYPE });
73
+ const fallback = defaults.get(symbol.name);
74
+ return {
75
+ name: member.name,
76
+ typeText: member.typeText,
77
+ kind: member.kind,
78
+ required: member.required,
79
+ ...(fallback?.value !== undefined ? { default: fallback.value } : {}),
80
+ ...(fallback && fallback.value === undefined ? { defaultExpression: fallback.expression } : {}),
81
+ ...(member.allowedValues ? { allowedValues: member.allowedValues } : {}),
82
+ ...(member.description ? { description: member.description } : {}),
83
+ ...(member.deprecated ? { deprecated: member.deprecated } : {}),
84
+ ...(member.parentName && member.parentName !== rootName ? { inheritedFrom: member.parentName } : {}),
85
+ role: roleOf(member.name, member.typeText, member.kind),
86
+ declaration: { node: declaration, line: member.line }
87
+ };
88
+ }
89
+ function roleOf(name, typeText, kind) {
90
+ if (name === "children")
91
+ return "children";
92
+ if (name === "ref")
93
+ return "ref";
94
+ if (kind === "function")
95
+ return RENDER_FUNCTION.test(typeText) ? "render-prop" : /^on[A-Z]/.test(name) ? "event-handler" : "prop";
96
+ if (kind === "component")
97
+ return "slot";
98
+ return "prop";
99
+ }
100
+ function firstParameterType(candidate) {
101
+ const impl = candidate.impl;
102
+ if (!impl)
103
+ return undefined;
104
+ if (ts.isClassLike(impl))
105
+ return undefined;
106
+ return impl.parameters[0]?.type;
107
+ }
108
+ function declaredTypeName(node) {
109
+ return ts.isTypeReferenceNode(node) ? node.typeName.getText() : undefined;
110
+ }
111
+ /** Defaults from destructuring initializers, `static defaultProps`, and `Name.defaultProps = {…}`. */
112
+ function collectDefaults(candidate) {
113
+ const defaults = new Map();
114
+ const set = (name, init) => {
115
+ const value = literalValue(init);
116
+ defaults.set(name, {
117
+ ...(value !== undefined ? { value } : {}),
118
+ expression: collapse(init.getText()).slice(0, 200)
119
+ });
120
+ };
121
+ const impl = candidate.impl;
122
+ if (impl && ts.isFunctionLike(impl)) {
123
+ const first = impl.parameters[0];
124
+ if (first && ts.isObjectBindingPattern(first.name)) {
125
+ for (const element of first.name.elements) {
126
+ if (!element.initializer || element.dotDotDotToken)
127
+ continue;
128
+ const key = element.propertyName ?? element.name;
129
+ if (ts.isIdentifier(key) || ts.isStringLiteralLike(key))
130
+ set(key.text, element.initializer);
131
+ }
132
+ }
133
+ }
134
+ const fromObject = (expression) => {
135
+ const object = unwrap(expression);
136
+ if (!ts.isObjectLiteralExpression(object))
137
+ return;
138
+ for (const p of object.properties) {
139
+ if (ts.isPropertyAssignment(p) && (ts.isIdentifier(p.name) || ts.isStringLiteralLike(p.name)))
140
+ set(p.name.text, p.initializer);
141
+ }
142
+ };
143
+ if (impl && ts.isClassLike(impl)) {
144
+ for (const member of impl.members) {
145
+ if (ts.isPropertyDeclaration(member) &&
146
+ hasModifier(member, ts.SyntaxKind.StaticKeyword) &&
147
+ member.name.getText() === "defaultProps" &&
148
+ member.initializer)
149
+ fromObject(member.initializer);
150
+ }
151
+ }
152
+ const assigned = topLevelAssignments(candidate.decl.getSourceFile()).get(`${candidate.name}.defaultProps`);
153
+ if (assigned)
154
+ fromObject(assigned);
155
+ return defaults;
156
+ }
157
+ //# sourceMappingURL=props.js.map
@@ -0,0 +1,138 @@
1
+ import { posix, relativeFileOf, unwrap } from "../compiler-core/index.js";
2
+ import ts from "typescript";
3
+ const CSS_MODULE = /\.module\.(?:css|scss|sass|less)$/;
4
+ const VAR_REFERENCE = /var\(\s*(--[\w-]+)/g;
5
+ const CLASS_HELPERS = new Set(["cn", "clsx", "classNames", "classnames", "cx", "twMerge"]);
6
+ const MAX_CLASSES = 200;
7
+ /**
8
+ * Styling references found in a component: imported CSS modules and the custom properties they use,
9
+ * `var(--x)` references in the component source, literal `className` tokens, and CSS-in-JS libraries.
10
+ * References are recorded as written; they are not resolved to tokens here.
11
+ */
12
+ export function stylingOf(analysis, candidate, context) {
13
+ const node = candidate.impl ?? candidate.decl;
14
+ const file = relativeFileOf(analysis, candidate.decl);
15
+ if (!file)
16
+ return undefined;
17
+ const sf = candidate.decl.getSourceFile();
18
+ const cssModules = [];
19
+ const libraries = new Set();
20
+ if (candidate.styled)
21
+ libraries.add(candidate.styled.library);
22
+ for (const statement of sf.statements) {
23
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
24
+ continue;
25
+ const specifier = statement.moduleSpecifier.text;
26
+ if (specifier === "@emotion/react" && usesCssProp(node))
27
+ libraries.add("emotion");
28
+ if (!CSS_MODULE.test(specifier) || !specifier.startsWith("."))
29
+ continue;
30
+ const local = statement.importClause?.name?.text;
31
+ if (!local || !referencesIdentifier(node, local))
32
+ continue;
33
+ const path = posix.normalize(posix.join(posix.dirname(file), specifier));
34
+ const text = context.files.get(path);
35
+ cssModules.push({ path, variables: text === undefined ? [] : variableLines(text) });
36
+ }
37
+ const source = node.getText(sf);
38
+ const inlineVariables = [...new Set([...source.matchAll(VAR_REFERENCE)].map((m) => m[1]))].sort();
39
+ const classNames = classNamesIn(node);
40
+ if (cssModules.length === 0 &&
41
+ inlineVariables.length === 0 &&
42
+ classNames.length === 0 &&
43
+ libraries.size === 0)
44
+ return undefined;
45
+ return {
46
+ cssModules,
47
+ inlineVariables,
48
+ classNames,
49
+ tailwind: context.tailwind,
50
+ libraries: [...libraries].sort()
51
+ };
52
+ }
53
+ function variableLines(css) {
54
+ const seen = new Map();
55
+ css.split("\n").forEach((text, index) => {
56
+ for (const match of text.matchAll(VAR_REFERENCE))
57
+ if (!seen.has(match[1]))
58
+ seen.set(match[1], index + 1);
59
+ });
60
+ return [...seen]
61
+ .map(([name, line]) => ({ name, line }))
62
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
63
+ }
64
+ function referencesIdentifier(root, name) {
65
+ let found = false;
66
+ const walk = (n) => {
67
+ if (found)
68
+ return;
69
+ if (ts.isIdentifier(n) && n.text === name)
70
+ found = true;
71
+ else
72
+ ts.forEachChild(n, walk);
73
+ };
74
+ walk(root);
75
+ return found;
76
+ }
77
+ function usesCssProp(root) {
78
+ let found = false;
79
+ const walk = (n) => {
80
+ if (found)
81
+ return;
82
+ if (ts.isJsxAttribute(n) && n.name.getText() === "css")
83
+ found = true;
84
+ else
85
+ ts.forEachChild(n, walk);
86
+ };
87
+ walk(root);
88
+ return found;
89
+ }
90
+ function classNamesIn(root) {
91
+ const out = new Set();
92
+ const add = (text) => {
93
+ for (const token of text.split(/\s+/))
94
+ if (token && out.size < MAX_CLASSES)
95
+ out.add(token);
96
+ };
97
+ const fromExpression = (expression) => {
98
+ const node = unwrap(expression);
99
+ if (ts.isStringLiteralLike(node))
100
+ add(node.text);
101
+ else if (ts.isTemplateExpression(node)) {
102
+ add(node.head.text);
103
+ for (const span of node.templateSpans) {
104
+ add(span.literal.text);
105
+ fromExpression(span.expression);
106
+ }
107
+ }
108
+ else if (ts.isConditionalExpression(node)) {
109
+ fromExpression(node.whenTrue);
110
+ fromExpression(node.whenFalse);
111
+ }
112
+ else if (ts.isBinaryExpression(node)) {
113
+ fromExpression(node.right);
114
+ }
115
+ else if (ts.isCallExpression(node) &&
116
+ ts.isIdentifier(node.expression) &&
117
+ CLASS_HELPERS.has(node.expression.text)) {
118
+ for (const argument of node.arguments)
119
+ fromExpression(argument);
120
+ }
121
+ else if (ts.isArrayLiteralExpression(node)) {
122
+ for (const element of node.elements)
123
+ fromExpression(element);
124
+ }
125
+ };
126
+ const walk = (n) => {
127
+ if (ts.isJsxAttribute(n) && n.name.getText() === "className" && n.initializer) {
128
+ if (ts.isStringLiteral(n.initializer))
129
+ add(n.initializer.text);
130
+ else if (ts.isJsxExpression(n.initializer) && n.initializer.expression)
131
+ fromExpression(n.initializer.expression);
132
+ }
133
+ ts.forEachChild(n, walk);
134
+ };
135
+ walk(root);
136
+ return [...out].sort();
137
+ }
138
+ //# sourceMappingURL=styling.js.map
@@ -0,0 +1,116 @@
1
+ import { lineOf, literalValue, relativeFileOf } from "../compiler-core/index.js";
2
+ import ts from "typescript";
3
+ const MAX_VALUES = 20;
4
+ const MAX_SAMPLES = 5;
5
+ // Attributes that every element accepts regardless of its declared props.
6
+ const UNIVERSAL = new Set(["key", "ref"]);
7
+ /**
8
+ * Counts JSX sites that statically resolve to a known component: observed prop names and literal
9
+ * values, prop combinations, undeclared props, and which components render which. Values passed as
10
+ * expressions are counted as dynamic, never guessed. Frequencies are static source counts, not runtime.
11
+ */
12
+ export function analyzeUsage(input) {
13
+ const { analysis, files, resolveTarget, enclosing, declared } = input;
14
+ const byTarget = new Map();
15
+ const renders = new Map();
16
+ for (const file of files) {
17
+ const sf = analysis.sourceFile(file);
18
+ if (!sf)
19
+ continue;
20
+ const visit = (node) => {
21
+ if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node))
22
+ record(node);
23
+ ts.forEachChild(node, visit);
24
+ };
25
+ visit(sf);
26
+ }
27
+ return { byTarget, renders };
28
+ function record(element) {
29
+ const tag = element.tagName;
30
+ if (ts.isIdentifier(tag) && /^[a-z]/.test(tag.text))
31
+ return;
32
+ const target = resolveTarget(tag);
33
+ if (!target)
34
+ return;
35
+ const stats = byTarget.get(target) ?? {
36
+ sites: 0,
37
+ props: new Map(),
38
+ combinations: new Map(),
39
+ undeclared: new Set(),
40
+ samples: []
41
+ };
42
+ byTarget.set(target, stats);
43
+ stats.sites++;
44
+ const file = relativeFileOf(analysis, element);
45
+ if (file && stats.samples.length < MAX_SAMPLES)
46
+ stats.samples.push({
47
+ file,
48
+ line: lineOf(element),
49
+ text: element.getText().split("\n")[0]?.slice(0, 200) ?? ""
50
+ });
51
+ const names = [];
52
+ let spread = false;
53
+ for (const attribute of element.attributes.properties) {
54
+ if (ts.isJsxSpreadAttribute(attribute)) {
55
+ spread = true;
56
+ continue;
57
+ }
58
+ const name = attribute.name.getText();
59
+ if (name.includes(":") && !name.startsWith("aria-") && !name.startsWith("data-"))
60
+ continue;
61
+ names.push(name);
62
+ const entry = stats.props.get(name) ?? { count: 0, values: [], valuesTruncated: false, dynamic: 0 };
63
+ entry.count++;
64
+ const value = attributeValue(attribute);
65
+ if (value === undefined)
66
+ entry.dynamic++;
67
+ else if (!entry.values.some((v) => JSON.stringify(v) === JSON.stringify(value))) {
68
+ if (entry.values.length < MAX_VALUES)
69
+ entry.values.push(value);
70
+ else
71
+ entry.valuesTruncated = true;
72
+ }
73
+ stats.props.set(name, entry);
74
+ }
75
+ if (ts.isJsxOpeningElement(element) &&
76
+ ts.isJsxElement(element.parent) &&
77
+ element.parent.children.some((c) => !(ts.isJsxText(c) && c.containsOnlyTriviaWhiteSpaces))) {
78
+ names.push("children");
79
+ const entry = stats.props.get("children") ?? {
80
+ count: 0,
81
+ values: [],
82
+ valuesTruncated: false,
83
+ dynamic: 0
84
+ };
85
+ entry.count++;
86
+ entry.dynamic++;
87
+ stats.props.set("children", entry);
88
+ }
89
+ const key = [...new Set(names)].sort().join(",");
90
+ stats.combinations.set(key, (stats.combinations.get(key) ?? 0) + 1);
91
+ const { names: known, closed } = declared(target);
92
+ if (closed && !spread)
93
+ for (const name of names)
94
+ if (!known.has(name) && !UNIVERSAL.has(name))
95
+ stats.undeclared.add(name);
96
+ const parent = enclosing(element);
97
+ if (parent) {
98
+ const children = renders.get(parent) ?? new Map();
99
+ children.set(target, (children.get(target) ?? 0) + 1);
100
+ renders.set(parent, children);
101
+ }
102
+ }
103
+ }
104
+ function attributeValue(attribute) {
105
+ const init = attribute.initializer;
106
+ if (!init)
107
+ return true;
108
+ if (ts.isStringLiteral(init))
109
+ return init.text;
110
+ if (ts.isJsxExpression(init) && init.expression) {
111
+ const value = literalValue(init.expression);
112
+ return value !== undefined && typeof value !== "object" ? value : undefined;
113
+ }
114
+ return undefined;
115
+ }
116
+ //# sourceMappingURL=usage.js.map