@alloy-js/core 0.5.0 → 0.7.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 (212) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/babel.config.cjs +4 -1
  3. package/dist/src/binder.d.ts +8 -2
  4. package/dist/src/binder.d.ts.map +1 -1
  5. package/dist/src/binder.js +41 -15
  6. package/dist/src/binder.js.map +1 -1
  7. package/dist/src/code.d.ts +2 -2
  8. package/dist/src/code.d.ts.map +1 -1
  9. package/dist/src/code.js +4 -4
  10. package/dist/src/code.js.map +1 -1
  11. package/dist/src/components/Block.d.ts +25 -0
  12. package/dist/src/components/Block.d.ts.map +1 -0
  13. package/dist/src/components/Block.js +25 -0
  14. package/dist/src/components/Block.js.map +1 -0
  15. package/dist/src/components/Declaration.d.ts.map +1 -1
  16. package/dist/src/components/Declaration.js +4 -0
  17. package/dist/src/components/Declaration.js.map +1 -1
  18. package/dist/src/components/For.d.ts +44 -0
  19. package/dist/src/components/For.d.ts.map +1 -0
  20. package/dist/src/components/For.js +41 -0
  21. package/dist/src/components/For.js.map +1 -0
  22. package/dist/src/components/Indent.d.ts +5 -9
  23. package/dist/src/components/Indent.d.ts.map +1 -1
  24. package/dist/src/components/Indent.js +7 -18
  25. package/dist/src/components/Indent.js.map +1 -1
  26. package/dist/src/components/List.d.ts +38 -0
  27. package/dist/src/components/List.d.ts.map +1 -0
  28. package/dist/src/components/List.js +40 -0
  29. package/dist/src/components/List.js.map +1 -0
  30. package/dist/src/components/MemberDeclaration.d.ts.map +1 -1
  31. package/dist/src/components/MemberDeclaration.js.map +1 -1
  32. package/dist/src/components/MemberName.js +1 -1
  33. package/dist/src/components/MemberName.js.map +1 -1
  34. package/dist/src/components/MemberScope.d.ts.map +1 -1
  35. package/dist/src/components/MemberScope.js.map +1 -1
  36. package/dist/src/components/Name.js +1 -1
  37. package/dist/src/components/Name.js.map +1 -1
  38. package/dist/src/components/Output.d.ts +2 -1
  39. package/dist/src/components/Output.d.ts.map +1 -1
  40. package/dist/src/components/Output.js +9 -1
  41. package/dist/src/components/Output.js.map +1 -1
  42. package/dist/src/components/Scope.d.ts.map +1 -1
  43. package/dist/src/components/Scope.js.map +1 -1
  44. package/dist/src/components/Show.d.ts +8 -0
  45. package/dist/src/components/Show.d.ts.map +1 -0
  46. package/dist/src/components/Show.js +4 -0
  47. package/dist/src/components/Show.js.map +1 -0
  48. package/dist/src/components/SourceDirectory.d.ts.map +1 -1
  49. package/dist/src/components/SourceDirectory.js +1 -0
  50. package/dist/src/components/SourceDirectory.js.map +1 -1
  51. package/dist/src/components/SourceFile.d.ts +2 -6
  52. package/dist/src/components/SourceFile.d.ts.map +1 -1
  53. package/dist/src/components/SourceFile.js +6 -13
  54. package/dist/src/components/SourceFile.js.map +1 -1
  55. package/dist/src/components/StatementList.d.ts +9 -0
  56. package/dist/src/components/StatementList.d.ts.map +1 -0
  57. package/dist/src/components/StatementList.js +17 -0
  58. package/dist/src/components/StatementList.js.map +1 -0
  59. package/dist/src/components/Switch.d.ts +41 -0
  60. package/dist/src/components/Switch.d.ts.map +1 -0
  61. package/dist/src/components/Switch.js +41 -0
  62. package/dist/src/components/Switch.js.map +1 -0
  63. package/dist/src/components/Wrap.d.ts +20 -0
  64. package/dist/src/components/Wrap.d.ts.map +1 -0
  65. package/dist/src/components/Wrap.js +15 -0
  66. package/dist/src/components/Wrap.js.map +1 -0
  67. package/dist/src/components/index.d.ts +8 -1
  68. package/dist/src/components/index.d.ts.map +1 -1
  69. package/dist/src/components/index.js +7 -0
  70. package/dist/src/components/index.js.map +1 -1
  71. package/dist/src/components/stc/index.d.ts +77 -6
  72. package/dist/src/components/stc/index.d.ts.map +1 -1
  73. package/dist/src/components/stc/index.js +17 -1
  74. package/dist/src/components/stc/index.js.map +1 -1
  75. package/dist/src/context/index.d.ts +0 -1
  76. package/dist/src/context/index.d.ts.map +1 -1
  77. package/dist/src/context/index.js +0 -1
  78. package/dist/src/context/index.js.map +1 -1
  79. package/dist/src/context.d.ts.map +1 -1
  80. package/dist/src/context.js +3 -3
  81. package/dist/src/context.js.map +1 -1
  82. package/dist/src/index.browser.d.ts +3 -0
  83. package/dist/src/index.browser.d.ts.map +1 -0
  84. package/dist/src/index.browser.js +3 -0
  85. package/dist/src/index.browser.js.map +1 -0
  86. package/dist/src/index.d.ts +1 -0
  87. package/dist/src/index.d.ts.map +1 -1
  88. package/dist/src/index.js +1 -0
  89. package/dist/src/index.js.map +1 -1
  90. package/dist/src/jsx-runtime.d.ts +139 -8
  91. package/dist/src/jsx-runtime.d.ts.map +1 -1
  92. package/dist/src/jsx-runtime.js +102 -12
  93. package/dist/src/jsx-runtime.js.map +1 -1
  94. package/dist/src/render.d.ts +107 -132
  95. package/dist/src/render.d.ts.map +1 -1
  96. package/dist/src/render.js +281 -177
  97. package/dist/src/render.js.map +1 -1
  98. package/dist/src/stc.d.ts +14 -0
  99. package/dist/src/stc.d.ts.map +1 -0
  100. package/dist/src/stc.js +52 -0
  101. package/dist/src/stc.js.map +1 -0
  102. package/dist/src/utils.d.ts +22 -15
  103. package/dist/src/utils.d.ts.map +1 -1
  104. package/dist/src/utils.js +95 -59
  105. package/dist/src/utils.js.map +1 -1
  106. package/dist/src/write-output.js +3 -3
  107. package/dist/src/write-output.js.map +1 -1
  108. package/dist/test/browser-build.test.d.ts +2 -0
  109. package/dist/test/browser-build.test.d.ts.map +1 -0
  110. package/dist/test/components/block.test.d.ts +2 -0
  111. package/dist/test/components/block.test.d.ts.map +1 -0
  112. package/dist/test/components/declaration.test.d.ts +2 -0
  113. package/dist/test/components/declaration.test.d.ts.map +1 -0
  114. package/dist/test/components/list.test.d.ts +2 -0
  115. package/dist/test/components/list.test.d.ts.map +1 -0
  116. package/dist/test/components/wrap.test.d.ts +2 -0
  117. package/dist/test/components/wrap.test.d.ts.map +1 -0
  118. package/dist/test/control-flow/for.test.d.ts +2 -0
  119. package/dist/test/control-flow/for.test.d.ts.map +1 -0
  120. package/dist/test/control-flow/match.test.d.ts +2 -0
  121. package/dist/test/control-flow/match.test.d.ts.map +1 -0
  122. package/dist/test/control-flow/show.test.d.ts +2 -0
  123. package/dist/test/control-flow/show.test.d.ts.map +1 -0
  124. package/dist/test/reactivity/cleanup.test.d.ts +2 -0
  125. package/dist/test/reactivity/cleanup.test.d.ts.map +1 -0
  126. package/dist/test/reactivity/memo.test.d.ts +2 -0
  127. package/dist/test/reactivity/memo.test.d.ts.map +1 -0
  128. package/dist/test/reactivity/untrack.test.d.ts +2 -0
  129. package/dist/test/reactivity/untrack.test.d.ts.map +1 -0
  130. package/dist/test/rendering/formatting.test.d.ts +2 -0
  131. package/dist/test/rendering/formatting.test.d.ts.map +1 -0
  132. package/dist/test/rendering/memoization.test.d.ts +2 -0
  133. package/dist/test/rendering/memoization.test.d.ts.map +1 -0
  134. package/dist/test/split-props.test.d.ts +2 -0
  135. package/dist/test/split-props.test.d.ts.map +1 -0
  136. package/dist/test/stc.test.d.ts.map +1 -1
  137. package/dist/test/utils.test.d.ts.map +1 -1
  138. package/dist/testing/extend-expect.js +4 -4
  139. package/dist/testing/extend-expect.js.map +1 -1
  140. package/dist/testing/render.d.ts +2 -3
  141. package/dist/testing/render.d.ts.map +1 -1
  142. package/dist/testing/render.js +2 -4
  143. package/dist/testing/render.js.map +1 -1
  144. package/dist/tsconfig.tsbuildinfo +1 -1
  145. package/package.json +6 -8
  146. package/src/binder.ts +54 -18
  147. package/src/code.ts +17 -12
  148. package/src/components/Block.tsx +44 -0
  149. package/src/components/Declaration.tsx +10 -4
  150. package/src/components/For.tsx +81 -0
  151. package/src/components/Indent.tsx +20 -27
  152. package/src/components/List.tsx +94 -0
  153. package/src/components/MemberDeclaration.tsx +9 -6
  154. package/src/components/MemberScope.tsx +4 -2
  155. package/src/components/Output.tsx +25 -13
  156. package/src/components/Scope.tsx +4 -2
  157. package/src/components/Show.tsx +11 -0
  158. package/src/components/SourceDirectory.tsx +5 -1
  159. package/src/components/SourceFile.tsx +12 -16
  160. package/src/components/StatementList.tsx +16 -0
  161. package/src/components/Switch.tsx +62 -0
  162. package/src/components/Wrap.tsx +29 -0
  163. package/src/components/index.tsx +8 -1
  164. package/src/components/stc/index.ts +18 -1
  165. package/src/context/index.ts +0 -1
  166. package/src/context.ts +2 -3
  167. package/src/index.browser.ts +2 -0
  168. package/src/index.ts +1 -0
  169. package/src/jsx-runtime.ts +245 -23
  170. package/src/render.ts +392 -198
  171. package/src/stc.ts +95 -0
  172. package/src/utils.ts +162 -95
  173. package/src/write-output.ts +3 -3
  174. package/temp/api.json +8407 -3301
  175. package/test/browser-build.test.ts +91 -0
  176. package/test/children.test.tsx +8 -10
  177. package/test/components/block.test.tsx +48 -0
  178. package/test/components/declaration.test.tsx +37 -0
  179. package/test/components/list.test.tsx +91 -0
  180. package/test/components/slot.test.tsx +31 -25
  181. package/test/components/source-file.test.tsx +11 -31
  182. package/test/components/wrap.test.tsx +42 -0
  183. package/test/control-flow/for.test.tsx +194 -0
  184. package/test/control-flow/match.test.tsx +49 -0
  185. package/test/control-flow/show.test.tsx +25 -0
  186. package/test/name-policy.test.tsx +5 -5
  187. package/test/reactivity/cleanup.test.tsx +91 -0
  188. package/test/reactivity/memo.test.tsx +17 -0
  189. package/test/reactivity/ref-rendering.test.tsx +3 -8
  190. package/test/reactivity/test.test.tsx +7 -6
  191. package/test/reactivity/untrack.test.ts +33 -0
  192. package/test/rendering/basic.test.tsx +25 -47
  193. package/test/rendering/code.test.tsx +3 -3
  194. package/test/rendering/formatting.test.tsx +487 -0
  195. package/test/rendering/indent.test.tsx +42 -529
  196. package/test/rendering/memoization.test.tsx +30 -0
  197. package/test/split-props.test.ts +87 -0
  198. package/test/stc.test.tsx +29 -8
  199. package/test/symbols.test.ts +87 -8
  200. package/test/utils.test.tsx +129 -20
  201. package/testing/extend-expect.ts +14 -4
  202. package/testing/render.ts +2 -4
  203. package/testing/vitest.d.ts +6 -1
  204. package/vitest.config.ts +1 -1
  205. package/dist/src/context/indent.d.ts +0 -5
  206. package/dist/src/context/indent.d.ts.map +0 -1
  207. package/dist/src/context/indent.js +0 -8
  208. package/dist/src/context/indent.js.map +0 -1
  209. package/dist/test/rendering/linebreaks.test.d.ts +0 -2
  210. package/dist/test/rendering/linebreaks.test.d.ts.map +0 -1
  211. package/src/context/indent.ts +0 -17
  212. package/test/rendering/linebreaks.test.tsx +0 -72
