@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
@@ -3,5 +3,208 @@
3
3
  *
4
4
  * Custom linting rules specific to Qwik patterns and conventions
5
5
  */
6
- declare const qdsPlugin: import("oxlint").Plugin;
6
+ interface Ranged {
7
+ range: [number, number];
8
+ }
9
+ interface Node extends Ranged {
10
+ type: string;
11
+ start: number;
12
+ end: number;
13
+ }
14
+ interface Context {
15
+ filename: string;
16
+ report(descriptor: {
17
+ node: Ranged;
18
+ messageId?: string;
19
+ message?: string;
20
+ data?: Record<string, string>;
21
+ }): void;
22
+ }
23
+ export declare const requireContextProxyRule: {
24
+ readonly meta: {
25
+ readonly type: "problem";
26
+ readonly docs: {
27
+ readonly description: "Enforce createContextProxy pattern for getter exports";
28
+ readonly recommended: true;
29
+ };
30
+ readonly messages: {
31
+ readonly nonStringArg: "Getter export '{{name}}' calls context() with a non-string argument. Use a string literal: context('fieldName').";
32
+ readonly notContextProxy: "Getter export '{{name}}' is not created via createContextProxy. Use: const context = createContextProxy<T>(); export const {{name}} = context('fieldName');";
33
+ readonly missingArg: "Getter export '{{name}}' calls context() without arguments. Provide the context field name: context('fieldName').";
34
+ readonly missingGetPrefix: "Export '{{name}}' uses createContextProxy but is missing the 'get' prefix. Rename to 'get{{suggested}}' so the UI plugin can detect it.";
35
+ readonly getterNameMismatch: "Getter '{{name}}' does not match context field '{{field}}'. Expected export name: 'get{{expected}}' (derived: '{{derived}}', actual field: '{{field}}'). The UI plugin uses deriveContextField which strips 'get' and lowercases the next char.";
36
+ };
37
+ readonly schema: readonly [];
38
+ };
39
+ readonly create: (context: Context) => {
40
+ ImportDeclaration(node: Node): void;
41
+ VariableDeclarator(node: Node): void;
42
+ ExportNamedDeclaration(node: Node): void;
43
+ };
44
+ };
45
+ declare const qdsPlugin: {
46
+ readonly meta: {
47
+ readonly name: "qds-internal";
48
+ };
49
+ readonly rules: {
50
+ readonly "no-default-name": {
51
+ readonly meta: {
52
+ readonly type: "problem";
53
+ readonly docs: {
54
+ readonly description: "Disallow 'defaultX' naming pattern in component props - use Qwik's signal/value-based patterns instead";
55
+ readonly category: "Best Practices";
56
+ readonly recommended: true;
57
+ readonly url: "https://qds.dev/contributing/state/";
58
+ };
59
+ readonly messages: {
60
+ readonly defaultXPattern: "'{{name}}' uses React's defaultX pattern. Qwik uses signal-based (two-way binding) or value-based (one-way binding) patterns instead. See: https://qds.dev/contributing/state/";
61
+ };
62
+ readonly schema: readonly [];
63
+ };
64
+ readonly createOnce: (context: Context) => {
65
+ TSPropertySignature(node: Node): void;
66
+ };
67
+ };
68
+ readonly "event-handlers-array-pattern": {
69
+ readonly meta: {
70
+ readonly type: "problem";
71
+ readonly docs: {
72
+ readonly description: "Event handlers should use array pattern to combine local handlers with props";
73
+ readonly category: "Best Practices";
74
+ readonly recommended: true;
75
+ readonly url: "https://qds.dev/contributing/";
76
+ };
77
+ readonly messages: {
78
+ readonly requireArrayPattern: "Event handler '{{name}}' should use array pattern: [localHandler$, props.{{name}}].";
79
+ };
80
+ readonly schema: readonly [];
81
+ };
82
+ readonly createOnce: (context: Context) => {
83
+ JSXAttribute(node: Node): void;
84
+ };
85
+ };
86
+ readonly "one-element-composition": {
87
+ readonly meta: {
88
+ readonly type: "problem";
89
+ readonly docs: {
90
+ readonly description: "Components should follow 'One Component, One Markup Element' principle";
91
+ readonly category: "Best Practices";
92
+ readonly recommended: true;
93
+ readonly url: "https://qds.dev/contributing/composition/";
94
+ };
95
+ readonly messages: {
96
+ readonly multipleElements: "Component returns multiple HTML element types: {{elements}}. Each component should correspond to ONE type of markup element. Add '// no-composition-check' to exempt.";
97
+ };
98
+ readonly schema: readonly [];
99
+ };
100
+ readonly createOnce: (context: Context) => {
101
+ Program(): void;
102
+ VariableDeclarator(node: Node): void;
103
+ ReturnStatement(node: Node): void;
104
+ "VariableDeclarator:exit"(node: Node): void;
105
+ };
106
+ };
107
+ readonly "require-use-bindings": {
108
+ readonly meta: {
109
+ readonly type: "problem";
110
+ readonly docs: {
111
+ readonly description: "Root components (*-root.tsx) should use useBindings or include '// no-bindings' comment";
112
+ readonly category: "Best Practices";
113
+ readonly recommended: true;
114
+ readonly url: "https://qds.dev/contributing/state/#useBindings";
115
+ };
116
+ readonly messages: {
117
+ readonly missingBindings: "Root component is missing useBindings. Add useBindings or include '// no-bindings' comment if not needed.";
118
+ };
119
+ readonly schema: readonly [];
120
+ };
121
+ readonly createOnce: (context: Context) => {
122
+ Program(): void;
123
+ CallExpression(node: Node): void;
124
+ "Program:exit"(): void;
125
+ };
126
+ };
127
+ readonly "require-destructure-bindings": {
128
+ readonly meta: {
129
+ readonly type: "problem";
130
+ readonly docs: {
131
+ readonly description: "If useBindings is used, destructureBindings must also be used in the same file to ensure proper prop handling";
132
+ readonly category: "Best Practices";
133
+ readonly recommended: true;
134
+ readonly url: "https://qds.dev/contributing/state/#destructureBindings";
135
+ };
136
+ readonly messages: {
137
+ readonly missingDestructureBindings: "File uses useBindings but is missing destructureBindings. Both are required for proper QDS component state management. Example: const { ... } = destructureBindings(props, initialValues);";
138
+ };
139
+ readonly schema: readonly [];
140
+ };
141
+ readonly createOnce: (context: Context) => {
142
+ CallExpression(node: Node): void;
143
+ "Program:exit"(): void;
144
+ };
145
+ };
146
+ readonly "require-research-file": {
147
+ readonly meta: {
148
+ readonly type: "problem";
149
+ readonly docs: {
150
+ readonly description: "Root components (*-root.tsx) require a research file (research.md or research.mdx)";
151
+ readonly category: "Best Practices";
152
+ readonly recommended: true;
153
+ readonly url: "https://qwik.design/contributing/research/";
154
+ };
155
+ readonly messages: {
156
+ readonly missingResearch: "Root component requires a research file. Create research.md or research.mdx in this directory documenting component research, accessibility, design decisions, and usage guidelines.";
157
+ };
158
+ readonly schema: readonly [];
159
+ };
160
+ readonly createOnce: (context: Context) => {
161
+ Program(): void;
162
+ CallExpression(node: Node): void;
163
+ "Program:exit"(): void;
164
+ };
165
+ };
166
+ readonly "require-test-file": {
167
+ readonly meta: {
168
+ readonly type: "problem";
169
+ readonly docs: {
170
+ readonly description: "Root components (*-root.tsx) require a test file (*.browser.tsx)";
171
+ readonly category: "Best Practices";
172
+ readonly recommended: true;
173
+ readonly url: "https://qwik.design/contributing/testing/";
174
+ };
175
+ readonly messages: {
176
+ readonly missingTest: "Root component requires a test file. Create a *.browser.tsx file in this directory with component tests.";
177
+ };
178
+ readonly schema: readonly [];
179
+ };
180
+ readonly createOnce: (context: Context) => {
181
+ Program(): void;
182
+ CallExpression(node: Node): void;
183
+ "Program:exit"(): void;
184
+ };
185
+ };
186
+ readonly "require-context-proxy": {
187
+ readonly meta: {
188
+ readonly type: "problem";
189
+ readonly docs: {
190
+ readonly description: "Enforce createContextProxy pattern for getter exports";
191
+ readonly recommended: true;
192
+ };
193
+ readonly messages: {
194
+ readonly nonStringArg: "Getter export '{{name}}' calls context() with a non-string argument. Use a string literal: context('fieldName').";
195
+ readonly notContextProxy: "Getter export '{{name}}' is not created via createContextProxy. Use: const context = createContextProxy<T>(); export const {{name}} = context('fieldName');";
196
+ readonly missingArg: "Getter export '{{name}}' calls context() without arguments. Provide the context field name: context('fieldName').";
197
+ readonly missingGetPrefix: "Export '{{name}}' uses createContextProxy but is missing the 'get' prefix. Rename to 'get{{suggested}}' so the UI plugin can detect it.";
198
+ readonly getterNameMismatch: "Getter '{{name}}' does not match context field '{{field}}'. Expected export name: 'get{{expected}}' (derived: '{{derived}}', actual field: '{{field}}'). The UI plugin uses deriveContextField which strips 'get' and lowercases the next char.";
199
+ };
200
+ readonly schema: readonly [];
201
+ };
202
+ readonly create: (context: Context) => {
203
+ ImportDeclaration(node: Node): void;
204
+ VariableDeclarator(node: Node): void;
205
+ ExportNamedDeclaration(node: Node): void;
206
+ };
207
+ };
208
+ };
209
+ };
7
210
  export default qdsPlugin;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Oxlint plugin for Qwik Design System (public)
