From 619559d39a183db2d1fdfa7b84c1be4340e04479 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Wed, 23 Oct 2024 23:09:27 -0700 Subject: [PATCH] Implement hash and array literal syntax --- .eslintignore | 1 + .eslintrc.js => .eslintrc.cjs | 0 lib/helpers.js | 28 +-- lib/index.js | 12 +- lib/parse.js | 47 ++++- lib/printer.js | 126 +++++++----- lib/types.d.ts | 235 ---------------------- lib/visitor.js | 36 ++-- lib/whitespace-control.js | 145 +++++++------- package.json | 29 ++- pnpm-lock.yaml | 278 ++++++++++----------------- spec/{.eslintrc.js => .eslintrc.cjs} | 2 +- spec/ast.js | 122 ++++++------ spec/parser.js | 230 +++++++++++++--------- spec/utils.js | 81 ++++++-- spec/visitor.js | 74 ++++--- src/handlebars.l | 25 ++- src/handlebars.yy | 18 +- tsconfig.json | 18 +- 19 files changed, 692 insertions(+), 815 deletions(-) rename .eslintrc.js => .eslintrc.cjs (100%) delete mode 100644 lib/types.d.ts rename spec/{.eslintrc.js => .eslintrc.cjs} (61%) diff --git a/.eslintignore b/.eslintignore index 6ca4a43..801723d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ /dist/ /lib/parser.js /src/ +*.d.ts diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/lib/helpers.js b/lib/helpers.js index de2806d..ef567e2 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,4 +1,4 @@ -import Exception from './exception'; +import Exception from './exception.js'; function validateClose(open, close) { close = close.path ? close.path.original : close; @@ -17,11 +17,11 @@ export function SourceLocation(source, locInfo) { this.source = source; this.start = { line: locInfo.first_line, - column: locInfo.first_column + column: locInfo.first_column, }; this.end = { line: locInfo.last_line, - column: locInfo.last_column + column: locInfo.last_column, }; } @@ -36,7 +36,7 @@ export function id(token) { export function stripFlags(open, close) { return { open: open.charAt(2) === '~', - close: close.charAt(close.length - 3) === '~' + close: close.charAt(close.length - 3) === '~', }; } @@ -93,7 +93,7 @@ export function preparePath(data, sexpr, parts, loc) { tail, parts: head ? [head, ...tail] : tail, original, - loc + loc, }; } @@ -110,7 +110,7 @@ export function prepareMustache(path, params, hash, open, strip, locInfo) { hash, escaped, strip, - loc: this.locInfo(locInfo) + loc: this.locInfo(locInfo), }; } @@ -122,7 +122,7 @@ export function prepareRawBlock(openRawBlock, contents, close, locInfo) { type: 'Program', body: contents, strip: {}, - loc: locInfo + loc: locInfo, }; return { @@ -134,7 +134,7 @@ export function prepareRawBlock(openRawBlock, contents, close, locInfo) { openStrip: {}, inverseStrip: {}, closeStrip: {}, - loc: locInfo + loc: locInfo, }; } @@ -188,7 +188,7 @@ export function prepareBlock( openStrip: openBlock.strip, inverseStrip, closeStrip: close && close.strip, - loc: this.locInfo(locInfo) + loc: this.locInfo(locInfo), }; } @@ -203,12 +203,12 @@ export function prepareProgram(statements, loc) { source: firstLoc.source, start: { line: firstLoc.start.line, - column: firstLoc.start.column + column: firstLoc.start.column, }, end: { line: lastLoc.end.line, - column: lastLoc.end.column - } + column: lastLoc.end.column, + }, }; } } @@ -217,7 +217,7 @@ export function prepareProgram(statements, loc) { type: 'Program', body: statements, strip: {}, - loc: loc + loc: loc, }; } @@ -232,6 +232,6 @@ export function preparePartialBlock(open, program, close, locInfo) { program, openStrip: open.strip, closeStrip: close && close.strip, - loc: this.locInfo(locInfo) + loc: this.locInfo(locInfo), }; } diff --git a/lib/index.js b/lib/index.js index a9813bb..cf22bff 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,6 +1,6 @@ -export { default as Visitor } from './visitor'; -export { default as WhitespaceControl } from './whitespace-control'; -export { default as parser } from './parser'; -export { default as Exception } from './exception'; -export { print, PrintVisitor } from './printer'; -export { parse, parseWithoutProcessing } from './parse'; +export { default as Visitor } from './visitor.js'; +export { default as WhitespaceControl } from './whitespace-control.js'; +export { default as parser } from './parser.js'; +export { default as Exception } from './exception.js'; +export { print, PrintVisitor } from './printer.js'; +export { parse, parseWithoutProcessing } from './parse.js'; diff --git a/lib/parse.js b/lib/parse.js index ce29867..9927b5f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1,6 +1,6 @@ -import parser from './parser'; -import WhitespaceControl from './whitespace-control'; -import * as Helpers from './helpers'; +import parser from './parser.js'; +import WhitespaceControl from './whitespace-control.js'; +import * as Helpers from './helpers.js'; let baseHelpers = {}; @@ -19,13 +19,52 @@ export function parseWithoutProcessing(input, options) { parser.yy = baseHelpers; // Altering the shared object here, but this is ok as parser is a sync operation - parser.yy.locInfo = function(locInfo) { + parser.yy.locInfo = function (locInfo) { return new Helpers.SourceLocation(options && options.srcName, locInfo); }; + let squareSyntax; + + if (typeof options?.syntax?.square === 'function') { + squareSyntax = options.syntax.square; + } else if (options?.syntax?.square === 'node') { + squareSyntax = arrayLiteralNode; + } else { + squareSyntax = 'string'; + } + + let hashSyntax; + + if (typeof options?.syntax?.hash === 'function') { + hashSyntax = options.syntax.hash; + } else { + hashSyntax = hashLiteralNode; + } + + parser.yy.syntax = { + square: squareSyntax, + hash: hashSyntax, + }; + return parser.parse(input); } +function arrayLiteralNode(array, loc) { + return { + type: 'ArrayLiteral', + items: array, + loc, + }; +} + +function hashLiteralNode(hash, loc) { + return { + type: 'HashLiteral', + pairs: hash.pairs, + loc, + }; +} + export function parse(input, options) { let ast = parseWithoutProcessing(input, options); let strip = new WhitespaceControl(options); diff --git a/lib/printer.js b/lib/printer.js index cca457e..739acf2 100644 --- a/lib/printer.js +++ b/lib/printer.js @@ -1,5 +1,5 @@ /* eslint-disable new-cap */ -import Visitor from './visitor'; +import Visitor from './visitor.js'; export function print(ast) { return new PrintVisitor().accept(ast); @@ -11,7 +11,7 @@ export function PrintVisitor() { PrintVisitor.prototype = new Visitor(); -PrintVisitor.prototype.pad = function(string) { +PrintVisitor.prototype.pad = function (string) { let out = ''; for (let i = 0, l = this.padding; i < l; i++) { @@ -22,7 +22,7 @@ PrintVisitor.prototype.pad = function(string) { return out; }; -PrintVisitor.prototype.Program = function(program) { +PrintVisitor.prototype.Program = function (program) { let out = '', body = program.body, i, @@ -46,47 +46,50 @@ PrintVisitor.prototype.Program = function(program) { return out; }; -PrintVisitor.prototype.MustacheStatement = function(mustache) { - return this.pad('{{ ' + this.SubExpression(mustache) + ' }}'); +PrintVisitor.prototype.MustacheStatement = function (mustache) { + if (mustache.params.length > 0 || mustache.hash) { + return this.pad('{{ ' + this.callBody(mustache) + ' }}'); + } else { + return this.pad('{{ ' + this.accept(mustache.path) + ' }}'); + } }; -PrintVisitor.prototype.Decorator = function(mustache) { - return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}'); +PrintVisitor.prototype.Decorator = function (mustache) { + return this.pad('{{ DIRECTIVE ' + this.callBody(mustache) + ' }}'); }; -PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function( - block -) { - let out = ''; +PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = + function (block) { + let out = ''; - out += this.pad( - (block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:' - ); - this.padding++; - out += this.pad(this.SubExpression(block)); - if (block.program) { - out += this.pad('PROGRAM:'); + out += this.pad( + (block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:' + ); this.padding++; - out += this.accept(block.program); - this.padding--; - } - if (block.inverse) { + out += this.pad(this.callBody(block)); if (block.program) { + out += this.pad('PROGRAM:'); this.padding++; + out += this.accept(block.program); + this.padding--; } - out += this.pad('{{^}}'); - this.padding++; - out += this.accept(block.inverse); - this.padding--; - if (block.program) { + if (block.inverse) { + if (block.program) { + this.padding++; + } + out += this.pad('{{^}}'); + this.padding++; + out += this.accept(block.inverse); this.padding--; + if (block.program) { + this.padding--; + } } - } - this.padding--; + this.padding--; - return out; -}; + return out; + }; -PrintVisitor.prototype.PartialStatement = function(partial) { +PrintVisitor.prototype.PartialStatement = function (partial) { let content = 'PARTIAL:' + partial.name.original; if (partial.params[0]) { content += ' ' + this.accept(partial.params[0]); @@ -96,7 +99,7 @@ PrintVisitor.prototype.PartialStatement = function(partial) { } return this.pad('{{> ' + content + ' }}'); }; -PrintVisitor.prototype.PartialBlockStatement = function(partial) { +PrintVisitor.prototype.PartialBlockStatement = function (partial) { let content = 'PARTIAL BLOCK:' + partial.name.original; if (partial.params[0]) { content += ' ' + this.accept(partial.params[0]); @@ -113,16 +116,20 @@ PrintVisitor.prototype.PartialBlockStatement = function(partial) { return this.pad('{{> ' + content + ' }}'); }; -PrintVisitor.prototype.ContentStatement = function(content) { +PrintVisitor.prototype.ContentStatement = function (content) { return this.pad("CONTENT[ '" + content.value + "' ]"); }; -PrintVisitor.prototype.CommentStatement = function(comment) { +PrintVisitor.prototype.CommentStatement = function (comment) { return this.pad("{{! '" + comment.value + "' }}"); }; -PrintVisitor.prototype.SubExpression = function(sexpr) { - let params = sexpr.params, +PrintVisitor.prototype.SubExpression = function (sexpr) { + return `(${this.callBody(sexpr)})`; +}; + +PrintVisitor.prototype.callBody = function (callExpr) { + let params = callExpr.params, paramStrings = [], hash; @@ -130,17 +137,19 @@ PrintVisitor.prototype.SubExpression = function(sexpr) { paramStrings.push(this.accept(params[i])); } - params = '[' + paramStrings.join(', ') + ']'; + params = + paramStrings.length === 0 ? '' : ' [' + paramStrings.join(', ') + ']'; - hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : ''; + hash = callExpr.hash ? ' ' + this.accept(callExpr.hash) : ''; - return this.accept(sexpr.path) + ' ' + params + hash; + return this.accept(callExpr.path) + params + hash; }; -PrintVisitor.prototype.PathExpression = function(id) { - let head = typeof id.head === 'string' ? id.head : `[${this.accept(id.head)}]`; +PrintVisitor.prototype.PathExpression = function (id) { + let head = + typeof id.head === 'string' ? id.head : `[${this.accept(id.head)}]`; let path = [head, ...id.tail].join('/'); - return 'p%' + prefix(id) + path; + return 'p%' + prefix(id) + path; }; function prefix(path) { @@ -153,37 +162,50 @@ function prefix(path) { } } -PrintVisitor.prototype.StringLiteral = function(string) { +PrintVisitor.prototype.StringLiteral = function (string) { return '"' + string.value + '"'; }; -PrintVisitor.prototype.NumberLiteral = function(number) { +PrintVisitor.prototype.NumberLiteral = function (number) { return 'n%' + number.value; }; -PrintVisitor.prototype.BooleanLiteral = function(bool) { +PrintVisitor.prototype.BooleanLiteral = function (bool) { return 'b%' + bool.value; }; -PrintVisitor.prototype.UndefinedLiteral = function() { +PrintVisitor.prototype.UndefinedLiteral = function () { return 'UNDEFINED'; }; -PrintVisitor.prototype.NullLiteral = function() { +PrintVisitor.prototype.NullLiteral = function () { return 'NULL'; }; -PrintVisitor.prototype.Hash = function(hash) { +PrintVisitor.prototype.ArrayLiteral = function (array) { + return `Array[${array.items.map((item) => this.accept(item)).join(', ')}]`; +}; + +PrintVisitor.prototype.HashLiteral = function (hash) { + return `Hash{${this.hashPairs(hash)}}`; +}; + +PrintVisitor.prototype.Hash = function (hash) { + return `HASH{${this.hashPairs(hash)}}`; +}; + +PrintVisitor.prototype.hashPairs = function (hash) { let pairs = hash.pairs, joinedPairs = []; for (let i = 0, l = pairs.length; i < l; i++) { - joinedPairs.push(this.accept(pairs[i])); + joinedPairs.push(this.HashPair(pairs[i])); } - return 'HASH{' + joinedPairs.join(' ') + '}'; + return joinedPairs.join(' '); }; -PrintVisitor.prototype.HashPair = function(pair) { + +PrintVisitor.prototype.HashPair = function (pair) { return pair.key + '=' + this.accept(pair.value); }; /* eslint-enable new-cap */ diff --git a/lib/types.d.ts b/lib/types.d.ts deleted file mode 100644 index afe4272..0000000 --- a/lib/types.d.ts +++ /dev/null @@ -1,235 +0,0 @@ -export interface BaseNode { - type: string; - loc: SourceLocation; -} - -export interface InverseChain { - strip: StripFlags; - program: Program; - chain?: boolean; -} - -export interface Program { - type: 'Program'; - /** - * The root node of a program has no `loc` if it's empty. - */ - loc: SourceLocation | undefined; - blockParams?: string[]; - body: Statement[]; - chained?: boolean; - strip: StripFlags; -} - -export interface CommentStatement extends BaseNode { - type: 'CommentStatement'; - value: string; - strip: StripFlags; -} - -export interface PartialStatement extends BaseNode { - type: 'PartialStatement'; - name: Expression; - params: Expression[]; - hash: Hash; - indent: string; - strip: StripFlags; -} - -export interface BlockStatement extends BaseNode { - type: 'BlockStatement'; - path: Expression; - params: Expression[]; - hash: Hash; - program: Program | undefined; - inverse?: Program | undefined; - openStrip: StripFlags; - inverseStrip: StripFlags | undefined; - closeStrip: StripFlags; -} - -export interface DecoratorBlock extends BaseNode { - type: 'DecoratorBlock'; - path: Expression; - params: Expression[]; - hash: Hash; - program: Program; - inverse?: undefined; - inverseStrip?: undefined; - openStrip: StripFlags; - closeStrip: StripFlags; -} - -export interface PartialBlockStatement extends BaseNode { - type: 'PartialBlockStatement'; - name: Expression; - params: Expression[]; - hash: Hash; - program: Program; - inverse?: undefined; - inverseStrip?: undefined; - openStrip: StripFlags; - closeStrip: StripFlags; -} - -export type Statement = - | MustacheStatement - | Content - | BlockStatement - | PartialStatement - | PartialBlockStatement; - -export interface MustacheStatement extends BaseNode { - type: 'Decorator' | 'MustacheStatement'; - path: Expression; - params: Expression[]; - hash: Hash; - escaped: boolean; - strip: StripFlags; -} - -export interface PathExpression extends BaseNode { - readonly original: string; - readonly this: boolean; - readonly data: boolean; - readonly depth: number; - readonly parts: (string | SubExpression)[]; - readonly head: string | SubExpression | undefined; - readonly tail: string[]; -} - -export interface SubExpression extends BaseNode { - readonly original: string; -} - -export interface Hash { - readonly pairs: HashPair[]; -} - -export interface StripFlags { - readonly open?: boolean; - readonly close?: boolean; - readonly openStandalone?: boolean; - readonly closeStandalone?: boolean; - readonly inlineStandalone?: boolean; -} - -export interface HashPair { - readonly key: string; - readonly value: Expression; -} - -export interface ParserPart { - readonly part: string; - readonly original: string; - readonly separator: string; -} - -export interface Content extends BaseNode { - type: 'ContentStatement'; - original: string; - value: string; -} - -export type Expression = SubExpression | PathExpression; - -export interface SourcePosition { - line: number; - column: number; -} - -export interface SourceLocation { - source: string | undefined; - start: SourcePosition; - end: SourcePosition; -} - -export interface CallNode { - path: Expression; - params: Expression[]; - hash: Hash; -} - -export interface OpenPartial { - strip: StripFlags; -} - -export interface OpenPartialBlock extends CallNode { - strip: StripFlags; -} - -export interface OpenRawBlock extends CallNode, BaseNode {} - -export interface OpenBlock extends CallNode { - open: string; - blockParams: string[]; - strip: StripFlags; -} - -export interface OpenInverse extends CallNode { - blockParams: string[]; - strip: StripFlags; -} - -export interface CloseBlock { - readonly path: PathExpression; - strip: StripFlags; -} - -export type AcceptedNode = Program; - -/// JISON TYPES /// - -export interface Parser { - parse: (input: string) => Program; - yy: YY; -} - -export interface YY { - locInfo(locInfo: LocInfo): SourceLocation; - preparePath( - this: YY, - data: boolean, - sexpr: { expr: SubExpression; sep: string } | false, - parts: ParserPart[], - locInfo: LocInfo, - ): PathExpression; - - prepareMustache( - this: YY, - path: PathExpression, - params: Expression[], - hash: Hash, - open: string, - strip: StripFlags, - locInfo: LocInfo, - ): MustacheStatement; - - prepareRawBlock( - this: YY, - openRawBlock: OpenRawBlock, - contents: Content[], - close: string, - locInfo: LocInfo, - ): BlockStatement; - - prepareBlock( - this: YY, - openBlock: OpenBlock, - program: Program, - inverseChain: InverseChain, - close: CloseBlock, - inverted: boolean, - locInfo: LocInfo, - ): BlockStatement | DecoratorBlock; -} - -/** - * The `LocInfo` object comes from the generated `jison` parser. - */ -export interface LocInfo { - first_line: number; - first_column: number; - last_line: number; - last_column: number; -} diff --git a/lib/visitor.js b/lib/visitor.js index 0f7826b..e9b5d68 100644 --- a/lib/visitor.js +++ b/lib/visitor.js @@ -1,4 +1,4 @@ -import Exception from './exception'; +import Exception from './exception.js'; function Visitor() { this.parents = []; @@ -9,7 +9,7 @@ Visitor.prototype = { mutating: false, // Visits a given value. If mutating, will replace the value if necessary. - acceptKey: function(node, name) { + acceptKey: function (node, name) { let value = this.accept(node[name]); if (this.mutating) { // Hacky sanity check: This may have a few false positives for type for the helper @@ -30,7 +30,7 @@ Visitor.prototype = { // Performs an accept operation with added sanity check to ensure // required keys are not removed. - acceptRequired: function(node, name) { + acceptRequired: function (node, name) { this.acceptKey(node, name); if (!node[name]) { @@ -40,7 +40,7 @@ Visitor.prototype = { // Traverses a given array. If mutating, empty responses will be removed // for child elements. - acceptArray: function(array) { + acceptArray: function (array) { for (let i = 0, l = array.length; i < l; i++) { this.acceptKey(array, i); @@ -52,7 +52,7 @@ Visitor.prototype = { } }, - accept: function(object) { + accept: function (object) { if (!object) { return; } @@ -78,7 +78,7 @@ Visitor.prototype = { } }, - Program: function(program) { + Program: function (program) { this.acceptArray(program.body); }, @@ -89,31 +89,31 @@ Visitor.prototype = { DecoratorBlock: visitBlock, PartialStatement: visitPartial, - PartialBlockStatement: function(partial) { + PartialBlockStatement: function (partial) { visitPartial.call(this, partial); this.acceptKey(partial, 'program'); }, - ContentStatement: function(/* content */) {}, - CommentStatement: function(/* comment */) {}, + ContentStatement: function (/* content */) {}, + CommentStatement: function (/* comment */) {}, SubExpression: visitSubExpression, - PathExpression: function(/* path */) {}, + PathExpression: function (/* path */) {}, - StringLiteral: function(/* string */) {}, - NumberLiteral: function(/* number */) {}, - BooleanLiteral: function(/* bool */) {}, - UndefinedLiteral: function(/* literal */) {}, - NullLiteral: function(/* literal */) {}, + StringLiteral: function (/* string */) {}, + NumberLiteral: function (/* number */) {}, + BooleanLiteral: function (/* bool */) {}, + UndefinedLiteral: function (/* literal */) {}, + NullLiteral: function (/* literal */) {}, - Hash: function(hash) { + Hash: function (hash) { this.acceptArray(hash.pairs); }, - HashPair: function(pair) { + HashPair: function (pair) { this.acceptRequired(pair, 'value'); - } + }, }; function visitSubExpression(mustache) { diff --git a/lib/whitespace-control.js b/lib/whitespace-control.js index e85d66a..8d98c14 100644 --- a/lib/whitespace-control.js +++ b/lib/whitespace-control.js @@ -1,11 +1,11 @@ -import Visitor from './visitor'; +import Visitor from './visitor.js'; function WhitespaceControl(options = {}) { this.options = options; } WhitespaceControl.prototype = new Visitor(); -WhitespaceControl.prototype.Program = function(program) { +WhitespaceControl.prototype.Program = function (program) { const doStandalone = !this.options.ignoreStandalone; let isRoot = !this.isRootSeen; @@ -62,88 +62,87 @@ WhitespaceControl.prototype.Program = function(program) { return program; }; -WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function( - block -) { - this.accept(block.program); - this.accept(block.inverse); - - // Find the inverse program that is involved with whitespace stripping. - let program = block.program || block.inverse, - inverse = block.program && block.inverse, - firstInverse = inverse, - lastInverse = inverse; - - if (inverse && inverse.chained) { - firstInverse = inverse.body[0].program; - - // Walk the inverse chain to find the last inverse that is actually in the chain. - while (lastInverse.chained) { - lastInverse = lastInverse.body[lastInverse.body.length - 1].program; - } - } +WhitespaceControl.prototype.BlockStatement = + WhitespaceControl.prototype.DecoratorBlock = + WhitespaceControl.prototype.PartialBlockStatement = + function (block) { + this.accept(block.program); + this.accept(block.inverse); + + // Find the inverse program that is involved with whitespace stripping. + let program = block.program || block.inverse, + inverse = block.program && block.inverse, + firstInverse = inverse, + lastInverse = inverse; + + if (inverse && inverse.chained) { + firstInverse = inverse.body[0].program; + + // Walk the inverse chain to find the last inverse that is actually in the chain. + while (lastInverse.chained) { + lastInverse = lastInverse.body[lastInverse.body.length - 1].program; + } + } - let strip = { - open: block.openStrip.open, - close: block.closeStrip.close, + let strip = { + open: block.openStrip.open, + close: block.closeStrip.close, - // Determine the standalone candidacy. Basically flag our content as being possibly standalone - // so our parent can determine if we actually are standalone - openStandalone: isNextWhitespace(program.body), - closeStandalone: isPrevWhitespace((firstInverse || program).body) - }; + // Determine the standalone candidacy. Basically flag our content as being possibly standalone + // so our parent can determine if we actually are standalone + openStandalone: isNextWhitespace(program.body), + closeStandalone: isPrevWhitespace((firstInverse || program).body), + }; - if (block.openStrip.close) { - omitRight(program.body, null, true); - } + if (block.openStrip.close) { + omitRight(program.body, null, true); + } - if (inverse) { - let inverseStrip = block.inverseStrip; + if (inverse) { + let inverseStrip = block.inverseStrip; - if (inverseStrip.open) { - omitLeft(program.body, null, true); - } + if (inverseStrip.open) { + omitLeft(program.body, null, true); + } - if (inverseStrip.close) { - omitRight(firstInverse.body, null, true); - } - if (block.closeStrip.open) { - omitLeft(lastInverse.body, null, true); - } + if (inverseStrip.close) { + omitRight(firstInverse.body, null, true); + } + if (block.closeStrip.open) { + omitLeft(lastInverse.body, null, true); + } - // Find standalone else statements - if ( - !this.options.ignoreStandalone && - isPrevWhitespace(program.body) && - isNextWhitespace(firstInverse.body) - ) { - omitLeft(program.body); - omitRight(firstInverse.body); - } - } else if (block.closeStrip.open) { - omitLeft(program.body, null, true); - } + // Find standalone else statements + if ( + !this.options.ignoreStandalone && + isPrevWhitespace(program.body) && + isNextWhitespace(firstInverse.body) + ) { + omitLeft(program.body); + omitRight(firstInverse.body); + } + } else if (block.closeStrip.open) { + omitLeft(program.body, null, true); + } - return strip; -}; + return strip; + }; -WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function( - mustache -) { - return mustache.strip; -}; +WhitespaceControl.prototype.Decorator = + WhitespaceControl.prototype.MustacheStatement = function (mustache) { + return mustache.strip; + }; -WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function( - node -) { - /* istanbul ignore next */ - let strip = node.strip || {}; - return { - inlineStandalone: true, - open: strip.open, - close: strip.close +WhitespaceControl.prototype.PartialStatement = + WhitespaceControl.prototype.CommentStatement = function (node) { + /* istanbul ignore next */ + let strip = node.strip || {}; + return { + inlineStandalone: true, + open: strip.open, + close: strip.close, + }; }; -}; function isPrevWhitespace(body, i, isRoot) { if (i === undefined) { diff --git a/package.json b/package.json index 29acaa3..a52f128 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "@handlebars/parser", + "type": "module", "version": "2.1.0", "description": "The parser for the Handlebars language", "homepage": "https://github.com/handlebars-lang/handlebars-parser#readme", @@ -15,16 +16,35 @@ "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "types": "types/index.d.ts", + "exports": { + ".": { + "require": { + "types": "./types/index.d.ts", + "default": "./dist/cjs/index.js" + }, + "import": { + "types": "./types/index.d.ts", + "default": "./dist/esm/index.js" + }, + "default": { + "types": "./types/index.d.ts", + "default": "./dist/cjs/index.js" + } + } + }, "scripts": { "lint": "eslint .", "prepublishOnly": "pnpm run build", "build": "npm-run-all build:parser build:esm build:cjs", - "build:cjs": "tsc --module commonjs --target es5 --outDir dist/cjs", + "build:cjs": "tsc --module nodenext --moduleResolution nodenext --target es5 --outDir dist/cjs", "build:esm": "tsc --module es2015 --target es5 --outDir dist/esm", "build:jison": "jison -m js src/handlebars.yy src/handlebars.l -o lib/parser.js", "build:parser": "npm-run-all build:jison build:parser-suffix", "build:parser-suffix": "combine-files lib/parser.js,src/parser-suffix.js lib/parser.js", - "test": "pnpm run build && mocha spec --require esm" + "pretest": "pnpm run build", + "test": "mocha --inline-diffs spec", + "pretest:bail": "pnpm run build", + "test:bail": "mocha --bail --inline-diffs spec" }, "prettier": { "tabWidth": 2, @@ -35,10 +55,9 @@ "combine-files": "^1.1.8", "eslint": "^8.57.1", "eslint-config-prettier": "^8.10.0", - "eslint-plugin-compat": "^3.13.0", - "esm": "^3.2.25", + "eslint-plugin-compat": "^6.0.1", "jison": "^0.4.18", - "mocha": "^8.1.3", + "mocha": "^10.7.3", "npm-run-all": "^4.1.5", "prettier": "^2.1.1", "release-it": "^14.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cef0bd9..a2e74b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,17 +18,14 @@ importers: specifier: ^8.10.0 version: 8.10.0(eslint@8.57.1) eslint-plugin-compat: - specifier: ^3.13.0 - version: 3.13.0(eslint@8.57.1) - esm: - specifier: ^3.2.25 - version: 3.2.25 + specifier: ^6.0.1 + version: 6.0.1(eslint@8.57.1) jison: specifier: ^0.4.18 version: 0.4.18 mocha: - specifier: ^8.1.3 - version: 8.4.0 + specifier: ^10.7.3 + version: 10.7.3 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -93,8 +90,8 @@ packages: '@iarna/toml@2.2.5': resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} - '@mdn/browser-compat-data@3.3.14': - resolution: {integrity: sha512-n2RC9d6XatVbWFdHLimzzUJxJ1KY8LdjqrW6YvGPiRmsHkhOUx74/Ct10x5Yo7bC/Jvqx7cDEW8IMPv/+vwEzA==} + '@mdn/browser-compat-data@5.6.9': + resolution: {integrity: sha512-xbpYnhcx48qe1p8qimSCUu79QPhK6STaj5mUJ7A0VRCxgfZ5boJ4L/Vy9e5lOPquPSQ1tWZ6mOO+01VzLJg2iA==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -196,9 +193,6 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@ungap/promise-all-settled@1.1.2': - resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} - '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -253,18 +247,14 @@ packages: ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - ansi-colors@4.1.1: - resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - ansi-regex@3.0.1: - resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} - engines: {node: '>=4'} - ansi-regex@4.1.1: resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} engines: {node: '>=6'} @@ -310,8 +300,8 @@ packages: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} - ast-metadata-inferer@0.7.0: - resolution: {integrity: sha512-OkMLzd8xelb3gmnp6ToFvvsHLtS6CbagTkFQvQ+ZYFe3/AIl9iKikNR9G7pY3GfOR/2Xc222hwBjzI7HLkE76Q==} + ast-metadata-inferer@0.8.0: + resolution: {integrity: sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA==} ast-types@0.13.4: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} @@ -357,6 +347,9 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -433,8 +426,8 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - chokidar@3.5.1: - resolution: {integrity: sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} chownr@1.1.4: @@ -529,9 +522,6 @@ packages: resolution: {integrity: sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==} deprecated: This package is no longer supported. - core-js@3.38.1: - resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} - core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -571,15 +561,6 @@ packages: resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} engines: {node: '>= 0.4'} - debug@4.3.1: - resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.3.2: resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==} engines: {node: '>=6.0'} @@ -679,8 +660,8 @@ packages: resolution: {integrity: sha512-sarumrIS8/WEcRudIG0PQRSJQ7TLX6WAPrYg4SZtaYSoc5wMXzL1f2HU2dO7G/9X87yk7LgGk8fkKxTm7ZweGQ==} engines: {node: '>=4.0.0'} - diff@5.0.0: - resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} dir-glob@3.0.1: @@ -790,11 +771,11 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-plugin-compat@3.13.0: - resolution: {integrity: sha512-cv8IYMuTXm7PIjMVDN2y4k/KVnKZmoNGHNq27/9dLstOLydKblieIv+oe2BN2WthuXnFNhaNvv3N1Bvl4dbIGA==} - engines: {node: '>=9.x'} + eslint-plugin-compat@6.0.1: + resolution: {integrity: sha512-0MeIEuoy8kWkOhW38kK8hU4vkb6l/VvyjpuYDymYOXmUY9NvTgyErF16lYuX+HPS5hkmym7lfA+XpYZiWYWmYA==} + engines: {node: '>=18.x'} peerDependencies: - eslint: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} @@ -810,10 +791,6 @@ packages: deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true - esm@3.2.25: - resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} - engines: {node: '>=6'} - espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1025,14 +1002,15 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@7.1.6: - resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} - deprecated: Glob versions prior to v9 are no longer supported - glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + global-dirs@2.1.0: resolution: {integrity: sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==} engines: {node: '>=8'} @@ -1045,6 +1023,10 @@ packages: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} + globals@15.11.0: + resolution: {integrity: sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -1074,10 +1056,6 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - growl@1.10.5: - resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==} - engines: {node: '>=4.x'} - has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -1475,10 +1453,6 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.0.0: - resolution: {integrity: sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==} - hasBin: true - js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1569,10 +1543,6 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@4.0.0: - resolution: {integrity: sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==} - engines: {node: '>=10'} - log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} @@ -1647,12 +1617,13 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - minimatch@3.0.4: - resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -1697,9 +1668,9 @@ packages: engines: {node: '>=10'} hasBin: true - mocha@8.4.0: - resolution: {integrity: sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==} - engines: {node: '>= 10.12.0'} + mocha@10.7.3: + resolution: {integrity: sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==} + engines: {node: '>= 14.0.0'} hasBin: true move-concurrently@1.0.1: @@ -1718,11 +1689,6 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.1.20: - resolution: {integrity: sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -2070,8 +2036,8 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readdirp@3.5.0: - resolution: {integrity: sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==} + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} rechoir@0.6.2: @@ -2218,8 +2184,8 @@ packages: engines: {node: '>=10'} hasBin: true - serialize-javascript@5.0.1: - resolution: {integrity: sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==} + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -2338,10 +2304,6 @@ packages: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} engines: {node: '>=4'} - string-width@2.1.1: - resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} - engines: {node: '>=4'} - string-width@3.1.0: resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==} engines: {node: '>=6'} @@ -2374,10 +2336,6 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - strip-ansi@4.0.0: - resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} - engines: {node: '>=4'} - strip-ansi@5.2.0: resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} engines: {node: '>=6'} @@ -2618,9 +2576,6 @@ packages: engines: {node: '>= 8'} hasBin: true - wide-align@1.1.3: - resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==} - widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'} @@ -2641,8 +2596,8 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - workerpool@6.1.0: - resolution: {integrity: sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==} + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} wrap-ansi@5.1.0: resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==} @@ -2738,7 +2693,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -2754,7 +2709,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -2765,7 +2720,7 @@ snapshots: '@iarna/toml@2.2.5': {} - '@mdn/browser-compat-data@3.3.14': {} + '@mdn/browser-compat-data@5.6.9': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -2908,8 +2863,6 @@ snapshots: dependencies: '@types/node': 22.7.5 - '@ungap/promise-all-settled@1.1.2': {} - '@ungap/structured-clone@1.2.0': {} JSONSelect@0.4.0: {} @@ -2934,7 +2887,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -2961,14 +2914,12 @@ snapshots: dependencies: string-width: 4.2.3 - ansi-colors@4.1.1: {} + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 - ansi-regex@3.0.1: {} - ansi-regex@4.1.1: {} ansi-regex@5.0.1: {} @@ -3019,9 +2970,9 @@ snapshots: is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 - ast-metadata-inferer@0.7.0: + ast-metadata-inferer@0.8.0: dependencies: - '@mdn/browser-compat-data': 3.3.14 + '@mdn/browser-compat-data': 5.6.9 ast-types@0.13.4: dependencies: @@ -3082,6 +3033,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -3189,7 +3144,7 @@ snapshots: chardet@0.7.0: {} - chokidar@3.5.1: + chokidar@3.6.0: dependencies: anymatch: 3.1.3 braces: 3.0.3 @@ -3197,7 +3152,7 @@ snapshots: is-binary-path: 2.1.0 is-glob: 4.0.3 normalize-path: 3.0.0 - readdirp: 3.5.0 + readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 @@ -3292,8 +3247,6 @@ snapshots: rimraf: 2.7.1 run-queue: 1.0.3 - core-js@3.38.1: {} - core-util-is@1.0.3: {} cosmiconfig@7.0.0: @@ -3348,12 +3301,6 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.1 - debug@4.3.1(supports-color@8.1.1): - dependencies: - ms: 2.1.2 - optionalDependencies: - supports-color: 8.1.1 - debug@4.3.2(supports-color@7.2.0): dependencies: ms: 2.1.2 @@ -3364,9 +3311,11 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.3.7: + debug@4.3.7(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 decamelize@1.2.0: {} @@ -3435,7 +3384,7 @@ snapshots: lodash.find: 4.6.0 pify: 2.3.0 - diff@5.0.0: {} + diff@5.2.0: {} dir-glob@3.0.1: dependencies: @@ -3594,17 +3543,17 @@ snapshots: dependencies: eslint: 8.57.1 - eslint-plugin-compat@3.13.0(eslint@8.57.1): + eslint-plugin-compat@6.0.1(eslint@8.57.1): dependencies: - '@mdn/browser-compat-data': 3.3.14 - ast-metadata-inferer: 0.7.0 + '@mdn/browser-compat-data': 5.6.9 + ast-metadata-inferer: 0.8.0 browserslist: 4.24.0 caniuse-lite: 1.0.30001668 - core-js: 3.38.1 eslint: 8.57.1 find-up: 5.0.0 + globals: 15.11.0 lodash.memoize: 4.1.2 - semver: 7.3.5 + semver: 7.6.3 eslint-scope@7.2.2: dependencies: @@ -3626,7 +3575,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -3656,8 +3605,6 @@ snapshots: transitivePeerDependencies: - supports-color - esm@3.2.25: {} - espree@9.6.1: dependencies: acorn: 8.12.1 @@ -3877,7 +3824,7 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 data-uri-to-buffer: 3.0.1 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) file-uri-to-path: 2.0.0 fs-extra: 8.1.0 ftp: 0.3.10 @@ -3905,23 +3852,22 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@7.1.6: + glob@7.2.3: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.0.4 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - glob@7.2.3: + glob@8.1.0: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 5.1.6 once: 1.4.0 - path-is-absolute: 1.0.1 global-dirs@2.1.0: dependencies: @@ -3935,6 +3881,8 @@ snapshots: dependencies: type-fest: 0.20.2 + globals@15.11.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -3996,8 +3944,6 @@ snapshots: graphemer@1.4.0: {} - growl@1.10.5: {} - has-bigints@1.0.2: {} has-flag@3.0.0: {} @@ -4041,7 +3987,7 @@ snapshots: http-proxy-agent@3.0.0: dependencies: agent-base: 5.1.1 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -4049,7 +3995,7 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -4061,14 +4007,14 @@ snapshots: https-proxy-agent@4.0.0: dependencies: agent-base: 5.1.1 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -4373,10 +4319,6 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.0.0: - dependencies: - argparse: 2.0.1 - js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -4472,10 +4414,6 @@ snapshots: lodash@4.17.21: {} - log-symbols@4.0.0: - dependencies: - chalk: 4.1.2 - log-symbols@4.1.0: dependencies: chalk: 4.1.2 @@ -4549,13 +4487,13 @@ snapshots: mimic-response@3.1.0: {} - minimatch@3.0.4: + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 - minimatch@3.1.2: + minimatch@5.1.6: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 2.0.1 minimist@1.2.8: {} @@ -4600,32 +4538,27 @@ snapshots: mkdirp@1.0.4: {} - mocha@8.4.0: + mocha@10.7.3: dependencies: - '@ungap/promise-all-settled': 1.1.2 - ansi-colors: 4.1.1 + ansi-colors: 4.1.3 browser-stdout: 1.3.1 - chokidar: 3.5.1 - debug: 4.3.1(supports-color@8.1.1) - diff: 5.0.0 + chokidar: 3.6.0 + debug: 4.3.7(supports-color@8.1.1) + diff: 5.2.0 escape-string-regexp: 4.0.0 find-up: 5.0.0 - glob: 7.1.6 - growl: 1.10.5 + glob: 8.1.0 he: 1.2.0 - js-yaml: 4.0.0 - log-symbols: 4.0.0 - minimatch: 3.0.4 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.1.6 ms: 2.1.3 - nanoid: 3.1.20 - serialize-javascript: 5.0.1 + serialize-javascript: 6.0.2 strip-json-comments: 3.1.1 supports-color: 8.1.1 - which: 2.0.2 - wide-align: 1.1.3 - workerpool: 6.1.0 + workerpool: 6.5.1 yargs: 16.2.0 - yargs-parser: 20.2.4 + yargs-parser: 20.2.9 yargs-unparser: 2.0.0 move-concurrently@1.0.1: @@ -4649,8 +4582,6 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.1.20: {} - natural-compare@1.4.0: {} netmask@2.0.2: {} @@ -4809,7 +4740,7 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) get-uri: 3.0.2 http-proxy-agent: 4.0.1 https-proxy-agent: 5.0.1 @@ -4935,7 +4866,7 @@ snapshots: proxy-agent@5.0.0: dependencies: agent-base: 6.0.2 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) http-proxy-agent: 4.0.1 https-proxy-agent: 5.0.1 lru-cache: 5.1.1 @@ -5020,7 +4951,7 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@3.5.0: + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -5224,7 +5155,7 @@ snapshots: semver@7.6.3: {} - serialize-javascript@5.0.1: + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 @@ -5295,7 +5226,7 @@ snapshots: socks-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -5349,11 +5280,6 @@ snapshots: strict-uri-encode@2.0.0: {} - string-width@2.1.1: - dependencies: - is-fullwidth-code-point: 2.0.0 - strip-ansi: 4.0.0 - string-width@3.1.0: dependencies: emoji-regex: 7.0.3 @@ -5402,10 +5328,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - strip-ansi@4.0.0: - dependencies: - ansi-regex: 3.0.1 - strip-ansi@5.2.0: dependencies: ansi-regex: 4.1.1 @@ -5664,10 +5586,6 @@ snapshots: dependencies: isexe: 2.0.0 - wide-align@1.1.3: - dependencies: - string-width: 2.1.1 - widest-line@3.1.0: dependencies: string-width: 4.2.3 @@ -5685,7 +5603,7 @@ snapshots: word-wrap@1.2.5: {} - workerpool@6.1.0: {} + workerpool@6.5.1: {} wrap-ansi@5.1.0: dependencies: diff --git a/spec/.eslintrc.js b/spec/.eslintrc.cjs similarity index 61% rename from spec/.eslintrc.js rename to spec/.eslintrc.cjs index 9fa44c3..f078531 100644 --- a/spec/.eslintrc.js +++ b/spec/.eslintrc.cjs @@ -1,5 +1,5 @@ module.exports = { - extends: ['../.eslintrc.js'], + extends: ['../.eslintrc.cjs'], env: { mocha: true, }, diff --git a/spec/ast.js b/spec/ast.js index fb45f00..31c4c44 100644 --- a/spec/ast.js +++ b/spec/ast.js @@ -1,17 +1,17 @@ -import { parse, parseWithoutProcessing } from '../dist/esm'; -import { equals } from './utils'; +import { parse, parseWithoutProcessing } from '../dist/esm/index.js'; +import { equals } from './utils.js'; -describe('ast', function() { - describe('whitespace control', function() { - describe('parse', function() { - it('mustache', function() { +describe('ast', function () { + describe('whitespace control', function () { + describe('parse', function () { + it('mustache', function () { let ast = parse(' {{~comment~}} '); equals(ast.body[0].value, ''); equals(ast.body[2].value, ''); }); - it('block statements', function() { + it('block statements', function () { let ast = parse(' {{# comment~}} \nfoo\n {{~/comment}}'); equals(ast.body[0].value, ''); @@ -19,15 +19,15 @@ describe('ast', function() { }); }); - describe('parseWithoutProcessing', function() { - it('mustache', function() { + describe('parseWithoutProcessing', function () { + it('mustache', function () { let ast = parseWithoutProcessing(' {{~comment~}} '); equals(ast.body[0].value, ' '); equals(ast.body[2].value, ' '); }); - it('block statements', function() { + it('block statements', function () { let ast = parseWithoutProcessing( ' {{# comment~}} \nfoo\n {{~/comment}}' ); @@ -90,19 +90,19 @@ describe('ast', function() { }); }); - describe('standalone flags', function() { - describe('mustache', function() { - it('does not mark mustaches as standalone', function() { + describe('standalone flags', function () { + describe('mustache', function () { + it('does not mark mustaches as standalone', function () { let ast = parse(' {{comment}} '); equals(!!ast.body[0].value, true); equals(!!ast.body[2].value, true); }); }); - describe('blocks - parseWithoutProcessing', function() { - it('block mustaches', function() { + describe('blocks - parseWithoutProcessing', function () { + it('block mustaches', function () { let ast = parseWithoutProcessing( - ' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), + ' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' + ), block = ast.body[1]; equals(ast.body[0].value, ' '); @@ -112,28 +112,26 @@ describe('ast', function() { equals(ast.body[2].value, ' '); }); - it('initial block mustaches', function() { - let ast = parseWithoutProcessing( - '{{# comment}} \nfoo\n {{/comment}}' - ), + it('initial block mustaches', function () { + let ast = parseWithoutProcessing('{{# comment}} \nfoo\n {{/comment}}'), block = ast.body[0]; equals(block.program.body[0].value, ' \nfoo\n '); }); - it('mustaches with children', function() { + it('mustaches with children', function () { let ast = parseWithoutProcessing( - '{{# comment}} \n{{foo}}\n {{/comment}}' - ), + '{{# comment}} \n{{foo}}\n {{/comment}}' + ), block = ast.body[0]; equals(block.program.body[0].value, ' \n'); equals(block.program.body[1].path.original, 'foo'); equals(block.program.body[2].value, '\n '); }); - it('nested block mustaches', function() { + it('nested block mustaches', function () { let ast = parseWithoutProcessing( - '{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}' - ), + '{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}' + ), body = ast.body[0].program.body, block = body[1]; @@ -142,10 +140,10 @@ describe('ast', function() { equals(block.program.body[0].value, ' \nfoo\n '); equals(block.inverse.body[0].value, ' \n bar \n '); }); - it('column 0 block mustaches', function() { + it('column 0 block mustaches', function () { let ast = parseWithoutProcessing( - 'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), + 'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' + ), block = ast.body[1]; equals(ast.body[0].omit, undefined); @@ -156,11 +154,11 @@ describe('ast', function() { equals(ast.body[2].value, ' '); }); }); - describe('blocks', function() { - it('marks block mustaches as standalone', function() { + describe('blocks', function () { + it('marks block mustaches as standalone', function () { let ast = parse( - ' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), + ' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' + ), block = ast.body[1]; equals(ast.body[0].value, ''); @@ -170,13 +168,13 @@ describe('ast', function() { equals(ast.body[2].value, ''); }); - it('marks initial block mustaches as standalone', function() { + it('marks initial block mustaches as standalone', function () { let ast = parse('{{# comment}} \nfoo\n {{/comment}}'), block = ast.body[0]; equals(block.program.body[0].value, 'foo\n'); }); - it('marks mustaches with children as standalone', function() { + it('marks mustaches with children as standalone', function () { let ast = parse('{{# comment}} \n{{foo}}\n {{/comment}}'), block = ast.body[0]; @@ -184,10 +182,10 @@ describe('ast', function() { equals(block.program.body[1].path.original, 'foo'); equals(block.program.body[2].value, '\n'); }); - it('marks nested block mustaches as standalone', function() { + it('marks nested block mustaches as standalone', function () { let ast = parse( - '{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}' - ), + '{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}' + ), body = ast.body[0].program.body, block = body[1]; @@ -198,10 +196,10 @@ describe('ast', function() { equals(body[0].value, ''); }); - it('does not mark nested block mustaches as standalone', function() { + it('does not mark nested block mustaches as standalone', function () { let ast = parse( - '{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}' - ), + '{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}' + ), body = ast.body[0].program.body, block = body[1]; @@ -212,10 +210,10 @@ describe('ast', function() { equals(body[0].omit, undefined); }); - it('does not mark nested initial block mustaches as standalone', function() { + it('does not mark nested initial block mustaches as standalone', function () { let ast = parse( - '{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}' - ), + '{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}' + ), body = ast.body[0].program.body, block = body[0]; @@ -225,10 +223,10 @@ describe('ast', function() { equals(body[0].omit, undefined); }); - it('marks column 0 block mustaches as standalone', function() { + it('marks column 0 block mustaches as standalone', function () { let ast = parse( - 'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), + 'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' + ), block = ast.body[1]; equals(ast.body[0].omit, undefined); @@ -239,30 +237,30 @@ describe('ast', function() { equals(ast.body[2].value, ''); }); }); - describe('partials - parseWithoutProcessing', function() { - it('simple partial', function() { + describe('partials - parseWithoutProcessing', function () { + it('simple partial', function () { let ast = parseWithoutProcessing('{{> partial }} '); equals(ast.body[1].value, ' '); }); - it('indented partial', function() { + it('indented partial', function () { let ast = parseWithoutProcessing(' {{> partial }} '); equals(ast.body[0].value, ' '); equals(ast.body[1].indent, ''); equals(ast.body[2].value, ' '); }); }); - describe('partials', function() { - it('marks partial as standalone', function() { + describe('partials', function () { + it('marks partial as standalone', function () { let ast = parse('{{> partial }} '); equals(ast.body[1].value, ''); }); - it('marks indented partial as standalone', function() { + it('marks indented partial as standalone', function () { let ast = parse(' {{> partial }} '); equals(ast.body[0].value, ''); equals(ast.body[1].indent, ' '); equals(ast.body[2].value, ''); }); - it('marks those around content as not standalone', function() { + it('marks those around content as not standalone', function () { let ast = parse('a{{> partial }}'); equals(ast.body[0].omit, undefined); @@ -270,28 +268,28 @@ describe('ast', function() { equals(ast.body[1].omit, undefined); }); }); - describe('comments - parseWithoutProcessing', function() { - it('simple comment', function() { + describe('comments - parseWithoutProcessing', function () { + it('simple comment', function () { let ast = parseWithoutProcessing('{{! comment }} '); equals(ast.body[1].value, ' '); }); - it('indented comment', function() { + it('indented comment', function () { let ast = parseWithoutProcessing(' {{! comment }} '); equals(ast.body[0].value, ' '); equals(ast.body[2].value, ' '); }); }); - describe('comments', function() { - it('marks comment as standalone', function() { + describe('comments', function () { + it('marks comment as standalone', function () { let ast = parse('{{! comment }} '); equals(ast.body[1].value, ''); }); - it('marks indented comment as standalone', function() { + it('marks indented comment as standalone', function () { let ast = parse(' {{! comment }} '); equals(ast.body[0].value, ''); equals(ast.body[2].value, ''); }); - it('marks those around content as not standalone', function() { + it('marks those around content as not standalone', function () { let ast = parse('a{{! comment }}'); equals(ast.body[0].omit, undefined); diff --git a/spec/parser.js b/spec/parser.js index 2c2c476..93ad216 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -1,5 +1,5 @@ -import { parse, print } from '../dist/esm'; -import { equals, equalsAst, shouldThrow } from './utils'; +import { parse, print } from '../dist/esm/index.js'; +import { equals, equalsAst, shouldThrow } from './utils.js'; describe('parser', function () { function astFor(template) { @@ -8,44 +8,92 @@ describe('parser', function () { } it('parses simple mustaches', function () { - equalsAst('{{123}}', '{{ n%123 [] }}'); - equalsAst('{{"foo"}}', '{{ "foo" [] }}'); - equalsAst('{{false}}', '{{ b%false [] }}'); - equalsAst('{{true}}', '{{ b%true [] }}'); - equalsAst('{{foo}}', '{{ p%foo [] }}'); - equalsAst('{{foo?}}', '{{ p%foo? [] }}'); - equalsAst('{{foo_}}', '{{ p%foo_ [] }}'); - equalsAst('{{foo-}}', '{{ p%foo- [] }}'); - equalsAst('{{foo:}}', '{{ p%foo: [] }}'); + equalsAst('{{123}}', '{{ n%123 }}'); + equalsAst('{{"foo"}}', '{{ "foo" }}'); + equalsAst('{{false}}', '{{ b%false }}'); + equalsAst('{{true}}', '{{ b%true }}'); + equalsAst('{{foo}}', '{{ p%foo }}'); + equalsAst('{{foo?}}', '{{ p%foo? }}'); + equalsAst('{{foo_}}', '{{ p%foo_ }}'); + equalsAst('{{foo-}}', '{{ p%foo- }}'); + equalsAst('{{foo:}}', '{{ p%foo: }}'); }); it('parses simple mustaches with data', function () { - equalsAst('{{@foo}}', '{{ p%@foo [] }}'); + equalsAst('{{@foo}}', '{{ p%@foo }}'); }); it('parses simple mustaches with data paths', function () { - equalsAst('{{@../foo}}', '{{ p%@foo [] }}'); + equalsAst('{{@../foo}}', '{{ p%@foo }}'); }); it('parses mustaches with paths', function () { - equalsAst('{{foo/bar}}', '{{ p%foo/bar [] }}'); - equalsAst('{{foo.bar}}', '{{ p%foo/bar [] }}'); - equalsAst('{{foo.#bar}}', '{{ p%foo/#bar [] }}'); - equalsAst('{{@foo.#bar}}', '{{ p%@foo/#bar [] }}'); + equalsAst('{{foo/bar}}', '{{ p%foo/bar }}'); + equalsAst('{{foo.bar}}', '{{ p%foo/bar }}'); + equalsAst('{{foo.#bar}}', '{{ p%foo/#bar }}'); + equalsAst('{{@foo.#bar}}', '{{ p%@foo/#bar }}'); - equalsAst('{{this/foo}}', '{{ p%foo [] }}'); - equalsAst('{{this.foo}}', '{{ p%this.foo [] }}'); - equalsAst('{{this.#foo}}', '{{ p%this.#foo [] }}'); + equalsAst('{{this/foo}}', '{{ p%foo }}'); + equalsAst('{{this.foo}}', '{{ p%this.foo }}'); + equalsAst('{{this.#foo}}', '{{ p%this.#foo }}'); }); it('parses mustaches with - in a path', function () { - equalsAst('{{foo-bar}}', '{{ p%foo-bar [] }}'); + equalsAst('{{foo-bar}}', '{{ p%foo-bar }}'); }); + it('parses mustaches with escaped [] in a path', function () { - equalsAst('{{[foo[\\]]}}', '{{ p%foo[] [] }}'); + equalsAst('{{[foo[\\]]}}', '{{ p%foo[] }}'); }); + it('parses escaped \\\\ in path', function () { - equalsAst('{{[foo\\\\]}}', '{{ p%foo\\ [] }}'); + equalsAst('{{[foo\\\\]}}', '{{ p%foo\\ }}'); + }); + + it('parses hash literals', function () { + equalsAst('{{(foo=bar)}}', '{{ Hash{foo=p%bar} }}'); + equalsAst('{{(foo=bar)}}', '{{ p%@hello }}', { + options: { + syntax: { + hash: (hash, loc, { yy }) => { + return yy.preparePath( + true, + false, + [{ part: yy.id('hello'), original: 'hello' }], + loc + ); + }, + }, + }, + }); + }); + + it('parses array literals', function () { + equalsAst('{{[foo bar]}}', '{{ Array[p%foo, p%bar] }}', { + options: { syntax: { square: 'node' } }, + }); + + equalsAst('{{[foo bar].baz}}', '{{ p%[Array[p%foo, p%bar]]/baz }}', { + options: { syntax: { square: 'node' } }, + }); + }); + + it('parses mustaches that are hash literals', function () { + equalsAst('{{foo=bar}}', '{{ Hash{foo=p%bar} }}'); + equalsAst('{{foo=bar}}', `{{ "HASH{foo=p%bar}" }}`, { + options: { + syntax: { + hash: (hash, loc) => { + return { + type: 'StringLiteral', + original: print(hash), + value: print(hash), + loc, + }; + }, + }, + }, + }); }); it('parses mustaches with parameters', function () { @@ -76,9 +124,10 @@ describe('parser', function () { }); it('parses mustaches with undefined and null paths', function () { - equalsAst('{{undefined}}', '{{ UNDEFINED [] }}'); - equalsAst('{{null}}', '{{ NULL [] }}'); + equalsAst('{{undefined}}', '{{ UNDEFINED }}'); + equalsAst('{{null}}', '{{ NULL }}'); }); + it('parses mustaches with undefined and null parameters', function () { equalsAst('{{foo undefined null}}', '{{ p%foo [UNDEFINED, NULL] }}'); }); @@ -88,43 +137,43 @@ describe('parser', function () { }); it('parses mustaches with hash arguments', function () { - equalsAst('{{foo bar=baz}}', '{{ p%foo [] HASH{bar=p%baz} }}'); - equalsAst('{{foo bar=1}}', '{{ p%foo [] HASH{bar=n%1} }}'); - equalsAst('{{foo bar=true}}', '{{ p%foo [] HASH{bar=b%true} }}'); - equalsAst('{{foo bar=false}}', '{{ p%foo [] HASH{bar=b%false} }}'); - equalsAst('{{foo bar=@baz}}', '{{ p%foo [] HASH{bar=p%@baz} }}'); + equalsAst('{{foo bar=baz}}', '{{ p%foo HASH{bar=p%baz} }}'); + equalsAst('{{foo bar=1}}', '{{ p%foo HASH{bar=n%1} }}'); + equalsAst('{{foo bar=true}}', '{{ p%foo HASH{bar=b%true} }}'); + equalsAst('{{foo bar=false}}', '{{ p%foo HASH{bar=b%false} }}'); + equalsAst('{{foo bar=@baz}}', '{{ p%foo HASH{bar=p%@baz} }}'); equalsAst( '{{foo bar=baz bat=bam}}', - '{{ p%foo [] HASH{bar=p%baz bat=p%bam} }}', + '{{ p%foo HASH{bar=p%baz bat=p%bam} }}' ); equalsAst( '{{foo bar=baz bat="bam"}}', - '{{ p%foo [] HASH{bar=p%baz bat="bam"} }}', + '{{ p%foo HASH{bar=p%baz bat="bam"} }}' ); - equalsAst("{{foo bat='bam'}}", '{{ p%foo [] HASH{bat="bam"} }}'); + equalsAst("{{foo bat='bam'}}", '{{ p%foo HASH{bat="bam"} }}'); equalsAst( '{{foo omg bar=baz bat="bam"}}', - '{{ p%foo [p%omg] HASH{bar=p%baz bat="bam"} }}', + '{{ p%foo [p%omg] HASH{bar=p%baz bat="bam"} }}' ); equalsAst( '{{foo omg bar=baz bat="bam" baz=1}}', - '{{ p%foo [p%omg] HASH{bar=p%baz bat="bam" baz=n%1} }}', + '{{ p%foo [p%omg] HASH{bar=p%baz bat="bam" baz=n%1} }}' ); equalsAst( '{{foo omg bar=baz bat="bam" baz=true}}', - '{{ p%foo [p%omg] HASH{bar=p%baz bat="bam" baz=b%true} }}', + '{{ p%foo [p%omg] HASH{bar=p%baz bat="bam" baz=b%true} }}' ); equalsAst( '{{foo omg bar=baz bat="bam" baz=false}}', - '{{ p%foo [p%omg] HASH{bar=p%baz bat="bam" baz=b%false} }}', + '{{ p%foo [p%omg] HASH{bar=p%baz bat="bam" baz=b%false} }}' ); }); it('parses contents followed by a mustache', function () { - equalsAst('foo bar {{baz}}', "CONTENT[ 'foo bar ' ]\n{{ p%baz [] }}"); + equalsAst('foo bar {{baz}}', "CONTENT[ 'foo bar ' ]\n{{ p%baz }}"); }); it('parses a partial', function () { @@ -144,21 +193,21 @@ describe('parser', function () { it('parses a partial with context and hash', function () { equalsAst( '{{> foo bar bat=baz}}', - '{{> PARTIAL:foo p%bar HASH{bat=p%baz} }}', + '{{> PARTIAL:foo p%bar HASH{bat=p%baz} }}' ); }); it('parses a partial with a complex name', function () { equalsAst( '{{> shared/partial?.bar}}', - '{{> PARTIAL:shared/partial?.bar }}', + '{{> PARTIAL:shared/partial?.bar }}' ); }); it('parsers partial blocks', function () { equalsAst( '{{#> foo}}bar{{/foo}}', - "{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ 'bar' ]\n }}", + "{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ 'bar' ]\n }}" ); }); it('should handle parser block mismatch', function () { @@ -167,13 +216,13 @@ describe('parser', function () { astFor('{{#> goodbyes}}{{/hellos}}'); }, Error, - /goodbyes doesn't match hellos/, + /goodbyes doesn't match hellos/ ); }); it('parsers partial blocks with arguments', function () { equalsAst( '{{#> foo context hash=value}}bar{{/foo}}', - "{{> PARTIAL BLOCK:foo p%context HASH{hash=p%value} PROGRAM:\n CONTENT[ 'bar' ]\n }}", + "{{> PARTIAL BLOCK:foo p%context HASH{hash=p%value} PROGRAM:\n CONTENT[ 'bar' ]\n }}" ); }); @@ -184,81 +233,78 @@ describe('parser', function () { it('parses a multi-line comment', function () { equalsAst( '{{!\nthis is a multi-line comment\n}}', - "{{! '\nthis is a multi-line comment\n' }}", + "{{! '\nthis is a multi-line comment\n' }}" ); }); it('parses an inverse section', function () { equalsAst( '{{#foo}} bar {{^}} baz {{/foo}}', - "BLOCK:\n p%foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]", + "BLOCK:\n p%foo\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]" ); }); it('parses an inverse (else-style) section', function () { equalsAst( '{{#foo}} bar {{else}} baz {{/foo}}', - "BLOCK:\n p%foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]", + "BLOCK:\n p%foo\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]" ); }); it('parses multiple inverse sections', function () { equalsAst( '{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}', - "BLOCK:\n p%foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n p%if [p%bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]", + "BLOCK:\n p%foo\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n p%if [p%bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]" ); }); it('parses empty blocks', function () { - equalsAst('{{#foo}}{{/foo}}', 'BLOCK:\n p%foo []\n PROGRAM:'); + equalsAst('{{#foo}}{{/foo}}', 'BLOCK:\n p%foo\n PROGRAM:'); }); it('parses empty blocks with empty inverse section', function () { - equalsAst( - '{{#foo}}{{^}}{{/foo}}', - 'BLOCK:\n p%foo []\n PROGRAM:\n {{^}}', - ); + equalsAst('{{#foo}}{{^}}{{/foo}}', 'BLOCK:\n p%foo\n PROGRAM:\n {{^}}'); }); it('parses empty blocks with empty inverse (else-style) section', function () { equalsAst( '{{#foo}}{{else}}{{/foo}}', - 'BLOCK:\n p%foo []\n PROGRAM:\n {{^}}', + 'BLOCK:\n p%foo\n PROGRAM:\n {{^}}' ); }); it('parses non-empty blocks with empty inverse section', function () { equalsAst( '{{#foo}} bar {{^}}{{/foo}}', - "BLOCK:\n p%foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}", + "BLOCK:\n p%foo\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}" ); }); it('parses non-empty blocks with empty inverse (else-style) section', function () { equalsAst( '{{#foo}} bar {{else}}{{/foo}}', - "BLOCK:\n p%foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}", + "BLOCK:\n p%foo\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}" ); }); it('parses empty blocks with non-empty inverse section', function () { equalsAst( '{{#foo}}{{^}} bar {{/foo}}', - "BLOCK:\n p%foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]", + "BLOCK:\n p%foo\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]" ); }); it('parses empty blocks with non-empty inverse (else-style) section', function () { equalsAst( '{{#foo}}{{else}} bar {{/foo}}', - "BLOCK:\n p%foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]", + "BLOCK:\n p%foo\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]" ); }); it('parses a standalone inverse section', function () { equalsAst( '{{^foo}}bar{{/foo}}', - "BLOCK:\n p%foo []\n {{^}}\n CONTENT[ 'bar' ]", + "BLOCK:\n p%foo\n {{^}}\n CONTENT[ 'bar' ]" ); }); @@ -271,74 +317,71 @@ describe('parser', function () { it('parses block with block params', function () { equalsAst( '{{#foo as |bar baz|}}content{{/foo}}', - "BLOCK:\n p%foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]", + "BLOCK:\n p%foo\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]" ); }); it('parses mustaches with sub-expressions as the callable', function () { - equalsAst('{{(my-helper foo)}}', '{{ p%my-helper [p%foo] [] }}'); + equalsAst('{{(my-helper foo)}}', '{{ (p%my-helper [p%foo]) }}'); }); it('parses mustaches with sub-expressions as the callable (with args)', function () { - equalsAst('{{(my-helper foo) bar}}', '{{ p%my-helper [p%foo] [p%bar] }}'); + equalsAst('{{(my-helper foo) bar}}', '{{ (p%my-helper [p%foo]) [p%bar] }}'); }); it('parses sub-expressions with a sub-expression as the callable', function () { - equalsAst('{{((my-helper foo))}}', '{{ p%my-helper [p%foo] [] [] }}'); + equalsAst('{{((my-helper foo))}}', '{{ ((p%my-helper [p%foo])) }}'); }); it('parses sub-expressions with a sub-expression as the callable (with args)', function () { equalsAst( '{{((my-helper foo) bar)}}', - '{{ p%my-helper [p%foo] [p%bar] [] }}', + '{{ ((p%my-helper [p%foo]) [p%bar]) }}' ); }); it('parses arguments with a sub-expression as the callable (with args)', function () { equalsAst( '{{my-helper ((foo) bar) baz=((foo bar))}}', - '{{ p%my-helper [p%foo [] [p%bar]] HASH{baz=p%foo [p%bar] []} }}', + '{{ p%my-helper [((p%foo) [p%bar])] HASH{baz=((p%foo [p%bar]))} }}' ); }); it('parses paths with sub-expressions as the root', function () { - equalsAst( - '{{(my-helper foo).bar}}', - '{{ p%[p%my-helper [p%foo]]/bar [] }}', - ); + equalsAst('{{(my-helper foo).bar}}', '{{ p%[(p%my-helper [p%foo])]/bar }}'); }); it('parses paths with sub-expressions as the root as a callable', function () { equalsAst( '{{((my-helper foo).bar baz)}}', - '{{ p%[p%my-helper [p%foo]]/bar [p%baz] [] }}', + '{{ (p%[(p%my-helper [p%foo])]/bar [p%baz]) }}' ); }); it('parses paths with sub-expressions as the root as an argument', function () { equalsAst( '{{(foo (my-helper bar).baz)}}', - '{{ p%foo [p%[p%my-helper [p%bar]]/baz] [] }}', + '{{ (p%foo [p%[(p%my-helper [p%bar])]/baz]) }}' ); }); it('parses paths with sub-expressions as the root as a named argument', function () { equalsAst( '{{(foo bar=(my-helper baz).qux)}}', - '{{ p%foo [] HASH{bar=p%[p%my-helper [p%baz]]/qux} [] }}', + '{{ (p%foo HASH{bar=p%[(p%my-helper [p%baz])]/qux}) }}' ); }); it('parses inverse block with block params', function () { equalsAst( '{{^foo as |bar baz|}}content{{/foo}}', - "BLOCK:\n p%foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]", + "BLOCK:\n p%foo\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]" ); }); it('parses chained inverse block with block params', function () { equalsAst( '{{#foo}}{{else foo as |bar baz|}}content{{/foo}}', - "BLOCK:\n p%foo []\n PROGRAM:\n {{^}}\n BLOCK:\n p%foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]", + "BLOCK:\n p%foo\n PROGRAM:\n {{^}}\n BLOCK:\n p%foo\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]" ); }); it("raises if there's a Parse error", function () { @@ -347,28 +390,28 @@ describe('parser', function () { astFor('foo{{^}}bar'); }, Error, - /Parse error on line 1/, + /Parse error on line 1/ ); shouldThrow( function () { astFor('{{foo}'); }, Error, - /Parse error on line 1/, + /Parse error on line 1/ ); shouldThrow( function () { astFor('{{foo &}}'); }, Error, - /Parse error on line 1/, + /Parse error on line 1/ ); shouldThrow( function () { astFor('{{#goodbyes}}{{/hellos}}'); }, Error, - /goodbyes doesn't match hellos/, + /goodbyes doesn't match hellos/ ); shouldThrow( @@ -376,7 +419,7 @@ describe('parser', function () { astFor('{{{{goodbyes}}}} {{{{/hellos}}}}'); }, Error, - /goodbyes doesn't match hellos/, + /goodbyes doesn't match hellos/ ); }); @@ -386,21 +429,21 @@ describe('parser', function () { astFor('{{foo/../bar}}'); }, Error, - /Invalid path: foo\/\.\. - 1:2/, + /Invalid path: foo\/\.\. - 1:2/ ); shouldThrow( function () { astFor('{{foo/./bar}}'); }, Error, - /Invalid path: foo\/\. - 1:2/, + /Invalid path: foo\/\. - 1:2/ ); shouldThrow( function () { astFor('{{foo/this/bar}}'); }, Error, - /Invalid path: foo\/this - 1:2/, + /Invalid path: foo\/this - 1:2/ ); }); @@ -410,14 +453,14 @@ describe('parser', function () { astFor('hello\nmy\n{{foo}'); }, Error, - /Parse error on line 3/, + /Parse error on line 3/ ); shouldThrow( function () { astFor('hello\n\nmy\n\n{{foo}'); }, Error, - /Parse error on line 5/, + /Parse error on line 5/ ); }); @@ -427,7 +470,7 @@ describe('parser', function () { astFor('\n\nhello\n\nmy\n\n{{foo}'); }, Error, - /Parse error on line 7/, + /Parse error on line 7/ ); }); @@ -438,20 +481,17 @@ describe('parser', function () { type: 'Program', body: [{ type: 'ContentStatement', value: 'Hello' }], }), - "CONTENT[ 'Hello' ]\n", + "CONTENT[ 'Hello' ]\n" ); }); }); describe('directives', function () { it('should parse block directives', function () { - equalsAst( - '{{#* foo}}{{/foo}}', - 'DIRECTIVE BLOCK:\n p%foo []\n PROGRAM:', - ); + equalsAst('{{#* foo}}{{/foo}}', 'DIRECTIVE BLOCK:\n p%foo\n PROGRAM:'); }); it('should parse directives', function () { - equalsAst('{{* foo}}', '{{ DIRECTIVE p%foo [] }}'); + equalsAst('{{* foo}}', '{{ DIRECTIVE p%foo }}'); }); it('should fail if directives have inverse', function () { shouldThrow( @@ -459,7 +499,7 @@ describe('parser', function () { astFor('{{#* foo}}{{^}}{{/foo}}'); }, Error, - /Unexpected inverse/, + /Unexpected inverse/ ); }); }); @@ -472,7 +512,7 @@ describe('parser', function () { ' {{else}} {{baz}}\n' + '\n' + ' {{/if}}\n' + - ' ', + ' ' ); // We really need a deep equals but for now this should be stable... @@ -481,21 +521,21 @@ describe('parser', function () { JSON.stringify({ start: { line: 1, column: 0 }, end: { line: 7, column: 4 }, - }), + }) ); equals( JSON.stringify(p.body[1].program.loc), JSON.stringify({ start: { line: 2, column: 13 }, end: { line: 4, column: 7 }, - }), + }) ); equals( JSON.stringify(p.body[1].inverse.loc), JSON.stringify({ start: { line: 4, column: 15 }, end: { line: 6, column: 5 }, - }), + }) ); }); }); diff --git a/spec/utils.js b/spec/utils.js index ad09270..503eff0 100644 --- a/spec/utils.js +++ b/spec/utils.js @@ -1,4 +1,4 @@ -import { parse, print } from '../dist/esm'; +import { parse, print } from '../dist/esm/index.js'; let AssertError; if (Error.captureStackTrace) { @@ -22,23 +22,70 @@ if (Error.captureStackTrace) { */ export function equals(actual, expected, msg) { if (actual !== expected) { - throw new AssertError( - `\n Actual: ${actual} Expected: ${expected}` + (msg ? `\n${msg}` : ''), + const error = new AssertError( + `\n Actual: ${actual} Expected: ${expected}` + + (msg ? `\n${msg}` : ''), equals ); + error.expected = expected; + error.actual = actual; + throw error; } } -export function equalsAst(source, expected, msg) { - const ast = astFor(source); +export function equalsAst(source, expected, options) { + const msg = typeof options === 'string' ? options : options?.msg; + const parserOptions = + typeof options === 'string' ? undefined : options?.options; + const ast = astFor(source, parserOptions); + const padding = ` `.repeat(8); if (ast !== `${expected}\n`) { - throw new AssertError( - `\n Source: ${source}\n\n Actual: ${ast} Expected: ${expected}\n` + (msg ? `\n${msg}` : ''), - equals + let sourceMsg = `${padding}Source: ${source}`; + if (parserOptions) { + let formattedOptions = printOptions(parserOptions) + .split('\n') + .join(`\n${padding}`); + + sourceMsg += `\n${padding}Options: ${formattedOptions}`; + } + const error = new AssertError( + `\n${sourceMsg}${msg ? `\n${msg}` : ''}\n`, + equalsAst ); + error.expected = expected; + error.actual = ast; + throw error; + } +} + +function printOptions(options) { + if (!options) { + return ''; + } + + let outOptions = {}; + + if (options.srcName) { + outOptions.srcName = options.srcName; + } + if (options.syntax) { + outOptions.syntax = {}; + + if (options.syntax.hash) { + outOptions.syntax.hash = `{function ${ + options.syntax.hash.name ?? 'anonymous' + }}`; + } + if (options.syntax.square) { + outOptions.syntax.square = `{function ${ + options.syntax.square.name ?? 'anonymous' + }}`; + } } + + return JSON.stringify(outOptions, null, 2); } /** @@ -60,11 +107,11 @@ export function shouldThrow(callback, type, msg) { ) { throw new AssertError( 'Throw mismatch: Expected ' + - caught.message + - ' to match ' + - msg + - '\n\n' + - caught.stack, + caught.message + + ' to match ' + + msg + + '\n\n' + + caught.stack, shouldThrow ); } @@ -73,7 +120,7 @@ export function shouldThrow(callback, type, msg) { throw new AssertError('It failed to throw', shouldThrow); } } - function astFor(template) { - let ast = parse(template); - return print(ast); - } +function astFor(template, options = {}) { + let ast = parse(template, options); + return print(ast); +} diff --git a/spec/visitor.js b/spec/visitor.js index 77a1dc6..4135466 100644 --- a/spec/visitor.js +++ b/spec/visitor.js @@ -1,8 +1,8 @@ -import { Visitor, parse, print, Exception } from '../dist/esm'; -import { equals, shouldThrow } from './utils'; +import { Visitor, parse, print, Exception } from '../dist/esm/index.js'; +import { equals, shouldThrow } from './utils.js'; -describe('Visitor', function() { - it('should provide coverage', function() { +describe('Visitor', function () { + it('should provide coverage', function () { // Simply run the thing and make sure it does not fail and that all of the // stub methods are executed let visitor = new Visitor(); @@ -16,16 +16,16 @@ describe('Visitor', function() { visitor.accept(parse('{{* bar }}')); }); - it('should traverse to stubs', function() { + it('should traverse to stubs', function () { let visitor = new Visitor(); - visitor.StringLiteral = function(string) { + visitor.StringLiteral = function (string) { equals(string.value, '2'); }; - visitor.NumberLiteral = function(number) { + visitor.NumberLiteral = function (number) { equals(number.value, 1); }; - visitor.BooleanLiteral = function(bool) { + visitor.BooleanLiteral = function (bool) { equals(bool.value, true); equals(this.parents.length, 3); @@ -33,13 +33,13 @@ describe('Visitor', function() { equals(this.parents[1].type, 'BlockStatement'); equals(this.parents[2].type, 'Program'); }; - visitor.PathExpression = function(id) { + visitor.PathExpression = function (id) { equals(/(foo\.)?bar$/.test(id.original), true); }; - visitor.ContentStatement = function(content) { + visitor.ContentStatement = function (content) { equals(content.value, ' '); }; - visitor.CommentStatement = function(comment) { + visitor.CommentStatement = function (comment) { equals(comment.value, 'comment'); }; @@ -50,53 +50,47 @@ describe('Visitor', function() { ); }); - describe('mutating', function() { - describe('fields', function() { - it('should replace value', function() { + describe('mutating', function () { + describe('fields', function () { + it('should replace value', function () { let visitor = new Visitor(); visitor.mutating = true; - visitor.StringLiteral = function(string) { + visitor.StringLiteral = function (string) { return { type: 'NumberLiteral', value: 42, loc: string.loc }; }; let ast = parse('{{foo foo="foo"}}'); visitor.accept(ast); - equals( - print(ast), - '{{ p%foo [] HASH{foo=n%42} }}\n' - ); + equals(print(ast), '{{ p%foo HASH{foo=n%42} }}\n'); }); - it('should treat undefined resonse as identity', function() { + it('should treat undefined resonse as identity', function () { let visitor = new Visitor(); visitor.mutating = true; let ast = parse('{{foo foo=42}}'); visitor.accept(ast); - equals( - print(ast), - '{{ p%foo [] HASH{foo=n%42} }}\n' - ); + equals(print(ast), '{{ p%foo HASH{foo=n%42} }}\n'); }); - it('should remove false responses', function() { + it('should remove false responses', function () { let visitor = new Visitor(); visitor.mutating = true; - visitor.Hash = function() { + visitor.Hash = function () { return false; }; let ast = parse('{{foo foo=42}}'); visitor.accept(ast); - equals(print(ast), '{{ p%foo [] }}\n'); + equals(print(ast), '{{ p%foo }}\n'); }); - it('should throw when removing required values', function() { + it('should throw when removing required values', function () { shouldThrow( - function() { + function () { let visitor = new Visitor(); visitor.mutating = true; - visitor.PathExpression = function() { + visitor.PathExpression = function () { return false; }; @@ -107,13 +101,13 @@ describe('Visitor', function() { 'MustacheStatement requires path' ); }); - it('should throw when returning non-node responses', function() { + it('should throw when returning non-node responses', function () { shouldThrow( - function() { + function () { let visitor = new Visitor(); visitor.mutating = true; - visitor.PathExpression = function() { + visitor.PathExpression = function () { return {}; }; @@ -125,12 +119,12 @@ describe('Visitor', function() { ); }); }); - describe('arrays', function() { - it('should replace value', function() { + describe('arrays', function () { + it('should replace value', function () { let visitor = new Visitor(); visitor.mutating = true; - visitor.StringLiteral = function(string) { + visitor.StringLiteral = function (string) { return { type: 'NumberLiteral', value: 42, loc: string.locInfo }; }; @@ -138,7 +132,7 @@ describe('Visitor', function() { visitor.accept(ast); equals(print(ast), '{{ p%foo [n%42] }}\n'); }); - it('should treat undefined resonse as identity', function() { + it('should treat undefined resonse as identity', function () { let visitor = new Visitor(); visitor.mutating = true; @@ -146,17 +140,17 @@ describe('Visitor', function() { visitor.accept(ast); equals(print(ast), '{{ p%foo [n%42] }}\n'); }); - it('should remove false responses', function() { + it('should remove false responses', function () { let visitor = new Visitor(); visitor.mutating = true; - visitor.NumberLiteral = function() { + visitor.NumberLiteral = function () { return false; }; let ast = parse('{{foo 42}}'); visitor.accept(ast); - equals(print(ast), '{{ p%foo [] }}\n'); + equals(print(ast), '{{ p%foo }}\n'); }); }); }); diff --git a/src/handlebars.l b/src/handlebars.l index 39f1fda..c23971f 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -1,5 +1,5 @@ -%x mu emu com raw +%x mu emu com raw escl %{ @@ -12,8 +12,8 @@ function strip(start, end) { LEFT_STRIP "~" RIGHT_STRIP "~" -LOOKAHEAD [=~}\s\/.)|] -LITERAL_LOOKAHEAD [~}\s)] +LOOKAHEAD [=~}\s\/.)\]|] +LITERAL_LOOKAHEAD [~}\s)\]] /* ID is the inverse of control characters. @@ -73,6 +73,18 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} "(" return 'OPEN_SEXPR'; ")" return 'CLOSE_SEXPR'; +"[" { + if (yy.syntax.square === 'string') { + this.unput(yytext); + // escaped literal + this.begin('escl'); + } else { + return 'OPEN_ARRAY'; + } +} +"]" return 'CLOSE_ARRAY'; + + "{{{{" { return 'OPEN_RAW_BLOCK'; } "}}}}" { this.popState(); @@ -121,7 +133,12 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} {ID} return 'ID'; -'['('\\]'|[^\]])*']' yytext = yytext.replace(/\\([\\\]])/g,'$1'); return 'ID'; +'['('\\]'|[^\]])*']' { + yytext = yytext.replace(/\\([\\\]])/g,'$1'); + this.popState(); + return 'ID'; +} + . return 'INVALID'; <> return 'EOF'; diff --git a/src/handlebars.yy b/src/handlebars.yy index 278dc73..ed26c2c 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -85,7 +85,8 @@ closeBlock mustache // Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node. // This also allows for handler unification as all mustache node instances can utilize the same handler - : OPEN expr expr* hash? CLOSE -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$) + : OPEN hash CLOSE -> yy.prepareMustache(yy.syntax.hash($2, yy.locInfo(@$), { yy, syntax: 'expr' }), [], undefined, $1, yy.stripFlags($1, $3), @$) + | OPEN expr expr* hash? CLOSE -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$) | OPEN_UNESCAPED expr expr* hash? CLOSE_UNESCAPED -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$) ; @@ -111,11 +112,18 @@ openPartialBlock expr : helperName -> $1 + | exprHead -> $1 + ; + +exprHead + : arrayLiteral -> $1 | sexpr -> $1 ; + sexpr - : OPEN_SEXPR expr expr* hash? CLOSE_SEXPR { + : OPEN_SEXPR hash CLOSE_SEXPR -> yy.syntax.hash($2, yy.locInfo(@$), { yy, syntax: 'expr' }) + | OPEN_SEXPR expr expr* hash? CLOSE_SEXPR { $$ = { type: 'SubExpression', path: $2, @@ -133,6 +141,10 @@ hashSegment : ID EQUALS expr -> {type: 'HashPair', key: yy.id($1), value: $3, loc: yy.locInfo(@$)} ; +arrayLiteral + : OPEN_ARRAY expr* CLOSE_ARRAY -> yy.syntax.square($2, yy.locInfo(@$), { yy, syntax: 'expr' }) + ; + blockParams : OPEN_BLOCK_PARAMS ID+ CLOSE_BLOCK_PARAMS -> yy.id($2) ; @@ -157,7 +169,7 @@ sep ; path - : sexpr sep pathSegments -> yy.preparePath(false, $1, $3, @$) + : exprHead sep pathSegments -> yy.preparePath(false, $1, $3, @$) | pathSegments -> yy.preparePath(false, false, $1, @$) ; diff --git a/tsconfig.json b/tsconfig.json index 715c1c6..fb59719 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,18 +8,24 @@ "baseUrl": "lib", "rootDir": "lib", "esModuleInterop": true, - "moduleResolution": "node", - + "moduleResolution": "bundler", + "verbatimModuleSyntax": true, // Enhance Strictness "strict": true, "suppressImplicitAnyIndexErrors": false, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, - "newLine": "LF", "allowJs": true }, - "include": ["lib/**/*.js"], - "exclude": ["dist", "node_modules", ".vscode"] -} + "include": [ + "lib/**/*.js", + "lib/**/*.d.ts" + ], + "exclude": [ + "dist", + "node_modules", + ".vscode" + ] +} \ No newline at end of file