package/package.json CHANGED
@@ -1,28 +1,24 @@
1
1
  {
2
2
  "name": "@alloy-js/core",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "",
5
5
  "main": "./dist/src/index.js",
6
6
  "exports": {
7
7
  ".": {
8
- "development": "./src/index.ts",
8
+ "browser": "./dist/src/index.browser.js",
9
9
  "import": "./dist/src/index.js"
10
10
  },
11
11
  "./jsx-runtime": {
12
12
  "types": "./dist/src/jsx-runtime.d.ts",
13
- "development": "./src/jsx-runtime.ts",
14
13
  "import": "./dist/src/jsx-runtime.js"
15
14
  },
16
15
  "./testing": {
17
- "development": "./testing/index.ts",
18
16
  "import": "./dist/testing/index.js"
19
17
  },
20
18
  "./stc": {
21
- "development": "./src/components/stc/index.ts",
22
19
  "import": "./dist/src/components/stc/index.js"
23
20
  },
24
21
  "./components": {
25
- "development": "./src/components/index.ts",
26
22
  "import": "./dist/src/components/index.js"
27
23
  }
28
24
  },
@@ -40,7 +36,8 @@
40
36
  "chalk": "^5.3.0",
41
37
  "cli-table3": "^0.6.5",
42
38
  "pathe": "^1.1.2",
43
- "@alloy-js/babel-preset": "~0.1.1"
39
+ "prettier": "^3.5.1",
40
+ "@alloy-js/babel-preset": "~0.2.0"
44
41
  },
