ember-source
Version:
A JavaScript framework for creating ambitious web applications
1,393 lines (1,389 loc) • 75.4 kB
JavaScript
import templateOnly from '../../component/template-only.js';
import { isPresentArray, getLast, mapPresentArray, exhausted, assert as debugAssert, assertPresentArray } from '../../../@glimmer/util/index.js';
import { WellKnownTagNames, WellKnownAttrNames, SexpOpcodes as opcodes } from '../../../@glimmer/wire-format/index.js';
import { n as node, a as api$2, g as generateSyntaxError, b as api$1, m as maybeLoc, K as KEYWORDS_TYPES, i as isKeyword, S as SourceSlice, c as normalize, l as loc } from '../../../shared-chunks/transform-resolutions-O6uYv8DS.js';
import { CurriedType as CurriedTypes } from '../../../@glimmer/vm/index.js';
import { setComponentTemplate } from '../../../@glimmer/manager/index.js';
import { templateFactory } from '../../../@glimmer/opcode-compiler/index.js';
import compileOptions from './compile-options.js';
(function (HeadKind) {
return HeadKind.Block = "Block", HeadKind.Call = "Call", HeadKind.Element = "Element", HeadKind.AppendPath = "AppendPath", HeadKind.AppendExpr = "AppendExpr", HeadKind.Literal = "Literal", HeadKind.Modifier = "Modifier", HeadKind.DynamicComponent = "DynamicComponent", HeadKind.Comment = "Comment", HeadKind.Splat = "Splat", HeadKind.Keyword = "Keyword", HeadKind;
})({});
(function (VariableKind) {
return VariableKind.Local = "Local", VariableKind.Free = "Free", VariableKind.Arg = "Arg", VariableKind.Block = "Block", VariableKind.This = "This", VariableKind;
})({});
(function (Builder) {
return Builder[Builder.Literal = 0] = "Literal", Builder[Builder.Comment = 1] = "Comment", Builder[Builder.Append = 2] = "Append", Builder[Builder.Modifier = 3] = "Modifier", Builder[Builder.DynamicComponent = 4] = "DynamicComponent", Builder[Builder.Get = 5] = "Get", Builder[Builder.Concat = 6] = "Concat", Builder[Builder.HasBlock = 7] = "HasBlock", Builder[Builder.HasBlockParams = 8] = "HasBlockParams", Builder;
})({});
(function (ExpressionKind) {
return ExpressionKind.Literal = "Literal", ExpressionKind.Call = "Call", ExpressionKind.GetPath = "GetPath", ExpressionKind.GetVar = "GetVar", ExpressionKind.Concat = "Concat", ExpressionKind.HasBlock = "HasBlock", ExpressionKind.HasBlockParams = "HasBlockParams", ExpressionKind;
})({});
class Template extends node("Template").fields() {}
class InElement extends node("InElement").fields() {}
class Not extends node("Not").fields() {}
class If extends node("If").fields() {}
class IfInline extends node("IfInline").fields() {}
class Each extends node("Each").fields() {}
class Let extends node("Let").fields() {}
class WithDynamicVars extends node("WithDynamicVars").fields() {}
class GetDynamicVar extends node("GetDynamicVar").fields() {}
class Log extends node("Log").fields() {}
class InvokeComponent extends node("InvokeComponent").fields() {}
class NamedBlocks extends node("NamedBlocks").fields() {}
class NamedBlock extends node("NamedBlock").fields() {}
class AppendTrustedHTML extends node("AppendTrustedHTML").fields() {}
class AppendTextNode extends node("AppendTextNode").fields() {}
class AppendComment extends node("AppendComment").fields() {}
class Component extends node("Component").fields() {}
class StaticAttr extends node("StaticAttr").fields() {}
class DynamicAttr extends node("DynamicAttr").fields() {}
class SimpleElement extends node("SimpleElement").fields() {}
class ElementParameters extends node("ElementParameters").fields() {}
class Yield extends node("Yield").fields() {}
class Debugger extends node("Debugger").fields() {}
class CallExpression extends node("CallExpression").fields() {}
class Modifier extends node("Modifier").fields() {}
class InvokeBlock extends node("InvokeBlock").fields() {}
class SplatAttr extends node("SplatAttr").fields() {}
class PathExpression extends node("PathExpression").fields() {}
class Missing extends node("Missing").fields() {}
class InterpolateExpression extends node("InterpolateExpression").fields() {}
class HasBlock extends node("HasBlock").fields() {}
class HasBlockParams extends node("HasBlockParams").fields() {}
class Curry extends node("Curry").fields() {}
class Positional extends node("Positional").fields() {}
class NamedArguments extends node("NamedArguments").fields() {}
class NamedArgument extends node("NamedArgument").fields() {}
class Args extends node("Args").fields() {}
class Tail extends node("Tail").fields() {}
class PresentList {
constructor(list) {
this.list = list;
}
toArray() {
return this.list;
}
map(callback) {
let result = mapPresentArray(this.list, callback);
return new PresentList(result);
}
filter(predicate) {
let out = [];
for (let item of this.list) predicate(item) && out.push(item);
return OptionalList(out);
}
toPresentArray() {
return this.list;
}
into({
ifPresent: ifPresent
}) {
return ifPresent(this);
}
}
class EmptyList {
list = [];
map(_callback) {
return new EmptyList();
}
filter(_predicate) {
return new EmptyList();
}
toArray() {
return this.list;
}
toPresentArray() {
return null;
}
into({
ifEmpty: ifEmpty
}) {
return ifEmpty();
}
}
// export type OptionalList<T> = PresentList<T> | EmptyList<T>;
function OptionalList(value) {
return isPresentArray(value) ? new PresentList(value) : new EmptyList();
}
class ResultImpl {
static all(...results) {
let out = [];
for (let result of results) {
if (result.isErr) return result.cast();
out.push(result.value);
}
return Ok(out);
}
}
const Result = ResultImpl;
class OkImpl extends ResultImpl {
isOk = !0;
isErr = !1;
constructor(value) {
super(), this.value = value;
}
expect(_message) {
return this.value;
}
ifOk(callback) {
return callback(this.value), this;
}
andThen(callback) {
return callback(this.value);
}
mapOk(callback) {
return Ok(callback(this.value));
}
ifErr(_callback) {
return this;
}
mapErr(_callback) {
return this;
}
}
class ErrImpl extends ResultImpl {
isOk = !1;
isErr = !0;
constructor(reason) {
super(), this.reason = reason;
}
expect(message) {
throw new Error(message || "expected an Ok, got Err");
}
andThen(_callback) {
return this.cast();
}
mapOk(_callback) {
return this.cast();
}
ifOk(_callback) {
return this;
}
mapErr(callback) {
return Err(callback(this.reason));
}
ifErr(callback) {
return callback(this.reason), this;
}
cast() {
return this;
}
}
function Ok(value) {
return new OkImpl(value);
}
function Err(reason) {
return new ErrImpl(reason);
}
class ResultArray {
constructor(items = []) {
this.items = items;
}
add(item) {
this.items.push(item);
}
toArray() {
let err = this.items.filter(item => item instanceof ErrImpl)[0];
return void 0 !== err ? err.cast() : Ok(this.items.map(item => item.value));
}
toOptionalList() {
return this.toArray().mapOk(arr => OptionalList(arr));
}
}
function convertPathToCallIfKeyword(path) {
return "Path" === path.type && "Free" === path.ref.type && path.ref.name in KEYWORDS_TYPES ? new api$1.CallExpression({
callee: path,
args: api$1.Args.empty(path.loc),
loc: path.loc
}) : path;
}
const VISIT_EXPRS = new class {
visit(node, state) {
switch (node.type) {
case "Literal":
return Ok(this.Literal(node));
case "Keyword":
return Ok(this.Keyword(node));
case "Interpolate":
return this.Interpolate(node, state);
case "Path":
return this.PathExpression(node);
case "Call":
{
let translated = CALL_KEYWORDS.translate(node, state);
return null !== translated ? translated : this.CallExpression(node, state);
}
}
}
visitList(nodes, state) {
return new ResultArray(nodes.map(e => VISIT_EXPRS.visit(e, state))).toOptionalList();
}
/**
* Normalize paths into `hir.Path` or a `hir.Expr` that corresponds to the ref.
*
* TODO since keywords don't support tails anyway, distinguish PathExpression from
* VariableReference in ASTv2.
*/
PathExpression(path) {
let ref = this.VariableReference(path.ref),
{
tail: tail
} = path;
if (isPresentArray(tail)) {
let tailLoc = tail[0].loc.extend(getLast(tail).loc);
return Ok(new PathExpression({
loc: path.loc,
head: ref,
tail: new Tail({
loc: tailLoc,
members: tail
})
}));
}
return Ok(ref);
}
VariableReference(ref) {
return ref;
}
Literal(literal) {
return literal;
}
Keyword(keyword) {
return keyword;
}
Interpolate(expr, state) {
let parts = expr.parts.map(convertPathToCallIfKeyword);
return VISIT_EXPRS.visitList(parts, state).mapOk(parts => new InterpolateExpression({
loc: expr.loc,
parts: parts
}));
}
CallExpression(expr, state) {
if ("Call" === expr.callee.type) throw new Error("unimplemented: subexpression at the head of a subexpression");
return Result.all(VISIT_EXPRS.visit(expr.callee, state), VISIT_EXPRS.Args(expr.args, state)).mapOk(([callee, args]) => new CallExpression({
loc: expr.loc,
callee: callee,
args: args
}));
}
Args({
positional: positional,
named: named,
loc: loc
}, state) {
return Result.all(this.Positional(positional, state), this.NamedArguments(named, state)).mapOk(([positional, named]) => new Args({
loc: loc,
positional: positional,
named: named
}));
}
Positional(positional, state) {
return VISIT_EXPRS.visitList(positional.exprs, state).mapOk(list => new Positional({
loc: positional.loc,
list: list
}));
}
NamedArguments(named, state) {
let pairs = named.entries.map(arg => {
let value = convertPathToCallIfKeyword(arg.value);
return VISIT_EXPRS.visit(value, state).mapOk(value => new NamedArgument({
loc: arg.loc,
key: arg.name,
value: value
}));
});
return new ResultArray(pairs).toOptionalList().mapOk(pairs => new NamedArguments({
loc: named.loc,
entries: pairs
}));
}
}();
class KeywordImpl {
types;
constructor(keyword, type, delegate) {
this.keyword = keyword, this.delegate = delegate;
let nodes = new Set();
for (let nodeType of KEYWORD_NODES[type]) nodes.add(nodeType);
this.types = nodes;
}
match(node) {
if (!this.types.has(node.type)) return !1;
let path = getCalleeExpression(node);
return null !== path && "Path" === path.type && "Free" === path.ref.type && path.ref.name === this.keyword;
}
translate(node, state) {
if (this.match(node)) {
let path = getCalleeExpression(node);
return null !== path && "Path" === path.type && path.tail.length > 0 ? Err(generateSyntaxError(`The \`${this.keyword}\` keyword was used incorrectly. It was used as \`${path.loc.asString()}\`, but it cannot be used with additional path segments. \n\nError caused by`, node.loc)) : this.delegate.assert(node, state).andThen(param => this.delegate.translate({
node: node,
state: state
}, param));
}
return null;
}
}
const KEYWORD_NODES = {
Call: ["Call"],
Block: ["InvokeBlock"],
Append: ["AppendContent"],
Modifier: ["ElementModifier"]
};
/**
* A "generic" keyword is something like `has-block`, which makes sense in the context
* of sub-expression or append
*/
function getCalleeExpression(node) {
switch (node.type) {
// This covers the inside of attributes and expressions, as well as the callee
// of call nodes
case "Path":
return node;
case "AppendContent":
return getCalleeExpression(node.value);
case "Call":
case "InvokeBlock":
case "ElementModifier":
return node.callee;
default:
return null;
}
}
class Keywords {
_keywords = [];
_type;
constructor(type) {
this._type = type;
}
kw(name, delegate) {
return this._keywords.push(function (keyword, type, delegate) {
return new KeywordImpl(keyword, type, delegate);
}(name, this._type, delegate)), this;
}
translate(node, state) {
for (let keyword of this._keywords) {
let result = keyword.translate(node, state);
if (null !== result) return result;
}
let path = getCalleeExpression(node);
if (path && "Path" === path.type && "Free" === path.ref.type && isKeyword(path.ref.name)) {
let {
name: name
} = path.ref,
usedType = this._type,
validTypes = KEYWORDS_TYPES[name];
if (!validTypes.includes(usedType)) return Err(generateSyntaxError(`The \`${name}\` keyword was used incorrectly. It was used as ${typesToReadableName[usedType]}, but its valid usages are:\n\n${function (name, types) {
return types.map(type => {
switch (type) {
case "Append":
return `- As an append statement, as in: {{${name}}}`;
case "Block":
return `- As a block statement, as in: {{#${name}}}{{/${name}}}`;
case "Call":
return `- As an expression, as in: (${name})`;
case "Modifier":
return `- As a modifier, as in: <div {{${name}}}></div>`;
default:
return exhausted(type);
}
}).join("\n\n");
}
/**
* This function builds keyword definitions for a particular type of AST node (`KeywordType`).
*
* You can build keyword definitions for:
*
* - `Expr`: A `SubExpression` or `PathExpression`
* - `Block`: A `BlockStatement`
* - A `BlockStatement` is a keyword candidate if its head is a
* `PathExpression`
* - `Append`: An `AppendStatement`
*
* A node is a keyword candidate if:
*
* - A `PathExpression` is a keyword candidate if it has no tail, and its
* head expression is a `LocalVarHead` or `FreeVarHead` whose name is
* the keyword's name.
* - A `SubExpression`, `AppendStatement`, or `BlockStatement` is a keyword
* candidate if its head is a keyword candidate.
*
* The keyword infrastructure guarantees that:
*
* - If a node is not a keyword candidate, it is never passed to any keyword's
* `assert` method.
* - If a node is not the `KeywordType` for a particular keyword, it will not
* be passed to the keyword's `assert` method.
*
* `Expr` keywords are used in expression positions and should return HIR
* expressions. `Block` and `Append` keywords are used in statement
* positions and should return HIR statements.
*
* A keyword definition has two parts:
*
* - `match`, which determines whether an AST node matches the keyword, and can
* optionally return some information extracted from the AST node.
* - `translate`, which takes a matching AST node as well as the extracted
* information and returns an appropriate HIR instruction.
*
* # Example
*
* This keyword:
*
* - turns `(hello)` into `"hello"`
* - as long as `hello` is not in scope
* - makes it an error to pass any arguments (such as `(hello world)`)
*
* ```ts
* keywords('SubExpr').kw('hello', {
* assert(node: ExprKeywordNode): Result<void> | false {
* // we don't want to transform `hello` as a `PathExpression`
* if (node.type !== 'SubExpression') {
* return false;
* }
*
* // node.head would be `LocalVarHead` if `hello` was in scope
* if (node.head.type !== 'FreeVarHead') {
* return false;
* }
*
* if (node.params.length || node.hash) {
* return Err(generateSyntaxError(`(hello) does not take any arguments`), node.loc);
* } else {
* return Ok();
* }
* },
*
* translate(node: ASTv2.SubExpression): hir.Expression {
* return ASTv2.builders.literal("hello", node.loc)
* }
* })
* ```
*
* The keyword infrastructure checks to make sure that the node is the right
* type before calling `assert`, so you only need to consider `SubExpression`
* and `PathExpression` here. It also checks to make sure that the node passed
* to `assert` has the keyword name in the right place.
*
* Note the important difference between returning `false` from `assert`,
* which just means that the node didn't match, and returning `Err`, which
* means that the node matched, but there was a keyword-specific syntax
* error.
*/(name, validTypes)}\n\nError caused by`, node.loc));
}
return null;
}
}
const typesToReadableName = {
Append: "an append statement",
Block: "a block statement",
Call: "a call expression",
Modifier: "a modifier"
};
function keywords(type) {
return new Keywords(type);
}
function toAppend({
assert: assert,
translate: translate
}) {
return {
assert: assert,
translate: ({
node: node,
state: state
}, value) => translate({
node: node,
state: state
}, value).mapOk(text => new AppendTextNode({
text: text,
loc: node.loc
}))
};
}
const CurriedTypeToReadableType = {
[CurriedTypes.Component]: "component",
[CurriedTypes.Helper]: "helper",
[CurriedTypes.Modifier]: "modifier"
};
function assertCurryKeyword(curriedType) {
return (node, state) => {
let readableType = CurriedTypeToReadableType[curriedType],
stringsAllowed = curriedType === CurriedTypes.Component,
{
args: args
} = node,
definition = args.nth(0);
if (null === definition) return Err(generateSyntaxError(`(${readableType}) requires a ${readableType} definition or identifier as its first positional parameter, did not receive any parameters.`, args.loc));
if ("Literal" === definition.type) {
if (stringsAllowed && state.isStrict) return Err(generateSyntaxError(`(${readableType}) cannot resolve string values in strict mode templates`, node.loc));
if (!stringsAllowed) return Err(generateSyntaxError(`(${readableType}) cannot resolve string values, you must pass a ${readableType} definition directly`, node.loc));
}
return args = new api$1.Args({
positional: new api$1.PositionalArguments({
exprs: args.positional.exprs.slice(1),
loc: args.positional.loc
}),
named: args.named,
loc: args.loc
}), Ok({
definition: definition,
args: args
});
};
}
function translateCurryKeyword(curriedType) {
return ({
node: node,
state: state
}, {
definition: definition,
args: args
}) => {
let definitionResult = VISIT_EXPRS.visit(definition, state),
argsResult = VISIT_EXPRS.Args(args, state);
return Result.all(definitionResult, argsResult).mapOk(([definition, args]) => new Curry({
loc: node.loc,
curriedType: curriedType,
definition: definition,
args: args
}));
};
}
function curryKeyword(curriedType) {
return {
assert: assertCurryKeyword(curriedType),
translate: translateCurryKeyword(curriedType)
};
}
const getDynamicVarKeyword = {
assert: function (node) {
let call = "AppendContent" === node.type ? node.value : node,
named = "Call" === call.type ? call.args.named : null,
positionals = "Call" === call.type ? call.args.positional : null;
if (named && !named.isEmpty()) return Err(generateSyntaxError("(-get-dynamic-vars) does not take any named arguments", node.loc));
let varName = positionals?.nth(0);
return varName ? positionals && positionals.size > 1 ? Err(generateSyntaxError("(-get-dynamic-vars) only receives one positional arg", node.loc)) : Ok(varName) : Err(generateSyntaxError("(-get-dynamic-vars) requires a var name to get", node.loc));
},
translate: function ({
node: node,
state: state
}, name) {
return VISIT_EXPRS.visit(name, state).mapOk(name => new GetDynamicVar({
name: name,
loc: node.loc
}));
}
};
function assertHasBlockKeyword(type) {
return node => {
let call = "AppendContent" === node.type ? node.value : node,
named = "Call" === call.type ? call.args.named : null,
positionals = "Call" === call.type ? call.args.positional : null;
if (named && !named.isEmpty()) return Err(generateSyntaxError(`(${type}) does not take any named arguments`, call.loc));
if (!positionals || positionals.isEmpty()) return Ok(SourceSlice.synthetic("default"));
if (1 === positionals.exprs.length) {
let positional = positionals.exprs[0];
return api$1.isLiteral(positional, "string") ? Ok(positional.toSlice()) : Err(generateSyntaxError(`(${type}) can only receive a string literal as its first argument`, call.loc));
}
return Err(generateSyntaxError(`(${type}) only takes a single positional argument`, call.loc));
};
}
function translateHasBlockKeyword(type) {
return ({
node: node,
state: {
scope: scope
}
}, target) => Ok("has-block" === type ? new HasBlock({
loc: node.loc,
target: target,
symbol: scope.allocateBlock(target.chars)
}) : new HasBlockParams({
loc: node.loc,
target: target,
symbol: scope.allocateBlock(target.chars)
}));
}
function hasBlockKeyword(type) {
return {
assert: assertHasBlockKeyword(type),
translate: translateHasBlockKeyword(type)
};
}
function assertIfUnlessInlineKeyword(type) {
return originalNode => {
let inverted = "unless" === type,
node = "AppendContent" === originalNode.type ? originalNode.value : originalNode,
named = "Call" === node.type ? node.args.named : null,
positional = "Call" === node.type ? node.args.positional : null;
if (named && !named.isEmpty()) return Err(generateSyntaxError(`(${type}) cannot receive named parameters, received ${named.entries.map(e => e.name.chars).join(", ")}`, originalNode.loc));
let condition = positional?.nth(0);
if (!positional || !condition) return Err(generateSyntaxError(`When used inline, (${type}) requires at least two parameters 1. the condition that determines the state of the (${type}), and 2. the value to return if the condition is ${inverted ? "false" : "true"}. Did not receive any parameters`, originalNode.loc));
let truthy = positional.nth(1),
falsy = positional.nth(2);
return null === truthy ? Err(generateSyntaxError(`When used inline, (${type}) requires at least two parameters 1. the condition that determines the state of the (${type}), and 2. the value to return if the condition is ${inverted ? "false" : "true"}. Received only one parameter, the condition`, originalNode.loc)) : positional.size > 3 ? Err(generateSyntaxError(`When used inline, (${type}) can receive a maximum of three positional parameters 1. the condition that determines the state of the (${type}), 2. the value to return if the condition is ${inverted ? "false" : "true"}, and 3. the value to return if the condition is ${inverted ? "true" : "false"}. Received ${positional?.size ?? 0} parameters`, originalNode.loc)) : Ok({
condition: condition,
truthy: truthy,
falsy: falsy
});
};
}
function translateIfUnlessInlineKeyword(type) {
let inverted = "unless" === type;
return ({
node: node,
state: state
}, {
condition: condition,
truthy: truthy,
falsy: falsy
}) => {
let conditionResult = VISIT_EXPRS.visit(condition, state),
truthyResult = VISIT_EXPRS.visit(truthy, state),
falsyResult = falsy ? VISIT_EXPRS.visit(falsy, state) : Ok(null);
return Result.all(conditionResult, truthyResult, falsyResult).mapOk(([condition, truthy, falsy]) => (inverted && (condition = new Not({
value: condition,
loc: node.loc
})), new IfInline({
loc: node.loc,
condition: condition,
truthy: truthy,
falsy: falsy
})));
};
}
function ifUnlessInlineKeyword(type) {
return {
assert: assertIfUnlessInlineKeyword(type),
translate: translateIfUnlessInlineKeyword(type)
};
}
const logKeyword = {
assert: function (node) {
let {
args: {
named: named,
positional: positional
}
} = node;
return named && !named.isEmpty() ? Err(generateSyntaxError("(log) does not take any named arguments", node.loc)) : Ok(positional);
},
translate: function ({
node: node,
state: state
}, positional) {
return VISIT_EXPRS.Positional(positional, state).mapOk(positional => new Log({
positional: positional,
loc: node.loc
}));
}
},
APPEND_KEYWORDS = keywords("Append").kw("has-block", toAppend(hasBlockKeyword("has-block"))).kw("has-block-params", toAppend(hasBlockKeyword("has-block-params"))).kw("-get-dynamic-var", toAppend(getDynamicVarKeyword)).kw("log", toAppend(logKeyword)).kw("if", toAppend(ifUnlessInlineKeyword("if"))).kw("unless", toAppend(ifUnlessInlineKeyword("unless"))).kw("yield", {
assert(node) {
let {
args: args
} = node;
if (args.named.isEmpty()) return Ok({
target: api$2.SourceSpan.synthetic("default").toSlice(),
positional: args.positional
});
{
let target = args.named.get("to");
return args.named.size > 1 || null === target ? Err(generateSyntaxError("yield only takes a single named argument: 'to'", args.named.loc)) : api$1.isLiteral(target, "string") ? Ok({
target: target.toSlice(),
positional: args.positional
}) : Err(generateSyntaxError("you can only yield to a literal string value", target.loc));
}
},
translate: ({
node: node,
state: state
}, {
target: target,
positional: positional
}) => VISIT_EXPRS.Positional(positional, state).mapOk(positional => new Yield({
loc: node.loc,
target: target,
to: state.scope.allocateBlock(target.chars),
positional: positional
}))
}).kw("debugger", {
assert(node) {
let {
args: args
} = node,
{
positional: positional
} = args;
return args.isEmpty() ? Ok(void 0) : positional.isEmpty() ? Err(generateSyntaxError("debugger does not take any named arguments", node.loc)) : Err(generateSyntaxError("debugger does not take any positional arguments", node.loc));
},
translate: ({
node: node,
state: {
scope: scope
}
}) => (scope.setHasDebugger(), Ok(new Debugger({
loc: node.loc,
scope: scope
})))
}).kw("component", {
assert: assertCurryKeyword(CurriedTypes.Component),
translate({
node: node,
state: state
}, {
definition: definition,
args: args
}) {
let definitionResult = VISIT_EXPRS.visit(definition, state),
argsResult = VISIT_EXPRS.Args(args, state);
return Result.all(definitionResult, argsResult).mapOk(([definition, args]) => new InvokeComponent({
loc: node.loc,
definition: definition,
args: args,
blocks: null
}));
}
}).kw("helper", {
assert: assertCurryKeyword(CurriedTypes.Helper),
translate({
node: node,
state: state
}, {
definition: definition,
args: args
}) {
let definitionResult = VISIT_EXPRS.visit(definition, state),
argsResult = VISIT_EXPRS.Args(args, state);
return Result.all(definitionResult, argsResult).mapOk(([definition, args]) => {
let text = new CallExpression({
callee: definition,
args: args,
loc: node.loc
});
return new AppendTextNode({
loc: node.loc,
text: text
});
});
}
}),
BLOCK_KEYWORDS = keywords("Block").kw("in-element", {
assert(node) {
let {
args: args
} = node,
guid = args.get("guid");
if (guid) return Err(generateSyntaxError("Cannot pass `guid` to `{{#in-element}}`", guid.loc));
let insertBefore = args.get("insertBefore"),
destination = args.nth(0);
return null === destination ? Err(generateSyntaxError("{{#in-element}} requires a target element as its first positional parameter", args.loc)) : Ok({
insertBefore: insertBefore,
destination: destination
});
// TODO Better syntax checks
},
translate({
node: node,
state: state
}, {
insertBefore: insertBefore,
destination: destination
}) {
let named = node.blocks.get("default"),
body = VISIT_STMTS.NamedBlock(named, state),
destinationResult = VISIT_EXPRS.visit(destination, state);
return Result.all(body, destinationResult).andThen(([body, destination]) => insertBefore ? VISIT_EXPRS.visit(insertBefore, state).mapOk(insertBefore => ({
body: body,
destination: destination,
insertBefore: insertBefore
})) : Ok({
body: body,
destination: destination,
insertBefore: new Missing({
loc: node.callee.loc.collapse("end")
})
})).mapOk(({
body: body,
destination: destination,
insertBefore: insertBefore
}) => new InElement({
loc: node.loc,
block: body,
insertBefore: insertBefore,
guid: state.generateUniqueCursor(),
destination: destination
}));
}
}).kw("if", {
assert(node) {
let {
args: args
} = node;
if (!args.named.isEmpty()) return Err(generateSyntaxError(`{{#if}} cannot receive named parameters, received ${args.named.entries.map(e => e.name.chars).join(", ")}`, node.loc));
if (args.positional.size > 1) return Err(generateSyntaxError(`{{#if}} can only receive one positional parameter in block form, the conditional value. Received ${args.positional.size} parameters`, node.loc));
let condition = args.nth(0);
return null === condition ? Err(generateSyntaxError("{{#if}} requires a condition as its first positional parameter, did not receive any parameters", node.loc)) : Ok({
condition: condition
});
},
translate({
node: node,
state: state
}, {
condition: condition
}) {
let block = node.blocks.get("default"),
inverse = node.blocks.get("else"),
conditionResult = VISIT_EXPRS.visit(condition, state),
blockResult = VISIT_STMTS.NamedBlock(block, state),
inverseResult = inverse ? VISIT_STMTS.NamedBlock(inverse, state) : Ok(null);
return Result.all(conditionResult, blockResult, inverseResult).mapOk(([condition, block, inverse]) => new If({
loc: node.loc,
condition: condition,
block: block,
inverse: inverse
}));
}
}).kw("unless", {
assert(node) {
let {
args: args
} = node;
if (!args.named.isEmpty()) return Err(generateSyntaxError(`{{#unless}} cannot receive named parameters, received ${args.named.entries.map(e => e.name.chars).join(", ")}`, node.loc));
if (args.positional.size > 1) return Err(generateSyntaxError(`{{#unless}} can only receive one positional parameter in block form, the conditional value. Received ${args.positional.size} parameters`, node.loc));
let condition = args.nth(0);
return null === condition ? Err(generateSyntaxError("{{#unless}} requires a condition as its first positional parameter, did not receive any parameters", node.loc)) : Ok({
condition: condition
});
},
translate({
node: node,
state: state
}, {
condition: condition
}) {
let block = node.blocks.get("default"),
inverse = node.blocks.get("else"),
conditionResult = VISIT_EXPRS.visit(condition, state),
blockResult = VISIT_STMTS.NamedBlock(block, state),
inverseResult = inverse ? VISIT_STMTS.NamedBlock(inverse, state) : Ok(null);
return Result.all(conditionResult, blockResult, inverseResult).mapOk(([condition, block, inverse]) => new If({
loc: node.loc,
condition: new Not({
value: condition,
loc: node.loc
}),
block: block,
inverse: inverse
}));
}
}).kw("each", {
assert(node) {
let {
args: args
} = node;
if (!args.named.entries.every(e => "key" === e.name.chars)) return Err(generateSyntaxError(`{{#each}} can only receive the 'key' named parameter, received ${args.named.entries.filter(e => "key" !== e.name.chars).map(e => e.name.chars).join(", ")}`, args.named.loc));
if (args.positional.size > 1) return Err(generateSyntaxError(`{{#each}} can only receive one positional parameter, the collection being iterated. Received ${args.positional.size} parameters`, args.positional.loc));
let value = args.nth(0),
key = args.get("key");
return null === value ? Err(generateSyntaxError("{{#each}} requires an iterable value to be passed as its first positional parameter, did not receive any parameters", args.loc)) : Ok({
value: value,
key: key
});
},
translate({
node: node,
state: state
}, {
value: value,
key: key
}) {
let block = node.blocks.get("default"),
inverse = node.blocks.get("else"),
valueResult = VISIT_EXPRS.visit(value, state),
keyResult = key ? VISIT_EXPRS.visit(key, state) : Ok(null),
blockResult = VISIT_STMTS.NamedBlock(block, state),
inverseResult = inverse ? VISIT_STMTS.NamedBlock(inverse, state) : Ok(null);
return Result.all(valueResult, keyResult, blockResult, inverseResult).mapOk(([value, key, block, inverse]) => new Each({
loc: node.loc,
value: value,
key: key,
block: block,
inverse: inverse
}));
}
}).kw("let", {
assert(node) {
let {
args: args
} = node;
return args.named.isEmpty() ? 0 === args.positional.size ? Err(generateSyntaxError("{{#let}} requires at least one value as its first positional parameter, did not receive any parameters", args.positional.loc)) : node.blocks.get("else") ? Err(generateSyntaxError("{{#let}} cannot receive an {{else}} block", args.positional.loc)) : Ok({
positional: args.positional
}) : Err(generateSyntaxError(`{{#let}} cannot receive named parameters, received ${args.named.entries.map(e => e.name.chars).join(", ")}`, args.named.loc));
},
translate({
node: node,
state: state
}, {
positional: positional
}) {
let block = node.blocks.get("default"),
positionalResult = VISIT_EXPRS.Positional(positional, state),
blockResult = VISIT_STMTS.NamedBlock(block, state);
return Result.all(positionalResult, blockResult).mapOk(([positional, block]) => new Let({
loc: node.loc,
positional: positional,
block: block
}));
}
}).kw("-with-dynamic-vars", {
assert: node => Ok({
named: node.args.named
}),
translate({
node: node,
state: state
}, {
named: named
}) {
let block = node.blocks.get("default"),
namedResult = VISIT_EXPRS.NamedArguments(named, state),
blockResult = VISIT_STMTS.NamedBlock(block, state);
return Result.all(namedResult, blockResult).mapOk(([named, block]) => new WithDynamicVars({
loc: node.loc,
named: named,
block: block
}));
}
}).kw("component", {
assert: assertCurryKeyword(CurriedTypes.Component),
translate({
node: node,
state: state
}, {
definition: definition,
args: args
}) {
let definitionResult = VISIT_EXPRS.visit(definition, state),
argsResult = VISIT_EXPRS.Args(args, state),
blocksResult = VISIT_STMTS.NamedBlocks(node.blocks, state);
return Result.all(definitionResult, argsResult, blocksResult).mapOk(([definition, args, blocks]) => new InvokeComponent({
loc: node.loc,
definition: definition,
args: args,
blocks: blocks
}));
}
}),
CALL_KEYWORDS = keywords("Call").kw("has-block", hasBlockKeyword("has-block")).kw("has-block-params", hasBlockKeyword("has-block-params")).kw("-get-dynamic-var", getDynamicVarKeyword).kw("log", logKeyword).kw("if", ifUnlessInlineKeyword("if")).kw("unless", ifUnlessInlineKeyword("unless")).kw("component", curryKeyword(CurriedTypes.Component)).kw("helper", curryKeyword(CurriedTypes.Helper)).kw("modifier", curryKeyword(CurriedTypes.Modifier)),
MODIFIER_KEYWORDS = keywords("Modifier"),
XLINK = "http://www.w3.org/1999/xlink",
XML = "http://www.w3.org/XML/1998/namespace",
XMLNS = "http://www.w3.org/2000/xmlns/",
WHITELIST = {
"xlink:actuate": XLINK,
"xlink:arcrole": XLINK,
"xlink:href": XLINK,
"xlink:role": XLINK,
"xlink:show": XLINK,
"xlink:title": XLINK,
"xlink:type": XLINK,
"xml:base": XML,
"xml:lang": XML,
"xml:space": XML,
xmlns: XMLNS,
"xmlns:xlink": XMLNS
},
DEFLATE_TAG_TABLE = {
div: WellKnownTagNames.div,
span: WellKnownTagNames.span,
p: WellKnownTagNames.p,
a: WellKnownTagNames.a
};
const DEFLATE_ATTR_TABLE = {
class: WellKnownAttrNames.class,
id: WellKnownAttrNames.id,
value: WellKnownAttrNames.value,
name: WellKnownAttrNames.name,
type: WellKnownAttrNames.type,
style: WellKnownAttrNames.style,
href: WellKnownAttrNames.href
};
function deflateAttrName(attrName) {
return DEFLATE_ATTR_TABLE[attrName] ?? attrName;
}
class ClassifiedElement {
delegate;
constructor(element, delegate, state) {
this.element = element, this.state = state, this.delegate = delegate;
}
toStatement() {
return this.prepare().andThen(prepared => this.delegate.toStatement(this, prepared));
}
attr(attr) {
let name = attr.name,
rawValue = attr.value,
namespace = (attrName = name.chars, WHITELIST[attrName] || void 0);
var attrName;
return api$1.isLiteral(rawValue, "string") ? Ok(new StaticAttr({
loc: attr.loc,
name: name,
value: rawValue.toSlice(),
namespace: namespace,
kind: {
component: this.delegate.dynamicFeatures
}
})) : VISIT_EXPRS.visit(convertPathToCallIfKeyword(rawValue), this.state).mapOk(value => {
let isTrusting = attr.trusting;
return new DynamicAttr({
loc: attr.loc,
name: name,
value: value,
namespace: namespace,
kind: {
trusting: isTrusting,
component: this.delegate.dynamicFeatures
}
});
});
}
modifier(modifier) {
let translated = MODIFIER_KEYWORDS.translate(modifier, this.state);
if (null !== translated) return translated;
let head = VISIT_EXPRS.visit(modifier.callee, this.state),
args = VISIT_EXPRS.Args(modifier.args, this.state);
return Result.all(head, args).mapOk(([head, args]) => new Modifier({
loc: modifier.loc,
callee: head,
args: args
}));
}
attrs() {
let attrs = new ResultArray(),
args = new ResultArray(),
typeAttr = null,
simple = 0 === this.element.attrs.filter(attr => "SplatAttr" === attr.type).length;
for (let attr of this.element.attrs) "SplatAttr" === attr.type ? attrs.add(Ok(new SplatAttr({
loc: attr.loc,
symbol: this.state.scope.allocateBlock("attrs")
}))) : "type" === attr.name.chars && simple ? typeAttr = attr : attrs.add(this.attr(attr));
for (let arg of this.element.componentArgs) args.add(this.delegate.arg(arg, this));
return typeAttr && attrs.add(this.attr(typeAttr)), Result.all(args.toArray(), attrs.toArray()).mapOk(([args, attrs]) => ({
attrs: attrs,
args: new NamedArguments({
loc: maybeLoc(args, api$2.SourceSpan.NON_EXISTENT),
entries: OptionalList(args)
})
}));
}
prepare() {
let attrs = this.attrs(),
modifiers = new ResultArray(this.element.modifiers.map(m => this.modifier(m))).toArray();
return Result.all(attrs, modifiers).mapOk(([result, modifiers]) => {
let {
attrs: attrs,
args: args
} = result,
elementParams = [...attrs, ...modifiers];
return {
args: args,
params: new ElementParameters({
loc: maybeLoc(elementParams, api$2.SourceSpan.NON_EXISTENT),
body: OptionalList(elementParams)
})
};
});
}
}
class ClassifiedComponent {
dynamicFeatures = !0;
constructor(tag, element) {
this.tag = tag, this.element = element;
}
arg(attr, {
state: state
}) {
let name = attr.name;
return VISIT_EXPRS.visit(convertPathToCallIfKeyword(attr.value), state).mapOk(value => new NamedArgument({
loc: attr.loc,
key: name,
value: value
}));
}
toStatement(component, {
args: args,
params: params
}) {
let {
element: element,
state: state
} = component;
return this.blocks(state).mapOk(blocks => new Component({
loc: element.loc,
tag: this.tag,
params: params,
args: args,
blocks: blocks
}));
}
blocks(state) {
return VISIT_STMTS.NamedBlocks(this.element.blocks, state);
}
}
class ClassifiedSimpleElement {
constructor(tag, element, dynamicFeatures) {
this.tag = tag, this.element = element, this.dynamicFeatures = dynamicFeatures;
}
isComponent = !1;
arg(attr) {
return Err(generateSyntaxError(`${attr.name.chars} is not a valid attribute name. @arguments are only allowed on components, but the tag for this element (\`${this.tag.chars}\`) is a regular, non-component HTML element.`, attr.loc));
}
toStatement(classified, {
params: params
}) {
let {
state: state,
element: element
} = classified;
return VISIT_STMTS.visitList(this.element.body, state).mapOk(body => new SimpleElement({
loc: element.loc,
tag: this.tag,
params: params,
body: body.toArray(),
dynamicFeatures: this.dynamicFeatures
}));
}
}
const VISIT_STMTS = new class {
visitList(nodes, state) {
return new ResultArray(nodes.map(e => VISIT_STMTS.visit(e, state))).toOptionalList().mapOk(list => list.filter(s => null !== s));
}
visit(node, state) {
switch (node.type) {
case "GlimmerComment":
return Ok(null);
case "AppendContent":
return this.AppendContent(node, state);
case "HtmlText":
return Ok(this.TextNode(node));
case "HtmlComment":
return Ok(this.HtmlComment(node));
case "InvokeBlock":
return this.InvokeBlock(node, state);
case "InvokeComponent":
return this.Component(node, state);
case "SimpleElement":
return this.SimpleElement(node, state);
}
}
InvokeBlock(node, state) {
let translated = BLOCK_KEYWORDS.translate(node, state);
if (null !== translated) return translated;
let head = VISIT_EXPRS.visit(node.callee, state),
args = VISIT_EXPRS.Args(node.args, state);
return Result.all(head, args).andThen(([head, args]) => this.NamedBlocks(node.blocks, state).mapOk(blocks => new InvokeBlock({
loc: node.loc,
head: head,
args: args,
blocks: blocks
})));
}
NamedBlocks(blocks, state) {
return new ResultArray(blocks.blocks.map(b => this.NamedBlock(b, state))).toArray().mapOk(list => new NamedBlocks({
loc: blocks.loc,
blocks: OptionalList(list)
}));
}
NamedBlock(named, state) {
return state.visitBlock(named.block).mapOk(body => new NamedBlock({
loc: named.loc,
name: named.name,
body: body.toArray(),
scope: named.block.scope
}));
}
SimpleElement(element, state) {
return new ClassifiedElement(element, new ClassifiedSimpleElement(element.tag, element, function ({
attrs: attrs,
modifiers: modifiers
}) {
// ElementModifier needs the special ComponentOperations
return modifiers.length > 0 || !!attrs.filter(attr => "SplatAttr" === attr.type)[0];
// Splattributes need the special ComponentOperations to merge into
}(element)), state).toStatement();
}
Component(component, state) {
return VISIT_EXPRS.visit(component.callee, state).andThen(callee => new ClassifiedElement(component, new ClassifiedComponent(callee, component), state).toStatement());
}
AppendContent(append, state) {
let translated = APPEND_KEYWORDS.translate(append, state);
return null !== translated ? translated : VISIT_EXPRS.visit(append.value, state).mapOk(value => append.trusting ? new AppendTrustedHTML({
loc: append.loc,
html: value
}) : new AppendTextNode({
loc: append.loc,
text: value
}));
}
TextNode(text) {
return new AppendTextNode({
loc: text.loc,
text: new api$1.LiteralExpression({
loc: text.loc,
value: text.chars
})
});
}
HtmlComment(comment) {
return new AppendComment({
loc: comment.loc,
value: comment.text
});
}
}();
/**
* This is the mutable state for this compiler pass.
*/
class NormalizationState {
_currentScope;
_cursorCount = 0;
constructor(block, isStrict) {
this.isStrict = isStrict, this._currentScope = block;
}
generateUniqueCursor() {
return `%cursor:${this._cursorCount++}%`;
}
get scope() {
return this._currentScope;
}
visitBlock(block) {
let oldBlock = this._currentScope;
this._currentScope = block.scope;
try {
return VISIT_STMTS.visitList(block.body, this);
} finally {
this._currentScope = oldBlock;
}
}
}
var ResolutionType = function (ResolutionType) {
return ResolutionType.Value = "value", ResolutionType.Component = "component", ResolutionType.Helper = "helper", ResolutionType.Modifier = "modifier", ResolutionType.ComponentOrHelper = "component or helper", ResolutionType;
}(ResolutionType || {});
class StrictModeValidationPass {
// This is done at the end of all the keyword normalizations
// At this point any free variables that isn't a valid keyword
// in its context should be considered a syntax error. We
// probably had various opportunities to do this inline in the
// earlier passes, but this aims to produce a better syntax
// error as we don't always have the right loc-context to do
// so in the other spots.
static validate(template) {
return new this(template).validate();
}
constructor(template) {
this.template = template;
}
validate() {
return this.Statements(this.template.body).mapOk(() => this.template);
}
Statements(statements) {
let result = Ok(null);
for (let statement of statements) result = result.andThen(() => this.Statement(statement));
return result;
}
NamedBlocks({
blocks: blocks
}) {
let result = Ok(null);
for (let block of blocks.toArray()) result = result.andThen(() => this.NamedBlock(block));
return result;
}
NamedBlock(block) {
return this.Statements(block.body);
}
Statement(statement) {
switch (statement.type) {
case "InElement":
return this.InElement(statement);
case "Debugger":
case "AppendComment":
return Ok(null);
case "Yield":
return this.Yield(statement);
case "AppendTrustedHTML":
return this.AppendTrustedHTML(statement);
case "AppendTextNode":
return this.AppendTextNode(statement);
case "Component":
return this.Component(statement);
case "SimpleElement":
return this.SimpleElement(statement);
case "InvokeBlock":
return this.InvokeBlock(statement);
case "If":
return this.If(statement);
case "Each":
return this.Each(statement);
case "Let":
return this.Let(statement);
case "WithDynamicVars":
return this.WithDynamicVars(statement);
case "InvokeComponent":
return this.InvokeComponent(statement);
}
}
Expressions(expressions) {
let result = Ok(null);
for (let expression of expressions) result = result.andThen(() => this.Expression(expression));
return result;
}
Expression(expression, span = expression, resolution) {
switch (expression.type) {
case "Literal":
case "Keyword":
case "Missing":
case "This":
case "Arg":
case "Local":
case "HasBlock":
case "HasBlockParams":
case "GetDynamicVar":
return Ok(null);
case "PathExpression":
return this.Expression(expression.head, span, resolution);
case "Free":
return this.errorFor(expression.name, span, resolution);
case "InterpolateExpression":
return this.InterpolateExpression(expression, span, resolution);
case "CallExpression":
return this.CallExpression(expression, span, resolution ?? ResolutionType.Helper);
case "Not":
return this.Expression(expression.value, span, resolution);
case "IfInline":
return this.IfInline(expression);
case "Curry":
return this.Curry(expression);
case "Log":
return this.Log(expression);
}
}
Args(args) {
return this.Positional(args.positional).andThen(() => this.NamedArguments(args.named));
}
Positional(positional, span) {
let result = Ok(null),
expressions = positional.list.toArray();
// For cases like {{yield foo}}, when there is only a single argument, it
// makes for a slightly better error to report that entire span. However,
// when there are more than one, we need to be specific.
return result = 1 === expressions.length ? this.Expression(expressions[0], span) : this.Expressions(expressions), result;
}
NamedArguments({
entries: entries
}) {
let result = Ok(null);
for (let arg of entries.toArray()) result = result.andThen(() => this.NamedArgument(arg));
return result;
}
NamedArgument(arg) {
return "CallExpression" === arg.value.type ? this.Expression(ar