svelte
Version:
Cybernetically enhanced web apps
661 lines (592 loc) • 16.2 kB
JavaScript
/** @import * as ESTree from 'estree' */
import { regex_is_valid_identifier } from '../phases/patterns.js';
import { sanitize_template_string } from './sanitize_template_string.js';
/**
* @param {Array<ESTree.Expression | ESTree.SpreadElement | null>} elements
* @returns {ESTree.ArrayExpression}
*/
export function array(elements = []) {
return { type: 'ArrayExpression', elements };
}
/**
* @param {Array<ESTree.Pattern | null>} elements
* @returns {ESTree.ArrayPattern}
*/
export function array_pattern(elements) {
return { type: 'ArrayPattern', elements };
}
/**
* @param {ESTree.Pattern} left
* @param {ESTree.Expression} right
* @returns {ESTree.AssignmentPattern}
*/
export function assignment_pattern(left, right) {
return { type: 'AssignmentPattern', left, right };
}
/**
* @param {Array<ESTree.Pattern>} params
* @param {ESTree.BlockStatement | ESTree.Expression} body
* @returns {ESTree.ArrowFunctionExpression}
*/
export function arrow(params, body) {
return {
type: 'ArrowFunctionExpression',
params,
body,
expression: body.type !== 'BlockStatement',
generator: false,
async: false,
metadata: /** @type {any} */ (null) // should not be used by codegen
};
}
/**
* @param {ESTree.AssignmentOperator} operator
* @param {ESTree.Pattern} left
* @param {ESTree.Expression} right
* @returns {ESTree.AssignmentExpression}
*/
export function assignment(operator, left, right) {
return { type: 'AssignmentExpression', operator, left, right };
}
/**
* @template T
* @param {T & ESTree.BaseFunction} func
* @returns {T & ESTree.BaseFunction}
*/
export function async(func) {
return { ...func, async: true };
}
/**
* @param {ESTree.Expression} argument
* @returns {ESTree.AwaitExpression}
*/
function await_builder(argument) {
return { type: 'AwaitExpression', argument };
}
/**
* @param {ESTree.BinaryOperator} operator
* @param {ESTree.Expression} left
* @param {ESTree.Expression} right
* @returns {ESTree.BinaryExpression}
*/
export function binary(operator, left, right) {
return { type: 'BinaryExpression', operator, left, right };
}
/**
* @param {ESTree.Statement[]} body
* @returns {ESTree.BlockStatement}
*/
export function block(body) {
return { type: 'BlockStatement', body };
}
/**
* @param {string} name
* @param {ESTree.Statement} body
* @returns {ESTree.LabeledStatement}
*/
export function labeled(name, body) {
return { type: 'LabeledStatement', label: id(name), body };
}
/**
* @param {string | ESTree.Expression} callee
* @param {...(ESTree.Expression | ESTree.SpreadElement | false | undefined)} args
* @returns {ESTree.CallExpression}
*/
export function call(callee, ...args) {
if (typeof callee === 'string') callee = id(callee);
args = args.slice();
// replacing missing arguments with `undefined`, unless they're at the end in which case remove them
let i = args.length;
let popping = true;
while (i--) {
if (!args[i]) {
if (popping) {
args.pop();
} else {
args[i] = id('undefined');
}
} else {
popping = false;
}
}
return {
type: 'CallExpression',
callee,
arguments: /** @type {Array<ESTree.Expression | ESTree.SpreadElement>} */ (args),
optional: false
};
}
/**
* @param {string | ESTree.Expression} callee
* @param {...ESTree.Expression} args
* @returns {ESTree.ChainExpression}
*/
export function maybe_call(callee, ...args) {
const expression = /** @type {ESTree.SimpleCallExpression} */ (call(callee, ...args));
expression.optional = true;
return {
type: 'ChainExpression',
expression
};
}
/**
* @param {ESTree.UnaryOperator} operator
* @param {ESTree.Expression} argument
* @returns {ESTree.UnaryExpression}
*/
export function unary(operator, argument) {
return { type: 'UnaryExpression', argument, operator, prefix: true };
}
export const void0 = unary('void', literal(0));
/**
* @param {ESTree.Expression} test
* @param {ESTree.Expression} consequent
* @param {ESTree.Expression} alternate
* @returns {ESTree.ConditionalExpression}
*/
export function conditional(test, consequent, alternate) {
return { type: 'ConditionalExpression', test, consequent, alternate };
}
/**
* @param {ESTree.LogicalOperator} operator
* @param {ESTree.Expression} left
* @param {ESTree.Expression} right
* @returns {ESTree.LogicalExpression}
*/
export function logical(operator, left, right) {
return { type: 'LogicalExpression', operator, left, right };
}
/**
* @param {'const' | 'let' | 'var'} kind
* @param {ESTree.VariableDeclarator[]} declarations
* @returns {ESTree.VariableDeclaration}
*/
export function declaration(kind, declarations) {
return {
type: 'VariableDeclaration',
kind,
declarations
};
}
/**
* @param {ESTree.Pattern | string} pattern
* @param {ESTree.Expression} [init]
* @returns {ESTree.VariableDeclarator}
*/
export function declarator(pattern, init) {
if (typeof pattern === 'string') pattern = id(pattern);
return { type: 'VariableDeclarator', id: pattern, init };
}
/** @type {ESTree.EmptyStatement} */
export const empty = {
type: 'EmptyStatement'
};
/**
* @param {ESTree.Expression | ESTree.MaybeNamedClassDeclaration | ESTree.MaybeNamedFunctionDeclaration} declaration
* @returns {ESTree.ExportDefaultDeclaration}
*/
export function export_default(declaration) {
return { type: 'ExportDefaultDeclaration', declaration };
}
/**
* @param {ESTree.Identifier} id
* @param {ESTree.Pattern[]} params
* @param {ESTree.BlockStatement} body
* @returns {ESTree.FunctionDeclaration}
*/
export function function_declaration(id, params, body) {
return {
type: 'FunctionDeclaration',
id,
params,
body,
generator: false,
async: false,
metadata: /** @type {any} */ (null) // should not be used by codegen
};
}
/**
* @param {string} name
* @param {ESTree.Statement[]} body
* @returns {ESTree.Property & { value: ESTree.FunctionExpression}}}
*/
export function get(name, body) {
return prop('get', key(name), function_builder(null, [], block(body)));
}
/**
* @param {string} name
* @returns {ESTree.Identifier}
*/
export function id(name) {
return { type: 'Identifier', name };
}
/**
* @param {string} name
* @returns {ESTree.PrivateIdentifier}
*/
export function private_id(name) {
return { type: 'PrivateIdentifier', name };
}
/**
* @param {string} local
* @returns {ESTree.ImportNamespaceSpecifier}
*/
function import_namespace(local) {
return {
type: 'ImportNamespaceSpecifier',
local: id(local)
};
}
/**
* @param {string} name
* @param {ESTree.Expression} value
* @returns {ESTree.Property}
*/
export function init(name, value) {
return prop('init', key(name), value);
}
/**
* @param {string | boolean | null | number | RegExp} value
* @returns {ESTree.Literal}
*/
export function literal(value) {
// @ts-expect-error we don't want to muck around with bigint here
return { type: 'Literal', value };
}
/**
* @param {ESTree.Expression | ESTree.Super} object
* @param {string | ESTree.Expression | ESTree.PrivateIdentifier} property
* @param {boolean} computed
* @param {boolean} optional
* @returns {ESTree.MemberExpression}
*/
export function member(object, property, computed = false, optional = false) {
if (typeof property === 'string') {
property = id(property);
}
return { type: 'MemberExpression', object, property, computed, optional };
}
/**
* @param {string} path
* @returns {ESTree.Identifier | ESTree.MemberExpression}
*/
export function member_id(path) {
const parts = path.split('.');
/** @type {ESTree.Identifier | ESTree.MemberExpression} */
let expression = id(parts[0]);
for (let i = 1; i < parts.length; i += 1) {
expression = member(expression, id(parts[i]));
}
return expression;
}
/**
* @param {Array<ESTree.Property | ESTree.SpreadElement>} properties
* @returns {ESTree.ObjectExpression}
*/
export function object(properties) {
return { type: 'ObjectExpression', properties };
}
/**
* @param {Array<ESTree.RestElement | ESTree.AssignmentProperty | ESTree.Property>} properties
* @returns {ESTree.ObjectPattern}
*/
export function object_pattern(properties) {
// @ts-expect-error the types appear to be wrong
return { type: 'ObjectPattern', properties };
}
/**
* @template {ESTree.Expression} Value
* @param {'init' | 'get' | 'set'} kind
* @param {ESTree.Expression} key
* @param {Value} value
* @param {boolean} computed
* @returns {ESTree.Property & { value: Value }}
*/
export function prop(kind, key, value, computed = false) {
return { type: 'Property', kind, key, value, method: false, shorthand: false, computed };
}
/**
* @param {ESTree.Expression | ESTree.PrivateIdentifier} key
* @param {ESTree.Expression | null | undefined} value
* @param {boolean} computed
* @param {boolean} is_static
* @returns {ESTree.PropertyDefinition}
*/
export function prop_def(key, value, computed = false, is_static = false) {
return { type: 'PropertyDefinition', key, value, computed, static: is_static };
}
/**
* @param {string} cooked
* @param {boolean} tail
* @returns {ESTree.TemplateElement}
*/
export function quasi(cooked, tail = false) {
const raw = sanitize_template_string(cooked);
return { type: 'TemplateElement', value: { raw, cooked }, tail };
}
/**
* @param {ESTree.Pattern} argument
* @returns {ESTree.RestElement}
*/
export function rest(argument) {
return { type: 'RestElement', argument };
}
/**
* @param {ESTree.Expression[]} expressions
* @returns {ESTree.SequenceExpression}
*/
export function sequence(expressions) {
return { type: 'SequenceExpression', expressions };
}
/**
* @param {string} name
* @param {ESTree.Statement[]} body
* @returns {ESTree.Property & { value: ESTree.FunctionExpression}}
*/
export function set(name, body) {
return prop('set', key(name), function_builder(null, [id('$$value')], block(body)));
}
/**
* @param {ESTree.Expression} argument
* @returns {ESTree.SpreadElement}
*/
export function spread(argument) {
return { type: 'SpreadElement', argument };
}
/**
* @param {ESTree.Expression} expression
* @returns {ESTree.ExpressionStatement}
*/
export function stmt(expression) {
return { type: 'ExpressionStatement', expression };
}
/**
* @param {ESTree.TemplateElement[]} elements
* @param {ESTree.Expression[]} expressions
* @returns {ESTree.TemplateLiteral}
*/
export function template(elements, expressions) {
return { type: 'TemplateLiteral', quasis: elements, expressions };
}
/**
* @param {ESTree.Expression | ESTree.BlockStatement} expression
* @param {boolean} [async]
* @returns {ESTree.Expression}
*/
export function thunk(expression, async = false) {
const fn = arrow([], expression);
if (async) fn.async = true;
return unthunk(fn);
}
/**
* Replace "(arg) => func(arg)" to "func"
* @param {ESTree.Expression} expression
* @returns {ESTree.Expression}
*/
export function unthunk(expression) {
if (
expression.type === 'ArrowFunctionExpression' &&
expression.async === false &&
expression.body.type === 'CallExpression' &&
expression.body.callee.type === 'Identifier' &&
expression.params.length === expression.body.arguments.length &&
expression.params.every((param, index) => {
const arg = /** @type {ESTree.SimpleCallExpression} */ (expression.body).arguments[index];
return param.type === 'Identifier' && arg.type === 'Identifier' && param.name === arg.name;
})
) {
return expression.body.callee;
}
return expression;
}
/**
*
* @param {string | ESTree.Expression} expression
* @param {...ESTree.Expression} args
* @returns {ESTree.NewExpression}
*/
function new_builder(expression, ...args) {
if (typeof expression === 'string') expression = id(expression);
return {
callee: expression,
arguments: args,
type: 'NewExpression'
};
}
/**
* @param {ESTree.UpdateOperator} operator
* @param {ESTree.Expression} argument
* @param {boolean} prefix
* @returns {ESTree.UpdateExpression}
*/
export function update(operator, argument, prefix = false) {
return { type: 'UpdateExpression', operator, argument, prefix };
}
/**
* @param {ESTree.Expression} test
* @param {ESTree.Statement} body
* @returns {ESTree.DoWhileStatement}
*/
export function do_while(test, body) {
return { type: 'DoWhileStatement', test, body };
}
const true_instance = literal(true);
const false_instance = literal(false);
const null_instance = literal(null);
/** @type {ESTree.DebuggerStatement} */
const debugger_builder = {
type: 'DebuggerStatement'
};
/** @type {ESTree.ThisExpression} */
const this_instance = {
type: 'ThisExpression'
};
/**
* @param {string | ESTree.Pattern} pattern
* @param { ESTree.Expression} [init]
* @returns {ESTree.VariableDeclaration}
*/
function let_builder(pattern, init) {
return declaration('let', [declarator(pattern, init)]);
}
/**
* @param {string | ESTree.Pattern} pattern
* @param { ESTree.Expression} init
* @returns {ESTree.VariableDeclaration}
*/
function const_builder(pattern, init) {
return declaration('const', [declarator(pattern, init)]);
}
/**
* @param {string | ESTree.Pattern} pattern
* @param { ESTree.Expression} [init]
* @returns {ESTree.VariableDeclaration}
*/
function var_builder(pattern, init) {
return declaration('var', [declarator(pattern, init)]);
}
/**
*
* @param {ESTree.VariableDeclaration | ESTree.Expression | null} init
* @param {ESTree.Expression} test
* @param {ESTree.Expression} update
* @param {ESTree.Statement} body
* @returns {ESTree.ForStatement}
*/
function for_builder(init, test, update, body) {
return { type: 'ForStatement', init, test, update, body };
}
/**
*
* @param {'constructor' | 'method' | 'get' | 'set'} kind
* @param {ESTree.Expression | ESTree.PrivateIdentifier} key
* @param {ESTree.Pattern[]} params
* @param {ESTree.Statement[]} body
* @param {boolean} computed
* @param {boolean} is_static
* @returns {ESTree.MethodDefinition}
*/
export function method(kind, key, params, body, computed = false, is_static = false) {
return {
type: 'MethodDefinition',
key,
kind,
value: function_builder(null, params, block(body)),
computed,
static: is_static
};
}
/**
*
* @param {ESTree.Identifier | null} id
* @param {ESTree.Pattern[]} params
* @param {ESTree.BlockStatement} body
* @returns {ESTree.FunctionExpression}
*/
function function_builder(id, params, body) {
return {
type: 'FunctionExpression',
id,
params,
body,
generator: false,
async: false,
metadata: /** @type {any} */ (null) // should not be used by codegen
};
}
/**
* @param {ESTree.Expression} test
* @param {ESTree.Statement} consequent
* @param {ESTree.Statement} [alternate]
* @returns {ESTree.IfStatement}
*/
function if_builder(test, consequent, alternate) {
return { type: 'IfStatement', test, consequent, alternate };
}
/**
* @param {string} as
* @param {string} source
* @returns {ESTree.ImportDeclaration}
*/
export function import_all(as, source) {
return {
type: 'ImportDeclaration',
source: literal(source),
specifiers: [import_namespace(as)]
};
}
/**
* @param {Array<[string, string]>} parts
* @param {string} source
* @returns {ESTree.ImportDeclaration}
*/
export function imports(parts, source) {
return {
type: 'ImportDeclaration',
source: literal(source),
specifiers: parts.map((p) => ({
type: 'ImportSpecifier',
imported: id(p[0]),
local: id(p[1])
}))
};
}
/**
* @param {ESTree.Expression | null} argument
* @returns {ESTree.ReturnStatement}
*/
function return_builder(argument = null) {
return { type: 'ReturnStatement', argument };
}
/**
* @param {string} str
* @returns {ESTree.ThrowStatement}
*/
export function throw_error(str) {
return {
type: 'ThrowStatement',
argument: new_builder('Error', literal(str))
};
}
export {
await_builder as await,
let_builder as let,
const_builder as const,
var_builder as var,
true_instance as true,
false_instance as false,
for_builder as for,
function_builder as function,
return_builder as return,
if_builder as if,
this_instance as this,
null_instance as null,
debugger_builder as debugger
};
/**
* @param {string} name
* @returns {ESTree.Expression}
*/
export function key(name) {
return regex_is_valid_identifier.test(name) ? id(name) : literal(name);
}