@tsrx/core 0.1.28 → 0.1.30
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/package.json +4 -1
- package/src/analyze/prune.js +6 -4
- package/src/parse/index.js +3 -2
- package/src/parse/parse-module.js +3 -1
- package/src/parse/style.js +12 -3
- package/src/plugin.js +15 -1
- package/src/transform/jsx/index.js +36 -26
- package/src/transform/scoping.js +64 -6
- package/src/transform/style-ref.js +24 -3
- package/{src → types}/helpers.d.ts +2 -0
- package/types/index.d.ts +1 -1
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Core compiler infrastructure for TSRX syntax",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.30",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"./types/acorn": {
|
|
28
28
|
"types": "./types/acorn.d.ts"
|
|
29
29
|
},
|
|
30
|
+
"./types/helpers": {
|
|
31
|
+
"types": "./types/helpers.d.ts"
|
|
32
|
+
},
|
|
30
33
|
"./runtime/ref": {
|
|
31
34
|
"types": "./types/runtime/ref.d.ts",
|
|
32
35
|
"default": "./src/runtime/ref.js"
|
package/src/analyze/prune.js
CHANGED
|
@@ -756,11 +756,13 @@ function attribute_matches(node, name, expected_value, operator, case_insensitiv
|
|
|
756
756
|
if (attribute.type !== 'JSXAttribute') continue;
|
|
757
757
|
|
|
758
758
|
const lowerCaseName = name.toLowerCase();
|
|
759
|
+
const accepted_names = [lowerCaseName, `$${lowerCaseName}`];
|
|
760
|
+
if (lowerCaseName === 'class') {
|
|
761
|
+
// React-style targets author the class attribute as `className`.
|
|
762
|
+
accepted_names.push('classname');
|
|
763
|
+
}
|
|
759
764
|
const attributeName = get_attribute_name(attribute);
|
|
760
|
-
if (
|
|
761
|
-
!attributeName ||
|
|
762
|
-
![lowerCaseName, `$${lowerCaseName}`].includes(attributeName.toLowerCase())
|
|
763
|
-
) {
|
|
765
|
+
if (!attributeName || !accepted_names.includes(attributeName.toLowerCase())) {
|
|
764
766
|
continue;
|
|
765
767
|
}
|
|
766
768
|
|
package/src/parse/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
@import * as AST from 'estree'
|
|
3
3
|
@import * as ESTreeJSX from 'estree-jsx'
|
|
4
4
|
@import { Parse } from '../../types/parse'
|
|
5
|
+
@import { NonEmptyString } from '../../types/helpers'
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import * as acorn from 'acorn';
|
|
@@ -189,7 +190,7 @@ function elementTemplateClosingTagPlugin(Base) {
|
|
|
189
190
|
* extend the base parser with framework-specific syntax.
|
|
190
191
|
*
|
|
191
192
|
* @param {...(AcornPlugin | Function)} plugins - Framework parser plugins to compose
|
|
192
|
-
* @returns {(source: string, filename
|
|
193
|
+
* @returns {<T extends string>(source: string, filename: NonEmptyString<T>, options?: any) => AST.Program} A parse function
|
|
193
194
|
*/
|
|
194
195
|
export function createParser(...plugins) {
|
|
195
196
|
const parser = /** @type {Parse.ParserConstructor} */ (
|
|
@@ -204,7 +205,7 @@ export function createParser(...plugins) {
|
|
|
204
205
|
|
|
205
206
|
/**
|
|
206
207
|
* @param {string} source
|
|
207
|
-
* @param {string}
|
|
208
|
+
* @param {string} filename
|
|
208
209
|
* @param {any} [options]
|
|
209
210
|
* @returns {AST.Program}
|
|
210
211
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
2
|
/** @import { ParseOptions } from '../../types/index' */
|
|
3
|
+
/** @import { NonEmptyString } from '../../types/helpers' */
|
|
3
4
|
|
|
4
5
|
import { createParser } from './index.js';
|
|
5
6
|
import { TSRXPlugin } from '../plugin.js';
|
|
@@ -8,8 +9,9 @@ const parse = createParser(TSRXPlugin());
|
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Parse source code to an ESTree AST using the TSRX parser.
|
|
12
|
+
* @template {string} T
|
|
11
13
|
* @param {string} source
|
|
12
|
-
* @param {
|
|
14
|
+
* @param {NonEmptyString<T>} filename
|
|
13
15
|
* @param {ParseOptions} [options]
|
|
14
16
|
* @returns {AST.Program}
|
|
15
17
|
*/
|
package/src/parse/style.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
|
+
/** @import { NonEmptyString } from '../../types/helpers' */
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { strong_hash } from '../utils/hashing.js';
|
|
4
5
|
|
|
5
6
|
const REGEX_MATCHER = /^[~^$*|]?=/;
|
|
6
7
|
const REGEX_ATTRIBUTE_FLAGS = /^[a-zA-Z]+/;
|
|
@@ -110,16 +111,24 @@ class Parser {
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
/**
|
|
114
|
+
* @template {string} T
|
|
113
115
|
* @param {string} content
|
|
116
|
+
* @param {{ filename: NonEmptyString<T>, line: number, column: number }} location position of the `<style>` tag in the source file
|
|
114
117
|
* @param {{ loose?: boolean }} options
|
|
115
118
|
* @returns {AST.CSS.StyleSheet}
|
|
116
119
|
*/
|
|
117
|
-
export function parse_style(content, options) {
|
|
120
|
+
export function parse_style(content, location, options) {
|
|
118
121
|
const parser = new Parser(content, options.loose || false);
|
|
119
122
|
|
|
123
|
+
// The filename and the position of the `<style>` tag keep the hash unique
|
|
124
|
+
// when identical content appears in multiple style blocks within a file or
|
|
125
|
+
// across files. The filename may be an absolute path, so this must be a
|
|
126
|
+
// pre-image-resistant hash to avoid leaking file structure into the bundle.
|
|
127
|
+
const hash_source = `${location.filename}:${location.line}:${location.column}:${content}`;
|
|
128
|
+
|
|
120
129
|
return {
|
|
121
130
|
source: content,
|
|
122
|
-
hash: `tsrx-${
|
|
131
|
+
hash: `tsrx-${strong_hash(hash_source)}`,
|
|
123
132
|
type: 'StyleSheet',
|
|
124
133
|
children: read_body(parser),
|
|
125
134
|
start: 0,
|
package/src/plugin.js
CHANGED
|
@@ -1995,11 +1995,25 @@ export function TSRXPlugin(config) {
|
|
|
1995
1995
|
* @param {boolean} insideHead
|
|
1996
1996
|
*/
|
|
1997
1997
|
#parseStyleElement(open, node, insideHead) {
|
|
1998
|
+
const filename = this.#filename;
|
|
1999
|
+
if (!filename) {
|
|
2000
|
+
throw new Error(
|
|
2001
|
+
'<style> elements require a filename: pass one to parse so style scope hashes are unique per file.',
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
1998
2004
|
const contentStart = open.end;
|
|
1999
2005
|
const input = this.input.slice(contentStart);
|
|
2000
2006
|
const relativeCloseStart = input.indexOf('</style>');
|
|
2001
2007
|
const content = relativeCloseStart === -1 ? input : input.slice(0, relativeCloseStart);
|
|
2002
|
-
const parsedCss = parse_style(
|
|
2008
|
+
const parsedCss = parse_style(
|
|
2009
|
+
content,
|
|
2010
|
+
{
|
|
2011
|
+
filename,
|
|
2012
|
+
line: open.loc.start.line,
|
|
2013
|
+
column: open.loc.start.column,
|
|
2014
|
+
},
|
|
2015
|
+
{ loose: this.#loose },
|
|
2016
|
+
);
|
|
2003
2017
|
|
|
2004
2018
|
if (!insideHead) {
|
|
2005
2019
|
node.metadata.styleScopeHash = parsedCss.hash;
|
|
@@ -441,15 +441,6 @@ export function createJsxTransform(platform) {
|
|
|
441
441
|
return next() ?? node;
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
-
if (is_style_element(node) && is_style_expression_position(path)) {
|
|
445
|
-
const stylesheet = get_style_element_stylesheet(node);
|
|
446
|
-
if (stylesheet) {
|
|
447
|
-
analyze_css(stylesheet);
|
|
448
|
-
state.stylesheets.push(stylesheet);
|
|
449
|
-
return /** @type {any} */ (create_style_expression_value(node, stylesheet, state));
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
444
|
// Capture raw children BEFORE the walker transforms them so platform
|
|
454
445
|
// hooks can inspect the original JSX child shape.
|
|
455
446
|
const raw_children = /** @type {any} */ (node.children || []).map(
|
|
@@ -487,16 +478,14 @@ export function createJsxTransform(platform) {
|
|
|
487
478
|
const stylesheet = get_style_element_stylesheet(node);
|
|
488
479
|
if (stylesheet) {
|
|
489
480
|
analyze_css(stylesheet);
|
|
490
|
-
state.stylesheets.push(stylesheet);
|
|
481
|
+
state.stylesheets.push(prepare_stylesheet_for_render(stylesheet, true));
|
|
491
482
|
return /** @type {any} */ (create_style_expression_value(node, stylesheet, state));
|
|
492
483
|
}
|
|
493
484
|
}
|
|
494
|
-
return
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
[],
|
|
499
|
-
)
|
|
485
|
+
return b.jsx_element(
|
|
486
|
+
/** @type {ESTreeJSX.JSXElement} */ ({ ...node, type: 'JSXElement', children: [] }),
|
|
487
|
+
node.openingElement?.attributes ?? [],
|
|
488
|
+
[],
|
|
500
489
|
);
|
|
501
490
|
},
|
|
502
491
|
|
|
@@ -561,9 +550,7 @@ export function createJsxTransform(platform) {
|
|
|
561
550
|
sourceMapContent: source,
|
|
562
551
|
});
|
|
563
552
|
|
|
564
|
-
const { css, cssHash } = render_css_result(
|
|
565
|
-
/** @type {any} */ (stylesheets.map(prepare_stylesheet_for_render)),
|
|
566
|
-
);
|
|
553
|
+
const { css, cssHash } = render_css_result(/** @type {any} */ (stylesheets));
|
|
567
554
|
|
|
568
555
|
return { ast: final_program, code: result.code, map: result.map, css, cssHash };
|
|
569
556
|
}
|
|
@@ -792,19 +779,25 @@ function apply_css_definition_metadata(
|
|
|
792
779
|
}
|
|
793
780
|
|
|
794
781
|
/**
|
|
782
|
+
* Pruning runs from the fragment visitor, before the walker has descended into
|
|
783
|
+
* the subtree and stamped walker paths, so each collected element gets its
|
|
784
|
+
* ancestor chain (`metadata.path`) here — descendant/sibling selector matching
|
|
785
|
+
* in `prune_css` reads it.
|
|
786
|
+
*
|
|
795
787
|
* @param {any} value
|
|
796
788
|
* @param {any[]} [elements]
|
|
797
789
|
* @param {TransformContext | null} [transform_context]
|
|
790
|
+
* @param {any[]} [path]
|
|
798
791
|
* @returns {any[]}
|
|
799
792
|
*/
|
|
800
|
-
function collect_css_prunable_elements(value, elements = [], transform_context = null) {
|
|
793
|
+
function collect_css_prunable_elements(value, elements = [], transform_context = null, path = []) {
|
|
801
794
|
if (!value || typeof value !== 'object') {
|
|
802
795
|
return elements;
|
|
803
796
|
}
|
|
804
797
|
|
|
805
798
|
if (Array.isArray(value)) {
|
|
806
799
|
for (const child of value) {
|
|
807
|
-
collect_css_prunable_elements(child, elements, transform_context);
|
|
800
|
+
collect_css_prunable_elements(child, elements, transform_context, path);
|
|
808
801
|
}
|
|
809
802
|
return elements;
|
|
810
803
|
}
|
|
@@ -823,15 +816,18 @@ function collect_css_prunable_elements(value, elements = [], transform_context =
|
|
|
823
816
|
|
|
824
817
|
if (value.type === 'JSXElement' && value.metadata?.native_tsrx) {
|
|
825
818
|
if (!is_style_element(value)) {
|
|
819
|
+
set_node_path_metadata(value, path);
|
|
826
820
|
elements.push(value);
|
|
827
821
|
}
|
|
828
822
|
}
|
|
829
823
|
|
|
824
|
+
const child_path = value.type ? [...path, value] : path;
|
|
825
|
+
|
|
830
826
|
for (const key of Object.keys(value)) {
|
|
831
827
|
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata' || key === 'css') {
|
|
832
828
|
continue;
|
|
833
829
|
}
|
|
834
|
-
collect_css_prunable_elements(value[key], elements, transform_context);
|
|
830
|
+
collect_css_prunable_elements(value[key], elements, transform_context, child_path);
|
|
835
831
|
}
|
|
836
832
|
|
|
837
833
|
return elements;
|
|
@@ -1378,10 +1374,10 @@ function transform_return_statement(node, { next, visit, state, path }) {
|
|
|
1378
1374
|
|
|
1379
1375
|
/**
|
|
1380
1376
|
* @param {any} node
|
|
1381
|
-
* @param {{ state: TransformContext, path: AST.Node[] }} context
|
|
1377
|
+
* @param {{ state: TransformContext, path: AST.Node[], visit: (node: any, state?: TransformContext) => any }} context
|
|
1382
1378
|
* @returns {any}
|
|
1383
1379
|
*/
|
|
1384
|
-
function transform_jsx_code_block(node, { state, path }) {
|
|
1380
|
+
function transform_jsx_code_block(node, { state, path, visit }) {
|
|
1385
1381
|
const body_nodes = get_jsx_code_block_body_nodes(node, state);
|
|
1386
1382
|
const parent = /** @type {any} */ (path.at(-1));
|
|
1387
1383
|
|
|
@@ -1398,10 +1394,22 @@ function transform_jsx_code_block(node, { state, path }) {
|
|
|
1398
1394
|
}
|
|
1399
1395
|
|
|
1400
1396
|
const expression = b.call(
|
|
1401
|
-
b.arrow(
|
|
1397
|
+
b.arrow(
|
|
1398
|
+
[],
|
|
1399
|
+
b.block(
|
|
1400
|
+
mark_native_pretransformed_jsx(build_render_statements(body_nodes, true, state)),
|
|
1401
|
+
node,
|
|
1402
|
+
),
|
|
1403
|
+
),
|
|
1402
1404
|
);
|
|
1403
1405
|
|
|
1404
|
-
|
|
1406
|
+
// Setup statements were carried over verbatim, so re-visit the lowered
|
|
1407
|
+
// scope: TSRX-only nodes they contain (style elements, nested `@{ … }`
|
|
1408
|
+
// blocks) still need their own lowering before printing.
|
|
1409
|
+
const result = in_jsx_child_context(path)
|
|
1410
|
+
? to_jsx_expression_container(expression, node)
|
|
1411
|
+
: expression;
|
|
1412
|
+
return visit(result, state);
|
|
1405
1413
|
}
|
|
1406
1414
|
|
|
1407
1415
|
/**
|
|
@@ -1922,6 +1930,8 @@ function prepare_tsrx_fragment_styles(node, transform_context) {
|
|
|
1922
1930
|
if (!css) return null;
|
|
1923
1931
|
|
|
1924
1932
|
const style_refs = collect_style_ref_attributes(node);
|
|
1933
|
+
// `prune_css` inside marks the matching selectors as used/scoped; selectors
|
|
1934
|
+
// that match no element render commented out, like the Ripple target.
|
|
1925
1935
|
apply_css_definition_metadata(node, css, transform_context, style_refs.length > 0);
|
|
1926
1936
|
transform_context.stylesheets.push(css);
|
|
1927
1937
|
const fragment = annotate_tsrx_with_hash(
|
package/src/transform/scoping.js
CHANGED
|
@@ -7,20 +7,47 @@
|
|
|
7
7
|
|
|
8
8
|
import { walk } from 'zimmerframe';
|
|
9
9
|
import * as b from '../utils/builders.js';
|
|
10
|
+
import { mark_class_map_selectors } from './style-ref.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
* Mark
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `<style>` block is
|
|
13
|
+
* Mark selectors inside the stylesheet as "used" so `renderStylesheets` does
|
|
14
|
+
* not comment them out.
|
|
15
|
+
*
|
|
16
|
+
* For a free-standing `<style>` block every selector is marked: we skip
|
|
17
|
+
* selector-pruning because component boundaries can be dynamic — any selector
|
|
18
|
+
* authored inside the component's `<style>` block is considered intentional.
|
|
19
|
+
*
|
|
20
|
+
* When the `<style>` block is assigned to a variable (`is_style_expression`),
|
|
21
|
+
* the only selectors reachable through the generated class map are standalone
|
|
22
|
+
* class selectors — scoped (`.x`) or global-wrapped (`:global(.x)`). Anything
|
|
23
|
+
* else at the top level — element selectors, compound selectors, descendant
|
|
24
|
+
* chains, global tag selectors — never ends up in the class map and is marked
|
|
25
|
+
* unused for `renderStylesheets` to comment out. Selectors of nested rules ride
|
|
26
|
+
* along with their parent: they apply where the parent's class matched, and the
|
|
27
|
+
* whole rule is pruned when the parent itself is unreachable.
|
|
16
28
|
*
|
|
17
29
|
* @param {any} stylesheet
|
|
30
|
+
* @param {boolean} [is_style_expression]
|
|
18
31
|
* @returns {any}
|
|
19
32
|
*/
|
|
20
|
-
export function prepare_stylesheet_for_render(stylesheet) {
|
|
33
|
+
export function prepare_stylesheet_for_render(stylesheet, is_style_expression = false) {
|
|
34
|
+
if (is_style_expression) {
|
|
35
|
+
mark_class_map_selectors(stylesheet);
|
|
36
|
+
}
|
|
21
37
|
walk(stylesheet, null, {
|
|
22
|
-
_(node, { next }) {
|
|
38
|
+
_(node, { next, path }) {
|
|
23
39
|
if (node && node.metadata && typeof node.metadata === 'object') {
|
|
40
|
+
if (
|
|
41
|
+
is_style_expression &&
|
|
42
|
+
node.type === 'ComplexSelector' &&
|
|
43
|
+
is_unreachable_via_class_map(node, path)
|
|
44
|
+
) {
|
|
45
|
+
// Not in the generated class map. The analyzer pre-marks global
|
|
46
|
+
// selectors as used, so reset, and leave the subtree untouched —
|
|
47
|
+
// no `scoped` marks that would splice the hash into pruned output.
|
|
48
|
+
node.metadata.used = false;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
24
51
|
node.metadata.used = true;
|
|
25
52
|
if (node.type === 'RelativeSelector' && !node.metadata.is_global) {
|
|
26
53
|
node.metadata.scoped = true;
|
|
@@ -32,6 +59,37 @@ export function prepare_stylesheet_for_render(stylesheet) {
|
|
|
32
59
|
return stylesheet;
|
|
33
60
|
}
|
|
34
61
|
|
|
62
|
+
/**
|
|
63
|
+
* True when a selector of a style expression should be pruned because nothing
|
|
64
|
+
* reachable through the generated class map can match it. The class map
|
|
65
|
+
* collection in `style-ref.js` is the single decider of what the map exposes:
|
|
66
|
+
* it marks the carrying prelude-level selectors with `class_map_selector`.
|
|
67
|
+
* The remaining cases are structural, not class-shaped: selectors of nested
|
|
68
|
+
* rules ride along with their parent (the whole rule is pruned when the parent
|
|
69
|
+
* is unreachable), selectors inside another selector's arguments belong to
|
|
70
|
+
* their enclosing prelude-level selector, and a bare `:global` block prelude
|
|
71
|
+
* is kept because its contents render unscoped as authored and cannot be
|
|
72
|
+
* pruned selector-by-selector.
|
|
73
|
+
*
|
|
74
|
+
* @param {any} complex_selector
|
|
75
|
+
* @param {any[]} path
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
function is_unreachable_via_class_map(complex_selector, path) {
|
|
79
|
+
if (complex_selector.metadata.class_map_selector) return false;
|
|
80
|
+
if (complex_selector.metadata.rule?.metadata?.parent_rule != null) return false;
|
|
81
|
+
if (path.some((parent) => parent.type === 'ComplexSelector')) return false;
|
|
82
|
+
|
|
83
|
+
if (complex_selector.children?.length === 1) {
|
|
84
|
+
const first = complex_selector.children[0]?.selectors?.[0];
|
|
85
|
+
if (first?.type === 'PseudoClassSelector' && first.name === 'global' && first.args === null) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
35
93
|
/**
|
|
36
94
|
* @param {any} node
|
|
37
95
|
* @returns {boolean}
|
|
@@ -260,22 +260,43 @@ function collect_style_class_map_entries(css) {
|
|
|
260
260
|
return entries;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Stamp `class_map_selector` on the prelude-level selectors whose classes the
|
|
265
|
+
* class map exposes, without building the map. Runs the same collection as
|
|
266
|
+
* `create_style_class_map_from_stylesheet`, so marking and the generated map
|
|
267
|
+
* always agree; calling both is harmless.
|
|
268
|
+
*
|
|
269
|
+
* @param {any} css
|
|
270
|
+
* @returns {void}
|
|
271
|
+
*/
|
|
272
|
+
export function mark_class_map_selectors(css) {
|
|
273
|
+
collect_rule_class_map_entries(css, new Map());
|
|
274
|
+
}
|
|
275
|
+
|
|
263
276
|
/**
|
|
264
277
|
* @param {any} node
|
|
265
278
|
* @param {Map<string, any>} entries
|
|
279
|
+
* @param {any} [enclosing_selector] the nearest prelude-level selector; classes
|
|
280
|
+
* found inside another selector (e.g. in `:global(...)` args) mark it as the
|
|
281
|
+
* selector that carries their class map entry
|
|
266
282
|
* @returns {void}
|
|
267
283
|
*/
|
|
268
|
-
function collect_rule_class_map_entries(node, entries) {
|
|
284
|
+
function collect_rule_class_map_entries(node, entries, enclosing_selector = null) {
|
|
269
285
|
if (!node || typeof node !== 'object') return;
|
|
270
286
|
|
|
271
287
|
if (Array.isArray(node)) {
|
|
272
|
-
for (const child of node) collect_rule_class_map_entries(child, entries);
|
|
288
|
+
for (const child of node) collect_rule_class_map_entries(child, entries, enclosing_selector);
|
|
273
289
|
return;
|
|
274
290
|
}
|
|
275
291
|
|
|
276
292
|
if (node.type === 'ComplexSelector') {
|
|
293
|
+
enclosing_selector ??= node;
|
|
277
294
|
const class_selector = get_standalone_class_selector(node);
|
|
278
295
|
if (class_selector) {
|
|
296
|
+
// Mark the prelude-level selector for every occurrence (not just the
|
|
297
|
+
// deduped first) so the render preparation of style expressions keeps
|
|
298
|
+
// exactly the selectors whose classes the map exposes.
|
|
299
|
+
(enclosing_selector.metadata ??= {}).class_map_selector = true;
|
|
279
300
|
const name = class_selector.name.replace(regex_backslash_and_following_character, '$1');
|
|
280
301
|
if (!entries.has(name)) {
|
|
281
302
|
entries.set(name, {
|
|
@@ -295,7 +316,7 @@ function collect_rule_class_map_entries(node, entries) {
|
|
|
295
316
|
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
296
317
|
continue;
|
|
297
318
|
}
|
|
298
|
-
collect_rule_class_map_entries(node[key], entries);
|
|
319
|
+
collect_rule_class_map_entries(node[key], entries, enclosing_selector);
|
|
299
320
|
}
|
|
300
321
|
}
|
|
301
322
|
|
package/types/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { TSESTree } from '@typescript-eslint/types';
|
|
|
4
4
|
import type { Parse } from './parse.js';
|
|
5
5
|
import type * as ESRap from 'esrap';
|
|
6
6
|
import type { Position } from 'acorn';
|
|
7
|
-
import type { RequireAllOrNone } from '
|
|
7
|
+
import type { RequireAllOrNone } from './helpers';
|
|
8
8
|
import type {
|
|
9
9
|
JsxPlatform,
|
|
10
10
|
JsxPlatformHooks,
|