readers move the cursor themselves; read/context and read/expression fold into js.js

goodbye-acorn
Nic 22 hours ago
parent e5e5f4d40f
commit 803476f793

@ -3,6 +3,7 @@
/** @import { Parser } from './index.js' */
import * as teasel from '@teasel/parser';
import * as e from '../../errors.js';
import { find_matching_bracket } from './utils/bracket.js';
/**
* A standalone module, as `analyze_module` reads one.
@ -63,88 +64,113 @@ export function parse_script(parser, start, end) {
}
/**
* Reads JavaScript at the cursor with the template's parser and moves the cursor past it.
* @template {{ end: number, comments?: teasel.Comment[], typescript?: teasel.Kept[] }} T
* @param {Parser} parser
* @param {number} index
* @param {'as'} [until] the host's `as` follows the expression, as an each block's item does
* @returns {{ node: Expression, end: number }}
* @param {(js: teasel.Source) => T} run
* @returns {T}
*/
export function parse_expression_at(parser, index, until) {
function read(parser, run) {
let answer;
try {
answer = parser.js.parseExpressionAt(index, until);
answer = run(parser.js);
} catch (err) {
// the parser's syntax errors; a compile error thrown while reading is the host's own
if (!(err instanceof SyntaxError)) throw err;
return handle_parse_error(err);
}
return accept(parser, answer, answer.node);
add_comments(
parser.template,
parser.root.comments,
/** @type {teasel.Comment[]} */ (answer.comments)
);
unsupported(answer.typescript);
parser.index = answer.end;
return answer;
}
/**
* @param {Parser} parser
* @param {number} index
* @returns {{ node: Pattern, end: number }}
* @param {'as'} [until] the host's `as` follows the expression, as an each block's item does
* @param {string} [opening_token] the bracket the expression sits in, for loose mode
* @returns {Expression}
*/
export function parse_pattern_at(parser, index) {
let answer;
export function read_expression(parser, until, opening_token = '{') {
const start = parser.index;
try {
answer = parser.js.parsePatternAt(index);
return read(parser, (js) => js.parseExpressionAt(start, until)).node;
} catch (err) {
return handle_parse_error(err);
}
if (parser.loose) {
// Find the next } and treat it as the end of the expression
const end = find_matching_bracket(parser.template, start, opening_token);
if (end !== undefined) {
parser.index = end;
// We don't know what the expression is and signal this by returning an empty identifier
return { type: 'Identifier', start, end, name: '' };
}
}
return accept(parser, answer, answer.node);
throw err;
}
}
/**
* @param {Parser} parser
* @param {number} index the opening paren
* @returns {{ node: Pattern[], end: number }}
* @returns {Pattern}
*/
export function parse_params_at(parser, index) {
let answer;
try {
answer = parser.js.parseParamsAt(index);
} catch (err) {
return handle_parse_error(err);
export function read_pattern(parser) {
const start = parser.index;
const id = parser.read_identifier();
if (id.name !== '') {
const after = parser.index;
parser.allow_whitespace();
// a type annotation makes it a job for the parser
if (!parser.match(':')) {
parser.index = after;
return id;
}
} else {
const char = parser.template[start];
if (char !== '{' && char !== '[') {
e.expected_pattern(start);
}
}
return accept(parser, answer, answer.params);
return read(parser, (js) => js.parsePatternAt(start)).node;
}
/**
* @param {Parser} parser
* @param {number} index
* @returns {{ node: Statement, end: number }}
* @param {Parser} parser at the opening paren
* @returns {Pattern[]}
*/
export function parse_statement_at(parser, index) {
let answer;
try {
answer = parser.js.parseStatementAt(index);
} catch (err) {
if (/** @type {any} */ (err).code === 'unexpected_eof') e.unexpected_eof(parser.template.length);
return handle_parse_error(err);
}
return accept(parser, answer, answer.node);
export function read_params(parser) {
const start = parser.index;
return read(parser, (js) => js.parseParamsAt(start)).params;
}
/**
* Keeps an answer's comments, rejects what erasure could not express, and hands back the node
* with the offset the parse stopped at.
* @template T
* @param {Parser} parser
* @param {{ end: number, comments?: teasel.Comment[], typescript?: teasel.Kept[] }} answer
* @param {T} node
* @returns {{ node: T, end: number }}
* @returns {Statement}
*/
function accept(parser, answer, node) {
add_comments(
parser.template,
parser.root.comments,
/** @type {teasel.Comment[]} */ (answer.comments)
);
unsupported(answer.typescript);
return { node, end: answer.end };
export function read_statement(parser) {
const start = parser.index;
return read(parser, (js) => {
try {
return js.parseStatementAt(start);
} catch (err) {
if (/** @type {any} */ (err).code === 'unexpected_eof')
e.unexpected_eof(parser.template.length);
throw err;
}
}).node;
}
/** What erasure leaves in place needs a compiler, not this one */

@ -1,36 +0,0 @@
/** @import { Pattern } from 'estree' */
/** @import { Parser } from '../index.js' */
import { parse_pattern_at } from '../js.js';
import * as e from '../../../errors.js';
/**
* @param {Parser} parser
* @returns {Pattern}
*/
export default function read_pattern(parser) {
const start = parser.index;
const id = parser.read_identifier();
if (id.name !== '') {
const after = parser.index;
parser.allow_whitespace();
// a type annotation makes it a job for the parser
if (!parser.match(':')) {
parser.index = after;
return id;
}
} else {
const char = parser.template[start];
if (char !== '{' && char !== '[') {
e.expected_pattern(start);
}
}
const { node, end } = parse_pattern_at(parser, start);
parser.index = end;
return node;
}

@ -1,52 +0,0 @@
/** @import { Expression, Identifier } from 'estree' */
/** @import { Parser } from '../index.js' */
import { parse_expression_at } from '../js.js';
import * as e from '../../../errors.js';
import { find_matching_bracket } from '../utils/bracket.js';
/**
* @param {Parser} parser
* @param {string} [opening_token]
* @returns {Expression | undefined}
*/
export function get_loose_identifier(parser, opening_token) {
// Find the next } and treat it as the end of the expression
const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{');
if (end) {
const start = parser.index;
parser.index = end;
// We don't know what the expression is and signal this by returning an empty identifier
return {
type: 'Identifier',
start,
end,
name: ''
};
}
}
/**
* @param {Parser} parser
* @param {string} [opening_token]
* @param {boolean} [disallow_loose]
* @param {'as'} [until] the host's `as` follows the expression, as an each block's item does
* @returns {Expression}
*/
export default function read_expression(parser, opening_token, disallow_loose, until) {
try {
const { node, end } = parse_expression_at(parser, parser.index, until);
parser.index = end;
return node;
} catch (err) {
// If we are in an each loop we need the error to be thrown in cases like
// `as { y = z }` so we still throw and handle the error there
if (parser.loose && !disallow_loose) {
const expression = get_loose_identifier(parser, opening_token);
if (expression) {
return expression;
}
}
throw err;
}
}

@ -3,7 +3,7 @@
/** @import { AST } from '#compiler' */
/** @import { Parser } from '../index.js' */
import { is_void, REGEX_VALID_TAG_NAME } from '../../../../utils.js';
import read_expression from '../read/expression.js';
import { read_expression } from '../js.js';
import { read_script } from '../read/script.js';
import read_style from '../read/style.js';
import { decode_character_references } from '../utils/html.js';
@ -11,7 +11,11 @@ import * as e from '../../../errors.js';
import * as w from '../../../warnings.js';
import { create_fragment } from '../utils/create.js';
import { create_attribute, ExpressionMetadata, is_element_node } from '../../nodes.js';
import { get_attribute_expression, is_expression_attribute, value_names } from '../../../utils/ast.js';
import {
get_attribute_expression,
is_expression_attribute,
value_names
} from '../../../utils/ast.js';
import { closing_tag_omitted } from '../../../../html-tree-validation.js';
import { list } from '../../../utils/string.js';
import { locator } from '../../../state.js';

@ -3,9 +3,7 @@
/** @import { Parser } from '../index.js' */
import * as e from '../../../errors.js';
import { ExpressionMetadata } from '../../nodes.js';
import { parse_params_at, parse_statement_at } from '../js.js';
import read_pattern from '../read/context.js';
import read_expression from '../read/expression.js';
import { read_expression, read_params, read_pattern, read_statement } from '../js.js';
import { create_fragment } from '../utils/create.js';
import { find_matching_bracket, match_bracket } from '../utils/bracket.js';
@ -89,14 +87,12 @@ function read_declaration(parser) {
/** @type {import('estree').Statement | import('estree').VariableDeclaration} */
let declaration;
/** @type {number} */
let end;
try {
({ node: declaration, end } = parse_statement_at(parser, start));
declaration = read_statement(parser);
} catch (error) {
if (!parser.loose) throw error;
end = /** @type {number} */ (find_matching_bracket(parser.template, start, '{'));
const end = find_matching_bracket(parser.template, start, '{');
if (end === undefined) throw error;
parser.index = end;
@ -126,6 +122,7 @@ function read_declaration(parser) {
if (declaration.type !== 'VariableDeclaration') {
if (declaration.type === 'ExpressionStatement') {
parser.index = start;
parser.root.comments.length = initial_comment_count; // Else they show up duplicated
return null;
} else {
@ -142,7 +139,6 @@ function read_declaration(parser) {
e.declaration_tag_invalid_type(declaration);
}
parser.index = end;
parser.allow_whitespace();
parser.eat('}', true);
@ -184,7 +180,7 @@ function open(parser) {
parser.require_whitespace();
// the list ends at the `as` that names the item, so a TypeScript assertion in it needs parens
let expression = read_expression(parser, undefined, false, 'as');
let expression = read_expression(parser, 'as');
parser.allow_whitespace();
@ -224,7 +220,7 @@ function open(parser) {
if (parser.eat('(')) {
parser.allow_whitespace();
key = read_expression(parser, '(');
key = read_expression(parser, undefined, '(');
parser.allow_whitespace();
parser.eat(')', true);
parser.allow_whitespace();
@ -410,16 +406,14 @@ function open(parser) {
/** @type {import('estree').Pattern[]} */
let parameters = [];
if (parser.eat('(', true, false)) {
const open = parser.index - 1;
if (find_matching_bracket(parser.template, parser.index, '(') === undefined) {
if (parser.match('(')) {
if (find_matching_bracket(parser.template, parser.index + 1, '(') === undefined) {
e.expected_token(parser.template.length, ')');
}
const { node, end } = parse_params_at(parser, open);
parameters = node;
parser.index = end;
parameters = read_params(parser);
} else if (!parser.loose) {
e.expected_token(parser.index, '(');
}
parser.allow_whitespace();

Loading…
Cancel
Save