45
42
  "devDependencies": {
46
43
  "@babel/cli": "^7.24.7",
@@ -49,7 +46,8 @@
49
46
  "@rollup/plugin-typescript": "^11.1.6",
50
47
  "concurrently": "^8.2.2",
51
48
  "typescript": "^5.7.3",
52
- "vitest": "^3.0.4"
49
+ "vitest": "^3.0.4",
50
+ "vite": "^6.0.1"
53
51
  },
54
52
  "type": "module",
55
53
  "scripts": {
package/src/binder.ts CHANGED
@@ -99,9 +99,9 @@ export interface OutputSymbol {
99
99
  binder: Binder;
100
100
 
101
101
  /**
102
- * A unique value that references this symbol.
102
+ * The unique values that reference this symbol.
103
103
  */
104
- refkey: Refkey;
104
+ refkeys: Refkey[];
105
105
 
106
106
  /**
107
107
  * The instance members available on this symbol.
@@ -228,6 +228,7 @@ export type CreateSymbolOptions<T extends OutputSymbol = OutputSymbol> = {
228
228
  name: string;
229
229
  scope?: OutputScope;
230
230
  refkey?: Refkey;
231
+ refkeys?: Refkey[];
231
232
  flags?: OutputSymbolFlags;
232
233
  } & Omit<T, keyof OutputSymbol>;
233
234
 
@@ -265,6 +266,12 @@ export interface Binder {
265
266
  */
266
267
  createSymbol<T extends OutputSymbol>(args: CreateSymbolOptions<T>): T;
267
268
 
269
+ /**
270
+ * Delete the given symbol. The symbol will be removed from its parent's
271
+ * scope. Any resolutions to this symbol will become undefined.
272
+ */
273
+ deleteSymbol(symbol: OutputSymbol): void;
274
+
268
275
  /**
269
276
  * Instantiate the static members of a symbol into the instance members of
270
277
  * another symbol.
@@ -415,6 +422,7 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
415
422
  const binder: Binder = {
416
423
  createScope,
417
424
  createSymbol,
425
+ deleteSymbol,
418
426
  resolveDeclarationByKey,
419
427
  addStaticMembersToSymbol,
420
428
  addInstanceMembersToSymbol,
@@ -517,10 +525,20 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
517
525
  name,
518
526
  scope = useDefaultScope(args.flags),
519
527
  refkey,
528
+ refkeys,
520
529
  flags = OutputSymbolFlags.None,
521
530
  ...rest
522
531
  } = args;
523
532
 
533
+ const allRefkeys = [];
534
+ if (refkey) {
535
+ allRefkeys.push(refkey);
536
+ }
537
+
538
+ if (refkeys) {
539
+ allRefkeys.push(...refkeys);
540
+ }
541
+
524
542
  if (!scope) {
525
543
  throw new Error(
526
544
  "No scope was provided and no scope could be found in context",
@@ -549,7 +567,7 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
549
567
  originalName: name,
550
568
  name: name,
551
569
  scope,
552
- refkey,
570
+ refkeys: allRefkeys,
553
571
  binder,
554
572
  flags,
555
573
  ...rest,
@@ -576,7 +594,9 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
576
594
  }
577
595
 
578
596
  scope.symbols.add(symbol);
579
- scope.symbolsByRefkey.set(symbol.refkey, symbol);
597
+ for (const refkey of allRefkeys) {
598
+ scope.symbolsByRefkey.set(refkey, symbol);
599
+ }
580
600
 
581
601
  deconflict(symbol);
582
602
  notifyRefkey(symbol);
@@ -584,6 +604,20 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
584
604
  return symbol;
585
605
  }
586
606
 
607
+ function deleteSymbol(symbol: OutputSymbol) {
608
+ symbol.scope.symbols.delete(symbol);
609
+
610
+ if (!refkey) {
611
+ return;
612
+ }
613
+
614
+ for (const refkey of symbol.refkeys) {
615
+ const resolution = waitingDeclarations.get(refkey);
616
+ if (!resolution) return;
617
+ resolution.value = undefined;
618
+ }
619
+ }
620
+
587
621
  function instantiateSymbolInto(source: OutputSymbol, target: OutputSymbol) {
588
622
  if (~source.flags & OutputSymbolFlags.InstanceMemberContainer) {
589
623
  throw new Error("Can only instantiate symbols with instance members");
@@ -600,7 +634,8 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
600
634
  createSymbol({
601
635
  name: sym.name,
602
636
  scope: target.instanceMemberScope!,
603
- refkey: refkey(target.refkey, sym.refkey),
637
+ // todo: fix this or change this or something??
638
+ refkeys: [refkey(target.refkeys[0], sym.refkeys[0])],
604
639
  flags: sym.flags | OutputSymbolFlags.InstanceMember,
605
640
  });
606
641
  }
@@ -797,20 +832,21 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
797
832
 
798
833
  function notifyRefkey(symbol: OutputSymbol): void {
799
834
  effect(() => {
800
- const refkey = symbol.refkey;
801
- if (!refkey) return;
802
-
803
- knownDeclarations.set(refkey, symbol);
804
- if (waitingDeclarations.has(refkey)) {
805
- const signal = waitingDeclarations.get(refkey)!;
806
- signal.value = symbol;
807
- }
835
+ for (const refkey of symbol.refkeys) {
836
+ // notify those waiting for this refkey
837
+ knownDeclarations.set(refkey, symbol);
838
+ if (waitingDeclarations.has(refkey)) {
839
+ const signal = waitingDeclarations.get(refkey)!;
840
+ signal.value = symbol;
841
+ }
808
842
 
809
- const waitingScope = waitingSymbolNames.get(symbol.scope);
810
- if (waitingScope) {
811
- const waitingName = waitingScope.get(symbol.name);
812
- if (waitingName) {
813
- waitingName.value = symbol;
843
+ // notify those waiting for this symbol name
844
+ const waitingScope = waitingSymbolNames.get(symbol.scope);
845
+ if (waitingScope) {
846
+ const waitingName = waitingScope.get(symbol.name);
847
+ if (waitingName) {
848
+ waitingName.value = symbol;
849
+ }
814
850
  }
815
851
  }
816
852
  });
package/src/code.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  // this code is split into a tokenizer and a parser of sorts because I feel like
2
2
  // it should be psosible to share logic between this and the babel transform, but
3
3
  // this is an exercise for the future.
4
- import { Child, Indent } from "@alloy-js/core";
5
-
4
+ import { hbr, indent } from "./components/stc/index.js";
5
+ import { Child, Children } from "./jsx-runtime.js";
6
6
  interface IndentLevelData {
7
7
  kind: "indent";
8
- children: (string | Child | IndentLevelData)[];
8
+ children: (string | Children | IndentLevelData)[];
9
9
  pendingLines: string[];
10
10
  }
11
11
  export function code(
12
12
  template: TemplateStringsArray,
13
- ...substitutions: Child[]
13
+ ...substitutions: Children[]
14
14
  ) {
15
15
  const indentNodes: IndentLevelData[] = [
16
16
  {
@@ -39,6 +39,8 @@ export function code(
39
39
  popIndent();
40
40
  flushLines();
41
41
 
42
+ return childNodesFor(indentNodes[0]);
43
+
42
44
  function childNodesFor(indentNode: IndentLevelData): Child[] {
43
45
  return indentNode.children.map((child) => {
44
46
  if (
@@ -46,16 +48,13 @@ export function code(
46
48
  child !== null &&
47
49
  (child as any).kind === "indent"
48
50
  ) {
49
- return () =>
50
- Indent({ children: childNodesFor(child as IndentLevelData) });
51
+ return indent({ children: childNodesFor(child as IndentLevelData) });
51
52
  } else {
52
53
  return child as Child;
53
54
  }
54
55
  });
55
56
  }
56
57
 
57
- return childNodesFor(indentNodes[0]);
58
-
59
58
  function pushIndent() {
60
59
  flushLines();
61
60
  const newIndent: IndentLevelData = {
@@ -63,7 +62,7 @@ export function code(
63
62
  children: [],
64
63
  pendingLines: [""],
65
64
  };
66
- indentNodes.at(-1)!.children.push(newIndent as any);
65
+ indentNodes.at(-1)!.children.push(newIndent);
67
66
  indentNodes.push(newIndent);
68
67
  }
69
68
 
@@ -84,7 +83,13 @@ export function code(
84
83
  }
85
84
  function flushLines() {
86
85
  const currentIndent = indentNodes.at(-1)!;
87
- currentIndent.children.push(currentIndent.pendingLines.join("\n"));
86
+ currentIndent.children.push(
87
+ ...currentIndent.pendingLines
88
+ .map((str, index) =>
89
+ index < currentIndent.pendingLines.length - 1 ? [str, hbr()] : [str],
90
+ )
91
+ .flat(),
92
+ );
88
93
  currentIndent.pendingLines = [];
89
94
  }
90
95
  }
@@ -103,12 +108,12 @@ interface LineToken extends ChildTokenBase {
103
108
 
104
109
  interface OtherToken extends ChildTokenBase {
105
110
  kind: "other";
106
- value: Child;
111
+ value: Children;
107
112
  }
108
113
 
109
114
  function* childTokens(
110
115
  template: TemplateStringsArray,
111
- substitutions: Child[],
116
+ substitutions: Children[],
112
117
  ): IterableIterator<ChildToken> {
113
118
  let newline = false;
114
119
  const indentStack: { level: number; literalIndent: string }[] = [
@@ -0,0 +1,44 @@
1
+ import { Children, childrenArray, computed, Indent } from "@alloy-js/core";
2
+
3
+ export interface BlockProps {
4
+ /**
5
+ * The opening punctuation of the block. Defaults to "\{".
6
+ */
7
+ opener?: string;
8
+
9
+ /**
10
+ * The closing punctuation of the block. Defaults to "\}".
11
+ */
12
+ closer?: string;
13
+
14
+ /**
15
+ * Whether the block starts on a new line. When true, a hardline is added
16
+ * prior to the block.
17
+ */
18
+ newline?: boolean;
19
+
20
+ /** The block's contents */
21
+ children?: Children;
22
+ }
23
+
24
+ /**
25
+ * Create an indented block of source text. The block has `opener` text which is
26
+ * added prior to the block, which defaults to "\{", and `closer` text which is
27
+ * added after the block, which defaults to "\}".
28
+ */
29
+ export function Block(props: BlockProps) {
30
+ const childCount = computed(() => childrenArray(() => props.children).length);
31
+ return (
32
+ <group>
33
+ {props.newline && <br />}
34
+ {props.opener ?? "{"}
35
+ <Indent break={childCount.value > 0 ? "hard" : "soft"}>
36
+ {props.children}
37
+ </Indent>
38
+ {childCount.value > 0 ?
39
+ <hbr />
40
+ : <sbr />}
41
+ {props.closer ?? "}"}
42
+ </group>
43
+ );
44
+ }
@@ -2,7 +2,7 @@ import { OutputSymbol } from "../binder.js";
2
2
  import { useContext } from "../context.js";
3
3
  import { BinderContext } from "../context/binder.js";
4
4
  import { DeclarationContext } from "../context/declaration.js";
5
- import { Children } from "../jsx-runtime.js";
5
+ import { Children, onCleanup } from "../jsx-runtime.js";
6
6
  import { Refkey, refkey } from "../refkey.js";
7
7
 
8
8
  export interface DeclarationProps {
@@ -45,9 +45,15 @@ export function Declaration(props: DeclarationProps) {
45
45
  name: props.name!,
46
46
  refkey: rk,
47
47
  });
48
+
49
+ onCleanup(() => {
50
+ binder.deleteSymbol(declaration!);
51
+ });
48
52
  }
