@contentful/experience-design-system-cli 2.2.1
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/README.md +532 -0
- package/bin/cli.js +58 -0
- package/dist/package.json +56 -0
- package/dist/src/analyze/command.d.ts +3 -0
- package/dist/src/analyze/command.js +175 -0
- package/dist/src/analyze/extract/astro.d.ts +5 -0
- package/dist/src/analyze/extract/astro.js +280 -0
- package/dist/src/analyze/extract/pipeline.d.ts +6 -0
- package/dist/src/analyze/extract/pipeline.js +298 -0
- package/dist/src/analyze/extract/react.d.ts +2 -0
- package/dist/src/analyze/extract/react.js +1949 -0
- package/dist/src/analyze/extract/slot-detection.d.ts +35 -0
- package/dist/src/analyze/extract/slot-detection.js +101 -0
- package/dist/src/analyze/extract/stencil.d.ts +2 -0
- package/dist/src/analyze/extract/stencil.js +293 -0
- package/dist/src/analyze/extract/tsx-shared.d.ts +8 -0
- package/dist/src/analyze/extract/tsx-shared.js +263 -0
- package/dist/src/analyze/extract/vue-tsx.d.ts +2 -0
- package/dist/src/analyze/extract/vue-tsx.js +498 -0
- package/dist/src/analyze/extract/vue.d.ts +5 -0
- package/dist/src/analyze/extract/vue.js +647 -0
- package/dist/src/analyze/extract/web-components.d.ts +2 -0
- package/dist/src/analyze/extract/web-components.js +866 -0
- package/dist/src/analyze/pre-classify.d.ts +17 -0
- package/dist/src/analyze/pre-classify.js +144 -0
- package/dist/src/analyze/select/command.d.ts +2 -0
- package/dist/src/analyze/select/command.js +256 -0
- package/dist/src/analyze/select/index.d.ts +6 -0
- package/dist/src/analyze/select/index.js +5 -0
- package/dist/src/analyze/select/parser.d.ts +6 -0
- package/dist/src/analyze/select/parser.js +53 -0
- package/dist/src/analyze/select/persistence.d.ts +9 -0
- package/dist/src/analyze/select/persistence.js +42 -0
- package/dist/src/analyze/select/stdout.d.ts +7 -0
- package/dist/src/analyze/select/stdout.js +3 -0
- package/dist/src/analyze/select/tui/App.d.ts +8 -0
- package/dist/src/analyze/select/tui/App.js +491 -0
- package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +20 -0
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +43 -0
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/FieldEditor.js +531 -0
- package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +10 -0
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +15 -0
- package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +7 -0
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +11 -0
- package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/JsonEditor.js +154 -0
- package/dist/src/analyze/select/tui/components/JsonPanel.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/JsonPanel.js +62 -0
- package/dist/src/analyze/select/tui/components/PreviewSummaryBar.d.ts +8 -0
- package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +29 -0
- package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +8 -0
- package/dist/src/analyze/select/tui/components/QuitDialog.js +14 -0
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +15 -0
- package/dist/src/analyze/select/tui/components/Sidebar.js +48 -0
- package/dist/src/analyze/select/tui/components/SourcePanel.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/SourcePanel.js +52 -0
- package/dist/src/analyze/select/tui/components/StatusBar.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/StatusBar.js +6 -0
- package/dist/src/analyze/select/tui/components/TopBar.d.ts +10 -0
- package/dist/src/analyze/select/tui/components/TopBar.js +5 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +24 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +68 -0
- package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +24 -0
- package/dist/src/analyze/select/tui/hooks/useKeymap.js +67 -0
- package/dist/src/analyze/select/tui/hooks/useSession.d.ts +19 -0
- package/dist/src/analyze/select/tui/hooks/useSession.js +52 -0
- package/dist/src/analyze/select/tui/hooks/useUndo.d.ts +8 -0
- package/dist/src/analyze/select/tui/hooks/useUndo.js +26 -0
- package/dist/src/analyze/select/types.d.ts +46 -0
- package/dist/src/analyze/select/types.js +20 -0
- package/dist/src/analyze/select-agent/command.d.ts +2 -0
- package/dist/src/analyze/select-agent/command.js +208 -0
- package/dist/src/analyze/tui/AnalyzeView.d.ts +24 -0
- package/dist/src/analyze/tui/AnalyzeView.js +38 -0
- package/dist/src/apply/api-client.d.ts +35 -0
- package/dist/src/apply/api-client.js +143 -0
- package/dist/src/apply/command.d.ts +6 -0
- package/dist/src/apply/command.js +787 -0
- package/dist/src/apply/manifest.d.ts +1 -0
- package/dist/src/apply/manifest.js +1 -0
- package/dist/src/apply/tui/SelectView.d.ts +18 -0
- package/dist/src/apply/tui/SelectView.js +34 -0
- package/dist/src/apply/tui/ServerApplyView.d.ts +32 -0
- package/dist/src/apply/tui/ServerApplyView.js +42 -0
- package/dist/src/apply/tui/ServerPreviewView.d.ts +9 -0
- package/dist/src/apply/tui/ServerPreviewView.js +21 -0
- package/dist/src/credentials-store.d.ts +8 -0
- package/dist/src/credentials-store.js +30 -0
- package/dist/src/generate/agent-runner.d.ts +86 -0
- package/dist/src/generate/agent-runner.js +314 -0
- package/dist/src/generate/command.d.ts +2 -0
- package/dist/src/generate/command.js +545 -0
- package/dist/src/generate/edit/command.d.ts +2 -0
- package/dist/src/generate/edit/command.js +126 -0
- package/dist/src/generate/prompt-builder.d.ts +18 -0
- package/dist/src/generate/prompt-builder.js +202 -0
- package/dist/src/generate/tui/GenerateView.d.ts +12 -0
- package/dist/src/generate/tui/GenerateView.js +10 -0
- package/dist/src/import/command.d.ts +2 -0
- package/dist/src/import/command.js +96 -0
- package/dist/src/import/orchestrator.d.ts +37 -0
- package/dist/src/import/orchestrator.js +374 -0
- package/dist/src/import/path-utils.d.ts +15 -0
- package/dist/src/import/path-utils.js +30 -0
- package/dist/src/import/tui/WizardApp.d.ts +10 -0
- package/dist/src/import/tui/WizardApp.js +906 -0
- package/dist/src/import/tui/steps/CredentialsStep.d.ts +15 -0
- package/dist/src/import/tui/steps/CredentialsStep.js +79 -0
- package/dist/src/import/tui/steps/DoneStep.d.ts +20 -0
- package/dist/src/import/tui/steps/DoneStep.js +17 -0
- package/dist/src/import/tui/steps/ErrorStep.d.ts +8 -0
- package/dist/src/import/tui/steps/ErrorStep.js +11 -0
- package/dist/src/import/tui/steps/GateStep.d.ts +14 -0
- package/dist/src/import/tui/steps/GateStep.js +20 -0
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +8 -0
- package/dist/src/import/tui/steps/GenerateReviewStep.js +208 -0
- package/dist/src/import/tui/steps/PathValidationStep.d.ts +10 -0
- package/dist/src/import/tui/steps/PathValidationStep.js +151 -0
- package/dist/src/import/tui/steps/PreviewStep.d.ts +21 -0
- package/dist/src/import/tui/steps/PreviewStep.js +36 -0
- package/dist/src/import/tui/steps/RunningStep.d.ts +10 -0
- package/dist/src/import/tui/steps/RunningStep.js +20 -0
- package/dist/src/import/tui/steps/TokenInputStep.d.ts +8 -0
- package/dist/src/import/tui/steps/TokenInputStep.js +70 -0
- package/dist/src/import/tui/steps/WelcomeStep.d.ts +7 -0
- package/dist/src/import/tui/steps/WelcomeStep.js +33 -0
- package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +15 -0
- package/dist/src/import/tui/steps/WizardPreviewStep.js +121 -0
- package/dist/src/import/tui/steps/preview-diff.d.ts +10 -0
- package/dist/src/import/tui/steps/preview-diff.js +132 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/output/format.d.ts +23 -0
- package/dist/src/output/format.js +110 -0
- package/dist/src/print/command.d.ts +2 -0
- package/dist/src/print/command.js +199 -0
- package/dist/src/print/validate/tui/ValidateView.d.ts +15 -0
- package/dist/src/print/validate/tui/ValidateView.js +37 -0
- package/dist/src/print/validate/validators/cdf-validator.d.ts +2 -0
- package/dist/src/print/validate/validators/cdf-validator.js +104 -0
- package/dist/src/print/validate/validators/dtcg-validator.d.ts +2 -0
- package/dist/src/print/validate/validators/dtcg-validator.js +110 -0
- package/dist/src/print/validate/validators/format-errors.d.ts +12 -0
- package/dist/src/print/validate/validators/format-errors.js +18 -0
- package/dist/src/program.d.ts +2 -0
- package/dist/src/program.js +25 -0
- package/dist/src/session/command.d.ts +2 -0
- package/dist/src/session/command.js +261 -0
- package/dist/src/session/db.d.ts +111 -0
- package/dist/src/session/db.js +1114 -0
- package/dist/src/session/migration.d.ts +4 -0
- package/dist/src/session/migration.js +117 -0
- package/dist/src/session/session-id.d.ts +1 -0
- package/dist/src/session/session-id.js +212 -0
- package/dist/src/session/stats.d.ts +27 -0
- package/dist/src/session/stats.js +89 -0
- package/dist/src/setup/command.d.ts +2 -0
- package/dist/src/setup/command.js +765 -0
- package/dist/src/types.d.ts +48 -0
- package/dist/src/types.js +1 -0
- package/package.json +55 -0
- package/skills/generate-components.md +361 -0
- package/skills/generate-tokens.md +194 -0
- package/skills/select-components.md +180 -0
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
2
|
+
import { Project, Node, SyntaxKind } from 'ts-morph';
|
|
3
|
+
function kebabToPascal(input) {
|
|
4
|
+
return input
|
|
5
|
+
.split('-')
|
|
6
|
+
.filter(Boolean)
|
|
7
|
+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
|
8
|
+
.join('');
|
|
9
|
+
}
|
|
10
|
+
function normalizeComponentName(input) {
|
|
11
|
+
return input.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
function camelToKebab(input) {
|
|
14
|
+
return input
|
|
15
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
16
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
17
|
+
.toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
function kebabToCamel(input) {
|
|
20
|
+
return input.replace(/-([a-z0-9])/gi, (_, char) => char.toUpperCase());
|
|
21
|
+
}
|
|
22
|
+
function resolveDecoratorInterpolation(node) {
|
|
23
|
+
if (Node.isIdentifier(node)) {
|
|
24
|
+
const definitions = node.getDefinitions();
|
|
25
|
+
for (const definition of definitions) {
|
|
26
|
+
const declarationNode = definition.getDeclarationNode();
|
|
27
|
+
if (!declarationNode)
|
|
28
|
+
continue;
|
|
29
|
+
if (Node.isVariableDeclaration(declarationNode)) {
|
|
30
|
+
const initializer = declarationNode.getInitializer();
|
|
31
|
+
if (initializer && Node.isStringLiteral(initializer)) {
|
|
32
|
+
return initializer.getLiteralValue();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const text = node.getText();
|
|
37
|
+
if (/Prefix$/.test(text)) {
|
|
38
|
+
return camelToKebab(text.replace(/Prefix$/, ''));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function resolveDecoratorTagArgument(node) {
|
|
44
|
+
if (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node)) {
|
|
45
|
+
return node.getLiteralValue();
|
|
46
|
+
}
|
|
47
|
+
if (!Node.isTemplateExpression(node)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
let resolved = node.getHead().getLiteralText();
|
|
51
|
+
for (const span of node.getTemplateSpans()) {
|
|
52
|
+
const interpolation = resolveDecoratorInterpolation(span.getExpression());
|
|
53
|
+
if (!interpolation) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
resolved += interpolation;
|
|
57
|
+
resolved += span.getLiteral().getLiteralText();
|
|
58
|
+
}
|
|
59
|
+
return resolved;
|
|
60
|
+
}
|
|
61
|
+
function loadSourceFile(project, filePath) {
|
|
62
|
+
let sourceFile = project.getSourceFile(filePath);
|
|
63
|
+
if (sourceFile)
|
|
64
|
+
return sourceFile;
|
|
65
|
+
try {
|
|
66
|
+
sourceFile = project.addSourceFileAtPath(filePath);
|
|
67
|
+
return sourceFile;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function resolveStaticStringExpression(node, sourceFile, project) {
|
|
74
|
+
if (!node)
|
|
75
|
+
return null;
|
|
76
|
+
if (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node)) {
|
|
77
|
+
return node.getLiteralValue();
|
|
78
|
+
}
|
|
79
|
+
if (Node.isTemplateExpression(node)) {
|
|
80
|
+
let resolved = node.getHead().getLiteralText();
|
|
81
|
+
for (const span of node.getTemplateSpans()) {
|
|
82
|
+
const interpolation = resolveStaticStringExpression(span.getExpression(), sourceFile, project);
|
|
83
|
+
if (!interpolation)
|
|
84
|
+
return null;
|
|
85
|
+
resolved += interpolation;
|
|
86
|
+
resolved += span.getLiteral().getLiteralText();
|
|
87
|
+
}
|
|
88
|
+
return resolved;
|
|
89
|
+
}
|
|
90
|
+
if (Node.isIdentifier(node)) {
|
|
91
|
+
for (const definition of node.getDefinitions()) {
|
|
92
|
+
const declarationNode = definition.getDeclarationNode();
|
|
93
|
+
if (!declarationNode)
|
|
94
|
+
continue;
|
|
95
|
+
if (Node.isVariableDeclaration(declarationNode)) {
|
|
96
|
+
const resolved = resolveStaticStringExpression(declarationNode.getInitializer(), declarationNode.getSourceFile(), project);
|
|
97
|
+
if (resolved)
|
|
98
|
+
return resolved;
|
|
99
|
+
}
|
|
100
|
+
if (Node.isImportSpecifier(declarationNode)) {
|
|
101
|
+
const importDecl = declarationNode.getImportDeclaration();
|
|
102
|
+
const resolvedImportPath = resolveImportSourcePath(sourceFile.getFilePath(), importDecl.getModuleSpecifierValue());
|
|
103
|
+
if (!resolvedImportPath)
|
|
104
|
+
continue;
|
|
105
|
+
const importedFile = loadSourceFile(project, resolvedImportPath);
|
|
106
|
+
if (!importedFile)
|
|
107
|
+
continue;
|
|
108
|
+
const importedName = declarationNode.getNameNode().getText();
|
|
109
|
+
const importedDeclaration = importedFile.getVariableDeclaration(importedName);
|
|
110
|
+
if (!importedDeclaration)
|
|
111
|
+
continue;
|
|
112
|
+
const resolved = resolveStaticStringExpression(importedDeclaration.getInitializer(), importedFile, project);
|
|
113
|
+
if (resolved)
|
|
114
|
+
return resolved;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const text = node.getText();
|
|
118
|
+
if (/Prefix$/.test(text)) {
|
|
119
|
+
return camelToKebab(text.replace(/Prefix$/, ''));
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (Node.isPropertyAccessExpression(node)) {
|
|
124
|
+
const target = node.getExpression();
|
|
125
|
+
if (!Node.isIdentifier(target))
|
|
126
|
+
return null;
|
|
127
|
+
const propertyName = node.getName();
|
|
128
|
+
for (const definition of target.getDefinitions()) {
|
|
129
|
+
const declarationNode = definition.getDeclarationNode();
|
|
130
|
+
if (!declarationNode)
|
|
131
|
+
continue;
|
|
132
|
+
let variableDeclaration;
|
|
133
|
+
if (Node.isVariableDeclaration(declarationNode)) {
|
|
134
|
+
variableDeclaration = declarationNode;
|
|
135
|
+
}
|
|
136
|
+
else if (Node.isImportSpecifier(declarationNode)) {
|
|
137
|
+
const importDecl = declarationNode.getImportDeclaration();
|
|
138
|
+
const resolvedImportPath = resolveImportSourcePath(sourceFile.getFilePath(), importDecl.getModuleSpecifierValue());
|
|
139
|
+
if (!resolvedImportPath)
|
|
140
|
+
continue;
|
|
141
|
+
const importedFile = loadSourceFile(project, resolvedImportPath);
|
|
142
|
+
if (!importedFile)
|
|
143
|
+
continue;
|
|
144
|
+
const importedName = declarationNode.getNameNode().getText();
|
|
145
|
+
variableDeclaration = importedFile.getVariableDeclaration(importedName);
|
|
146
|
+
}
|
|
147
|
+
if (!variableDeclaration)
|
|
148
|
+
continue;
|
|
149
|
+
let objectLiteral = variableDeclaration.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
|
|
150
|
+
const initializer = variableDeclaration.getInitializer();
|
|
151
|
+
if (!objectLiteral &&
|
|
152
|
+
initializer &&
|
|
153
|
+
Node.isCallExpression(initializer) &&
|
|
154
|
+
initializer.getExpression().getText() === 'Object.freeze') {
|
|
155
|
+
objectLiteral = initializer.getArguments()[0]?.asKind(SyntaxKind.ObjectLiteralExpression) ?? undefined;
|
|
156
|
+
}
|
|
157
|
+
const property = objectLiteral
|
|
158
|
+
?.getProperties()
|
|
159
|
+
.find((prop) => Node.isPropertyAssignment(prop) && prop.getName() === propertyName);
|
|
160
|
+
if (!property)
|
|
161
|
+
continue;
|
|
162
|
+
const resolved = resolveStaticStringExpression(property.getInitializer(), variableDeclaration.getSourceFile(), project);
|
|
163
|
+
if (resolved)
|
|
164
|
+
return resolved;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
function shouldPreferTagName(className, tagName) {
|
|
170
|
+
const pascalTagName = kebabToPascal(tagName);
|
|
171
|
+
if (className.startsWith('HTML') && className.endsWith('Element'))
|
|
172
|
+
return true;
|
|
173
|
+
if (className.endsWith('Element'))
|
|
174
|
+
return true;
|
|
175
|
+
if (normalizeComponentName(className) === normalizeComponentName(pascalTagName))
|
|
176
|
+
return false;
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
function getFileDerivedComponentName(sourceFile) {
|
|
180
|
+
const baseName = basename(sourceFile.getFilePath())
|
|
181
|
+
.replace(/\.[^.]+$/, '')
|
|
182
|
+
.replace(/\.component$/, '');
|
|
183
|
+
return kebabToPascal(baseName);
|
|
184
|
+
}
|
|
185
|
+
function chooseComponentName(classDecl, className, tagName) {
|
|
186
|
+
if (!tagName)
|
|
187
|
+
return className;
|
|
188
|
+
const tagDerivedName = kebabToPascal(tagName);
|
|
189
|
+
const fileDerivedName = getFileDerivedComponentName(classDecl.getSourceFile());
|
|
190
|
+
if (normalizeComponentName(className) === normalizeComponentName(tagDerivedName)) {
|
|
191
|
+
return className;
|
|
192
|
+
}
|
|
193
|
+
if (normalizeComponentName(className) === normalizeComponentName(fileDerivedName)) {
|
|
194
|
+
return className;
|
|
195
|
+
}
|
|
196
|
+
if (fileDerivedName && tagDerivedName.endsWith(fileDerivedName)) {
|
|
197
|
+
return fileDerivedName;
|
|
198
|
+
}
|
|
199
|
+
if (shouldPreferTagName(className, tagName)) {
|
|
200
|
+
return tagDerivedName;
|
|
201
|
+
}
|
|
202
|
+
return className;
|
|
203
|
+
}
|
|
204
|
+
function extractSlotsFromTemplate(templateContent) {
|
|
205
|
+
const slots = new Map();
|
|
206
|
+
const slotRegex = /<slot\b([^>]*)>/g;
|
|
207
|
+
for (const match of templateContent.matchAll(slotRegex)) {
|
|
208
|
+
const attrs = match[1] ?? '';
|
|
209
|
+
const nameMatch = attrs.match(/\bname=["']([^"']+)["']/);
|
|
210
|
+
const name = nameMatch?.[1] ?? 'default';
|
|
211
|
+
slots.set(name, name === 'default');
|
|
212
|
+
}
|
|
213
|
+
return [...slots.entries()]
|
|
214
|
+
.map(([name, isDefault]) => ({ name, isDefault }))
|
|
215
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
216
|
+
}
|
|
217
|
+
function mergeSlotLists(...slotLists) {
|
|
218
|
+
const merged = new Map();
|
|
219
|
+
for (const slotList of slotLists) {
|
|
220
|
+
for (const slot of slotList) {
|
|
221
|
+
const existing = merged.get(slot.name);
|
|
222
|
+
if (!existing) {
|
|
223
|
+
merged.set(slot.name, slot);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
merged.set(slot.name, {
|
|
227
|
+
...existing,
|
|
228
|
+
...slot,
|
|
229
|
+
description: existing.description ?? slot.description,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
234
|
+
}
|
|
235
|
+
function extractJsDocSlots(classDecl) {
|
|
236
|
+
const slots = [];
|
|
237
|
+
for (const jsDoc of classDecl.getJsDocs()) {
|
|
238
|
+
for (const tag of jsDoc.getTags()) {
|
|
239
|
+
if (tag.getTagName() !== 'slot')
|
|
240
|
+
continue;
|
|
241
|
+
const comment = tag.getCommentText()?.trim();
|
|
242
|
+
if (!comment)
|
|
243
|
+
continue;
|
|
244
|
+
let name = 'default';
|
|
245
|
+
let description = comment;
|
|
246
|
+
if (comment.startsWith('-')) {
|
|
247
|
+
description = comment.slice(1).trim();
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
const match = comment.match(/^(\S+)\s*-\s*(.*)$/s);
|
|
251
|
+
if (match) {
|
|
252
|
+
name = match[1];
|
|
253
|
+
description = match[2].trim();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
slots.push({
|
|
257
|
+
name,
|
|
258
|
+
isDefault: name === 'default',
|
|
259
|
+
...(description && { description }),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return mergeSlotLists(slots);
|
|
264
|
+
}
|
|
265
|
+
function extractObservedAttributes(classDecl) {
|
|
266
|
+
const props = [];
|
|
267
|
+
const getter = classDecl.getGetAccessor('observedAttributes');
|
|
268
|
+
if (!getter || !getter.isStatic())
|
|
269
|
+
return props;
|
|
270
|
+
const body = getter.getBody();
|
|
271
|
+
if (!body)
|
|
272
|
+
return props;
|
|
273
|
+
// Find the return statement containing an array literal
|
|
274
|
+
body.forEachDescendant((node) => {
|
|
275
|
+
if (!Node.isReturnStatement(node))
|
|
276
|
+
return;
|
|
277
|
+
const expression = node.getExpression();
|
|
278
|
+
if (!expression || !Node.isArrayLiteralExpression(expression))
|
|
279
|
+
return;
|
|
280
|
+
for (const element of expression.getElements()) {
|
|
281
|
+
if (Node.isStringLiteral(element)) {
|
|
282
|
+
const attrName = element.getLiteralValue();
|
|
283
|
+
props.push({
|
|
284
|
+
name: attrName,
|
|
285
|
+
type: 'string',
|
|
286
|
+
required: false,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
return props;
|
|
292
|
+
}
|
|
293
|
+
function hasInternalJsDocTag(member) {
|
|
294
|
+
return member.getJsDocs().some((doc) => doc.getTags().some((tag) => tag.getTagName() === 'internal'));
|
|
295
|
+
}
|
|
296
|
+
const NON_PUBLIC_LIT_DECORATORS = new Set(['consume', 'provide', 'query', 'queryAsync', 'state']);
|
|
297
|
+
const INTERNAL_RUNTIME_FIELD_NAMES = new Set(['dir', 'initialReflectedProperties', 'lang']);
|
|
298
|
+
function isNonPublicLitMember(member) {
|
|
299
|
+
return member.getDecorators().some((decorator) => NON_PUBLIC_LIT_DECORATORS.has(decorator.getName()));
|
|
300
|
+
}
|
|
301
|
+
function hasShoelaceRuntimeBookkeepingField(classDecl) {
|
|
302
|
+
return classDecl.getProperties().some((property) => property.getName() === 'initialReflectedProperties');
|
|
303
|
+
}
|
|
304
|
+
function isInternalRuntimeField(name, applyRuntimeFieldDenylist) {
|
|
305
|
+
return applyRuntimeFieldDenylist && INTERNAL_RUNTIME_FIELD_NAMES.has(name);
|
|
306
|
+
}
|
|
307
|
+
function getExplicitLitPropertyAttributeName(property) {
|
|
308
|
+
const decorator = property.getDecorators().find((candidate) => candidate.getName() === 'property');
|
|
309
|
+
const firstArg = decorator?.getArguments()[0];
|
|
310
|
+
if (!firstArg || !Node.isObjectLiteralExpression(firstArg)) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const attributeProp = firstArg.getProperty('attribute');
|
|
314
|
+
if (!attributeProp || !Node.isPropertyAssignment(attributeProp)) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
const initializer = attributeProp.getInitializer();
|
|
318
|
+
if (!initializer || !Node.isStringLiteral(initializer)) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
return initializer.getLiteralValue();
|
|
322
|
+
}
|
|
323
|
+
function extractClassProperties(classDecl, applyRuntimeFieldDenylist = false) {
|
|
324
|
+
const props = [];
|
|
325
|
+
for (const property of classDecl.getProperties()) {
|
|
326
|
+
if (property.isStatic())
|
|
327
|
+
continue;
|
|
328
|
+
if (hasInternalJsDocTag(property))
|
|
329
|
+
continue;
|
|
330
|
+
if (isNonPublicLitMember(property))
|
|
331
|
+
continue;
|
|
332
|
+
const name = property.getName();
|
|
333
|
+
if (name.startsWith('#') || isInternalRuntimeField(name, applyRuntimeFieldDenylist))
|
|
334
|
+
continue;
|
|
335
|
+
const scope = property.getScope();
|
|
336
|
+
if (scope === 'private' || scope === 'protected')
|
|
337
|
+
continue;
|
|
338
|
+
const initializer = property.getInitializer();
|
|
339
|
+
const hasDecorators = property.getDecorators().length > 0;
|
|
340
|
+
// Skip undecorated arrow-function properties (internal event handlers / callbacks)
|
|
341
|
+
if (!hasDecorators && initializer && Node.isArrowFunction(initializer)) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
// Skip properties initialized with this.attachInternals() (ElementInternals)
|
|
345
|
+
if (initializer &&
|
|
346
|
+
Node.isCallExpression(initializer) &&
|
|
347
|
+
initializer.getExpression().getText() === 'this.attachInternals') {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const typeNode = property.getTypeNode();
|
|
351
|
+
const type = typeNode ? typeNode.getText() : 'any';
|
|
352
|
+
const publicName = name.startsWith('_') && hasDecorators ? getExplicitLitPropertyAttributeName(property) : null;
|
|
353
|
+
let defaultValue;
|
|
354
|
+
if (initializer) {
|
|
355
|
+
if (Node.isStringLiteral(initializer)) {
|
|
356
|
+
defaultValue = initializer.getLiteralValue();
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
defaultValue = initializer.getText();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
props.push({
|
|
363
|
+
name: publicName ? kebabToCamel(publicName) : name,
|
|
364
|
+
type,
|
|
365
|
+
required: false,
|
|
366
|
+
...(defaultValue !== undefined && { defaultValue }),
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
return props;
|
|
370
|
+
}
|
|
371
|
+
function extractAccessorProperties(classDecl, applyRuntimeFieldDenylist = false) {
|
|
372
|
+
const props = [];
|
|
373
|
+
const accessors = new Map();
|
|
374
|
+
for (const getter of classDecl.getGetAccessors()) {
|
|
375
|
+
if (getter.isStatic())
|
|
376
|
+
continue;
|
|
377
|
+
if (hasInternalJsDocTag(getter))
|
|
378
|
+
continue;
|
|
379
|
+
if (isNonPublicLitMember(getter))
|
|
380
|
+
continue;
|
|
381
|
+
const name = getter.getName();
|
|
382
|
+
if (name === 'observedAttributes' ||
|
|
383
|
+
name === 'template' ||
|
|
384
|
+
name.startsWith('#') ||
|
|
385
|
+
isInternalRuntimeField(name, applyRuntimeFieldDenylist))
|
|
386
|
+
continue;
|
|
387
|
+
const scope = getter.getScope();
|
|
388
|
+
if (scope === 'private' || scope === 'protected')
|
|
389
|
+
continue;
|
|
390
|
+
const entry = accessors.get(name) ?? {};
|
|
391
|
+
entry.getter = getter;
|
|
392
|
+
accessors.set(name, entry);
|
|
393
|
+
}
|
|
394
|
+
for (const setter of classDecl.getSetAccessors()) {
|
|
395
|
+
if (setter.isStatic())
|
|
396
|
+
continue;
|
|
397
|
+
if (hasInternalJsDocTag(setter))
|
|
398
|
+
continue;
|
|
399
|
+
if (isNonPublicLitMember(setter))
|
|
400
|
+
continue;
|
|
401
|
+
const name = setter.getName();
|
|
402
|
+
if (name === 'observedAttributes' ||
|
|
403
|
+
name === 'template' ||
|
|
404
|
+
name.startsWith('#') ||
|
|
405
|
+
isInternalRuntimeField(name, applyRuntimeFieldDenylist))
|
|
406
|
+
continue;
|
|
407
|
+
const scope = setter.getScope();
|
|
408
|
+
if (scope === 'private' || scope === 'protected')
|
|
409
|
+
continue;
|
|
410
|
+
const entry = accessors.get(name) ?? {};
|
|
411
|
+
entry.setter = setter;
|
|
412
|
+
accessors.set(name, entry);
|
|
413
|
+
}
|
|
414
|
+
for (const [name, { getter, setter }] of accessors) {
|
|
415
|
+
const hasPropertyDecorator = getter?.getDecorators().some((decorator) => decorator.getName() === 'property') ||
|
|
416
|
+
setter?.getDecorators().some((decorator) => decorator.getName() === 'property');
|
|
417
|
+
if (!hasPropertyDecorator && !(getter && setter))
|
|
418
|
+
continue;
|
|
419
|
+
const type = getter?.getReturnTypeNode()?.getText() ?? setter?.getParameters()[0]?.getTypeNode()?.getText() ?? 'any';
|
|
420
|
+
props.push({
|
|
421
|
+
name,
|
|
422
|
+
type,
|
|
423
|
+
required: false,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return props;
|
|
427
|
+
}
|
|
428
|
+
function extractJsDocAttributeProps(classDecl) {
|
|
429
|
+
const props = [];
|
|
430
|
+
for (const doc of classDecl.getJsDocs()) {
|
|
431
|
+
for (const tag of doc.getTags()) {
|
|
432
|
+
if (tag.getTagName() !== 'attribute')
|
|
433
|
+
continue;
|
|
434
|
+
const tagComment = tag.getCommentText();
|
|
435
|
+
const normalizedComment = Array.isArray(tagComment) ? tagComment.join(' ') : tagComment;
|
|
436
|
+
const match = normalizedComment?.match(/(?:\{([^}]+)\}\s+)?([^\s]+)(?:\s*-\s*([\s\S]*))?/);
|
|
437
|
+
if (!match)
|
|
438
|
+
continue;
|
|
439
|
+
const [, type, name, description] = match;
|
|
440
|
+
if (!name || name.startsWith('#'))
|
|
441
|
+
continue;
|
|
442
|
+
props.push({
|
|
443
|
+
name,
|
|
444
|
+
type: type?.trim() || 'any',
|
|
445
|
+
required: false,
|
|
446
|
+
...(description ? { description: description.replace(/\s+/g, ' ').trim() } : {}),
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return props;
|
|
451
|
+
}
|
|
452
|
+
function mergePropLists(...propLists) {
|
|
453
|
+
const merged = new Map();
|
|
454
|
+
for (const propList of propLists) {
|
|
455
|
+
for (const prop of propList) {
|
|
456
|
+
merged.set(prop.name, prop);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
460
|
+
}
|
|
461
|
+
function mergeProps(observed, classProps) {
|
|
462
|
+
return mergePropLists(observed, classProps);
|
|
463
|
+
}
|
|
464
|
+
function collectHtmlTaggedTemplates(root) {
|
|
465
|
+
const templates = [];
|
|
466
|
+
root.forEachDescendant((node) => {
|
|
467
|
+
if (!Node.isTaggedTemplateExpression(node))
|
|
468
|
+
return;
|
|
469
|
+
const tag = node.getTag();
|
|
470
|
+
if (tag.getText() !== 'html')
|
|
471
|
+
return;
|
|
472
|
+
const template = node.getTemplate();
|
|
473
|
+
if (Node.isNoSubstitutionTemplateLiteral(template) || Node.isTemplateExpression(template)) {
|
|
474
|
+
templates.push(template.getText().slice(1, -1));
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
return templates;
|
|
478
|
+
}
|
|
479
|
+
function collectHtmlTaggedTemplatesWithHelpers(root, project) {
|
|
480
|
+
const templates = [...collectHtmlTaggedTemplates(root)];
|
|
481
|
+
const seenTemplateHelpers = new Set();
|
|
482
|
+
root.forEachDescendant((node) => {
|
|
483
|
+
if (!Node.isCallExpression(node))
|
|
484
|
+
return;
|
|
485
|
+
for (const declaration of resolveTemplateHelperDeclarations(node, project)) {
|
|
486
|
+
const helperKey = `${declaration.getSourceFile().getFilePath()}:${declaration.getStart()}`;
|
|
487
|
+
if (seenTemplateHelpers.has(helperKey))
|
|
488
|
+
continue;
|
|
489
|
+
seenTemplateHelpers.add(helperKey);
|
|
490
|
+
templates.push(...collectHtmlTaggedTemplates(declaration));
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
return templates;
|
|
494
|
+
}
|
|
495
|
+
function resolveTemplateHelperDeclarations(callExpression, project) {
|
|
496
|
+
const expression = callExpression.getExpression();
|
|
497
|
+
if (!Node.isIdentifier(expression))
|
|
498
|
+
return [];
|
|
499
|
+
const declarations = [];
|
|
500
|
+
for (const definition of expression.getDefinitions()) {
|
|
501
|
+
const declarationNode = definition.getDeclarationNode();
|
|
502
|
+
if (!declarationNode)
|
|
503
|
+
continue;
|
|
504
|
+
if (Node.isFunctionDeclaration(declarationNode) || Node.isVariableDeclaration(declarationNode)) {
|
|
505
|
+
declarations.push(declarationNode);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
if (!Node.isImportSpecifier(declarationNode))
|
|
509
|
+
continue;
|
|
510
|
+
const importDecl = declarationNode.getImportDeclaration();
|
|
511
|
+
const resolvedImportPath = resolveImportSourcePath(callExpression.getSourceFile().getFilePath(), importDecl.getModuleSpecifierValue());
|
|
512
|
+
if (!resolvedImportPath)
|
|
513
|
+
continue;
|
|
514
|
+
const importedFile = loadSourceFile(project, resolvedImportPath);
|
|
515
|
+
if (!importedFile)
|
|
516
|
+
continue;
|
|
517
|
+
const importedName = declarationNode.getNameNode().getText();
|
|
518
|
+
const importedFunction = importedFile.getFunction(importedName);
|
|
519
|
+
if (importedFunction) {
|
|
520
|
+
declarations.push(importedFunction);
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
const importedVariable = importedFile.getVariableDeclaration(importedName);
|
|
524
|
+
if (importedVariable) {
|
|
525
|
+
declarations.push(importedVariable);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return declarations;
|
|
529
|
+
}
|
|
530
|
+
function extractTemplateContent(classDecl, project) {
|
|
531
|
+
const templates = [];
|
|
532
|
+
// Strategy 1: innerHTML assignment in methods (connectedCallback, etc.)
|
|
533
|
+
classDecl.forEachDescendant((node) => {
|
|
534
|
+
if (!Node.isBinaryExpression(node))
|
|
535
|
+
return;
|
|
536
|
+
const leftText = node.getLeft().getText();
|
|
537
|
+
if (!leftText.includes('.innerHTML'))
|
|
538
|
+
return;
|
|
539
|
+
const right = node.getRight();
|
|
540
|
+
if (Node.isTemplateExpression(right) || Node.isNoSubstitutionTemplateLiteral(right)) {
|
|
541
|
+
templates.push(right.getText().slice(1, -1)); // strip surrounding backticks
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
// Strategy 2: Lit render() method with html tagged template
|
|
545
|
+
const renderMethod = classDecl.getMethod('render');
|
|
546
|
+
if (renderMethod && !renderMethod.isStatic()) {
|
|
547
|
+
templates.push(...collectHtmlTaggedTemplatesWithHelpers(renderMethod, project));
|
|
548
|
+
}
|
|
549
|
+
// Strategy 3: Polymer static get template() with html tagged template
|
|
550
|
+
const templateGetter = classDecl.getGetAccessor('template');
|
|
551
|
+
if (templateGetter?.isStatic()) {
|
|
552
|
+
templateGetter.forEachDescendant((node) => {
|
|
553
|
+
if (Node.isTaggedTemplateExpression(node)) {
|
|
554
|
+
const tag = node.getTag();
|
|
555
|
+
if (tag.getText() === 'html') {
|
|
556
|
+
const template = node.getTemplate();
|
|
557
|
+
if (Node.isNoSubstitutionTemplateLiteral(template)) {
|
|
558
|
+
templates.push(template.getText().slice(1, -1));
|
|
559
|
+
}
|
|
560
|
+
else if (Node.isTemplateExpression(template)) {
|
|
561
|
+
templates.push(template.getText().slice(1, -1));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
return templates.join('\n');
|
|
568
|
+
}
|
|
569
|
+
function extractFastTemplateSlots(classDecl, project) {
|
|
570
|
+
const sourceFilePath = classDecl.getSourceFile().getFilePath();
|
|
571
|
+
if (!sourceFilePath.endsWith('.ts') || sourceFilePath.endsWith('.template.ts')) {
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
574
|
+
const templatePath = `${sourceFilePath.slice(0, -'.ts'.length)}.template.ts`;
|
|
575
|
+
const templateFile = loadSourceFile(project, templatePath);
|
|
576
|
+
if (!templateFile) {
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
579
|
+
const fragments = [];
|
|
580
|
+
fragments.push(...collectHtmlTaggedTemplatesWithHelpers(templateFile, project));
|
|
581
|
+
return extractSlotsFromTemplate(fragments.join('\n'));
|
|
582
|
+
}
|
|
583
|
+
function getElementTagNameFromJsDoc(classDecl) {
|
|
584
|
+
for (const doc of classDecl.getJsDocs()) {
|
|
585
|
+
for (const tag of doc.getTags()) {
|
|
586
|
+
if (tag.getTagName() !== 'element')
|
|
587
|
+
continue;
|
|
588
|
+
const comment = tag.getCommentText();
|
|
589
|
+
if (comment) {
|
|
590
|
+
return comment.trim();
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
function getElementTagNameFromDecorator(classDecl) {
|
|
597
|
+
for (const decorator of classDecl.getDecorators()) {
|
|
598
|
+
if (decorator.getName() !== 'customElement')
|
|
599
|
+
continue;
|
|
600
|
+
const firstArg = decorator.getArguments()[0];
|
|
601
|
+
if (!firstArg)
|
|
602
|
+
continue;
|
|
603
|
+
const resolved = resolveDecoratorTagArgument(firstArg);
|
|
604
|
+
if (resolved) {
|
|
605
|
+
return resolved;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
function getElementTagNameFromSiblingDefine(classDecl, project) {
|
|
611
|
+
const sourceFilePath = classDecl.getSourceFile().getFilePath();
|
|
612
|
+
if (!sourceFilePath.endsWith('.component.ts')) {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
const siblingPath = `${sourceFilePath.slice(0, -'.component.ts'.length)}.ts`;
|
|
616
|
+
let siblingFile = project.getSourceFile(siblingPath);
|
|
617
|
+
if (!siblingFile) {
|
|
618
|
+
try {
|
|
619
|
+
siblingFile = project.addSourceFileAtPath(siblingPath);
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const className = classDecl.getName();
|
|
626
|
+
if (!className)
|
|
627
|
+
return null;
|
|
628
|
+
for (const node of siblingFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
629
|
+
const expression = node.getExpression();
|
|
630
|
+
if (!Node.isPropertyAccessExpression(expression))
|
|
631
|
+
continue;
|
|
632
|
+
if (expression.getName() !== 'define')
|
|
633
|
+
continue;
|
|
634
|
+
if (expression.getExpression().getText() !== className)
|
|
635
|
+
continue;
|
|
636
|
+
const tagArg = node.getArguments()[0];
|
|
637
|
+
if (!tagArg || !Node.isStringLiteral(tagArg))
|
|
638
|
+
continue;
|
|
639
|
+
return tagArg.getLiteralValue();
|
|
640
|
+
}
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
function getElementTagNameFromFastDefinition(classDecl, project) {
|
|
644
|
+
const sourceFilePath = classDecl.getSourceFile().getFilePath();
|
|
645
|
+
if (!sourceFilePath.endsWith('.ts') || sourceFilePath.endsWith('.definition.ts')) {
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
const definitionPath = `${sourceFilePath.slice(0, -'.ts'.length)}.definition.ts`;
|
|
649
|
+
const definitionFile = loadSourceFile(project, definitionPath);
|
|
650
|
+
if (!definitionFile) {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
for (const callExpr of definitionFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
654
|
+
const expression = callExpr.getExpression();
|
|
655
|
+
if (!Node.isPropertyAccessExpression(expression))
|
|
656
|
+
continue;
|
|
657
|
+
if (expression.getName() !== 'compose')
|
|
658
|
+
continue;
|
|
659
|
+
const options = callExpr.getArguments()[0];
|
|
660
|
+
if (!options || !Node.isObjectLiteralExpression(options))
|
|
661
|
+
continue;
|
|
662
|
+
const nameProperty = options
|
|
663
|
+
.getProperties()
|
|
664
|
+
.find((prop) => Node.isPropertyAssignment(prop) && (prop.getName() === 'name' || prop.getName() === 'baseName'));
|
|
665
|
+
if (!nameProperty)
|
|
666
|
+
continue;
|
|
667
|
+
const resolved = resolveStaticStringExpression(nameProperty.getInitializer(), definitionFile, project);
|
|
668
|
+
if (resolved) {
|
|
669
|
+
return resolved;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
function resolveImportSourcePath(fromFilePath, specifier) {
|
|
675
|
+
if (specifier.startsWith('.')) {
|
|
676
|
+
const resolvedPath = resolve(dirname(fromFilePath), specifier);
|
|
677
|
+
if (resolvedPath.endsWith('.js')) {
|
|
678
|
+
return `${resolvedPath.slice(0, -3)}.ts`;
|
|
679
|
+
}
|
|
680
|
+
return resolvedPath;
|
|
681
|
+
}
|
|
682
|
+
const spectrumPrefix = '@spectrum-web-components/core/components/';
|
|
683
|
+
if (!specifier.startsWith(spectrumPrefix)) {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
const packagesMarker = `${join('2nd-gen', 'packages')}${fromFilePath.includes('\\') ? '\\' : '/'}`;
|
|
687
|
+
const markerIndex = fromFilePath.lastIndexOf(packagesMarker);
|
|
688
|
+
if (markerIndex === -1) {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
const packagesRoot = fromFilePath.slice(0, markerIndex + packagesMarker.length - 1);
|
|
692
|
+
const componentPath = specifier.slice(spectrumPrefix.length);
|
|
693
|
+
return join(packagesRoot, 'core', 'components', componentPath, 'index.ts');
|
|
694
|
+
}
|
|
695
|
+
function getImportedBaseClass(classDecl, project, visitedFiles) {
|
|
696
|
+
const extendsClause = classDecl
|
|
697
|
+
.getHeritageClauses()
|
|
698
|
+
.find((clause) => clause.getToken() === SyntaxKind.ExtendsKeyword);
|
|
699
|
+
const typeNode = extendsClause?.getTypeNodes()[0];
|
|
700
|
+
if (!typeNode)
|
|
701
|
+
return null;
|
|
702
|
+
const expression = typeNode.getExpression();
|
|
703
|
+
if (!Node.isIdentifier(expression))
|
|
704
|
+
return null;
|
|
705
|
+
const baseName = expression.getText();
|
|
706
|
+
const importDecl = classDecl
|
|
707
|
+
.getSourceFile()
|
|
708
|
+
.getImportDeclarations()
|
|
709
|
+
.find((decl) => decl.getNamedImports().some((namedImport) => namedImport.getName() === baseName) ||
|
|
710
|
+
decl.getDefaultImport()?.getText() === baseName);
|
|
711
|
+
if (!importDecl)
|
|
712
|
+
return null;
|
|
713
|
+
const resolvedImportPath = resolveImportSourcePath(classDecl.getSourceFile().getFilePath(), importDecl.getModuleSpecifierValue());
|
|
714
|
+
if (!resolvedImportPath || visitedFiles.has(resolvedImportPath)) {
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
visitedFiles.add(resolvedImportPath);
|
|
718
|
+
let sourceFile = project.getSourceFile(resolvedImportPath);
|
|
719
|
+
if (!sourceFile) {
|
|
720
|
+
try {
|
|
721
|
+
sourceFile = project.addSourceFileAtPath(resolvedImportPath);
|
|
722
|
+
}
|
|
723
|
+
catch {
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const directClass = sourceFile.getClass(baseName);
|
|
728
|
+
if (directClass) {
|
|
729
|
+
return directClass;
|
|
730
|
+
}
|
|
731
|
+
for (const exportDecl of sourceFile.getExportDeclarations()) {
|
|
732
|
+
const moduleSpecifier = exportDecl.getModuleSpecifierValue();
|
|
733
|
+
if (!moduleSpecifier)
|
|
734
|
+
continue;
|
|
735
|
+
const reexportPath = resolveImportSourcePath(sourceFile.getFilePath(), moduleSpecifier);
|
|
736
|
+
if (!reexportPath || visitedFiles.has(reexportPath))
|
|
737
|
+
continue;
|
|
738
|
+
visitedFiles.add(reexportPath);
|
|
739
|
+
let reexportFile = project.getSourceFile(reexportPath);
|
|
740
|
+
if (!reexportFile) {
|
|
741
|
+
try {
|
|
742
|
+
reexportFile = project.addSourceFileAtPath(reexportPath);
|
|
743
|
+
}
|
|
744
|
+
catch {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const reexportedClass = reexportFile.getClass(baseName);
|
|
749
|
+
if (reexportedClass) {
|
|
750
|
+
return reexportedClass;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
function extractInheritedClassProperties(classDecl, project, visitedFiles = new Set(), applyRuntimeFieldDenylist = false) {
|
|
756
|
+
const baseClass = getImportedBaseClass(classDecl, project, visitedFiles);
|
|
757
|
+
if (!baseClass) {
|
|
758
|
+
return [];
|
|
759
|
+
}
|
|
760
|
+
const nextApplyRuntimeFieldDenylist = applyRuntimeFieldDenylist || hasShoelaceRuntimeBookkeepingField(baseClass);
|
|
761
|
+
return mergePropLists(extractInheritedClassProperties(baseClass, project, visitedFiles, nextApplyRuntimeFieldDenylist), extractJsDocAttributeProps(baseClass), extractAccessorProperties(baseClass, nextApplyRuntimeFieldDenylist), extractClassProperties(baseClass, nextApplyRuntimeFieldDenylist));
|
|
762
|
+
}
|
|
763
|
+
function extractInheritedSlots(classDecl, project, visitedFiles = new Set()) {
|
|
764
|
+
const baseClass = getImportedBaseClass(classDecl, project, visitedFiles);
|
|
765
|
+
if (!baseClass) {
|
|
766
|
+
return [];
|
|
767
|
+
}
|
|
768
|
+
return mergeSlotLists(extractInheritedSlots(baseClass, project, visitedFiles), extractJsDocSlots(baseClass));
|
|
769
|
+
}
|
|
770
|
+
function buildTagNameMap(sourceFile) {
|
|
771
|
+
// Maps class name → tag name from customElements.define('tag-name', ClassName) calls
|
|
772
|
+
const tagNameMap = new Map();
|
|
773
|
+
sourceFile.forEachDescendant((node) => {
|
|
774
|
+
if (!Node.isCallExpression(node))
|
|
775
|
+
return;
|
|
776
|
+
const expr = node.getExpression();
|
|
777
|
+
if (!Node.isPropertyAccessExpression(expr))
|
|
778
|
+
return;
|
|
779
|
+
if (expr.getName() !== 'define')
|
|
780
|
+
return;
|
|
781
|
+
const obj = expr.getExpression();
|
|
782
|
+
if (obj.getText() !== 'customElements')
|
|
783
|
+
return;
|
|
784
|
+
const args = node.getArguments();
|
|
785
|
+
if (args.length < 2)
|
|
786
|
+
return;
|
|
787
|
+
const tagArg = args[0];
|
|
788
|
+
const classArg = args[1];
|
|
789
|
+
if (!Node.isStringLiteral(tagArg))
|
|
790
|
+
return;
|
|
791
|
+
const tagName = tagArg.getLiteralValue();
|
|
792
|
+
const className = classArg.getText();
|
|
793
|
+
tagNameMap.set(className, tagName);
|
|
794
|
+
});
|
|
795
|
+
return tagNameMap;
|
|
796
|
+
}
|
|
797
|
+
function extractFromSourceFile(sourceFile, project) {
|
|
798
|
+
const components = [];
|
|
799
|
+
const tagNameMap = buildTagNameMap(sourceFile);
|
|
800
|
+
for (const classDecl of sourceFile.getClasses()) {
|
|
801
|
+
const siblingDefinedTagName = getElementTagNameFromSiblingDefine(classDecl, project);
|
|
802
|
+
const className = classDecl.getName();
|
|
803
|
+
if (!className)
|
|
804
|
+
continue;
|
|
805
|
+
// Prefer tag name from customElements.define() converted to PascalCase; fall back to class name
|
|
806
|
+
const decoratorTagName = getElementTagNameFromDecorator(classDecl);
|
|
807
|
+
const jsDocTagName = getElementTagNameFromJsDoc(classDecl);
|
|
808
|
+
const fastDefinitionTagName = getElementTagNameFromFastDefinition(classDecl, project);
|
|
809
|
+
const tagName = tagNameMap.get(className) ??
|
|
810
|
+
siblingDefinedTagName ??
|
|
811
|
+
decoratorTagName ??
|
|
812
|
+
jsDocTagName ??
|
|
813
|
+
fastDefinitionTagName ??
|
|
814
|
+
undefined;
|
|
815
|
+
if (!tagName)
|
|
816
|
+
continue;
|
|
817
|
+
const name = chooseComponentName(classDecl, className, tagName);
|
|
818
|
+
const observedAttrs = extractObservedAttributes(classDecl);
|
|
819
|
+
const inheritedProps = extractInheritedClassProperties(classDecl, project);
|
|
820
|
+
const classProps = mergePropLists(extractJsDocAttributeProps(classDecl), extractAccessorProperties(classDecl), extractClassProperties(classDecl));
|
|
821
|
+
const props = mergeProps(observedAttrs, mergePropLists(inheritedProps, classProps));
|
|
822
|
+
const templateContent = extractTemplateContent(classDecl, project);
|
|
823
|
+
const slots = mergeSlotLists(extractInheritedSlots(classDecl, project), extractJsDocSlots(classDecl), extractSlotsFromTemplate(templateContent), extractFastTemplateSlots(classDecl, project));
|
|
824
|
+
components.push({
|
|
825
|
+
name,
|
|
826
|
+
source: sourceFile.getFilePath(),
|
|
827
|
+
framework: 'web-component',
|
|
828
|
+
props,
|
|
829
|
+
slots,
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
return components;
|
|
833
|
+
}
|
|
834
|
+
export async function extractWebComponentDefinitions(filePaths) {
|
|
835
|
+
const tsFiles = filePaths.filter((f) => /\.[jt]s$/.test(f) && !f.endsWith('.d.ts'));
|
|
836
|
+
if (tsFiles.length === 0) {
|
|
837
|
+
return { components: [], warnings: [] };
|
|
838
|
+
}
|
|
839
|
+
const project = new Project({
|
|
840
|
+
compilerOptions: {
|
|
841
|
+
target: 99, // ScriptTarget.ESNext
|
|
842
|
+
module: 99, // ModuleKind.ESNext
|
|
843
|
+
moduleResolution: 100, // ModuleResolutionKind.Bundler
|
|
844
|
+
skipLibCheck: true,
|
|
845
|
+
},
|
|
846
|
+
skipAddingFilesFromTsConfig: true,
|
|
847
|
+
});
|
|
848
|
+
for (const filePath of tsFiles) {
|
|
849
|
+
project.addSourceFileAtPath(filePath);
|
|
850
|
+
}
|
|
851
|
+
const warnings = [];
|
|
852
|
+
const components = [];
|
|
853
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
854
|
+
try {
|
|
855
|
+
const extracted = extractFromSourceFile(sourceFile, project);
|
|
856
|
+
components.push(...extracted);
|
|
857
|
+
}
|
|
858
|
+
catch (e) {
|
|
859
|
+
warnings.push(`Failed to extract from ${sourceFile.getFilePath()}: ${e instanceof Error ? e.message : String(e)}`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return {
|
|
863
|
+
components: components.sort((a, b) => a.name.localeCompare(b.name)),
|
|
864
|
+
warnings,
|
|
865
|
+
};
|
|
866
|
+
}
|