@tsrx/core 0.1.65 → 0.1.66
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 +26 -1
- package/package.json +9 -5
- package/src/analyze/css-analyze.js +44 -6
- package/src/analyze/index.js +14 -1
- package/src/analyze/style-analyze.js +467 -0
- package/src/analyze/validation.js +57 -0
- package/src/diagnostics.js +22 -0
- package/src/index.js +17 -0
- package/src/parse/style.js +97 -7
- package/src/plugin.js +145 -47
- package/src/scope.js +1 -1
- package/src/transform/jsx/index.js +94 -417
- package/src/transform/jsx/style-scopes.js +842 -0
- package/src/transform/scoping.js +129 -79
- package/src/transform/segments.js +16 -4
- package/src/transform/style-ref.js +74 -13
- package/src/transform/stylesheet.js +2 -1
- package/src/utils/is-reference.js +59 -0
- package/tests/fixtures/scoped-styles/README.md +71 -0
- package/tests/fixtures/scoped-styles/apply-forms.expected.json +18 -0
- package/tests/fixtures/scoped-styles/apply-forms.tsrx +69 -0
- package/tests/fixtures/scoped-styles/assigned-positions.expected.json +29 -0
- package/tests/fixtures/scoped-styles/assigned-positions.tsrx +90 -0
- package/tests/fixtures/scoped-styles/class-opt-in.expected.json +13 -0
- package/tests/fixtures/scoped-styles/class-opt-in.tsrx +39 -0
- package/tests/fixtures/scoped-styles/control-flow-else-if.expected.json +11 -0
- package/tests/fixtures/scoped-styles/control-flow-else-if.tsrx +26 -0
- package/tests/fixtures/scoped-styles/control-flow.expected.json +17 -0
- package/tests/fixtures/scoped-styles/control-flow.tsrx +102 -0
- package/tests/fixtures/scoped-styles/cross-module-apply.expected.json +14 -0
- package/tests/fixtures/scoped-styles/cross-module-apply.tsrx +48 -0
- package/tests/fixtures/scoped-styles/element-rooted-templates.expected.json +12 -0
- package/tests/fixtures/scoped-styles/element-rooted-templates.tsrx +33 -0
- package/tests/fixtures/scoped-styles/precedence.expected.json +11 -0
- package/tests/fixtures/scoped-styles/precedence.tsrx +45 -0
- package/tests/fixtures/scoped-styles/rfc-opening-example/panel.expected.json +12 -0
- package/tests/fixtures/scoped-styles/rfc-opening-example/panel.tsrx +46 -0
- package/tests/fixtures/scoped-styles/rfc-opening-example/theme.expected.json +9 -0
- package/tests/fixtures/scoped-styles/rfc-opening-example/theme.tsrx +24 -0
- package/tests/fixtures/scoped-styles/search-panel.expected.json +11 -0
- package/tests/fixtures/scoped-styles/search-panel.tsrx +48 -0
- package/tests/fixtures/scoped-styles/sibling-scope.expected.json +11 -0
- package/tests/fixtures/scoped-styles/sibling-scope.tsrx +44 -0
- package/tests/fixtures/scoped-styles/sibling-scopes.expected.json +12 -0
- package/tests/fixtures/scoped-styles/sibling-scopes.tsrx +51 -0
- package/tests/fixtures/scoped-styles/theme-composition.expected.json +14 -0
- package/tests/fixtures/scoped-styles/theme-composition.tsrx +45 -0
- package/tests/fixtures/scoped-styles/theme-diamond.expected.json +10 -0
- package/tests/fixtures/scoped-styles/theme-diamond.tsrx +18 -0
- package/tests/shared/scoped-styles-fixtures.js +67 -0
- package/tests/utils/fixtures/style-syntax.js +519 -0
- package/types/index.d.ts +85 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
2
|
/** @import * as ESTreeJSX from 'estree-jsx' */
|
|
3
|
-
/** @import { BaseNodeMetaData, FunctionMetaData, JsxHelperComponent, JsxHelperState, JsxPlatform,
|
|
3
|
+
/** @import { BaseNodeMetaData, FunctionMetaData, JsxHelperComponent, JsxHelperState, JsxPlatform, JsxTransformContext as TransformContext, JsxTransformOptions, JsxTransformResult, JsxVisitorContext } from '@tsrx/core/types' */
|
|
4
4
|
|
|
5
5
|
import { walk } from 'zimmerframe';
|
|
6
6
|
import { print } from 'esrap';
|
|
7
7
|
import { error } from '../../errors.js';
|
|
8
8
|
import { is_template_value_position } from '../../analyze/validation.js';
|
|
9
9
|
import { analyze_css } from '../../analyze/css-analyze.js';
|
|
10
|
-
import { prune_css } from '../../analyze/prune.js';
|
|
11
10
|
import {
|
|
12
11
|
in_jsx_child_context,
|
|
13
12
|
is_empty_jsx_fragment,
|
|
@@ -44,12 +43,11 @@ import {
|
|
|
44
43
|
find_first_top_level_await,
|
|
45
44
|
find_first_top_level_await_in_tsrx_function_body,
|
|
46
45
|
} from '../await.js';
|
|
47
|
-
import { prepare_stylesheet_for_render,
|
|
46
|
+
import { prepare_stylesheet_for_render, is_style_element } from '../scoping.js';
|
|
47
|
+
import { prepare_style_scopes, type_only_style } from './style-scopes.js';
|
|
48
48
|
import {
|
|
49
|
-
|
|
50
|
-
create_style_class_map,
|
|
49
|
+
build_style_class_map,
|
|
51
50
|
create_style_class_map_from_stylesheet,
|
|
52
|
-
create_style_ref_setup_statements,
|
|
53
51
|
get_style_element_stylesheet,
|
|
54
52
|
} from '../style-ref.js';
|
|
55
53
|
import {
|
|
@@ -635,6 +633,12 @@ export function createJsxTransform(platform) {
|
|
|
635
633
|
ast = lower_server_module_for_types(ast, platform.serverModule);
|
|
636
634
|
}
|
|
637
635
|
|
|
636
|
+
// Style scopes: which `<style>` blocks scope which template, CSS emission
|
|
637
|
+
// order, and the classes every element carries. Runs once, before any
|
|
638
|
+
// lowering, so order is an invariant of source structure (see
|
|
639
|
+
// style-scopes.js). Copy-on-write like the passes below.
|
|
640
|
+
ast = prepare_style_scopes(ast, transform_context);
|
|
641
|
+
|
|
638
642
|
// Both passes are copy-on-write over arbitrary property values, so they
|
|
639
643
|
// hand back the same shape they were given.
|
|
640
644
|
ast = /** @type {AST.Program} */ (expand_child_code_blocks(ast));
|
|
@@ -668,10 +672,7 @@ export function createJsxTransform(platform) {
|
|
|
668
672
|
return visit(create_native_tsrx_render_block(node, state), state);
|
|
669
673
|
}
|
|
670
674
|
|
|
671
|
-
const
|
|
672
|
-
const target = /** @type {AST.TSRXJSXElement | AST.TSRXJSXFragment} */ (
|
|
673
|
-
style_context?.fragment ?? next() ?? node
|
|
674
|
-
);
|
|
675
|
+
const target = /** @type {AST.TSRXJSXElement | AST.TSRXJSXFragment} */ (next() ?? node);
|
|
675
676
|
// An EMPTY fragment that is the sole expression of a `{ … }` container in a
|
|
676
677
|
// JSX child slot (`<b>{<></>}</b>`) must stay `<></>`: the container already
|
|
677
678
|
// supplies the `{}` wrapper, so lowering it to a bare `null` (the default
|
|
@@ -701,11 +702,7 @@ export function createJsxTransform(platform) {
|
|
|
701
702
|
) {
|
|
702
703
|
expression = wrap_lowered_value_in_fragment(expression, node);
|
|
703
704
|
}
|
|
704
|
-
for (const statement of
|
|
705
|
-
target,
|
|
706
|
-
style_context,
|
|
707
|
-
state,
|
|
708
|
-
)) {
|
|
705
|
+
for (const statement of take_style_ref_statements(target)) {
|
|
709
706
|
add_jsx_setup_declaration(expression, statement);
|
|
710
707
|
}
|
|
711
708
|
return wrap_jsx_setup_declarations(expression, in_jsx_child);
|
|
@@ -776,17 +773,41 @@ export function createJsxTransform(platform) {
|
|
|
776
773
|
JSXStyleElement(node, { path, state }) {
|
|
777
774
|
if (is_style_expression_position(path)) {
|
|
778
775
|
const stylesheet = get_style_element_stylesheet(node);
|
|
779
|
-
|
|
776
|
+
// The scope pre-pass renders assigned blocks at their declaration
|
|
777
|
+
// position; a block it did not reach (generated after it ran) is
|
|
778
|
+
// rendered here, at the end of the emission order.
|
|
779
|
+
if (stylesheet && !node.metadata.tsrx_style_prepared) {
|
|
780
|
+
node.metadata.tsrx_style_prepared = true;
|
|
780
781
|
analyze_css(stylesheet);
|
|
781
|
-
state.stylesheets.push(
|
|
782
|
-
|
|
782
|
+
state.stylesheets.push(
|
|
783
|
+
prepare_stylesheet_for_render(
|
|
784
|
+
stylesheet,
|
|
785
|
+
node.metadata.styleKind === 'theme' ? 'theme' : 'class-map',
|
|
786
|
+
),
|
|
787
|
+
);
|
|
783
788
|
}
|
|
789
|
+
return create_style_expression_value(node, stylesheet, state);
|
|
784
790
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
791
|
+
// A scoped block never reaches the output: the pre-pass strips it.
|
|
792
|
+
// What is left is the type-only stand-in (kept for editor mappings)
|
|
793
|
+
// or a block outside any scope, which is already reported.
|
|
794
|
+
const stand_in = state.typeOnly ? type_only_style(node) : node;
|
|
795
|
+
const element = b.jsx_element(
|
|
796
|
+
/** @type {ESTreeJSX.JSXElement} */ ({ ...stand_in, type: 'JSXElement', children: [] }),
|
|
797
|
+
stand_in.openingElement?.attributes ?? [],
|
|
788
798
|
[],
|
|
789
799
|
);
|
|
800
|
+
// A stand-in in a statement slot (a `<style>` sibling of the output
|
|
801
|
+
// node) prints as an expression statement; two in a row would parse as
|
|
802
|
+
// adjacent JSX (TS2657), so each is its own `void` expression.
|
|
803
|
+
const parent = path.at(-1);
|
|
804
|
+
const in_statement_slot =
|
|
805
|
+
parent?.type === 'JSXCodeBlock' ||
|
|
806
|
+
parent?.type === 'BlockStatement' ||
|
|
807
|
+
parent?.type === 'SwitchCase' ||
|
|
808
|
+
parent?.type === 'Program' ||
|
|
809
|
+
parent?.type === 'ExpressionStatement';
|
|
810
|
+
return state.typeOnly && in_statement_slot ? b.unary('void', element) : element;
|
|
790
811
|
},
|
|
791
812
|
|
|
792
813
|
JSXCodeBlock: transform_jsx_code_block,
|
|
@@ -1083,99 +1104,6 @@ function inject_dynamic_import(program, transform_context) {
|
|
|
1083
1104
|
);
|
|
1084
1105
|
}
|
|
1085
1106
|
|
|
1086
|
-
/**
|
|
1087
|
-
* Attach selector-location metadata used by editor definitions/hover before
|
|
1088
|
-
* the shared scoping pass mutates class attributes with the component hash.
|
|
1089
|
-
*
|
|
1090
|
-
* @param {AST.NativeTSRXNode} component
|
|
1091
|
-
* @param {AST.CSS.StyleSheet} css
|
|
1092
|
-
* @param {TransformContext} transform_context
|
|
1093
|
-
* @param {boolean} [export_top_scoped_classes]
|
|
1094
|
-
* @param {string} [region_hash]
|
|
1095
|
-
* @returns {void}
|
|
1096
|
-
*/
|
|
1097
|
-
function apply_css_definition_metadata(
|
|
1098
|
-
component,
|
|
1099
|
-
css,
|
|
1100
|
-
transform_context,
|
|
1101
|
-
export_top_scoped_classes = false,
|
|
1102
|
-
region_hash = css.hash,
|
|
1103
|
-
) {
|
|
1104
|
-
analyze_css(css);
|
|
1105
|
-
|
|
1106
|
-
const metadata = component.metadata || (component.metadata = { path: [] });
|
|
1107
|
-
const style_classes = metadata.styleClasses || (metadata.styleClasses = new Map());
|
|
1108
|
-
const top_scoped_classes = metadata.topScopedClasses || new Map();
|
|
1109
|
-
const elements = collect_css_prunable_elements(node_children(component), [], transform_context);
|
|
1110
|
-
|
|
1111
|
-
const prune = () => {
|
|
1112
|
-
for (const element of elements) {
|
|
1113
|
-
prune_css(css, element, style_classes, top_scoped_classes, region_hash);
|
|
1114
|
-
}
|
|
1115
|
-
};
|
|
1116
|
-
|
|
1117
|
-
prune();
|
|
1118
|
-
|
|
1119
|
-
if (export_top_scoped_classes) {
|
|
1120
|
-
for (const [class_name, class_info] of top_scoped_classes) {
|
|
1121
|
-
style_classes.set(class_name, class_info.selector ?? class_info);
|
|
1122
|
-
}
|
|
1123
|
-
prune();
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
if (top_scoped_classes.size > 0) {
|
|
1127
|
-
metadata.topScopedClasses = top_scoped_classes;
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
/**
|
|
1132
|
-
* Pruning runs from the fragment visitor, before the walker has descended into
|
|
1133
|
-
* the subtree and stamped walker paths, so each collected element gets its
|
|
1134
|
-
* ancestor chain (`metadata.path`) here — descendant/sibling selector matching
|
|
1135
|
-
* in `prune_css` reads it.
|
|
1136
|
-
*
|
|
1137
|
-
* @param {AST.Node | AST.Node[]} value
|
|
1138
|
-
* @param {AST.TSRXJSXElement[]} [elements]
|
|
1139
|
-
* @param {TransformContext | null} [transform_context]
|
|
1140
|
-
* @param {AST.Node[]} [path]
|
|
1141
|
-
* @returns {AST.TSRXJSXElement[]}
|
|
1142
|
-
*/
|
|
1143
|
-
function collect_css_prunable_elements(value, elements = [], transform_context = null, path = []) {
|
|
1144
|
-
if (Array.isArray(value)) {
|
|
1145
|
-
for (const child of value) {
|
|
1146
|
-
collect_css_prunable_elements(child, elements, transform_context, path);
|
|
1147
|
-
}
|
|
1148
|
-
return elements;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
if (
|
|
1152
|
-
(value.type === 'FunctionDeclaration' ||
|
|
1153
|
-
value.type === 'FunctionExpression' ||
|
|
1154
|
-
value.type === 'ArrowFunctionExpression') &&
|
|
1155
|
-
// Generated dynamic-tag wrappers are render-block closures, not user
|
|
1156
|
-
// component boundaries — the element inside still belongs to this
|
|
1157
|
-
// component's scoped CSS.
|
|
1158
|
-
value.metadata?.tsrx_dynamic_wrapper !== true
|
|
1159
|
-
) {
|
|
1160
|
-
return elements;
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
if (value.type === 'JSXElement' && value.metadata?.native_tsrx) {
|
|
1164
|
-
if (!is_style_element(value)) {
|
|
1165
|
-
set_node_path_metadata(value, path);
|
|
1166
|
-
elements.push(value);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
const child_path = [...path, value];
|
|
1171
|
-
|
|
1172
|
-
for (const child of child_nodes(value, 'css')) {
|
|
1173
|
-
collect_css_prunable_elements(child, elements, transform_context, child_path);
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
return elements;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
1107
|
/**
|
|
1180
1108
|
* @param {AST.Node[]} body_nodes
|
|
1181
1109
|
* @param {TransformContext} transform_context
|
|
@@ -2318,112 +2246,20 @@ function node_contains_native_tsrx_template(node) {
|
|
|
2318
2246
|
}
|
|
2319
2247
|
|
|
2320
2248
|
/**
|
|
2321
|
-
*
|
|
2322
|
-
*
|
|
2323
|
-
*
|
|
2324
|
-
|
|
2325
|
-
function collect_tsrx_stylesheets(node, allow_multiple) {
|
|
2326
|
-
/** @type {AST.CSS.StyleSheet[]} */
|
|
2327
|
-
const styles = [];
|
|
2328
|
-
collect_style_elements(node_children(node), styles);
|
|
2329
|
-
|
|
2330
|
-
if (styles.length === 0) return null;
|
|
2331
|
-
if (styles.length > 1 && !allow_multiple) {
|
|
2332
|
-
throw new Error('TSRX fragments can only have one style tag');
|
|
2333
|
-
}
|
|
2334
|
-
|
|
2335
|
-
return styles;
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
/**
|
|
2339
|
-
* @param {AST.NativeTSRXNode} node
|
|
2340
|
-
* @param {TransformContext} transform_context
|
|
2341
|
-
* @returns {JsxStyleContext | null}
|
|
2342
|
-
*/
|
|
2343
|
-
function prepare_tsrx_fragment_styles(node, transform_context) {
|
|
2344
|
-
// Type-only output must stay analyzable: a throw here makes language hosts
|
|
2345
|
-
// (tsrx-tsc, editors) fall back to presenting the RAW source as the virtual
|
|
2346
|
-
// TSX, so every CSS brace in the file becomes a TSX parse error. Platforms
|
|
2347
|
-
// whose runtime dialect allows one scope to split its CSS across several
|
|
2348
|
-
// `<style>` tags are valid input here, and platforms that forbid it still
|
|
2349
|
-
// report the rule through their own runtime compiler.
|
|
2350
|
-
const sheets = collect_tsrx_stylesheets(node, transform_context.typeOnly);
|
|
2351
|
-
if (!sheets) return null;
|
|
2352
|
-
|
|
2353
|
-
const css = sheets[0];
|
|
2354
|
-
const style_refs = collect_style_ref_attributes(node);
|
|
2355
|
-
// A component scope keeps ONE hash even when its CSS is split across
|
|
2356
|
-
// several `<style>` tags: rebase every sheet onto the first sheet's hash
|
|
2357
|
-
// before scoping, so selector rewriting and the DOM hash annotation below
|
|
2358
|
-
// agree. `apply_css_definition_metadata` accumulates into the component's
|
|
2359
|
-
// `styleClasses`/`topScopedClasses` metadata, so per-sheet calls compose
|
|
2360
|
-
// into one class map for style refs.
|
|
2361
|
-
for (const sheet of sheets) {
|
|
2362
|
-
const region_hash = sheet.hash;
|
|
2363
|
-
sheet.hash = css.hash;
|
|
2364
|
-
// `prune_css` inside marks the matching selectors as used/scoped; selectors
|
|
2365
|
-
// that match no element render commented out, matching target behavior.
|
|
2366
|
-
apply_css_definition_metadata(
|
|
2367
|
-
node,
|
|
2368
|
-
sheet,
|
|
2369
|
-
transform_context,
|
|
2370
|
-
style_refs.length > 0,
|
|
2371
|
-
region_hash,
|
|
2372
|
-
);
|
|
2373
|
-
transform_context.stylesheets.push(sheet);
|
|
2374
|
-
}
|
|
2375
|
-
const fragment = annotate_tsrx_with_hash(
|
|
2376
|
-
node,
|
|
2377
|
-
css.hash,
|
|
2378
|
-
transform_context.platform.jsx.classAttrName ??
|
|
2379
|
-
(transform_context.platform.jsx.rewriteClassAttr ? 'className' : 'class'),
|
|
2380
|
-
transform_context.typeOnly,
|
|
2381
|
-
);
|
|
2382
|
-
return { css, style_refs, fragment };
|
|
2383
|
-
}
|
|
2384
|
-
|
|
2385
|
-
/**
|
|
2386
|
-
* @template T
|
|
2387
|
-
* @param {AST.NativeTSRXNode} node
|
|
2388
|
-
* @param {TransformContext} transform_context
|
|
2389
|
-
* @param {(style_context: JsxStyleContext | null) => T} callback
|
|
2390
|
-
* @returns {T}
|
|
2391
|
-
*/
|
|
2392
|
-
function with_tsrx_fragment_styles(node, transform_context, callback) {
|
|
2393
|
-
const style_context = prepare_tsrx_fragment_styles(node, transform_context);
|
|
2394
|
-
return callback(style_context);
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
/**
|
|
2398
|
-
* @param {AST.Node} fragment
|
|
2399
|
-
* @param {JsxStyleContext | null} style_context
|
|
2400
|
-
* @param {TransformContext} transform_context
|
|
2401
|
-
* @returns {AST.Statement[]}
|
|
2402
|
-
*/
|
|
2403
|
-
function create_tsrx_style_ref_setup_statements(fragment, style_context, transform_context) {
|
|
2404
|
-
if (!style_context || style_context.style_refs.length === 0) {
|
|
2405
|
-
return [];
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
return create_style_ref_setup_statements(
|
|
2409
|
-
style_context.style_refs,
|
|
2410
|
-
create_style_class_map(fragment, style_context.css),
|
|
2411
|
-
{
|
|
2412
|
-
allowMutableRefTarget: transform_context.platform.jsx.multiRefStrategy === 'array',
|
|
2413
|
-
createTempIdentifier: () =>
|
|
2414
|
-
create_generated_identifier(create_style_ref_temp_name(transform_context)),
|
|
2415
|
-
},
|
|
2416
|
-
);
|
|
2417
|
-
}
|
|
2418
|
-
|
|
2419
|
-
/**
|
|
2249
|
+
* The object an assigned block evaluates to: `$class` (applied themes'
|
|
2250
|
+
* classes, then the own hash) followed by one entry per exposed class. A
|
|
2251
|
+
* body-less `<style apply={…} />` exposes `$class` only.
|
|
2252
|
+
*
|
|
2420
2253
|
* @param {AST.JSXStyleElement} node
|
|
2421
|
-
* @param {AST.CSS.StyleSheet} stylesheet
|
|
2254
|
+
* @param {AST.CSS.StyleSheet | null} stylesheet
|
|
2422
2255
|
* @param {TransformContext} transform_context
|
|
2423
2256
|
* @returns {AST.Expression}
|
|
2424
2257
|
*/
|
|
2425
2258
|
function create_style_expression_value(node, stylesheet, transform_context) {
|
|
2426
|
-
const
|
|
2259
|
+
const options = { applied: node.metadata.tsrx_style_class_parts ?? [] };
|
|
2260
|
+
const class_map = stylesheet
|
|
2261
|
+
? create_style_class_map_from_stylesheet(stylesheet, options)
|
|
2262
|
+
: build_style_class_map(new Map(), null, options);
|
|
2427
2263
|
if (!transform_context.typeOnly) {
|
|
2428
2264
|
return class_map;
|
|
2429
2265
|
}
|
|
@@ -2437,7 +2273,7 @@ function create_style_expression_value(node, stylesheet, transform_context) {
|
|
|
2437
2273
|
* @param {TransformContext} transform_context
|
|
2438
2274
|
*/
|
|
2439
2275
|
function add_type_only_style_anchor(node, transform_context) {
|
|
2440
|
-
const style_anchor = b.jsx_element(clone_ast_node(node, true), [], []);
|
|
2276
|
+
const style_anchor = b.jsx_element(clone_ast_node(type_only_style(node), true), [], []);
|
|
2441
2277
|
disable_style_anchor_verification(style_anchor);
|
|
2442
2278
|
|
|
2443
2279
|
const anchor_id = create_generated_identifier(create_style_anchor_name(transform_context));
|
|
@@ -2474,177 +2310,6 @@ function disable_style_anchor_verification(element) {
|
|
|
2474
2310
|
}
|
|
2475
2311
|
}
|
|
2476
2312
|
|
|
2477
|
-
/**
|
|
2478
|
-
* @param {TransformContext} transform_context
|
|
2479
|
-
* @returns {string}
|
|
2480
|
-
*/
|
|
2481
|
-
function create_style_ref_temp_name(transform_context) {
|
|
2482
|
-
if (transform_context.helper_state) {
|
|
2483
|
-
return create_helper_name(transform_context.helper_state, 'style_ref');
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2486
|
-
transform_context.local_statement_component_index += 1;
|
|
2487
|
-
return `_tsrx_style_ref_${transform_context.local_statement_component_index}`;
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
/**
|
|
2491
|
-
* @param {AST.Node | AST.Node[] | null | undefined} node
|
|
2492
|
-
* @param {AST.CSS.StyleSheet[]} styles
|
|
2493
|
-
* @returns {void}
|
|
2494
|
-
*/
|
|
2495
|
-
function collect_style_elements(node, styles) {
|
|
2496
|
-
if (!node) return;
|
|
2497
|
-
|
|
2498
|
-
if (Array.isArray(node)) {
|
|
2499
|
-
for (const child of node) {
|
|
2500
|
-
collect_style_elements(child, styles);
|
|
2501
|
-
}
|
|
2502
|
-
return;
|
|
2503
|
-
}
|
|
2504
|
-
|
|
2505
|
-
if (is_style_element(node)) {
|
|
2506
|
-
const stylesheet = node.children?.find((child) => child.type === 'StyleSheet');
|
|
2507
|
-
if (stylesheet) {
|
|
2508
|
-
styles.push(stylesheet);
|
|
2509
|
-
}
|
|
2510
|
-
return;
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
if (is_function_or_class_boundary(node)) {
|
|
2514
|
-
return;
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
|
-
if ((node.type === 'JSXElement' || node.type === 'JSXFragment') && node.metadata?.native_tsrx) {
|
|
2518
|
-
collect_style_elements(node.children || [], styles);
|
|
2519
|
-
return;
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
if (node.type === 'BlockStatement') {
|
|
2523
|
-
collect_style_elements(node.body || [], styles);
|
|
2524
|
-
return;
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
if (is_if_control_node(node)) {
|
|
2528
|
-
collect_style_elements(node.consequent, styles);
|
|
2529
|
-
collect_style_elements(node.alternate, styles);
|
|
2530
|
-
return;
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
if (is_switch_control_node(node)) {
|
|
2534
|
-
for (const switch_case of node.cases || []) {
|
|
2535
|
-
collect_style_elements(switch_case.consequent || [], styles);
|
|
2536
|
-
}
|
|
2537
|
-
return;
|
|
2538
|
-
}
|
|
2539
|
-
|
|
2540
|
-
if (is_try_control_node(node)) {
|
|
2541
|
-
collect_style_elements(node.block, styles);
|
|
2542
|
-
collect_style_elements(node.handler?.body, styles);
|
|
2543
|
-
collect_style_elements(node.finalizer, styles);
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2547
|
-
/**
|
|
2548
|
-
* @template {AST.NativeTSRXNode} T
|
|
2549
|
-
* @param {T} node
|
|
2550
|
-
* @param {string} hash
|
|
2551
|
-
* @param {'class' | 'className'} jsx_class_attr_name
|
|
2552
|
-
* @param {boolean} preserve_style_elements
|
|
2553
|
-
* @returns {T}
|
|
2554
|
-
*/
|
|
2555
|
-
function annotate_tsrx_with_hash(node, hash, jsx_class_attr_name, preserve_style_elements) {
|
|
2556
|
-
// `annotate_with_hash` returns null for a `<style>` element it drops, so
|
|
2557
|
-
// filter before the children go back on the node.
|
|
2558
|
-
let children = node_children(node)
|
|
2559
|
-
.map((statement) =>
|
|
2560
|
-
annotate_with_hash(
|
|
2561
|
-
clone_ast_node(statement),
|
|
2562
|
-
hash,
|
|
2563
|
-
jsx_class_attr_name,
|
|
2564
|
-
preserve_style_elements,
|
|
2565
|
-
),
|
|
2566
|
-
)
|
|
2567
|
-
.filter(is_ast_node);
|
|
2568
|
-
if (!preserve_style_elements) {
|
|
2569
|
-
children = strip_style_elements_from_list(children);
|
|
2570
|
-
}
|
|
2571
|
-
// Shallow copy with rewritten children; the spread preserves `node`'s type.
|
|
2572
|
-
return /** @type {T} */ ({ ...node, children });
|
|
2573
|
-
}
|
|
2574
|
-
|
|
2575
|
-
/**
|
|
2576
|
-
* Drop `<style>` elements from a child list, recursing into the nodes that
|
|
2577
|
-
* survive.
|
|
2578
|
-
*
|
|
2579
|
-
* @param {Array<AST.Node | null | undefined>} nodes
|
|
2580
|
-
* @returns {AST.Node[]}
|
|
2581
|
-
*/
|
|
2582
|
-
function strip_style_elements_from_list(nodes) {
|
|
2583
|
-
/** @type {AST.Node[]} */
|
|
2584
|
-
const kept = [];
|
|
2585
|
-
for (const child of nodes) {
|
|
2586
|
-
if (is_style_element(child)) continue;
|
|
2587
|
-
const stripped = strip_style_elements(child);
|
|
2588
|
-
if (stripped) kept.push(stripped);
|
|
2589
|
-
}
|
|
2590
|
-
return kept;
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
/**
|
|
2594
|
-
* Strip `<style>` elements out of one node's render subtree, in place. Returns
|
|
2595
|
-
* `null` when the node itself is a `<style>` element and should be dropped, or
|
|
2596
|
-
* when there is no node — child lists can hold holes.
|
|
2597
|
-
*
|
|
2598
|
-
* @param {AST.Node | null | undefined} node
|
|
2599
|
-
* @returns {AST.Node | null}
|
|
2600
|
-
*/
|
|
2601
|
-
function strip_style_elements(node) {
|
|
2602
|
-
if (!node || is_style_element(node)) {
|
|
2603
|
-
return null;
|
|
2604
|
-
}
|
|
2605
|
-
|
|
2606
|
-
if (is_function_or_class_boundary(node)) {
|
|
2607
|
-
return node;
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
if ((node.type === 'JSXElement' || node.type === 'JSXFragment') && node.metadata?.native_tsrx) {
|
|
2611
|
-
node.children = strip_style_elements_from_list(node.children || []);
|
|
2612
|
-
return node;
|
|
2613
|
-
}
|
|
2614
|
-
|
|
2615
|
-
if (node.type === 'BlockStatement') {
|
|
2616
|
-
node.body = /** @type {AST.Statement[]} */ (strip_style_elements_from_list(node.body || []));
|
|
2617
|
-
return node;
|
|
2618
|
-
}
|
|
2619
|
-
|
|
2620
|
-
if (is_if_control_node(node)) {
|
|
2621
|
-
const consequent = strip_style_elements(node.consequent);
|
|
2622
|
-
if (consequent) node.consequent = /** @type {AST.Statement} */ (consequent);
|
|
2623
|
-
if (node.alternate) {
|
|
2624
|
-
const alternate = strip_style_elements(node.alternate);
|
|
2625
|
-
if (alternate) node.alternate = /** @type {AST.Statement} */ (alternate);
|
|
2626
|
-
}
|
|
2627
|
-
return node;
|
|
2628
|
-
}
|
|
2629
|
-
|
|
2630
|
-
if (is_switch_control_node(node)) {
|
|
2631
|
-
for (const switch_case of node.cases || []) {
|
|
2632
|
-
switch_case.consequent = /** @type {AST.Statement[]} */ (
|
|
2633
|
-
strip_style_elements_from_list(switch_case.consequent || [])
|
|
2634
|
-
);
|
|
2635
|
-
}
|
|
2636
|
-
return node;
|
|
2637
|
-
}
|
|
2638
|
-
|
|
2639
|
-
if (is_try_control_node(node)) {
|
|
2640
|
-
strip_style_elements(node.block);
|
|
2641
|
-
if (node.handler?.body) strip_style_elements(node.handler.body);
|
|
2642
|
-
if (node.finalizer) strip_style_elements(node.finalizer);
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
return node;
|
|
2646
|
-
}
|
|
2647
|
-
|
|
2648
2313
|
/**
|
|
2649
2314
|
* @param {AST.Node[]} path
|
|
2650
2315
|
* @returns {boolean}
|
|
@@ -2708,15 +2373,28 @@ function create_native_tsrx_statement_list_block(block, transform_context) {
|
|
|
2708
2373
|
* @returns {AST.Statement[]}
|
|
2709
2374
|
*/
|
|
2710
2375
|
function create_native_tsrx_render_statements(fragment, transform_context) {
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2376
|
+
const render_nodes =
|
|
2377
|
+
fragment.type === 'JSXFragment' ? get_tsrx_render_children(fragment) : [fragment];
|
|
2378
|
+
return [
|
|
2379
|
+
...take_style_ref_statements(fragment),
|
|
2380
|
+
...build_render_statements(render_nodes, true, transform_context, fragment),
|
|
2381
|
+
];
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
/**
|
|
2385
|
+
* Style `ref` setup statements the scope pre-pass left on a scope rooted at
|
|
2386
|
+
* a native fragment/element (see style-scopes.js), consumed once: the same
|
|
2387
|
+
* fragment can reach both the return-statement lowering and the fragment
|
|
2388
|
+
* visitor.
|
|
2389
|
+
*
|
|
2390
|
+
* @param {AST.Node} fragment
|
|
2391
|
+
* @returns {AST.Statement[]}
|
|
2392
|
+
*/
|
|
2393
|
+
function take_style_ref_statements(fragment) {
|
|
2394
|
+
const statements = fragment.metadata?.tsrx_style_ref_statements;
|
|
2395
|
+
if (!statements) return [];
|
|
2396
|
+
delete fragment.metadata.tsrx_style_ref_statements;
|
|
2397
|
+
return statements;
|
|
2720
2398
|
}
|
|
2721
2399
|
|
|
2722
2400
|
/**
|
|
@@ -3909,20 +3587,27 @@ function to_jsx_element(
|
|
|
3909
3587
|
? opening_element_node
|
|
3910
3588
|
: set_loc(opening_element_node, node.openingElement || node);
|
|
3911
3589
|
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3590
|
+
let closingElement = null;
|
|
3591
|
+
if (!selfClosing) {
|
|
3592
|
+
const authored_closing = node.closingElement;
|
|
3593
|
+
if (authored_closing) {
|
|
3594
|
+
// Clone from the actual closing name when there is one: a dynamic
|
|
3595
|
+
// tag's closing expression (`</{Tag}>`) has its own source positions,
|
|
3596
|
+
// which editor mappings need.
|
|
3597
|
+
closingElement = set_loc(
|
|
3915
3598
|
b.jsx_closing_element(
|
|
3916
|
-
|
|
3917
|
-
// tag's closing expression (`</{Tag}>`) has its own source positions,
|
|
3918
|
-
// which editor mappings need.
|
|
3919
|
-
clone_jsx_name(
|
|
3920
|
-
node.closingElement?.name ?? name,
|
|
3921
|
-
node.closingElement?.name || node.closingElement || node,
|
|
3922
|
-
),
|
|
3599
|
+
clone_jsx_name(authored_closing.name ?? name, authored_closing.name || authored_closing),
|
|
3923
3600
|
),
|
|
3924
|
-
|
|
3601
|
+
authored_closing,
|
|
3925
3602
|
);
|
|
3603
|
+
} else {
|
|
3604
|
+
// Recovered unclosed tags have no authored close. Map the synthesized
|
|
3605
|
+
// name to the opening name token so rename/hover land on `span`, not
|
|
3606
|
+
// `<spa`, and leave the closing element itself unlocated so it does
|
|
3607
|
+
// not steal the opening `<` mapping.
|
|
3608
|
+
closingElement = b.jsx_closing_element(clone_jsx_name(name, source_opening.name));
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3926
3611
|
|
|
3927
3612
|
const element = set_loc(b.jsx_element_fresh(openingElement, closingElement, children), node);
|
|
3928
3613
|
if (node.metadata?.dynamicElement === true) {
|
|
@@ -4448,16 +4133,8 @@ function get_jsx_code_block_body_nodes(node, transform_context) {
|
|
|
4448
4133
|
return node.body || [];
|
|
4449
4134
|
}
|
|
4450
4135
|
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
const render = style_context?.fragment ?? node.render;
|
|
4454
|
-
return [
|
|
4455
|
-
...(node.body || []),
|
|
4456
|
-
...create_tsrx_style_ref_setup_statements(render, style_context, transform_context),
|
|
4457
|
-
render,
|
|
4458
|
-
];
|
|
4459
|
-
}
|
|
4460
|
-
|
|
4136
|
+
// Scoped styles were collected, stamped, and stripped by the pre-pass;
|
|
4137
|
+
// style `ref` setup statements already sit at the end of `body`.
|
|
4461
4138
|
return [...(node.body || []), node.render];
|
|
4462
4139
|
}
|
|
4463
4140
|
|