49
53
 
50
- return <DeclarationContext.Provider value={declaration}>
51
- {props.children}
52
- </DeclarationContext.Provider>;
54
+ return (
55
+ <DeclarationContext.Provider value={declaration}>
56
+ {props.children}
57
+ </DeclarationContext.Provider>
58
+ );
53
59
  }
@@ -0,0 +1,81 @@
1
+ import { Children, memo } from "@alloy-js/core/jsx-runtime";
2
+ import { isRef, Ref } from "@vue/reactivity";
3
+ import { mapJoin } from "../utils.js";
4
+ import { BaseListProps, baseListPropsToMapJoinArgs } from "./List.jsx";
5
+
6
+ export type ForCallbackArgs<T> =
7
+ T extends Ref<infer U> ? ForCallbackArgs<U>
8
+ : T extends () => infer U ? ForCallbackArgs<U>
9
+ : T extends (infer U)[] ? [value: U]
10
+ : T extends Map<infer U, infer V> ? [key: U, value: V]
11
+ : T extends Set<infer U> ? [value: U]
12
+ : [];
13
+
14
+ export interface ForProps<
15
+ T extends
16
+ | ForSupportedCollections
17
+ | (() => ForSupportedCollections)
18
+ | Ref<ForSupportedCollections>,
19
+ U extends Children,
20
+ > extends BaseListProps {
21
+ /**
22
+ * The array to iterate over.
23
+ */
24
+ each: T;
25
+
26
+ /**
27
+ * A function to call for each item.
28
+ */
29
+ children: (...args: [...ForCallbackArgs<T>, index: number]) => U;
30
+ }
31
+
32
+ export type ForSupportedCollections = any[] | Map<any, any> | Set<any>;
33
+ /**
34
+ * The For component iterates over the provided array and invokes the child
35
+ * callback for each item. It can optionally be provided with a `joiner` which
36
+ * is placed between each item, and an `ender` which is placed after the last
37
+ * item when there is at least one item.
38
+ *
39
+ * @example
40
+ *
41
+ * ```tsx
42
+ * const items = ["apple", "pear", "plum"];
43
+ * return <For each={items}>
44
+ * {(item) => <>Fruit: {item}</>}
45
+ * </For>
46
+ * ```
47
+ *
48
+ * @remarks
49
+ *
50
+ * When the `each` prop is a reactive (e.g. a reactive array, or ref to an
51
+ * array), `For` will automatically update when the array changes. When doing
52
+ * so, it will attempt to avoid re-rendering items which have not changed. For
53
+ * example, when appending an item to a reactive array, existing items will not
54
+ * be re-rendered. Note that presently the implementation is fairly simple -
55
+ * when making modifications to the middle of an array it likely that every
56
+ * element after the modification will be rerendered.
57
+ *
58
+ * @see {@link (mapJoin:1)} for mapping arrays to elements outside of JSX templates.
59
+ */
60
+ export function For<
61
+ T extends
62
+ | ForSupportedCollections
63
+ | (() => ForSupportedCollections)
64
+ | Ref<ForSupportedCollections>,
65
+ U extends Children,
66
+ >(props: ForProps<T, U>) {
67
+ const cb = props.children;
68
+ const options = baseListPropsToMapJoinArgs(props);
69
+ options.skipFalsy = true;
70
+ return memo(() => {
71
+ const maybeRef = props.each;
72
+
73
+ return (mapJoin as any)(
74
+ typeof maybeRef === "function" ? maybeRef : (
75
+ () => (isRef(maybeRef) ? maybeRef.value : maybeRef)
76
+ ),
77
+ cb,
78
+ options,
79
+ );
80
+ });
81
+ }
@@ -1,33 +1,26 @@
1
- import { useContext } from "../context.js";
2
- import { IndentContext } from "../context/indent.js";
3
- import { Children } from "../jsx-runtime.js";
1
+ import { Children } from "@alloy-js/core/jsx-runtime";
4
2
 
