Rich Harris 3 months ago
parent 486d10c880
commit 97c5d98f79

@ -70,7 +70,7 @@ export function compileModule(source, options) {
const validated = validate_module_options(options, '');
state.reset(source, validated);
const analysis = analyze_module(parse_acorn(source, false), validated);
const analysis = analyze_module(source, validated);
return transform_module(analysis, source, validated);
}

@ -5,14 +5,27 @@ import { tsPlugin } from '@sveltejs/acorn-typescript';
const ParserWithTS = acorn.Parser.extend(tsPlugin());
/**
* @typedef {Comment & {
* start: number;
* end: number;
* }} CommentWithLocation
*/
/**
* @param {string} source
* @param {Comment[]} comments
* @param {boolean} typescript
* @param {boolean} [is_script]
*/
export function parse(source, typescript, is_script) {
export function parse(source, comments, typescript, is_script) {
const parser = typescript ? ParserWithTS : acorn.Parser;
const { onComment, add_comments } = get_comment_handlers(source);
const { onComment, add_comments } = get_comment_handlers(
source,
/** @type {CommentWithLocation[]} */ (comments)
);
// @ts-ignore
const parse_statement = parser.prototype.parseStatement;
@ -53,13 +66,18 @@ export function parse(source, typescript, is_script) {
/**
* @param {string} source
* @param {Comment[]} comments
* @param {boolean} typescript
* @param {number} index
* @returns {acorn.Expression & { leadingComments?: CommentWithLocation[]; trailingComments?: CommentWithLocation[]; }}
*/
export function parse_expression_at(source, typescript, index) {
export function parse_expression_at(source, comments, typescript, index) {
const parser = typescript ? ParserWithTS : acorn.Parser;
const { onComment, add_comments } = get_comment_handlers(source);
const { onComment, add_comments } = get_comment_handlers(
source,
/** @type {CommentWithLocation[]} */ (comments)
);
const ast = parser.parseExpressionAt(source, index, {
onComment,
@ -78,18 +96,9 @@ export function parse_expression_at(source, typescript, index) {
* to add them after the fact. They are needed in order to support `svelte-ignore` comments
* in JS code and so that `prettier-plugin-svelte` doesn't remove all comments when formatting.
* @param {string} source
* @param {CommentWithLocation[]} comments
*/
function get_comment_handlers(source) {
/**
* @typedef {Comment & {
* start: number;
* end: number;
* }} CommentWithLocation
*/
/** @type {CommentWithLocation[]} */
const comments = [];
function get_comment_handlers(source, comments) {
return {
/**
* @param {boolean} block
@ -97,7 +106,7 @@ function get_comment_handlers(source) {
* @param {number} start
* @param {number} end
*/
onComment: (block, value, start, end) => {
onComment: (block, value, start, end, start_loc, end_loc) => {
if (block && /\n/.test(value)) {
let a = start;
while (a > 0 && source[a - 1] !== '\n') a -= 1;
@ -109,13 +118,21 @@ function get_comment_handlers(source) {
value = value.replace(new RegExp(`^${indentation}`, 'gm'), '');
}
comments.push({ type: block ? 'Block' : 'Line', value, start, end });
comments.push({
type: block ? 'Block' : 'Line',
value,
start,
end,
loc: { start: start_loc, end: end_loc }
});
},
/** @param {acorn.Node & { leadingComments?: CommentWithLocation[]; trailingComments?: CommentWithLocation[]; }} ast */
add_comments(ast) {
if (comments.length === 0) return;
comments = comments.slice();
walk(ast, null, {
_(node, { next, path }) {
let comment;

@ -1,4 +1,5 @@
/** @import { AST } from '#compiler' */
/** @import { Comment } from 'estree' */
// @ts-expect-error acorn type definitions are borked in the release we use
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import fragment from './state/fragment.js';
@ -87,6 +88,7 @@ export class Parser {
type: 'Root',
fragment: create_fragment(),
options: null,
comments: [],
metadata: {
ts: this.ts
}

@ -59,7 +59,12 @@ export default function read_pattern(parser) {
space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
const expression = /** @type {any} */ (
parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, parser.ts, start - 1)
parse_expression_at(
`${space_with_newline}(${pattern_string} = 1)`,
parser.root.comments,
parser.ts,
start - 1
)
).left;
expression.typeAnnotation = read_type_annotation(parser);
@ -96,13 +101,13 @@ function read_type_annotation(parser) {
// parameters as part of a sequence expression instead, and will then error on optional
// parameters (`?:`). Therefore replace that sequence with something that will not error.
parser.template.slice(parser.index).replace(/\?\s*:/g, ':');
let expression = parse_expression_at(template, parser.ts, a);
let expression = parse_expression_at(template, parser.root.comments, parser.ts, a);
// `foo: bar = baz` gets mangled — fix it
if (expression.type === 'AssignmentExpression') {
let b = expression.right.start;
while (template[b] !== '=') b -= 1;
expression = parse_expression_at(template.slice(0, b), parser.ts, a);
expression = parse_expression_at(template.slice(0, b), parser.root.comments, parser.ts, a);
}
// `array as item: string, index` becomes `string, index`, which is mistaken as a sequence expression - fix that

@ -34,7 +34,12 @@ export function get_loose_identifier(parser, opening_token) {
*/
export default function read_expression(parser, opening_token, disallow_loose) {
try {
const node = parse_expression_at(parser.template, parser.ts, parser.index);
const node = parse_expression_at(
parser.template,
parser.root.comments,
parser.ts,
parser.index
);
let num_parens = 0;

@ -34,7 +34,7 @@ export function read_script(parser, start, attributes) {
let ast;
try {
ast = acorn.parse(source, parser.ts, true);
ast = acorn.parse(source, parser.root.comments, parser.ts, true);
} catch (err) {
parser.acorn_error(err);
}

@ -389,7 +389,12 @@ function open(parser) {
let function_expression = matched
? /** @type {ArrowFunctionExpression} */ (
parse_expression_at(prelude + `${params} => {}`, parser.ts, params_start)
parse_expression_at(
prelude + `${params} => {}`,
parser.root.comments,
parser.ts,
params_start
)
)
: { params: [] };

@ -1,8 +1,9 @@
/** @import { Expression, Node, Program } from 'estree' */
/** @import { Comment, Expression, Node, Program } from 'estree' */
/** @import { Binding, AST, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
/** @import { AnalysisState, Visitors } from './types' */
/** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */
import { walk } from 'zimmerframe';
import { parse } from '../1-parse/acorn.js';
import * as e from '../../errors.js';
import * as w from '../../warnings.js';
import { extract_identifiers } from '../../utils/ast.js';
@ -231,11 +232,16 @@ function get_component_name(filename) {
const RESERVED = ['$$props', '$$restProps', '$$slots'];
/**
* @param {Program} ast
* @param {string} source
* @param {ValidatedModuleCompileOptions} options
* @returns {Analysis}
*/
export function analyze_module(ast, options) {
export function analyze_module(source, options) {
/** @type {Comment[]} */
const comments = [];
const ast = parse(source, comments, false, false);
const { scope, scopes } = create_scopes(ast, new ScopeRoot(), false, null);
for (const [name, references] of scope.references) {
@ -259,6 +265,7 @@ export function analyze_module(ast, options) {
runes: true,
immutable: true,
tracing: false,
comments,
classes: new Map()
};
@ -429,6 +436,7 @@ export function analyze_component(root, source, options) {
module,
instance,
template,
comments: root.comments,
elements: [],
runes,
tracing: false,

@ -362,6 +362,9 @@ export function client_component(analysis, options) {
.../** @type {ESTree.Statement[]} */ (template.body)
]);
// trick esrap into including comments
component_block.loc = instance.loc;
if (!analysis.runes) {
// Bind static exports to props so that people can access them with bind:x
for (const { name, alias } of analysis.exports) {

@ -36,7 +36,7 @@ export function transform_component(analysis, source, options) {
const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte');
// @ts-ignore TODO
const js = print(program, ts(), {
const js = print(program, ts({ comments: analysis.comments }), {
// include source content; makes it easier/more robust looking up the source map code
// (else esrap does return null for source and sourceMapContent which may trip up tooling)
sourceMapContent: source,
@ -95,14 +95,20 @@ export function transform_module(analysis, source, options) {
];
}
return {
// @ts-expect-error
js: print(program, ts(), {
const js = print(program, ts({ comments: analysis.comments }), {
// include source content; makes it easier/more robust looking up the source map code
// (else esrap does return null for source and sourceMapContent which may trip up tooling)
sourceMapContent: source,
sourceMapSource: get_source_name(options.filename, undefined, 'input.svelte.js')
}),
});
// prepend comment
js.code = `/* ${basename} generated by Svelte v${VERSION} */\n${js.code}`;
js.map.mappings = ';' + js.map.mappings;
return {
js,
css: null,
metadata: {
runes: true

@ -242,6 +242,9 @@ export function server_component(analysis, options) {
.../** @type {Statement[]} */ (template.body)
]);
// trick esrap into including comments
component_block.loc = instance.loc;
if (analysis.props_id) {
// need to be placed on first line of the component for hydration
component_block.body.unshift(

@ -2,6 +2,7 @@ import type { AST, Binding, StateField } from '#compiler';
import type {
AssignmentExpression,
ClassBody,
Comment,
Identifier,
LabeledStatement,
Node,
@ -37,6 +38,7 @@ export interface Analysis {
runes: boolean;
immutable: boolean;
tracing: boolean;
comments: Comment[];
classes: Map<ClassBody, Map<string, StateField>>;

@ -9,7 +9,8 @@ import ts from 'esrap/languages/ts';
export function print(ast) {
// @ts-expect-error some bullshit
return esrap.print(ast, {
...ts(),
// @ts-expect-error some bullshit
...ts({ comments: ast.type === 'Root' ? ast.comments : [] }),
...visitors
});
}
@ -127,7 +128,13 @@ const visitors = {
context.write(`</${node.name}>`);
},
OnDirective(node, context) {
// TODO
},
TransitionDirective(node, context) {
// TODO
},
Comment(node, context) {
// TODO
}
};

@ -72,6 +72,8 @@ export namespace AST {
instance: Script | null;
/** The parsed `<script module>` element, if exists */
module: Script | null;
/** Comments found in <script> and {expressions} */
comments: import('estree').Comment[];
/** @internal */
metadata: {
/** Whether the component was parsed with typescript */

@ -1,6 +1,5 @@
/* module.svelte.js generated by Svelte VERSION */
import * as $ from 'svelte/internal/client';
import { random } from './export';
export { random };

@ -1,6 +1,5 @@
/* module.svelte.js generated by Svelte VERSION */
import * as $ from 'svelte/internal/server';
import { random } from './export';
export { random };
Loading…
Cancel
Save