@onemrvapublic/design-system-mcp-server 2.0.1-rc.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 +420 -0
- package/dist/component-parser.d.ts +79 -0
- package/dist/component-parser.js +646 -0
- package/dist/component-parser.js.map +1 -0
- package/dist/component-scanner.d.ts +81 -0
- package/dist/component-scanner.js +444 -0
- package/dist/component-scanner.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +999 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-shared.d.ts +141 -0
- package/dist/mcp-shared.js +519 -0
- package/dist/mcp-shared.js.map +1 -0
- package/dist/metrics.d.ts +64 -0
- package/dist/metrics.js +271 -0
- package/dist/metrics.js.map +1 -0
- package/dist/migration-rules.d.ts +29 -0
- package/dist/migration-rules.js +862 -0
- package/dist/migration-rules.js.map +1 -0
- package/dist/tool-components.d.ts +23 -0
- package/dist/tool-components.js +816 -0
- package/dist/tool-components.js.map +1 -0
- package/dist/tool-design.d.ts +41 -0
- package/dist/tool-design.js +618 -0
- package/dist/tool-design.js.map +1 -0
- package/dist/tool-migration.d.ts +5 -0
- package/dist/tool-migration.js +976 -0
- package/dist/tool-migration.js.map +1 -0
- package/dist/tool-registry.d.ts +3 -0
- package/dist/tool-registry.js +458 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/tools.d.ts +13 -0
- package/dist/tools.js +349 -0
- package/dist/tools.js.map +1 -0
- package/mcp-data/codebase.json +4809 -0
- package/mcp-data/components.json +129424 -0
- package/mcp-data/icons.json +470 -0
- package/mcp-data/icons_sorted.txt +2 -0
- package/mcp-data/mapping.json +309 -0
- package/mcp-data/styles.json +331 -0
- package/mcp-data/sync-status.json +83 -0
- package/mcp-data/tokens.json +1586 -0
- package/package.json +77 -0
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
export class ComponentParser {
|
|
2
|
+
parse(content, filePath, htmlContent = '') {
|
|
3
|
+
const componentMatch = content.match(/@Component\s*\(\s*\{([\s\S]*?)\}\s*\)/);
|
|
4
|
+
if (!componentMatch)
|
|
5
|
+
return null;
|
|
6
|
+
const decorator = componentMatch[1];
|
|
7
|
+
const selectorMatch = decorator.match(/selector:\s*['"]([^'"]+)['"]/);
|
|
8
|
+
const selector = selectorMatch ? selectorMatch[1] : 'unknown';
|
|
9
|
+
const standalone = /standalone:\s*true/.test(decorator);
|
|
10
|
+
const importsMatch = decorator.match(/imports:\s*\[([^\]]+)\]/s);
|
|
11
|
+
const imports = importsMatch
|
|
12
|
+
? importsMatch[1]
|
|
13
|
+
.split(',')
|
|
14
|
+
.map(i => i.trim())
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
: [];
|
|
17
|
+
const classJsdoc = this.extractClassJSDoc(content);
|
|
18
|
+
const description = classJsdoc.description || `Angular component: ${selector}`;
|
|
19
|
+
const inputs = this.extractInputs(content);
|
|
20
|
+
const outputs = this.extractOutputs(content);
|
|
21
|
+
const methods = this.extractMethods(content);
|
|
22
|
+
const slots = this.extractSlots(htmlContent);
|
|
23
|
+
const accessibility = this.extractAccessibilityInfo(content, htmlContent);
|
|
24
|
+
const hostListeners = this.extractHostListeners(content);
|
|
25
|
+
const queryChildren = this.extractQueryChildren(content);
|
|
26
|
+
const hasEffects = /\beffect\s*\(/.test(content);
|
|
27
|
+
return {
|
|
28
|
+
selector,
|
|
29
|
+
description,
|
|
30
|
+
inputs,
|
|
31
|
+
outputs,
|
|
32
|
+
methods,
|
|
33
|
+
slots,
|
|
34
|
+
hostListeners,
|
|
35
|
+
queryChildren,
|
|
36
|
+
hasEffects,
|
|
37
|
+
standalone,
|
|
38
|
+
imports,
|
|
39
|
+
accessibility,
|
|
40
|
+
keywords: classJsdoc.keywords,
|
|
41
|
+
group: classJsdoc.group,
|
|
42
|
+
themes: classJsdoc.themes,
|
|
43
|
+
deprecated: classJsdoc.deprecated,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
extractClassJSDoc(content) {
|
|
47
|
+
const componentAt = content.search(/@Component\s*\(/);
|
|
48
|
+
const exportClassAt = content.search(/export\s+class\s+/);
|
|
49
|
+
const anchorAt = componentAt !== -1
|
|
50
|
+
? componentAt
|
|
51
|
+
: exportClassAt !== -1
|
|
52
|
+
? exportClassAt
|
|
53
|
+
: -1;
|
|
54
|
+
if (anchorAt === -1) {
|
|
55
|
+
return { description: '', keywords: [], group: '', themes: [] };
|
|
56
|
+
}
|
|
57
|
+
const before = content.substring(0, anchorAt);
|
|
58
|
+
const jsdocEndIdx = before.lastIndexOf('*/');
|
|
59
|
+
if (jsdocEndIdx === -1) {
|
|
60
|
+
return { description: '', keywords: [], group: '', themes: [] };
|
|
61
|
+
}
|
|
62
|
+
const jsdocStartIdx = before.lastIndexOf('/**', jsdocEndIdx);
|
|
63
|
+
if (jsdocStartIdx === -1) {
|
|
64
|
+
return { description: '', keywords: [], group: '', themes: [] };
|
|
65
|
+
}
|
|
66
|
+
const between = before.substring(jsdocEndIdx + 2).trim();
|
|
67
|
+
const linesBetween = (between.match(/\n/g) || []).length;
|
|
68
|
+
if (linesBetween > 8) {
|
|
69
|
+
return { description: '', keywords: [], group: '', themes: [] };
|
|
70
|
+
}
|
|
71
|
+
const rawJsdoc = content.substring(jsdocStartIdx + 3, jsdocEndIdx);
|
|
72
|
+
const lines = rawJsdoc
|
|
73
|
+
.split('\n')
|
|
74
|
+
.map(line => line.replace(/^\s*\*\s?/, ''));
|
|
75
|
+
const firstBlock = lines.slice(0, 6).join(' ').toLowerCase();
|
|
76
|
+
if (firstBlock.includes('copyright') ||
|
|
77
|
+
firstBlock.includes('all rights reserved') ||
|
|
78
|
+
firstBlock.includes('mit license') ||
|
|
79
|
+
firstBlock.includes('apache license') ||
|
|
80
|
+
firstBlock.includes('licensed under')) {
|
|
81
|
+
return { description: '', keywords: [], group: '', themes: [] };
|
|
82
|
+
}
|
|
83
|
+
let description = '';
|
|
84
|
+
const keywords = [];
|
|
85
|
+
let group = '';
|
|
86
|
+
const themes = [];
|
|
87
|
+
let deprecated;
|
|
88
|
+
let inCodeBlock = false;
|
|
89
|
+
let inExample = false;
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
const trimmed = line.trim();
|
|
92
|
+
if (trimmed.startsWith('```')) {
|
|
93
|
+
inCodeBlock = !inCodeBlock;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (inCodeBlock)
|
|
97
|
+
continue;
|
|
98
|
+
if (trimmed.startsWith('@example')) {
|
|
99
|
+
inExample = true;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (inExample) {
|
|
103
|
+
if (trimmed.startsWith('@')) {
|
|
104
|
+
inExample = false;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
continue; // inside @example body — skip
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (trimmed.startsWith('@MatKeywords')) {
|
|
111
|
+
const value = trimmed.replace('@MatKeywords', '').trim();
|
|
112
|
+
keywords.push(...value
|
|
113
|
+
.split(',')
|
|
114
|
+
.map(k => k.trim())
|
|
115
|
+
.filter(Boolean));
|
|
116
|
+
}
|
|
117
|
+
else if (trimmed.startsWith('@MatGroup')) {
|
|
118
|
+
group = trimmed.replace('@MatGroup', '').trim();
|
|
119
|
+
}
|
|
120
|
+
else if (trimmed.startsWith('@MatTheme')) {
|
|
121
|
+
const value = trimmed.replace('@MatTheme', '').trim();
|
|
122
|
+
themes.push(...value
|
|
123
|
+
.split(',')
|
|
124
|
+
.map(t => t.trim())
|
|
125
|
+
.filter(Boolean));
|
|
126
|
+
}
|
|
127
|
+
else if (trimmed.startsWith('@deprecated')) {
|
|
128
|
+
deprecated =
|
|
129
|
+
trimmed.replace('@deprecated', '').trim() ||
|
|
130
|
+
'This component is deprecated';
|
|
131
|
+
}
|
|
132
|
+
else if (!trimmed.startsWith('@') && trimmed) {
|
|
133
|
+
if (description)
|
|
134
|
+
description += ' ';
|
|
135
|
+
description += trimmed;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
description: description.trim(),
|
|
140
|
+
keywords,
|
|
141
|
+
group,
|
|
142
|
+
themes,
|
|
143
|
+
deprecated,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
extractInputs(content) {
|
|
147
|
+
const results = [];
|
|
148
|
+
const inputPattern = /((?:@\w+\([^)]*\)\s*)*)@Input\(([^)]*)\)\s*(?:public\s+|private\s+|protected\s+)?(\w+)(?:\?|!)?(?::\s*([^;=]+?))?(?:\s*=\s*([^;]+))?;/g;
|
|
149
|
+
let match;
|
|
150
|
+
while ((match = inputPattern.exec(content)) !== null) {
|
|
151
|
+
const jsdocBlock = this.extractNearbyPropertyJSDoc(content, match.index);
|
|
152
|
+
const inputOptions = match[2] || '';
|
|
153
|
+
const name = match[3];
|
|
154
|
+
const explicitType = match[4]?.trim();
|
|
155
|
+
const defaultValue = match[5]?.trim();
|
|
156
|
+
if (jsdocBlock.includes('@hidden') || jsdocBlock.includes('@internal')) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const { description, examples, deprecated } = this.parsePropertyJSDoc(jsdocBlock);
|
|
160
|
+
const required = inputOptions.includes('required: true');
|
|
161
|
+
const type = explicitType || this.inferTypeFromValue(defaultValue) || 'any';
|
|
162
|
+
const cleanedType = this.cleanType(type);
|
|
163
|
+
const resolvedDescription = description ||
|
|
164
|
+
this.buildInputFallbackDescription({
|
|
165
|
+
name,
|
|
166
|
+
type: cleanedType,
|
|
167
|
+
required,
|
|
168
|
+
defaultValue,
|
|
169
|
+
});
|
|
170
|
+
results.push({
|
|
171
|
+
name,
|
|
172
|
+
type: cleanedType,
|
|
173
|
+
description: resolvedDescription,
|
|
174
|
+
defaultValue,
|
|
175
|
+
required,
|
|
176
|
+
deprecated,
|
|
177
|
+
examples,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const signalInputPattern = /(?:public\s+|private\s+|protected\s+)?(?:readonly\s+)?(\w+)\s*=\s*input(?:\.required)?<([^>]+)>\s*\(([^)]*)\)/g;
|
|
181
|
+
while ((match = signalInputPattern.exec(content)) !== null) {
|
|
182
|
+
const jsdocBlock = this.extractNearbyPropertyJSDoc(content, match.index);
|
|
183
|
+
const name = match[1];
|
|
184
|
+
const type = match[2]?.trim() || 'any';
|
|
185
|
+
const defaultValue = match[3]?.trim() || undefined;
|
|
186
|
+
if (jsdocBlock.includes('@hidden') || jsdocBlock.includes('@internal')) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const { description, examples, deprecated } = this.parsePropertyJSDoc(jsdocBlock);
|
|
190
|
+
const required = content.includes(`${name} = input.required`);
|
|
191
|
+
const cleanedType = this.cleanType(type);
|
|
192
|
+
const resolvedDescription = description ||
|
|
193
|
+
this.buildInputFallbackDescription({
|
|
194
|
+
name,
|
|
195
|
+
type: cleanedType,
|
|
196
|
+
required,
|
|
197
|
+
defaultValue,
|
|
198
|
+
});
|
|
199
|
+
results.push({
|
|
200
|
+
name,
|
|
201
|
+
type: cleanedType,
|
|
202
|
+
description: resolvedDescription,
|
|
203
|
+
defaultValue: defaultValue || undefined,
|
|
204
|
+
required,
|
|
205
|
+
deprecated,
|
|
206
|
+
examples,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const signalInputNoTypePattern = /(?:public\s+|private\s+|protected\s+)?(?:readonly\s+)?(\w+)\s*=\s*input(?:\.required)?\s*\(([^)]*)\)/g;
|
|
210
|
+
while ((match = signalInputNoTypePattern.exec(content)) !== null) {
|
|
211
|
+
const jsdocBlock = this.extractNearbyPropertyJSDoc(content, match.index);
|
|
212
|
+
const name = match[1];
|
|
213
|
+
const defaultValue = match[2]?.trim() || undefined;
|
|
214
|
+
if (results.some(r => r.name === name))
|
|
215
|
+
continue;
|
|
216
|
+
if (jsdocBlock.includes('@hidden') || jsdocBlock.includes('@internal')) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const { description, examples, deprecated } = this.parsePropertyJSDoc(jsdocBlock);
|
|
220
|
+
const required = content.includes(`${name} = input.required`);
|
|
221
|
+
const type = this.inferTypeFromValue(defaultValue) || 'any';
|
|
222
|
+
const resolvedDescription = description ||
|
|
223
|
+
this.buildInputFallbackDescription({
|
|
224
|
+
name,
|
|
225
|
+
type,
|
|
226
|
+
required,
|
|
227
|
+
defaultValue,
|
|
228
|
+
});
|
|
229
|
+
results.push({
|
|
230
|
+
name,
|
|
231
|
+
type,
|
|
232
|
+
description: resolvedDescription,
|
|
233
|
+
defaultValue: defaultValue || undefined,
|
|
234
|
+
required,
|
|
235
|
+
deprecated,
|
|
236
|
+
examples,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const modelPattern = /(?:public\s+|private\s+|protected\s+)?(?:readonly\s+)?(\w+)\s*=\s*model(?:\.required)?(?:<([^>]+)>)?\s*\(([^)]*)\)/g;
|
|
240
|
+
while ((match = modelPattern.exec(content)) !== null) {
|
|
241
|
+
const jsdocBlock = this.extractNearbyPropertyJSDoc(content, match.index);
|
|
242
|
+
const name = match[1];
|
|
243
|
+
const explicitType = match[2]?.trim();
|
|
244
|
+
const defaultValue = match[3]?.trim() || undefined;
|
|
245
|
+
if (jsdocBlock.includes('@hidden') || jsdocBlock.includes('@internal')) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const { description, examples, deprecated } = this.parsePropertyJSDoc(jsdocBlock);
|
|
249
|
+
const required = content.includes(`${name} = model.required`);
|
|
250
|
+
const type = explicitType || this.inferTypeFromValue(defaultValue) || 'any';
|
|
251
|
+
const cleanedType = this.cleanType(type);
|
|
252
|
+
const resolvedDescription = description ||
|
|
253
|
+
`${this.buildInputFallbackDescription({
|
|
254
|
+
name,
|
|
255
|
+
type: cleanedType,
|
|
256
|
+
required,
|
|
257
|
+
defaultValue,
|
|
258
|
+
})} Two-way binding signal (model).`;
|
|
259
|
+
results.push({
|
|
260
|
+
name,
|
|
261
|
+
type: cleanedType,
|
|
262
|
+
description: resolvedDescription,
|
|
263
|
+
defaultValue: defaultValue || undefined,
|
|
264
|
+
required,
|
|
265
|
+
deprecated,
|
|
266
|
+
examples,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return results;
|
|
270
|
+
}
|
|
271
|
+
extractOutputs(content) {
|
|
272
|
+
const results = [];
|
|
273
|
+
const outputPattern = /@Output\(([^)]*)\)\s*(?:public\s+|private\s+|protected\s+)?(\w+)(?:\?|!)?(?::\s*([^;=]+?))?(?:\s*=\s*([^;]+))?;/g;
|
|
274
|
+
let match;
|
|
275
|
+
while ((match = outputPattern.exec(content)) !== null) {
|
|
276
|
+
const jsdocBlock = this.extractNearbyPropertyJSDoc(content, match.index);
|
|
277
|
+
const name = match[2];
|
|
278
|
+
const type = match[3]?.trim() || 'EventEmitter<any>';
|
|
279
|
+
if (jsdocBlock.includes('@hidden') || jsdocBlock.includes('@internal')) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const { description, deprecated } = this.parsePropertyJSDoc(jsdocBlock);
|
|
283
|
+
const cleanedType = this.cleanType(type);
|
|
284
|
+
const resolvedDescription = description ||
|
|
285
|
+
this.buildOutputFallbackDescription({ name, type: cleanedType });
|
|
286
|
+
results.push({
|
|
287
|
+
name,
|
|
288
|
+
type: cleanedType,
|
|
289
|
+
description: resolvedDescription,
|
|
290
|
+
deprecated,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const signalOutputPattern = /(?:public\s+|private\s+|protected\s+)?(\w+)\s*=\s*output<([^>]+)>\s*\(\)/g;
|
|
294
|
+
while ((match = signalOutputPattern.exec(content)) !== null) {
|
|
295
|
+
const jsdocBlock = this.extractNearbyPropertyJSDoc(content, match.index);
|
|
296
|
+
const name = match[1];
|
|
297
|
+
const type = match[2]?.trim() || 'any';
|
|
298
|
+
if (jsdocBlock.includes('@hidden') || jsdocBlock.includes('@internal')) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const { description, deprecated } = this.parsePropertyJSDoc(jsdocBlock);
|
|
302
|
+
const emittedType = `OutputEmitter<${type}>`;
|
|
303
|
+
const resolvedDescription = description ||
|
|
304
|
+
this.buildOutputFallbackDescription({
|
|
305
|
+
name,
|
|
306
|
+
type: emittedType,
|
|
307
|
+
});
|
|
308
|
+
results.push({
|
|
309
|
+
name,
|
|
310
|
+
type: emittedType,
|
|
311
|
+
description: resolvedDescription,
|
|
312
|
+
deprecated,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return results;
|
|
316
|
+
}
|
|
317
|
+
extractNearbyPropertyJSDoc(content, anchorIndex) {
|
|
318
|
+
const before = content.slice(0, anchorIndex);
|
|
319
|
+
const jsdocEndIdx = before.lastIndexOf('*/');
|
|
320
|
+
if (jsdocEndIdx === -1) {
|
|
321
|
+
return '';
|
|
322
|
+
}
|
|
323
|
+
const trailing = before.slice(jsdocEndIdx + 2);
|
|
324
|
+
if (trailing.trim() !== '') {
|
|
325
|
+
return '';
|
|
326
|
+
}
|
|
327
|
+
const jsdocStartIdx = before.lastIndexOf('/**', jsdocEndIdx);
|
|
328
|
+
if (jsdocStartIdx === -1) {
|
|
329
|
+
return '';
|
|
330
|
+
}
|
|
331
|
+
return before.slice(jsdocStartIdx + 3, jsdocEndIdx);
|
|
332
|
+
}
|
|
333
|
+
buildInputFallbackDescription(input) {
|
|
334
|
+
const requiredText = input.required ? 'Required ' : '';
|
|
335
|
+
let description = `${requiredText}Input property \`${input.name}\` of type \`${input.type || 'any'}\`.`;
|
|
336
|
+
if (input.defaultValue && input.defaultValue.trim()) {
|
|
337
|
+
description += ` Default: \`${this.truncateForDescription(input.defaultValue)}\`.`;
|
|
338
|
+
}
|
|
339
|
+
return description;
|
|
340
|
+
}
|
|
341
|
+
buildOutputFallbackDescription(output) {
|
|
342
|
+
return `Output event \`${output.name}\` emits \`${output.type || 'any'}\`.`;
|
|
343
|
+
}
|
|
344
|
+
truncateForDescription(value, maxLength = 80) {
|
|
345
|
+
const cleaned = value.replace(/\s+/g, ' ').trim();
|
|
346
|
+
if (cleaned.length <= maxLength) {
|
|
347
|
+
return cleaned;
|
|
348
|
+
}
|
|
349
|
+
return `${cleaned.substring(0, maxLength - 3)}...`;
|
|
350
|
+
}
|
|
351
|
+
extractMethods(content) {
|
|
352
|
+
const results = [];
|
|
353
|
+
const methodPattern = /(\/\*\*([\s\S]*?)\*\/\s*)?(?:public\s+)(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{]+))?\s*{/g;
|
|
354
|
+
let match;
|
|
355
|
+
while ((match = methodPattern.exec(content)) !== null) {
|
|
356
|
+
const jsdocBlock = match[2] || '';
|
|
357
|
+
const name = match[3];
|
|
358
|
+
const paramsStr = match[4] || '';
|
|
359
|
+
const returnType = match[5]?.trim() || 'void';
|
|
360
|
+
const lifecycleHooks = [
|
|
361
|
+
'ngOnInit',
|
|
362
|
+
'ngOnDestroy',
|
|
363
|
+
'ngOnChanges',
|
|
364
|
+
'ngAfterViewInit',
|
|
365
|
+
'ngAfterContentInit',
|
|
366
|
+
'ngDoCheck',
|
|
367
|
+
];
|
|
368
|
+
if (lifecycleHooks.includes(name))
|
|
369
|
+
continue;
|
|
370
|
+
if (jsdocBlock.includes('@hidden') || jsdocBlock.includes('@internal'))
|
|
371
|
+
continue;
|
|
372
|
+
if (name.startsWith('_'))
|
|
373
|
+
continue;
|
|
374
|
+
const { description, deprecated } = this.parsePropertyJSDoc(jsdocBlock);
|
|
375
|
+
const params = [];
|
|
376
|
+
if (paramsStr.trim()) {
|
|
377
|
+
const paramParts = paramsStr.split(',');
|
|
378
|
+
for (const part of paramParts) {
|
|
379
|
+
const paramMatch = part.trim().match(/(\w+)(?:\?)?(?::\s*(.+))?/);
|
|
380
|
+
if (paramMatch) {
|
|
381
|
+
params.push({
|
|
382
|
+
name: paramMatch[1],
|
|
383
|
+
type: paramMatch[2]?.trim() || 'any',
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
results.push({
|
|
389
|
+
name,
|
|
390
|
+
description,
|
|
391
|
+
params,
|
|
392
|
+
returnType: this.cleanType(returnType),
|
|
393
|
+
deprecated,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return results;
|
|
397
|
+
}
|
|
398
|
+
extractSlots(htmlContent) {
|
|
399
|
+
const slots = [];
|
|
400
|
+
if (!htmlContent)
|
|
401
|
+
return slots;
|
|
402
|
+
const slotPattern = /<ng-content(?:\s+select=["']([^"']+)["'])?[^>]*>/g;
|
|
403
|
+
let match;
|
|
404
|
+
while ((match = slotPattern.exec(htmlContent)) !== null) {
|
|
405
|
+
const selector = match[1] || '*';
|
|
406
|
+
const name = selector === '*' ? 'default' : selector.replace(/[.[\]]/g, '');
|
|
407
|
+
slots.push({
|
|
408
|
+
name,
|
|
409
|
+
selector,
|
|
410
|
+
description: selector === '*'
|
|
411
|
+
? 'Default content projection slot'
|
|
412
|
+
: `Content projection for elements matching "${selector}"`,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return slots;
|
|
416
|
+
}
|
|
417
|
+
parsePropertyJSDoc(jsdoc) {
|
|
418
|
+
const lines = jsdoc.split('\n').map(line => line.replace(/^\s*\*\s?/, ''));
|
|
419
|
+
let description = '';
|
|
420
|
+
const examples = [];
|
|
421
|
+
let inExample = false;
|
|
422
|
+
let currentExample = '';
|
|
423
|
+
let deprecated;
|
|
424
|
+
for (const line of lines) {
|
|
425
|
+
const trimmed = line.trim();
|
|
426
|
+
if (trimmed.startsWith('@example')) {
|
|
427
|
+
inExample = true;
|
|
428
|
+
currentExample = '';
|
|
429
|
+
}
|
|
430
|
+
else if (trimmed.startsWith('@deprecated')) {
|
|
431
|
+
deprecated = trimmed.replace('@deprecated', '').trim() || 'Deprecated';
|
|
432
|
+
}
|
|
433
|
+
else if (trimmed.startsWith('@') && inExample) {
|
|
434
|
+
if (currentExample.trim()) {
|
|
435
|
+
examples.push(currentExample.trim());
|
|
436
|
+
}
|
|
437
|
+
inExample = false;
|
|
438
|
+
currentExample = '';
|
|
439
|
+
}
|
|
440
|
+
else if (inExample) {
|
|
441
|
+
if (trimmed.startsWith('```')) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
currentExample += line + '\n';
|
|
445
|
+
}
|
|
446
|
+
else if (!trimmed.startsWith('@') && trimmed) {
|
|
447
|
+
if (description)
|
|
448
|
+
description += ' ';
|
|
449
|
+
description += trimmed;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (currentExample.trim()) {
|
|
453
|
+
examples.push(currentExample.trim());
|
|
454
|
+
}
|
|
455
|
+
return { description: description.trim(), examples, deprecated };
|
|
456
|
+
}
|
|
457
|
+
cleanType(type) {
|
|
458
|
+
return type.replace(/\s+/g, ' ').trim();
|
|
459
|
+
}
|
|
460
|
+
inferTypeFromValue(value) {
|
|
461
|
+
if (!value)
|
|
462
|
+
return null;
|
|
463
|
+
const trimmed = value.trim();
|
|
464
|
+
if (trimmed === 'true' || trimmed === 'false') {
|
|
465
|
+
return 'boolean';
|
|
466
|
+
}
|
|
467
|
+
if (trimmed === "''" ||
|
|
468
|
+
trimmed === '""' ||
|
|
469
|
+
trimmed.startsWith("'") ||
|
|
470
|
+
trimmed.startsWith('"')) {
|
|
471
|
+
return 'string';
|
|
472
|
+
}
|
|
473
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
474
|
+
return 'number';
|
|
475
|
+
}
|
|
476
|
+
if (trimmed.startsWith('[')) {
|
|
477
|
+
return 'array';
|
|
478
|
+
}
|
|
479
|
+
if (trimmed.startsWith('{')) {
|
|
480
|
+
return 'object';
|
|
481
|
+
}
|
|
482
|
+
if (trimmed === 'null' || trimmed === 'undefined') {
|
|
483
|
+
return trimmed;
|
|
484
|
+
}
|
|
485
|
+
if (trimmed.startsWith('new ')) {
|
|
486
|
+
const match = trimmed.match(/new\s+(\w+)/);
|
|
487
|
+
return match ? match[1] : null;
|
|
488
|
+
}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
extractHostListeners(content) {
|
|
492
|
+
const results = [];
|
|
493
|
+
const hostListenerPattern = /@HostListener\(\s*['"]([^'"]+)['"]\s*(?:,\s*\[([^\]]*)\])?\s*\)\s*(?:\w+\s+)?(\w+)\s*\(/g;
|
|
494
|
+
let match;
|
|
495
|
+
while ((match = hostListenerPattern.exec(content)) !== null) {
|
|
496
|
+
const event = match[1];
|
|
497
|
+
const argsStr = match[2] || '';
|
|
498
|
+
const methodName = match[3];
|
|
499
|
+
const args = argsStr
|
|
500
|
+
? argsStr
|
|
501
|
+
.split(',')
|
|
502
|
+
.map(a => a.trim().replace(/['"]/g, ''))
|
|
503
|
+
.filter(Boolean)
|
|
504
|
+
: [];
|
|
505
|
+
results.push({ event, methodName, args });
|
|
506
|
+
}
|
|
507
|
+
return results;
|
|
508
|
+
}
|
|
509
|
+
extractQueryChildren(content) {
|
|
510
|
+
const results = [];
|
|
511
|
+
const queryPattern = /@(ViewChild|ContentChild|ViewChildren|ContentChildren)\(\s*(\w+)(?:\s*,\s*\{([^}]*)\})?\s*\)\s*(?:public\s+|private\s+|protected\s+)?(\w+)(?:\?|!)?(?::\s*([^;=]+?))?;/g;
|
|
512
|
+
let match;
|
|
513
|
+
while ((match = queryPattern.exec(content)) !== null) {
|
|
514
|
+
const queryType = match[1];
|
|
515
|
+
const type = match[2];
|
|
516
|
+
const options = match[3] || '';
|
|
517
|
+
const name = match[4];
|
|
518
|
+
if (name.startsWith('_'))
|
|
519
|
+
continue;
|
|
520
|
+
const isStatic = /static\s*:\s*true/.test(options);
|
|
521
|
+
const isRequired = !match[0].includes('?');
|
|
522
|
+
results.push({
|
|
523
|
+
name,
|
|
524
|
+
type,
|
|
525
|
+
queryType,
|
|
526
|
+
required: isRequired,
|
|
527
|
+
static: isStatic,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return results;
|
|
531
|
+
}
|
|
532
|
+
extractAccessibilityInfo(tsContent, htmlContent = '') {
|
|
533
|
+
const a11yInfo = [];
|
|
534
|
+
const combined = tsContent + '\n' + htmlContent;
|
|
535
|
+
const ariaAttrs = this.extractAriaFromHtml(htmlContent);
|
|
536
|
+
a11yInfo.push(...ariaAttrs);
|
|
537
|
+
if (tsContent.includes('aria-label') || tsContent.includes('ariaLabel')) {
|
|
538
|
+
if (!a11yInfo.some(i => i.includes('aria-label'))) {
|
|
539
|
+
a11yInfo.push('Uses aria-label for screen readers');
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (tsContent.includes('aria-describedby') ||
|
|
543
|
+
tsContent.includes('ariaDescribedby')) {
|
|
544
|
+
if (!a11yInfo.some(i => i.includes('aria-describedby'))) {
|
|
545
|
+
a11yInfo.push('Uses aria-describedby for descriptions');
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const tsRoleMatch = tsContent.match(/role\s*=\s*['"](\w+)['"]/);
|
|
549
|
+
if (tsRoleMatch &&
|
|
550
|
+
!a11yInfo.some(i => i.includes(`role="${tsRoleMatch[1]}"`))) {
|
|
551
|
+
a11yInfo.push(`Defines role="${tsRoleMatch[1]}"`);
|
|
552
|
+
}
|
|
553
|
+
if (combined.includes('keydown') ||
|
|
554
|
+
combined.includes('keyup') ||
|
|
555
|
+
combined.includes('keypress')) {
|
|
556
|
+
a11yInfo.push('Supports keyboard navigation');
|
|
557
|
+
}
|
|
558
|
+
if (combined.includes('tabindex') || combined.includes('tabIndex')) {
|
|
559
|
+
a11yInfo.push('Manages focus with tabindex');
|
|
560
|
+
}
|
|
561
|
+
if (combined.includes('.focus()') ||
|
|
562
|
+
combined.includes('FocusTrap') ||
|
|
563
|
+
combined.includes('cdkTrapFocus')) {
|
|
564
|
+
a11yInfo.push('Implements focus management');
|
|
565
|
+
}
|
|
566
|
+
if (combined.includes('sr-only') ||
|
|
567
|
+
combined.includes('visually-hidden') ||
|
|
568
|
+
combined.includes('cdk-visually-hidden')) {
|
|
569
|
+
a11yInfo.push('Includes screen reader only content');
|
|
570
|
+
}
|
|
571
|
+
return [...new Set(a11yInfo)]; // Remove duplicates
|
|
572
|
+
}
|
|
573
|
+
extractAriaFromHtml(html) {
|
|
574
|
+
const a11yInfo = [];
|
|
575
|
+
if (!html)
|
|
576
|
+
return a11yInfo;
|
|
577
|
+
const roleMatches = html.matchAll(/role\s*=\s*["']([^"']+)["']/g);
|
|
578
|
+
const roles = new Set();
|
|
579
|
+
for (const match of roleMatches) {
|
|
580
|
+
roles.add(match[1]);
|
|
581
|
+
}
|
|
582
|
+
if (roles.size > 0) {
|
|
583
|
+
a11yInfo.push(`Defines roles: ${[...roles].join(', ')}`);
|
|
584
|
+
}
|
|
585
|
+
const attrRoleMatches = html.matchAll(/\[attr\.role\]\s*=\s*["']([^"']+)["']/g);
|
|
586
|
+
for (const _match of attrRoleMatches) {
|
|
587
|
+
if (!a11yInfo.some(i => i.includes('dynamic role'))) {
|
|
588
|
+
a11yInfo.push('Uses dynamic role bindings');
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const ariaPatterns = [
|
|
592
|
+
[
|
|
593
|
+
/aria-label\s*=|attr\.aria-label/g,
|
|
594
|
+
'Uses aria-label for screen readers',
|
|
595
|
+
],
|
|
596
|
+
[
|
|
597
|
+
/aria-labelledby\s*=|attr\.aria-labelledby/g,
|
|
598
|
+
'Uses aria-labelledby for label references',
|
|
599
|
+
],
|
|
600
|
+
[
|
|
601
|
+
/aria-describedby\s*=|attr\.aria-describedby/g,
|
|
602
|
+
'Uses aria-describedby for descriptions',
|
|
603
|
+
],
|
|
604
|
+
[
|
|
605
|
+
/aria-expanded\s*=|attr\.aria-expanded/g,
|
|
606
|
+
'Uses aria-expanded for expandable content',
|
|
607
|
+
],
|
|
608
|
+
[
|
|
609
|
+
/aria-selected\s*=|attr\.aria-selected/g,
|
|
610
|
+
'Uses aria-selected for selection state',
|
|
611
|
+
],
|
|
612
|
+
[
|
|
613
|
+
/aria-controls\s*=|attr\.aria-controls/g,
|
|
614
|
+
'Uses aria-controls for related elements',
|
|
615
|
+
],
|
|
616
|
+
[
|
|
617
|
+
/aria-live\s*=|attr\.aria-live/g,
|
|
618
|
+
'Uses aria-live for dynamic announcements',
|
|
619
|
+
],
|
|
620
|
+
[
|
|
621
|
+
/aria-hidden\s*=|attr\.aria-hidden/g,
|
|
622
|
+
'Uses aria-hidden for decorative elements',
|
|
623
|
+
],
|
|
624
|
+
[
|
|
625
|
+
/aria-disabled\s*=|attr\.aria-disabled/g,
|
|
626
|
+
'Uses aria-disabled for disabled state',
|
|
627
|
+
],
|
|
628
|
+
[
|
|
629
|
+
/aria-posinset\s*=|attr\.aria-posinset/g,
|
|
630
|
+
'Uses aria-posinset for position in set',
|
|
631
|
+
],
|
|
632
|
+
[
|
|
633
|
+
/aria-setsize\s*=|attr\.aria-setsize/g,
|
|
634
|
+
'Uses aria-setsize for set size',
|
|
635
|
+
],
|
|
636
|
+
[/aria-busy\s*=|attr\.aria-busy/g, 'Uses aria-busy for loading state'],
|
|
637
|
+
];
|
|
638
|
+
for (const [pattern, message] of ariaPatterns) {
|
|
639
|
+
if (pattern.test(html)) {
|
|
640
|
+
a11yInfo.push(message);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return a11yInfo;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
//# sourceMappingURL=component-parser.js.map
|