5
3
  export interface IndentProps {
6
- children?: Children;
7
- indent?: string;
4
+ children: Children;
5
+ nobreak?: boolean;
6
+ break?: "space" | "soft" | "hard";
7
+ trailingBreak?: boolean;
8
8
  }
9
-
10
- export interface IndentState {
11
- level: number;
12
- indent: string;
13
- indentString: string; // awful name
14
- noLeading?: boolean;
15
- }
16
-
17
9
  export function Indent(props: IndentProps) {
18
- const previousIndent = useContext(IndentContext) ?? {
19
- level: 0,
20
- indent: props.indent ?? " ",
21
- indentString: "",
22
- };
23
-
24
- const level = previousIndent.level + 1;
25
-
26
- const currentIndent = {
27
- level,
28
- indent: props.indent ?? previousIndent.indent,
29
- indentString: (props.indent ?? previousIndent.indent).repeat(level),
30
- };
10
+ const breakStyle = props.break ?? "hard";
11
+ const breakElem =
12
+ props.nobreak ? ""
13
+ : breakStyle === "hard" ? <hbr />
14
+ : breakStyle === "soft" ? <sbr />
15
+ : <br />;
31
16
 
32
- return <IndentContext.Provider value={currentIndent}>{props.children}</IndentContext.Provider>;
17
+ return (
18
+ <>
19
+ <indent>
20
+ {breakElem}
21
+ {props.children}
22
+ </indent>
23
+ {props.trailingBreak && breakElem}
24
+ </>
25
+ );
33
26
  }
