@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.
- package/lib/docs/component-props.qwik.mjs +500 -0
- package/lib/docs/index.qwik.mjs +3 -0
- package/lib/playground/generate-jsx.qwik.mjs +153 -0
- package/lib/playground/index.qwik.mjs +10 -0
- package/lib/playground/prop-extraction.qwik.mjs +500 -0
- package/lib/playground/scenario-injection.qwik.mjs +68 -0
- package/lib/repl/bundler/bundled.qwik.mjs +38 -0
- package/lib/repl/bundler/index.qwik.mjs +89 -0
- package/lib/repl/index.qwik.mjs +21 -0
- package/lib/repl/repl-constants.qwik.mjs +5 -0
- package/lib/rolldown/index.qwik.mjs +2 -1
- package/lib/rolldown/inline-asset.qwik.mjs +171 -0
- package/lib/rolldown/playground.qwik.mjs +67 -0
- package/lib/vite/index.qwik.mjs +2 -1
- package/lib/vite/minify-content.qwik.mjs +6 -4
- package/lib-types/tools/docs/component-props.d.ts +32 -0
- package/lib-types/tools/docs/component-props.unit.d.ts +1 -0
- package/lib-types/tools/docs/generate-metadata.d.ts +1 -0
- package/lib-types/tools/docs/generate-metadata.unit.d.ts +1 -0
- package/lib-types/tools/docs/index.d.ts +2 -0
- package/lib-types/tools/playground/generate-jsx.d.ts +9 -0
- package/lib-types/tools/playground/generate-metadata.d.ts +1 -0
- package/lib-types/tools/playground/generate-metadata.unit.d.ts +1 -0
- package/lib-types/tools/playground/index.d.ts +5 -0
- package/lib-types/tools/playground/prop-extraction.d.ts +32 -0
- package/lib-types/tools/playground/prop-extraction.unit.d.ts +1 -0
- package/lib-types/tools/playground/scenario-injection.d.ts +2 -0
- package/lib-types/tools/playground/scenario-injection.unit.d.ts +1 -0
- package/lib-types/tools/rolldown/index.d.ts +2 -0
- package/lib-types/tools/rolldown/inline-asset.d.ts +18 -0
- package/lib-types/tools/rolldown/inline-asset.unit.d.ts +1 -0
- package/lib-types/tools/rolldown/playground.d.ts +8 -0
- package/lib-types/tools/rolldown/playground.unit.d.ts +1 -0
- package/lib-types/tools/src/vite.d.ts +1 -0
- package/lib-types/tools/utils/fs-mock.d.ts +17 -0
- package/lib-types/tools/vite/index.d.ts +3 -2
- package/package.json +6 -2
- 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 playground/prop-extraction.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 propExtraction = (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(createRegExp(whitespace.times.any().at.lineStart(), maybe("*"), maybe(whitespace)), "").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 { propExtraction };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createRegExp, exactly, not } from "magic-regexp";
|
|
2
|
+
import MagicString from "magic-string";
|
|
3
|
+
import { walk } from "oxc-walker";
|
|
4
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
5
|
+
import { basename, dirname, extname, join } from "node:path";
|
|
6
|
+
import { remark } from "remark";
|
|
7
|
+
import remarkMdx from "remark-mdx";
|
|
8
|
+
|
|
9
|
+
//#region playground/scenario-injection.ts
|
|
10
|
+
const scenarioInjection = () => {
|
|
11
|
+
const isMDX = createRegExp(exactly(".").and("mdx").at.lineEnd());
|
|
12
|
+
const sanitizePattern = createRegExp(not.wordChar, ["g"]);
|
|
13
|
+
return {
|
|
14
|
+
name: "vite-plugin-qds-playground-scenarios",
|
|
15
|
+
enforce: "pre",
|
|
16
|
+
transform(code, id) {
|
|
17
|
+
if (!isMDX.test(id)) return;
|
|
18
|
+
try {
|
|
19
|
+
const mdast = remark().use(remarkMdx).parse(code);
|
|
20
|
+
const s = new MagicString(code);
|
|
21
|
+
let hasPlayground = false;
|
|
22
|
+
const scenariosDir = join(dirname(id), "scenarios");
|
|
23
|
+
if (!existsSync(scenariosDir) || !statSync(scenariosDir).isDirectory()) return null;
|
|
24
|
+
const scenarioFiles = readdirSync(scenariosDir).filter((file) => file.endsWith(".tsx") || file.endsWith(".ts")).map((file) => {
|
|
25
|
+
const name = basename(file, extname(file));
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
file,
|
|
29
|
+
componentVar: `Scenario_${name.replace(sanitizePattern, "_")}`,
|
|
30
|
+
sourceVar: `Source_${name.replace(sanitizePattern, "_")}`
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
if (scenarioFiles.length === 0) return null;
|
|
34
|
+
walk(mdast, { enter(node) {
|
|
35
|
+
const mdxNode = node;
|
|
36
|
+
if ((mdxNode.type === "mdxJsxFlowElement" || mdxNode.type === "mdxJsxTextElement") && mdxNode.name === "Playground") {
|
|
37
|
+
if (mdxNode.position?.start?.offset !== void 0 && mdxNode.position?.end?.offset !== void 0) {
|
|
38
|
+
if (!mdxNode.attributes.some((attr) => attr.name === "scenarios")) {
|
|
39
|
+
const scenariosArrayString = `[${scenarioFiles.map((s$1) => `{name: "${s$1.name}", component: ${s$1.componentVar}, source: ${s$1.sourceVar}}`).join(", ")}]`;
|
|
40
|
+
const insertPos$1 = mdxNode.position.start.offset + 11;
|
|
41
|
+
s.appendLeft(insertPos$1, ` scenarios={${scenariosArrayString}}`);
|
|
42
|
+
hasPlayground = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} });
|
|
47
|
+
if (!hasPlayground) return null;
|
|
48
|
+
const imports = scenarioFiles.map((file) => `import ${file.componentVar} from "./scenarios/${file.file}";\nimport ${file.sourceVar} from "./scenarios/${file.file}?raw";`).join("\n");
|
|
49
|
+
let insertPos = 0;
|
|
50
|
+
if (code.startsWith("---")) {
|
|
51
|
+
const secondDelimiter = code.indexOf("---", 3);
|
|
52
|
+
if (secondDelimiter !== -1) insertPos = secondDelimiter + 3;
|
|
53
|
+
}
|
|
54
|
+
s.appendLeft(insertPos, `\n${imports}\n`);
|
|
55
|
+
return {
|
|
56
|
+
code: s.toString(),
|
|
57
|
+
map: s.generateMap({ hires: true })
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(`Error transforming Playground in ${id}:`, error);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
export { scenarioInjection };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { QWIK_PKG_NAME } from "../repl-constants.qwik.mjs";
|
|
2
|
+
import { version } from "@qwik.dev/core";
|
|
3
|
+
import qWasmMjs from "@qwik.dev/core/bindings/qwik.wasm.mjs?raw-source";
|
|
4
|
+
import qWasmBinUrl from "@qwik.dev/core/bindings/qwik_wasm_bg.wasm?raw-source";
|
|
5
|
+
import qBuild from "@qwik.dev/core/dist/build/index.d.ts?raw-source";
|
|
6
|
+
import qCoreMinMjs from "@qwik.dev/core/dist/core.min.mjs?raw-source";
|
|
7
|
+
import qCoreMjs from "@qwik.dev/core/dist/core.mjs?raw-source";
|
|
8
|
+
import qCoreDts from "@qwik.dev/core/dist/core-internal.d.ts?raw-source";
|
|
9
|
+
import qOptimizerMjs from "@qwik.dev/core/dist/optimizer.mjs?raw-source";
|
|
10
|
+
import qPreloaderMjs from "@qwik.dev/core/dist/preloader.mjs?raw-source";
|
|
11
|
+
import qQwikLoaderJs from "@qwik.dev/core/dist/qwikloader.debug.js?raw-source";
|
|
12
|
+
import qServerDts from "@qwik.dev/core/dist/server.d.ts?raw-source";
|
|
13
|
+
import qServerMjs from "@qwik.dev/core/dist/server.mjs?raw-source";
|
|
14
|
+
import qHandlersMjs from "@qwik.dev/core/handlers.mjs?raw-source";
|
|
15
|
+
|
|
16
|
+
//#region repl/bundler/bundled.ts
|
|
17
|
+
const qwikUrls = {
|
|
18
|
+
version,
|
|
19
|
+
"/dist/build/index.d.ts": qBuild,
|
|
20
|
+
"/dist/core.d.ts": qCoreDts,
|
|
21
|
+
"/dist/core.min.mjs": qCoreMinMjs,
|
|
22
|
+
"/dist/core.mjs": qCoreMjs,
|
|
23
|
+
"/dist/optimizer.mjs": qOptimizerMjs,
|
|
24
|
+
"/dist/server.mjs": qServerMjs,
|
|
25
|
+
"/dist/server.d.ts": qServerDts,
|
|
26
|
+
"/dist/preloader.mjs": qPreloaderMjs,
|
|
27
|
+
"/dist/qwikloader.js": qQwikLoaderJs,
|
|
28
|
+
"/bindings/qwik.wasm.mjs": qWasmMjs,
|
|
29
|
+
"/bindings/qwik_wasm_bg.wasm": qWasmBinUrl,
|
|
30
|
+
"/handlers.mjs": qHandlersMjs
|
|
31
|
+
};
|
|
32
|
+
const bundled = { [QWIK_PKG_NAME]: qwikUrls };
|
|
33
|
+
const getDeps = () => {
|
|
34
|
+
return bundled;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { getDeps };
|