3
+ *
4
+ * Consumer-facing lint plugin that QDS users install in their own projects.
5
+ * Contains only the no-outside-getter-usage rule.
6
+ */
7
+ interface Ranged {
8
+ range: [number, number];
9
+ }
10
+ interface Node extends Ranged {
11
+ type: string;
12
+ start: number;
13
+ end: number;
14
+ }
15
+ interface Context {
16
+ filename: string;
17
+ report(descriptor: {
18
+ node: Ranged;
19
+ messageId?: string;
20
+ message?: string;
21
+ data?: Record<string, string>;
22
+ }): void;
23
+ }
24
+ declare const qdsPlugin: {
25
+ readonly meta: {
26
+ readonly name: "qds";
27
+ };
28
+ readonly rules: {
29
+ readonly "no-outside-getter-usage": {
30
+ readonly meta: {
31
+ readonly type: "problem";
32
+ readonly docs: {
33
+ readonly description: "Getters from QDS namespaces must be used inside a component$ that renders the matching root element";
34
+ readonly category: "Best Practices";
35
+ readonly recommended: true;
36
+ readonly url: "https://qds.dev/contributing/state/";
37
+ };
38
+ readonly messages: {
39
+ readonly moduleScope: "'{{getter}}' used at module scope. Getters must be used inside a component$ that renders <{{namespace}}.root> or any of its descendants.";
40
+ readonly outsideComponent: "'{{getter}}' used in a function outside any component$. Move this into a component$ rendered inside <{{namespace}}.root>.";
41
+ readonly noMatchingRoot: "'{{getter}}' used but no <{{namespace}}.root> found in JSX return. Add <{{namespace}}.root> or move this into a component that renders inside one.";
42
+ };
43
+ readonly schema: readonly [];
44
+ };
45
+ readonly create: (context: Context) => {
46
+ ImportDeclaration(node: Node): void;
47
+ CallExpression(node: Node): void;
48
+ "CallExpression:exit"(node: Node): void;
49
+ "Program:exit"(): void;
50
+ FunctionDeclaration(node: Node): void;
51
+ "FunctionDeclaration:exit"(node: Node): void;
52
+ JSXMemberExpression(node: Node): void;
53
+ MemberExpression(node: Node): void;
54
+ VariableDeclarator(node: Node): void;
55
+ };
56
+ };
57
+ };
58
+ };
59
+ export default qdsPlugin;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,26 @@
1
- import type { Ranged, Rule } from "oxlint";
1
+ interface Ranged {
2
+ range: [number, number];
3
+ }
4
+ interface Context {
5
+ filename: string;
6
+ report(descriptor: {
7
+ node: Ranged;
8
+ messageId?: string;
9
+ message?: string;
10
+ data?: Record<string, string>;
11
+ }): void;
12
+ }
13
+ type VisitorWithHooks = Record<string, unknown> & {
14
+ before?: () => boolean | void;
15
+ after?: () => void;
16
+ };
17
+ type Rule = {
18
+ meta: Record<string, unknown>;
19
+ createOnce: (context: Context) => VisitorWithHooks;
20
+ } | {
21
+ meta: Record<string, unknown>;
22
+ create: (context: Context) => VisitorWithHooks;
23
+ };
2
24
  export interface ValidTestCase {
3
25
  /** The code that should not trigger any errors */
4
26
  code: string;
@@ -1,4 +1,4 @@
1
- import type { Node } from "@oxc-project/types";
1
+ import type { Node, TSPropertySignature } from "@oxc-project/types";
2
2
  import type { Plugin as VitePlugin } from "vite";
3
3
  export type PropType = {
4
4
  name: string;
@@ -26,7 +26,12 @@ export type ComponentPropsPluginOptions = {
26
26
  export declare const propExtraction: (options: ComponentPropsPluginOptions) => VitePlugin;
27
27
  export declare function generateAllComponentMetadata(componentsDir: string, outputDir: string, debug: (message: string, ...data: unknown[]) => void): Promise<void>;
28
28
  export declare function generateComponentMetadata(componentName: string, componentsDir: string, outputDir: string, debug: (message: string, ...data: unknown[]) => void): Promise<void>;
29
+ export declare function isPieceFile(file: string, debug: (message: string, ...data: unknown[]) => void): boolean;
30
+ export declare function extractPieceName(file: string, componentName: string): string;
31
+ export declare function getInterfaceName(node: Node): string;
32
+ export declare function getTypeMembers(node: unknown): TSPropertySignature[];
29
33
  export declare function extractJSDoc(node: Node, source: string, propName?: string): {
30
34
  comment?: string;
31
35
  scenario?: string;
32
36
  };
37
+ export declare function extractComponentName(filePath: string, componentsDir: string): string | null;
@@ -1,8 +1,8 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { dirname, join, relative } from "node:path";
1
3
  import { charIn, charNotIn, createRegExp, exactly, maybe, oneOrMore, whitespace } from "magic-regexp";
2
4
  import { parseSync } from "oxc-parser";
3
5
  import { walk } from "oxc-walker";
4
- import { existsSync, statSync } from "node:fs";
5
- import { dirname, join, relative } from "node:path";
6
6
  import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
7
7
 
8
8
  //#region playground/prop-extraction.ts
@@ -163,6 +163,10 @@ function buildTypeAliasCache(program) {
163
163
  const typeName = getTypeName(node);
164
164
  if (typeName) cache.set(typeName, node);
165
165
  }
166
+ if (node.type === "TSInterfaceDeclaration") {
167
+ const typeName = getInterfaceName(node);
168
+ if (typeName) cache.set(typeName, node);
169
+ }
166
170
  } });