@@ -0,0 +1,94 @@
1
+ import { Children, memo, splitProps } from "@alloy-js/core/jsx-runtime";
2
+ import { childrenArray, JoinOptions } from "../utils.js";
3
+ import { For } from "./For.jsx";
4
+
5
+ export type BreakKind = "none" | "space" | "soft" | "hard" | "literal";
6
+
7
+ export interface BaseListProps {
8
+ /** Text to place between each element */
9
+ joiner?: Children;
10
+
11
+ /** Place a comma between each element */
12
+ comma?: boolean;
13
+
14
+ /** Place a semicolon between each element */
15
+ semicolon?: boolean;
16
+
17
+ line?: boolean;
18
+ softline?: boolean;
19
+ hardline?: boolean;
20
+ literalline?: boolean;
21
+ space?: boolean;
22
+
23
+ /**
24
+ * Text to place at the end of the list when there is at least one item. If
25
+ * set to true, the joiner is used.
26
+ **/
27
+ ender?: Children;
28
+
29
+ /**
30
+ * Place the join punctuation at the end, but without a line break.
31
+ */
32
+ enderPunctuation?: boolean;
33
+ }
34
+
35
+ export function baseListPropsToMapJoinArgs(props: BaseListProps): JoinOptions {
36
+ let joiner, punctuation;
37
+ if ("joiner" in props) {
38
+ joiner = props.joiner;
39
+ } else {
40
+ punctuation =
41
+ props.comma ? ","
42
+ : props.semicolon ? ";"
43
+ : "";
44
+
45
+ joiner = (
46
+ <>
47
+ {punctuation}
48
+ {props.softline ?
49
+ <sbr />
50
+ : props.hardline ?
51
+ <hbr />
52
+ : props.literalline ?
53
+ <lbr />
54
+ : props.line ?
55
+ <br />
56
+ : props.space ?
57
+ <> </>
58
+ : <hbr />}
59
+ </>
60
+ );
61
+ }
62
+
63
+ const ender =
64
+ "ender" in props ? props.ender
65
+ : props.enderPunctuation ? punctuation
66
+ : undefined;
67
+
68
+ return { joiner, ender };
69
+ }
70
+
71
+ export interface ListProps extends BaseListProps {
72
+ children?: Children;
73
+ }
74
+
75
+ /**
76
+ * Create a list of children with text between each child. The text to join with
77
+ * is specified by providing either `joiner` children, or providing boolean
78
+ * props for the punctuation and line breaks. The default joiner is no
79
+ * punctuation and a hard break. The `ender` prop can provide text to place at
80
+ * the end of the list when there is at least one child.
81
+ */
82
+ export function List(props: ListProps) {
83
+ const [rest, forProps] = splitProps(props, ["children"]);
84
+ const resolvedChildren = memo(() =>
85
+ childrenArray(() => rest.children, {
86
+ preserveFragments: true,
87
+ }),
88
+ );
89
+ return (
90
+ <For each={resolvedChildren} {...forProps}>
91
+ {(child) => child}
92
+ </For>
93
+ );
94
+ }
@@ -50,13 +50,16 @@ export function MemberDeclaration(props: MemberDeclarationProps) {
50
50
  declaration = binder.createSymbol({
51
51
  name: props.name!,
52
52
  refkey: rk,
53
- flags: props.static ?
54
- OutputSymbolFlags.StaticMember
55
- : OutputSymbolFlags.InstanceMember,
53
+ flags:
54
+ props.static ?
55
+ OutputSymbolFlags.StaticMember
56
+ : OutputSymbolFlags.InstanceMember,
56
57
  });
57
58
  }
