@qds.dev/tools 0.11.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/lib/linter/qds-internal.d.ts +204 -1
  2. package/lib/linter/qds.d.ts +59 -0
  3. package/lib/linter/qds.unit.d.ts +1 -0
  4. package/lib/linter/rule-tester.d.ts +23 -1
  5. package/lib/playground/prop-extraction.d.ts +6 -1
  6. package/lib/playground/prop-extraction.qwik.mjs +68 -9
  7. package/lib/playground/scenario-injection.qwik.mjs +41 -8
  8. package/lib/rolldown/as-child.d.ts +6 -5
  9. package/lib/rolldown/as-child.qwik.mjs +52 -91
  10. package/lib/rolldown/index.d.ts +3 -2
  11. package/lib/rolldown/index.qwik.mjs +2 -3
  12. package/lib/rolldown/inject-component-types.qwik.mjs +1 -1
  13. package/lib/rolldown/inline-asset.qwik.mjs +6 -6
  14. package/lib/rolldown/inline-css.qwik.mjs +1 -1
  15. package/lib/rolldown/qds-types.d.ts +41 -0
  16. package/lib/rolldown/qds.d.ts +5 -0
  17. package/lib/rolldown/qds.qwik.mjs +147 -0
  18. package/lib/rolldown/qds.unit.d.ts +1 -0
  19. package/lib/rolldown/ui-types.d.ts +42 -0
  20. package/lib/rolldown/ui.d.ts +12 -0
  21. package/lib/rolldown/ui.qwik.mjs +445 -0
  22. package/lib/rolldown/ui.unit.d.ts +1 -0
  23. package/lib/utils/icons/transform/mdx.d.ts +3 -11
  24. package/lib/utils/icons/transform/mdx.qwik.mjs +14 -20
  25. package/lib/utils/icons/transform/tsx.d.ts +3 -12
  26. package/lib/utils/icons/transform/tsx.qwik.mjs +28 -37
  27. package/lib/utils/index.qwik.mjs +5 -5
  28. package/lib/utils/transform-dts.qwik.mjs +1 -1
  29. package/lib/vite/index.d.ts +2 -2
  30. package/lib/vite/index.qwik.mjs +2 -3
  31. package/lib/vite/minify-content.qwik.mjs +1 -1
  32. package/linter/qds-internal.ts +707 -0
  33. package/linter/qds-internal.unit.ts +399 -0
  34. package/linter/qds.ts +300 -0
  35. package/linter/qds.unit.ts +158 -0
  36. package/linter/rule-tester.ts +395 -0
  37. package/package.json +8 -7
  38. package/lib/rolldown/icons.qwik.mjs +0 -107
