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 { create_fragment } from '../utils/create.js';
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*}/;
@ -263,29 +265,40 @@ function open(parser) {
return;
}
if (parser.eat('snippet')) {
parser.require_whitespace();
const name_start = parser.index;
const name = parser.read_identifier();
const name_end = parser.index;
parser.eat('(', true);
parser.allow_whitespace();
if (parser.match('snippet')) {
const snippet_declaraion_end = find_matching_bracket(
parser,
full_char_code_at(parser.template, start)
);
// we'll eat this later, so save it
const raw_snippet_declaration = parser.template.slice(start, snippet_declaraion_end);
const start_subtractions = '{#snippet ';
const end_subtractions = '}';
// 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 = [];
while (!parser.match(')')) {
elements.push(read_context(parser));
parser.eat(',');
parser.allow_whitespace();
// TODO: handle error
if (snippet_expression.type !== 'FunctionExpression') {
throw new Error();
}
parser.allow_whitespace();
parser.eat(')', true);
// TODO: handle error
if (!snippet_expression.id) {
throw new Error();
}
parser.allow_whitespace();
parser.eat('}', true);
parser.eat(raw_snippet_declaration);
const block = parser.append(
/** @type {Omit<import('#compiler').SnippetBlock, 'parent'>} */
@ -293,15 +306,10 @@ function open(parser) {
type: 'SnippetBlock',
start,
end: -1,
expression: {
type: 'Identifier',
start: name_start,
end: name_end,
name
},
expression: snippet_expression.id,
context: {
type: 'ArrayPattern',
elements
elements: snippet_expression.params
},
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_CLOSE = ']'.charCodeAt(0);
const CURLY_BRACKET_OPEN = '{'.charCodeAt(0);
@ -33,3 +35,26 @@ export function get_bracket_close(open) {
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) {
// TODO hoist where possible
/** @type {import('estree').Pattern[]} */
const args = [b.id('$$anchor')];
/** @type {import('estree').BlockStatement} */
@ -2448,18 +2449,38 @@ export const template_visitors = {
/** @type {import('estree').Statement[]} */
const declarations = [];
node.context.elements.forEach((element, i) => {
if (!element) return;
const id = element.type === 'Identifier' ? element : b.id(`$$arg${i}`);
args.push(id);
node.context.elements.forEach((argument, i) => {
if (!argument) return;
// If the argument is itself an identifier (`foo`) or is a simple rest identifier (`...foo`)
// 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} */ (
context.state.scope.get(id.name)
context.state.scope.get(identifier.name)
);
binding.expression = b.call(id);
binding.expression = b.call(identifier);
} 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) {
const name = /** @type {import('estree').Identifier} */ (path.node).name;

Loading…
Cancel
Save