167
171
  return cache;
168
172
  }
@@ -239,7 +243,43 @@ function analyzeProgram(program, debug, typeAliasCache) {
239
243
  }
240
244
  }
241
245
  }
246
+ if (node.type === "TSInterfaceDeclaration") {
247
+ const typeName = getInterfaceName(node);
248
+ if (!typeName) return;
249
+ if (!propsType && typeName.endsWith("Props")) {
250
+ debug(`Found *Props interface: ${typeName}`);
251
+ propsType = node;
252
+ }
253
+ if (typeName.endsWith("Props")) {
254
+ const extendsClause = "extends" in node && Array.isArray(node.extends) ? node.extends : [];
255
+ for (const heritage of extendsClause) {
256
+ const expr = "expression" in heritage ? heritage.expression : null;
257
+ if (!expr || !("name" in expr) || expr.name !== "BindableProps") continue;
258
+ const typeParams = "typeArguments" in heritage && heritage.typeArguments ? heritage.typeArguments : "typeParameters" in heritage && heritage.typeParameters ? heritage.typeParameters : null;
259
+ const firstParam = (typeParams && typeof typeParams === "object" && "params" in typeParams ? typeParams.params : [])[0];
260
+ if (firstParam && typeof firstParam === "object" && firstParam !== null && "type" in firstParam) {
261
+ if (firstParam.type === "TSTypeReference") {
262
+ const refTypeName = firstParam.typeName?.name;
263
+ if (typeof refTypeName === "string") bindableTypeName = refTypeName;
264
+ } else if (firstParam.type === "TSTypeLiteral") aliasBindableTypes.push(firstParam);
265
+ }
266
+ }
267
+ }
268
+ }
242
269
  } });
