feat: ok I think the client snippet block finally works

pull/10320/head
S. Elliott Johnson 2 years ago
parent 5bab2db39e
commit 5beaf36d35

@ -3,7 +3,9 @@ import read_expression from '../read/expression.js';
import { error } from '../../../errors.js'; import { error } from '../../../errors.js';
import { create_fragment } from '../utils/create.js'; import { create_fragment } from '../utils/create.js';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import { parse } from '../acorn.js'; import { parse, parse_expression_at } from '../acorn.js';
import { find_matching_bracket } from '../utils/bracket.js';
import full_char_code_at from '../utils/full_char_code_at.js';
const regex_whitespace_with_closing_curly_brace = /^\s*}/; const regex_whitespace_with_closing_curly_brace = /^\s*}/;
@ -263,29 +265,40 @@ function open(parser) {
return; return;
} }
if (parser.eat('snippet')) { if (parser.match('snippet')) {
parser.require_whitespace(); const snippet_declaraion_end = find_matching_bracket(
parser,
const name_start = parser.index; full_char_code_at(parser.template, start)
const name = parser.read_identifier(); );
const name_end = parser.index; // we'll eat this later, so save it
const raw_snippet_declaration = parser.template.slice(start, snippet_declaraion_end);
parser.eat('(', true); const start_subtractions = '{#snippet ';
const end_subtractions = '}';
parser.allow_whitespace();
// now the snippet declaration is just a function declaration (`function snipName() {}`)
const snippet_declaration = `function ${raw_snippet_declaration.slice(
start_subtractions.length,
raw_snippet_declaration.length - end_subtractions.length
)} {}`;
// we offset the index by one because `function` takes the same space as `#snippet`
const snippet_expression = parse_expression_at(
`${parser.template.slice(0, parser.index - 1)}${snippet_declaration}`,
parser.ts,
parser.index - 1
);
const elements = []; // TODO: handle error
while (!parser.match(')')) { if (snippet_expression.type !== 'FunctionExpression') {
elements.push(read_context(parser)); throw new Error();
parser.eat(',');
parser.allow_whitespace();
} }
parser.allow_whitespace(); // TODO: handle error
parser.eat(')', true); if (!snippet_expression.id) {
throw new Error();
}
parser.allow_whitespace(); parser.eat(raw_snippet_declaration);
parser.eat('}', true);
const block = parser.append( const block = parser.append(
/** @type {Omit<import('#compiler').SnippetBlock, 'parent'>} */ /** @type {Omit<import('#compiler').SnippetBlock, 'parent'>} */
@ -293,15 +306,10 @@ function open(parser) {
type: 'SnippetBlock', type: 'SnippetBlock',
start, start,
end: -1, end: -1,
expression: { expression: snippet_expression.id,
type: 'Identifier',
start: name_start,
end: name_end,
name
},
context: { context: {
type: 'ArrayPattern', type: 'ArrayPattern',
elements elements: snippet_expression.params
}, },
body: create_fragment() body: create_fragment()
}) })

@ -1,3 +1,5 @@
import full_char_code_at from './full_char_code_at.js';
const SQUARE_BRACKET_OPEN = '['.charCodeAt(0); const SQUARE_BRACKET_OPEN = '['.charCodeAt(0);
const SQUARE_BRACKET_CLOSE = ']'.charCodeAt(0); const SQUARE_BRACKET_CLOSE = ']'.charCodeAt(0);
const CURLY_BRACKET_OPEN = '{'.charCodeAt(0); const CURLY_BRACKET_OPEN = '{'.charCodeAt(0);
@ -33,3 +35,26 @@ export function get_bracket_close(open) {
return CURLY_BRACKET_CLOSE; return CURLY_BRACKET_CLOSE;
} }
} }
/**
* The function almost known as bracket_fight
* @param {import('..').Parser} parser
* @param {number} open
* @returns {number}
*/
export function find_matching_bracket(parser, open) {
const close = get_bracket_close(open);
let brackets = 1;
let i = parser.index;
while (brackets > 0 && i < parser.template.length) {
const code = full_char_code_at(parser.template, i);
if (code === open) {
brackets++;
} else if (code === close) {
brackets--;
}
i++;
}
// TODO: If not found
return i;
}

@ -2440,6 +2440,7 @@ export const template_visitors = {
}, },
SnippetBlock(node, context) { SnippetBlock(node, context) {
// TODO hoist where possible // TODO hoist where possible
/** @type {import('estree').Pattern[]} */
const args = [b.id('$$anchor')]; const args = [b.id('$$anchor')];
/** @type {import('estree').BlockStatement} */ /** @type {import('estree').BlockStatement} */
@ -2448,18 +2449,38 @@ export const template_visitors = {
/** @type {import('estree').Statement[]} */ /** @type {import('estree').Statement[]} */
const declarations = []; const declarations = [];
node.context.elements.forEach((element, i) => { node.context.elements.forEach((argument, i) => {
if (!element) return; if (!argument) return;
const id = element.type === 'Identifier' ? element : b.id(`$$arg${i}`); // If the argument is itself an identifier (`foo`) or is a simple rest identifier (`...foo`)
args.push(id); // we don't have to do anything fancy -- just push it onto the args array and make sure its
// binding expression is a call of itself.
/** @type {import('estree').Identifier | undefined} */
let identifier;
/** @type {import('estree').Identifier | import('estree').RestElement | string} */
let arg;
if (argument.type === 'Identifier') {
identifier = argument;
arg = argument;
} else if (argument.type === 'RestElement' && argument.argument.type === 'Identifier') {
identifier = argument.argument;
arg = argument;
} else if (argument.type === 'RestElement') {
arg = b.rest(b.id(`$$arg${i}`));
} else {
arg = b.id(`$$arg${i}`);
}
args.push(arg);
if (element.type === 'Identifier') { if (identifier) {
const binding = /** @type {import('#compiler').Binding} */ ( const binding = /** @type {import('#compiler').Binding} */ (
context.state.scope.get(id.name) context.state.scope.get(identifier.name)
); );
binding.expression = b.call(id); binding.expression = b.call(identifier);
} else { } else {
const paths = extract_paths(element); // If, on the other hand, it isn't an identifier, it's a destructuring expression, which could be
// a rest destructure (eg. `...[foo, bar, { baz }, ...rest]`, which, absurdly, is all valid syntax).
// In this case, we need to follow the destructuring expression to figure out what variables are being extracted.
const paths = extract_paths(argument.type === 'RestElement' ? argument.argument : argument);
for (const path of paths) { for (const path of paths) {
const name = /** @type {import('estree').Identifier} */ (path.node).name; const name = /** @type {import('estree').Identifier} */ (path.node).name;

Loading…
Cancel
Save