@@ -0,0 +1,445 @@
1
+ import { anyOf, createRegExp, exactly } from "magic-regexp";
2
+ import MagicString from "magic-string";
3
+ import { parseSync } from "oxc-parser";
4
+ import { walk } from "oxc-walker";
5
+
6
+ //#region rolldown/ui.ts
7
+ const deriveContextField = (getterName) => getterName.slice(3, 4).toLowerCase() + getterName.slice(4);
8
+ function generateComponentName(namespace, contextField, index) {
9
+ return `_Qds${namespace[0].toUpperCase() + namespace.slice(1)}${contextField[0].toUpperCase() + contextField.slice(1)}${index}`;
10
+ }
11
+ function collectUniqueNamespaces(target) {
12
+ const seen = /* @__PURE__ */ new Set();
13
+ const namespaces = [];
14
+ for (const ref of target.stateRefs) {
15
+ if (seen.has(ref.namespace.localName)) continue;
16
+ seen.add(ref.namespace.localName);
17
+ namespaces.push(ref.namespace);
18
+ }
19
+ for (const alias of target.aliasRefs) {
20
+ if (seen.has(alias.namespace.localName)) continue;
21
+ seen.add(alias.namespace.localName);
22
+ namespaces.push(alias.namespace);
23
+ }
24
+ return namespaces;
25
+ }
26
+ function getContextVarName(namespace, isSingle) {
27
+ return isSingle ? "ctx" : `${namespace.localName}Ctx`;
28
+ }
29
+ function isInsideDollarBoundary(ancestors) {
30
+ return ancestors.some((ancestor) => {
31
+ if (ancestor.type !== "CallExpression") return false;
32
+ const callee = ancestor.callee;
33
+ if (callee.type === "Identifier") return callee.name.endsWith("$") && callee.name !== "component$";
34
+ if (callee.type === "MemberExpression" && !callee.computed) return callee.property.name.endsWith("$");
35
+ return false;
36
+ });
37
+ }
38
+ function getFirstRef(target) {
39
+ if (target.stateRefs.length > 0) return {
40
+ namespace: target.stateRefs[0].namespace,
41
+ contextField: target.stateRefs[0].contextField
42
+ };
43
+ return {
44
+ namespace: target.aliasRefs[0].namespace,
45
+ contextField: target.aliasRefs[0].contextField
46
+ };
47
+ }
48
+ function rewriteExpression(source, expressionStart, expressionEnd, stateRefs, aliasRefs, isSingle) {
49
+ const replacements = [];
50
+ for (const ref of stateRefs) {
51
+ const ctxVar = isSingle ? "ctx" : `${ref.namespace.localName}Ctx`;
52
+ replacements.push({
53
+ start: ref.start - expressionStart,
54
+ end: ref.end - expressionStart,
55
+ text: `${ctxVar}.${ref.contextField}`
56
+ });
57
+ }
58
+ replacements.sort((a, b) => b.start - a.start);
59
+ let expr = source.slice(expressionStart, expressionEnd);
60
+ for (const r of replacements) expr = expr.slice(0, r.start) + r.text + expr.slice(r.end);
61
+ const seenAliases = /* @__PURE__ */ new Set();
62
+ for (const alias of aliasRefs) {
63
+ if (seenAliases.has(alias.localName)) continue;
64
+ seenAliases.add(alias.localName);
65
+ const replacement = `${isSingle ? "ctx" : `${alias.namespace.localName}Ctx`}.${alias.contextField}`;
66
+ expr = expr.replace(new RegExp(`\\b${alias.localName}\\b`, "g"), replacement);
67
+ }
68
+ return expr;
69
+ }
70
+ function buildUseContextLines(uniqueNamespaces) {
71
+ const isSingle = uniqueNamespaces.length === 1;
72
+ return uniqueNamespaces.map((ns) => {
73
+ return ` const ${getContextVarName(ns, isSingle)} = useContext(${ns.localName}.contextId);`;
74
+ }).join("\n");
75
+ }
76
+ function generateExpressionComponent(name, uniqueNamespaces, rewrittenExpression) {
77
+ return `\nconst ${name} = component$(() => {\n${buildUseContextLines(uniqueNamespaces)}\n return (\n <>\n {${rewrittenExpression}}\n </>\n );\n});\n`;
78
+ }
79
+ function generateElementComponent(name, uniqueNamespaces, rewrittenElement) {
80
+ return `\nconst ${name} = component$(() => {\n${buildUseContextLines(uniqueNamespaces)}\n return (\n ${rewrittenElement}\n );\n});\n`;
81
+ }
82
+ function generateHookComponent(name, uniqueNamespaces, rewrittenHook) {
83
+ return `const ${name} = component$(() => {\n${buildUseContextLines(uniqueNamespaces)}\n${rewrittenHook}\n return <></>;\n});\n`;
84
+ }
85
+ function generateChainedHookComponent(name, uniqueNamespaces, rewrittenHook, consumingJsx) {
86
+ return `const ${name} = component$(() => {\n${buildUseContextLines(uniqueNamespaces)}\n${rewrittenHook}\n return (\n ${consumingJsx}\n );\n});\n`;
87
+ }
88
+ const HOOK_NAMES = new Set([
89
+ "useTask$",
90
+ "useVisibleTask$",
91
+ "useComputed$"
92
+ ]);
93
+ function filterToInnermostConsumers(ranges) {
94
+ const toRemove = /* @__PURE__ */ new Set();
95
+ for (let i = 0; i < ranges.length; i++) for (let j = 0; j < ranges.length; j++) {
96
+ if (i === j) continue;
97
+ if (ranges[i].start <= ranges[j].start && ranges[i].end >= ranges[j].end) toRemove.add(i);
98
+ }
99
+ for (let i = ranges.length - 1; i >= 0; i--) if (toRemove.has(i)) ranges.splice(i, 1);
100
+ }
101
+ function collectUniqueNamespacesFromRefs(stateRefs, aliasRefs) {
102
+ const seen = /* @__PURE__ */ new Set();
103
+ const namespaces = [];
104
+ for (const ref of stateRefs) {
105
+ if (seen.has(ref.namespace.localName)) continue;
106
+ seen.add(ref.namespace.localName);
107
+ namespaces.push(ref.namespace);
108
+ }
109
+ for (const alias of aliasRefs) {
110
+ if (seen.has(alias.namespace.localName)) continue;
111
+ seen.add(alias.namespace.localName);
112
+ namespaces.push(alias.namespace);
113
+ }
114
+ return namespaces;
115
+ }
116
+ function uiTransformCore(code, id, ast, s, importSources, debug, warn) {
117
+ const ancestors = [];
118
+ debug("processing", id);
119
+ const boundNamespaces = /* @__PURE__ */ new Map();
120
+ const stateRefs = /* @__PURE__ */ new Map();
121
+ const aliasBindings = /* @__PURE__ */ new Map();
122
+ const transformTargets = [];
123
+ const pendingWarnings = [];
124
+ const collectImportsAndBindings = function(node) {
125
+ if (node.type === "ImportDeclaration" && importSources.includes(node.source.value)) for (const specifier of node.specifiers) {
126
+ if (specifier.type !== "ImportSpecifier") continue;
127
+ const importedName = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
128
+ const localName = specifier.local.name;
129
+ boundNamespaces.set(localName, {
130
+ localName,
131
+ importedName
132
+ });
133
+ debug("detected namespace", localName, "->", importedName);
134
+ }
135
+ if (node.type === "MemberExpression" && !node.computed && node.object.type === "Identifier" && boundNamespaces.has(node.object.name) && node.property.name.startsWith("get")) {
136
+ const namespace = boundNamespaces.get(node.object.name);
137
+ const getterName = node.property.name;
138
+ stateRefs.set(String(node.start), {
139
+ node,
140
+ namespace,
141
+ getterName,
142
+ contextField: deriveContextField(getterName),
143
+ start: node.start,
144
+ end: node.end
145
+ });
146
+ debug("state ref", getterName, "->", deriveContextField(getterName));
147
+ }
148
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier" && node.init !== null && node.init.type === "MemberExpression" && !node.init.computed && node.init.object.type === "Identifier" && boundNamespaces.has(node.init.object.name) && node.init.property.name.startsWith("get")) {
149
+ const getterName = node.init.property.name;
150
+ aliasBindings.set(node.id.name, {
151
+ localName: node.id.name,
152
+ namespace: boundNamespaces.get(node.init.object.name),
153
+ getterName,
154
+ contextField: deriveContextField(getterName)
155
+ });
156
+ debug("alias binding (direct)", node.id.name, "->", getterName);
157
+ }
158
+ if (node.type === "VariableDeclarator" && node.id.type === "ObjectPattern" && node.init !== null && node.init.type === "Identifier" && boundNamespaces.has(node.init.name)) {
159
+ const namespace = boundNamespaces.get(node.init.name);
160
+ for (const prop of node.id.properties) {
161
+ if (prop.type !== "Property") continue;
162
+ if (prop.key.type !== "Identifier") continue;
163
+ const keyName = prop.key.name;
164
+ if (!keyName.startsWith("get")) continue;
165
+ if (prop.value.type === "ObjectPattern") throw new Error(`[vite-plugin-qds-ui] Nested destructuring of QDS state getter '${keyName}' from '${namespace.localName}' is not supported. Use simple destructuring: const { ${keyName} } = ${namespace.localName}`);
166
+ if (prop.value.type !== "Identifier") continue;
167
+ aliasBindings.set(prop.value.name, {
168
+ localName: prop.value.name,
169
+ namespace,
170
+ getterName: keyName,
171
+ contextField: deriveContextField(keyName)
172
+ });
173
+ debug("alias binding (destructure)", prop.value.name, "->", keyName, prop.shorthand ? "(shorthand)" : "(renamed)");
174
+ }
175
+ }
176
+ };
177
+ walk(ast, { enter: collectImportsAndBindings });
178
+ if (boundNamespaces.size === 0) return { changed: false };
179
+ if (stateRefs.size === 0 && aliasBindings.size === 0) return { changed: false };
180
+ const elementTargetRanges = [];
181
+ function collectRefsInRange(rangeStart, rangeEnd, rootNode) {
182
+ const foundStateRefs = [];
183
+ for (const ref of stateRefs.values()) if (ref.start >= rangeStart && ref.end <= rangeEnd) foundStateRefs.push(ref);
184
+ const foundAliasRefs = [];
185
+ walk(rootNode, { enter(innerNode) {
186
+ if (innerNode.type === "Identifier" && aliasBindings.has(innerNode.name)) foundAliasRefs.push(aliasBindings.get(innerNode.name));
187
+ } });
188
+ return {
189
+ stateRefs: foundStateRefs,
190
+ aliasRefs: foundAliasRefs
191
+ };
192
+ }
193
+ function hasGetterInAttributes(element) {
194
+ for (const attr of element.openingElement.attributes) {
195
+ if (attr.type !== "JSXAttribute") continue;
196
+ if (!attr.value) continue;
197
+ if (attr.value.type !== "JSXExpressionContainer") continue;
198
+ if (attr.value.expression.type === "JSXEmptyExpression") continue;
199
+ const { stateRefs: attrStateRefs, aliasRefs: attrAliasRefs } = collectRefsInRange(attr.value.start, attr.value.end, attr.value.expression);
200
+ if (attrStateRefs.length > 0 || attrAliasRefs.length > 0) return true;
201
+ }
202
+ return false;
203
+ }
204
+ walk(ast, {
205
+ enter(node) {
206
+ ancestors.push(node);
207
+ if (node.type === "JSXElement") {
208
+ const jsxElement = node;
209
+ if (elementTargetRanges.some((range) => range.start <= jsxElement.start && range.end >= jsxElement.end)) return;
210
+ if (isInsideDollarBoundary(ancestors)) return;
211
+ if (!hasGetterInAttributes(jsxElement)) return;
212
+ const { stateRefs: elementStateRefs, aliasRefs: elementAliasRefs } = collectRefsInRange(jsxElement.start, jsxElement.end, jsxElement);
213
+ if (elementStateRefs.length === 0 && elementAliasRefs.length === 0) return;
214
+ elementTargetRanges.push({
215
+ start: jsxElement.start,
216
+ end: jsxElement.end
217
+ });
218
+ transformTargets.push({
219
+ kind: "element",
220
+ stateRefs: elementStateRefs,
221
+ aliasRefs: elementAliasRefs,
222
+ start: jsxElement.start,
223
+ end: jsxElement.end
224
+ });
225
+ }
226
+ if (node.type === "JSXExpressionContainer") {
227
+ if (node.expression.type === "JSXEmptyExpression") return;
228
+ const containerStart = node.start;
229
+ const containerEnd = node.end;
230
+ if (isInsideDollarBoundary(ancestors)) {
231
+ const { stateRefs: containerStateRefs, aliasRefs: containerAliasRefs } = collectRefsInRange(containerStart, containerEnd, node.expression);
232
+ if (containerStateRefs.length > 0 || containerAliasRefs.length > 0) pendingWarnings.push("[vite-plugin-qds-ui] QDS state access found inside a $() boundary. The compiler cannot transform state access inside event handlers. Use the state through a JSX expression instead.");
233
+ return;
234
+ }
235
+ if (ancestors[ancestors.length - 2]?.type === "JSXAttribute") return;
236
+ if (transformTargets.some((target) => target.start <= containerStart && target.end >= containerEnd)) return;
237
+ const { stateRefs: containerStateRefs, aliasRefs: containerAliasRefs } = collectRefsInRange(containerStart, containerEnd, node.expression);
238
+ if (containerStateRefs.length === 0 && containerAliasRefs.length === 0) return;
239
+ transformTargets.push({
240
+ kind: "expression",
241
+ container: node,
242
+ stateRefs: containerStateRefs,
243
+ aliasRefs: containerAliasRefs,
244
+ start: containerStart,
245
+ end: containerEnd
246
+ });
247
+ }
248
+ },
249
+ leave() {
250
+ ancestors.pop();
251
+ }
252
+ });
253
+ for (const warning of pendingWarnings) warn?.(warning);
254
+ const collectedHookTargets = [];
255
+ walk(ast, { enter(node) {
256
+ if (node.type !== "CallExpression") return;
257
+ if (node.callee.type !== "Identifier" || node.callee.name !== "component$") return;
258
+ if (!node.arguments.length) return;
259
+ const fnArg = node.arguments[0];
260
+ const fnBody = fnArg.body;
261
+ if (!fnBody || fnBody.type !== "BlockStatement") return;
262
+ const bodyStmts = fnBody.body;
263
+ const rootPositions = /* @__PURE__ */ new Map();
264
+ walk(fnArg, { enter(inner) {
265
+ if (inner.type !== "JSXElement") return;
266
+ const opening = inner.openingElement;
267
+ if (opening.name.type !== "JSXMemberExpression") return;
268
+ const nsObj = opening.name.object;
269
+ const nsProp = opening.name.property;
270
+ if (nsObj.type === "JSXIdentifier" && nsProp.type === "JSXIdentifier" && nsProp.name === "root" && boundNamespaces.has(nsObj.name)) rootPositions.set(nsObj.name, opening.end);
271
+ } });
272
+ let returnPos = -1;
273
+ let returnNode = null;
274
+ for (const bStmt of bodyStmts) if (bStmt.type === "ReturnStatement") {
275
+ returnPos = bStmt.start;
276
+ returnNode = bStmt;
277
+ break;
278
+ }
279
+ const aliasDeclRanges = /* @__PURE__ */ new Map();
280
+ for (const bStmt of bodyStmts) {
281
+ if (bStmt.type !== "VariableDeclaration") continue;
282
+ for (const decl of bStmt.declarations) {
283
+ if (decl.id.type !== "ObjectPattern" || decl.init?.type !== "Identifier") continue;
284
+ if (!boundNamespaces.has(decl.init.name)) continue;
285
+ for (const prop of decl.id.properties) {
286
+ if (prop.type !== "Property" || prop.key.type !== "Identifier") continue;
287
+ if (!prop.key.name.startsWith("get")) continue;
288
+ const localName = prop.value.type === "Identifier" ? prop.value.name : prop.key.name;
289
+ aliasDeclRanges.set(localName, {
290
+ start: bStmt.start,
291
+ end: bStmt.end
292
+ });
293
+ }
294
+ }
295
+ }
296
+ for (const bStmt of bodyStmts) {
297
+ let expr;
298
+ let producedBinding = null;
299
+ if (bStmt.type === "ExpressionStatement") {
300
+ const exprStmt = bStmt.expression;
301
+ if (exprStmt.type !== "CallExpression") continue;
302
+ if (exprStmt.callee.type !== "Identifier" || !HOOK_NAMES.has(exprStmt.callee.name)) continue;
303
+ expr = exprStmt;
304
+ } else if (bStmt.type === "VariableDeclaration") {
305
+ const decls = bStmt.declarations;
306
+ if (decls.length !== 1) continue;
307
+ const decl = decls[0];
308
+ if (!decl.init || decl.init.type !== "CallExpression") continue;
309
+ if (decl.init.callee.type !== "Identifier" || !HOOK_NAMES.has(decl.init.callee.name)) continue;
310
+ if (decl.id.type !== "Identifier") continue;
311
+ expr = decl.init;
312
+ producedBinding = decl.id.name;
313
+ } else continue;
314
+ const callExpr = expr;
315
+ const hookStateRefs = [];
316
+ for (const ref of stateRefs.values()) if (ref.start >= callExpr.start && ref.end <= callExpr.end) hookStateRefs.push(ref);
317
+ const hookAliasRefs = [];
318
+ const seen = /* @__PURE__ */ new Set();
319
+ walk(bStmt, { enter(inner) {
320
+ if (inner.type !== "Identifier") return;
321
+ if (aliasBindings.has(inner.name) && !seen.has(inner.name)) {
322
+ seen.add(inner.name);
323
+ hookAliasRefs.push(aliasBindings.get(inner.name));
324
+ }
325
+ } });
326
+ if (hookStateRefs.length === 0 && hookAliasRefs.length === 0) continue;
327
+ const primary = hookStateRefs.length > 0 ? hookStateRefs[0] : {
328
+ namespace: hookAliasRefs[0].namespace,
329
+ contextField: hookAliasRefs[0].contextField
330
+ };
331
+ const rootEnd = rootPositions.get(primary.namespace.localName);
332
+ if (rootEnd === void 0) {
333
+ warn?.(`[vite-plugin-qds-ui] Getter used in ${expr.callee.name} but no <${primary.namespace.localName}.root> found in JSX return. Move this into a component rendered inside <${primary.namespace.localName}.root>.`);
334
+ continue;
335
+ }
336
+ let aliasDeclRange = null;
337
+ for (const alias of hookAliasRefs) {
338
+ const range = aliasDeclRanges.get(alias.localName);
339
+ if (range) {
340
+ aliasDeclRange = range;
341
+ break;
342
+ }
343
+ }
344
+ const jsxConsumers = [];
345
+ if (producedBinding !== null && returnNode !== null) {
346
+ walk(returnNode, { enter(inner) {
347
+ if (inner.type !== "JSXElement") return;
348
+ const jsxEl = inner;
349
+ let hasProducedRef = false;
350
+ walk(jsxEl, { enter(deepNode) {
351
+ if (deepNode.type !== "Identifier") return;
352
+ if (deepNode.name === producedBinding) hasProducedRef = true;
353
+ } });
354
+ if (hasProducedRef) jsxConsumers.push({
355
+ start: jsxEl.start,
356
+ end: jsxEl.end
357
+ });
358
+ } });
359
+ filterToInnermostConsumers(jsxConsumers);
360
+ }
361
+ collectedHookTargets.push({
362
+ statementStart: bStmt.start,
363
+ statementEnd: bStmt.end,
364
+ stateRefs: hookStateRefs,
365
+ aliasRefs: hookAliasRefs,
366
+ rootOpeningEnd: rootEnd,
367
+ returnStart: returnPos,
368
+ aliasDeclarationRange: aliasDeclRange,
369
+ producedBinding,
370
+ jsxConsumers
371
+ });
372
+ }
373
+ } });
374
+ if (transformTargets.length === 0 && collectedHookTargets.length === 0) return { changed: false };
375
+ const generated = transformTargets.map((target, index) => {
376
+ const uniqueNamespaces = collectUniqueNamespaces(target);
377
+ const firstRef = getFirstRef(target);
378
+ const name = generateComponentName(firstRef.namespace.localName, firstRef.contextField, index);
379
+ const isSingle = uniqueNamespaces.length === 1;
380
+ if (target.kind === "element") return {
381
+ name,
382
+ code: generateElementComponent(name, uniqueNamespaces, rewriteExpression(code, target.start, target.end, target.stateRefs, target.aliasRefs, isSingle))
383
+ };
384
+ return {
385
+ name,
386
+ code: generateExpressionComponent(name, uniqueNamespaces, rewriteExpression(code, target.container.expression.start, target.container.expression.end, target.stateRefs, target.aliasRefs, isSingle))
387
+ };
388
+ });
389
+ let lastImportEnd = 0;
390
+ for (const node of ast.body) if (node.type === "ImportDeclaration") lastImportEnd = Math.max(lastImportEnd, node.end);
391
+ debug("generated", generated.length, "components, insert at", lastImportEnd);
392
+ for (let i = 0; i < transformTargets.length; i++) s.overwrite(transformTargets[i].start, transformTargets[i].end, `<${generated[i].name} />`);
393
+ const coreNeeded = new Set(["component$", "useContext"]);
394
+ for (const node of ast.body) {
395
+ if (node.type !== "ImportDeclaration") continue;
396
+ if (code.slice(node.source.start + 1, node.source.end - 1) !== "@qwik.dev/core") continue;
397
+ if (node.importKind === "type") continue;
398
+ for (const spec of node.specifiers) {
399
+ if (spec.type !== "ImportSpecifier") continue;
400
+ const importedName = spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value;
401
+ coreNeeded.delete(importedName);
402
+ }
403
+ if (coreNeeded.size === 0) continue;
404
+ const lastSpec = node.specifiers[node.specifiers.length - 1];
405
+ if (lastSpec) {
406
+ s.appendLeft(lastSpec.end, `, ${[...coreNeeded].join(", ")}`);
407
+ coreNeeded.clear();
408
+ }
409
+ }
410
+ if (coreNeeded.size > 0) s.appendLeft(lastImportEnd, `\nimport { ${[...coreNeeded].join(", ")} } from "@qwik.dev/core";`);
411
+ for (const g of generated) s.appendLeft(lastImportEnd, g.code);
412
+ const removedRanges = /* @__PURE__ */ new Set();
413
+ for (let hi = collectedHookTargets.length - 1; hi >= 0; hi--) {
414
+ const ht = collectedHookTargets[hi];
415
+ const uniqueNs = collectUniqueNamespacesFromRefs(ht.stateRefs, ht.aliasRefs);
416
+ const primary = ht.stateRefs.length > 0 ? ht.stateRefs[0] : {
417
+ namespace: ht.aliasRefs[0].namespace,
418
+ contextField: ht.aliasRefs[0].contextField
419
+ };
420
+ const compName = generateComponentName(primary.namespace.localName, primary.contextField, transformTargets.length + hi);
421
+ const isSingle = uniqueNs.length === 1;
422
+ const rewrittenHook = rewriteExpression(code, ht.statementStart, ht.statementEnd, ht.stateRefs, ht.aliasRefs, isSingle);
423
+ const isChained = ht.producedBinding !== null && ht.jsxConsumers.length > 0;
424
+ let hookComponentCode;
425
+ if (isChained) {
426
+ const consumers = ht.jsxConsumers;
427
+ hookComponentCode = generateChainedHookComponent(compName, uniqueNs, rewrittenHook, consumers.length === 1 ? code.slice(consumers[0].start, consumers[0].end) : `<>\n ${consumers.map((c) => code.slice(c.start, c.end)).join("\n ")}\n </>`);
428
+ } else hookComponentCode = generateHookComponent(compName, uniqueNs, rewrittenHook);
429
+ s.remove(ht.statementStart, ht.statementEnd);
430
+ if (ht.aliasDeclarationRange) {
431
+ const key = `${ht.aliasDeclarationRange.start}-${ht.aliasDeclarationRange.end}`;
432
+ if (!removedRanges.has(key)) {
433
+ removedRanges.add(key);
434
+ s.remove(ht.aliasDeclarationRange.start, ht.aliasDeclarationRange.end);
435
+ }
436
+ }
437
+ if (isChained) for (const consumer of ht.jsxConsumers) s.remove(consumer.start, consumer.end);
438
+ s.appendLeft(ht.returnStart, hookComponentCode);
439
+ s.appendLeft(ht.rootOpeningEnd, `\n<${compName} />`);
440
+ }
441
+ return { changed: true };
442
+ }
443
+
444
+ //#endregion
445
+ export { uiTransformCore };
@@ -0,0 +1 @@
1
+ export {};
@@ -11,17 +11,9 @@ export declare function extractMDXAttributes(jsxNode: MDXJSXElement): {
11
11
  titleProp?: string;
12
12
  descriptionProp?: string;
13
13
  };
