@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.
- package/LICENSE +105 -0
- package/README.md +248 -2
- package/dist/agent-kit/apply.js +232 -0
- package/dist/agent-kit/guidance.js +236 -0
- package/dist/agent-kit/index.js +5 -0
- package/dist/agent-kit/integrations.js +121 -0
- package/dist/agent-kit/plugin.js +115 -0
- package/dist/artifact-generator/index.js +75 -0
- package/dist/browser-verify/driver.js +2 -0
- package/dist/browser-verify/index.js +4 -0
- package/dist/browser-verify/playwright.js +82 -0
- package/dist/browser-verify/runner.js +175 -0
- package/dist/browser-verify/spec.js +103 -0
- package/dist/cli/args.js +77 -0
- package/dist/cli/baseline.js +64 -0
- package/dist/cli/cli.js +121 -0
- package/dist/cli/commands-browser.js +104 -0
- package/dist/cli/commands-discover.js +312 -0
- package/dist/cli/commands-enforcement.js +35 -0
- package/dist/cli/commands-governance.js +179 -0
- package/dist/cli/commands-hook.js +269 -0
- package/dist/cli/commands-patterns.js +324 -0
- package/dist/cli/commands-setup.js +485 -0
- package/dist/cli/commands-validate.js +157 -0
- package/dist/cli/commands.js +517 -0
- package/dist/cli/config.js +197 -0
- package/dist/cli/diff.js +66 -0
- package/dist/cli/enforcement-log.js +83 -0
- package/dist/cli/enforcement-report.js +96 -0
- package/dist/cli/errors.js +10 -0
- package/dist/cli/format.js +22 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/io.js +2 -0
- package/dist/cli/main.js +31 -0
- package/dist/cli/pipeline.js +262 -0
- package/dist/cli/publish.js +77 -0
- package/dist/cli/report.js +127 -0
- package/dist/cli/starter-rules.js +57 -0
- package/dist/cli/store.js +60 -0
- package/dist/cli/version.js +5 -0
- package/dist/cli/watch.js +68 -0
- package/dist/cli/workflow.js +52 -0
- package/dist/compiler-angular/adapter.js +115 -0
- package/dist/compiler-angular/analyze.js +222 -0
- package/dist/compiler-angular/classes.js +446 -0
- package/dist/compiler-angular/declared.js +285 -0
- package/dist/compiler-angular/emit.js +183 -0
- package/dist/compiler-angular/index.js +4 -0
- package/dist/compiler-angular/template.js +142 -0
- package/dist/compiler-core/adapter.js +2 -0
- package/dist/compiler-core/adapters/inventory.js +312 -0
- package/dist/compiler-core/compile.js +148 -0
- package/dist/compiler-core/component-facts.js +123 -0
- package/dist/compiler-core/discovery/docs.js +66 -0
- package/dist/compiler-core/discovery/entries.js +270 -0
- package/dist/compiler-core/discovery/exports.js +207 -0
- package/dist/compiler-core/discovery/graph.js +59 -0
- package/dist/compiler-core/discovery/index.js +103 -0
- package/dist/compiler-core/discovery/packages.js +148 -0
- package/dist/compiler-core/discovery/schema.js +144 -0
- package/dist/compiler-core/discovery/storybook.js +232 -0
- package/dist/compiler-core/discovery/support.js +82 -0
- package/dist/compiler-core/discovery/tokens.js +186 -0
- package/dist/compiler-core/discovery/tooling.js +146 -0
- package/dist/compiler-core/fs.js +89 -0
- package/dist/compiler-core/glob.js +3 -0
- package/dist/compiler-core/index.js +16 -0
- package/dist/compiler-core/ir-builder.js +68 -0
- package/dist/compiler-core/json-schema.js +11 -0
- package/dist/compiler-core/testing/index.js +81 -0
- package/dist/compiler-core/ts/ast.js +166 -0
- package/dist/compiler-core/ts/members.js +163 -0
- package/dist/compiler-core/ts/program.js +81 -0
- package/dist/compiler-core/ts/static-eval.js +145 -0
- package/dist/compiler-core/ts/tsconfig-paths.js +60 -0
- package/dist/compiler-core/walk.js +46 -0
- package/dist/compiler-ir/index.js +4 -0
- package/dist/compiler-ir/ir.js +67 -0
- package/dist/compiler-ir/json-schema.js +6 -0
- package/dist/compiler-ir/validate.js +116 -0
- package/dist/compiler-react/adapter.js +147 -0
- package/dist/compiler-react/analyze.js +199 -0
- package/dist/compiler-react/ast.js +105 -0
- package/dist/compiler-react/behavior.js +75 -0
- package/dist/compiler-react/detect.js +558 -0
- package/dist/compiler-react/emit.js +188 -0
- package/dist/compiler-react/index.js +2 -0
- package/dist/compiler-react/program.js +58 -0
- package/dist/compiler-react/props.js +157 -0
- package/dist/compiler-react/styling.js +138 -0
- package/dist/compiler-react/usage.js +116 -0
- package/dist/compiler-react/variants.js +111 -0
- package/dist/compiler-storybook/adapter.js +253 -0
- package/dist/compiler-storybook/csf.js +521 -0
- package/dist/compiler-storybook/emit.js +202 -0
- package/dist/compiler-storybook/index-json.js +41 -0
- package/dist/compiler-storybook/index.js +5 -0
- package/dist/compiler-storybook/mdx.js +120 -0
- package/dist/compiler-storybook/static-eval.js +145 -0
- package/dist/compiler-tokens/adapter.js +158 -0
- package/dist/compiler-tokens/css.js +59 -0
- package/dist/compiler-tokens/index.js +5 -0
- package/dist/compiler-tokens/json-lines.js +106 -0
- package/dist/compiler-tokens/json-tokens.js +52 -0
- package/dist/compiler-vue/adapter.js +107 -0
- package/dist/compiler-vue/analyze.js +344 -0
- package/dist/compiler-vue/declared.js +343 -0
- package/dist/compiler-vue/emit.js +129 -0
- package/dist/compiler-vue/index.js +5 -0
- package/dist/compiler-vue/script.js +472 -0
- package/dist/compiler-vue/sfc.js +44 -0
- package/dist/compiler-vue/template.js +73 -0
- package/dist/contract/archetype-checks.js +153 -0
- package/dist/contract/components.js +102 -0
- package/dist/contract/conflicts.js +39 -0
- package/dist/contract/contract.js +46 -0
- package/dist/contract/evidence.js +84 -0
- package/dist/contract/fact-path.js +39 -0
- package/dist/contract/facts.js +62 -0
- package/dist/contract/index.js +16 -0
- package/dist/contract/integrity.js +21 -0
- package/dist/contract/json-schema.js +11 -0
- package/dist/contract/knowledge.js +95 -0
- package/dist/contract/pattern-checks.js +254 -0
- package/dist/contract/patterns.js +225 -0
- package/dist/contract/primitives.js +81 -0
- package/dist/contract/sources.js +30 -0
- package/dist/contract/tokens.js +41 -0
- package/dist/contract/validate.js +253 -0
- package/dist/contract-query/archetype.js +188 -0
- package/dist/contract-query/index.js +6 -0
- package/dist/contract-query/patterns.js +298 -0
- package/dist/contract-query/scan.js +164 -0
- package/dist/contract-query/search.js +132 -0
- package/dist/contract-query/system.js +148 -0
- package/dist/contract-query/validate-code.js +239 -0
- package/dist/contract-query/validate-usage.js +150 -0
- package/dist/contract-query/views.js +288 -0
- package/dist/governance/decisions.js +229 -0
- package/dist/governance/index.js +5 -0
- package/dist/governance/patterns-file.js +156 -0
- package/dist/governance/patterns.js +510 -0
- package/dist/governance/policies.js +148 -0
- package/dist/mcp/index.js +7 -0
- package/dist/mcp/loader.js +70 -0
- package/dist/mcp/protocol.js +27 -0
- package/dist/mcp/resources.js +138 -0
- package/dist/mcp/server.js +177 -0
- package/dist/mcp/stdio.js +28 -0
- package/dist/mcp/tools.js +267 -0
- package/dist/reconciliation/code.js +62 -0
- package/dist/reconciliation/components.js +401 -0
- package/dist/reconciliation/conflicts.js +126 -0
- package/dist/reconciliation/facts.js +41 -0
- package/dist/reconciliation/index.js +7 -0
- package/dist/reconciliation/reconcile.js +134 -0
- package/dist/reconciliation/resolve.js +158 -0
- package/dist/reconciliation/review.js +81 -0
- package/dist/reconciliation/storybook.js +370 -0
- package/dist/reconciliation/tokens.js +179 -0
- package/dist/reconciliation/util.js +55 -0
- package/dist/semantic-engine/additive.js +51 -0
- package/dist/semantic-engine/compounds.js +91 -0
- package/dist/semantic-engine/documents.js +157 -0
- package/dist/semantic-engine/enrich.js +42 -0
- package/dist/semantic-engine/index.js +7 -0
- package/dist/semantic-engine/infer.js +38 -0
- package/dist/semantic-engine/tokens.js +120 -0
- package/dist/shared/canonical.js +65 -0
- package/dist/shared/diagnostic.js +78 -0
- package/dist/shared/glob.js +98 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/json.js +2 -0
- package/dist/validation/card.js +50 -0
- package/dist/validation/discovery/arrangements.js +162 -0
- package/dist/validation/discovery/discover.js +70 -0
- package/dist/validation/discovery/index.js +8 -0
- package/dist/validation/discovery/model.js +127 -0
- package/dist/validation/discovery/promote.js +24 -0
- package/dist/validation/discovery/reviews.js +63 -0
- package/dist/validation/discovery/shapes.js +47 -0
- package/dist/validation/discovery/similar-files.js +146 -0
- package/dist/validation/discovery/util.js +47 -0
- package/dist/validation/index.js +11 -0
- package/dist/validation/interaction-facts.js +341 -0
- package/dist/validation/interaction-workflows.js +633 -0
- package/dist/validation/literals.js +251 -0
- package/dist/validation/pattern-rules.js +608 -0
- package/dist/validation/policy.js +241 -0
- package/dist/validation/report.js +163 -0
- package/dist/validation/rules.js +269 -0
- package/dist/validation/scan.js +295 -0
- package/dist/validation/text.js +34 -0
- package/dist/validation/validate-code.js +513 -0
- package/dist/validation/validate-usage.js +196 -0
- package/package.json +38 -4
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { classifyKind, collapse, describeMember, externalBases, literalDomain, literalValue, ownProperties, unwrap } from "../compiler-core/index.js";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
const RENDERABLE = /^(?:VNode|VNodeChild|VNodeArrayChildren|Component|DefineComponent)(?:<.*>)?$/;
|
|
4
|
+
const RUNTIME_KINDS = {
|
|
5
|
+
String: { text: "string", kind: "string" },
|
|
6
|
+
Number: { text: "number", kind: "number" },
|
|
7
|
+
Boolean: { text: "boolean", kind: "boolean" },
|
|
8
|
+
Array: { text: "unknown[]", kind: "array" },
|
|
9
|
+
Object: { text: "object", kind: "object" },
|
|
10
|
+
Function: { text: "Function", kind: "function" },
|
|
11
|
+
Symbol: { text: "symbol", kind: "unknown" },
|
|
12
|
+
Date: { text: "Date", kind: "object" }
|
|
13
|
+
};
|
|
14
|
+
/** Reads the component declaration of a (virtual) script module. Never evaluates or runs anything. */
|
|
15
|
+
export function analyzeScript(analysis, sf) {
|
|
16
|
+
const info = {
|
|
17
|
+
props: [],
|
|
18
|
+
emits: [],
|
|
19
|
+
slots: [],
|
|
20
|
+
exposed: [],
|
|
21
|
+
options: {},
|
|
22
|
+
inheritsFrom: [],
|
|
23
|
+
complete: true,
|
|
24
|
+
notes: [],
|
|
25
|
+
optionsApi: false,
|
|
26
|
+
imports: readImports(sf)
|
|
27
|
+
};
|
|
28
|
+
const state = { analysis, info };
|
|
29
|
+
for (const statement of sf.statements) {
|
|
30
|
+
if (ts.isVariableStatement(statement)) {
|
|
31
|
+
for (const d of statement.declarationList.declarations)
|
|
32
|
+
if (d.initializer)
|
|
33
|
+
macro(state, unwrap(d.initializer), d);
|
|
34
|
+
}
|
|
35
|
+
else if (ts.isExpressionStatement(statement))
|
|
36
|
+
macro(state, unwrap(statement.expression), undefined);
|
|
37
|
+
else if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
|
|
38
|
+
const options = optionsObject(unwrap(statement.expression));
|
|
39
|
+
if (options)
|
|
40
|
+
readOptions(state, options);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return info;
|
|
44
|
+
}
|
|
45
|
+
/** Analyzes `defineComponent({...})`/options-object declarations in a TS file (for non-SFC components). */
|
|
46
|
+
export function analyzeOptionsDeclaration(analysis, declaration) {
|
|
47
|
+
if (!declaration.initializer)
|
|
48
|
+
return undefined;
|
|
49
|
+
const options = optionsObject(unwrap(declaration.initializer));
|
|
50
|
+
if (!options)
|
|
51
|
+
return undefined;
|
|
52
|
+
const info = {
|
|
53
|
+
props: [],
|
|
54
|
+
emits: [],
|
|
55
|
+
slots: [],
|
|
56
|
+
exposed: [],
|
|
57
|
+
options: {},
|
|
58
|
+
inheritsFrom: [],
|
|
59
|
+
complete: true,
|
|
60
|
+
notes: [],
|
|
61
|
+
optionsApi: true,
|
|
62
|
+
imports: readImports(declaration.getSourceFile())
|
|
63
|
+
};
|
|
64
|
+
readOptions({ analysis, info }, options);
|
|
65
|
+
return info;
|
|
66
|
+
}
|
|
67
|
+
function macro(state, call, declaration) {
|
|
68
|
+
if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression))
|
|
69
|
+
return;
|
|
70
|
+
const name = call.expression.text;
|
|
71
|
+
const { info } = state;
|
|
72
|
+
if (name === "withDefaults") {
|
|
73
|
+
const inner = call.arguments[0] ? unwrap(call.arguments[0]) : undefined;
|
|
74
|
+
if (inner &&
|
|
75
|
+
ts.isCallExpression(inner) &&
|
|
76
|
+
ts.isIdentifier(inner.expression) &&
|
|
77
|
+
inner.expression.text === "defineProps") {
|
|
78
|
+
definePropsMacro(state, inner, defaultsFromObject(call.arguments[1]), declaration);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (name === "defineProps")
|
|
82
|
+
definePropsMacro(state, call, new Map(), declaration);
|
|
83
|
+
else if (name === "defineEmits")
|
|
84
|
+
defineEmitsMacro(state, call);
|
|
85
|
+
else if (name === "defineSlots")
|
|
86
|
+
defineSlotsMacro(state, call);
|
|
87
|
+
else if (name === "defineModel")
|
|
88
|
+
defineModelMacro(state, call);
|
|
89
|
+
else if (name === "defineExpose") {
|
|
90
|
+
const arg = call.arguments[0] ? unwrap(call.arguments[0]) : undefined;
|
|
91
|
+
if (arg && ts.isObjectLiteralExpression(arg))
|
|
92
|
+
info.exposed = arg.properties
|
|
93
|
+
.flatMap((p) => p.name && (ts.isIdentifier(p.name) || ts.isStringLiteralLike(p.name)) ? [p.name.text] : [])
|
|
94
|
+
.sort();
|
|
95
|
+
else if (arg)
|
|
96
|
+
info.notes.push("defineExpose argument is not an object literal");
|
|
97
|
+
}
|
|
98
|
+
else if (name === "defineOptions") {
|
|
99
|
+
const arg = call.arguments[0] ? unwrap(call.arguments[0]) : undefined;
|
|
100
|
+
if (arg && ts.isObjectLiteralExpression(arg))
|
|
101
|
+
readOptionMetadata(info, arg);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function definePropsMacro(state, call, withDefaults, declaration) {
|
|
105
|
+
const { analysis, info } = state;
|
|
106
|
+
const defaults = new Map(withDefaults);
|
|
107
|
+
// Reactive props destructure (Vue 3.5+): `const { size = "md" } = defineProps<…>()`.
|
|
108
|
+
if (declaration && ts.isObjectBindingPattern(declaration.name)) {
|
|
109
|
+
for (const element of declaration.name.elements) {
|
|
110
|
+
if (!element.initializer || element.dotDotDotToken)
|
|
111
|
+
continue;
|
|
112
|
+
const key = element.propertyName ?? element.name;
|
|
113
|
+
if (ts.isIdentifier(key) || ts.isStringLiteralLike(key))
|
|
114
|
+
defaults.set(key.text, specOf(element.initializer));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const typeArgument = call.typeArguments?.[0];
|
|
118
|
+
if (typeArgument) {
|
|
119
|
+
const type = analysis.checker.getTypeFromTypeNode(typeArgument);
|
|
120
|
+
if (type.flags & ts.TypeFlags.Any) {
|
|
121
|
+
info.complete = false;
|
|
122
|
+
info.notes.push("defineProps type did not resolve");
|
|
123
|
+
}
|
|
124
|
+
info.inheritsFrom.push(...externalBases(analysis, typeArgument));
|
|
125
|
+
for (const { symbol, declaration: decl } of ownProperties(analysis, type)) {
|
|
126
|
+
const member = describeMember(analysis, symbol, decl, { renderable: RENDERABLE });
|
|
127
|
+
const fallback = defaults.get(member.name);
|
|
128
|
+
info.props.push({
|
|
129
|
+
name: member.name,
|
|
130
|
+
typeText: member.typeText,
|
|
131
|
+
kind: member.kind,
|
|
132
|
+
required: member.required,
|
|
133
|
+
...(fallback?.value !== undefined ? { default: fallback.value } : {}),
|
|
134
|
+
...(fallback && fallback.value === undefined ? { defaultExpression: fallback.expression } : {}),
|
|
135
|
+
...(member.allowedValues ? { allowedValues: member.allowedValues } : {}),
|
|
136
|
+
...(member.description ? { description: member.description } : {}),
|
|
137
|
+
...(member.deprecated ? { deprecated: member.deprecated } : {}),
|
|
138
|
+
source: "type",
|
|
139
|
+
node: decl
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (info.inheritsFrom.length > 0)
|
|
143
|
+
info.complete = false;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const arg = call.arguments[0] ? unwrap(call.arguments[0]) : undefined;
|
|
147
|
+
if (arg)
|
|
148
|
+
readRuntimeProps(state, arg);
|
|
149
|
+
}
|
|
150
|
+
function specOf(expression) {
|
|
151
|
+
const value = literalValue(expression);
|
|
152
|
+
return {
|
|
153
|
+
...(value !== undefined ? { value } : {}),
|
|
154
|
+
expression: collapse(expression.getText()).slice(0, 200)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function defaultsFromObject(node) {
|
|
158
|
+
const out = new Map();
|
|
159
|
+
const object = node ? unwrap(node) : undefined;
|
|
160
|
+
if (!object || !ts.isObjectLiteralExpression(object))
|
|
161
|
+
return out;
|
|
162
|
+
for (const p of object.properties) {
|
|
163
|
+
if (!p.name || !(ts.isIdentifier(p.name) || ts.isStringLiteralLike(p.name)))
|
|
164
|
+
continue;
|
|
165
|
+
if (ts.isPropertyAssignment(p))
|
|
166
|
+
out.set(p.name.text, specOf(p.initializer));
|
|
167
|
+
else if (ts.isMethodDeclaration(p))
|
|
168
|
+
out.set(p.name.text, { expression: collapse(p.getText()).slice(0, 200) });
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
/** `props: ['a', 'b']` and `props: { a: String, b: { type: Number, default: 1 } }` (also the `defineProps(...)` argument). */
|
|
173
|
+
function readRuntimeProps(state, node) {
|
|
174
|
+
const { analysis, info } = state;
|
|
175
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
176
|
+
for (const element of node.elements) {
|
|
177
|
+
if (ts.isStringLiteralLike(element))
|
|
178
|
+
info.props.push({
|
|
179
|
+
name: element.text,
|
|
180
|
+
typeText: "unknown",
|
|
181
|
+
kind: "unknown",
|
|
182
|
+
required: false,
|
|
183
|
+
source: "array",
|
|
184
|
+
node: element
|
|
185
|
+
});
|
|
186
|
+
else
|
|
187
|
+
info.complete = false;
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (!ts.isObjectLiteralExpression(node)) {
|
|
192
|
+
info.complete = false;
|
|
193
|
+
info.notes.push("props declaration is not an object or array literal");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
for (const property of node.properties) {
|
|
197
|
+
if (!property.name ||
|
|
198
|
+
!(ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)) ||
|
|
199
|
+
ts.isSpreadAssignment(property)) {
|
|
200
|
+
info.complete = false;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const name = property.name.text;
|
|
204
|
+
// Keep `as PropType<…>` casts: they carry the type. Only parentheses are stripped.
|
|
205
|
+
const value = ts.isPropertyAssignment(property) ? withoutParens(property.initializer) : undefined;
|
|
206
|
+
const spec = value
|
|
207
|
+
? runtimeSpec(analysis, value)
|
|
208
|
+
: { typeText: "unknown", kind: "unknown", required: false };
|
|
209
|
+
info.props.push({ name, ...spec, source: "runtime", node: property });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function withoutParens(node) {
|
|
213
|
+
let current = node;
|
|
214
|
+
while (ts.isParenthesizedExpression(current))
|
|
215
|
+
current = current.expression;
|
|
216
|
+
return current;
|
|
217
|
+
}
|
|
218
|
+
function runtimeSpec(analysis, value) {
|
|
219
|
+
const base = { typeText: "unknown", kind: "unknown", required: false };
|
|
220
|
+
if (ts.isIdentifier(value))
|
|
221
|
+
return {
|
|
222
|
+
...base,
|
|
223
|
+
...(RUNTIME_KINDS[value.text]
|
|
224
|
+
? {
|
|
225
|
+
typeText: RUNTIME_KINDS[value.text].text,
|
|
226
|
+
kind: RUNTIME_KINDS[value.text].kind
|
|
227
|
+
}
|
|
228
|
+
: {})
|
|
229
|
+
};
|
|
230
|
+
if (ts.isAsExpression(value))
|
|
231
|
+
return typeFromCast(analysis, value, base);
|
|
232
|
+
if (ts.isArrayLiteralExpression(value)) {
|
|
233
|
+
const names = value.elements.flatMap((e) => ts.isIdentifier(e) && RUNTIME_KINDS[e.text] ? [RUNTIME_KINDS[e.text].text] : []);
|
|
234
|
+
return {
|
|
235
|
+
...base,
|
|
236
|
+
typeText: names.join(" | ") || "unknown",
|
|
237
|
+
kind: names.length > 1
|
|
238
|
+
? "union"
|
|
239
|
+
: names.length === 1
|
|
240
|
+
? RUNTIME_KINDS[value.elements[0].text].kind
|
|
241
|
+
: "unknown"
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
if (!ts.isObjectLiteralExpression(value))
|
|
245
|
+
return base;
|
|
246
|
+
const get = (key) => {
|
|
247
|
+
const p = value.properties.find((x) => ts.isPropertyAssignment(x) && x.name.getText() === key);
|
|
248
|
+
return p && ts.isPropertyAssignment(p) ? unwrap(p.initializer) : undefined;
|
|
249
|
+
};
|
|
250
|
+
// `type: String as PropType<…>`: the cast carries the type, so it must not be unwrapped.
|
|
251
|
+
const typeProperty = value.properties.find((x) => ts.isPropertyAssignment(x) && x.name.getText() === "type");
|
|
252
|
+
const typeExpression = typeProperty && ts.isPropertyAssignment(typeProperty) ? typeProperty.initializer : undefined;
|
|
253
|
+
const typed = typeExpression ? runtimeSpec(analysis, typeExpression) : base;
|
|
254
|
+
const requiredNode = get("required");
|
|
255
|
+
const defaultNode = get("default");
|
|
256
|
+
const fallback = defaultNode ? specOf(defaultNode) : undefined;
|
|
257
|
+
return {
|
|
258
|
+
...typed,
|
|
259
|
+
required: requiredNode?.kind === ts.SyntaxKind.TrueKeyword,
|
|
260
|
+
...(fallback?.value !== undefined ? { default: fallback.value } : {}),
|
|
261
|
+
...(fallback && fallback.value === undefined ? { defaultExpression: fallback.expression } : {})
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/** `String as PropType<"a" | "b">`. */
|
|
265
|
+
function typeFromCast(analysis, cast, base) {
|
|
266
|
+
const target = cast.type;
|
|
267
|
+
if (ts.isTypeReferenceNode(target) &&
|
|
268
|
+
target.typeName.getText().endsWith("PropType") &&
|
|
269
|
+
target.typeArguments?.[0]) {
|
|
270
|
+
const inner = target.typeArguments[0];
|
|
271
|
+
const text = collapse(inner.getText());
|
|
272
|
+
const checker = analysis.checker;
|
|
273
|
+
const type = checker.getTypeFromTypeNode(inner);
|
|
274
|
+
const allowed = literalDomain(checker, inner);
|
|
275
|
+
const kind = allowed && allowed.length > 0 && !allowed.every((v) => typeof v === "boolean")
|
|
276
|
+
? "enum"
|
|
277
|
+
: classifyKind(checker, type, text, RENDERABLE);
|
|
278
|
+
return {
|
|
279
|
+
...base,
|
|
280
|
+
typeText: text,
|
|
281
|
+
kind,
|
|
282
|
+
...(kind === "enum" && allowed && allowed.length > 0 ? { allowedValues: allowed } : {})
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return base;
|
|
286
|
+
}
|
|
287
|
+
function defineEmitsMacro(state, call) {
|
|
288
|
+
const { analysis, info } = state;
|
|
289
|
+
const typeArgument = call.typeArguments?.[0];
|
|
290
|
+
if (typeArgument) {
|
|
291
|
+
const checker = analysis.checker;
|
|
292
|
+
const type = checker.getTypeFromTypeNode(typeArgument);
|
|
293
|
+
// Call-signature form: (e: 'change', id: number): void
|
|
294
|
+
for (const signature of type.getCallSignatures()) {
|
|
295
|
+
const [first, ...rest] = signature.getParameters();
|
|
296
|
+
const eventType = first ? checker.getTypeOfSymbolAtLocation(first, typeArgument) : undefined;
|
|
297
|
+
if (!eventType?.isStringLiteral())
|
|
298
|
+
continue;
|
|
299
|
+
const payload = rest
|
|
300
|
+
.map((p) => `${p.name}: ${checker.typeToString(checker.getTypeOfSymbolAtLocation(p, typeArgument))}`)
|
|
301
|
+
.join(", ");
|
|
302
|
+
info.emits.push({
|
|
303
|
+
name: eventType.value,
|
|
304
|
+
...(payload ? { payloadText: payload } : {}),
|
|
305
|
+
source: "type",
|
|
306
|
+
node: typeArgument
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
// Named-tuple form (Vue 3.3+): change: [id: number]
|
|
310
|
+
for (const property of type.getProperties()) {
|
|
311
|
+
const propType = checker.getTypeOfSymbolAtLocation(property, typeArgument);
|
|
312
|
+
if (!checker.isTupleType(propType) && !checker.isArrayType(propType))
|
|
313
|
+
continue;
|
|
314
|
+
const text = checker.typeToString(propType);
|
|
315
|
+
info.emits.push({
|
|
316
|
+
name: property.name,
|
|
317
|
+
...(text !== "[]" ? { payloadText: text } : {}),
|
|
318
|
+
source: "type",
|
|
319
|
+
node: property.valueDeclaration ?? typeArgument
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const arg = call.arguments[0] ? unwrap(call.arguments[0]) : undefined;
|
|
325
|
+
if (arg)
|
|
326
|
+
readRuntimeEmits(info, arg);
|
|
327
|
+
}
|
|
328
|
+
function readRuntimeEmits(info, node) {
|
|
329
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
330
|
+
for (const e of node.elements) {
|
|
331
|
+
if (ts.isStringLiteralLike(e))
|
|
332
|
+
info.emits.push({ name: e.text, source: "array", node: e });
|
|
333
|
+
else
|
|
334
|
+
info.complete = false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else if (ts.isObjectLiteralExpression(node)) {
|
|
338
|
+
for (const p of node.properties) {
|
|
339
|
+
if (p.name && (ts.isIdentifier(p.name) || ts.isStringLiteralLike(p.name)))
|
|
340
|
+
info.emits.push({ name: p.name.text, source: "runtime", node: p });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
else
|
|
344
|
+
info.notes.push("emits declaration is not an array or object literal");
|
|
345
|
+
}
|
|
346
|
+
function defineSlotsMacro(state, call) {
|
|
347
|
+
const { analysis, info } = state;
|
|
348
|
+
const typeArgument = call.typeArguments?.[0];
|
|
349
|
+
if (!typeArgument)
|
|
350
|
+
return;
|
|
351
|
+
const checker = analysis.checker;
|
|
352
|
+
const type = checker.getTypeFromTypeNode(typeArgument);
|
|
353
|
+
for (const property of type.getProperties()) {
|
|
354
|
+
const propType = checker.getTypeOfSymbolAtLocation(property, typeArgument);
|
|
355
|
+
const parameter = propType.getCallSignatures()[0]?.getParameters()[0];
|
|
356
|
+
const propsText = parameter
|
|
357
|
+
? checker.typeToString(checker.getTypeOfSymbolAtLocation(parameter, typeArgument))
|
|
358
|
+
: undefined;
|
|
359
|
+
info.slots.push({
|
|
360
|
+
name: property.name,
|
|
361
|
+
...(propsText ? { propsText } : {}),
|
|
362
|
+
node: property.valueDeclaration ?? typeArgument
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/** `defineModel()` is a prop named `modelValue` (or the given name) plus an `update:<name>` event. */
|
|
367
|
+
function defineModelMacro(state, call) {
|
|
368
|
+
const { analysis, info } = state;
|
|
369
|
+
const first = call.arguments[0] ? unwrap(call.arguments[0]) : undefined;
|
|
370
|
+
const named = first && ts.isStringLiteralLike(first);
|
|
371
|
+
const name = named ? first.text : "modelValue";
|
|
372
|
+
const options = (named ? call.arguments[1] : call.arguments[0]);
|
|
373
|
+
const object = options ? unwrap(options) : undefined;
|
|
374
|
+
const checker = analysis.checker;
|
|
375
|
+
const typeArgument = call.typeArguments?.[0];
|
|
376
|
+
const typeText = typeArgument ? collapse(typeArgument.getText()) : "unknown";
|
|
377
|
+
let kind = "unknown";
|
|
378
|
+
let allowedValues;
|
|
379
|
+
if (typeArgument) {
|
|
380
|
+
const allowed = literalDomain(checker, typeArgument);
|
|
381
|
+
kind =
|
|
382
|
+
allowed && allowed.length > 0 && !allowed.every((v) => typeof v === "boolean")
|
|
383
|
+
? "enum"
|
|
384
|
+
: classifyKind(checker, checker.getTypeFromTypeNode(typeArgument), typeText, RENDERABLE);
|
|
385
|
+
if (kind === "enum")
|
|
386
|
+
allowedValues = allowed;
|
|
387
|
+
}
|
|
388
|
+
else if (object && ts.isObjectLiteralExpression(object)) {
|
|
389
|
+
const type = object.properties.find((p) => ts.isPropertyAssignment(p) && p.name.getText() === "type");
|
|
390
|
+
if (type && ts.isPropertyAssignment(type))
|
|
391
|
+
kind = runtimeSpec(analysis, unwrap(type.initializer)).kind;
|
|
392
|
+
}
|
|
393
|
+
let required = false;
|
|
394
|
+
let fallback;
|
|
395
|
+
if (object && ts.isObjectLiteralExpression(object)) {
|
|
396
|
+
for (const p of object.properties) {
|
|
397
|
+
if (!ts.isPropertyAssignment(p))
|
|
398
|
+
continue;
|
|
399
|
+
if (p.name.getText() === "required")
|
|
400
|
+
required = unwrap(p.initializer).kind === ts.SyntaxKind.TrueKeyword;
|
|
401
|
+
if (p.name.getText() === "default")
|
|
402
|
+
fallback = specOf(p.initializer);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
info.props.push({
|
|
406
|
+
name,
|
|
407
|
+
typeText,
|
|
408
|
+
kind,
|
|
409
|
+
required,
|
|
410
|
+
...(fallback?.value !== undefined ? { default: fallback.value } : {}),
|
|
411
|
+
...(fallback && fallback.value === undefined ? { defaultExpression: fallback.expression } : {}),
|
|
412
|
+
...(allowedValues ? { allowedValues } : {}),
|
|
413
|
+
source: "model",
|
|
414
|
+
node: call
|
|
415
|
+
});
|
|
416
|
+
info.emits.push({ name: `update:${name}`, payloadText: typeText, source: "model", node: call });
|
|
417
|
+
}
|
|
418
|
+
/** `export default { … }` and `defineComponent({ … })`. */
|
|
419
|
+
function optionsObject(node) {
|
|
420
|
+
if (ts.isObjectLiteralExpression(node))
|
|
421
|
+
return node;
|
|
422
|
+
if (ts.isCallExpression(node) &&
|
|
423
|
+
ts.isIdentifier(node.expression) &&
|
|
424
|
+
node.expression.text === "defineComponent" &&
|
|
425
|
+
node.arguments[0]) {
|
|
426
|
+
const arg = unwrap(node.arguments[0]);
|
|
427
|
+
return ts.isObjectLiteralExpression(arg) ? arg : undefined;
|
|
428
|
+
}
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
function readOptions(state, options) {
|
|
432
|
+
const { info } = state;
|
|
433
|
+
info.optionsApi = true;
|
|
434
|
+
readOptionMetadata(info, options);
|
|
435
|
+
for (const p of options.properties) {
|
|
436
|
+
if (!ts.isPropertyAssignment(p) || !p.name)
|
|
437
|
+
continue;
|
|
438
|
+
const key = p.name.getText().replace(/^["']|["']$/g, "");
|
|
439
|
+
if (key === "props")
|
|
440
|
+
readRuntimeProps(state, unwrap(p.initializer));
|
|
441
|
+
else if (key === "emits")
|
|
442
|
+
readRuntimeEmits(info, unwrap(p.initializer));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function readOptionMetadata(info, options) {
|
|
446
|
+
for (const p of options.properties) {
|
|
447
|
+
if (!ts.isPropertyAssignment(p) || !p.name)
|
|
448
|
+
continue;
|
|
449
|
+
const key = p.name.getText().replace(/^["']|["']$/g, "");
|
|
450
|
+
const value = unwrap(p.initializer);
|
|
451
|
+
if (key === "name" && ts.isStringLiteralLike(value))
|
|
452
|
+
info.options.name = value.text;
|
|
453
|
+
if (key === "inheritAttrs")
|
|
454
|
+
info.options.inheritAttrs = value.kind === ts.SyntaxKind.TrueKeyword;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function readImports(sf) {
|
|
458
|
+
const imports = new Map();
|
|
459
|
+
for (const statement of sf.statements) {
|
|
460
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
|
|
461
|
+
continue;
|
|
462
|
+
const clause = statement.importClause;
|
|
463
|
+
if (clause?.name)
|
|
464
|
+
imports.set(clause.name.text, statement.moduleSpecifier.text);
|
|
465
|
+
if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
466
|
+
for (const e of clause.namedBindings.elements)
|
|
467
|
+
imports.set(e.name.text, `${statement.moduleSpecifier.text}#${(e.propertyName ?? e.name).text}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return imports;
|
|
471
|
+
}
|
|
472
|
+
//# sourceMappingURL=script.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { componentNameFromFile } from "../compiler-core/index.js";
|
|
2
|
+
import { parse } from "@vue/compiler-sfc";
|
|
3
|
+
export function parseSfc(path, text) {
|
|
4
|
+
const { descriptor, errors } = parse(text, { filename: path, sourceMap: false });
|
|
5
|
+
const script = buildScript(path, descriptor);
|
|
6
|
+
return {
|
|
7
|
+
path,
|
|
8
|
+
descriptor,
|
|
9
|
+
errors: errors.map((e) => ("message" in e ? e.message : String(e))),
|
|
10
|
+
...(script ? { script } : {})
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function buildScript(path, descriptor) {
|
|
14
|
+
const blocks = [descriptor.script, descriptor.scriptSetup].filter((b) => b !== null);
|
|
15
|
+
// Blocks that load their content from another file (`<script src>`) are not read here.
|
|
16
|
+
const readable = blocks.filter((b) => !b.src);
|
|
17
|
+
if (readable.length === 0)
|
|
18
|
+
return undefined;
|
|
19
|
+
const segments = [];
|
|
20
|
+
let text = "";
|
|
21
|
+
let line = 1;
|
|
22
|
+
for (const block of readable) {
|
|
23
|
+
const lines = block.content.split("\n").length;
|
|
24
|
+
segments.push({ virtualStart: line, sfcStart: block.loc.start.line, lines });
|
|
25
|
+
text += `${block.content}\n`;
|
|
26
|
+
line += lines;
|
|
27
|
+
}
|
|
28
|
+
const lang = readable.map((b) => b.lang).find((l) => l === "tsx" || l === "jsx");
|
|
29
|
+
const ext = lang ? ".tsx" : ".ts";
|
|
30
|
+
return {
|
|
31
|
+
path: `${path}${ext}`,
|
|
32
|
+
text,
|
|
33
|
+
tsx: lang !== undefined,
|
|
34
|
+
lineOf(virtualLine) {
|
|
35
|
+
const segment = [...segments].reverse().find((s) => virtualLine >= s.virtualStart) ?? segments[0];
|
|
36
|
+
return segment ? segment.sfcStart + (virtualLine - segment.virtualStart) : virtualLine;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Display and link name of an SFC component: the PascalCase file name (`button-group.vue` is `ButtonGroup`). */
|
|
41
|
+
export function sfcLocalName(path) {
|
|
42
|
+
return componentNameFromFile(path);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=sfc.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Numeric node and element kinds of the Vue template AST (stable constants of the compiler).
|
|
2
|
+
const ELEMENT = 1;
|
|
3
|
+
const ATTRIBUTE = 6;
|
|
4
|
+
const DIRECTIVE = 7;
|
|
5
|
+
const TAG_ELEMENT = 0;
|
|
6
|
+
const TAG_SLOT = 2;
|
|
7
|
+
/** Reads slot outlets, component tags and static accessibility attributes from the parsed template. */
|
|
8
|
+
export function analyzeTemplate(descriptor) {
|
|
9
|
+
const info = { slots: [], components: [], accessibility: { roles: [], attributes: [] } };
|
|
10
|
+
const template = descriptor.template;
|
|
11
|
+
if (!template)
|
|
12
|
+
return info;
|
|
13
|
+
if (template.src)
|
|
14
|
+
return { ...info, skipped: "template loaded from an external file (src)" };
|
|
15
|
+
if (template.lang && template.lang !== "html")
|
|
16
|
+
return { ...info, skipped: `template language ${template.lang} is not analyzed` };
|
|
17
|
+
const root = template.ast;
|
|
18
|
+
if (!root)
|
|
19
|
+
return { ...info, skipped: "template did not parse" };
|
|
20
|
+
const components = new Map();
|
|
21
|
+
const roles = new Set();
|
|
22
|
+
const attributes = new Set();
|
|
23
|
+
const visit = (node) => {
|
|
24
|
+
if (node.type === ELEMENT && node.tag !== undefined) {
|
|
25
|
+
const props = node.props ?? [];
|
|
26
|
+
if (node.tagType === TAG_SLOT) {
|
|
27
|
+
const named = props.find((p) => p.type === ATTRIBUTE && p.name === "name");
|
|
28
|
+
const bound = props.find((p) => p.type === DIRECTIVE && p.name === "bind" && p.arg?.content === "name");
|
|
29
|
+
const slotProps = props
|
|
30
|
+
.flatMap((p) => p.type === ATTRIBUTE
|
|
31
|
+
? [p.name]
|
|
32
|
+
: p.type === DIRECTIVE && p.name === "bind" && p.arg?.isStatic
|
|
33
|
+
? [p.arg.content]
|
|
34
|
+
: [])
|
|
35
|
+
.filter((n) => n !== "name");
|
|
36
|
+
info.slots.push({
|
|
37
|
+
...(named?.value ? { name: named.value.content } : bound ? {} : { name: "default" }),
|
|
38
|
+
dynamic: bound !== undefined,
|
|
39
|
+
line: node.loc.start.line,
|
|
40
|
+
props: [...new Set(slotProps)].sort()
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else if (node.tagType === TAG_ELEMENT) {
|
|
44
|
+
for (const p of props) {
|
|
45
|
+
if (p.type === ATTRIBUTE && p.name === "role" && p.value)
|
|
46
|
+
roles.add(p.value.content);
|
|
47
|
+
const name = p.type === ATTRIBUTE
|
|
48
|
+
? p.name
|
|
49
|
+
: p.type === DIRECTIVE && p.name === "bind" && p.arg?.isStatic
|
|
50
|
+
? p.arg.content
|
|
51
|
+
: undefined;
|
|
52
|
+
if (name?.startsWith("aria-"))
|
|
53
|
+
attributes.add(name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (node.tagType !== undefined && node.tagType !== 3) {
|
|
57
|
+
const entry = components.get(node.tag) ?? { count: 0, line: node.loc.start.line };
|
|
58
|
+
entry.count++;
|
|
59
|
+
components.set(node.tag, entry);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const child of node.children ?? [])
|
|
63
|
+
if (typeof child === "object" && child !== null)
|
|
64
|
+
visit(child);
|
|
65
|
+
};
|
|
66
|
+
visit(root);
|
|
67
|
+
info.components = [...components]
|
|
68
|
+
.map(([tag, v]) => ({ tag, ...v }))
|
|
69
|
+
.sort((a, b) => (a.tag < b.tag ? -1 : 1));
|
|
70
|
+
info.accessibility = { roles: [...roles].sort(), attributes: [...attributes].sort() };
|
|
71
|
+
return info;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=template.js.map
|