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.
- package/README.md +5 -0
- package/dist/behavior-root.d.ts +13 -3
- package/dist/behavior-root.js +28 -5
- package/dist/cjs/behavior-root.cjs +32 -6
- package/dist/cjs/class-names.cjs +49 -0
- package/dist/cjs/constants.cjs +9 -0
- package/dist/cjs/context-epoch.cjs +38 -0
- package/dist/cjs/css.cjs +6 -27
- package/dist/cjs/data-encoding.cjs +160 -0
- package/dist/cjs/dom-binding-handoff.cjs +93 -0
- package/dist/cjs/dom-binding-protocol.cjs +115 -0
- package/dist/cjs/dom-binding-styles.cjs +292 -0
- package/dist/cjs/dom-bindings.cjs +700 -0
- package/dist/cjs/dom-focused-move.cjs +66 -0
- package/dist/cjs/dom-stage.cjs +896 -0
- package/dist/cjs/dom-tables.cjs +4 -94
- package/dist/cjs/error-codes.client.generated.cjs +33 -0
- package/dist/cjs/error-codes.server.generated.cjs +24 -0
- package/dist/cjs/hydration/control-capture.cjs +244 -0
- package/dist/cjs/hydration/event-capture.cjs +125 -43
- package/dist/cjs/hydration/interaction-config.cjs +50 -2
- package/dist/cjs/hydration-markers.cjs +37 -0
- package/dist/cjs/independent-hydration-protocol.cjs +106 -0
- package/dist/cjs/index.cjs +9 -0
- package/dist/cjs/runtime.cjs +6397 -1683
- package/dist/cjs/runtime.server.cjs +1465 -213
- package/dist/cjs/server/early-signals.cjs +45 -0
- package/dist/cjs/server/index.cjs +47 -0
- package/dist/cjs/server/rpc-batch.cjs +149 -0
- package/dist/cjs/server/rpc-stream.cjs +146 -0
- package/dist/cjs/server/rpc.cjs +34 -15
- package/dist/cjs/server/signal-query-observation.cjs +158 -0
- package/dist/cjs/server/streamed-region.cjs +52 -0
- package/dist/cjs/server/streamed-signals.cjs +512 -0
- package/dist/cjs/server-call.cjs +103 -0
- package/dist/cjs/server-rpc-batch-client.cjs +141 -0
- package/dist/cjs/server-rpc-client.cjs +94 -27
- package/dist/cjs/server-rpc-protocol.cjs +141 -0
- package/dist/cjs/server-rpc-stream-client.cjs +118 -0
- package/dist/cjs/signals/actions.cjs +474 -0
- package/dist/cjs/signals/client.cjs +4 -1
- package/dist/cjs/signals/computations.cjs +460 -0
- package/dist/cjs/signals/control-binding.cjs +368 -0
- package/dist/cjs/signals/derived-facade.cjs +50 -0
- package/dist/cjs/signals/document-owner.cjs +71 -0
- package/dist/cjs/signals/early-values.cjs +155 -0
- package/dist/cjs/signals/encoding.cjs +6 -95
- package/dist/cjs/signals/engine.cjs +276 -12
- package/dist/cjs/signals/errors.cjs +18 -0
- package/dist/cjs/signals/facade.cjs +477 -0
- package/dist/cjs/signals/graph.cjs +166 -20
- package/dist/cjs/signals/handle-protocol.cjs +36 -0
- package/dist/cjs/signals/index.cjs +81 -2
- package/dist/cjs/signals/native-read-client.cjs +10 -0
- package/dist/cjs/signals/owner-context.cjs +112 -0
- package/dist/cjs/signals/query-attempt-observer.cjs +68 -0
- package/dist/cjs/signals/query-facade.cjs +72 -0
- package/dist/cjs/signals/read-protocol.cjs +54 -0
- package/dist/cjs/signals/requests.cjs +287 -14
- package/dist/cjs/signals/scalar-computations.cjs +76 -0
- package/dist/cjs/signals/server.cjs +4 -1
- package/dist/cjs/signals/transition-candidate.cjs +567 -0
- package/dist/cjs/signals/transition-state.cjs +106 -0
- package/dist/cjs/signals/types.cjs +50 -0
- package/dist/cjs/stream-protocol.cjs +3 -0
- package/dist/cjs/streamed-signals-protocol.cjs +159 -0
- package/dist/cjs/style-values.cjs +122 -0
- package/dist/cjs/trusted-html.cjs +30 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/class-names.d.ts +5 -0
- package/dist/class-names.js +25 -0
- package/dist/compiler/bundler.js +51 -22
- package/dist/compiler/compile.js +2534 -581
- package/dist/compiler/dom-binding-program.js +935 -0
- package/dist/compiler/dom-binding-request.js +57 -0
- package/dist/compiler/dom-bindings.js +2245 -0
- package/dist/compiler/hook-deps.js +759 -10
- package/dist/compiler/hydrate-boundaries.js +358 -15
- package/dist/compiler/index.d.ts +39 -2
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/native-attribute-reads.js +127 -0
- package/dist/compiler/native-change-diagnostics.js +32 -38
- package/dist/compiler/native-read-diagnostics.js +69 -8
- package/dist/compiler/native-read-types.js +80 -34
- package/dist/compiler/parser.node.js +1 -1
- package/dist/compiler/renderer-boundaries.js +40 -0
- package/dist/compiler/server-context.js +118 -0
- package/dist/compiler/signal-attempt-reads.js +479 -0
- package/dist/compiler/signal-declarations.js +557 -0
- package/dist/compiler/signal-start-reads.js +413 -0
- package/dist/compiler/slot-hooks.d.ts +3 -1
- package/dist/compiler/slot-hooks.js +168 -34
- package/dist/compiler/strong-auto-memo.js +90 -0
- package/dist/compiler/strong-html.js +143 -0
- package/dist/compiler/strong-mode.js +764 -66
- package/dist/compiler/strong-template-policy.js +307 -0
- package/dist/compiler/template-ir.js +200 -0
- package/dist/compiler/typescript.d.ts +2 -0
- package/dist/compiler/typescript.js +2 -0
- package/dist/compiler/vite.d.ts +3 -1
- package/dist/compiler/vite.js +122 -4
- package/dist/compiler/volar.LICENSES.txt +1 -1
- package/dist/compiler/volar.d.ts +6 -1
- package/dist/compiler/volar.js +750 -1433
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +10 -0
- package/dist/context-epoch.d.ts +2 -0
- package/dist/context-epoch.js +13 -0
- package/dist/css.d.ts +2 -5
- package/dist/css.js +2 -23
- package/dist/data-encoding.d.ts +9 -0
- package/dist/data-encoding.js +134 -0
- package/dist/dom-binding-classes.d.ts +15 -0
- package/dist/dom-binding-classes.js +129 -0
- package/dist/dom-binding-controls.d.ts +2 -0
- package/dist/dom-binding-controls.js +6 -0
- package/dist/dom-binding-handoff.d.ts +55 -0
- package/dist/dom-binding-handoff.js +62 -0
- package/dist/dom-binding-program.d.ts +300 -0
- package/dist/dom-binding-program.js +1625 -0
- package/dist/dom-binding-projections.d.ts +15 -0
- package/dist/dom-binding-projections.js +187 -0
- package/dist/dom-binding-protocol.d.ts +13 -0
- package/dist/dom-binding-protocol.js +88 -0
- package/dist/dom-binding-signals.d.ts +21 -0
- package/dist/dom-binding-signals.js +109 -0
- package/dist/dom-binding-styles.d.ts +17 -0
- package/dist/dom-binding-styles.js +272 -0
- package/dist/dom-bindings.d.ts +89 -0
- package/dist/dom-bindings.js +670 -0
- package/dist/dom-focused-move.d.ts +6 -0
- package/dist/dom-focused-move.js +42 -0
- package/dist/dom-stage.d.ts +52 -0
- package/dist/dom-stage.js +872 -0
- package/dist/dom-tables.d.ts +1 -3
- package/dist/dom-tables.js +1 -91
- package/dist/error-codes.client.generated.d.ts +9 -0
- package/dist/error-codes.client.generated.js +33 -0
- package/dist/error-codes.server.generated.d.ts +6 -0
- package/dist/error-codes.server.generated.js +24 -0
- package/dist/hydration/control-capture.d.ts +62 -0
- package/dist/hydration/control-capture.js +223 -0
- package/dist/hydration/document-lifecycle.d.ts +23 -0
- package/dist/hydration/document-lifecycle.js +80 -0
- package/dist/hydration/event-capture.d.ts +20 -1
- package/dist/hydration/event-capture.js +128 -42
- package/dist/hydration/independent-island.d.ts +45 -0
- package/dist/hydration/independent-island.js +233 -0
- package/dist/hydration/index.d.ts +8 -1
- package/dist/hydration/index.js +55 -1
- package/dist/hydration/interaction-config.d.ts +5 -0
- package/dist/hydration/interaction-config.js +44 -1
- package/dist/hydration/stream-delivery.d.ts +32 -0
- package/dist/hydration/stream-delivery.js +333 -0
- package/dist/hydration/stream-receiver.d.ts +26 -0
- package/dist/hydration/stream-receiver.js +204 -0
- package/dist/hydration/stream-result-receiver.d.ts +70 -0
- package/dist/hydration/stream-result-receiver.js +246 -0
- package/dist/hydration/streamed-signals.d.ts +44 -0
- package/dist/hydration/streamed-signals.js +186 -0
- package/dist/hydration/types.d.ts +6 -0
- package/dist/hydration-markers.d.ts +7 -0
- package/dist/hydration-markers.js +10 -0
- package/dist/independent-hydration-protocol.d.ts +31 -0
- package/dist/independent-hydration-protocol.js +80 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/internal/signal-read.d.ts +1 -0
- package/dist/internal/signal-read.js +4 -0
- package/dist/jsx-runtime-strong.d.ts +33 -0
- package/dist/jsx-runtime.d.ts +58 -2
- package/dist/public-types.d.ts +15 -1
- package/dist/runtime.d.ts +297 -23
- package/dist/runtime.js +6405 -1679
- package/dist/runtime.server.d.ts +110 -27
- package/dist/runtime.server.js +1458 -213
- package/dist/server/early-signals.d.ts +14 -0
- package/dist/server/early-signals.js +34 -0
- package/dist/server/index.d.ts +11 -2
- package/dist/server/index.js +50 -1
- package/dist/server/rpc-batch.d.ts +17 -0
- package/dist/server/rpc-batch.js +129 -0
- package/dist/server/rpc-stream.d.ts +9 -0
- package/dist/server/rpc-stream.js +125 -0
- package/dist/server/rpc.d.ts +5 -1
- package/dist/server/rpc.js +37 -13
- package/dist/server/signal-query-observation.d.ts +3 -0
- package/dist/server/signal-query-observation.js +134 -0
- package/dist/server/streamed-region.d.ts +15 -0
- package/dist/server/streamed-region.js +30 -0
- package/dist/server/streamed-signals.d.ts +45 -0
- package/dist/server/streamed-signals.js +488 -0
- package/dist/server-call.d.ts +41 -0
- package/dist/server-call.js +75 -0
- package/dist/server-rpc-batch-client.d.ts +7 -0
- package/dist/server-rpc-batch-client.js +119 -0
- package/dist/server-rpc-client.d.ts +3 -1
- package/dist/server-rpc-client.js +99 -27
- package/dist/server-rpc-protocol.d.ts +47 -0
- package/dist/server-rpc-protocol.js +109 -0
- package/dist/server-rpc-stream-client.d.ts +5 -0
- package/dist/server-rpc-stream-client.js +98 -0
- package/dist/signals/actions.d.ts +12 -0
- package/dist/signals/actions.js +466 -0
- package/dist/signals/client.d.ts +2 -0
- package/dist/signals/client.js +1 -0
- package/dist/signals/computations.d.ts +37 -0
- package/dist/signals/computations.js +455 -0
- package/dist/signals/control-binding.d.ts +31 -0
- package/dist/signals/control-binding.js +353 -0
- package/dist/signals/derived-facade.d.ts +3 -0
- package/dist/signals/derived-facade.js +25 -0
- package/dist/signals/document-owner.d.ts +10 -0
- package/dist/signals/document-owner.js +43 -0
- package/dist/signals/early-values.d.ts +24 -0
- package/dist/signals/early-values.js +124 -0
- package/dist/signals/encoding.d.ts +1 -3
- package/dist/signals/encoding.js +3 -93
- package/dist/signals/engine.d.ts +53 -4
- package/dist/signals/engine.js +275 -16
- package/dist/signals/errors.d.ts +7 -0
- package/dist/signals/errors.js +16 -0
- package/dist/signals/facade.d.ts +74 -0
- package/dist/signals/facade.js +452 -0
- package/dist/signals/graph.d.ts +52 -6
- package/dist/signals/graph.js +174 -19
- package/dist/signals/handle-protocol.d.ts +4 -0
- package/dist/signals/handle-protocol.js +11 -0
- package/dist/signals/index.d.ts +12 -3
- package/dist/signals/index.js +71 -2
- package/dist/signals/native-read-client.d.ts +3 -0
- package/dist/signals/native-read-client.js +10 -0
- package/dist/signals/owner-context.d.ts +22 -0
- package/dist/signals/owner-context.js +77 -0
- package/dist/signals/query-attempt-observer.d.ts +49 -0
- package/dist/signals/query-attempt-observer.js +41 -0
- package/dist/signals/query-facade.d.ts +3 -0
- package/dist/signals/query-facade.js +49 -0
- package/dist/signals/read-protocol.d.ts +37 -2
- package/dist/signals/read-protocol.js +44 -0
- package/dist/signals/requests.d.ts +38 -5
- package/dist/signals/requests.js +298 -15
- package/dist/signals/scalar-computations.d.ts +2 -0
- package/dist/signals/scalar-computations.js +60 -0
- package/dist/signals/server.d.ts +2 -0
- package/dist/signals/server.js +1 -0
- package/dist/signals/transition-candidate.d.ts +71 -0
- package/dist/signals/transition-candidate.js +557 -0
- package/dist/signals/transition-state.d.ts +51 -0
- package/dist/signals/transition-state.js +70 -0
- package/dist/signals/types.d.ts +95 -5
- package/dist/signals/types.js +20 -0
- package/dist/stream-protocol.d.ts +0 -5
- package/dist/stream-protocol.js +3 -0
- package/dist/streamed-signals-protocol.d.ts +60 -0
- package/dist/streamed-signals-protocol.js +130 -0
- package/dist/style-values.d.ts +3 -0
- package/dist/style-values.js +96 -0
- package/dist/trusted-html.d.ts +17 -0
- package/dist/trusted-html.js +6 -0
- package/dist/universal-core.d.ts +6 -0
- package/dist/universal-core.js +214 -202
- package/dist/version.js +1 -1
- package/package.json +48 -5
|
@@ -17,14 +17,16 @@ import { parseModule, builders as b } from '@tsrx/core';
|
|
|
17
17
|
import { parseModule as parseFallbackModule } from '#octane/compiler-parser';
|
|
18
18
|
import { HOOK_NAMES, hookSlotHash } from './compile.js';
|
|
19
19
|
import { NATIVE_SIGNAL_HOOK_NAMES } from './hook-names.js';
|
|
20
|
-
import { METHOD_DEP_IMPORT, annotateHookCalls } from './hook-deps.js';
|
|
20
|
+
import { METHOD_DEP_IMPORT, annotateHookCalls, analyzeStrongMemoCandidates } from './hook-deps.js';
|
|
21
21
|
import { inlinePlainHookMemos } from './plain-hook-memo.js';
|
|
22
22
|
import { assertStrongMode } from './strong-mode.js';
|
|
23
|
+
import { unsupportedStrongAutomaticMemo } from './strong-auto-memo.js';
|
|
23
24
|
import { assertNativeReadDiagnostics, nativeReadOptions } from './native-read-diagnostics.js';
|
|
24
25
|
import { nativeReadActivationIndex } from './native-read-codegen.js';
|
|
25
26
|
import { findManualHookProviders, manualHookWrapperParameters } from './manual-hooks.js';
|
|
26
27
|
import { findLeadingJsxImportSourcePragma } from './pragma.js';
|
|
27
28
|
import { collectProvenContextBindings, isProvenContextUse } from './context-use.js';
|
|
29
|
+
import { signalDeclarationSourceEdits } from './signal-declarations.js';
|
|
28
30
|
import {
|
|
29
31
|
hookMethodName,
|
|
30
32
|
hasHookMethods,
|
|
@@ -1109,6 +1111,43 @@ function emitHookMethodChain(node, owner, st) {
|
|
|
1109
1111
|
return true;
|
|
1110
1112
|
}
|
|
1111
1113
|
|
|
1114
|
+
function strongCallSeparator(node, st) {
|
|
1115
|
+
// Parser expression ranges omit grouping parentheses. Append at the CALL
|
|
1116
|
+
// delimiter so a new slot never becomes part of an authored comma expression.
|
|
1117
|
+
const tail = st.source
|
|
1118
|
+
.slice(node.arguments.at(-1)?.end ?? node.start, node.end - 1)
|
|
1119
|
+
.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, '')
|
|
1120
|
+
.trimEnd();
|
|
1121
|
+
return tail.endsWith(',') ? ' ' : ', ';
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function emitInferredDependencies(dependencies, st) {
|
|
1125
|
+
return dependencies
|
|
1126
|
+
.map((dependency) =>
|
|
1127
|
+
dependency.method
|
|
1128
|
+
? `${requireParallelHelper(st, METHOD_DEP_IMPORT)}(${dependency.method.root.name}, ${JSON.stringify(dependency.method.name)})`
|
|
1129
|
+
: st.source.slice(dependency.node.start, dependency.node.end),
|
|
1130
|
+
)
|
|
1131
|
+
.join(', ');
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function emitStrongMemo(node, owner, st) {
|
|
1135
|
+
if (node.type !== 'VariableDeclarator') return;
|
|
1136
|
+
const memo = st.strongMemos?.get(node.start);
|
|
1137
|
+
if (!memo) return;
|
|
1138
|
+
const slot = allocHookSymbol(st, owner, node.id.name, memo.hook, node.init);
|
|
1139
|
+
const helper = requireParallelHelper(
|
|
1140
|
+
st,
|
|
1141
|
+
st.nativeReads && memo.hook === 'useMemo' ? 'nativePuMemo' : memo.hook,
|
|
1142
|
+
);
|
|
1143
|
+
const dependencyText = emitInferredDependencies(memo.dependencies, st);
|
|
1144
|
+
st.edits.push({
|
|
1145
|
+
pos: node.init.start,
|
|
1146
|
+
text: `${helper}(${memo.hook === 'useMemo' ? '() => ' : ''}(`,
|
|
1147
|
+
});
|
|
1148
|
+
st.edits.push({ pos: node.init.end, text: `), [${dependencyText}], ${slot})` });
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1112
1151
|
function walk(node, owner, st) {
|
|
1113
1152
|
if (!node || typeof node !== 'object') return;
|
|
1114
1153
|
if (Array.isArray(node)) {
|
|
@@ -1116,6 +1155,8 @@ function walk(node, owner, st) {
|
|
|
1116
1155
|
return;
|
|
1117
1156
|
}
|
|
1118
1157
|
|
|
1158
|
+
emitStrongMemo(node, owner, st);
|
|
1159
|
+
|
|
1119
1160
|
// A `const x = (…) => …` / `function …` gives the enclosing name used in the
|
|
1120
1161
|
// (debug-only) slot key. For a named declaration the id is on the node; for an
|
|
1121
1162
|
// arrow/expr assigned to a `const`, take the declarator name.
|
|
@@ -1195,17 +1236,17 @@ function walk(node, owner, st) {
|
|
|
1195
1236
|
// dependencies are the one synthesized form: the helper call's root is
|
|
1196
1237
|
// a bare identifier and its name a JSON string, so no arbitrary TS
|
|
1197
1238
|
// syntax needs reprinting there either.
|
|
1198
|
-
const deps = inferred.dependencies
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
.
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
}
|
|
1239
|
+
const deps = emitInferredDependencies(inferred.dependencies, st);
|
|
1240
|
+
if (inferred.replaceDependency) {
|
|
1241
|
+
const argument = node.arguments[inferred.depsIndex];
|
|
1242
|
+
st.edits.push({ pos: argument.start, end: argument.end, text: `[${deps}]` });
|
|
1243
|
+
st.edits.push({ pos: node.end - 1, text: `${strongCallSeparator(node, st)}${sym}` });
|
|
1244
|
+
} else {
|
|
1245
|
+
st.edits.push({
|
|
1246
|
+
pos: st.strong ? node.end - 1 : node.arguments[node.arguments.length - 1].end,
|
|
1247
|
+
text: `${st.strong ? strongCallSeparator(node, st) : ', '}[${deps}], ${sym}`,
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1209
1250
|
} else if (node.arguments.length === 0) {
|
|
1210
1251
|
// State/ref initializers may themselves be Symbols. Reserve their
|
|
1211
1252
|
// authored position even when empty; other hooks keep their ABI.
|
|
@@ -1217,8 +1258,8 @@ function walk(node, owner, st) {
|
|
|
1217
1258
|
// `useState(0)` → `useState(0, _h$N)` — insert AFTER the last arg's end so
|
|
1218
1259
|
// trailing commas / whitespace before `)` stay valid.
|
|
1219
1260
|
st.edits.push({
|
|
1220
|
-
pos: node.arguments[node.arguments.length - 1].end,
|
|
1221
|
-
text: ', ' + sym,
|
|
1261
|
+
pos: st.strong ? node.end - 1 : node.arguments[node.arguments.length - 1].end,
|
|
1262
|
+
text: (st.strong ? strongCallSeparator(node, st) : ', ') + sym,
|
|
1222
1263
|
});
|
|
1223
1264
|
}
|
|
1224
1265
|
}
|
|
@@ -1297,7 +1338,7 @@ function parseHookSource(source, id) {
|
|
|
1297
1338
|
* `inlineHookMemo: true` enables the production-client whole-AST memo path;
|
|
1298
1339
|
* the default remains surgical. `manualSlots: true` permits memo and observed
|
|
1299
1340
|
* getter rewrites without injecting or changing the authored slot policy.
|
|
1300
|
-
* @returns {{ code: string, map: any } | null}
|
|
1341
|
+
* @returns {{ code: string, map: any, streamedSignals?: true, diagnostics?: any[] } | null}
|
|
1301
1342
|
*/
|
|
1302
1343
|
export function slotHooks(source, id, options) {
|
|
1303
1344
|
const environment = options?.environment ?? 'client';
|
|
@@ -1314,15 +1355,34 @@ export function slotHooks(source, id, options) {
|
|
|
1314
1355
|
return null; // let the normal pipeline surface the parse error
|
|
1315
1356
|
}
|
|
1316
1357
|
options = nativeReadOptions(ast, options);
|
|
1317
|
-
assertStrongMode(ast, source, id, options);
|
|
1358
|
+
const strongAnalysis = assertStrongMode(ast, source, id, { ...options, onlyImported: true });
|
|
1359
|
+
const strongHints = strongAnalysis?.diagnostics.length
|
|
1360
|
+
? { diagnostics: strongAnalysis.diagnostics }
|
|
1361
|
+
: {};
|
|
1318
1362
|
assertNativeReadDiagnostics(ast, source, id, options);
|
|
1363
|
+
const strongHookAnalysis = strongAnalysis?.strongHookAnalysis ?? null;
|
|
1364
|
+
const strongPlan = strongHookAnalysis
|
|
1365
|
+
? analyzeStrongMemoCandidates(ast, { ...options, onlyImported: true, strongHookAnalysis })
|
|
1366
|
+
: null;
|
|
1367
|
+
if (strongPlan?.candidates.size && options?.manualSlots)
|
|
1368
|
+
throw unsupportedStrongAutomaticMemo(
|
|
1369
|
+
ast.body[0],
|
|
1370
|
+
id,
|
|
1371
|
+
'Strong declaration caching cannot add cache slots to a manually slotted module.',
|
|
1372
|
+
);
|
|
1319
1373
|
const importInfo = octaneHookLocals(
|
|
1320
1374
|
ast,
|
|
1321
1375
|
options?.nativeReads === true,
|
|
1322
|
-
|
|
1376
|
+
// The bundler claims both pragmas for Octane (see pragmaOwnedModules).
|
|
1377
|
+
['octane', 'octane/strong'].includes(findLeadingJsxImportSourcePragma(source)),
|
|
1323
1378
|
);
|
|
1379
|
+
if (strongHookAnalysis?.callNames.size)
|
|
1380
|
+
importInfo.importsHook ||= [...strongHookAnalysis.callNames.values()].some((name) =>
|
|
1381
|
+
HOOK_NAMES.has(name),
|
|
1382
|
+
);
|
|
1324
1383
|
const manualProviders = options?.manualSlots ? findManualHookProviders(ast) : new Map();
|
|
1325
1384
|
const nativeReadActivation = options?.nativeReads === true && importsNativeRenderer(ast);
|
|
1385
|
+
const signalLowering = signalDeclarationSourceEdits(ast, id, source);
|
|
1326
1386
|
const canSpecializeRoot =
|
|
1327
1387
|
!options?.manualSlots &&
|
|
1328
1388
|
!options?.hmr &&
|
|
@@ -1333,9 +1393,10 @@ export function slotHooks(source, id, options) {
|
|
|
1333
1393
|
!importInfo.importsHook &&
|
|
1334
1394
|
!canSpecializeRoot &&
|
|
1335
1395
|
!nativeReadActivation &&
|
|
1396
|
+
!signalLowering.usesSignals &&
|
|
1336
1397
|
!manualProviders.size
|
|
1337
1398
|
) {
|
|
1338
|
-
return null;
|
|
1399
|
+
return strongAnalysis?.diagnostics.length ? { code: source, map: null, ...strongHints } : null;
|
|
1339
1400
|
}
|
|
1340
1401
|
// The parsed tree is never mutated: annotateHookCalls returns a COW-rebuilt
|
|
1341
1402
|
// module whose hook calls carry their `_octane*` props (start/end offsets are
|
|
@@ -1346,6 +1407,7 @@ export function slotHooks(source, id, options) {
|
|
|
1346
1407
|
const annotated = annotateHookCalls(ast, {
|
|
1347
1408
|
filename: id,
|
|
1348
1409
|
onlyImported: true,
|
|
1410
|
+
...(strongHookAnalysis ? { strongHookAnalysis } : {}),
|
|
1349
1411
|
nativeReads: options?.nativeReads === true,
|
|
1350
1412
|
...(options?.nativeReads === true
|
|
1351
1413
|
? { hookRuntimeModules: ['octane/signals/client', 'octane/signals/server'] }
|
|
@@ -1357,6 +1419,9 @@ export function slotHooks(source, id, options) {
|
|
|
1357
1419
|
}
|
|
1358
1420
|
const getterCalls = importInfo.importsHook ? collectStateGetterCalls(ast) : new WeakSet();
|
|
1359
1421
|
if (
|
|
1422
|
+
!strongAnalysis?.enabled &&
|
|
1423
|
+
// The whole-AST memo path does not apply declaration identity edits.
|
|
1424
|
+
!signalLowering.usesSignals &&
|
|
1360
1425
|
canPrint &&
|
|
1361
1426
|
options?.inlineHookMemo === true &&
|
|
1362
1427
|
environment === 'client' &&
|
|
@@ -1380,9 +1445,16 @@ export function slotHooks(source, id, options) {
|
|
|
1380
1445
|
getterCalls,
|
|
1381
1446
|
stateGetterHelpers: STATE_GETTER_HELPERS,
|
|
1382
1447
|
});
|
|
1383
|
-
if (inlined !== null) return inlined;
|
|
1448
|
+
if (inlined !== null) return { ...inlined, ...strongHints };
|
|
1384
1449
|
}
|
|
1385
1450
|
const st = {
|
|
1451
|
+
strong: strongAnalysis?.enabled === true,
|
|
1452
|
+
strongMemos: new Map(
|
|
1453
|
+
[...(strongPlan?.candidates ?? [])].map(([node, hook]) => [
|
|
1454
|
+
node.start,
|
|
1455
|
+
{ hook, dependencies: strongPlan.candidateDependencies.get(node) },
|
|
1456
|
+
]),
|
|
1457
|
+
),
|
|
1386
1458
|
manualSlots: options?.manualSlots === true,
|
|
1387
1459
|
nativeReads: options?.nativeReads === true,
|
|
1388
1460
|
locals: importInfo.locals,
|
|
@@ -1398,7 +1470,7 @@ export function slotHooks(source, id, options) {
|
|
|
1398
1470
|
hash: hookSlotHash(id),
|
|
1399
1471
|
nextId: 0,
|
|
1400
1472
|
nextPuId: 0,
|
|
1401
|
-
edits: [],
|
|
1473
|
+
edits: [...signalLowering.edits],
|
|
1402
1474
|
decls: [],
|
|
1403
1475
|
parallelHelpers: new Map(),
|
|
1404
1476
|
provenContextBindings: collectProvenContextBindings(ast),
|
|
@@ -1418,7 +1490,8 @@ export function slotHooks(source, id, options) {
|
|
|
1418
1490
|
if (canSpecializeRoot) {
|
|
1419
1491
|
collectVoidRootEdits(ast, st, options.isVoidComponentImport);
|
|
1420
1492
|
}
|
|
1421
|
-
if (st.edits.length === 0 && !nativeReadActivation
|
|
1493
|
+
if (st.edits.length === 0 && !nativeReadActivation && !signalLowering.usesSignals)
|
|
1494
|
+
return strongAnalysis?.diagnostics.length ? { code: source, map: null, ...strongHints } : null;
|
|
1422
1495
|
const activation = nativeReadActivation
|
|
1423
1496
|
? requireParallelHelper(st, 'enableNativeReadCollection')
|
|
1424
1497
|
: null;
|
|
@@ -1458,6 +1531,35 @@ export function slotHooks(source, id, options) {
|
|
|
1458
1531
|
const otherHelperImports = [...otherHelpers]
|
|
1459
1532
|
.map(([request, specifiers]) => `import { ${specifiers.join(', ')} } from '${request}';\n`)
|
|
1460
1533
|
.join('');
|
|
1534
|
+
const signalHelperImports = new Map();
|
|
1535
|
+
for (const helper of signalLowering.imports) {
|
|
1536
|
+
let specifiers = signalHelperImports.get(helper.source);
|
|
1537
|
+
if (specifiers === undefined) signalHelperImports.set(helper.source, (specifiers = []));
|
|
1538
|
+
specifiers.push(`${helper.imported} as ${helper.local}`);
|
|
1539
|
+
}
|
|
1540
|
+
let signalActivation = '';
|
|
1541
|
+
if (signalLowering.usesSignals) {
|
|
1542
|
+
const request =
|
|
1543
|
+
environment === 'server'
|
|
1544
|
+
? 'octane/internal/server'
|
|
1545
|
+
: nativeReadActivation
|
|
1546
|
+
? 'octane/internal/client'
|
|
1547
|
+
: 'octane/signals';
|
|
1548
|
+
const imported =
|
|
1549
|
+
environment === 'server'
|
|
1550
|
+
? 'enableServerSignalBindings'
|
|
1551
|
+
: nativeReadActivation
|
|
1552
|
+
? 'enableSignalBindings'
|
|
1553
|
+
: '__enableSignalDocument';
|
|
1554
|
+
const local = allocSlotName(st, `_$${imported}`);
|
|
1555
|
+
let specifiers = signalHelperImports.get(request);
|
|
1556
|
+
if (specifiers === undefined) signalHelperImports.set(request, (specifiers = []));
|
|
1557
|
+
specifiers.push(`${imported} as ${local}`);
|
|
1558
|
+
signalActivation = `${local}(1);\n`;
|
|
1559
|
+
}
|
|
1560
|
+
const signalImports = [...signalHelperImports]
|
|
1561
|
+
.map(([request, specifiers]) => `import { ${specifiers.join(', ')} } from '${request}';\n`)
|
|
1562
|
+
.join('');
|
|
1461
1563
|
const profileImport =
|
|
1462
1564
|
st.profile && !st.manualSlots
|
|
1463
1565
|
? "import { __profileHook as _$__profileHook } from 'octane/profiling';\n"
|
|
@@ -1467,26 +1569,58 @@ export function slotHooks(source, id, options) {
|
|
|
1467
1569
|
? `const ${st.slotBaseName} = /* @__PURE__ */ ${st.hookSlotsName}(${st.nextId});\n`
|
|
1468
1570
|
: '';
|
|
1469
1571
|
const block =
|
|
1470
|
-
helperImport +
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1572
|
+
helperImport +
|
|
1573
|
+
otherHelperImports +
|
|
1574
|
+
signalImports +
|
|
1575
|
+
profileImport +
|
|
1576
|
+
slotBase +
|
|
1577
|
+
st.decls.join('\n') +
|
|
1578
|
+
'\n' +
|
|
1579
|
+
signalActivation;
|
|
1580
|
+
if (activation !== null || signalLowering.usesSignals) {
|
|
1581
|
+
// Plain modules may read global signals or render during evaluation.
|
|
1582
|
+
// Their document capability and any render slots must already exist.
|
|
1474
1583
|
// No newline is inserted, retaining the surgical pass's line mapping.
|
|
1475
1584
|
const index = nativeReadActivationIndex(ast.body);
|
|
1476
1585
|
st.edits.push({
|
|
1477
1586
|
pos: ast.body[index]?.start ?? source.length,
|
|
1478
|
-
text: `;${block.replace(/\n/g, ' ')}${activation}(1); `,
|
|
1587
|
+
text: `;${block.replace(/\n/g, ' ')}${activation === null ? '' : `${activation}(1); `}`,
|
|
1479
1588
|
});
|
|
1480
1589
|
}
|
|
1481
|
-
//
|
|
1590
|
+
// Assemble disjoint edits once instead of copying the growing module for
|
|
1591
|
+
// every insertion. Descending, stable order retains the existing rule that
|
|
1592
|
+
// later insertions at the same offset appear before earlier insertions.
|
|
1482
1593
|
st.edits.sort((a, b) => b.pos - a.pos);
|
|
1483
|
-
|
|
1594
|
+
const chunks = [];
|
|
1595
|
+
let cursor = source.length;
|
|
1596
|
+
let overlaps = false;
|
|
1484
1597
|
for (const edit of st.edits) {
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1598
|
+
const end = edit.end ?? edit.pos;
|
|
1599
|
+
if (end > cursor) {
|
|
1600
|
+
overlaps = true;
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
chunks.push(source.slice(end, cursor), edit.text);
|
|
1604
|
+
cursor = edit.pos;
|
|
1489
1605
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1606
|
+
let code;
|
|
1607
|
+
if (overlaps) {
|
|
1608
|
+
// Overlapping replacements refer to the already edited text. Preserve
|
|
1609
|
+
// that sequential behavior, including insertion/replacement ties.
|
|
1610
|
+
code = source;
|
|
1611
|
+
for (const edit of st.edits) {
|
|
1612
|
+
code = code.slice(0, edit.pos) + edit.text + code.slice(edit.end ?? edit.pos);
|
|
1613
|
+
}
|
|
1614
|
+
} else {
|
|
1615
|
+
chunks.push(source.slice(0, cursor));
|
|
1616
|
+
code = chunks.reverse().join('');
|
|
1617
|
+
}
|
|
1618
|
+
if (activation === null && !signalLowering.usesSignals)
|
|
1619
|
+
code = code.endsWith('\n') ? code + block : code + '\n' + block;
|
|
1620
|
+
return {
|
|
1621
|
+
code,
|
|
1622
|
+
map: null,
|
|
1623
|
+
...(signalLowering.usesSignals || nativeReadActivation ? { streamedSignals: true } : null),
|
|
1624
|
+
...strongHints,
|
|
1625
|
+
};
|
|
1492
1626
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { builders as b } from '@tsrx/core';
|
|
2
|
+
import { inheritHookMemoOrigin as origin } from './inline-hook-memo.js';
|
|
3
|
+
import { analyzeStrongMemoCandidates, STRONG_AUTOMATIC_MEMO_UNSUPPORTED } from './hook-deps.js';
|
|
4
|
+
|
|
5
|
+
const SKIP_KEYS = new Set(['loc', 'start', 'end', 'range', 'metadata', 'parent']);
|
|
6
|
+
|
|
7
|
+
export function unsupportedStrongAutomaticMemo(node, filename, message) {
|
|
8
|
+
const line = node?.loc?.start?.line ?? 1;
|
|
9
|
+
const column = node?.loc?.start?.column ?? 0;
|
|
10
|
+
const error = new SyntaxError(
|
|
11
|
+
`${filename ?? '<anonymous>'}:${line}:${column + 1}: [${STRONG_AUTOMATIC_MEMO_UNSUPPORTED}] ${message}`,
|
|
12
|
+
);
|
|
13
|
+
Object.assign(error, {
|
|
14
|
+
code: STRONG_AUTOMATIC_MEMO_UNSUPPORTED,
|
|
15
|
+
filename,
|
|
16
|
+
loc: { line, column },
|
|
17
|
+
pos: node?.start ?? 0,
|
|
18
|
+
end: node?.end ?? 0,
|
|
19
|
+
});
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Cache eligible Strong declarations before dependency inference. These hooks
|
|
25
|
+
* retain effect/subscription identity in development as well as production;
|
|
26
|
+
* later production passes may inline the same runtime cache semantics.
|
|
27
|
+
* The caller invokes this only after validating authored Strong source.
|
|
28
|
+
*/
|
|
29
|
+
export function applyStrongAutomaticMemo(ast, options = {}) {
|
|
30
|
+
const { candidates, names, hookCalls, omittedDependencies } = analyzeStrongMemoCandidates(
|
|
31
|
+
ast,
|
|
32
|
+
options,
|
|
33
|
+
);
|
|
34
|
+
if (candidates.size === 0 && hookCalls.size === 0 && omittedDependencies.size === 0) return ast;
|
|
35
|
+
const aliases = new Map();
|
|
36
|
+
for (const hook of new Set([...candidates.values(), ...hookCalls.values()])) {
|
|
37
|
+
let alias = `_$strong${hook}`;
|
|
38
|
+
while (names.has(alias)) alias += '_';
|
|
39
|
+
names.add(alias);
|
|
40
|
+
aliases.set(hook, alias);
|
|
41
|
+
}
|
|
42
|
+
function rebuild(node) {
|
|
43
|
+
if (!node || typeof node !== 'object') return node;
|
|
44
|
+
if (Array.isArray(node)) {
|
|
45
|
+
const children = node.map(rebuild);
|
|
46
|
+
return children.some((child, index) => child !== node[index]) ? children : node;
|
|
47
|
+
}
|
|
48
|
+
const importedHook = hookCalls.get(node);
|
|
49
|
+
const omittedDependency = omittedDependencies.has(node);
|
|
50
|
+
if (importedHook || omittedDependency) {
|
|
51
|
+
// Built-in exports are immutable and defined: normalize proven aliases
|
|
52
|
+
// and optional calls so existing dependency/slot passes see their ABI.
|
|
53
|
+
return {
|
|
54
|
+
...node,
|
|
55
|
+
callee: importedHook ? b.id(aliases.get(importedHook), node.callee) : rebuild(node.callee),
|
|
56
|
+
optional: false,
|
|
57
|
+
arguments: rebuild(node.arguments),
|
|
58
|
+
// Replace the proven placeholder during inference, keeping later slot
|
|
59
|
+
// arguments at their authored positions. Never rewrite an array.
|
|
60
|
+
...(omittedDependency ? { _octaneStrongOmittedDependency: true } : {}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const hook = candidates.get(node);
|
|
64
|
+
if (hook) {
|
|
65
|
+
const initial = rebuild(node.init);
|
|
66
|
+
const callback = hook === 'useCallback' ? initial : origin(b.arrow([], initial), node.init);
|
|
67
|
+
return { ...node, init: origin(b.call(b.id(aliases.get(hook)), callback), node.init) };
|
|
68
|
+
}
|
|
69
|
+
let output = node;
|
|
70
|
+
for (const key in node) {
|
|
71
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
72
|
+
const value = rebuild(node[key]);
|
|
73
|
+
if (value !== node[key]) {
|
|
74
|
+
if (output === node) output = { ...node };
|
|
75
|
+
output[key] = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return output;
|
|
79
|
+
}
|
|
80
|
+
const result = rebuild(ast);
|
|
81
|
+
if (aliases.size === 0) return result;
|
|
82
|
+
const imports = origin(
|
|
83
|
+
b.imports(
|
|
84
|
+
[...aliases].map(([hook, alias]) => [hook, alias]),
|
|
85
|
+
'octane',
|
|
86
|
+
),
|
|
87
|
+
ast.body.find((node) => node.loc) ?? ast,
|
|
88
|
+
);
|
|
89
|
+
return { ...result, body: [...result.body, imports] };
|
|
90
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createRendererRegionResolver } from './renderer-boundaries.js';
|
|
2
|
+
|
|
3
|
+
export const STRONG_UNTRUSTED_HTML = 'OCTANE_STRONG_UNTRUSTED_HTML';
|
|
4
|
+
export const STRONG_HTML_MESSAGE =
|
|
5
|
+
'Strong mode requires a TrustedHTML value for dangerouslySetInnerHTML. Use trustHTML() only for trusted or already sanitized HTML; trustHTML() does not sanitize its input.';
|
|
6
|
+
const SKIP_KEYS = new Set(['type', 'loc', 'start', 'end', 'range', 'metadata', 'parent']);
|
|
7
|
+
const TRANSPARENT = new Set([
|
|
8
|
+
'ParenthesizedExpression',
|
|
9
|
+
'TSAsExpression',
|
|
10
|
+
'TSSatisfiesExpression',
|
|
11
|
+
'TSTypeAssertion',
|
|
12
|
+
'TSNonNullExpression',
|
|
13
|
+
'TSInstantiationExpression',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
// Identifier escapes use Unicode; quoted property names also admit hex and
|
|
17
|
+
// escaped letters. Ordinary escapes such as a newline cannot spell this key.
|
|
18
|
+
export function mayHaveStrongHTML(source) {
|
|
19
|
+
return (
|
|
20
|
+
source.includes('dangerouslySetInnerHTML') ||
|
|
21
|
+
/\\(?:u|x|[dageoslySIHTML]|[\r\n\u2028\u2029])/.test(source)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function unwrap(node) {
|
|
26
|
+
while (node && TRANSPARENT.has(node.type)) node = node.expression;
|
|
27
|
+
return node;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function propertyName(node) {
|
|
31
|
+
return node?.type === 'Identifier' || node?.type === 'JSXIdentifier' ? node.name : node?.value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function checkStrongHTMLValue(expression, report) {
|
|
35
|
+
const node = unwrap(expression);
|
|
36
|
+
if (!node) return;
|
|
37
|
+
switch (node.type) {
|
|
38
|
+
case 'ObjectExpression':
|
|
39
|
+
// A spread of an existing branded value retains its type. The type
|
|
40
|
+
// checker verifies that value rather than rejecting all object syntax.
|
|
41
|
+
if (!node.properties.some((property) => property.type === 'SpreadElement')) report(node);
|
|
42
|
+
return;
|
|
43
|
+
case 'Literal':
|
|
44
|
+
if (node.value !== null) report(node);
|
|
45
|
+
return;
|
|
46
|
+
case 'TemplateLiteral':
|
|
47
|
+
case 'ArrayExpression':
|
|
48
|
+
case 'ArrowFunctionExpression':
|
|
49
|
+
case 'FunctionExpression':
|
|
50
|
+
report(node);
|
|
51
|
+
return;
|
|
52
|
+
case 'ConditionalExpression':
|
|
53
|
+
checkStrongHTMLValue(node.consequent, report);
|
|
54
|
+
checkStrongHTMLValue(node.alternate, report);
|
|
55
|
+
return;
|
|
56
|
+
case 'LogicalExpression':
|
|
57
|
+
checkStrongHTMLValue(node.left, report);
|
|
58
|
+
checkStrongHTMLValue(node.right, report);
|
|
59
|
+
return;
|
|
60
|
+
case 'SequenceExpression':
|
|
61
|
+
checkStrongHTMLValue(node.expressions.at(-1), report);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Resolve only a statically visible final writer. An unknown later spread
|
|
65
|
+
// may replace an earlier raw value, so its actual type belongs to TypeScript.
|
|
66
|
+
export function strongHTMLSpreadWriter(expression) {
|
|
67
|
+
const node = unwrap(expression);
|
|
68
|
+
if (node?.type !== 'ObjectExpression') return undefined;
|
|
69
|
+
for (let index = node.properties.length - 1; index >= 0; index--) {
|
|
70
|
+
const property = node.properties[index];
|
|
71
|
+
if (property.type === 'SpreadElement') {
|
|
72
|
+
const writer = strongHTMLSpreadWriter(property.argument);
|
|
73
|
+
if (writer !== null) return writer;
|
|
74
|
+
} else if (property.type === 'Property') {
|
|
75
|
+
if (property.computed && property.key.type !== 'Literal') return undefined;
|
|
76
|
+
if (propertyName(property.key) === 'dangerouslySetInnerHTML')
|
|
77
|
+
return property.kind === 'init' ? property.value : undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A syntax-only compiler can reject visibly unbranded values. Imported values,
|
|
85
|
+
* props, aliases and function results are checked by Strong's nominal JSX types
|
|
86
|
+
* in Volar/tsrx-tsc; guessing their types here would reject valid trust boundaries.
|
|
87
|
+
*/
|
|
88
|
+
export function analyzeStrongHTML(ast, source, filename, options = {}) {
|
|
89
|
+
if (!mayHaveStrongHTML(source)) return [];
|
|
90
|
+
const diagnostics = [];
|
|
91
|
+
const reported = new WeakSet();
|
|
92
|
+
const isDOM =
|
|
93
|
+
options.rendererRegionResolver ?? createRendererRegionResolver(ast, source, filename, options);
|
|
94
|
+
function report(node) {
|
|
95
|
+
if (reported.has(node)) return;
|
|
96
|
+
reported.add(node);
|
|
97
|
+
const position = (edge) => ({
|
|
98
|
+
offset: node[edge] ?? 0,
|
|
99
|
+
line: node.loc?.[edge]?.line ?? 1,
|
|
100
|
+
column: node.loc?.[edge]?.column ?? 0,
|
|
101
|
+
});
|
|
102
|
+
diagnostics.push({
|
|
103
|
+
code: STRONG_UNTRUSTED_HTML,
|
|
104
|
+
severity: 'error',
|
|
105
|
+
filename,
|
|
106
|
+
start: position('start'),
|
|
107
|
+
end: position('end'),
|
|
108
|
+
message: STRONG_HTML_MESSAGE,
|
|
109
|
+
suggestions: [],
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function visit(node) {
|
|
113
|
+
if (!node || typeof node !== 'object') return;
|
|
114
|
+
if (Array.isArray(node)) {
|
|
115
|
+
for (const item of node) visit(item);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (
|
|
119
|
+
node.type === 'JSXOpeningElement' &&
|
|
120
|
+
node.name?.type === 'JSXIdentifier' &&
|
|
121
|
+
/^[a-z]/.test(node.name.name) &&
|
|
122
|
+
isDOM(node)
|
|
123
|
+
) {
|
|
124
|
+
for (let index = node.attributes.length - 1; index >= 0; index--) {
|
|
125
|
+
const attribute = node.attributes[index];
|
|
126
|
+
if (attribute.type === 'JSXSpreadAttribute') {
|
|
127
|
+
const writer = strongHTMLSpreadWriter(attribute.argument);
|
|
128
|
+
if (writer === null) continue;
|
|
129
|
+
checkStrongHTMLValue(writer, report);
|
|
130
|
+
break;
|
|
131
|
+
} else if (propertyName(attribute.name) === 'dangerouslySetInnerHTML') {
|
|
132
|
+
if (attribute.value?.type === 'JSXExpressionContainer')
|
|
133
|
+
checkStrongHTMLValue(attribute.value.expression, report);
|
|
134
|
+
else report(attribute.value ?? attribute);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const key of Object.keys(node)) if (!SKIP_KEYS.has(key)) visit(node[key]);
|
|
140
|
+
}
|
|
141
|
+
visit(ast);
|
|
142
|
+
return diagnostics;
|
|
143
|
+
}
|