14
- /**
15
- * Transform MDX file using remark-mdx for semantic analysis
16
- * @param code - Original MDX source code
17
- * @param id - File ID
18
- * @param importSources - Sources to scan for imports
19
- * @param availableCollections - Set of available collection names
20
- * @param collectionNames - Map of alias to collection name
21
- * @param packs - Custom packs configuration
22
- * @param debug - Debug logging function
23
- * @returns Transformation result or null
24
- */
14
+ export declare function transformMDXFileCore(code: string, id: string, s: MagicString, importSources: string[], availableCollections: Set<string>, collectionNames: Map<string, string>, packs: PacksMap | undefined, debug: (message: string, ...data: unknown[]) => void): {
15
+ changed: boolean;
16
+ };
25
17
  export declare function transformMDXFile(code: string, id: string, importSources: string[], availableCollections: Set<string>, collectionNames: Map<string, string>, packs: PacksMap | undefined, debug: (message: string, ...data: unknown[]) => void): {
26
18
  code: string;
27
19
  map: ReturnType<MagicString["generateMap"]>;
@@ -40,27 +40,16 @@ import remarkMdx from "remark-mdx";
40
40
  descriptionProp
41
41
  };
42
42
  }
43
- /**
44
- * Transform MDX file using remark-mdx for semantic analysis
45
- * @param code - Original MDX source code
46
- * @param id - File ID
47
- * @param importSources - Sources to scan for imports
48
- * @param availableCollections - Set of available collection names
49
- * @param collectionNames - Map of alias to collection name
50
- * @param packs - Custom packs configuration
51
- * @param debug - Debug logging function
52
- * @returns Transformation result or null
53
- */ function transformMDXFile(code, id, importSources, availableCollections, collectionNames, packs, debug) {
43
+ function transformMDXFileCore(code, id, s, importSources, availableCollections, collectionNames, packs, debug) {
54
44
  try {
55
45
  debug(`[MDX] Parsing ${id}`);
56
46
  const mdast = remark().use(remarkMdx).parse(code);
57
47
  const aliasToPack = extractMDXImportAliases(mdast, importSources, availableCollections, collectionNames, packs, debug);
58
48
  if (aliasToPack.size === 0) {
59
49
  debug(`[MDX] No icon imports found in ${id}`);
60
- return null;
50
+ return { changed: false };
61
51
  }
62
52
  debug("[MDX] Found icon imports:", Array.from(aliasToPack.entries()));
63
- const s = new MagicString(code);
64
53
  const ctx = {
65
54
  usedImports: /* @__PURE__ */ new Set(),
66
55
  importVars: /* @__PURE__ */ new Set(),
@@ -102,7 +91,7 @@ import remarkMdx from "remark-mdx";
102
91
  } });
103
92
  if (!hasChanges) {
104
93
  debug(`[MDX] No icon elements found in ${id}`);
105
- return null;
94
+ return { changed: false };
106
95
  }
107
96
  const importStatements = Array.from(ctx.usedImports).map((virtualId) => {
108
97
  return `import ${ctx.virtualToVar.get(virtualId)} from '${virtualId}';`;
@@ -117,15 +106,20 @@ import remarkMdx from "remark-mdx";
117
106
  }
118
107
  s.appendLeft(insertPos, `${importStatements}\n`);
119
108
  debug(`[MDX] Transformation complete for ${id}`);
120
- return {
121
- code: s.toString(),
122
- map: s.generateMap({ hires: true })
123
- };
109
+ return { changed: true };
124
110
  } catch (error) {
125
111
  debug("[MDX] Error transforming:", error);
126
- return null;
112
+ return { changed: false };
127
113
  }
128
114
  }