58
59
 
59
- return <MemberDeclarationContext.Provider value={declaration}>
60
- {props.children}
61
- </MemberDeclarationContext.Provider>;
60
+ return (
61
+ <MemberDeclarationContext.Provider value={declaration}>
62
+ {props.children}
63
+ </MemberDeclarationContext.Provider>
64
+ );
62
65
  }
@@ -34,7 +34,9 @@ export function MemberScope(props: MemberScopeProps) {
34
34
  instanceMembers: props.owner.instanceMemberScope,
35
35
  staticMembers: props.owner.staticMemberScope,
36
36
  };
37
- return <MemberScopeContext.Provider value={context}>
37
+ return (
38
+ <MemberScopeContext.Provider value={context}>
38
39
  {props.children}
39
- </MemberScopeContext.Provider>;
40
+ </MemberScopeContext.Provider>
41
+ );
40
42
  }
@@ -5,15 +5,16 @@ import {
5
5
  } from "../binder.js";
6
6
  import { BinderContext } from "../context/binder.js";
7
7
  import { NamePolicyContext } from "../context/name-policy.js";
8
- import { Children } from "../jsx-runtime.js";
8
+ import { Children, getContext } from "../jsx-runtime.js";
9
9
  import { NamePolicy } from "../name-policy.js";
