the parser's tables sit on the root they belong to, and a reference is one record: what resolves it and what the parser saw ride along

goodbye-acorn
Nic 3 days ago
parent ea1ef3a067
commit 9bc9689083

@ -4,7 +4,7 @@
import * as teasel from '@teasel/parser';
import * as e from '../../errors.js';
import { find_matching_bracket } from './utils/bracket.js';
import { parsed } from '../../utils/ast.js';
import { keep_tables } from '../../utils/ast.js';
/**
* A standalone module, as `analyze_module` reads one.
@ -29,7 +29,7 @@ export function parse(source, comments, typescript) {
add_comments(source, comments, /** @type {teasel.Comment[]} */ (ast.comments));
delete ast.comments;
parsed.set(ast, tables(ast));
keep_tables(ast, tables(ast));
return ast;
}
@ -67,7 +67,7 @@ export function parse_script(parser, start, end) {
/** @type {teasel.Comment[]} */ (ast.comments)
);
delete ast.comments;
parsed.set(ast, tables(ast));
keep_tables(ast, tables(ast));
unsupported(ast.typescript);
delete ast.typescript;
@ -113,7 +113,7 @@ export function read_expression(parser, until, opening_token = '{') {
try {
const answer = read(parser, (js) => js.parseExpressionAt(start, until));
parsed.set(answer.node, tables(answer));
keep_tables(answer.node, tables(answer));
return answer.node;
} catch (err) {
if (parser.loose) {
@ -157,7 +157,7 @@ export function read_pattern(parser) {
}
const answer = read(parser, (js) => js.parsePatternAt(start));
parsed.set(answer.node, tables(answer));
keep_tables(answer.node, tables(answer));
return answer.node;
}
@ -168,7 +168,7 @@ export function read_pattern(parser) {
export function read_params(parser) {
const start = parser.index;
const answer = read(parser, (js) => js.parseParamsAt(start));
parsed.set(answer.params, tables(answer));
keep_tables(answer.params, tables(answer));
return answer.params;
}
@ -188,7 +188,7 @@ export function read_statement(parser) {
throw err;
}
});
parsed.set(answer.node, tables(answer));
keep_tables(answer.node, tables(answer));
return answer.node;
}

