@juspay/svelte-ui-components 3.5.0 → 3.5.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 +1 -1
- package/dist/Accordion/Accordion.svelte +1 -5
- package/dist/Accordion/properties.d.ts +0 -1
- package/dist/AreaChart/AreaChart.svelte +2 -8
- package/dist/AreaChart/properties.d.ts +0 -10
- package/dist/AttachmentChipRow/AttachmentChipRow.svelte +30 -30
- package/dist/AttachmentChipRow/properties.d.ts +6 -6
- package/dist/Banner/Banner.svelte +1 -5
- package/dist/Banner/properties.d.ts +0 -1
- package/dist/BarChart/BarChart.svelte +7 -13
- package/dist/BarChart/properties.d.ts +1 -9
- package/dist/Book/Book.svelte +1 -5
- package/dist/Book/properties.d.ts +0 -1
- package/dist/Calendar/Calendar.svelte +3 -11
- package/dist/Calendar/properties.d.ts +0 -11
- package/dist/Chat/Chat.svelte +9 -29
- package/dist/Chat/properties.d.ts +0 -9
- package/dist/Chat/types.d.ts +7 -0
- package/dist/ChatBubble/ChatBubble.svelte +3 -11
- package/dist/ChatBubble/properties.d.ts +0 -3
- package/dist/ChatComposer/ChatComposer.svelte +18 -44
- package/dist/ChatComposer/properties.d.ts +0 -12
- package/dist/ChatHeader/ChatHeader.svelte +1 -5
- package/dist/ChatHeader/properties.d.ts +0 -1
- package/dist/ChatMessage/ChatMessage.svelte +87 -11
- package/dist/ChatMessage/properties.d.ts +18 -3
- package/dist/ChatMessageList/ChatMessageList.svelte +5 -11
- package/dist/ChatMessageList/properties.d.ts +0 -6
- package/dist/ChatSuggestions/ChatSuggestions.svelte +1 -5
- package/dist/ChatSuggestions/properties.d.ts +0 -1
- package/dist/CheckListItem/CheckListItem.svelte +1 -5
- package/dist/CheckListItem/properties.d.ts +0 -1
- package/dist/Checkbox/Checkbox.svelte +1 -5
- package/dist/Checkbox/properties.d.ts +0 -1
- package/dist/ChipInput/ChipInput.svelte +10 -20
- package/dist/ChipInput/properties.d.ts +0 -4
- package/dist/Choicebox/Choicebox.svelte +1 -5
- package/dist/Choicebox/properties.d.ts +0 -1
- package/dist/ColorPicker/ColorPicker.svelte +4 -10
- package/dist/ColorPicker/properties.d.ts +0 -2
- package/dist/Combobox/Combobox.svelte +15 -33
- package/dist/Combobox/properties.d.ts +0 -8
- package/dist/CommandMenu/CommandMenu.svelte +2 -8
- package/dist/CommandMenu/properties.d.ts +0 -2
- package/dist/ContextMenu/ContextMenu.svelte +3 -11
- package/dist/ContextMenu/properties.d.ts +0 -3
- package/dist/DateRangePicker/DateRangePicker.svelte +6 -20
- package/dist/DateRangePicker/properties.d.ts +0 -19
- package/dist/Draggable/Draggable.svelte +7 -7
- package/dist/Draggable/properties.d.ts +3 -3
- package/dist/DualAxisBarChart/DualAxisBarChart.svelte +1 -5
- package/dist/DualAxisBarChart/properties.d.ts +0 -4
- package/dist/FileDropzoneTrigger/FileDropzoneTrigger.svelte +1 -5
- package/dist/FileDropzoneTrigger/properties.d.ts +0 -1
- package/dist/FileInput/FileInput.svelte +2 -8
- package/dist/FileInput/properties.d.ts +0 -2
- package/dist/FunnelChart/FunnelChart.svelte +2 -8
- package/dist/FunnelChart/properties.d.ts +0 -8
- package/dist/Gallery/Gallery.svelte +15 -15
- package/dist/Gallery/properties.d.ts +6 -6
- package/dist/HITL/HITL.svelte +10 -8
- package/dist/HITL/properties.d.ts +2 -2
- package/dist/IframeViewer/IframeViewer.svelte +3 -2
- package/dist/IframeViewer/properties.d.ts +1 -1
- package/dist/Img/Img.svelte +119 -39
- package/dist/Img/properties.d.ts +0 -1
- package/dist/Input/Input.svelte +22 -37
- package/dist/Input/properties.d.ts +3 -10
- package/dist/InputButton/InputButton.svelte +2 -2
- package/dist/LineChart/LineChart.svelte +4 -10
- package/dist/LineChart/properties.d.ts +1 -11
- package/dist/ListItem/ListItem.svelte +10 -22
- package/dist/ListItem/properties.d.ts +5 -10
- package/dist/LottiePlayer/LottiePlayer.svelte +2 -8
- package/dist/LottiePlayer/properties.d.ts +0 -2
- package/dist/MediaPlayer/MediaPlayer.svelte +1 -5
- package/dist/MediaPlayer/properties.d.ts +0 -1
- package/dist/MediaUpload/MediaUpload.svelte +6 -6
- package/dist/MediaUpload/properties.d.ts +3 -3
- package/dist/Menu/Menu.svelte +3 -11
- package/dist/Menu/properties.d.ts +0 -3
- package/dist/Modal/Modal.svelte +15 -31
- package/dist/Modal/properties.d.ts +5 -11
- package/dist/Pagination/Pagination.svelte +3 -7
- package/dist/Pagination/properties.d.ts +1 -2
- package/dist/PieChart/PieChart.svelte +4 -10
- package/dist/PieChart/properties.d.ts +1 -9
- package/dist/Pill/Pill.svelte +1 -5
- package/dist/Pill/properties.d.ts +0 -1
- package/dist/Radio/Radio.svelte +1 -5
- package/dist/Radio/properties.d.ts +0 -1
- package/dist/Resizable/Resizable.svelte +3 -11
- package/dist/Resizable/properties.d.ts +0 -3
- package/dist/SankeyChart/SankeyChart.svelte +4 -14
- package/dist/SankeyChart/properties.d.ts +0 -12
- package/dist/Scroller/Scroller.svelte +1 -5
- package/dist/Scroller/properties.d.ts +0 -1
- package/dist/Select/Select.svelte +3 -11
- package/dist/Select/properties.d.ts +0 -3
- package/dist/Sheet/Sheet.svelte +3 -11
- package/dist/Sheet/properties.d.ts +0 -3
- package/dist/Slider/Slider.svelte +2 -8
- package/dist/Slider/properties.d.ts +0 -2
- package/dist/Snippet/Snippet.svelte +1 -5
- package/dist/Snippet/properties.d.ts +0 -1
- package/dist/SplitButton/SplitButton.svelte +1 -5
- package/dist/SplitButton/properties.d.ts +0 -1
- package/dist/SplitInput/SplitInput.svelte +6 -14
- package/dist/SplitInput/properties.d.ts +0 -3
- package/dist/StatCard/StatCard.svelte +2 -2
- package/dist/StatCard/properties.d.ts +1 -1
- package/dist/Status/Status.svelte +2 -6
- package/dist/Status/properties.d.ts +1 -2
- package/dist/Stepper/Stepper.svelte +2 -12
- package/dist/Stepper/properties.d.ts +1 -14
- package/dist/Table/BuiltinCell.svelte +1 -1
- package/dist/Table/Table.svelte +16 -17
- package/dist/Table/properties.d.ts +3 -35
- package/dist/Tabs/Tabs.svelte +2 -8
- package/dist/Tabs/properties.d.ts +0 -2
- package/dist/TaskList/TaskList.svelte +1 -4
- package/dist/TaskList/properties.d.ts +0 -1
- package/dist/ThemeSwitcher/ThemeSwitcher.svelte +1 -5
- package/dist/ThemeSwitcher/properties.d.ts +0 -1
- package/dist/ThinkingIndicator/ThinkingIndicator.svelte +4 -10
- package/dist/ThinkingIndicator/properties.d.ts +1 -3
- package/dist/Toast/Toast.svelte +2 -2
- package/dist/Toast/properties.d.ts +1 -1
- package/dist/Toggle/Toggle.svelte +1 -5
- package/dist/Toggle/properties.d.ts +0 -1
- package/dist/ToolCallLog/ToolCallLog.svelte +1 -10
- package/dist/ToolCallLog/properties.d.ts +0 -1
- package/dist/Toolbar/Toolbar.svelte +2 -6
- package/dist/Toolbar/properties.d.ts +1 -2
- package/dist/TypewriterText/TypewriterText.svelte +3 -3
- package/dist/TypewriterText/properties.d.ts +1 -1
- package/dist-codemod/bin.js +32 -0
- package/dist-codemod/cli.js +175 -0
- package/dist-codemod/legacy-pairs.js +228 -0
- package/dist-codemod/transform.js +278 -0
- package/dist-codemod/wc-children.js +66 -0
- package/dist-wc/index.js +4458 -5127
- package/package.json +9 -7
- package/scripts/postinstall.mjs +14 -12
- package/dist/deprecation.d.ts +0 -42
- package/dist/deprecation.js +0 -57
- package/scripts/codemod/README.md +0 -76
- package/scripts/codemod/cli.ts +0 -206
- package/scripts/codemod/map.ts +0 -118
- package/scripts/codemod/transform.ts +0 -549
- package/scripts/codemod/tsconfig.json +0 -15
- package/scripts/codemod/wc-children.ts +0 -75
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { parse } from 'svelte/compiler';
|
|
2
|
+
import { legacyRenameTable, legacyProps } from "./legacy-pairs.js";
|
|
3
|
+
export const SUI_PACKAGE = '@juspay/svelte-ui-components';
|
|
4
|
+
/**
|
|
5
|
+
* estree nodes inside `<script>` are not typed with offsets, but the acorn
|
|
6
|
+
* parser svelte uses attaches them at runtime; recover them by narrowing.
|
|
7
|
+
*/
|
|
8
|
+
function rangeOf(node) {
|
|
9
|
+
if ('start' in node &&
|
|
10
|
+
typeof node.start === 'number' &&
|
|
11
|
+
'end' in node &&
|
|
12
|
+
typeof node.end === 'number') {
|
|
13
|
+
return { start: node.start, end: node.end };
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function positionOf(source, offset) {
|
|
18
|
+
const before = source.slice(0, offset);
|
|
19
|
+
const line = (before.match(/\n/g) ?? []).length + 1;
|
|
20
|
+
return { line, column: offset - before.lastIndexOf('\n') };
|
|
21
|
+
}
|
|
22
|
+
function applyEdits(source, edits) {
|
|
23
|
+
const ordered = [...edits].sort((a, b) => b.start - a.start);
|
|
24
|
+
return ordered.reduce((code, edit) => code.slice(0, edit.start) + edit.text + code.slice(edit.end), source);
|
|
25
|
+
}
|
|
26
|
+
function isPackageSpecifier(specifier) {
|
|
27
|
+
return specifier === SUI_PACKAGE || specifier.startsWith(`${SUI_PACKAGE}/`);
|
|
28
|
+
}
|
|
29
|
+
function resolverFor(options) {
|
|
30
|
+
return {
|
|
31
|
+
isLibraryImport: options.isLibraryImport ?? isPackageSpecifier,
|
|
32
|
+
defaultImportComponent: options.defaultImportComponent ?? (() => null)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function scanScript(source, file, program, resolver, scan) {
|
|
36
|
+
for (const statement of program.body) {
|
|
37
|
+
if (statement.type !== 'ImportDeclaration') {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const specifier = typeof statement.source.value === 'string' ? statement.source.value : '';
|
|
41
|
+
const fromLibrary = resolver.isLibraryImport(specifier);
|
|
42
|
+
if (fromLibrary) {
|
|
43
|
+
scan.importsLibrary = true;
|
|
44
|
+
}
|
|
45
|
+
for (const spec of statement.specifiers) {
|
|
46
|
+
if (spec.type === 'ImportSpecifier') {
|
|
47
|
+
const exported = spec.imported.type === 'Identifier'
|
|
48
|
+
? spec.imported.name
|
|
49
|
+
: typeof spec.imported.value === 'string'
|
|
50
|
+
? spec.imported.value
|
|
51
|
+
: null;
|
|
52
|
+
scan.bindings.set(spec.local.name, fromLibrary && exported !== null ? { kind: 'component', exported } : { kind: 'other' });
|
|
53
|
+
}
|
|
54
|
+
else if (spec.type === 'ImportNamespaceSpecifier') {
|
|
55
|
+
scan.bindings.set(spec.local.name, fromLibrary ? { kind: 'namespace' } : { kind: 'other' });
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const component = fromLibrary ? resolver.defaultImportComponent(specifier) : null;
|
|
59
|
+
scan.bindings.set(spec.local.name, component !== null ? { kind: 'component', exported: component } : { kind: 'other' });
|
|
60
|
+
if (fromLibrary && component === null) {
|
|
61
|
+
const at = positionOf(source, rangeOf(spec)?.start ?? 0);
|
|
62
|
+
scan.warnings.push({
|
|
63
|
+
file,
|
|
64
|
+
line: at.line,
|
|
65
|
+
column: at.column,
|
|
66
|
+
message: `default import from '${specifier}' cannot be resolved to a component; its usages are not rewritten`
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function resolveTag(tagName, bindings) {
|
|
74
|
+
const segments = tagName.split('.');
|
|
75
|
+
const head = segments.at(0);
|
|
76
|
+
if (typeof head !== 'string' || head.length === 0) {
|
|
77
|
+
return { kind: 'unresolved' };
|
|
78
|
+
}
|
|
79
|
+
const binding = bindings.get(head) ?? null;
|
|
80
|
+
if (binding === null) {
|
|
81
|
+
return { kind: 'unresolved' };
|
|
82
|
+
}
|
|
83
|
+
if (segments.length === 1) {
|
|
84
|
+
return binding.kind === 'component'
|
|
85
|
+
? { kind: 'library', component: binding.exported }
|
|
86
|
+
: { kind: 'other' };
|
|
87
|
+
}
|
|
88
|
+
const member = segments.at(1);
|
|
89
|
+
if (segments.length === 2 && binding.kind === 'namespace' && typeof member === 'string') {
|
|
90
|
+
return { kind: 'library', component: member };
|
|
91
|
+
}
|
|
92
|
+
return { kind: 'other' };
|
|
93
|
+
}
|
|
94
|
+
function resolveThisExpression(expression, bindings) {
|
|
95
|
+
if (expression.type === 'Identifier') {
|
|
96
|
+
return resolveTag(expression.name, bindings);
|
|
97
|
+
}
|
|
98
|
+
if (expression.type === 'MemberExpression' &&
|
|
99
|
+
!expression.computed &&
|
|
100
|
+
expression.object.type === 'Identifier' &&
|
|
101
|
+
expression.property.type === 'Identifier') {
|
|
102
|
+
return resolveTag(`${expression.object.name}.${expression.property.name}`, bindings);
|
|
103
|
+
}
|
|
104
|
+
return { kind: 'unresolved' };
|
|
105
|
+
}
|
|
106
|
+
function renameAttributes(source, file, tagLabel, component, element, table, scan) {
|
|
107
|
+
if (table === null) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const presentNames = new Set();
|
|
111
|
+
for (const attribute of element.attributes) {
|
|
112
|
+
if (attribute.type === 'Attribute') {
|
|
113
|
+
presentNames.add(attribute.name);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const attribute of element.attributes) {
|
|
117
|
+
if (attribute.type === 'SpreadAttribute') {
|
|
118
|
+
const at = positionOf(source, attribute.start);
|
|
119
|
+
const renameable = [...table.keys()].sort().join(', ');
|
|
120
|
+
scan.warnings.push({
|
|
121
|
+
file,
|
|
122
|
+
line: at.line,
|
|
123
|
+
column: at.column,
|
|
124
|
+
message: `spread attribute on <${tagLabel}> (${component}) may carry renamed props (${renameable}) — not rewritten, review manually`
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (attribute.type !== 'Attribute') {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const target = table.get(attribute.name) ?? null;
|
|
132
|
+
if (target === null) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (presentNames.has(target)) {
|
|
136
|
+
const at = positionOf(source, attribute.start);
|
|
137
|
+
scan.warnings.push({
|
|
138
|
+
file,
|
|
139
|
+
line: at.line,
|
|
140
|
+
column: at.column,
|
|
141
|
+
message: `target prop '${target}' already present on <${tagLabel}> (${component}); '${attribute.name}' left as-is — review manually`
|
|
142
|
+
});
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (source.charAt(attribute.start) === '{') {
|
|
146
|
+
// Shorthand `{prop}`: expand to `newName={prop}`, keeping the local identifier.
|
|
147
|
+
scan.edits.push({
|
|
148
|
+
start: attribute.start,
|
|
149
|
+
end: attribute.end,
|
|
150
|
+
text: `${target}={${attribute.name}}`
|
|
151
|
+
});
|
|
152
|
+
scan.propsRenamed += 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const nameEnd = attribute.start + attribute.name.length;
|
|
156
|
+
if (source.slice(attribute.start, nameEnd) !== attribute.name) {
|
|
157
|
+
const at = positionOf(source, attribute.start);
|
|
158
|
+
scan.warnings.push({
|
|
159
|
+
file,
|
|
160
|
+
line: at.line,
|
|
161
|
+
column: at.column,
|
|
162
|
+
message: `could not locate attribute name '${attribute.name}' on <${tagLabel}> in source text — not rewritten`
|
|
163
|
+
});
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
scan.edits.push({ start: attribute.start, end: nameEnd, text: target });
|
|
167
|
+
scan.propsRenamed += 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function warnUnresolvedElement(source, file, tagLabel, element, renameableProps, scan) {
|
|
171
|
+
const suspicious = element.attributes
|
|
172
|
+
.filter((attribute) => attribute.type === 'Attribute')
|
|
173
|
+
.map((attribute) => attribute.name)
|
|
174
|
+
.filter((name) => renameableProps.has(name));
|
|
175
|
+
if (suspicious.length === 0) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const at = positionOf(source, element.start);
|
|
179
|
+
scan.warnings.push({
|
|
180
|
+
file,
|
|
181
|
+
line: at.line,
|
|
182
|
+
column: at.column,
|
|
183
|
+
message: `<${tagLabel}> is not resolvable to an import but has renameable prop(s) ${suspicious.join(', ')} — not rewritten, review manually`
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function walkFragment(fragment, visit) {
|
|
187
|
+
for (const node of fragment.nodes) {
|
|
188
|
+
if (node.type === 'Component' || node.type === 'SvelteComponent') {
|
|
189
|
+
visit(node);
|
|
190
|
+
}
|
|
191
|
+
// Structural recursion over every fragment-bearing property covers all
|
|
192
|
+
// element and block kinds (including future ones) without enumerating them.
|
|
193
|
+
if ('fragment' in node && typeof node.fragment === 'object') {
|
|
194
|
+
walkFragment(node.fragment, visit);
|
|
195
|
+
}
|
|
196
|
+
if ('body' in node && typeof node.body === 'object') {
|
|
197
|
+
walkFragment(node.body, visit);
|
|
198
|
+
}
|
|
199
|
+
if ('consequent' in node) {
|
|
200
|
+
walkFragment(node.consequent, visit);
|
|
201
|
+
}
|
|
202
|
+
if ('alternate' in node && node.alternate !== null) {
|
|
203
|
+
walkFragment(node.alternate, visit);
|
|
204
|
+
}
|
|
205
|
+
if ('fallback' in node) {
|
|
206
|
+
const fallback = node.fallback ?? null;
|
|
207
|
+
if (fallback !== null) {
|
|
208
|
+
walkFragment(fallback, visit);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if ('pending' in node && node.pending !== null) {
|
|
212
|
+
walkFragment(node.pending, visit);
|
|
213
|
+
}
|
|
214
|
+
if ('then' in node && node.then !== null) {
|
|
215
|
+
walkFragment(node.then, visit);
|
|
216
|
+
}
|
|
217
|
+
if ('catch' in node && node.catch !== null) {
|
|
218
|
+
walkFragment(node.catch, visit);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Rewrites every deprecated event-prop spelling on a library component in a
|
|
224
|
+
* consumer's `.svelte` file to the corrected one (`legacy-pairs.ts`). Only
|
|
225
|
+
* components resolvable to an import from this package are touched; a tag
|
|
226
|
+
* that cannot be resolved but carries a renameable prop is reported instead.
|
|
227
|
+
*/
|
|
228
|
+
export function transformSvelte(source, file, options = {}) {
|
|
229
|
+
const resolver = resolverFor(options);
|
|
230
|
+
let root;
|
|
231
|
+
try {
|
|
232
|
+
root = parse(source, { modern: true, filename: file });
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
236
|
+
return {
|
|
237
|
+
code: source,
|
|
238
|
+
changed: false,
|
|
239
|
+
propsRenamed: 0,
|
|
240
|
+
warnings: [{ file, line: 1, column: 1, message: `could not parse file: ${message}` }]
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const scan = {
|
|
244
|
+
edits: [],
|
|
245
|
+
warnings: [],
|
|
246
|
+
bindings: new Map(),
|
|
247
|
+
importsLibrary: false,
|
|
248
|
+
propsRenamed: 0
|
|
249
|
+
};
|
|
250
|
+
const instance = root.instance ?? null;
|
|
251
|
+
if (instance !== null) {
|
|
252
|
+
scanScript(source, file, instance.content, resolver, scan);
|
|
253
|
+
}
|
|
254
|
+
const moduleScript = root.module ?? null;
|
|
255
|
+
if (moduleScript !== null) {
|
|
256
|
+
scanScript(source, file, moduleScript.content, resolver, scan);
|
|
257
|
+
}
|
|
258
|
+
const renames = legacyRenameTable();
|
|
259
|
+
walkFragment(root.fragment, (element) => {
|
|
260
|
+
const resolution = element.type === 'Component'
|
|
261
|
+
? resolveTag(element.name, scan.bindings)
|
|
262
|
+
: resolveThisExpression(element.expression, scan.bindings);
|
|
263
|
+
const tagLabel = element.type === 'Component' ? element.name : 'svelte:component';
|
|
264
|
+
if (resolution.kind === 'library') {
|
|
265
|
+
renameAttributes(source, file, tagLabel, resolution.component, element, renames.get(resolution.component) ?? null, scan);
|
|
266
|
+
}
|
|
267
|
+
else if (resolution.kind === 'unresolved' && scan.importsLibrary) {
|
|
268
|
+
warnUnresolvedElement(source, file, tagLabel, element, legacyProps(), scan);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
const code = applyEdits(source, scan.edits);
|
|
272
|
+
return {
|
|
273
|
+
code,
|
|
274
|
+
changed: code !== source,
|
|
275
|
+
propsRenamed: scan.propsRenamed,
|
|
276
|
+
warnings: scan.warnings
|
|
277
|
+
};
|
|
278
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects the breaking change in the 4.0.0 custom-element surface (the other
|
|
3
|
+
* 4.0.0 break, the deprecated event spellings, is handled by legacy-pairs.ts):
|
|
4
|
+
* `children` is no longer a declared prop on any `sui-*` element.
|
|
5
|
+
*
|
|
6
|
+
* Three elements shipped it in 3.1.4 — `sui-chat-bubble`, `sui-draggable` and
|
|
7
|
+
* `sui-resizable` — and none of them had a `<slot>`, so assigning the property
|
|
8
|
+
* was the only way to give them content. Declaring it was also what left
|
|
9
|
+
* `element.children` returning undefined instead of the native HTMLCollection,
|
|
10
|
+
* so `el.children.length` threw. Both are fixed together: the declaration is
|
|
11
|
+
* gone and those wrappers forward the default slot instead.
|
|
12
|
+
*
|
|
13
|
+
* This reports rather than rewrites. The fix moves content from a JavaScript
|
|
14
|
+
* assignment into markup, which changes where the content is authored, not just
|
|
15
|
+
* how it is spelled — a person has to decide what the light-DOM children are.
|
|
16
|
+
* Auto-editing that would be guesswork dressed as a codemod.
|
|
17
|
+
*/
|
|
18
|
+
export const CHILDREN_BREAKING_TAGS = [
|
|
19
|
+
'sui-chat-bubble',
|
|
20
|
+
'sui-draggable',
|
|
21
|
+
'sui-resizable'
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Matches `<expr>.children =` but not `==`/`===`, and not `.childNodes`.
|
|
25
|
+
* Deliberately loose on the left-hand side: a consumer reaches these elements
|
|
26
|
+
* through any expression at all (`ref.current`, `this.$refs.panel`, a query
|
|
27
|
+
* result), so anchoring on the property and confirming the tag appears in the
|
|
28
|
+
* same file gives far better recall than trying to type the receiver.
|
|
29
|
+
*/
|
|
30
|
+
const CHILDREN_ASSIGNMENT = /(^|[^.\w])([\w$\][().]*?)\.children\s*=(?!=)/gm;
|
|
31
|
+
const lineAndColumn = (source, index) => {
|
|
32
|
+
const upTo = source.slice(0, index);
|
|
33
|
+
const lines = upTo.split('\n');
|
|
34
|
+
return { line: lines.length, column: (lines.at(-1)?.length ?? 0) + 1 };
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Returns one warning per `.children =` assignment in a file that also mentions
|
|
38
|
+
* an affected tag. Requiring the tag in the same file is what keeps this from
|
|
39
|
+
* flagging every DOM manipulation in a codebase: `element.children` is a common
|
|
40
|
+
* expression, and only these three elements changed.
|
|
41
|
+
*/
|
|
42
|
+
export const findChildrenAssignments = (source, file) => {
|
|
43
|
+
const mentioned = CHILDREN_BREAKING_TAGS.filter((tag) => source.includes(tag));
|
|
44
|
+
if (mentioned.length === 0) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const warnings = [];
|
|
48
|
+
for (const match of source.matchAll(CHILDREN_ASSIGNMENT)) {
|
|
49
|
+
if (typeof match.index !== 'number') {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const { line, column } = lineAndColumn(source, match.index + match[1].length);
|
|
53
|
+
warnings.push({
|
|
54
|
+
file,
|
|
55
|
+
line,
|
|
56
|
+
column,
|
|
57
|
+
message: `assignment to \`.children\` in a file using ${mentioned.join(', ')}. ` +
|
|
58
|
+
'That property is no longer declared on those elements in 4.0.0; assigning it ' +
|
|
59
|
+
'now sets an inert expando and the content silently disappears. Pass the ' +
|
|
60
|
+
'content as light-DOM children instead — `<sui-draggable><div>…</div></sui-draggable>` ' +
|
|
61
|
+
'— which the wrappers now forward through their default slot. Reading ' +
|
|
62
|
+
'`element.children` is unaffected, and in fact returns a real HTMLCollection again.'
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return warnings;
|
|
66
|
+
};
|