115
+ function transformMDXFile(code, id, importSources, availableCollections, collectionNames, packs, debug) {
116
+ const s = new MagicString(code);
117
+ if (!transformMDXFileCore(code, id, s, importSources, availableCollections, collectionNames, packs, debug).changed) return null;
118
+ return {
119
+ code: s.toString(),
120
+ map: s.generateMap({ hires: true })
121
+ };
122
+ }
129
123
 
130
124
  //#endregion
131
- export { extractMDXAttributes, transformMDXFile };
125
+ export { extractMDXAttributes, transformMDXFile, transformMDXFileCore };
@@ -33,18 +33,9 @@ export declare function findIconElements(ast: Program, aliasToPack: Map<string,
33
33
  * @returns True if transformation was applied
34
34
  */
35
35
  export declare function transformIconElement(elem: JSXElement, s: MagicString, source: string, aliasToPack: Map<string, string>, collectionNames: Map<string, string>, packs: PacksMap | undefined, ctx: TransformContext, debug: (message: string, ...data: unknown[]) => void): boolean;
36
- /**
37
- * Transform TSX/JSX file
38
- * @param code - Original source code
39
- * @param id - File ID
40
- * @param ast - Parsed AST
41
- * @param aliasToPack - Map of aliases to pack names
42
- * @param collectionNames - Map of alias to collection name
43
- * @param availableCollections - Set of available collection names
44
- * @param packs - Custom packs configuration
45
- * @param debug - Debug logging function
46
- * @returns Transformation result or null
47
- */
36
+ export declare function transformTSXFileCore(code: string, id: string, ast: Program, s: MagicString, aliasToPack: Map<string, string>, collectionNames: Map<string, string>, availableCollections: Set<string>, packs: PacksMap | undefined, debug: (message: string, ...data: unknown[]) => void): {
37
+ changed: boolean;
38
+ };
48
39
  export declare function transformTSXFile(code: string, id: string, ast: Program, aliasToPack: Map<string, string>, collectionNames: Map<string, string>, availableCollections: Set<string>, packs: PacksMap | undefined, debug: (message: string, ...data: unknown[]) => void): {
49
40
  code: string;
50
41
  map: ReturnType<MagicString["generateMap"]>;
@@ -108,27 +108,15 @@ import { walk } from "oxc-walker";
108
108
  debug(`[TRANSFORM_ICON] Successfully transformed ${alias}.${iconName} to SVG JSX`);
109
109
  return true;
110
110
  }
111
- /**
112
- * Transform TSX/JSX file
113
- * @param code - Original source code
114
- * @param id - File ID
115
- * @param ast - Parsed AST
116
- * @param aliasToPack - Map of aliases to pack names
117
- * @param collectionNames - Map of alias to collection name
118
- * @param availableCollections - Set of available collection names
119
- * @param packs - Custom packs configuration
120
- * @param debug - Debug logging function
121
- * @returns Transformation result or null
122
- */ function transformTSXFile(code, id, ast, aliasToPack, collectionNames, availableCollections, packs, debug) {
111
+ function transformTSXFileCore(code, id, ast, s, aliasToPack, collectionNames, availableCollections, packs, debug) {
123
112
  debug(`[TRANSFORM] Processing ${id} with ${aliasToPack.size} aliases:`, Array.from(aliasToPack.entries()));
124
113
  debug(`[DEBUG] collectionNames map: ${JSON.stringify(Array.from(collectionNames.entries()))}`);
125
114
  const iconElements = findIconElements(ast, aliasToPack, collectionNames, availableCollections);
126
115
  if (iconElements.length === 0) {
127
116
  debug(`[TRANSFORM] No icon elements found in ${id}`);
128
- return null;
117
+ return { changed: false };
129
118
  }
130
119
  debug(`[TRANSFORM] Found ${iconElements.length} icon elements in ${id}`);
131
- const s = new MagicString(code);
132
120
  const ctx = {
133
121
  usedImports: /* @__PURE__ */ new Set(),
134
122
  importVars: /* @__PURE__ */ new Set(),
@@ -136,31 +124,34 @@ import { walk } from "oxc-walker";
136
124
  };
137
125
  let hasChanges = false;
138
126
  for (let i = iconElements.length - 1; i >= 0; i--) if (transformIconElement(iconElements[i], s, code, aliasToPack, collectionNames, packs, ctx, debug)) hasChanges = true;
139
- if (hasChanges) {
140
- if (ctx.usedImports.size > 0) {
141
- const virtualImports = `${Array.from(ctx.usedImports).map((virtualId) => {
142
- return `import ${ctx.virtualToVar.get(virtualId)} from '${virtualId}';`;
143
- }).join("\n")}\n`;
144
- let insertPos = 0;
145
- let importCount = 0;
146
- for (const node of ast.body) if (node.type === "ImportDeclaration") {
147
- insertPos = Math.max(insertPos, node.end);
148
- importCount++;
149
- }
150
- debug(`Found ${importCount} imports, inserting at position ${insertPos}`);
151
- s.appendLeft(insertPos, `\n${virtualImports.trimEnd()}\n`);
127
+ if (!hasChanges) {
128
+ debug(`[TRANSFORM] No changes made to ${id}`);
129
+ return { changed: false };
130
+ }
131
+ if (ctx.usedImports.size > 0) {
132
+ const virtualImports = `${Array.from(ctx.usedImports).map((virtualId) => {
133
+ return `import ${ctx.virtualToVar.get(virtualId)} from '${virtualId}';`;
134
+ }).join("\n")}\n`;
135
+ let insertPos = 0;
136
+ let importCount = 0;
137
+ for (const node of ast.body) if (node.type === "ImportDeclaration") {
138
+ insertPos = Math.max(insertPos, node.end);
139
+ importCount++;
152
140
  }
153
- const resultCode = s.toString();
154
- debug(`Final transformed code length: ${resultCode.length}`);
155
- debug(`[TRANSFORM] Transformation successful for ${id}, returning transformed code`);
156
- return {
157
- code: resultCode,
158
- map: s.generateMap({ hires: true })
159
- };
141
+ debug(`Found ${importCount} imports, inserting at position ${insertPos}`);
142
+ s.appendLeft(insertPos, `\n${virtualImports.trimEnd()}\n`);
160
143
  }
161
- debug(`[TRANSFORM] No changes made to ${id}`);
162
- return null;
144
+ debug(`[TRANSFORM] Transformation successful for ${id}`);
145
+ return { changed: true };
146
+ }
147
+ function transformTSXFile(code, id, ast, aliasToPack, collectionNames, availableCollections, packs, debug) {
148
+ const s = new MagicString(code);
149
+ if (!transformTSXFileCore(code, id, ast, s, aliasToPack, collectionNames, availableCollections, packs, debug).changed) return null;
150
+ return {
151
+ code: s.toString(),
152
+ map: s.generateMap({ hires: true })
153
+ };
163
154
  }
164
155
 
165
156
  //#endregion
166
- export { findIconElements, isIconElement, transformIconElement, transformTSXFile };
157
+ export { findIconElements, isIconElement, transformIconElement, transformTSXFile, transformTSXFileCore };