put locations on template, instead of on the side

pull/15538/head
Rich Harris 4 months ago
parent af7846518c
commit 40be734f94

@ -170,8 +170,7 @@ export function client_component(analysis, options) {
update: /** @type {any} */ (null), update: /** @type {any} */ (null),
expressions: /** @type {any} */ (null), expressions: /** @type {any} */ (null),
after_update: /** @type {any} */ (null), after_update: /** @type {any} */ (null),
template: /** @type {any} */ (null), template: /** @type {any} */ (null)
locations: /** @type {any} */ (null)
}; };
const module = /** @type {ESTree.Program} */ ( const module = /** @type {ESTree.Program} */ (

@ -1,7 +1,8 @@
/** @import { ComponentClientTransformState } from '../types.js' */ /** @import { Location } from 'locate-character' */
/** @import { Namespace } from '#compiler' */ /** @import { Namespace } from '#compiler' */
/** @import { SourceLocation } from '#shared' */ /** @import { ComponentClientTransformState } from '../types.js' */
import { dev } from '../../../../state.js'; /** @import { Node } from './types.js' */
import { dev, locator } from '../../../../state.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
import { template_to_functions } from './to-functions.js'; import { template_to_functions } from './to-functions.js';
import { template_to_string } from './to-string.js'; import { template_to_string } from './to-string.js';
@ -28,20 +29,27 @@ function get_template_function(namespace, state) {
} }
/** /**
* @param {SourceLocation[]} locations * @param {Node[]} nodes
*/ */
function build_locations(locations) { function build_locations(nodes) {
return b.array( const array = b.array([]);
locations.map((loc) => {
const expression = b.array([b.literal(loc[0]), b.literal(loc[1])]); for (const node of nodes) {
if (node.type !== 'element') continue;
const { line, column } = /** @type {Location} */ (locator(node.start));
if (loc.length === 3) { const expression = b.array([b.literal(line), b.literal(column)]);
expression.elements.push(build_locations(loc[2])); const children = build_locations(node.children);
if (children.elements.length > 0) {
expression.elements.push(children);
} }
return expression; array.elements.push(expression);
}) }
);
return array;
} }
/** /**
@ -66,7 +74,7 @@ export function transform_template(state, namespace, flags) {
'$.add_locations', '$.add_locations',
call, call,
b.member(b.id(state.analysis.name), '$.FILENAME', true), b.member(b.id(state.analysis.name), '$.FILENAME', true),
build_locations(state.locations) build_locations(state.template.nodes)
); );
} }

@ -22,13 +22,17 @@ export class Template {
#fragment = this.nodes; #fragment = this.nodes;
/** @param {string} name */ /**
create_element(name) { * @param {string} name
* @param {number} start
*/
create_element(name, start) {
this.#element = { this.#element = {
type: 'element', type: 'element',
name, name,
attributes: {}, attributes: {},
children: [] children: [],
start
}; };
this.#fragment.push(this.#element); this.#fragment.push(this.#element);