270
+ if (bindableTypeName && bindableProps.size === 0) {
271
+ const bindableAlias = typeAliasCache.get(bindableTypeName);
272
+ if (bindableAlias && bindableAlias.type === "TSTypeAliasDeclaration") {
273
+ const typeAnnotation = "typeAnnotation" in bindableAlias ? bindableAlias.typeAnnotation : null;
274
+ if (typeAnnotation) {
275
+ if (typeAnnotation.type === "TSTypeLiteral") extractPropNames(typeAnnotation, bindableProps);
276
+ else if (typeAnnotation.type === "TSTypeReference") {
277
+ const resolvedType = resolveTypeAliasCached(typeAnnotation, typeAliasCache);
278
+ if (resolvedType?.type === "TSTypeLiteral") extractPropNames(resolvedType, bindableProps);
279
+ }
280
+ }
281
+ }
282
+ }
243
283
  return {
244
284
  propsType,
245
285
  bindableProps,
@@ -253,16 +293,27 @@ function resolveTypeAliasCached(type, typeAliasCache) {
253
293
  if (!typeName) return null;
254
294
  const aliasNode = typeAliasCache.get(typeName);
255
295
  if (!aliasNode) return null;
296
+ if (aliasNode.type === "TSInterfaceDeclaration") {
297
+ const body = "body" in aliasNode ? aliasNode.body : null;
298
+ if (body && typeof body === "object" && "type" in body && body.type === "TSInterfaceBody") return body;
299
+ return null;
300
+ }
256
301
  const typeAnnotation = "typeAnnotation" in aliasNode ? aliasNode.typeAnnotation : null;
257
302
  if (typeAnnotation && "type" in typeAnnotation && typeAnnotation.type !== "TSTypeAnnotation") return typeAnnotation;
258
303
  return null;
259
304
  }
260
305
  function collectTypeLiterals(type, typeAliasCache, collected) {
261
306
  if (!type) return;
262
- if (type.type === "TSTypeLiteral") {
307
+ const nodeType = type.type;
308
+ if (nodeType === "TSTypeLiteral" || nodeType === "TSInterfaceBody") {
263
309
  collected.add(type);
264
310
  return;
265
311
  }
312
+ if (nodeType === "TSInterfaceDeclaration") {
313
+ const body = "body" in type ? type.body : null;
314
+ if (body && body.type === "TSInterfaceBody") collected.add(body);
315
+ return;
316
+ }
266
317
  if (type.type === "TSIntersectionType" && "types" in type) {
267
318
  for (const intersectionType of type.types) collectTypeLiterals(intersectionType, typeAliasCache, collected);
268
319
  return;
@@ -289,6 +340,18 @@ function getTypeName(node) {
289
340
  if (!("id" in node) || !("name" in node.id)) return "";
290
341
  return node.id.name;
291
342
  }
343
+ function getInterfaceName(node) {
344
+ if (node.type !== "TSInterfaceDeclaration") return "";
345
+ if (!("id" in node) || !("name" in node.id)) return "";
346
+ return node.id.name;
347
+ }
348
+ function getTypeMembers(node) {
349
+ if (!node || typeof node !== "object" || !("type" in node)) return [];
350
+ const typed = node;
351
+ const raw = typed.type === "TSInterfaceBody" ? typed.body : typed.members;
352
+ if (!Array.isArray(raw)) return [];
353
+ return raw.filter((m) => typeof m === "object" && m !== null && "type" in m && m.type === "TSPropertySignature");
354
+ }
292
355
  function getTypeReferenceName(type) {
293
356
  if (type.type !== "TSTypeReference") return "";
294
357
  if (!("typeName" in type)) return "";
@@ -308,17 +371,13 @@ function getTypeParameters(type) {
308
371
  return type.typeParameters.params;
309
372
  }
310
373
  function extractPropNames(typeLiteral, props) {
311
- if (!("members" in typeLiteral)) return;
312
- for (const member of typeLiteral.members) {
313
- if (member.type !== "TSPropertySignature") continue;
374
+ for (const member of getTypeMembers(typeLiteral)) {
314
375
  const key = getPropertyKey(member);
315
376
  if (key) props.add(key);
316
377
  }
317
378
  }
318
379
  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;
380
+ for (const member of getTypeMembers(typeLiteral)) {
322
381
  const prop = extractPropFromSignature(member, source, bindableProps, typeAliasCache, forceBindable);
323
382
  if (prop) {
324
383
  const existing = propsMap.get(prop.name);
@@ -1,23 +1,56 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { basename, dirname, extname, join } from "node:path";
1
3
  import { createRegExp, exactly, not } from "magic-regexp";
2
4
  import MagicString from "magic-string";
5
+ import { parseSync } from "oxc-parser";
3
6
  import { walk } from "oxc-walker";
4
- import { existsSync, readdirSync, statSync } from "node:fs";
5
- import { basename, dirname, extname, join } from "node:path";
6
7
  import { remark } from "remark";
7
8
  import remarkMdx from "remark-mdx";
8
9
 
9
10
  //#region playground/scenario-injection.ts
11
+ const SCENARIO_SOURCE_QUERY = "?scenario-source";
12
+ function stripTypeAnnotations(filePath, source) {
13
+ const { program } = parseSync(filePath, source, { sourceType: "module" });
14
+ const s = new MagicString(source);
15
+ walk(program, { enter(node) {
16
+ const n = node;
17
+ if (n.type === "ImportDeclaration" && n.importKind === "type") {
18
+ let end = n.end;
19
+ if (source[end] === "\n") end++;
20
+ s.remove(n.start, end);
21
+ return;
22
+ }
23
+ if (n.type === "ImportSpecifier" && n.importKind === "type") {
24
+ const typePrefix = source.slice(n.start, n.end).indexOf("type ");
25
+ if (typePrefix !== -1) s.remove(n.start + typePrefix, n.start + typePrefix + 5);
26
+ return;
27
+ }
28
+ if (n.typeAnnotation) s.remove(n.typeAnnotation.start, n.typeAnnotation.end);
29
+ } });
30
+ return s.toString();
31
+ }
10
32
  const scenarioInjection = () => {
11
33
  const isMDX = createRegExp(exactly(".").and("mdx").at.lineEnd());
12
34
  const sanitizePattern = createRegExp(not.wordChar, ["g"]);
13
35
  return {
14
36
  name: "vite-plugin-qds-playground-scenarios",
15
37
  enforce: "pre",
38
+ resolveId(source, importer) {
39
+ if (!source.endsWith(SCENARIO_SOURCE_QUERY)) return;
40
+ const relativePath = source.slice(0, -16);
41
+ return `\0scenario-source:${importer ? join(dirname(importer), relativePath) : relativePath}`;
42
+ },
43
+ load(id) {
44
+ if (!id.startsWith("\0scenario-source:")) return;
45
+ const filePath = id.slice(17);
46
+ const stripped = stripTypeAnnotations(filePath, readFileSync(filePath, "utf-8"));
47
+ return `export default ${JSON.stringify(stripped)};`;
48
+ },
16
49
  transform(code, id) {
17
50
  if (!isMDX.test(id)) return;
18
51
  try {
19
52
  const mdast = remark().use(remarkMdx).parse(code);
20
- const s = new MagicString(code);
53
+ const codeStr = new MagicString(code);
21
54
  let hasPlayground = false;
22
55
  const scenariosDir = join(dirname(id), "scenarios");
23
56
  if (!existsSync(scenariosDir) || !statSync(scenariosDir).isDirectory()) return null;
@@ -38,23 +71,23 @@ const scenarioInjection = () => {
38
71
  if (!mdxNode.attributes.some((attr) => attr.name === "scenarios")) {
39
72
  const scenariosArrayString = `[${scenarioFiles.map((s) => `{name: "${s.name}", component: ${s.componentVar}, source: ${s.sourceVar}}`).join(", ")}]`;
40
73
  const insertPos = mdxNode.position.start.offset + 11;
41
- s.appendLeft(insertPos, ` scenarios={${scenariosArrayString}}`);
74
+ codeStr.appendLeft(insertPos, ` scenarios={${scenariosArrayString}}`);
42
75
  hasPlayground = true;
43
76
  }
44
77
  }
45
78
  }
46
79
  } });
47
80
  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");
81
+ const imports = scenarioFiles.map((file) => `import ${file.componentVar} from "./scenarios/${file.file}";\nimport ${file.sourceVar} from "./scenarios/${file.file}?scenario-source";`).join("\n");
49
82
  let insertPos = 0;
50
83
  if (code.startsWith("---")) {
51
84
  const secondDelimiter = code.indexOf("---", 3);
52
85
  if (secondDelimiter !== -1) insertPos = secondDelimiter + 3;
53
86
  }
54
- s.appendLeft(insertPos, `\n${imports}\n`);
87
+ codeStr.appendLeft(insertPos, `\n${imports}\n\n`);
55
88
  return {
56
- code: s.toString(),
57
- map: s.generateMap({ hires: true })
89
+ code: codeStr.toString(),
90
+ map: codeStr.generateMap({ hires: true })
58
91
  };
59
92
  } catch (error) {
60
93
  console.error(`Error transforming Playground in ${id}:`, error);
@@ -1,15 +1,16 @@
1
+ import type { Program } from "@oxc-project/types";
1
2
  import type { Plugin } from "rolldown";
3
+ import MagicString from "magic-string";
2
4
  export type AsChildTypes = {
3
5
  asChild?: true;
4
6
  };
5
7
  export type AsChildPluginOptions = {
6
8
  debug?: boolean;
7
9
  };
8
- /**
9
- * Rolldown plugin that transforms JSX elements with asChild prop by moving child element props to the parent
10
- * @param options - Plugin configuration options
11
- * @returns Rolldown-compatible plugin object
12
- */
10
+ export type AsChildTransformCoreResult = {
11
+ changed: boolean;
12
+ };
13
+ export declare function asChildTransformCore(ast: Program, s: MagicString, code: string, debug: (message: string) => void): AsChildTransformCoreResult;
13
14
  export declare const asChild: (options?: AsChildPluginOptions) => Plugin & {
14
15
  enforce?: "pre" | "post";
15
16
  };