@ -8,7 +8,7 @@ import * as e from '../errors.js';
import { bindingOf, parentOf } from '@teasel/parser';
import {
extract_identifiers,
parsed,
tables_of,
extract_identifiers_from_destructuring,
is_reference,
object,
@ -98,18 +98,30 @@ export class Reference {
/** @type {AST.SvelteNode[]} the template's ancestors, above the JavaScript the identifier sits in */
#template;
/**
* What the parser resolved the identifier to, when it did, and what it saw it do.
* @type {Binding | undefined}
*/
resolved;
/** @type {import('@teasel/parser').Reference | undefined} */
parsed;
/**
* @param {Identifier} node
* @param {Scope} scope
* @param {AST.SvelteNode[]} path the ancestors, or the template's alone when the identifier
* comes from the parser, whose parent links supply the rest
* @param {boolean} [complete]
* @param {Binding} [resolved]
* @param {import('@teasel/parser').Reference} [parsed]
*/
constructor(node, scope, path, complete = true) {
constructor(node, scope, path, complete = true, resolved = undefined, parsed = undefined) {
this.node = node;
this.scope = scope;
this.#template = path;
if (complete) this.#path = path;
this.resolved = resolved;
this.parsed = parsed;
}
/** The ancestors of the identifier, outermost first: the template's, then the JavaScript's. */
@ -976,10 +988,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
/** @type {State} */
const state = { scope };
/**
* Every reference with the scope it is made from, and what the parser knows of it.
* @type {[Scope, Reference, Binding | undefined, import('@teasel/parser').Reference | undefined][]}
*/
/** Every reference, resolved once the walk is done. @type {Reference[]} */
const references = [];
/** @type {[Scope, Pattern | MemberExpression, Expression][]} */
@ -1059,7 +1068,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
* The scopes, declarations and references of one parsed answer, from the parser's tables: a
* script's program, or a template's expression, pattern, parameter list or declaration.
* @param {Scope} scope what the answer's outermost scope is here
* @param {NonNullable<ReturnType<typeof parsed.get>>} answer
* @param {NonNullable<ReturnType<typeof tables_of>>} answer
* @param {AST.SvelteNode[]} path the answer's ancestors
* @param {boolean} template the answer's declarations are a const tag's
*/
@ -1073,12 +1082,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
answer.bindings.length === 0
) {
for (const reference of answer.references) {
references.push([
scope,
new Reference(reference.node, scope, path, false),
undefined,
reference
]);
references.push(new Reference(reference.node, scope, path, false, undefined, reference));
}
return;
}
@ -1205,7 +1209,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
entries.sort((a, b) => /** @type {number} */ (a[0].start) - /** @type {number} */ (b[0].start));
for (const [node, scope, binding, reference] of entries) {
references.push([scope, new Reference(node, scope, path, false), binding, reference]);
references.push(new Reference(node, scope, path, false, binding, reference));
}
}
@ -1227,7 +1231,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
let has_await = false;
// a script comes entirely from the parser's tables: nothing in it is walked
const program = ast.type === 'Program' ? parsed.get(ast) : undefined;
const program = ast.type === 'Program' ? tables_of(ast) : undefined;
if (program !== undefined) {
has_await = program.scopes[0].topLevelAwait;
for (const node of /** @type {Program} */ (ast).body) {
@ -1333,7 +1337,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
walk(ast, state, {
// the JavaScript of a template, parsed on its own: its scopes come from the parser's tables
_(node, context) {
const answer = parsed.get(node);
const answer = tables_of(node);
if (answer === undefined) return context.next();
has_await ||= answer.scopes[0].topLevelAwait;
from_tables(
@ -1347,12 +1351,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
// an identifier the template built itself, a shorthand attribute's say, is no parser's
Identifier(node, { path, state }) {
if (is_reference(node, path.at(-1))) {
references.push([
state.scope,
new Reference(node, state.scope, path.slice()),
undefined,
undefined
]);
references.push(new Reference(node, state.scope, path.slice()));
}
},
@ -1523,7 +1522,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
}
}
const params = parsed.get(node.parameters);
const params = tables_of(node.parameters);
if (params !== undefined) from_tables(child_scope, params, [...context.path, node], false);
for (const child of node.body.nodes) {
context.visit(child, { scope: child_scope });
@ -1572,8 +1571,9 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
// we do this after the fact, so that we don't need to worry
// about encountering references before their declarations
for (const [scope, reference, resolved, parsed] of references) {
const binding = scope.reference(reference.node, reference.path, resolved, reference);
for (const reference of references) {
const { scope, parsed } = reference;
const binding = scope.reference(reference.node, reference.path, reference.resolved, reference);
// what the parser saw the identifier do; a declaring identifier is no reference to it
if (binding === null || parsed === undefined) continue;
if (parsed.write) {

@ -4,11 +4,31 @@ import { walk } from 'zimmerframe';
import { bindingOf } from '@teasel/parser';
/**
* The parser's answer for each JavaScript root it parsed, a script's program or a template's
* expression, pattern, parameter list or declaration: its scope, binding and reference tables.
* @type {WeakMap<object, { scopes: import('@teasel/parser').Scope[]; bindings: import('@teasel/parser').Binding[]; references: import('@teasel/parser').Reference[] }>}
* @typedef {{ scopes: import('@teasel/parser').Scope[]; bindings: import('@teasel/parser').Binding[]; references: import('@teasel/parser').Reference[] }} Tables
* the parser's scope, binding and reference tables for one answer
*/
export const parsed = new WeakMap();
// a symbol on the root: a property load where the walk asks every node, not a map lookup
const PARSED = Symbol('parsed');
/**
* Keeps the parser's tables on the root it parsed, a script's program or a template's
* expression, pattern, parameter list or declaration.
* @param {object} root
* @param {Tables} tables
*/
export function keep_tables(root, tables) {
/** @type {any} */ (root)[PARSED] = tables;
}
/**
* The parser's tables for a root, when it is one.
* @param {object} node
* @returns {Tables | undefined}
*/
export function tables_of(node) {
return /** @type {any} */ (node)[PARSED];
}
import * as b from '#compiler/builders';
/**

Loading…
Cancel
Save