-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtemplate-helpers.ts
More file actions
135 lines (113 loc) · 3.96 KB
/
template-helpers.ts
File metadata and controls
135 lines (113 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* Utilities for processing Angular HTML templates.
* These are regex-based helpers for template analysis — not tied to the Angular compiler AST.
*/
import { escapeRegex } from '@push-based/utils';
export interface SelectorInfo {
style: 'element' | 'attribute' | 'unknown';
selector: string;
note?: string;
}
/**
* Clean a template string:
* - Replace `${...}` interpolations with `...` placeholders
* - Remove Storybook layout wrapper divs
* - Normalize whitespace
*
* Returns null if the cleaned result doesn't look like valid HTML
* (e.g., programmatically generated templates with JS fragments).
*/
export function cleanTemplate(template: string): string | null {
let t = template;
// Replace argsToTemplate(args) with a meaningful marker before generic replacement
t = t.replace(/\$\{argsToTemplate\([^)]*\)\}/g, '[all-meta-args-bound]');
// Replace ${...} interpolations with placeholder
t = t.replace(/\$\{[^}]*\}/g, '...');
// Remove HTML comments
t = t.replace(/<!--[\s\S]*?-->/g, '');
// Remove ALL inline style attributes (storybook layout concerns, not component API)
t = t.replace(/\s*style=["'][^"']*["']/gi, '');
// Remove storybook layout wrapper divs (with class but layout-only) — strip opening AND closing tag
t = t.replace(
/<div\s+class="[^"]*(?:example-|tabgroup-)[^"]*">([\s\S]*?)<\/div>/gi,
'$1',
);
// Collapse to single line: strip all newlines and excess whitespace
t = t.replace(/\s+/g, ' ').trim();
// Remove whitespace between tags (but not inside tags)
t = t.replace(/>\s+</g, '><');
// Detect programmatically generated templates (JS fragments, not HTML)
if (!looksLikeHtml(t)) {
return null;
}
return t;
}
/**
* Check if a cleaned template string looks like valid HTML rather than
* JS code fragments from programmatically generated templates.
*/
export function looksLikeHtml(template: string): boolean {
// Must contain at least one HTML tag
if (!/<\w/.test(template)) return false;
// JS artifacts that indicate a programmatic template
const jsArtifacts = ['.join(', '.map(', '=>', 'function ', 'return '];
const artifactCount = jsArtifacts.filter((a) => template.includes(a)).length;
if (artifactCount >= 2) return false;
return true;
}
/**
* Scan templates for `slot="name"` patterns, deduplicate and sort alphabetically.
*/
export function extractSlotsFromTemplates(templates: string[]): string[] {
const slots = new Set<string>();
const slotRegex = /slot="([^"]+)"/g;
for (const template of templates) {
let match: RegExpExecArray | null;
while ((match = slotRegex.exec(template)) !== null) {
slots.add(match[1]);
}
}
return [...slots].sort();
}
/**
* Detect whether a component uses attribute-style or element-style selectors.
*/
export function detectSelectorStyle(
content: string,
kebabName: string,
): SelectorInfo {
// Check for attribute usage: <button ds-{name}> or <a ds-{name}>
const attrRegex = new RegExp(
`<(?:button|a)\\s[^>]*\\bds-${escapeRegex(kebabName)}\\b`,
'i',
);
if (attrRegex.test(content)) {
return {
style: 'attribute',
selector: `ds-${kebabName}`,
note: 'Applied as attribute on `<button>` or `<a>`',
};
}
// Check for element usage: <ds-{name} followed by space, >, or newline
const elemRegex = new RegExp(
`<ds-${escapeRegex(kebabName)}(?=[\\s>/\\[])`,
'i',
);
if (elemRegex.test(content)) {
return { style: 'element', selector: `ds-${kebabName}` };
}
return { style: 'unknown', selector: `ds-${kebabName}` };
}
/**
* Detect Angular form integration patterns in templates.
*/
export function detectFormIntegration(content: string): string[] {
const patterns: string[] = [];
if (/\[\(ngModel\)\]/.test(content))
patterns.push('ngModel (template-driven)');
if (/formControlName/.test(content))
patterns.push('formControlName (reactive forms)');
if (/formControl[^N]/.test(content))
patterns.push('formControl (reactive forms)');
return patterns;
}