@qds.dev/tools 0.7.5 → 0.8.2

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/docs/component-props.qwik.mjs +500 -0
  2. package/lib/docs/index.qwik.mjs +3 -0
  3. package/lib/playground/generate-jsx.qwik.mjs +153 -0
  4. package/lib/playground/index.qwik.mjs +10 -0
  5. package/lib/playground/prop-extraction.qwik.mjs +500 -0
  6. package/lib/playground/scenario-injection.qwik.mjs +68 -0
  7. package/lib/repl/bundler/bundled.qwik.mjs +38 -0
  8. package/lib/repl/bundler/index.qwik.mjs +89 -0
  9. package/lib/repl/index.qwik.mjs +21 -0
  10. package/lib/repl/repl-constants.qwik.mjs +5 -0
  11. package/lib/rolldown/index.qwik.mjs +2 -1
  12. package/lib/rolldown/inline-asset.qwik.mjs +171 -0
  13. package/lib/rolldown/playground.qwik.mjs +67 -0
  14. package/lib/vite/index.qwik.mjs +2 -1
  15. package/lib/vite/minify-content.qwik.mjs +6 -4
  16. package/lib-types/tools/docs/component-props.d.ts +32 -0
  17. package/lib-types/tools/docs/component-props.unit.d.ts +1 -0
  18. package/lib-types/tools/docs/generate-metadata.d.ts +1 -0
  19. package/lib-types/tools/docs/generate-metadata.unit.d.ts +1 -0
  20. package/lib-types/tools/docs/index.d.ts +2 -0
  21. package/lib-types/tools/playground/generate-jsx.d.ts +9 -0
  22. package/lib-types/tools/playground/generate-metadata.d.ts +1 -0
  23. package/lib-types/tools/playground/generate-metadata.unit.d.ts +1 -0
  24. package/lib-types/tools/playground/index.d.ts +5 -0
  25. package/lib-types/tools/playground/prop-extraction.d.ts +32 -0
  26. package/lib-types/tools/playground/prop-extraction.unit.d.ts +1 -0
  27. package/lib-types/tools/playground/scenario-injection.d.ts +2 -0
  28. package/lib-types/tools/playground/scenario-injection.unit.d.ts +1 -0
  29. package/lib-types/tools/rolldown/index.d.ts +2 -0
  30. package/lib-types/tools/rolldown/inline-asset.d.ts +18 -0
  31. package/lib-types/tools/rolldown/inline-asset.unit.d.ts +1 -0
  32. package/lib-types/tools/rolldown/playground.d.ts +8 -0
  33. package/lib-types/tools/rolldown/playground.unit.d.ts +1 -0
  34. package/lib-types/tools/src/vite.d.ts +1 -0
  35. package/lib-types/tools/utils/fs-mock.d.ts +17 -0
  36. package/lib-types/tools/vite/index.d.ts +3 -2
  37. package/package.json +6 -2
  38. package/lib-types/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,500 @@
