pull/17954/head
Rich Harris 5 months ago
commit 592adfb389

@ -4,8 +4,10 @@
import * as acorn from 'acorn'; import * as acorn from 'acorn';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import { tsPlugin } from '@sveltejs/acorn-typescript'; import { tsPlugin } from '@sveltejs/acorn-typescript';
import * as e from '../../errors.js';
const ParserWithTS = acorn.Parser.extend(tsPlugin()); const JSParser = acorn.Parser;
const TSParser = JSParser.extend(tsPlugin());
/** /**
* @typedef {Comment & { * @typedef {Comment & {
@ -21,15 +23,15 @@ const ParserWithTS = acorn.Parser.extend(tsPlugin());
* @param {boolean} [is_script] * @param {boolean} [is_script]
*/ */
export function parse(source, comments, typescript, is_script) { export function parse(source, comments, typescript, is_script) {
const parser = typescript ? ParserWithTS : acorn.Parser; const acorn = typescript ? TSParser : JSParser;
const { onComment, add_comments } = get_comment_handlers( const { onComment, add_comments } = get_comment_handlers(
source, source,
/** @type {CommentWithLocation[]} */ (comments) /** @type {CommentWithLocation[]} */ (comments)
); );
// @ts-ignore // @ts-expect-error
const parse_statement = parser.prototype.parseStatement; const parse_statement = acorn.prototype.parseStatement;
// If we're dealing with a <script> then it might contain an export // If we're dealing with a <script> then it might contain an export
// for something that doesn't exist directly inside but is inside the // for something that doesn't exist directly inside but is inside the
@ -37,7 +39,7 @@ export function parse(source, comments, typescript, is_script) {
// an error in these cases // an error in these cases
if (is_script) { if (is_script) {
// @ts-ignore // @ts-ignore
parser.prototype.parseStatement = function (...args) { acorn.prototype.parseStatement = function (...args) {
const v = parse_statement.call(this, ...args); const v = parse_statement.call(this, ...args);
// @ts-ignore // @ts-ignore
this.undefinedExports = {}; this.undefinedExports = {};
@ -45,25 +47,27 @@ export function parse(source, comments, typescript, is_script) {
}; };
} }
let ast;
try { try {
ast = parser.parse(source, { const ast = acorn.parse(source, {
onComment, onComment,
sourceType: 'module', sourceType: 'module',
ecmaVersion: 16, ecmaVersion: 16,
locations: true locations: true
}); });
add_comments(ast);
return /** @type {Program} */ (ast);
} catch (err) {
// TODO the `return` in necessary for TS<7 due to a bug; otherwise
// the `finally` block is regarded as unreachable
return handle_parse_error(err, source, typescript);
} finally { } finally {
if (is_script) { if (is_script) {
// @ts-ignore // @ts-expect-error
parser.prototype.parseStatement = parse_statement; acorn.prototype.parseStatement = parse_statement;
} }
} }
add_comments(ast);
return /** @type {Program} */ (ast);
} }
/** /**
@ -73,21 +77,44 @@ export function parse(source, comments, typescript, is_script) {
* @returns {acorn.Expression & { leadingComments?: CommentWithLocation[]; trailingComments?: CommentWithLocation[]; }} * @returns {acorn.Expression & { leadingComments?: CommentWithLocation[]; trailingComments?: CommentWithLocation[]; }}
*/ */
export function parse_expression_at(parser, source, index) { export function parse_expression_at(parser, source, index) {
const _ = parser.ts ? ParserWithTS : acorn.Parser; const acorn = parser.ts ? TSParser : JSParser;
const { onComment, add_comments } = get_comment_handlers(source, parser.root.comments, index); const { onComment, add_comments } = get_comment_handlers(source, parser.root.comments, index);
const ast = _.parseExpressionAt(source, index, { try {
onComment, const ast = acorn.parseExpressionAt(source, index, {
sourceType: 'module', onComment,
ecmaVersion: 16, sourceType: 'module',
locations: true, ecmaVersion: 16,
preserveParens: true locations: true,
}); preserveParens: true
});
add_comments(ast); add_comments(ast);
return ast;
} catch (e) {
handle_parse_error(e, source, parser.ts);
}
}
const regex_position_indicator = / \(\d+:\d+\)$/;
/**
* @param {any} err
* @param {string} source
* @param {boolean} typescript
* @returns {never}
*/
function handle_parse_error(err, source, typescript) {
let message = /** @type {string} */ (err.message).replace(regex_position_indicator, '');
let pos = /** @type {number} */ (err.pos);
if (typescript && source[pos] === ':') {
message += ` (did you forget to add \`lang="ts"\`?)`;
}
return ast; e.js_parse_error(pos, message);
} }
/** /**

@ -11,8 +11,6 @@ import { is_reserved } from '../../../utils.js';
import { disallow_children } from '../2-analyze/visitors/shared/special-element.js'; import { disallow_children } from '../2-analyze/visitors/shared/special-element.js';
import * as state from '../../state.js'; import * as state from '../../state.js';
const regex_position_indicator = / \(\d+:\d+\)$/;
/** @param {number} cc */ /** @param {number} cc */
function is_whitespace(cc) { function is_whitespace(cc) {
// fast path for common whitespace // fast path for common whitespace
@ -175,21 +173,6 @@ export class Parser {
return this.stack[this.stack.length - 1]; return this.stack[this.stack.length - 1];
} }
/**
* @param {any} err
* @returns {never}
*/
acorn_error(err) {
const message = err.message.replace(regex_position_indicator, '');
const hint =
!this.ts && typeof err.pos === 'number' && this.template[err.pos] === ':'
? ` (did you forget to add \`lang="ts"\`?)`
: '';
e.js_parse_error(err.pos, message + hint);
}
/** /**
* @param {string} str * @param {string} str
* @param {boolean} required * @param {boolean} required

@ -35,36 +35,32 @@ export default function read_pattern(parser) {
const pattern_string = parser.template.slice(start, i); const pattern_string = parser.template.slice(start, i);
try { // the length of the `space_with_newline` has to be start - 1
// the length of the `space_with_newline` has to be start - 1 // because we added a `(` in front of the pattern_string,
// because we added a `(` in front of the pattern_string, // which shifted the entire string to right by 1
// which shifted the entire string to right by 1 // so we offset it by removing 1 character in the `space_with_newline`
// so we offset it by removing 1 character in the `space_with_newline` // to achieve that, we remove the 1st space encountered,
// to achieve that, we remove the 1st space encountered, // so it will not affect the `column` of the node
// so it will not affect the `column` of the node let space_with_newline = parser.template
let space_with_newline = parser.template .slice(0, start)
.slice(0, start) .replace(regex_not_newline_characters, ' ');
.replace(regex_not_newline_characters, ' '); const first_space = space_with_newline.indexOf(' ');
const first_space = space_with_newline.indexOf(' '); space_with_newline =
space_with_newline = space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
/** @type {any} */
/** @type {any} */ let expression = remove_parens(
let expression = remove_parens( parse_expression_at(parser, `${space_with_newline}(${pattern_string} = 1)`, start - 1)
parse_expression_at(parser, `${space_with_newline}(${pattern_string} = 1)`, start - 1) );
);
expression = expression.left;
expression = expression.left;
expression.typeAnnotation = read_type_annotation(parser);
expression.typeAnnotation = read_type_annotation(parser); if (expression.typeAnnotation) {
if (expression.typeAnnotation) { expression.end = expression.typeAnnotation.end;
expression.end = expression.typeAnnotation.end;
}
return expression;
} catch (error) {
parser.acorn_error(error);
} }
return expression;
} }
/** /**

@ -55,7 +55,7 @@ export default function read_expression(parser, opening_token, disallow_loose) {
} }
} }
parser.acorn_error(err); throw err;
} }
if (!parser.ts) { if (!parser.ts) {

@ -31,14 +31,7 @@ export function read_script(parser, start, attributes) {
parser.template.slice(0, script_start).replace(regex_not_newline_characters, ' ') + data; parser.template.slice(0, script_start).replace(regex_not_newline_characters, ' ') + data;
parser.read(regex_starts_with_closing_script_tag); parser.read(regex_starts_with_closing_script_tag);
/** @type {Program} */ const ast = acorn.parse(source, parser.root.comments, parser.ts, true);
let ast;
try {
ast = acorn.parse(source, parser.root.comments, parser.ts, true);
} catch (err) {
parser.acorn_error(err);
}
ast.start = script_start; ast.start = script_start;

Loading…
Cancel
Save