@tsrx/core 0.1.65 → 0.1.67
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/prune.js +36 -9
- 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 +18 -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 +866 -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-combinators.expected.json +16 -0
- package/tests/fixtures/scoped-styles/sibling-combinators.tsrx +42 -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
package/src/parse/style.js
CHANGED
|
@@ -113,7 +113,15 @@ class Parser {
|
|
|
113
113
|
/**
|
|
114
114
|
* @template {string} T
|
|
115
115
|
* @param {string} content
|
|
116
|
-
* @param {{
|
|
116
|
+
* @param {{
|
|
117
|
+
* filename: NonEmptyString<T>,
|
|
118
|
+
* line: number,
|
|
119
|
+
* column: number,
|
|
120
|
+
* body?: { start: number, line: number, column: number },
|
|
121
|
+
* }} location position of the `<style>` tag in the source file, and optionally
|
|
122
|
+
* `body`, the file offset and 1-based line / 0-based column of the style body's
|
|
123
|
+
* first character. With `body`, the sheet records `sourceStart` and a
|
|
124
|
+
* file-relative `loc`, and diagnostics on its nodes carry file positions.
|
|
117
125
|
* @param {{ loose?: boolean }} options
|
|
118
126
|
* @returns {AST.CSS.StyleSheet}
|
|
119
127
|
*/
|
|
@@ -126,14 +134,82 @@ export function parse_style(content, location, options) {
|
|
|
126
134
|
// pre-image-resistant hash to avoid leaking file structure into the bundle.
|
|
127
135
|
const hash_source = `${location.filename}:${location.line}:${location.column}:${content}`;
|
|
128
136
|
|
|
129
|
-
|
|
137
|
+
/** @type {AST.CSS.StyleSheet} */
|
|
138
|
+
const sheet = {
|
|
130
139
|
source: content,
|
|
131
140
|
hash: `tsrx-${strong_hash(hash_source)}`,
|
|
132
141
|
type: 'StyleSheet',
|
|
133
142
|
children: read_body(parser),
|
|
134
143
|
start: 0,
|
|
135
144
|
end: content.length,
|
|
145
|
+
filename: location.filename,
|
|
136
146
|
};
|
|
147
|
+
|
|
148
|
+
if (location.body) {
|
|
149
|
+
sheet.sourceStart = location.body.start;
|
|
150
|
+
sheet.loc = {
|
|
151
|
+
start: { line: location.body.line, column: location.body.column },
|
|
152
|
+
end: css_position(sheet, content.length, location.body),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return sheet;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Map a style-body offset to a file line / column, given the file position of
|
|
161
|
+
* the body's first character.
|
|
162
|
+
*
|
|
163
|
+
* @param {AST.CSS.StyleSheet} sheet
|
|
164
|
+
* @param {number} offset
|
|
165
|
+
* @param {{ line: number, column: number }} origin
|
|
166
|
+
* @returns {{ line: number, column: number }}
|
|
167
|
+
*/
|
|
168
|
+
function css_position(sheet, offset, origin) {
|
|
169
|
+
const source = sheet.source;
|
|
170
|
+
let line = origin.line;
|
|
171
|
+
let line_start = -1;
|
|
172
|
+
for (let i = 0; i < offset; i++) {
|
|
173
|
+
const ch = source.charCodeAt(i);
|
|
174
|
+
if (ch === 13 && source.charCodeAt(i + 1) === 10) {
|
|
175
|
+
i++;
|
|
176
|
+
} else if (ch !== 10 && ch !== 13 && ch !== 0x2028 && ch !== 0x2029) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
line++;
|
|
180
|
+
line_start = i;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
line,
|
|
184
|
+
column: line_start === -1 ? origin.column + offset : offset - line_start - 1,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The file-relative `start` / `end` / `loc` of a CSS node in a sheet parsed
|
|
190
|
+
* with a `body` origin; the node itself when the sheet has none. CSS node
|
|
191
|
+
* offsets are relative to the style body, so this is what a diagnostic on a
|
|
192
|
+
* CSS node has to be anchored on.
|
|
193
|
+
*
|
|
194
|
+
* @param {AST.CSS.StyleSheet | null | undefined} sheet
|
|
195
|
+
* @param {AST.CSS.Node} node
|
|
196
|
+
* @returns {AST.Node | AST.NodeWithLocation}
|
|
197
|
+
*/
|
|
198
|
+
export function css_node_source_position(sheet, node) {
|
|
199
|
+
if (sheet?.sourceStart === undefined || !sheet.loc) {
|
|
200
|
+
return /** @type {AST.Node} */ (/** @type {unknown} */ (node));
|
|
201
|
+
}
|
|
202
|
+
const origin = sheet.loc.start;
|
|
203
|
+
return /** @type {AST.NodeWithLocation} */ (
|
|
204
|
+
/** @type {unknown} */ ({
|
|
205
|
+
start: node.start + sheet.sourceStart,
|
|
206
|
+
end: node.end + sheet.sourceStart,
|
|
207
|
+
loc: {
|
|
208
|
+
start: css_position(sheet, node.start, origin),
|
|
209
|
+
end: css_position(sheet, node.end, origin),
|
|
210
|
+
},
|
|
211
|
+
})
|
|
212
|
+
);
|
|
137
213
|
}
|
|
138
214
|
|
|
139
215
|
/** @param {Parser} parser */
|
|
@@ -163,12 +239,26 @@ function read_body(parser) {
|
|
|
163
239
|
const children = [];
|
|
164
240
|
|
|
165
241
|
while (parser.index < parser.template.length) {
|
|
166
|
-
|
|
242
|
+
try {
|
|
243
|
+
allow_comment_or_whitespace(parser);
|
|
167
244
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
245
|
+
if (parser.index >= parser.template.length) {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (parser.match('@')) {
|
|
250
|
+
children.push(read_at_rule(parser));
|
|
251
|
+
} else {
|
|
252
|
+
children.push(read_rule(parser));
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (!parser.loose) {
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
// Partial CSS while typing (`.foo {`, `/*`), or whatever follows an
|
|
259
|
+
// unclosed module-scope `<style>`, must not take the whole file down in
|
|
260
|
+
// loose mode: keep the rules parsed so far and drop the rest.
|
|
261
|
+
break;
|
|
172
262
|
}
|
|
173
263
|
}
|
|
174
264
|
|
package/src/plugin.js
CHANGED
|
@@ -2245,26 +2245,66 @@ export function TSRXPlugin(config) {
|
|
|
2245
2245
|
* synthesize the closing element, and restore the tokenizer state past it.
|
|
2246
2246
|
* Shared by `<style>` and `<script>`.
|
|
2247
2247
|
*
|
|
2248
|
+
* Without a closing tag the element is unclosed and the rest of the input
|
|
2249
|
+
* is its body, except that in loose mode inside a template the body stops
|
|
2250
|
+
* at the next tag start (`<x`, `</x>`, `</>`): while `<style>` is being
|
|
2251
|
+
* typed, partial CSS reaches the loose CSS parser, and the tokenizer
|
|
2252
|
+
* resumes at the following sibling or closing tag so the rest of the file
|
|
2253
|
+
* keeps its mappings. Outside a template only JS follows, with no marker to
|
|
2254
|
+
* resume at, so the body runs to the end of the input.
|
|
2255
|
+
*
|
|
2248
2256
|
* @param {ESTreeJSX.TSRXJSXOpeningElement & AST.NodeWithLocation} open
|
|
2249
2257
|
* @param {AST.JSXStyleElement | AST.TSRXJSXElement} node
|
|
2250
2258
|
* @param {'style' | 'script'} tagName
|
|
2259
|
+
* @param {number} [contextDepth] tokenizer context depth to restore before
|
|
2260
|
+
* reading the token after the closing tag when the element sits outside
|
|
2261
|
+
* a template (see parseElement)
|
|
2251
2262
|
* @returns {string} The raw body text
|
|
2252
2263
|
*/
|
|
2253
|
-
#parseRawTextElement(open, node, tagName) {
|
|
2264
|
+
#parseRawTextElement(open, node, tagName, contextDepth) {
|
|
2254
2265
|
const closeTag = `</${tagName}>`;
|
|
2255
2266
|
const contentStart = open.end;
|
|
2256
2267
|
const input = this.input.slice(contentStart);
|
|
2257
|
-
const
|
|
2258
|
-
const
|
|
2268
|
+
const parent = this.#path.at(-2);
|
|
2269
|
+
const insideTemplate = this.#isNativeTemplateNode(parent);
|
|
2270
|
+
let relativeCloseStart = input.indexOf(closeTag);
|
|
2271
|
+
const unclosed = relativeCloseStart === -1;
|
|
2259
2272
|
|
|
2273
|
+
if (unclosed) {
|
|
2274
|
+
this.#report_broken_markup_error(
|
|
2275
|
+
open.end,
|
|
2276
|
+
`Unclosed tag '<${tagName}>'. Expected '${closeTag}' before end of template.`,
|
|
2277
|
+
);
|
|
2278
|
+
node.unclosed = true;
|
|
2279
|
+
relativeCloseStart = input.length;
|
|
2280
|
+
if (!this.#loose) {
|
|
2281
|
+
const newLines = input.match(regex_newline_characters)?.length;
|
|
2282
|
+
if (newLines) {
|
|
2283
|
+
this.curLine = open.loc.end.line + newLines;
|
|
2284
|
+
this.lineStart = contentStart + input.lastIndexOf('\n') + 1;
|
|
2285
|
+
}
|
|
2286
|
+
return input;
|
|
2287
|
+
}
|
|
2288
|
+
if (insideTemplate) {
|
|
2289
|
+
// A `<` inside CSS text (`content: "<"`, `<!--`) is not a tag start.
|
|
2290
|
+
let lt = input.indexOf('<');
|
|
2291
|
+
while (lt !== -1 && !can_start_tag_after_lt(input, lt)) {
|
|
2292
|
+
lt = input.indexOf('<', lt + 1);
|
|
2293
|
+
}
|
|
2294
|
+
if (lt !== -1) relativeCloseStart = lt;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
const content = input.slice(0, relativeCloseStart);
|
|
2260
2299
|
const newLines = content.match(regex_newline_characters)?.length;
|
|
2261
2300
|
if (newLines) {
|
|
2262
2301
|
this.curLine = open.loc.end.line + newLines;
|
|
2263
2302
|
this.lineStart = contentStart + content.lastIndexOf('\n') + 1;
|
|
2264
2303
|
}
|
|
2265
2304
|
|
|
2266
|
-
|
|
2267
|
-
|
|
2305
|
+
const closingStart = contentStart + content.length;
|
|
2306
|
+
let closingEnd = closingStart;
|
|
2307
|
+
if (!unclosed) {
|
|
2268
2308
|
const closingLineInfo = get_line_info(this, closingStart);
|
|
2269
2309
|
const closingStartLoc = new acorn.Position(closingLineInfo.line, closingLineInfo.column);
|
|
2270
2310
|
const nameStart = closingStart + 2;
|
|
@@ -2284,7 +2324,7 @@ export function TSRXPlugin(config) {
|
|
|
2284
2324
|
nameEnd,
|
|
2285
2325
|
new acorn.Position(nameEndInfo.line, nameEndInfo.column),
|
|
2286
2326
|
);
|
|
2287
|
-
|
|
2327
|
+
closingEnd = closingStart + closeTag.length;
|
|
2288
2328
|
const closingEndInfo = get_line_info(this, closingEnd);
|
|
2289
2329
|
const closingElement =
|
|
2290
2330
|
/** @type {ESTreeJSX.TSRXJSXClosingElement & AST.NodeWithLocation} */ (
|
|
@@ -2298,65 +2338,84 @@ export function TSRXPlugin(config) {
|
|
|
2298
2338
|
new acorn.Position(closingEndInfo.line, closingEndInfo.column),
|
|
2299
2339
|
);
|
|
2300
2340
|
node.closingElement = closingElement;
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
if (this.curContext() === tstc.tc_expr && !insideTemplate) {
|
|
2344
|
+
this.context.pop();
|
|
2345
|
+
}
|
|
2346
|
+
this.exprAllowed = false;
|
|
2347
|
+
this.pos = closingEnd;
|
|
2348
|
+
const closingEndInfo = get_line_info(this, closingEnd);
|
|
2349
|
+
this.curLine = closingEndInfo.line;
|
|
2350
|
+
this.lineStart = closingEnd - closingEndInfo.column;
|
|
2351
|
+
if (insideTemplate && relativeCloseStart === 0) {
|
|
2352
|
+
// Acorn has already tokenized the adjacent tag start (this element's
|
|
2353
|
+
// closing tag, or, when unclosed, the next sibling or parent close);
|
|
2354
|
+
// the element resumes there manually, so drop the stale tag context.
|
|
2355
|
+
if (this.curContext() === tstc.tc_oTag) {
|
|
2304
2356
|
this.context.pop();
|
|
2305
2357
|
}
|
|
2306
|
-
this.
|
|
2307
|
-
|
|
2308
|
-
this.curLine = closingEndInfo.line;
|
|
2309
|
-
this.lineStart = closingEnd - closingEndInfo.column;
|
|
2310
|
-
if (insideTemplate && relativeCloseStart === 0) {
|
|
2311
|
-
// Acorn has already tokenized the adjacent closing tag; TSRX
|
|
2312
|
-
// synthesizes that close manually, so drop the stale tag context.
|
|
2313
|
-
if (this.curContext() === tstc.tc_oTag) {
|
|
2314
|
-
this.context.pop();
|
|
2315
|
-
}
|
|
2316
|
-
if (this.curContext() === tstc.tc_expr) {
|
|
2317
|
-
this.context.pop();
|
|
2318
|
-
}
|
|
2358
|
+
if (this.curContext() === tstc.tc_expr) {
|
|
2359
|
+
this.context.pop();
|
|
2319
2360
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2361
|
+
}
|
|
2362
|
+
if (!insideTemplate && this.#path.at(-1) === node) {
|
|
2363
|
+
// Outside a template (a `@{ … }` body, a `@case` body, a statement),
|
|
2364
|
+
// the element must leave the tokenizer context exactly where it
|
|
2365
|
+
// began — like a balanced element does after its closing tag — so
|
|
2366
|
+
// the following `}`, `@case`, or sibling tokenizes as code, not as
|
|
2367
|
+
// JSX text of a children context this element's `<` opened.
|
|
2368
|
+
if (contextDepth !== undefined && this.context.length > contextDepth) {
|
|
2369
|
+
this.context.length = contextDepth;
|
|
2370
|
+
}
|
|
2371
|
+
this.#path.pop();
|
|
2372
|
+
try {
|
|
2328
2373
|
this.next();
|
|
2374
|
+
} finally {
|
|
2375
|
+
this.#path.push(node);
|
|
2329
2376
|
}
|
|
2330
2377
|
} else {
|
|
2331
|
-
this
|
|
2332
|
-
open.end,
|
|
2333
|
-
`Unclosed tag '<${tagName}>'. Expected '${closeTag}' before end of template.`,
|
|
2334
|
-
);
|
|
2335
|
-
node.unclosed = true;
|
|
2378
|
+
this.next();
|
|
2336
2379
|
}
|
|
2337
2380
|
|
|
2338
2381
|
return content;
|
|
2339
2382
|
}
|
|
2340
2383
|
|
|
2384
|
+
/**
|
|
2385
|
+
* Whether the first non-whitespace character after an opening tag is
|
|
2386
|
+
* `{`: the element's children start with an expression container.
|
|
2387
|
+
*
|
|
2388
|
+
* @param {ESTreeJSX.TSRXJSXOpeningElement & AST.NodeWithLocation} open
|
|
2389
|
+
* @returns {boolean}
|
|
2390
|
+
*/
|
|
2391
|
+
#hasExpressionChildStart(open) {
|
|
2392
|
+
if (open.selfClosing) return false;
|
|
2393
|
+
const index = skip_whitespace_from(this.input, open.end);
|
|
2394
|
+
return this.input.charCodeAt(index) === CharCode.openBrace;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2341
2397
|
/**
|
|
2342
2398
|
* @param {ESTreeJSX.TSRXJSXOpeningElement & AST.NodeWithLocation} open
|
|
2343
2399
|
* @param {AST.JSXStyleElement} node
|
|
2344
2400
|
* @param {boolean} insideHead
|
|
2401
|
+
* @param {number} [contextDepth] see #parseRawTextElement
|
|
2345
2402
|
*/
|
|
2346
|
-
#parseStyleElement(open, node, insideHead) {
|
|
2403
|
+
#parseStyleElement(open, node, insideHead, contextDepth) {
|
|
2347
2404
|
const filename = this.#filename;
|
|
2348
2405
|
if (!filename) {
|
|
2349
2406
|
throw new Error(
|
|
2350
2407
|
'<style> elements require a filename: pass one to parse so style scope hashes are unique per file.',
|
|
2351
2408
|
);
|
|
2352
2409
|
}
|
|
2353
|
-
const content = this.#parseRawTextElement(open, node, 'style');
|
|
2410
|
+
const content = this.#parseRawTextElement(open, node, 'style', contextDepth);
|
|
2411
|
+
const bodyLoc = get_line_info(this, open.end);
|
|
2354
2412
|
const parsedCss = parse_style(
|
|
2355
2413
|
content,
|
|
2356
2414
|
{
|
|
2357
2415
|
filename,
|
|
2358
2416
|
line: open.loc.start.line,
|
|
2359
2417
|
column: open.loc.start.column,
|
|
2418
|
+
body: { start: open.end, line: bodyLoc.line, column: bodyLoc.column },
|
|
2360
2419
|
},
|
|
2361
2420
|
{ loose: this.#loose },
|
|
2362
2421
|
);
|
|
@@ -2384,9 +2443,10 @@ export function TSRXPlugin(config) {
|
|
|
2384
2443
|
*
|
|
2385
2444
|
* @param {ESTreeJSX.TSRXJSXOpeningElement & AST.NodeWithLocation} open
|
|
2386
2445
|
* @param {AST.TSRXJSXElement} node
|
|
2446
|
+
* @param {number} [contextDepth] see #parseRawTextElement
|
|
2387
2447
|
*/
|
|
2388
|
-
#parseScriptElement(open, node) {
|
|
2389
|
-
const content = this.#parseRawTextElement(open, node, 'script');
|
|
2448
|
+
#parseScriptElement(open, node, contextDepth) {
|
|
2449
|
+
const content = this.#parseRawTextElement(open, node, 'script', contextDepth);
|
|
2390
2450
|
node.content = content;
|
|
2391
2451
|
node.children = [];
|
|
2392
2452
|
|
|
@@ -4740,7 +4800,10 @@ export function TSRXPlugin(config) {
|
|
|
4740
4800
|
}
|
|
4741
4801
|
const tag_name = open.name ? this.getElementName(open.name) : null;
|
|
4742
4802
|
const is_dynamic = this.#isDynamicJSXElementName(open.name);
|
|
4743
|
-
|
|
4803
|
+
// `<style>` holds raw CSS text (a JSXStyleElement) unless its first
|
|
4804
|
+
// child is an expression container: `<style>{css}</style>` is the
|
|
4805
|
+
// ordinary TSX element, parsed like any other host element.
|
|
4806
|
+
const is_style = tag_name === 'style' && !this.#hasExpressionChildStart(open);
|
|
4744
4807
|
const is_script = tag_name === 'script';
|
|
4745
4808
|
const inside_head = this.#path.findLast((n) => this.#isNativeElementNamed(n, 'head'));
|
|
4746
4809
|
|
|
@@ -4792,12 +4855,28 @@ export function TSRXPlugin(config) {
|
|
|
4792
4855
|
this.#path.push(node);
|
|
4793
4856
|
|
|
4794
4857
|
if (!is_fragment && open.selfClosing) {
|
|
4858
|
+
if (is_style) {
|
|
4859
|
+
// `<style apply={theme} />` has no CSS body and no scope hash of
|
|
4860
|
+
// its own; it only contributes its `apply` targets to the scope.
|
|
4861
|
+
const style = /** @type {AST.JSXStyleElement} */ (node);
|
|
4862
|
+
style.css = '';
|
|
4863
|
+
style.children = [];
|
|
4864
|
+
}
|
|
4795
4865
|
this.#path.pop();
|
|
4796
4866
|
} else if (is_style) {
|
|
4797
|
-
this.#parseStyleElement(
|
|
4867
|
+
this.#parseStyleElement(
|
|
4868
|
+
open,
|
|
4869
|
+
/** @type {AST.JSXStyleElement} */ (node),
|
|
4870
|
+
!!inside_head,
|
|
4871
|
+
pre_element_context_depth,
|
|
4872
|
+
);
|
|
4798
4873
|
this.#path.pop();
|
|
4799
4874
|
} else if (is_script) {
|
|
4800
|
-
this.#parseScriptElement(
|
|
4875
|
+
this.#parseScriptElement(
|
|
4876
|
+
open,
|
|
4877
|
+
/** @type {AST.TSRXJSXElement} */ (node),
|
|
4878
|
+
pre_element_context_depth,
|
|
4879
|
+
);
|
|
4801
4880
|
this.#path.pop();
|
|
4802
4881
|
} else {
|
|
4803
4882
|
this.#parseNativeTemplateBody(node, /** @type {AST.Node[]} */ (node.children), {
|
|
@@ -4842,11 +4921,30 @@ export function TSRXPlugin(config) {
|
|
|
4842
4921
|
}
|
|
4843
4922
|
}
|
|
4844
4923
|
|
|
4845
|
-
if (
|
|
4846
|
-
const
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4924
|
+
if (is_style || is_script) {
|
|
4925
|
+
const raw_text = /** @type {AST.JSXStyleElement} */ (node);
|
|
4926
|
+
if (raw_text.closingElement) {
|
|
4927
|
+
const closing = /** @type {ESTreeJSX.JSXClosingElement & AST.NodeWithLocation} */ (
|
|
4928
|
+
raw_text.closingElement
|
|
4929
|
+
);
|
|
4930
|
+
return this.finishNodeAt(node, node.type, closing.end, closing.loc.end);
|
|
4931
|
+
}
|
|
4932
|
+
if (raw_text.unclosed && this.#loose) {
|
|
4933
|
+
// Loose recovery captured a body without a closing tag: the element
|
|
4934
|
+
// ends where its body ends, not at whatever token was read next.
|
|
4935
|
+
const script = /** @type {AST.TSRXJSXElement} */ (/** @type {unknown} */ (node));
|
|
4936
|
+
const body = (is_style ? raw_text.css : script.content) ?? '';
|
|
4937
|
+
const body_end =
|
|
4938
|
+
/** @type {ESTreeJSX.TSRXJSXOpeningElement & AST.NodeWithLocation} */ (open).end +
|
|
4939
|
+
body.length;
|
|
4940
|
+
const body_end_info = get_line_info(this, body_end);
|
|
4941
|
+
return this.finishNodeAt(
|
|
4942
|
+
node,
|
|
4943
|
+
node.type,
|
|
4944
|
+
body_end,
|
|
4945
|
+
new acorn.Position(body_end_info.line, body_end_info.column),
|
|
4946
|
+
);
|
|
4947
|
+
}
|
|
4850
4948
|
}
|
|
4851
4949
|
|
|
4852
4950
|
return this.finishNode(node, node.type);
|
package/src/scope.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
@import * as AST from 'estree';
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import is_reference from 'is-reference';
|
|
14
|
+
import { is_reference } from './utils/is-reference.js';
|
|
15
15
|
import { extract_identifiers, object, unwrap_pattern } from './utils/ast.js';
|
|
16
16
|
import { walk } from 'zimmerframe';
|
|
17
17
|
import { is_reserved } from './utils.js';
|