1
+ import { charIn, charNotIn, createRegExp, exactly, maybe, oneOrMore, whitespace } from "magic-regexp";
2
+ import { parseSync } from "oxc-parser";
3
+ import { walk } from "oxc-walker";
4
+ import { existsSync, statSync } from "node:fs";
5
+ import { dirname, join, relative } from "node:path";
6
+ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
7
+
8
+ //#region docs/component-props.ts
9
+ const JSDOC_START_PATTERN = createRegExp(exactly("/**"));
10
+ const JSDOC_END_PATTERN = createRegExp(exactly("*/"));
11
+ const JSDOC_PATTERN = createRegExp(exactly("/**"), charNotIn("*").or(exactly("*").notAfter(exactly("/"))).times.any(), exactly("*/"));
12
+ const componentProps = (options) => {
13
+ const { componentsDir, outputDir, debug: isDebugMode = false } = options;
14
+ const debug = (message, ...data) => {
15
+ if (!isDebugMode) return;
16
+ console.log(`[component-props] ${message}`, ...data);
17
+ };
18
+ return {
19
+ name: "vite-plugin-qds-component-props",
20
+ enforce: "pre",
21
+ configResolved() {
22
+ debug("Component props plugin initialized");
23
+ generateAllComponentMetadata(componentsDir, outputDir, debug).catch((error) => {
24
+ console.error("[component-props] Error generating metadata:", error);
25
+ });
26
+ },
27
+ async handleHotUpdate(ctx) {
28
+ if (!ctx.file.includes(componentsDir) || !ctx.file.endsWith(".tsx")) return;
29
+ const componentName = extractComponentName(ctx.file, componentsDir);
30
+ if (!componentName) return;
31
+ await generateComponentMetadata(componentName, componentsDir, outputDir, debug);
32
+ }
33
+ };
34
+ };
35
+ async function generateAllComponentMetadata(componentsDir, outputDir, debug) {
36
+ const componentDirs = await findComponentDirs(componentsDir);
37
+ await Promise.all(componentDirs.map(async (componentName) => generateComponentMetadata(componentName, componentsDir, outputDir, debug)));
38
+ }
39
+ async function generateComponentMetadata(componentName, componentsDir, outputDir, debug) {
40
+ try {
41
+ debug(`Generating metadata for component: ${componentName}`);
42
+ const pieces = await parseComponentPieces(componentName, componentsDir, debug);
43
+ debug(`Found ${pieces.length} pieces for ${componentName}:`, pieces.map((p) => p.name));
44
+ const metadata = {
45
+ componentName,
46
+ pieces
47
+ };
48
+ const outputPath = join(outputDir, `${componentName}.json`);
49
+ await mkdir(dirname(outputPath), { recursive: true });
50
+ await writeFile(outputPath, JSON.stringify(metadata, null, 2), "utf-8");
51
+ debug(`Wrote metadata to ${outputPath}`);
52
+ } catch (error) {
53
+ debug(`Error generating metadata for ${componentName}:`, error);
54
+ console.error(`[component-props] Error generating metadata for ${componentName}:`, error);
55
+ }
56
+ }
57
+ async function parseComponentPieces(componentName, componentsDir, debug) {
58
+ const componentPath = join(componentsDir, componentName);
59
+ debug(`Parsing pieces for ${componentName}`);
60
+ if (!existsSync(componentPath)) {
61
+ debug(`Component path does not exist: ${componentPath}`);
62
+ return [];
63
+ }
64
+ const files = await readdir(componentPath);
65
+ debug(`Found ${files.length} files in directory:`, files);
66
+ const pieceFiles = files.filter((file) => isPieceFile(file, debug));
67
+ debug(`Filtered to ${pieceFiles.length} piece files:`, pieceFiles);
68
+ const piecePromises = pieceFiles.map(async (file) => {
69
+ const filePath = join(componentPath, file);
70
+ if (!statSync(filePath).isFile()) {
71
+ debug(`Skipping ${filePath} (not a file)`);
72
+ return null;
73
+ }
74
+ const pieceName = extractPieceName(file, componentName);
75
+ debug(`Parsing piece file: ${file} -> piece name: ${pieceName}`);
76
+ const props = await parseComponentFile(filePath, pieceName, debug);
77
+ if (props.length === 0) {
78
+ debug(`Skipping ${pieceName} (no props)`);
79
+ return null;
80
+ }
81
+ debug(`Found ${props.length} props in ${pieceName}`);
82
+ return {
83
+ name: pieceName,
84
+ props
85
+ };
86
+ });
87
+ const pieces = (await Promise.all(piecePromises)).filter((piece) => piece !== null);
88
+ pieces.sort((a, b) => {
89
+ if (a.name === "Root") return -1;
90
+ if (b.name === "Root") return 1;
91
+ return a.name.localeCompare(b.name);
92
+ });
93
+ debug(`Final pieces for ${componentName}:`, pieces.map((p) => p.name));
94
+ return pieces;
95
+ }
96
+ function isPieceFile(file, debug) {
97
+ if (!file.endsWith(".tsx")) {
98
+ debug(`Skipping ${file} (not .tsx)`);
99
+ return false;
100
+ }
101
+ if (file.endsWith("-context.tsx")) {
102
+ debug(`Skipping ${file} (is -context.tsx)`);
103
+ return false;
104
+ }
105
+ if (file.includes(".browser.")) {
106
+ debug(`Skipping ${file} (contains .browser.)`);
107
+ return false;
108
+ }
109
+ debug(`Including ${file} as piece file`);
110
+ return true;
111
+ }
112
+ function extractPieceName(file, componentName) {
113
+ return file.replace(`${componentName}-`, "").replace(".tsx", "").split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
114
+ }
115
+ async function parseComponentFile(filePath, pieceName, debug) {
116
+ debug(`parseComponentFile: ${filePath} (piece: ${pieceName})`);
117
+ const source = await readFile(filePath, "utf-8");
118
+ const ast = parseSync(filePath, source);
119
+ if (ast.errors.length > 0) {
120
+ debug(`Parse errors in ${filePath}:`, ast.errors);
121
+ return [];
122
+ }
123
+ const typeAliasCache = buildTypeAliasCache(ast.program);
124
+ const analysisResult = analyzeProgram(ast.program, debug, typeAliasCache);
125
+ if (!analysisResult.propsType) {
126
+ debug(`No props type found in ${filePath}`);
127
+ return [];
128
+ }
129
+ debug(`Bindable props:`, Array.from(analysisResult.bindableProps));
130
+ const typeLiterals = /* @__PURE__ */ new Set();
131
+ const bindableInlineTypes = /* @__PURE__ */ new Set();
132
+ collectTypeLiterals(analysisResult.propsType, typeAliasCache, typeLiterals);
133
+ collectBindableInlineTypes(analysisResult.propsType, bindableInlineTypes);
134
+ for (const bindableType of bindableInlineTypes) typeLiterals.add(bindableType);
135
+ for (const bindableType of analysisResult.aliasBindableTypes) {
136
+ bindableInlineTypes.add(bindableType);
137
+ typeLiterals.add(bindableType);
138
+ }
139
+ const propsMap = /* @__PURE__ */ new Map();
140
+ for (const typeLiteral of typeLiterals) {
141
+ const isFromBindable = bindableInlineTypes.has(typeLiteral);
142
+ extractPropsFromType(typeLiteral, source, analysisResult.bindableProps, propsMap, typeAliasCache, isFromBindable);
143
+ }
144
+ if (analysisResult.bindableTypeName) {
145
+ const bindableTypeAlias = typeAliasCache.get(analysisResult.bindableTypeName);
146
+ if (bindableTypeAlias) {
147
+ const typeAnnotation = "typeAnnotation" in bindableTypeAlias ? bindableTypeAlias.typeAnnotation : null;
148
+ if (typeAnnotation?.type === "TSTypeLiteral") extractPropsFromType(typeAnnotation, source, analysisResult.bindableProps, propsMap, typeAliasCache, true);
149
+ else if (typeAnnotation?.type === "TSTypeReference") {
150
+ const resolvedType = resolveTypeAliasCached(typeAnnotation, typeAliasCache);
151
+ if (resolvedType?.type === "TSTypeLiteral") extractPropsFromType(resolvedType, source, analysisResult.bindableProps, propsMap, typeAliasCache, true);
152
+ }
153
+ }
154
+ }
155
+ const finalProps = Array.from(propsMap.values());
156
+ debug(`Extracted ${finalProps.length} props from ${filePath}`);
157
+ return finalProps;
158
+ }
159
+ function buildTypeAliasCache(program) {
160
+ const cache = /* @__PURE__ */ new Map();
161
+ walk(program, { enter(node) {
162
+ if (node.type === "TSTypeAliasDeclaration") {
163
+ const typeName = getTypeName(node);
164
+ if (typeName) cache.set(typeName, node);
165
+ }
166
+ } });
167
+ return cache;
168
+ }
169
+ function analyzeProgram(program, debug, typeAliasCache) {
170
+ let propsType = null;
171
+ const bindableProps = /* @__PURE__ */ new Set();
172
+ let bindableTypeName = null;
173
+ const aliasBindableTypes = [];
174
+ let foundComponentCall = false;
175
+ walk(program, { enter(node) {
176
+ if (!foundComponentCall && node.type === "CallExpression") {
177
+ const callee = "callee" in node ? node.callee : null;
178
+ if (callee) {
179
+ if (callee.type === "Identifier" && "name" in callee && callee.name === "component$" || callee.type === "MemberExpression" && "property" in callee && callee.property && typeof callee.property === "object" && "name" in callee.property && callee.property.name === "component$") {
180
+ foundComponentCall = true;
181
+ const firstArg = ("arguments" in node ? node.arguments : [])[0];
182
+ if (firstArg && typeof firstArg === "object" && firstArg.type === "ArrowFunctionExpression") {
183
+ if ("typeArguments" in node && node.typeArguments) {
184
+ const typeArgs = node.typeArguments;
185
+ if (Array.isArray(typeArgs.params) && typeArgs.params.length > 0) {
186
+ const firstTypeArg = typeArgs.params[0];
187
+ if (firstTypeArg && typeof firstTypeArg === "object" && "type" in firstTypeArg) {
188
+ debug(`Found component$ call with generic type parameter`);
189
+ propsType = firstTypeArg;
190
+ return;
191
+ }
192
+ }
193
+ }
194
+ const firstParam = ("params" in firstArg ? firstArg.params : [])[0];
195
+ if (firstParam && typeof firstParam === "object") {
196
+ const typeAnnotation = "typeAnnotation" in firstParam ? firstParam.typeAnnotation : null;
197
+ if (typeAnnotation && typeof typeAnnotation === "object") {
198
+ const paramType = "typeAnnotation" in typeAnnotation ? typeAnnotation.typeAnnotation : null;
199
+ if (paramType && typeof paramType === "object") {
200
+ debug(`Found component$ call with props type`);
201
+ propsType = paramType;
202
+ return;
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ if (node.type === "TSTypeAliasDeclaration") {
211
+ const typeName = getTypeName(node);
212
+ if (!typeName) return;
213
+ const typeAnnotation = "typeAnnotation" in node ? node.typeAnnotation : null;
214
+ if (!typeAnnotation) return;
215
+ if (!propsType && typeName.endsWith("Props")) {
216
+ debug(`Found *Props type: ${typeName}`);
217
+ if ("type" in typeAnnotation) propsType = typeAnnotation;
218
+ }
219
+ if (typeName.endsWith("Props") && typeAnnotation.type === "TSIntersectionType") {
220
+ const types = "types" in typeAnnotation ? typeAnnotation.types : [];
221
+ for (const type of types) {
222
+ if (type.type !== "TSTypeReference") continue;
223
+ const typeNameNode = "typeName" in type ? type.typeName : null;
224
+ if (!typeNameNode || !("name" in typeNameNode) || typeNameNode.name !== "BindableProps") continue;
225
+ const firstParam = getTypeParameters(type)[0];
226
+ if (firstParam && typeof firstParam === "object" && firstParam !== null && "type" in firstParam) {
227
+ if (firstParam.type === "TSTypeReference") {
228
+ const refTypeName = firstParam.typeName?.name;
229
+ if (typeof refTypeName === "string") bindableTypeName = refTypeName;
230
+ } else if (firstParam.type === "TSTypeLiteral") aliasBindableTypes.push(firstParam);
231
+ }
232
+ }
233
+ }
234
+ if (bindableTypeName && typeName === bindableTypeName) {
235
+ if (typeAnnotation.type === "TSTypeLiteral") extractPropNames(typeAnnotation, bindableProps);
236
+ else if (typeAnnotation.type === "TSTypeReference") {
237
+ const resolvedType = resolveTypeAliasCached(typeAnnotation, typeAliasCache);
238
+ if (resolvedType?.type === "TSTypeLiteral") extractPropNames(resolvedType, bindableProps);
239
+ }
240
+ }
241
+ }
242
+ } });
243
+ return {
244
+ propsType,
245
+ bindableProps,
246
+ bindableTypeName,
247
+ aliasBindableTypes
248
+ };
249
+ }
250
+ function resolveTypeAliasCached(type, typeAliasCache) {
251
+ if (type.type !== "TSTypeReference") return null;
252
+ const typeName = getTypeReferenceName(type);
253
+ if (!typeName) return null;
254
+ const aliasNode = typeAliasCache.get(typeName);
255
+ if (!aliasNode) return null;
256
+ const typeAnnotation = "typeAnnotation" in aliasNode ? aliasNode.typeAnnotation : null;
257
+ if (typeAnnotation && "type" in typeAnnotation && typeAnnotation.type !== "TSTypeAnnotation") return typeAnnotation;
258
+ return null;
259
+ }
260
+ function collectTypeLiterals(type, typeAliasCache, collected) {
261
+ if (!type) return;
262
+ if (type.type === "TSTypeLiteral") {
263
+ collected.add(type);
264
+ return;
265
+ }
266
+ if (type.type === "TSIntersectionType" && "types" in type) {
267
+ for (const intersectionType of type.types) collectTypeLiterals(intersectionType, typeAliasCache, collected);
268
+ return;
269
+ }
270
+ if (type.type === "TSTypeReference") {
271
+ const resolved = resolveTypeAliasCached(type, typeAliasCache);
272
+ if (resolved) collectTypeLiterals(resolved, typeAliasCache, collected);
273
+ }
274
+ }
275
+ function collectBindableInlineTypes(type, collected) {
276
+ if (!type) return;
277
+ if (type.type === "TSIntersectionType" && "types" in type) for (const intersectionType of type.types) {
278
+ if (intersectionType.type === "TSTypeReference") {
279
+ if (getTypeReferenceName(intersectionType) === "BindableProps") {
280
+ const firstParam = getTypeParameters(intersectionType)[0];
281
+ if (firstParam && typeof firstParam === "object" && firstParam !== null && "type" in firstParam && firstParam.type === "TSTypeLiteral") collected.add(firstParam);
282
+ }
283
+ }
284
+ collectBindableInlineTypes(intersectionType, collected);
285
+ }
286
+ }
287
+ function getTypeName(node) {
288
+ if (node.type !== "TSTypeAliasDeclaration") return "";
289
+ if (!("id" in node) || !("name" in node.id)) return "";
290
+ return node.id.name;
291
+ }
292
+ function getTypeReferenceName(type) {
293
+ if (type.type !== "TSTypeReference") return "";
294
+ if (!("typeName" in type)) return "";
295
+ const typeName = type.typeName;
296
+ if (!typeName || !("name" in typeName) || typeof typeName.name !== "string") return "";
297
+ return typeName.name;
298
+ }
299
+ function getTypeParameters(type) {
300
+ if (type.type === "TSTypeReference") {
301
+ if ("typeArguments" in type && type.typeArguments) {
302
+ const typeArgs = type.typeArguments;
303
+ if (Array.isArray(typeArgs.params)) return typeArgs.params;
304
+ }
305
+ return [];
306
+ }
307
+ if (!("typeParameters" in type) || !type.typeParameters || typeof type.typeParameters !== "object" || !("params" in type.typeParameters) || !Array.isArray(type.typeParameters.params)) return [];
308
+ return type.typeParameters.params;
309
+ }
310
+ function extractPropNames(typeLiteral, props) {
311
+ if (!("members" in typeLiteral)) return;
312
+ for (const member of typeLiteral.members) {
313
+ if (member.type !== "TSPropertySignature") continue;
314
+ const key = getPropertyKey(member);
315
+ if (key) props.add(key);
316
+ }
317
+ }
318
+ function extractPropsFromType(typeLiteral, source, bindableProps, propsMap, typeAliasCache, forceBindable = false) {
319
+ if (!("members" in typeLiteral)) return;
320
+ for (const member of typeLiteral.members) {
321
+ if (member.type !== "TSPropertySignature") continue;
322
+ const prop = extractPropFromSignature(member, source, bindableProps, typeAliasCache, forceBindable);
323
+ if (prop) {
324
+ const existing = propsMap.get(prop.name);
325
+ if (existing) {
326
+ if (prop.isBindable) existing.isBindable = true;
327
+ if (prop.comment && !existing.comment) existing.comment = prop.comment;
328
+ if (prop.type !== "unknown" && existing.type === "unknown") {
329
+ existing.type = prop.type;
330
+ existing.unionValues = prop.unionValues;
331
+ existing.isFunction = prop.isFunction;
332
+ existing.initialValue = prop.initialValue;
333
+ }
334
+ } else propsMap.set(prop.name, prop);
335
+ }
336
+ }
337
+ }
338
+ function extractPropFromSignature(signature, source, bindableProps, typeAliasCache, forceBindable = false) {
339
+ const key = getPropertyKey(signature);
340
+ if (!key || key.startsWith("_")) return null;
341
+ const typeAnnotation = "typeAnnotation" in signature ? signature.typeAnnotation : null;
342
+ if (!typeAnnotation) return null;
343
+ const type = typeAnnotation.typeAnnotation;
344
+ const jsdoc = extractJSDoc(signature, source, key);
345
+ const propType = parseType(type, source, typeAliasCache);
346
+ return {
347
+ name: key,
348
+ type: propType.type,
349
+ unionValues: propType.unionValues,
350
+ isBindable: forceBindable || bindableProps.has(key),
351
+ isFunction: propType.isFunction,
352
+ initialValue: propType.initialValue,
353
+ comment: jsdoc.comment,
354
+ scenario: jsdoc.scenario
355
+ };
356
+ }
357
+ function getPropertyKey(signature) {
358
+ const keyNode = "key" in signature ? signature.key : null;
359
+ if (!keyNode || !("name" in keyNode)) return null;
360
+ if (typeof keyNode.name === "string") return keyNode.name;
361
+ return keyNode.name.name;
362
+ }
363
+ function extractJSDoc(node, source, propName) {
364
+ let commentValue = null;
365
+ if ("leadingComments" in node && node.leadingComments && Array.isArray(node.leadingComments)) for (const comment of node.leadingComments) {
366
+ if (typeof comment !== "object" || comment === null || !("type" in comment) || !("value" in comment)) continue;
367
+ const commentTyped = comment;
368
+ if (commentTyped.type !== "CommentBlock") continue;
369
+ const value = commentTyped.value;
370
+ if (typeof value === "string" && value.startsWith("*")) {
371
+ commentValue = value;
372
+ break;
373
+ }
374
+ }
375
+ if (!commentValue && propName) {
376
+ const propPattern = createRegExp(maybe(whitespace), exactly(propName), maybe(whitespace.times.any()), charIn("?:"));
377
+ const propPatternGlobal = new RegExp(propPattern.source, "g");
378
+ let match;
379
+ while ((match = propPatternGlobal.exec(source)) !== null) {
380
+ const propStart = match.index;
381
+ const beforeProp = source.slice(Math.max(0, propStart - 500), propStart);
382
+ const matches = Array.from(beforeProp.matchAll(new RegExp(JSDOC_PATTERN.source, "g")));
383
+ if (matches.length > 0) {
384
+ const lastMatch = matches[matches.length - 1];
385
+ if (beforeProp.slice(lastMatch.index + lastMatch[0].length).trim() === "") {
386
+ commentValue = lastMatch[0].replace(JSDOC_START_PATTERN, "").replace(JSDOC_END_PATTERN, "");
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ }
392
+ if (!commentValue) return {};
393
+ const lines = commentValue.split("\n").map((line) => {
394
+ return line.replace(/^\s*\*?\s?/, "").trim();
395
+ }).filter((line) => line !== "" && line !== "*");
396
+ const scenario = lines.find((line) => line.startsWith("@scenario"))?.replace("@scenario", "").trim();
397
+ return {
398
+ comment: lines.filter((line) => line && !line.startsWith("@")).join(" ").trim() || void 0,
399
+ scenario: scenario || void 0
400
+ };
401
+ }
402
+ function parseType(type, _source, typeAliasCache) {
403
+ if (type.type === "TSUnionType") return parseUnionType(type, typeAliasCache);
404
+ if (type.type === "TSBooleanKeyword") return {
405
+ type: "boolean",
406
+ initialValue: false
407
+ };
408
+ if (type.type === "TSStringKeyword") return {
409
+ type: "string",
410
+ initialValue: ""
411
+ };
412
+ if (type.type === "TSNumberKeyword") return {
413
+ type: "number",
414
+ initialValue: 0
415
+ };
416
+ if (type.type === "TSLiteralType") return parseLiteralType(type);
417
+ if (type.type === "TSFunctionType") return {
418
+ type: "function",
419
+ isFunction: true
420
+ };
421
+ if (type.type === "TSTypeReference") {
422
+ if (getTypeReferenceName(type) === "QRL") return {
423
+ type: "function",
424
+ isFunction: true
425
+ };
426
+ const resolvedType = resolveTypeAliasCached(type, typeAliasCache);
427
+ if (resolvedType) return parseType(resolvedType, _source, typeAliasCache);
428
+ }
429
+ return { type: "unknown" };
430
+ }
431
+ function parseUnionType(type, typeAliasCache) {
432
+ if (type.type !== "TSUnionType" || !("types" in type)) return { type: "unknown" };
433
+ const unionTypes = type.types;
434
+ const stringLiterals = [];
435
+ for (let i = 0; i < unionTypes.length; i++) {
436
+ let unionType = unionTypes[i];
437
+ if (unionType.type === "TSTypeReference") {
438
+ const resolvedType = resolveTypeAliasCached(unionType, typeAliasCache);
439
+ if (resolvedType) {
440
+ if (resolvedType.type === "TSUnionType") {
441
+ const nestedResult = parseUnionType(resolvedType, typeAliasCache);
442
+ if (nestedResult.type === "union" && nestedResult.unionValues) stringLiterals.push(...nestedResult.unionValues);
443
+ continue;
444
+ }
445
+ unionType = resolvedType;
446
+ }
447
+ }
448
+ if (unionType.type === "TSLiteralType" && "literal" in unionType) {
449
+ const literal = unionType.literal;
450
+ if (literal && typeof literal === "object" && "type" in literal && "value" in literal) {
451
+ const literalValue = literal.value;
452
+ if (typeof literalValue === "string") stringLiterals.push(literalValue);
453
+ }
454
+ }
455
+ }
456
+ const hasBoolean = unionTypes.some((t) => {
457
+ if (t.type === "TSBooleanKeyword") return true;
458
+ if (t.type === "TSLiteralType" && "literal" in t) {
459
+ const literal = t.literal;
460
+ if (literal && typeof literal === "object" && "value" in literal) return typeof literal.value === "boolean";
461
+ }
462
+ return false;
463
+ });
464
+ const unionValues = [];
465
+ if (hasBoolean) unionValues.push("false", "true");
466
+ if (stringLiterals.length > 0) unionValues.push(...stringLiterals);
467
+ if (unionValues.length > 0) return {
468
+ type: "union",
469
+ unionValues
470
+ };
471
+ return { type: "unknown" };
472
+ }
473
+ function parseLiteralType(type) {
474
+ if (type.type !== "TSLiteralType" || !("literal" in type)) return { type: "unknown" };
475
+ const literal = type.literal;
476
+ if (!literal || typeof literal !== "object" || !("type" in literal) || !("value" in literal)) return { type: "unknown" };
477
+ const literalValue = literal.value;
478
+ if (typeof literalValue === "string") return {
479
+ type: "union",
480
+ unionValues: [literalValue]
481
+ };
482
+ if (typeof literalValue === "boolean") return {
483
+ type: "boolean",
484
+ initialValue: literalValue
485
+ };
486
+ return { type: "unknown" };
487
+ }
488
+ function extractComponentName(filePath, componentsDir) {
489
+ const relativePath = relative(componentsDir, filePath);
490
+ const componentNamePattern = createRegExp(oneOrMore(charNotIn("/")).groupedAs("componentName"), exactly("/"));
491
+ return relativePath.match(componentNamePattern)?.groups?.componentName ?? null;
492
+ }
493
+ async function findComponentDirs(componentsDir) {
494
+ const components = (await readdir(componentsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
495
+ if (components.length === 0) throw new Error(`No component directories found in ${componentsDir}`);
496
+ return components;
497
+ }
498
+
499
+ //#endregion
500
+ export { componentProps, extractJSDoc, generateAllComponentMetadata, generateComponentMetadata };
@@ -0,0 +1,3 @@
1
+ import { componentProps, extractJSDoc, generateAllComponentMetadata, generateComponentMetadata } from "./component-props.qwik.mjs";
2
+
3
+ export { componentProps, extractJSDoc, generateAllComponentMetadata, generateComponentMetadata };
@@ -0,0 +1,153 @@
1
+ import MagicString from "magic-string";
2
+ import { parseSync } from "oxc-parser";
3
+ import { walk } from "oxc-walker";
4
+
5
+ //#region playground/generate-jsx.ts
6
+ function generateJsx({ metadata, selectedPieceName, propValues, isBindProps, baseSource }) {
7
+ const componentName = metadata.componentName.charAt(0).toUpperCase() + metadata.componentName.slice(1);
8
+ const getPieceTagName = (pieceName) => pieceName === "Root" ? `${componentName}.Root` : `${componentName}.${pieceName}`;
9
+ if (!baseSource) {
10
+ const piece = metadata.pieces.find((p) => p.name === selectedPieceName) || metadata.pieces[0];
11
+ if (!piece) return "No piece found";
12
+ const pieceTagName = getPieceTagName(piece.name);
13
+ const values = propValues[piece.name] || {};
14
+ const props = [];
15
+ for (const prop of piece.props) {
16
+ const value = values[prop.name];
17
+ if (value === prop.initialValue) continue;
18
+ if ((prop.type === "boolean" || prop.type === "union") && (value === false || value === "false") && prop.initialValue === void 0) continue;
19
+ if (prop.type === "string" && value === "" && prop.initialValue === void 0) continue;
20
+ if (prop.type === "number" && value === 0 && prop.initialValue === void 0) continue;
21
+ if (prop.isBindable && isBindProps) props.push(`bind:${prop.name}={signal}`);
22
+ else if (value === "true" || value === true) props.push(prop.name);
23
+ else if (value === "false" || value === false) props.push(`${prop.name}={false}`);
24
+ else if (typeof value === "string") props.push(`${prop.name}="${value}"`);
25
+ else if (typeof value === "number") props.push(`${prop.name}={${value}}`);
26
+ else if (value !== void 0) props.push(`${prop.name}={${JSON.stringify(value)}}`);
27
+ }
28
+ const propsString = props.length > 0 ? ` ${props.join(" ")}` : "";
29
+ if (piece.name === "Root") return `<${pieceTagName}${propsString}>Click me</${pieceTagName}>`;
30
+ return `<${pieceTagName}${propsString} />`;
31
+ }
32
+ const s = new MagicString(baseSource);
33
+ let ast;
34
+ try {
35
+ ast = parseSync("source.tsx", baseSource);
36
+ } catch (e) {
37
+ console.warn("generateJsx: Failed to parse", e);
38
+ return baseSource;
39
+ }
40
+ if (ast.errors.length > 0) return baseSource;
41
+ let componentBodyStart = -1;
42
+ let importsEnd = 0;
43
+ let qwikImportEnd = -1;
44
+ let existingUseSignal = false;
45
+ walk(ast.program, { enter(node) {
46
+ if (node.type === "ImportDeclaration") {
47
+ if (node.end > importsEnd) importsEnd = node.end;
48
+ const source = node.source.value;
49
+ if (source.includes("props-playground") || source.includes("ContextId")) s.remove(node.start, node.end);
50
+ if (source === "@qwik.dev/core") {
51
+ qwikImportEnd = node.end;
52
+ if (node.specifiers) {
53
+ node.specifiers.forEach((spec) => {
54
+ if (spec.local.name === "useSignal") existingUseSignal = true;
55
+ });
56
+ if (node.specifiers.length > 0) qwikImportEnd = node.specifiers[node.specifiers.length - 1].end;
57
+ }
58
+ }
59
+ }
60
+ if (node.type === "JSXElement") {
61
+ const opening = node.openingElement;
62
+ let tagName = "";
63
+ if (opening.name.type === "JSXIdentifier") tagName = opening.name.name;
64
+ if (tagName === "PlaygroundState") s.remove(node.start, node.end);
65
+ }
66
+ if (node.type === "ExportDefaultDeclaration") {
67
+ if (node.declaration.type === "CallExpression" && node.declaration.callee.type === "Identifier" && node.declaration.callee.name === "component$") {
68
+ const arg = node.declaration.arguments[0];
69
+ if (arg && arg.type === "ArrowFunctionExpression" && (arg.body.type === "FunctionBody" || arg.body.type === "BlockStatement")) componentBodyStart = arg.body.start + 1;
70
+ }
71
+ }
72
+ if (node.type === "ExportNamedDeclaration" && node.declaration && node.declaration.type === "VariableDeclaration") {
73
+ const decl = node.declaration;
74
+ if (decl.declarations[0].id.type === "Identifier" && decl.declarations[0].id.name === "PlaygroundState") s.remove(node.start, node.end);
75
+ }
76
+ } });
77
+ for (const piece of metadata.pieces) {
78
+ const pieceTagName = getPieceTagName(piece.name);
79
+ const values = propValues[piece.name] || {};
80
+ const openTags = [];
81
+ walk(ast.program, { enter(node) {
82
+ if (node.type === "JSXOpeningElement") {
83
+ let name = "";
84
+ if (node.name.type === "JSXIdentifier") name = node.name.name;
85
+ else if (node.name.type === "JSXMemberExpression") name = `${node.name.object.name}.${node.name.property.name}`;
86
+ if (name === pieceTagName) openTags.push(node);
87
+ }
88
+ } });
89
+ for (const openTag of openTags) {
90
+ const definedProps = /* @__PURE__ */ new Set();
91
+ openTag.attributes.forEach((attr) => {
92
+ if (attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier") definedProps.add(attr.name.name);
93
+ });
94
+ const propsToInject = [];
95
+ for (const prop of piece.props) {
96
+ const value = values[prop.name];
97
+ if (prop.type === "function") {
98
+ const isEnabled = value === true || value === "true";
99
+ const signalName = prop.name.replace(/\$$/, "").replace(/^on/, "").toLowerCase() + "Count";
100
+ if (isEnabled) {
101
+ if (!definedProps.has(prop.name)) {
102
+ if (!existingUseSignal) {
103
+ if (qwikImportEnd !== -1) s.appendLeft(qwikImportEnd, ", useSignal");
104
+ else s.prepend("import { useSignal } from \"@qwik.dev/core\";\n");
105
+ existingUseSignal = true;
106
+ }
107
+ if (componentBodyStart !== -1) {
108
+ if (!baseSource.includes(`const ${signalName} = useSignal`)) s.appendRight(componentBodyStart, `\n const ${signalName} = useSignal(0);`);
109
+ }
110
+ propsToInject.push(`${prop.name}={${"() => " + signalName + ".value++"}}`);
111
+ if (openTag.selfClosing) {
112
+ s.overwrite(openTag.end - 2, openTag.end, ">");
113
+ s.appendLeft(openTag.end, `\n <div><p class="text-sm">Change count: {${signalName}.value}</p></div>\n </${pieceTagName}>`);
114
+ } else {
115
+ let closingStart = -1;
116
+ walk(ast.program, { enter(n) {
117
+ if (n.type === "JSXElement" && n.openingElement === openTag && n.closingElement) closingStart = n.closingElement.start;
118
+ } });
119
+ if (closingStart !== -1) s.appendLeft(closingStart, `\n <div><p class="text-sm">Change count: {${signalName}.value}</p></div>\n `);
120
+ }
121
+ }
122
+ } else if (definedProps.has(prop.name)) {
123
+ const attr = openTag.attributes.find((a) => a.name?.name === prop.name);
124
+ if (attr) s.remove(attr.start, attr.end);
125
+ }
126
+ continue;
127
+ }
128
+ if (value === prop.initialValue) continue;
129
+ if ((prop.type === "boolean" || prop.type === "union") && (value === false || value === "false") && prop.initialValue === void 0) continue;
130
+ if (prop.type === "string" && value === "" && prop.initialValue === void 0) continue;
131
+ if (prop.type === "number" && value === 0 && prop.initialValue === void 0) continue;
132
+ if (definedProps.has(prop.name)) {
133
+ const attr = openTag.attributes.find((a) => a.name?.name === prop.name);
134
+ if (attr) s.remove(attr.start, attr.end);
135
+ }
136
+ if (prop.isBindable && isBindProps) propsToInject.push(`bind:${prop.name}={signal}`);
137
+ else if (value === "true" || value === true) propsToInject.push(prop.name);
138
+ else if (value === "false" || value === false) propsToInject.push(`${prop.name}={false}`);
139
+ else if (typeof value === "string") propsToInject.push(`${prop.name}="${value}"`);
140
+ else if (typeof value === "number") propsToInject.push(`${prop.name}={${value}}`);
141
+ else if (value !== void 0) propsToInject.push(`${prop.name}={${JSON.stringify(value)}}`);
142
+ }
143
+ if (propsToInject.length > 0) {
144
+ const insertPos = openTag.end - (openTag.selfClosing ? 2 : 1);
145
+ s.appendLeft(insertPos, " " + propsToInject.join(" "));
146
+ }
147
+ }
148
+ }
149
+ return s.toString().replace(/^\s*[\r\n]/gm, "");
150
+ }
151
+
152
+ //#endregion
153
+ export { generateJsx };
@@ -0,0 +1,10 @@
1
+ import { propExtraction } from "./prop-extraction.qwik.mjs";
2
+ import { scenarioInjection } from "./scenario-injection.qwik.mjs";
3
+
4
+ //#region playground/index.ts
5
+ const playground = (options) => {
6
+ return [propExtraction(options), scenarioInjection()];
7
+ };
8
+
9
+ //#endregion
10
+ export { playground };