@ -5,6 +5,8 @@ export interface Element {
name: string; name: string;
attributes: Record<string, string | undefined>; attributes: Record<string, string | undefined>;
children: Node[]; children: Node[];
/** used for populating __svelte_meta */
start: number;
} }
export interface Text { export interface Text {

@ -3,16 +3,14 @@ import type {
Statement, Statement,
LabeledStatement, LabeledStatement,
Identifier, Identifier,
PrivateIdentifier,
Expression, Expression,
AssignmentExpression, AssignmentExpression,
UpdateExpression, UpdateExpression,
VariableDeclaration VariableDeclaration
} from 'estree'; } from 'estree';
import type { AST, Namespace, StateField, ValidatedCompileOptions } from '#compiler'; import type { AST, Namespace, ValidatedCompileOptions } from '#compiler';
import type { TransformState } from '../types.js'; import type { TransformState } from '../types.js';
import type { ComponentAnalysis } from '../../types.js'; import type { ComponentAnalysis } from '../../types.js';
import type { SourceLocation } from '#shared';
import type { Template } from './transform-template/template.js'; import type { Template } from './transform-template/template.js';
export interface ClientTransformState extends TransformState { export interface ClientTransformState extends TransformState {
@ -55,7 +53,6 @@ export interface ComponentClientTransformState extends ClientTransformState {
readonly expressions: Expression[]; readonly expressions: Expression[];
/** The HTML template string */ /** The HTML template string */
readonly template: Template; readonly template: Template;
readonly locations: SourceLocation[];
readonly metadata: { readonly metadata: {
namespace: Namespace; namespace: Namespace;
bound_contenteditable: boolean; bound_contenteditable: boolean;

@ -67,7 +67,6 @@ export function Fragment(node, context) {
expressions: [], expressions: [],
after_update: [], after_update: [],
template: new Template(), template: new Template(),
locations: [],
transform: { ...context.state.transform }, transform: { ...context.state.transform },
metadata: { metadata: {
namespace, namespace,

@ -1,17 +1,14 @@
/** @import { ArrayExpression, Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression } from 'estree' */ /** @import { ArrayExpression, Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression } from 'estree' */
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { SourceLocation } from '#shared' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */ /** @import { ComponentClientTransformState, ComponentContext } from '../types' */
/** @import { Scope } from '../../../scope' */ /** @import { Scope } from '../../../scope' */
import { import {
cannot_be_set_statically, cannot_be_set_statically,
is_boolean_attribute, is_boolean_attribute,
is_dom_property, is_dom_property,
is_load_error_element, is_load_error_element
is_void
} from '../../../../../utils.js'; } from '../../../../../utils.js';
import { escape_html } from '../../../../../escaping.js'; import { is_ignored } from '../../../../state.js';
import { dev, is_ignored, locator } from '../../../../state.js';
import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js'; import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { is_custom_element_node } from '../../../nodes.js'; import { is_custom_element_node } from '../../../nodes.js';
@ -39,20 +36,9 @@ import { visit_event_attribute } from './shared/events.js';
* @param {ComponentContext} context * @param {ComponentContext} context
*/ */
export function RegularElement(node, context) { export function RegularElement(node, context) {
/** @type {SourceLocation} */ context.state.template.create_element(node.name, node.start);
let location = [-1, -1];
if (dev) {
const loc = locator(node.start);
if (loc) {
location[0] = loc.line;
location[1] = loc.column;
context.state.locations.push(location);
}
}
if (node.name === 'noscript') { if (node.name === 'noscript') {
context.state.template.create_element('noscript');
return; return;
} }
@ -68,8 +54,6 @@ export function RegularElement(node, context) {
context.state.template.contains_script_tag ||= node.name === 'script'; context.state.template.contains_script_tag ||= node.name === 'script';
context.state.template.create_element(node.name);
/** @type {Array<AST.Attribute | AST.SpreadAttribute>} */ /** @type {Array<AST.Attribute | AST.SpreadAttribute>} */
const attributes = []; const attributes = [];
@ -345,7 +329,6 @@ export function RegularElement(node, context) {
const state = { const state = {
...context.state, ...context.state,
metadata, metadata,
locations: [],
scope: /** @type {Scope} */ (context.state.scopes.get(node.fragment)), scope: /** @type {Scope} */ (context.state.scopes.get(node.fragment)),
preserve_whitespace: preserve_whitespace:
context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea' context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea'
@ -439,10 +422,6 @@ export function RegularElement(node, context) {
context.state.update.push(b.stmt(b.assignment('=', dir, dir))); context.state.update.push(b.stmt(b.assignment('=', dir, dir)));
} }
if (state.locations.length > 0) {
// @ts-expect-error
location.push(state.locations);
}
context.state.template.pop_element(); context.state.template.pop_element();
} }

@ -1,7 +1,7 @@
/** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Pattern, Property, SequenceExpression, Statement } from 'estree' */ /** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Pattern, Property, SequenceExpression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../../types.js' */ /** @import { ComponentContext } from '../../types.js' */
import { dev, is_ignored, locator } from '../../../../../state.js'; import { dev, is_ignored } from '../../../../../state.js';
import { get_attribute_chunks, object } from '../../../../../utils/ast.js'; import { get_attribute_chunks, object } from '../../../../../utils/ast.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { build_bind_this, memoize_expression, validate_binding } from '../shared/utils.js'; import { build_bind_this, memoize_expression, validate_binding } from '../shared/utils.js';
@ -440,19 +440,12 @@ export function build_component(node, component_name, context, anchor = context.
} }
if (Object.keys(custom_css_props).length > 0) { if (Object.keys(custom_css_props).length > 0) {
if (dev) {
const loc = locator(node.start);
if (loc) {
context.state.locations.push([loc.line, loc.column]);
}
}
if (context.state.metadata.namespace === 'svg') { if (context.state.metadata.namespace === 'svg') {
// this boils down to <g><!></g> // this boils down to <g><!></g>
context.state.template.create_element('g'); context.state.template.create_element('g', node.start);
} else { } else {
// this boils down to <svelte-css-wrapper style='display: contents'><!></svelte-css-wrapper> // this boils down to <svelte-css-wrapper style='display: contents'><!></svelte-css-wrapper>
context.state.template.create_element('svelte-css-wrapper'); context.state.template.create_element('svelte-css-wrapper', node.start);
context.state.template.set_prop('style', 'display: contents'); context.state.template.set_prop('style', 'display: contents');
} }

@ -1,4 +1,4 @@
/** @import { SourceLocation } from '#shared' */ /** @import { SourceLocation } from '#client' */
import { HYDRATION_END, HYDRATION_START, HYDRATION_START_ELSE } from '../../../constants.js'; import { HYDRATION_END, HYDRATION_START, HYDRATION_START_ELSE } from '../../../constants.js';
import { hydrating } from '../dom/hydration.js'; import { hydrating } from '../dom/hydration.js';

@ -183,4 +183,8 @@ export type ProxyStateObject<T = Record<string | symbol, any>> = T & {
[STATE_SYMBOL]: T; [STATE_SYMBOL]: T;
}; };
export type SourceLocation =
| [line: number, column: number]
| [line: number, column: number, SourceLocation[]];
export * from './reactivity/types'; export * from './reactivity/types';

@ -3,10 +3,6 @@ export type Store<V> = {
set(value: V): void; set(value: V): void;
}; };
export type SourceLocation =
| [line: number, column: number]
| [line: number, column: number, SourceLocation[]];
export type Getters<T> = { export type Getters<T> = {
[K in keyof T]: () => T[K]; [K in keyof T]: () => T[K];
}; };

Loading…
Cancel
Save