ripple 0.2.185 → 0.2.186
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 +2 -2
- package/src/compiler/index.d.ts +11 -2
- package/src/compiler/phases/1-parse/index.js +8 -13
- package/src/compiler/phases/2-analyze/css-analyze.js +2 -2
- package/src/compiler/phases/2-analyze/index.js +0 -1
- package/src/compiler/phases/2-analyze/prune.js +48 -3
- package/src/compiler/phases/3-transform/client/index.js +8 -7
- package/src/compiler/phases/3-transform/segments.js +313 -62
- package/src/compiler/phases/3-transform/stylesheet.js +17 -15
- package/src/compiler/source-map-utils.js +139 -65
- package/src/compiler/types/index.d.ts +22 -0
- package/src/compiler/types/parse.d.ts +6 -0
- package/src/compiler/utils.js +4 -2
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Ripple is an elegant TypeScript UI framework",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.186",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -86,6 +86,6 @@
|
|
|
86
86
|
"vscode-languageserver-types": "^3.17.5"
|
|
87
87
|
},
|
|
88
88
|
"peerDependencies": {
|
|
89
|
-
"ripple": "0.2.
|
|
89
|
+
"ripple": "0.2.186"
|
|
90
90
|
}
|
|
91
91
|
}
|
package/src/compiler/index.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface CompileResult {
|
|
|
24
24
|
css: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export interface DefinitionLocation {
|
|
28
|
+
embeddedId: string; // e.g., 'style_0', 'style_1'
|
|
29
|
+
start: number; // start offset
|
|
30
|
+
end: number; // end offset
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
export interface PluginActionOverrides {
|
|
28
34
|
/** Whether to enable word document highlighting for this mapping */
|
|
29
35
|
wordHighlight?: {
|
|
@@ -40,13 +46,17 @@ export interface PluginActionOverrides {
|
|
|
40
46
|
/** Custom definition info for this mapping, false to disable */
|
|
41
47
|
definition?:
|
|
42
48
|
| {
|
|
43
|
-
description
|
|
49
|
+
description?: string; // just for reference
|
|
50
|
+
// Generic location for embedded content (CSS, etc.)
|
|
51
|
+
location?: DefinitionLocation;
|
|
44
52
|
}
|
|
45
53
|
| false;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
export interface CustomMappingData extends PluginActionOverrides {
|
|
49
57
|
generatedLengths: number[];
|
|
58
|
+
embeddedId?: string; // e.g. css regions: 'style_0', 'style_1', etc.
|
|
59
|
+
content?: string; // (e.g., css code)
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
export interface MappingData extends VolarCodeInformation {
|
|
@@ -61,7 +71,6 @@ export interface VolarMappingsResult {
|
|
|
61
71
|
code: string;
|
|
62
72
|
mappings: CodeMapping[];
|
|
63
73
|
cssMappings: CodeMapping[];
|
|
64
|
-
cssSources: string[];
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
/**
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
/** @import * as AST from 'estree' */
|
|
2
|
-
/** @import * as ESTreeJSX from 'estree-jsx' */
|
|
3
|
-
/** @import { Parse } from '#parser' */
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
@import * as AST from 'estree'
|
|
3
|
+
@import * as ESTreeJSX from 'estree-jsx'
|
|
4
|
+
@import { Parse } from '#parser'
|
|
5
|
+
@import { RipplePluginConfig } from '#compiler';
|
|
6
|
+
@import { ParseOptions } from 'ripple/compiler'
|
|
7
|
+
*/
|
|
11
8
|
|
|
12
9
|
import * as acorn from 'acorn';
|
|
13
10
|
import { tsPlugin } from '@sveltejs/acorn-typescript';
|
|
@@ -1741,6 +1738,7 @@ function RipplePlugin(config) {
|
|
|
1741
1738
|
throw new Error('Components can only have one style tag');
|
|
1742
1739
|
}
|
|
1743
1740
|
component.css = parsed_css;
|
|
1741
|
+
/** @type {AST.Element} */ (element).metadata.styleScopeHash = parsed_css.hash;
|
|
1744
1742
|
}
|
|
1745
1743
|
|
|
1746
1744
|
const newLines = content.match(regex_newline_characters)?.length;
|
|
@@ -2167,6 +2165,7 @@ function RipplePlugin(config) {
|
|
|
2167
2165
|
* @param {string} source
|
|
2168
2166
|
* @param {AST.CommentWithLocation[]} comments
|
|
2169
2167
|
* @param {number} [index=0] - Starting index
|
|
2168
|
+
* @returns {{onComment: Parse.Options['onComment'], add_comments: (ast: AST.Node) => void}}
|
|
2170
2169
|
*/
|
|
2171
2170
|
function get_comment_handlers(source, comments, index = 0) {
|
|
2172
2171
|
/**
|
|
@@ -2230,11 +2229,7 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2230
2229
|
context,
|
|
2231
2230
|
}));
|
|
2232
2231
|
|
|
2233
|
-
/**
|
|
2234
|
-
* @param {AST.Node} ast
|
|
2235
|
-
*/
|
|
2236
2232
|
walk(ast, null, {
|
|
2237
|
-
/** @param {AST.Node} node */
|
|
2238
2233
|
_(node, { next, path }) {
|
|
2239
2234
|
const metadata = node?.metadata;
|
|
2240
2235
|
|
|
@@ -41,7 +41,7 @@ function is_global(relative_selector) {
|
|
|
41
41
|
export function analyze_css(css) {
|
|
42
42
|
walk(
|
|
43
43
|
css,
|
|
44
|
-
|
|
44
|
+
/** @type {{ rule: AST.CSS.Rule | null }} */ ({ rule: null }),
|
|
45
45
|
{
|
|
46
46
|
Rule(node, context) {
|
|
47
47
|
node.metadata.parent_rule = context.state.rule;
|
|
@@ -90,7 +90,7 @@ export function analyze_css(css) {
|
|
|
90
90
|
// Set the rule metadata before analyzing children
|
|
91
91
|
node.metadata.rule = context.state.rule;
|
|
92
92
|
|
|
93
|
-
context.next(); //
|
|
93
|
+
context.next(); // analyze relevant selectors first
|
|
94
94
|
|
|
95
95
|
{
|
|
96
96
|
const global = node.children.find(is_global);
|
|
@@ -5,13 +5,17 @@
|
|
|
5
5
|
import { walk } from 'zimmerframe';
|
|
6
6
|
import { is_element_dom_element } from '../../utils.js';
|
|
7
7
|
|
|
8
|
-
const seen = new Set();
|
|
9
8
|
const regex_backslash_and_following_character = /\\(.)/g;
|
|
10
9
|
/** @type {Direction} */
|
|
11
10
|
const FORWARD = 0;
|
|
12
11
|
/** @type {Direction} */
|
|
13
12
|
const BACKWARD = 1;
|
|
14
13
|
|
|
14
|
+
// this will be set for every prune_css call
|
|
15
|
+
// since the code is synchronous, this is safe
|
|
16
|
+
/** @type {string} */
|
|
17
|
+
let css_hash;
|
|
18
|
+
|
|
15
19
|
// CSS selector constants
|
|
16
20
|
/**
|
|
17
21
|
* @param {number} start
|
|
@@ -196,6 +200,47 @@ function apply_selector(relative_selectors, rule, element, direction) {
|
|
|
196
200
|
if (matched) {
|
|
197
201
|
if (!is_outer_global(relative_selector)) {
|
|
198
202
|
relative_selector.metadata.scoped = true;
|
|
203
|
+
|
|
204
|
+
// Store scoped class information on element for language server features
|
|
205
|
+
if (!relative_selector.metadata.is_global && !relative_selector.metadata.is_global_like) {
|
|
206
|
+
// Extract class selectors from the relative selector
|
|
207
|
+
for (const selector of relative_selector.selectors) {
|
|
208
|
+
if (selector.type === 'ClassSelector') {
|
|
209
|
+
const name = selector.name.replace(regex_backslash_and_following_character, '$1');
|
|
210
|
+
|
|
211
|
+
if (!element.metadata.css) {
|
|
212
|
+
element.metadata.css = {
|
|
213
|
+
scopedClasses: new Map(),
|
|
214
|
+
topScopedClasses: new Map(),
|
|
215
|
+
hash: css_hash,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Store class name → CSS location in scopedClasses
|
|
220
|
+
if (!element.metadata.css.scopedClasses.has(name)) {
|
|
221
|
+
element.metadata.css.scopedClasses.set(name, {
|
|
222
|
+
start: selector.start,
|
|
223
|
+
end: selector.end,
|
|
224
|
+
selector: selector,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Also store in topScopedClasses if standalone
|
|
229
|
+
// Standalone = only this ClassSelector in the RelativeSelector, no pseudo-classes/elements
|
|
230
|
+
const isStandalone =
|
|
231
|
+
relative_selector.selectors.length === 1 &&
|
|
232
|
+
relative_selector.selectors[0] === selector;
|
|
233
|
+
|
|
234
|
+
if (isStandalone && !element.metadata.css.topScopedClasses.has(name)) {
|
|
235
|
+
element.metadata.css.topScopedClasses.set(name, {
|
|
236
|
+
start: selector.start,
|
|
237
|
+
end: selector.end,
|
|
238
|
+
selector: selector,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
199
244
|
}
|
|
200
245
|
|
|
201
246
|
element.metadata.scoped = true;
|
|
@@ -1005,6 +1050,8 @@ function rule_has_animation(rule) {
|
|
|
1005
1050
|
* @return {void}
|
|
1006
1051
|
*/
|
|
1007
1052
|
export function prune_css(css, element) {
|
|
1053
|
+
css_hash = css.hash;
|
|
1054
|
+
|
|
1008
1055
|
/** @type {Visitors<AST.CSS.Node, null>} */
|
|
1009
1056
|
const visitors = {
|
|
1010
1057
|
Rule(node, context) {
|
|
@@ -1017,8 +1064,6 @@ export function prune_css(css, element) {
|
|
|
1017
1064
|
ComplexSelector(node, context) {
|
|
1018
1065
|
const selectors = get_relative_selectors(node);
|
|
1019
1066
|
|
|
1020
|
-
seen.clear();
|
|
1021
|
-
|
|
1022
1067
|
const rule = /** @type {AST.CSS.Rule} */ (node.metadata.rule);
|
|
1023
1068
|
|
|
1024
1069
|
if (apply_selector(selectors, rule, element, BACKWARD) || rule_has_animation(rule)) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
/** @import * as AST from 'estree' */
|
|
2
|
-
/** @import * as ESTreeJSX from 'estree-jsx' */
|
|
3
|
-
/** @import { SourceMapMappings } from '@jridgewell/sourcemap-codec' */
|
|
4
|
-
/** @import * as ESRap from 'esrap' */
|
|
5
1
|
/**
|
|
2
|
+
@import * as AST from 'estree';
|
|
3
|
+
@import * as ESTreeJSX from 'estree-jsx';
|
|
4
|
+
@import { SourceMapMappings } from '@jridgewell/sourcemap-codec';
|
|
6
5
|
@import {
|
|
7
6
|
AnalysisResult,
|
|
8
7
|
TransformClientContext,
|
|
@@ -14,8 +13,10 @@
|
|
|
14
13
|
} from '#compiler';
|
|
15
14
|
*/
|
|
16
15
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
16
|
+
/**
|
|
17
|
+
@typedef {Map<number, {offset: number, delta: number}>} PostProcessingChanges;
|
|
18
|
+
@typedef {number[]} LineOffsets;
|
|
19
|
+
*/
|
|
19
20
|
|
|
20
21
|
import { walk } from 'zimmerframe';
|
|
21
22
|
import path from 'node:path';
|
|
@@ -2996,7 +2997,7 @@ function create_tsx_with_typescript_support() {
|
|
|
2996
2997
|
context.write(node.computed ? ']: ' : ': ');
|
|
2997
2998
|
context.visit(node.value);
|
|
2998
2999
|
} else {
|
|
2999
|
-
base_tsx.Property?.(node,
|
|
3000
|
+
base_tsx.Property?.(node, context);
|
|
3000
3001
|
}
|
|
3001
3002
|
} else {
|
|
3002
3003
|
// Use default handler for non-component properties
|
|
@@ -1,22 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
@import { CustomMappingData, PluginActionOverrides } from 'ripple/compiler';
|
|
3
|
-
@import { DocumentHighlightKind } from 'vscode-languageserver-types';
|
|
4
2
|
@import * as AST from 'estree';
|
|
5
3
|
@import * as ESTreeJSX from 'estree-jsx';
|
|
6
|
-
@import {
|
|
7
|
-
@import {CodeMapping as VolarCodeMapping} from '@volar/language-core';
|
|
8
|
-
|
|
4
|
+
@import { DocumentHighlightKind } from 'vscode-languageserver-types';
|
|
5
|
+
@import { CodeMapping as VolarCodeMapping } from '@volar/language-core';
|
|
6
|
+
@import { SourceMapMappings } from '@jridgewell/sourcemap-codec';
|
|
7
|
+
@import {
|
|
8
|
+
CustomMappingData,
|
|
9
|
+
PluginActionOverrides,
|
|
10
|
+
MappingData,
|
|
11
|
+
CodeMapping,
|
|
12
|
+
VolarMappingsResult,
|
|
13
|
+
} from 'ripple/compiler';
|
|
14
|
+
@import { PostProcessingChanges } from './client/index.js';
|
|
15
|
+
*/
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
@typedef {{
|
|
19
|
+
start: number,
|
|
20
|
+
end: number,
|
|
21
|
+
content: string,
|
|
22
|
+
id: string,
|
|
23
|
+
}} CssSourceRegion;
|
|
24
|
+
@typedef {{
|
|
25
|
+
source: string | null | undefined;
|
|
26
|
+
generated: string;
|
|
27
|
+
is_full_import_statement?: boolean;
|
|
28
|
+
loc: AST.SourceLocation;
|
|
29
|
+
end_loc?: AST.SourceLocation;
|
|
30
|
+
metadata?: PluginActionOverrides;
|
|
31
|
+
}} Token;
|
|
32
|
+
@typedef {{
|
|
33
|
+
name: string,
|
|
34
|
+
line: number,
|
|
35
|
+
column: number,
|
|
36
|
+
offset: number,
|
|
37
|
+
length: number,
|
|
38
|
+
sourceOffset: number,
|
|
39
|
+
}} TokenClass
|
|
40
|
+
@typedef {Map<string, AST.Element['metadata']['css']>} CssElementInfo
|
|
16
41
|
*/
|
|
17
42
|
|
|
18
43
|
import { walk } from 'zimmerframe';
|
|
19
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
build_src_to_gen_map,
|
|
46
|
+
get_generated_position,
|
|
47
|
+
offset_to_line_col,
|
|
48
|
+
} from '../../source-map-utils.js';
|
|
20
49
|
|
|
21
50
|
/** @type {VolarCodeMapping['data']} */
|
|
22
51
|
export const mapping_data = {
|
|
@@ -28,6 +57,15 @@ export const mapping_data = {
|
|
|
28
57
|
format: false,
|
|
29
58
|
};
|
|
30
59
|
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} [hash]
|
|
62
|
+
* @param {string} [fallback]
|
|
63
|
+
* @returns `style-${hash | fallback}`
|
|
64
|
+
*/
|
|
65
|
+
function get_style_region_id(hash, fallback) {
|
|
66
|
+
return `style-${hash || fallback}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
31
69
|
/**
|
|
32
70
|
* Converts line/column positions to byte offsets
|
|
33
71
|
* @param {string} text
|
|
@@ -62,47 +100,205 @@ function loc_to_offset(line, column, line_offsets) {
|
|
|
62
100
|
/**
|
|
63
101
|
* Extract CSS source regions from style elements in the AST
|
|
64
102
|
* @param {AST.Node} ast - The parsed AST
|
|
65
|
-
* @param {
|
|
66
|
-
* @param {
|
|
67
|
-
*
|
|
103
|
+
* @param {number[]} src_line_offsets
|
|
104
|
+
* @param {{
|
|
105
|
+
* regions: CssSourceRegion[],
|
|
106
|
+
* css_element_info: CssElementInfo,
|
|
107
|
+
* }} param2
|
|
108
|
+
* @returns {void}
|
|
68
109
|
*/
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
const regions = [];
|
|
72
|
-
|
|
110
|
+
function visit_source_ast(ast, src_line_offsets, { regions, css_element_info }) {
|
|
111
|
+
let region_id = 0;
|
|
73
112
|
walk(ast, null, {
|
|
74
|
-
Element(node) {
|
|
113
|
+
Element(node, context) {
|
|
75
114
|
// Check if this is a style element with CSS content
|
|
76
115
|
if (node.id?.name === 'style' && node.css) {
|
|
77
116
|
const openLoc = /** @type {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} */ (
|
|
78
117
|
node.openingElement
|
|
79
118
|
).loc;
|
|
80
|
-
const cssStart = loc_to_offset(openLoc.end.line, openLoc.end.column,
|
|
119
|
+
const cssStart = loc_to_offset(openLoc.end.line, openLoc.end.column, src_line_offsets);
|
|
81
120
|
|
|
82
121
|
const closeLoc = /** @type {ESTreeJSX.JSXClosingElement & AST.NodeWithLocation} */ (
|
|
83
122
|
node.closingElement
|
|
84
123
|
).loc;
|
|
85
|
-
const cssEnd = loc_to_offset(
|
|
86
|
-
closeLoc.start.line,
|
|
87
|
-
closeLoc.start.column,
|
|
88
|
-
source_line_offsets,
|
|
89
|
-
);
|
|
124
|
+
const cssEnd = loc_to_offset(closeLoc.start.line, closeLoc.start.column, src_line_offsets);
|
|
90
125
|
|
|
91
126
|
regions.push({
|
|
92
127
|
start: cssStart,
|
|
93
128
|
end: cssEnd,
|
|
94
129
|
content: node.css,
|
|
130
|
+
id: get_style_region_id(node.metadata.styleScopeHash, `head-${region_id}`),
|
|
95
131
|
});
|
|
96
132
|
}
|
|
133
|
+
|
|
134
|
+
context.next();
|
|
97
135
|
},
|
|
98
|
-
|
|
136
|
+
Attribute(node, context) {
|
|
137
|
+
const element = context.path?.find((n) => n.type === 'Element');
|
|
138
|
+
if (element?.metadata?.css?.scopedClasses) {
|
|
139
|
+
// we don't need to check is_element_dom_element(node)
|
|
140
|
+
// since scopedClasses are added during pruning only to DOM elements
|
|
141
|
+
const css = element.metadata.css;
|
|
142
|
+
const { line, column } = node.value?.loc?.start ?? {};
|
|
99
143
|
|
|
100
|
-
|
|
144
|
+
if (line === undefined || column === undefined) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
css_element_info.set(`${line}:${column}`, css);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
});
|
|
101
152
|
}
|
|
102
153
|
|
|
103
154
|
/**
|
|
104
|
-
*
|
|
155
|
+
* Extract individual class names and their offsets from class attribute values
|
|
156
|
+
* Handles: "foo bar", { foo: true }, ['foo', { bar: true }], etc.
|
|
157
|
+
*
|
|
158
|
+
* @param {AST.Node} node - The attribute value node
|
|
159
|
+
* @param {ReturnType<typeof build_src_to_gen_map>[0]} src_to_gen_map
|
|
160
|
+
* @param {number[]} gen_line_offsets
|
|
161
|
+
* @param {number[]} src_line_offsets
|
|
162
|
+
* @returns {TokenClass[]}
|
|
105
163
|
*/
|
|
164
|
+
function extract_classes(node, src_to_gen_map, gen_line_offsets, src_line_offsets) {
|
|
165
|
+
/** @type {TokenClass[]} */
|
|
166
|
+
const classes = [];
|
|
167
|
+
|
|
168
|
+
switch (node.type) {
|
|
169
|
+
case 'Literal': {
|
|
170
|
+
// Static: class="foo bar baz"
|
|
171
|
+
|
|
172
|
+
const content = node.raw ?? '';
|
|
173
|
+
let text = content;
|
|
174
|
+
let textOffset = 0;
|
|
175
|
+
|
|
176
|
+
// Remove quotes
|
|
177
|
+
if (
|
|
178
|
+
(content.startsWith(`'`) && content.endsWith(`'`)) ||
|
|
179
|
+
(content.startsWith(`"`) && content.endsWith(`"`)) ||
|
|
180
|
+
(content.startsWith('`') && content.endsWith('`'))
|
|
181
|
+
) {
|
|
182
|
+
text = content.slice(1, -1);
|
|
183
|
+
textOffset = 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Split by whitespace
|
|
187
|
+
const classNames = text.split(/\s+/).filter((c) => c.length > 0);
|
|
188
|
+
const nodeSrcStart = /** @type {AST.Position} */ (node.loc?.start);
|
|
189
|
+
|
|
190
|
+
let currentPos = 0;
|
|
191
|
+
const nodeGenStart = get_generated_position(
|
|
192
|
+
nodeSrcStart.line,
|
|
193
|
+
nodeSrcStart.column,
|
|
194
|
+
src_to_gen_map,
|
|
195
|
+
);
|
|
196
|
+
const offset = loc_to_offset(nodeGenStart.line, nodeGenStart.column, gen_line_offsets);
|
|
197
|
+
const sourceOffset = loc_to_offset(nodeSrcStart.line, nodeSrcStart.column, src_line_offsets);
|
|
198
|
+
|
|
199
|
+
for (const name of classNames) {
|
|
200
|
+
const classStart = text.indexOf(name, currentPos);
|
|
201
|
+
const classOffset = offset + textOffset + classStart;
|
|
202
|
+
const classSourceOffset = sourceOffset + textOffset + classStart;
|
|
203
|
+
const { line, column } = offset_to_line_col(classOffset, gen_line_offsets);
|
|
204
|
+
|
|
205
|
+
classes.push({
|
|
206
|
+
name,
|
|
207
|
+
line,
|
|
208
|
+
column,
|
|
209
|
+
offset: classOffset,
|
|
210
|
+
length: name.length,
|
|
211
|
+
sourceOffset: classSourceOffset,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
currentPos = classStart + name.length;
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case 'ObjectExpression': {
|
|
220
|
+
// Dynamic: class={{ foo: true, bar: @show }}
|
|
221
|
+
for (const prop of node.properties) {
|
|
222
|
+
if (prop.type === 'Property' && prop.key) {
|
|
223
|
+
const key = prop.key;
|
|
224
|
+
if (key.type === 'Identifier' && key.name && key.loc) {
|
|
225
|
+
const nodeSrcStart = /** @type {AST.Position} */ (key.loc?.start);
|
|
226
|
+
const nodeGenStart = get_generated_position(
|
|
227
|
+
nodeSrcStart.line,
|
|
228
|
+
nodeSrcStart.column,
|
|
229
|
+
src_to_gen_map,
|
|
230
|
+
);
|
|
231
|
+
const offset = loc_to_offset(nodeGenStart.line, nodeGenStart.column, gen_line_offsets);
|
|
232
|
+
const sourceOffset = loc_to_offset(
|
|
233
|
+
nodeSrcStart.line,
|
|
234
|
+
nodeSrcStart.column,
|
|
235
|
+
src_line_offsets,
|
|
236
|
+
);
|
|
237
|
+
const { line, column } = offset_to_line_col(offset, gen_line_offsets);
|
|
238
|
+
|
|
239
|
+
classes.push({
|
|
240
|
+
name: key.name,
|
|
241
|
+
line,
|
|
242
|
+
column,
|
|
243
|
+
offset,
|
|
244
|
+
length: key.name.length,
|
|
245
|
+
sourceOffset,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
case 'ArrayExpression': {
|
|
254
|
+
// Dynamic: class={['foo', { bar: true }]}
|
|
255
|
+
for (const el of node.elements) {
|
|
256
|
+
if (el) {
|
|
257
|
+
classes.push(...extract_classes(el, src_to_gen_map, gen_line_offsets, src_line_offsets));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case 'ConditionalExpression': {
|
|
264
|
+
// Conditional: class={@show ? 'active' : 'inactive'}
|
|
265
|
+
if (node.consequent) {
|
|
266
|
+
classes.push(
|
|
267
|
+
...extract_classes(node.consequent, src_to_gen_map, gen_line_offsets, src_line_offsets),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
if (node.alternate) {
|
|
271
|
+
classes.push(
|
|
272
|
+
...extract_classes(node.alternate, src_to_gen_map, gen_line_offsets, src_line_offsets),
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case 'LogicalExpression': {
|
|
279
|
+
// Logical: class={[@show && 'active']}
|
|
280
|
+
if (node.operator === '&&' && node.right) {
|
|
281
|
+
classes.push(
|
|
282
|
+
...extract_classes(node.right, src_to_gen_map, gen_line_offsets, src_line_offsets),
|
|
283
|
+
);
|
|
284
|
+
} else if (node.operator === '||') {
|
|
285
|
+
if (node.left) {
|
|
286
|
+
classes.push(
|
|
287
|
+
...extract_classes(node.left, src_to_gen_map, gen_line_offsets, src_line_offsets),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (node.right) {
|
|
291
|
+
classes.push(
|
|
292
|
+
...extract_classes(node.right, src_to_gen_map, gen_line_offsets, src_line_offsets),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return classes;
|
|
301
|
+
}
|
|
106
302
|
|
|
107
303
|
/**
|
|
108
304
|
* Create Volar mappings by walking the transformed AST
|
|
@@ -110,7 +306,7 @@ function extractCssSourceRegions(ast, source, source_line_offsets) {
|
|
|
110
306
|
* @param {AST.Node} ast_from_source - The original AST from source
|
|
111
307
|
* @param {string} source - Original source code
|
|
112
308
|
* @param {string} generated_code - Generated code (returned in output, not used for searching)
|
|
113
|
-
* @param {
|
|
309
|
+
* @param {SourceMapMappings} source_map - Esrap source map for accurate position lookup
|
|
114
310
|
* @param {PostProcessingChanges } post_processing_changes - Optional post-processing changes
|
|
115
311
|
* @param {number[]} line_offsets - Pre-computed line offsets array for generated code
|
|
116
312
|
* @returns {VolarMappingsResult}
|
|
@@ -120,7 +316,7 @@ export function convert_source_map_to_mappings(
|
|
|
120
316
|
ast_from_source,
|
|
121
317
|
source,
|
|
122
318
|
generated_code,
|
|
123
|
-
|
|
319
|
+
source_map,
|
|
124
320
|
post_processing_changes,
|
|
125
321
|
line_offsets,
|
|
126
322
|
) {
|
|
@@ -128,7 +324,8 @@ export function convert_source_map_to_mappings(
|
|
|
128
324
|
const mappings = [];
|
|
129
325
|
let isImportDeclarationPresent = false;
|
|
130
326
|
|
|
131
|
-
const
|
|
327
|
+
const src_line_offsets = build_line_offsets(source);
|
|
328
|
+
const gen_line_offsets = build_line_offsets(generated_code);
|
|
132
329
|
|
|
133
330
|
/**
|
|
134
331
|
* Convert generated line/column to byte offset using pre-computed line_offsets
|
|
@@ -141,25 +338,24 @@ export function convert_source_map_to_mappings(
|
|
|
141
338
|
return line_offsets[line - 1] + column;
|
|
142
339
|
};
|
|
143
340
|
|
|
144
|
-
const
|
|
145
|
-
|
|
341
|
+
const [src_to_gen_map] = build_src_to_gen_map(
|
|
342
|
+
source_map,
|
|
146
343
|
post_processing_changes,
|
|
147
344
|
line_offsets,
|
|
345
|
+
generated_code,
|
|
148
346
|
);
|
|
149
347
|
|
|
150
|
-
|
|
151
|
-
// All tokens must have source/generated text and loc property for accurate positioning
|
|
152
|
-
/**
|
|
153
|
-
* @type {Array<{
|
|
154
|
-
* source: string | null | undefined,
|
|
155
|
-
* generated: string,
|
|
156
|
-
* is_full_import_statement?: boolean,
|
|
157
|
-
* loc: AST.SourceLocation,
|
|
158
|
-
* end_loc?: AST.SourceLocation,
|
|
159
|
-
* metadata?: PluginActionOverrides
|
|
160
|
-
* }>}
|
|
161
|
-
*/
|
|
348
|
+
/** @type {Token[]} */
|
|
162
349
|
const tokens = [];
|
|
350
|
+
/** @type {CssSourceRegion[]} */
|
|
351
|
+
const css_regions = [];
|
|
352
|
+
/** @type {CssElementInfo} */
|
|
353
|
+
const css_element_info = new Map();
|
|
354
|
+
|
|
355
|
+
visit_source_ast(ast_from_source, src_line_offsets, {
|
|
356
|
+
regions: css_regions,
|
|
357
|
+
css_element_info,
|
|
358
|
+
});
|
|
163
359
|
|
|
164
360
|
// We have to visit everything in generated order to maintain correct indices
|
|
165
361
|
|
|
@@ -310,11 +506,65 @@ export function convert_source_map_to_mappings(
|
|
|
310
506
|
visit(node.value);
|
|
311
507
|
}
|
|
312
508
|
} else {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
509
|
+
const attr =
|
|
510
|
+
node.name.name === 'class' && node.value?.type === 'JSXExpressionContainer'
|
|
511
|
+
? node.value.expression
|
|
512
|
+
: node.value;
|
|
513
|
+
|
|
514
|
+
const css = attr
|
|
515
|
+
? css_element_info.get(`${attr.loc?.start.line}:${attr.loc?.start.column}`)
|
|
516
|
+
: null;
|
|
517
|
+
|
|
518
|
+
if (attr && css) {
|
|
519
|
+
// Extract class names from the attribute value
|
|
520
|
+
const classes = extract_classes(
|
|
521
|
+
attr,
|
|
522
|
+
src_to_gen_map,
|
|
523
|
+
gen_line_offsets,
|
|
524
|
+
src_line_offsets,
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
// For each class name, look up CSS location and create token
|
|
528
|
+
for (const { name, line, column, offset, sourceOffset, length } of classes) {
|
|
529
|
+
const cssLocation = css.scopedClasses.get(name);
|
|
530
|
+
|
|
531
|
+
if (!cssLocation) {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
mappings.push({
|
|
536
|
+
sourceOffsets: [sourceOffset],
|
|
537
|
+
generatedOffsets: [offset],
|
|
538
|
+
lengths: [length],
|
|
539
|
+
data: {
|
|
540
|
+
...mapping_data,
|
|
541
|
+
customData: {
|
|
542
|
+
generatedLengths: [length],
|
|
543
|
+
hover: {
|
|
544
|
+
contents:
|
|
545
|
+
'```css\n.' +
|
|
546
|
+
name +
|
|
547
|
+
'\n```\n\nCSS class selector.\n\nUse **Cmd+Click** (macOS) or **Ctrl+Click** (Windows/Linux) to navigate to its definition.',
|
|
548
|
+
},
|
|
549
|
+
definition: {
|
|
550
|
+
description: `CSS class selector for '.${name}'`,
|
|
551
|
+
location: {
|
|
552
|
+
embeddedId: get_style_region_id(css.hash),
|
|
553
|
+
start: cssLocation.start,
|
|
554
|
+
end: cssLocation.end,
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
} else {
|
|
562
|
+
if (node.name) {
|
|
563
|
+
visit(node.name);
|
|
564
|
+
}
|
|
565
|
+
if (node.value) {
|
|
566
|
+
visit(node.value);
|
|
567
|
+
}
|
|
318
568
|
}
|
|
319
569
|
}
|
|
320
570
|
return;
|
|
@@ -1377,7 +1627,7 @@ export function convert_source_map_to_mappings(
|
|
|
1377
1627
|
const source_start = loc_to_offset(
|
|
1378
1628
|
token.loc.start.line,
|
|
1379
1629
|
token.loc.start.column,
|
|
1380
|
-
|
|
1630
|
+
src_line_offsets,
|
|
1381
1631
|
);
|
|
1382
1632
|
|
|
1383
1633
|
let source_length = source_text.length;
|
|
@@ -1389,15 +1639,15 @@ export function convert_source_map_to_mappings(
|
|
|
1389
1639
|
|
|
1390
1640
|
if (token.is_full_import_statement) {
|
|
1391
1641
|
const end_loc = /** @type {AST.SourceLocation} */ (token.end_loc).end;
|
|
1392
|
-
const source_end = loc_to_offset(end_loc.line, end_loc.column,
|
|
1642
|
+
const source_end = loc_to_offset(end_loc.line, end_loc.column, src_line_offsets);
|
|
1393
1643
|
|
|
1394
1644
|
// Look up where import keyword and source literal map to in generated code
|
|
1395
1645
|
const gen_start_pos = get_generated_position(
|
|
1396
1646
|
token.loc.start.line,
|
|
1397
1647
|
token.loc.start.column,
|
|
1398
|
-
|
|
1648
|
+
src_to_gen_map,
|
|
1399
1649
|
);
|
|
1400
|
-
const gen_end_pos = get_generated_position(end_loc.line, end_loc.column,
|
|
1650
|
+
const gen_end_pos = get_generated_position(end_loc.line, end_loc.column, src_to_gen_map);
|
|
1401
1651
|
|
|
1402
1652
|
gen_start = gen_loc_to_offset(gen_start_pos.line, gen_start_pos.column);
|
|
1403
1653
|
const gen_end = gen_loc_to_offset(gen_end_pos.line, gen_end_pos.column);
|
|
@@ -1418,7 +1668,7 @@ export function convert_source_map_to_mappings(
|
|
|
1418
1668
|
const gen_line_col = get_generated_position(
|
|
1419
1669
|
token.loc.start.line,
|
|
1420
1670
|
token.loc.start.column,
|
|
1421
|
-
|
|
1671
|
+
src_to_gen_map,
|
|
1422
1672
|
);
|
|
1423
1673
|
gen_start = gen_loc_to_offset(gen_line_col.line, gen_line_col.column);
|
|
1424
1674
|
|
|
@@ -1479,10 +1729,11 @@ export function convert_source_map_to_mappings(
|
|
|
1479
1729
|
});
|
|
1480
1730
|
}
|
|
1481
1731
|
|
|
1482
|
-
/** @type {
|
|
1483
|
-
const
|
|
1484
|
-
for (
|
|
1485
|
-
|
|
1732
|
+
/** @type {CodeMapping[]} */
|
|
1733
|
+
const cssMappings = [];
|
|
1734
|
+
for (let i = 0; i < css_regions.length; i++) {
|
|
1735
|
+
const region = css_regions[i];
|
|
1736
|
+
cssMappings.push({
|
|
1486
1737
|
sourceOffsets: [region.start],
|
|
1487
1738
|
generatedOffsets: [0],
|
|
1488
1739
|
lengths: [region.content.length],
|
|
@@ -1490,16 +1741,16 @@ export function convert_source_map_to_mappings(
|
|
|
1490
1741
|
...mapping_data,
|
|
1491
1742
|
customData: {
|
|
1492
1743
|
generatedLengths: [region.content.length],
|
|
1744
|
+
embeddedId: region.id,
|
|
1745
|
+
content: region.content,
|
|
1493
1746
|
},
|
|
1494
1747
|
},
|
|
1495
1748
|
});
|
|
1496
|
-
|
|
1497
|
-
cssResult.cssSources.push(region.content);
|
|
1498
1749
|
}
|
|
1499
1750
|
|
|
1500
1751
|
return {
|
|
1501
1752
|
code: generated_code,
|
|
1502
1753
|
mappings,
|
|
1503
|
-
|
|
1754
|
+
cssMappings,
|
|
1504
1755
|
};
|
|
1505
1756
|
}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
@import * as AST from 'estree';
|
|
3
|
+
@import { Visitors } from '#compiler';
|
|
4
|
+
*/
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
@typedef {{
|
|
8
|
+
code: MagicString;
|
|
9
|
+
hash: string;
|
|
10
|
+
minify: boolean;
|
|
11
|
+
selector: string;
|
|
12
|
+
keyframes: Record<string, {
|
|
13
|
+
indexes: number[];
|
|
14
|
+
local: boolean | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
specificity: {
|
|
17
|
+
bumped: boolean
|
|
18
|
+
}
|
|
19
|
+
}} State
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
22
|
import MagicString from 'magic-string';
|
|
@@ -1,9 +1,56 @@
|
|
|
1
1
|
import { decode } from '@jridgewell/sourcemap-codec';
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
@import { PostProcessingChanges, LineOffsets } from './phases/3-transform/client/index.js';
|
|
5
|
+
@import * as AST from 'estree';
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
@typedef {{
|
|
10
|
+
line: number,
|
|
11
|
+
column: number,
|
|
12
|
+
end_line: number,
|
|
13
|
+
end_column: number,
|
|
14
|
+
code: string,
|
|
15
|
+
metadata: {
|
|
16
|
+
css?: AST.Element['metadata']['css']
|
|
17
|
+
},
|
|
18
|
+
}} CodePosition
|
|
19
|
+
|
|
20
|
+
@typedef {Map<string, CodePosition[]>} CodeToGeneratedMap
|
|
21
|
+
@typedef {Map<string, {line: number, column: number}[]>} GeneratedToSourceMap
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Convert byte offset to line/column
|
|
26
|
+
* @param {number} offset
|
|
27
|
+
* @param {LineOffsets} line_offsets
|
|
28
|
+
* @returns {{ line: number, column: number }}
|
|
29
|
+
*/
|
|
30
|
+
export const offset_to_line_col = (offset, line_offsets) => {
|
|
31
|
+
// Binary search
|
|
32
|
+
let left = 0;
|
|
33
|
+
let right = line_offsets.length - 1;
|
|
34
|
+
let line = 1;
|
|
35
|
+
|
|
36
|
+
while (left <= right) {
|
|
37
|
+
const mid = Math.floor((left + right) / 2);
|
|
38
|
+
if (
|
|
39
|
+
offset >= line_offsets[mid] &&
|
|
40
|
+
(mid === line_offsets.length - 1 || offset < line_offsets[mid + 1])
|
|
41
|
+
) {
|
|
42
|
+
line = mid + 1;
|
|
43
|
+
break;
|
|
44
|
+
} else if (offset < line_offsets[mid]) {
|
|
45
|
+
right = mid - 1;
|
|
46
|
+
} else {
|
|
47
|
+
left = mid + 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
4
50
|
|
|
5
|
-
|
|
6
|
-
|
|
51
|
+
const column = offset - line_offsets[line - 1];
|
|
52
|
+
return { line, column };
|
|
53
|
+
};
|
|
7
54
|
|
|
8
55
|
/**
|
|
9
56
|
* Build a source-to-generated position lookup map from an esrap source map
|
|
@@ -11,11 +58,19 @@ import { decode } from '@jridgewell/sourcemap-codec';
|
|
|
11
58
|
* @param {object} source_map - The source map object from esrap (v3 format)
|
|
12
59
|
* @param {PostProcessingChanges} post_processing_changes - Optional post-processing changes to apply
|
|
13
60
|
* @param {LineOffsets} line_offsets - Pre-computed line offsets array
|
|
14
|
-
* @
|
|
61
|
+
* @param {string} generated_code - The final generated code (after post-processing)
|
|
62
|
+
* @returns {[CodeToGeneratedMap, GeneratedToSourceMap]} Tuple of [source-to-generated map, generated-to-source map]
|
|
15
63
|
*/
|
|
16
|
-
export function
|
|
17
|
-
|
|
64
|
+
export function build_src_to_gen_map(
|
|
65
|
+
source_map,
|
|
66
|
+
post_processing_changes,
|
|
67
|
+
line_offsets,
|
|
68
|
+
generated_code,
|
|
69
|
+
) {
|
|
70
|
+
/** @type {CodeToGeneratedMap} */
|
|
18
71
|
const map = new Map();
|
|
72
|
+
/** @type {GeneratedToSourceMap} */
|
|
73
|
+
const reverse_map = new Map();
|
|
19
74
|
|
|
20
75
|
// Decode the VLQ-encoded mappings string
|
|
21
76
|
// @ts-ignore
|
|
@@ -31,101 +86,120 @@ export function build_source_to_generated_map(source_map, post_processing_change
|
|
|
31
86
|
return line_offsets[line - 1] + column;
|
|
32
87
|
};
|
|
33
88
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* @returns {{ line: number, column: number }}
|
|
38
|
-
*/
|
|
39
|
-
const offset_to_line_col = (offset) => {
|
|
40
|
-
// Binary search
|
|
41
|
-
let left = 0;
|
|
42
|
-
let right = line_offsets.length - 1;
|
|
43
|
-
let line = 1;
|
|
44
|
-
|
|
45
|
-
while (left <= right) {
|
|
46
|
-
const mid = Math.floor((left + right) / 2);
|
|
47
|
-
if (
|
|
48
|
-
offset >= line_offsets[mid] &&
|
|
49
|
-
(mid === line_offsets.length - 1 || offset < line_offsets[mid + 1])
|
|
50
|
-
) {
|
|
51
|
-
line = mid + 1;
|
|
52
|
-
break;
|
|
53
|
-
} else if (offset < line_offsets[mid]) {
|
|
54
|
-
right = mid - 1;
|
|
55
|
-
} else {
|
|
56
|
-
left = mid + 1;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const column = offset - line_offsets[line - 1];
|
|
61
|
-
return { line, column };
|
|
62
|
-
};
|
|
89
|
+
// Apply post-processing adjustments to all segments first
|
|
90
|
+
/** @type {Array<Array<{line: number, column: number, sourceLine: number, sourceColumn: number}>>} */
|
|
91
|
+
const adjusted_segments = [];
|
|
63
92
|
|
|
64
|
-
// decoded is an array of lines, each line is an array of segments
|
|
65
|
-
// Each segment is [generatedColumn, sourceIndex, sourceLine, sourceColumn, nameIndex?]
|
|
66
93
|
for (let generated_line = 0; generated_line < decoded.length; generated_line++) {
|
|
67
94
|
const line = decoded[generated_line];
|
|
95
|
+
adjusted_segments[generated_line] = [];
|
|
68
96
|
|
|
69
97
|
for (const segment of line) {
|
|
70
98
|
if (segment.length >= 4) {
|
|
71
|
-
let generated_column = segment[0];
|
|
72
|
-
// just keeping this unused for context
|
|
73
|
-
// const source_index = segment[1]; // which source file (we only have one)
|
|
74
|
-
const source_line = /** @type {number} */ (segment[2]);
|
|
75
|
-
const source_column = /** @type {number} */ (segment[3]);
|
|
76
|
-
|
|
77
|
-
// Apply post-processing adjustments if needed
|
|
78
99
|
let adjusted_line = generated_line + 1;
|
|
79
|
-
let adjusted_column =
|
|
100
|
+
let adjusted_column = segment[0];
|
|
80
101
|
|
|
81
102
|
if (post_processing_changes) {
|
|
82
103
|
const line_change = post_processing_changes.get(adjusted_line);
|
|
83
104
|
|
|
84
105
|
if (line_change) {
|
|
85
|
-
// Check if this position is affected by the change
|
|
86
106
|
const pos_offset = line_col_to_byte_offset(adjusted_line, adjusted_column);
|
|
87
107
|
|
|
88
108
|
if (pos_offset >= line_change.offset) {
|
|
89
|
-
// Position is on or after the change - apply delta
|
|
90
109
|
const adjusted_offset = pos_offset + line_change.delta;
|
|
91
|
-
const adjusted_pos = offset_to_line_col(adjusted_offset);
|
|
110
|
+
const adjusted_pos = offset_to_line_col(adjusted_offset, line_offsets);
|
|
92
111
|
adjusted_line = adjusted_pos.line;
|
|
93
112
|
adjusted_column = adjusted_pos.column;
|
|
94
113
|
}
|
|
95
114
|
}
|
|
96
115
|
}
|
|
97
116
|
|
|
98
|
-
|
|
99
|
-
|
|
117
|
+
adjusted_segments[generated_line].push({
|
|
118
|
+
line: adjusted_line,
|
|
119
|
+
column: adjusted_column,
|
|
120
|
+
sourceLine: /** @type {number} */ (segment[2]),
|
|
121
|
+
sourceColumn: /** @type {number} */ (segment[3]),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Now build the map using adjusted positions
|
|
128
|
+
for (let line_idx = 0; line_idx < adjusted_segments.length; line_idx++) {
|
|
129
|
+
const line_segments = adjusted_segments[line_idx];
|
|
130
|
+
|
|
131
|
+
for (let seg_idx = 0; seg_idx < line_segments.length; seg_idx++) {
|
|
132
|
+
const segment = line_segments[seg_idx];
|
|
133
|
+
const line = segment.line;
|
|
134
|
+
const column = segment.column;
|
|
135
|
+
|
|
136
|
+
// Determine end position using next segment
|
|
137
|
+
let end_line = line;
|
|
138
|
+
let end_column = column;
|
|
139
|
+
|
|
140
|
+
// Look for next segment to determine end position
|
|
141
|
+
if (seg_idx + 1 < line_segments.length) {
|
|
142
|
+
// Next segment on same line
|
|
143
|
+
const next_segment = line_segments[seg_idx + 1];
|
|
144
|
+
end_line = next_segment.line;
|
|
145
|
+
end_column = next_segment.column;
|
|
146
|
+
} else if (
|
|
147
|
+
line_idx + 1 < adjusted_segments.length &&
|
|
148
|
+
adjusted_segments[line_idx + 1].length > 0
|
|
149
|
+
) {
|
|
150
|
+
// Look at first segment of next line
|
|
151
|
+
const next_segment = adjusted_segments[line_idx + 1][0];
|
|
152
|
+
end_line = next_segment.line;
|
|
153
|
+
end_column = next_segment.column;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Extract code snippet
|
|
157
|
+
const start_offset = line_col_to_byte_offset(line, column);
|
|
158
|
+
const end_offset = line_col_to_byte_offset(end_line, end_column);
|
|
159
|
+
const code_snippet = generated_code.slice(start_offset, end_offset);
|
|
100
160
|
|
|
101
|
-
|
|
102
|
-
|
|
161
|
+
// Create key from source position (1-indexed line, 0-indexed column)
|
|
162
|
+
segment.sourceLine += 1;
|
|
163
|
+
const key = `${segment.sourceLine}:${segment.sourceColumn}`;
|
|
103
164
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
165
|
+
// Store adjusted generated position with code snippet
|
|
166
|
+
const gen_pos = { line, column, end_line, end_column, code: code_snippet, metadata: {} };
|
|
167
|
+
|
|
168
|
+
if (!map.has(key)) {
|
|
169
|
+
map.set(key, []);
|
|
170
|
+
}
|
|
171
|
+
/** @type {CodePosition[]} */ (map.get(key)).push(gen_pos);
|
|
172
|
+
|
|
173
|
+
// Store reverse mapping (generated to source)
|
|
174
|
+
const gen_key = `${gen_pos.line}:${gen_pos.column}`;
|
|
175
|
+
|
|
176
|
+
if (!reverse_map.has(gen_key)) {
|
|
177
|
+
reverse_map.set(gen_key, []);
|
|
108
178
|
}
|
|
179
|
+
reverse_map.get(gen_key)?.push({
|
|
180
|
+
line: segment.sourceLine,
|
|
181
|
+
column: segment.sourceColumn,
|
|
182
|
+
});
|
|
109
183
|
}
|
|
110
184
|
}
|
|
111
185
|
|
|
112
|
-
return map;
|
|
186
|
+
return [map, reverse_map];
|
|
113
187
|
}
|
|
114
188
|
|
|
115
189
|
/**
|
|
116
190
|
* Look up generated position for a given source position
|
|
117
|
-
* @param {number}
|
|
118
|
-
* @param {number}
|
|
119
|
-
* @param {
|
|
120
|
-
* @returns {
|
|
191
|
+
* @param {number} src_line - 1-based line number in source
|
|
192
|
+
* @param {number} src_column - 0-based column number in source
|
|
193
|
+
* @param {CodeToGeneratedMap} src_to_gen_map - Lookup map
|
|
194
|
+
* @returns {CodePosition} Generated position
|
|
121
195
|
*/
|
|
122
|
-
export function get_generated_position(
|
|
123
|
-
const key = `${
|
|
124
|
-
const positions =
|
|
196
|
+
export function get_generated_position(src_line, src_column, src_to_gen_map) {
|
|
197
|
+
const key = `${src_line}:${src_column}`;
|
|
198
|
+
const positions = src_to_gen_map.get(key);
|
|
125
199
|
|
|
126
200
|
if (!positions || positions.length === 0) {
|
|
127
201
|
// No mapping found in source map - this shouldn't happen since all tokens should have mappings
|
|
128
|
-
throw new Error(`No source map entry for position "${
|
|
202
|
+
throw new Error(`No source map entry for position "${src_line}:${src_column}"`);
|
|
129
203
|
}
|
|
130
204
|
|
|
131
205
|
// If multiple generated positions map to same source, return the first
|
|
@@ -226,6 +226,28 @@ declare module 'estree' {
|
|
|
226
226
|
loc: SourceLocation;
|
|
227
227
|
metadata: BaseNodeMetaData & {
|
|
228
228
|
ts_name?: string;
|
|
229
|
+
// for <style> tag
|
|
230
|
+
styleScopeHash?: string;
|
|
231
|
+
// for elements with scoped style classes
|
|
232
|
+
css?: {
|
|
233
|
+
scopedClasses: Map<
|
|
234
|
+
string,
|
|
235
|
+
{
|
|
236
|
+
start: number;
|
|
237
|
+
end: number;
|
|
238
|
+
selector: CSS.ClassSelector;
|
|
239
|
+
}
|
|
240
|
+
>;
|
|
241
|
+
topScopedClasses: Map<
|
|
242
|
+
string,
|
|
243
|
+
{
|
|
244
|
+
start: number;
|
|
245
|
+
end: number;
|
|
246
|
+
selector: CSS.ClassSelector;
|
|
247
|
+
}
|
|
248
|
+
>;
|
|
249
|
+
hash: string;
|
|
250
|
+
};
|
|
229
251
|
};
|
|
230
252
|
|
|
231
253
|
// currently only for <style> and <script> tags
|
|
@@ -56,6 +56,12 @@ declare module 'zimmerframe' {
|
|
|
56
56
|
state: any,
|
|
57
57
|
visitors: RippleCompiler.Visitors<AST.Node, any>,
|
|
58
58
|
): AST.Node;
|
|
59
|
+
|
|
60
|
+
export function walk(
|
|
61
|
+
node: AST.CSS.Node,
|
|
62
|
+
state: any,
|
|
63
|
+
visitors: RippleCompiler.Visitors<(AST.CSS.Node), any>,
|
|
64
|
+
): AST.CSS.Node;
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
export namespace Parse {
|
package/src/compiler/utils.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
@import * as AST from 'estree';
|
|
3
|
+
@import { CommonContext, NameSpace, ScopeInterface } from '#compiler';
|
|
4
|
+
*/
|
|
3
5
|
|
|
4
6
|
import { build_assignment_value, extract_paths } from '../utils/ast.js';
|
|
5
7
|
import * as b from '../utils/builders.js';
|