octane 0.2.11 → 0.2.13

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 (264) hide show
  1. package/README.md +5 -0
  2. package/dist/behavior-root.d.ts +13 -3
  3. package/dist/behavior-root.js +28 -5
  4. package/dist/cjs/behavior-root.cjs +32 -6
  5. package/dist/cjs/class-names.cjs +49 -0
  6. package/dist/cjs/constants.cjs +9 -0
  7. package/dist/cjs/context-epoch.cjs +38 -0
  8. package/dist/cjs/css.cjs +6 -27
  9. package/dist/cjs/data-encoding.cjs +160 -0
  10. package/dist/cjs/dom-binding-handoff.cjs +93 -0
  11. package/dist/cjs/dom-binding-protocol.cjs +115 -0
  12. package/dist/cjs/dom-binding-styles.cjs +292 -0
  13. package/dist/cjs/dom-bindings.cjs +700 -0
  14. package/dist/cjs/dom-focused-move.cjs +66 -0
  15. package/dist/cjs/dom-stage.cjs +896 -0
  16. package/dist/cjs/dom-tables.cjs +4 -94
  17. package/dist/cjs/error-codes.client.generated.cjs +33 -0
  18. package/dist/cjs/error-codes.server.generated.cjs +24 -0
  19. package/dist/cjs/hydration/control-capture.cjs +244 -0
  20. package/dist/cjs/hydration/event-capture.cjs +125 -43
  21. package/dist/cjs/hydration/interaction-config.cjs +50 -2
  22. package/dist/cjs/hydration-markers.cjs +37 -0
  23. package/dist/cjs/independent-hydration-protocol.cjs +106 -0
  24. package/dist/cjs/index.cjs +9 -0
  25. package/dist/cjs/runtime.cjs +6397 -1683
  26. package/dist/cjs/runtime.server.cjs +1465 -213
  27. package/dist/cjs/server/early-signals.cjs +45 -0
  28. package/dist/cjs/server/index.cjs +47 -0
  29. package/dist/cjs/server/rpc-batch.cjs +149 -0
  30. package/dist/cjs/server/rpc-stream.cjs +146 -0
  31. package/dist/cjs/server/rpc.cjs +34 -15
  32. package/dist/cjs/server/signal-query-observation.cjs +158 -0
  33. package/dist/cjs/server/streamed-region.cjs +52 -0
  34. package/dist/cjs/server/streamed-signals.cjs +512 -0
  35. package/dist/cjs/server-call.cjs +103 -0
  36. package/dist/cjs/server-rpc-batch-client.cjs +141 -0
  37. package/dist/cjs/server-rpc-client.cjs +94 -27
  38. package/dist/cjs/server-rpc-protocol.cjs +141 -0
  39. package/dist/cjs/server-rpc-stream-client.cjs +118 -0
  40. package/dist/cjs/signals/actions.cjs +474 -0
  41. package/dist/cjs/signals/client.cjs +4 -1
  42. package/dist/cjs/signals/computations.cjs +460 -0
  43. package/dist/cjs/signals/control-binding.cjs +368 -0
  44. package/dist/cjs/signals/derived-facade.cjs +50 -0
  45. package/dist/cjs/signals/document-owner.cjs +71 -0
  46. package/dist/cjs/signals/early-values.cjs +155 -0
  47. package/dist/cjs/signals/encoding.cjs +6 -95
  48. package/dist/cjs/signals/engine.cjs +276 -12
  49. package/dist/cjs/signals/errors.cjs +18 -0
  50. package/dist/cjs/signals/facade.cjs +477 -0
  51. package/dist/cjs/signals/graph.cjs +166 -20
  52. package/dist/cjs/signals/handle-protocol.cjs +36 -0
  53. package/dist/cjs/signals/index.cjs +81 -2
  54. package/dist/cjs/signals/native-read-client.cjs +10 -0
  55. package/dist/cjs/signals/owner-context.cjs +112 -0
  56. package/dist/cjs/signals/query-attempt-observer.cjs +68 -0
  57. package/dist/cjs/signals/query-facade.cjs +72 -0
  58. package/dist/cjs/signals/read-protocol.cjs +54 -0
  59. package/dist/cjs/signals/requests.cjs +287 -14
  60. package/dist/cjs/signals/scalar-computations.cjs +76 -0
  61. package/dist/cjs/signals/server.cjs +4 -1
  62. package/dist/cjs/signals/transition-candidate.cjs +567 -0
  63. package/dist/cjs/signals/transition-state.cjs +106 -0
  64. package/dist/cjs/signals/types.cjs +50 -0
  65. package/dist/cjs/stream-protocol.cjs +3 -0
  66. package/dist/cjs/streamed-signals-protocol.cjs +159 -0
  67. package/dist/cjs/style-values.cjs +122 -0
  68. package/dist/cjs/trusted-html.cjs +30 -0
  69. package/dist/cjs/version.cjs +1 -1
  70. package/dist/class-names.d.ts +5 -0
  71. package/dist/class-names.js +25 -0
  72. package/dist/compiler/bundler.js +51 -22
  73. package/dist/compiler/compile.js +2534 -581
  74. package/dist/compiler/dom-binding-program.js +935 -0
  75. package/dist/compiler/dom-binding-request.js +57 -0
  76. package/dist/compiler/dom-bindings.js +2245 -0
  77. package/dist/compiler/hook-deps.js +759 -10
  78. package/dist/compiler/hydrate-boundaries.js +358 -15
  79. package/dist/compiler/index.d.ts +39 -2
  80. package/dist/compiler/index.js +1 -0
  81. package/dist/compiler/native-attribute-reads.js +127 -0
  82. package/dist/compiler/native-change-diagnostics.js +32 -38
  83. package/dist/compiler/native-read-diagnostics.js +69 -8
  84. package/dist/compiler/native-read-types.js +80 -34
  85. package/dist/compiler/parser.node.js +1 -1
  86. package/dist/compiler/renderer-boundaries.js +40 -0
  87. package/dist/compiler/server-context.js +118 -0
  88. package/dist/compiler/signal-attempt-reads.js +479 -0
  89. package/dist/compiler/signal-declarations.js +557 -0
  90. package/dist/compiler/signal-start-reads.js +413 -0
  91. package/dist/compiler/slot-hooks.d.ts +3 -1
  92. package/dist/compiler/slot-hooks.js +168 -34
  93. package/dist/compiler/strong-auto-memo.js +90 -0
  94. package/dist/compiler/strong-html.js +143 -0
  95. package/dist/compiler/strong-mode.js +764 -66
  96. package/dist/compiler/strong-template-policy.js +307 -0
  97. package/dist/compiler/template-ir.js +200 -0
  98. package/dist/compiler/typescript.d.ts +2 -0
  99. package/dist/compiler/typescript.js +2 -0
  100. package/dist/compiler/vite.d.ts +3 -1
  101. package/dist/compiler/vite.js +122 -4
  102. package/dist/compiler/volar.LICENSES.txt +1 -1
  103. package/dist/compiler/volar.d.ts +6 -1
  104. package/dist/compiler/volar.js +750 -1433
  105. package/dist/constants.d.ts +1 -0
  106. package/dist/constants.js +10 -0
  107. package/dist/context-epoch.d.ts +2 -0
  108. package/dist/context-epoch.js +13 -0
  109. package/dist/css.d.ts +2 -5
  110. package/dist/css.js +2 -23
  111. package/dist/data-encoding.d.ts +9 -0
  112. package/dist/data-encoding.js +134 -0
  113. package/dist/dom-binding-classes.d.ts +15 -0
  114. package/dist/dom-binding-classes.js +129 -0
  115. package/dist/dom-binding-controls.d.ts +2 -0
  116. package/dist/dom-binding-controls.js +6 -0
  117. package/dist/dom-binding-handoff.d.ts +55 -0
  118. package/dist/dom-binding-handoff.js +62 -0
  119. package/dist/dom-binding-program.d.ts +300 -0
  120. package/dist/dom-binding-program.js +1625 -0
  121. package/dist/dom-binding-projections.d.ts +15 -0
  122. package/dist/dom-binding-projections.js +187 -0
  123. package/dist/dom-binding-protocol.d.ts +13 -0
  124. package/dist/dom-binding-protocol.js +88 -0
  125. package/dist/dom-binding-signals.d.ts +21 -0
  126. package/dist/dom-binding-signals.js +109 -0
  127. package/dist/dom-binding-styles.d.ts +17 -0
  128. package/dist/dom-binding-styles.js +272 -0
  129. package/dist/dom-bindings.d.ts +89 -0
  130. package/dist/dom-bindings.js +670 -0
  131. package/dist/dom-focused-move.d.ts +6 -0
  132. package/dist/dom-focused-move.js +42 -0
  133. package/dist/dom-stage.d.ts +52 -0
  134. package/dist/dom-stage.js +872 -0
  135. package/dist/dom-tables.d.ts +1 -3
  136. package/dist/dom-tables.js +1 -91
  137. package/dist/error-codes.client.generated.d.ts +9 -0
  138. package/dist/error-codes.client.generated.js +33 -0
  139. package/dist/error-codes.server.generated.d.ts +6 -0
  140. package/dist/error-codes.server.generated.js +24 -0
  141. package/dist/hydration/control-capture.d.ts +62 -0
  142. package/dist/hydration/control-capture.js +223 -0
  143. package/dist/hydration/document-lifecycle.d.ts +23 -0
  144. package/dist/hydration/document-lifecycle.js +80 -0
  145. package/dist/hydration/event-capture.d.ts +20 -1
  146. package/dist/hydration/event-capture.js +128 -42
  147. package/dist/hydration/independent-island.d.ts +45 -0
  148. package/dist/hydration/independent-island.js +233 -0
  149. package/dist/hydration/index.d.ts +8 -1
  150. package/dist/hydration/index.js +55 -1
  151. package/dist/hydration/interaction-config.d.ts +5 -0
  152. package/dist/hydration/interaction-config.js +44 -1
  153. package/dist/hydration/stream-delivery.d.ts +32 -0
  154. package/dist/hydration/stream-delivery.js +333 -0
  155. package/dist/hydration/stream-receiver.d.ts +26 -0
  156. package/dist/hydration/stream-receiver.js +204 -0
  157. package/dist/hydration/stream-result-receiver.d.ts +70 -0
  158. package/dist/hydration/stream-result-receiver.js +246 -0
  159. package/dist/hydration/streamed-signals.d.ts +44 -0
  160. package/dist/hydration/streamed-signals.js +186 -0
  161. package/dist/hydration/types.d.ts +6 -0
  162. package/dist/hydration-markers.d.ts +7 -0
  163. package/dist/hydration-markers.js +10 -0
  164. package/dist/independent-hydration-protocol.d.ts +31 -0
  165. package/dist/independent-hydration-protocol.js +80 -0
  166. package/dist/index.d.ts +3 -0
  167. package/dist/index.js +6 -0
  168. package/dist/internal/signal-read.d.ts +1 -0
  169. package/dist/internal/signal-read.js +4 -0
  170. package/dist/jsx-runtime-strong.d.ts +33 -0
  171. package/dist/jsx-runtime.d.ts +58 -2
  172. package/dist/public-types.d.ts +15 -1
  173. package/dist/runtime.d.ts +297 -23
  174. package/dist/runtime.js +6405 -1679
  175. package/dist/runtime.server.d.ts +110 -27
  176. package/dist/runtime.server.js +1458 -213
  177. package/dist/server/early-signals.d.ts +14 -0
  178. package/dist/server/early-signals.js +34 -0
  179. package/dist/server/index.d.ts +11 -2
  180. package/dist/server/index.js +50 -1
  181. package/dist/server/rpc-batch.d.ts +17 -0
  182. package/dist/server/rpc-batch.js +129 -0
  183. package/dist/server/rpc-stream.d.ts +9 -0
  184. package/dist/server/rpc-stream.js +125 -0
  185. package/dist/server/rpc.d.ts +5 -1
  186. package/dist/server/rpc.js +37 -13
  187. package/dist/server/signal-query-observation.d.ts +3 -0
  188. package/dist/server/signal-query-observation.js +134 -0
  189. package/dist/server/streamed-region.d.ts +15 -0
  190. package/dist/server/streamed-region.js +30 -0
  191. package/dist/server/streamed-signals.d.ts +45 -0
  192. package/dist/server/streamed-signals.js +488 -0
  193. package/dist/server-call.d.ts +41 -0
  194. package/dist/server-call.js +75 -0
  195. package/dist/server-rpc-batch-client.d.ts +7 -0
  196. package/dist/server-rpc-batch-client.js +119 -0
  197. package/dist/server-rpc-client.d.ts +3 -1
  198. package/dist/server-rpc-client.js +99 -27
  199. package/dist/server-rpc-protocol.d.ts +47 -0
  200. package/dist/server-rpc-protocol.js +109 -0
  201. package/dist/server-rpc-stream-client.d.ts +5 -0
  202. package/dist/server-rpc-stream-client.js +98 -0
  203. package/dist/signals/actions.d.ts +12 -0
  204. package/dist/signals/actions.js +466 -0
  205. package/dist/signals/client.d.ts +2 -0
  206. package/dist/signals/client.js +1 -0
  207. package/dist/signals/computations.d.ts +37 -0
  208. package/dist/signals/computations.js +455 -0
  209. package/dist/signals/control-binding.d.ts +31 -0
  210. package/dist/signals/control-binding.js +353 -0
  211. package/dist/signals/derived-facade.d.ts +3 -0
  212. package/dist/signals/derived-facade.js +25 -0
  213. package/dist/signals/document-owner.d.ts +10 -0
  214. package/dist/signals/document-owner.js +43 -0
  215. package/dist/signals/early-values.d.ts +24 -0
  216. package/dist/signals/early-values.js +124 -0
  217. package/dist/signals/encoding.d.ts +1 -3
  218. package/dist/signals/encoding.js +3 -93
  219. package/dist/signals/engine.d.ts +53 -4
  220. package/dist/signals/engine.js +275 -16
  221. package/dist/signals/errors.d.ts +7 -0
  222. package/dist/signals/errors.js +16 -0
  223. package/dist/signals/facade.d.ts +74 -0
  224. package/dist/signals/facade.js +452 -0
  225. package/dist/signals/graph.d.ts +52 -6
  226. package/dist/signals/graph.js +174 -19
  227. package/dist/signals/handle-protocol.d.ts +4 -0
  228. package/dist/signals/handle-protocol.js +11 -0
  229. package/dist/signals/index.d.ts +12 -3
  230. package/dist/signals/index.js +71 -2
  231. package/dist/signals/native-read-client.d.ts +3 -0
  232. package/dist/signals/native-read-client.js +10 -0
  233. package/dist/signals/owner-context.d.ts +22 -0
  234. package/dist/signals/owner-context.js +77 -0
  235. package/dist/signals/query-attempt-observer.d.ts +49 -0
  236. package/dist/signals/query-attempt-observer.js +41 -0
  237. package/dist/signals/query-facade.d.ts +3 -0
  238. package/dist/signals/query-facade.js +49 -0
  239. package/dist/signals/read-protocol.d.ts +37 -2
  240. package/dist/signals/read-protocol.js +44 -0
  241. package/dist/signals/requests.d.ts +38 -5
  242. package/dist/signals/requests.js +298 -15
  243. package/dist/signals/scalar-computations.d.ts +2 -0
  244. package/dist/signals/scalar-computations.js +60 -0
  245. package/dist/signals/server.d.ts +2 -0
  246. package/dist/signals/server.js +1 -0
  247. package/dist/signals/transition-candidate.d.ts +71 -0
  248. package/dist/signals/transition-candidate.js +557 -0
  249. package/dist/signals/transition-state.d.ts +51 -0
  250. package/dist/signals/transition-state.js +70 -0
  251. package/dist/signals/types.d.ts +95 -5
  252. package/dist/signals/types.js +20 -0
  253. package/dist/stream-protocol.d.ts +0 -5
  254. package/dist/stream-protocol.js +3 -0
  255. package/dist/streamed-signals-protocol.d.ts +60 -0
  256. package/dist/streamed-signals-protocol.js +130 -0
  257. package/dist/style-values.d.ts +3 -0
  258. package/dist/style-values.js +96 -0
  259. package/dist/trusted-html.d.ts +17 -0
  260. package/dist/trusted-html.js +6 -0
  261. package/dist/universal-core.d.ts +6 -0
  262. package/dist/universal-core.js +214 -202
  263. package/dist/version.js +1 -1
  264. package/package.json +48 -5