10
+ import { PrintTreeOptions } from "../render.js";
10
11
  import { extensionEffects } from "../slot.js";
11
12
  import { SourceDirectory } from "./SourceDirectory.js";
12
13
 
13
14
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
15
  import { SourceFile } from "./SourceFile.js";
15
16
 
16
- export interface OutputProps {
17
+ export interface OutputProps extends PrintTreeOptions {
17
18
  children?: Children;
18
19
  /**
19
20
  * External libraries whose symbols should be available for reference.
@@ -48,10 +49,18 @@ export function Output(props: OutputProps) {
48
49
  const binder = createOutputBinder({
49
50
  nameConflictResolver: props.nameConflictResolver,
50
51
  });
51
- const dir =
52
- <SourceDirectory path={basePath}>
53
- {props.children}
54
- </SourceDirectory>;
52
+
53
+ const nodeContext = getContext()!;
54
+ nodeContext.meta ??= {};
55
+ nodeContext.meta.printOptions = {
56
+ printWidth: props.printWidth,
57
+ tabWidth: props.tabWidth,
58
+ useTabs: props.useTabs,
59
+ };
60
+
61
+ const dir = (
62
+ <SourceDirectory path={basePath}>{props.children}</SourceDirectory>
63
+ );
55
64
 
56
65
  if (props.externals) {
57
66
  for (const global of props.externals) {
@@ -59,13 +68,16 @@ export function Output(props: OutputProps) {
59
68
  }
60
69
  }
61
70
 
62
- return <BinderContext.Provider value={binder}>
63
- {() => { extensionEffects.forEach(e => e())}}{
64
- props.namePolicy ?
71
+ return (
72
+ <BinderContext.Provider value={binder}>
73
+ {() => {
74
+ extensionEffects.forEach((e) => e());
75
+ }}
76
+ {props.namePolicy ?
65
77
  <NamePolicyContext.Provider value={props.namePolicy}>
66
78
  {dir}
67
- </NamePolicyContext.Provider> :
68
- dir
69
- }
70
- </BinderContext.Provider>;
79
+ </NamePolicyContext.Provider>
80
+ : dir}
81
+ </BinderContext.Provider>
82
+ );
71
83
  }