@@ -0,0 +1,127 @@
1
+ // Direct native bindings recognize capability syntax, not structural `.get`
2
+ // methods. Share this syntax boundary between runtime and virtual TSX lowering.
3
+ function unwrapExpression(node) {
4
+ while (
5
+ node &&
6
+ (node.type === 'TSAsExpression' ||
7
+ node.type === 'TSTypeAssertion' ||
8
+ node.type === 'TSNonNullExpression' ||
9
+ node.type === 'TSSatisfiesExpression' ||
10
+ node.type === 'TSInstantiationExpression' ||
11
+ node.type === 'ChainExpression' ||
12
+ node.type === 'ParenthesizedExpression')
13
+ ) {
14
+ node = node.expression;
15
+ }
16
+ return node;
17
+ }
18
+
19
+ export function isDirectSignalHandleExpression(node) {
20
+ node = unwrapExpression(node);
21
+ if (!node) return false;
22
+ if (node.type === 'Identifier') return node.name.endsWith('$');
23
+ if (node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression') {
24
+ const name = node.computed
25
+ ? node.property?.type === 'Literal' && typeof node.property.value === 'string'
26
+ ? node.property.value
27
+ : null
28
+ : node.property?.name;
29
+ return typeof name === 'string' && name.endsWith('$');
30
+ }
31
+ if (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') {
32
+ const callee = unwrapExpression(node.callee);
33
+ const name =
34
+ callee?.type === 'Identifier'
35
+ ? callee.name
36
+ : callee?.type === 'MemberExpression' || callee?.type === 'OptionalMemberExpression'
37
+ ? callee.computed
38
+ ? callee.property?.value
39
+ : callee.property?.name
40
+ : null;
41
+ return /^_?\$?__(?:signal|derived|query)At$/.test(name ?? '');
42
+ }
43
+ return false;
44
+ }
45
+
46
+ function rejectNestedHandles(node) {
47
+ if (!node || typeof node !== 'object') return;
48
+ if (isDirectSignalHandleExpression(node))
49
+ throw new Error(
50
+ 'Native JSX signal projections do not support handles inside callbacks, constructors, or tagged templates.',
51
+ );
52
+ for (const key of Object.keys(node)) {
53
+ if (!['metadata', 'loc', 'start', 'end'].includes(key)) rejectNestedHandles(node[key]);
54
+ }
55
+ }
56
+
57
+ /** Only an explicitly configured native attribute admits these sampled reads. */
58
+ export function lowerNativeAttributeReads(expression, read) {
59
+ let reactive = false;
60
+ const rewrite = (node) => {
61
+ if (!node || typeof node !== 'object') return node;
62
+ if (Array.isArray(node)) return node.map(rewrite);
63
+ if (isDirectSignalHandleExpression(node)) {
64
+ reactive = true;
65
+ return read(node);
66
+ }
67
+ if (node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression') {
68
+ if (isDirectSignalHandleExpression(node.object))
69
+ throw new Error(
70
+ 'Native JSX signal projections require .get() before reading a signal payload property.',
71
+ );
72
+ return {
73
+ ...node,
74
+ object: rewrite(node.object),
75
+ property: node.computed ? rewrite(node.property) : node.property,
76
+ };
77
+ }
78
+ if (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') {
79
+ const callee = unwrapExpression(node.callee);
80
+ const handleGet =
81
+ (callee?.type === 'MemberExpression' || callee?.type === 'OptionalMemberExpression') &&
82
+ (callee.computed ? callee.property?.value : callee.property?.name) === 'get' &&
83
+ isDirectSignalHandleExpression(callee.object);
84
+ if (handleGet) reactive = true;
85
+ return {
86
+ ...node,
87
+ // Keep a method call as a member call so `this` and evaluation order
88
+ // survive a computed style selection. Explicit get keeps its receiver.
89
+ callee:
90
+ !handleGet &&
91
+ (callee?.type === 'MemberExpression' || callee?.type === 'OptionalMemberExpression')
92
+ ? rewrite(node.callee)
93
+ : node.callee,
94
+ arguments: node.arguments.map(rewrite),
95
+ };
96
+ }
97
+ if (
98
+ [
99
+ 'FunctionExpression',
100
+ 'ArrowFunctionExpression',
101
+ 'ClassExpression',
102
+ 'NewExpression',
103
+ 'TaggedTemplateExpression',
104
+ ].includes(node.type)
105
+ ) {
106
+ rejectNestedHandles(node);
107
+ return node;
108
+ }
109
+ if (node.type === 'Property') {
110
+ const value = rewrite(node.value);
111
+ return value === node.value ? node : { ...node, value, shorthand: false };
112
+ }
113
+ let result = node;
114
+ for (const key of Object.keys(node)) {
115
+ if (['metadata', 'loc', 'start', 'end'].includes(key)) continue;
116
+ const value = node[key];
117
+ if (!value || typeof value !== 'object') continue;
118
+ const next = rewrite(value);
119
+ if (next !== value) {
120
+ if (result === node) result = { ...node };
121
+ result[key] = next;
122
+ }
123
+ }
124
+ return result;
125
+ };
126
+ return { expression: rewrite(expression), reactive };
127
+ }
@@ -1,6 +1,6 @@
1
1
  import { builders as b } from '@tsrx/core';
2
2
  import { nsForChildren, nsForSelf } from './jsx-namespace.js';
3
- import { analyzeRendererBoundaries } from './renderer-boundaries.js';
3
+ import { createRendererRegionResolver } from './renderer-boundaries.js';
4
4
 
5
5
  export const NATIVE_TEXT_ONCHANGE_DIAGNOSTIC = 'OCTANE_NATIVE_TEXT_ONCHANGE';
6
6
 
@@ -263,15 +263,16 @@ function hostLabel(tag, type) {
263
263
  return tag === 'textarea' ? '<textarea>' : `<input type="${type.display}">`;
264
264
  }
265
265
 
266
- function diagnosticFor(source, filename, tag, type, changeAttributes, controlled) {
266
+ function diagnosticFor(source, filename, tag, type, changeAttributes, controlled, strong) {
267
267
  const first = changeAttributes[0];
268
268
  const captureOnly = attributeName(first) === 'onChangeCapture';
269
269
  const replacement = captureOnly ? 'onInputCapture' : 'onInput';
270
270
  let message =
271
271
  `[${NATIVE_TEXT_ONCHANGE_DIAGNOSTIC}] \`${attributeName(first)}\` on ${hostLabel(tag, type)} ` +
272
272
  `is a native commit event in Octane; it does not run for each text edit. Use \`${replacement}\` ` +
273
- 'for per-edit updates. If commit/blur behavior is intentional, add ' +
274
- '`suppressNativeChangeWarning`.';
273
+ (strong
274
+ ? 'for per-edit updates. Strong mode requires an explicit per-edit handler for editable text controls.'
275
+ : 'for per-edit updates. If commit/blur behavior is intentional, add `suppressNativeChangeWarning`.');
275
276
  if (controlled) {
276
277
  message +=
277
278
  ' This control also has `value`; edits are restored before the later native change. ' +
@@ -280,7 +281,7 @@ function diagnosticFor(source, filename, tag, type, changeAttributes, controlled
280
281
  const primary = rangeFor(source, first.name ?? first);
281
282
  return {
282
283
  code: NATIVE_TEXT_ONCHANGE_DIAGNOSTIC,
283
- severity: 'warning',
284
+ severity: strong ? 'error' : 'warning',
284
285
  message,
285
286
  filename: filename || 'module.tsrx',
286
287
  start: primary.start,
@@ -295,7 +296,16 @@ function diagnosticFor(source, filename, tag, type, changeAttributes, controlled
295
296
  };
296
297
  }
297
298
 
298
- function classifyHost(node, scope, namespace, source, filename, diagnostics, classifications) {
299
+ function classifyHost(
300
+ node,
301
+ scope,
302
+ namespace,
303
+ source,
304
+ filename,
305
+ diagnostics,
306
+ classifications,
307
+ strong,
308
+ ) {
299
309
  const tag = tagName(node);
300
310
  if ((tag !== 'input' && tag !== 'textarea') || namespace !== 'html') return;
301
311
  const attributes = attributesOf(node);
@@ -365,6 +375,7 @@ function classifyHost(node, scope, namespace, source, filename, diagnostics, cla
365
375
  type ?? { kind: 'text', display: 'text' },
366
376
  changeAttributes,
367
377
  lastAttribute(attributes, 'value') !== null,
378
+ strong,
368
379
  ),
369
380
  );
370
381
  }
@@ -377,41 +388,15 @@ function classifyHost(node, scope, namespace, source, filename, diagnostics, cla
377
388
  export function analyzeNativeChangeDiagnostics(ast, source, filename, options = {}) {
378
389
  // The parser permits trivia after `<` and Unicode escapes in JSX names. A
379
390
  // decoded lowercase input/textarea must still leave either its literal name
380
- // or a backslash in the paired authored source. False positives only retain
391
+ // or a Unicode escape in the paired authored source. False positives only retain
381
392
  // the existing analysis for unrelated identifiers, comments, and strings.
382
- if (!source.includes('input') && !source.includes('textarea') && !source.includes('\\')) {
393
+ if (!source.includes('input') && !source.includes('textarea') && !source.includes('\\u')) {
383
394
  return { diagnostics: [], classifications: new Map() };
384
395
  }
385
396
  const diagnostics = [];
386
397
  const classifications = new Map();
387
- const ownerRendererId = options.renderer?.id ?? (options.dom === false ? null : 'dom');
388
- let rendererRegions = [];
389
- if (options.rendererBoundaries && Object.keys(options.rendererBoundaries).length > 0) {
390
- rendererRegions = analyzeRendererBoundaries(source, {
391
- filename,
392
- rendererBoundaries: options.rendererBoundaries,
393
- })
394
- .boundaries.map((boundary) => ({
395
- childRenderer: boundary.childRenderer,
396
- range: boundary.region?.range ?? boundary.region?.valueRange,
397
- }))
398
- .filter((region) => region.range !== null);
399
- }
400
- const rendererIsDomAt = (node) => {
401
- let rendererId = ownerRendererId;
402
- let narrowest = Number.POSITIVE_INFINITY;
403
- for (const region of rendererRegions) {
404
- const [start, end] = region.range;
405
- if (start > node.start || node.end > end) continue;
406
- const width = end - start;
407
- if (width < narrowest) {
408
- rendererId = region.childRenderer;
409
- narrowest = width;
410
- }
411
- }
412
- if (rendererId === 'dom') return true;
413
- return options.rendererRegistry?.[rendererId]?.target === 'dom';
414
- };
398
+ const rendererIsDomAt =
399
+ options.rendererRegionResolver ?? createRendererRegionResolver(ast, source, filename, options);
415
400
 
416
401
  const programScope = createScope(null, ast?.body ?? []);
417
402
  const seen = new WeakSet();
@@ -484,7 +469,16 @@ export function analyzeNativeChangeDiagnostics(ast, source, filename, options =
484
469
  const isHost = typeof tag === 'string' && /^[a-z]/.test(tag);
485
470
  const selfNs = isHost ? nsForSelf(tag, parentNs) : parentNs;
486
471
  if (isHost && rendererIsDomAt(node)) {
487
- classifyHost(node, scope, selfNs, source, filename, diagnostics, classifications);
472
+ classifyHost(
473
+ node,
474
+ scope,
475
+ selfNs,
476
+ source,
477
+ filename,
478
+ diagnostics,
479
+ classifications,
480
+ options.strong === true,
481
+ );
488
482
  }
489
483
 
490
484
  for (const attribute of attributesOf(node)) {
@@ -508,5 +502,5 @@ export function analyzeNativeChangeDiagnostics(ast, source, filename, options =
508
502
  }
509
503
 
510
504
  export function formatCompileDiagnostic(diagnostic) {
511
- return `${diagnostic.filename}:${diagnostic.start.line}:${diagnostic.start.column + 1} ${diagnostic.message}`;
505
+ return `${diagnostic.filename}:${diagnostic.start.line}:${diagnostic.start.column + 1} ${diagnostic.severity}: ${diagnostic.message}`;
512
506
  }
@@ -5,9 +5,11 @@ export { NATIVE_SIGNAL_NAME, NATIVE_MEMO_READ } from './native-read-facts.js';
5
5
 
6
6
  const SIGNALS_MODULE = 'octane/signals';
7
7
  const CLIENT_MODULE = 'octane/signals/client';
8
+ const SIGNAL_MODULES = new Set([SIGNALS_MODULE, CLIENT_MODULE, 'octane/signals/server']);
8
9
  const LOCAL_MODULES = new Set([CLIENT_MODULE, 'octane/signals/server']);
9
10
  const HANDLE_TYPES = new Set(['SignalHandle', 'Resource', 'WritableSignal', 'DerivedSignal']);
10
- const SIGNAL_METHODS = new Set(['signal$', 'derived$', 'asyncSignal$']);
11
+ const SIGNAL_METHODS = new Set(['signal$', 'derived$']);
12
+ const SIGNAL_FACTORIES = new Set(['signal$', 'derived$', 'query$', 'createResource']);
11
13
  const LOCAL_HOOKS = new Set(['useSignal$']);
12
14
  const FUNCTION_TYPES = new Set([
13
15
  'ArrowFunctionExpression',
@@ -189,8 +191,12 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
189
191
  }
190
192
  }
191
193
  function importedValue(module, name) {
194
+ if (module === 'octane/behavior' && (name === 'adoptBindings' || name === 'mountBindings'))
195
+ return { kind: 'builtin', name, bindingSource: true };
192
196
  if (module === SIGNALS_MODULE && name === 'createScope')
193
197
  return { kind: 'builtin', name: 'createScope' };
198
+ if (SIGNAL_MODULES.has(module) && SIGNAL_FACTORIES.has(name))
199
+ return { kind: 'builtin', name, capability: true };
194
200
  if (LOCAL_MODULES.has(module) && name === 'useDerived$')
195
201
  return { kind: 'unsupportedHook', name };
196
202
  if (LOCAL_MODULES.has(module) && LOCAL_HOOKS.has(name))
@@ -382,7 +388,12 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
382
388
  if (callee.kind === 'function') return callee.result;
383
389
  if (callee.kind === 'builtin') {
384
390
  if (callee.name === 'createScope') return SCOPE;
385
- if (SIGNAL_METHODS.has(callee.name) || LOCAL_HOOKS.has(callee.name)) return HANDLE;
391
+ if (
392
+ SIGNAL_METHODS.has(callee.name) ||
393
+ SIGNAL_FACTORIES.has(callee.name) ||
394
+ LOCAL_HOOKS.has(callee.name)
395
+ )
396
+ return HANDLE;
386
397
  }
387
398
  return UNKNOWN;
388
399
  }
@@ -464,6 +475,16 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
464
475
  }
465
476
  }
466
477
  } while (changed);
478
+ // BindingSource uses a fixed protocol key even when its snapshot carries
479
+ // native handles. Exempt only callbacks reached through the source argument
480
+ // of a directly imported bindings API, never arbitrary getSnapshot methods.
481
+ const bindingSnapshots = new WeakSet();
482
+ for (const node of nodes) {
483
+ if (node.type !== 'CallExpression' && node.type !== 'OptionalCallExpression') continue;
484
+ if (recordFor(unwrap(node.callee))?.forced?.bindingSource !== true) continue;
485
+ const snapshot = memberValue(valueOf(node.arguments[2]), 'getSnapshot');
486
+ if (snapshot.kind === 'function') bindingSnapshots.add(snapshot.fn);
487
+ }
467
488
  function containsHandle(value, active = new Set()) {
468
489
  if (value.kind === 'handle') return true;
469
490
  if (value.kind !== 'object' || active.has(value)) return false;
@@ -510,9 +531,45 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
510
531
  `Native signal handles and functions exposing handles or live reads must end in $. Rename ${JSON.stringify(name)} to ${JSON.stringify(name + '$')}; sampled values keep ordinary names.`,
511
532
  );
512
533
  }
534
+ function createsCapability(node, value) {
535
+ node = unwrap(node);
536
+ if (!node || !isCapability(value)) return false;
537
+ return (
538
+ node.type === 'CallExpression' ||
539
+ node.type === 'OptionalCallExpression' ||
540
+ FUNCTION_TYPES.has(node.type)
541
+ );
542
+ }
543
+ function isDomStyleProperty(node) {
544
+ let object = parents.get(node);
545
+ let container = parents.get(object);
546
+ while (
547
+ container &&
548
+ (WRAPPERS.has(container.type) ||
549
+ (container.type === 'LogicalExpression' &&
550
+ (container.operator !== '&&' || container.right === object)) ||
551
+ (container.type === 'ConditionalExpression' &&
552
+ (container.consequent === object || container.alternate === object)))
553
+ ) {
554
+ object = container;
555
+ container = parents.get(object);
556
+ }
557
+ if (container?.type !== 'JSXExpressionContainer') return false;
558
+ const attribute = parents.get(container);
559
+ if (attribute?.type !== 'JSXAttribute' || attribute.name?.name !== 'style') return false;
560
+ const opening = parents.get(attribute);
561
+ const tag = opening?.name;
562
+ return tag?.type === 'JSXIdentifier' && /^[a-z]/.test(tag.name);
563
+ }
513
564
  for (const record of allRecords) {
514
565
  const value = recordValue(record);
515
- for (const declaration of record.declarations) checkName(declaration, record.name, value);
566
+ if (
567
+ record.expressions.some(({ expression }) =>
568
+ createsCapability(expression, valueOf(expression)),
569
+ )
570
+ ) {
571
+ for (const declaration of record.declarations) checkName(declaration, record.name, value);
572
+ }
516
573
  }
517
574
  for (const node of nodes) {
518
575
  if (
@@ -526,13 +583,17 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
526
583
  'useDerived$ is not available. Create derived$ on an explicitly owned Scope and pass the handle to the component.',
527
584
  );
528
585
  } else if (node.type === 'Property' && parents.get(node)?.type === 'ObjectExpression') {
529
- checkName(node.key, propertyName(node.key, node.computed), valueOf(node.value));
586
+ const value = valueOf(node.value);
587
+ const bindingSnapshot =
588
+ propertyName(node.key, node.computed) === 'getSnapshot' &&
589
+ bindingSnapshots.has(unwrap(node.value));
590
+ if (!isDomStyleProperty(node) && !bindingSnapshot && createsCapability(node.value, value))
591
+ checkName(node.key, propertyName(node.key, node.computed), value);
530
592
  } else if (node.type === 'AssignmentExpression' && node.operator === '=') {
531
593
  const left = unwrap(node.left);
532
- if (left?.type === 'MemberExpression')
533
- checkName(left.property, propertyName(left.property, left.computed), valueOf(node.right));
534
- } else if (node.type === 'ExportSpecifier' && parents.get(node)?.source == null) {
535
- checkName(node.exported, node.exported.name ?? node.exported.value, valueOf(node.local));
594
+ const value = valueOf(node.right);
595
+ if (left?.type === 'MemberExpression' && createsCapability(node.right, value))
596
+ checkName(left.property, propertyName(left.property, left.computed), value);
536
597
  } else if (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') {
537
598
  const callee = valueOf(node.callee);
538
599
  if (callee.kind === 'builtin' && callee.name === 'useMemo') {
@@ -57,6 +57,22 @@ export function validateNativeSignalNames(program, file) {
57
57
  if (property) recordSignatures(symbolType(property), nativeReadSignatures);
58
58
  }
59
59
  }
60
+ function recordFactory(exports, name) {
61
+ const factory = exports.get(name);
62
+ const type = factory && symbolType(factory);
63
+ if (!type) return;
64
+ for (const signature of checker.getSignaturesOfType(type, ts.SignatureKind.Call)) {
65
+ const returned = checker.getReturnTypeOfSignature(signature);
66
+ recordReads(returned, ['get', 'latest', 'snapshot']);
67
+ for (const property of checker.getPropertiesOfType(returned)) {
68
+ for (const declaration of property.declarations ?? []) {
69
+ if (!declaration.name || !ts.isComputedPropertyName(declaration.name)) continue;
70
+ const marker = canonical(checker.getSymbolAtLocation(declaration.name.expression));
71
+ if (marker?.name === 'SIGNAL_HANDLE') brands.add(marker);
72
+ }
73
+ }
74
+ }
75
+ }
60
76
  function inspectModule(symbol, request) {
61
77
  symbol = canonical(symbol);
62
78
  if (!symbol || inspectedModules.has(symbol)) return;
@@ -77,24 +93,14 @@ export function validateNativeSignalNames(program, file) {
77
93
  if (type)
78
94
  for (const signature of checker.getSignaturesOfType(type, ts.SignatureKind.Call))
79
95
  recordReads(checker.getReturnTypeOfSignature(signature), ['get']);
96
+ for (const name of ['signal$', 'derived$', 'query$']) recordFactory(exports, name);
80
97
  } else if (NATIVE_MODULES.has(request)) {
81
98
  // A project may import only the optional local hook entry. Follow the
82
99
  // trusted export's actual return type to the same nominal declaration;
83
100
  // do not require a redundant bare-engine import or brand lookalikes.
84
- const hook = exports.get('useSignal$');
85
- const type = hook && symbolType(hook);
86
- if (type)
87
- for (const signature of checker.getSignaturesOfType(type, ts.SignatureKind.Call)) {
88
- const returned = checker.getReturnTypeOfSignature(signature);
89
- recordReads(returned, ['get', 'latest', 'snapshot']);
90
- for (const property of checker.getPropertiesOfType(returned)) {
91
- for (const declaration of property.declarations ?? []) {
92
- if (!declaration.name || !ts.isComputedPropertyName(declaration.name)) continue;
93
- const marker = canonical(checker.getSymbolAtLocation(declaration.name.expression));
94
- if (marker?.name === 'SIGNAL_HANDLE') brands.add(marker);
95
- }
96
- }
97
- }
101
+ for (const name of ['useSignal$', 'signal$', 'derived$', 'query$']) {
102
+ recordFactory(exports, name);
103
+ }
98
104
  } else if (request === 'octane') {
99
105
  const memo = exports.get('useMemo');
100
106
  if (memo) recordSignatures(symbolType(memo), memoSignatures);
@@ -265,26 +271,64 @@ export function validateNativeSignalNames(program, file) {
265
271
  `Native signal handles and functions exposing handles or live reads must end in $. Rename ${JSON.stringify(text)} to ${JSON.stringify(text + '$')}; sampled values keep ordinary names.`,
266
272
  );
267
273
  }
268
- function valueExport(node) {
269
- if (node.isTypeOnly || node.parent.parent.isTypeOnly) return false;
270
- const symbol = canonical(checker.getSymbolAtLocation(node.name));
271
- return symbol && (symbol.flags & ts.SymbolFlags.Value) !== 0;
274
+ function unwrapExpression(node) {
275
+ while (
276
+ node &&
277
+ (ts.isParenthesizedExpression(node) ||
278
+ ts.isAsExpression(node) ||
279
+ ts.isTypeAssertionExpression(node) ||
280
+ ts.isNonNullExpression(node) ||
281
+ ts.isSatisfiesExpression(node))
282
+ ) {
283
+ node = node.expression;
284
+ }
285
+ return node;
286
+ }
287
+ function createsCapability(node) {
288
+ node = unwrapExpression(node);
289
+ if (!node) return false;
290
+ if (ts.isCallExpression(node)) return exposesHandle(checker.getTypeAtLocation(node));
291
+ return (
292
+ (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&
293
+ (exposesHandle(checker.getTypeAtLocation(node)) || exposesLiveRead(node))
294
+ );
295
+ }
296
+ function isDomStyleProperty(node) {
297
+ let object = node.parent;
298
+ if (!ts.isObjectLiteralExpression(object)) return false;
299
+ while (
300
+ object.parent &&
301
+ (ts.isParenthesizedExpression(object.parent) ||
302
+ ts.isAsExpression(object.parent) ||
303
+ ts.isSatisfiesExpression(object.parent) ||
304
+ ts.isNonNullExpression(object.parent) ||
305
+ (ts.isBinaryExpression(object.parent) &&
306
+ (object.parent.operatorToken.kind === ts.SyntaxKind.BarBarToken ||
307
+ object.parent.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken ||
308
+ (object.parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&
309
+ object.parent.right === object))) ||
310
+ (ts.isConditionalExpression(object.parent) &&
311
+ (object.parent.whenTrue === object || object.parent.whenFalse === object)))
312
+ )
313
+ object = object.parent;
314
+ const container = object.parent;
315
+ if (!container || !ts.isJsxExpression(container)) return false;
316
+ const attribute = container.parent;
317
+ if (!ts.isJsxAttribute(attribute) || attribute.name.getText(sourceFile) !== 'style')
318
+ return false;
319
+ const tag = attribute.parent.parent.tagName;
320
+ return ts.isIdentifier(tag) && /^[a-z]/.test(tag.text);
272
321
  }
273
322
  function visit(node) {
274
- if (ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node)) {
275
- checkName(node.name);
276
- } else if (ts.isImportSpecifier(node)) {
277
- if (!node.isTypeOnly && !node.parent.parent.isTypeOnly) checkName(node.name);
278
- } else if (ts.isExportSpecifier(node)) {
279
- if (valueExport(node)) checkName(node.name, node.propertyName ?? node.name);
323
+ if (ts.isVariableDeclaration(node)) {
324
+ if (node.initializer && createsCapability(node.initializer))
325
+ checkName(node.name, node.initializer);
280
326
  } else if (ts.isPropertyAssignment(node)) {
281
- checkName(node.name, node.initializer);
282
- } else if (
283
- ts.isShorthandPropertyAssignment(node) ||
284
- ts.isPropertyDeclaration(node) ||
285
- ts.isPropertySignature(node)
286
- ) {
287
- checkName(node.name);
327
+ if (!isDomStyleProperty(node) && createsCapability(node.initializer))
328
+ checkName(node.name, node.initializer);
329
+ } else if (ts.isPropertyDeclaration(node)) {
330
+ if (node.initializer && createsCapability(node.initializer))
331
+ checkName(node.name, node.initializer);
288
332
  } else if (
289
333
  ts.isFunctionDeclaration(node) ||
290
334
  ts.isMethodDeclaration(node) ||
@@ -295,9 +339,11 @@ export function validateNativeSignalNames(program, file) {
295
339
  ts.isBinaryExpression(node) &&
296
340
  node.operatorToken.kind === ts.SyntaxKind.EqualsToken
297
341
  ) {
298
- if (ts.isPropertyAccessExpression(node.left)) checkName(node.left.name, node.right);
299
- else if (ts.isElementAccessExpression(node.left))
300
- checkName(node.left.argumentExpression, node.right);
342
+ if (createsCapability(node.right)) {
343
+ if (ts.isPropertyAccessExpression(node.left)) checkName(node.left.name, node.right);
344
+ else if (ts.isElementAccessExpression(node.left))
345
+ checkName(node.left.argumentExpression, node.right);
346
+ }
301
347
  } else if (ts.isCallExpression(node)) {
302
348
  const declaration = checker.getResolvedSignature(node)?.declaration;
303
349
  if (
@@ -1,7 +1,7 @@
1
1
  /** @import { ParseOptions } from '@tsrx/core/types' */
2
2
  /** @import { Program } from 'estree' */
3
3
 
4
- import { parseModule as parseNativeModule } from 'oxc-tsrx/tsrx-core-compat';
4
+ import { parseModule as parseNativeModule } from '@tsrx/oxc/tsrx-core-compat';
5
5
  import { parseModule as parseJavaScriptModule } from './parser.browser.js';
6
6
 
7
7
  import { isolateOutputOptions, publishOutput } from './parser-output.js';
@@ -8,6 +8,46 @@
8
8
  */
9
9
  import { parseModule } from '@tsrx/core';
10
10
 
11
+ /**
12
+ * Share renderer ownership across diagnostics for one adopted parser tree.
13
+ * The lazy proof lives only for that analysis and never reparses editor input.
14
+ */
15
+ export function createRendererRegionResolver(ast, source, filename, options = {}) {
16
+ const owner = options.renderer?.id ?? (options.dom === false ? null : 'dom');
17
+ let regions;
18
+ return function isDOM(node) {
19
+ let renderer = owner;
20
+ if (regions === undefined) {
21
+ regions =
22
+ options.rendererBoundaries && Object.keys(options.rendererBoundaries).length > 0
23
+ ? analyzeRendererBoundaries(source, {
24
+ ast,
25
+ filename,
26
+ rendererBoundaries: options.rendererBoundaries,
27
+ }).boundaries
28
+ : [];
29
+ }
30
+ let narrowest = Infinity;
31
+ for (const boundary of regions) {
32
+ const range = boundary.region?.range ?? boundary.region?.valueRange;
33
+ if (
34
+ !range ||
35
+ node.start < range[0] ||
36
+ node.end > range[1] ||
37
+ range[1] - range[0] >= narrowest
38
+ )
39
+ continue;
40
+ renderer = boundary.childRenderer;
41
+ narrowest = range[1] - range[0];
42
+ }
43
+ return (
44
+ renderer === 'dom' ||
45
+ options.rendererRegistry?.[renderer]?.target === 'dom' ||
46
+ (renderer === options.renderer?.id && options.renderer?.target === 'dom')
47
+ );
48
+ };
49
+ }
50
+
11
51
  const TRANSPARENT_TS_EXPRESSIONS = new Set([
12
52
  'ParenthesizedExpression',
13
53
  'TSAsExpression',