parse await-then-catch

pull/952/head
Rich Harris 7 years ago
parent 14b27b71e1
commit 908fe2ab7a

@ -3,6 +3,7 @@ import fragment from './state/fragment';
import { whitespace } from '../utils/patterns';
import { trimStart, trimEnd } from '../utils/trim';
import getCodeFrame from '../utils/getCodeFrame';
import reservedNames from '../utils/reservedNames';
import hash from './utils/hash';
import { Node, Parsed } from '../interfaces';
import CompileError from '../utils/CompileError';
@ -139,6 +140,17 @@ export class Parser {
return match[0];
}
readIdentifier() {
const start = this.index;
const identifier = this.read(/[a-zA-Z_$][a-zA-Z0-9_$]*/);
if (reservedNames.has(identifier)) {
this.error(`'${identifier}' is a reserved word in JavaScript and cannot be used here`, start);
}
return identifier;
}
readUntil(pattern: RegExp) {
if (this.index >= this.template.length)
this.error('Unexpected end of input');

@ -5,8 +5,6 @@ import reservedNames from '../../utils/reservedNames';
import { Parser } from '../index';
import { Node } from '../../interfaces';
const validIdentifier = /[a-zA-Z_$][a-zA-Z0-9_$]*/;
function trimWhitespace(block: Node, trimBefore: boolean, trimAfter: boolean) {
const firstChild = block.children[0];
const lastChild = block.children[block.children.length - 1];
@ -41,16 +39,20 @@ export default function mustache(parser: Parser) {
let block = parser.current();
let expected;
if (block.type === 'ElseBlock') {
if (block.type === 'ElseBlock' || block.type === 'ThenBlock' || block.type === 'CatchBlock') {
block.end = start;
parser.stack.pop();
block = parser.current();
expected = 'await';
}
if (block.type === 'IfBlock') {
expected = 'if';
} else if (block.type === 'EachBlock') {
expected = 'each';
} else if (block.type === 'AwaitBlock') {
expected = 'await';
} else {
parser.error(`Unexpected block closing tag`);
}
@ -131,6 +133,50 @@ export default function mustache(parser: Parser) {
};
parser.stack.push(block.else);
} else if (parser.eat('then')) {
// {{then}} is valid by itself — we need to check that a)
// we're inside an await block, and b) there's an expression
const awaitBlock = parser.current();
if (awaitBlock.type === 'AwaitBlock') {
parser.requireWhitespace();
awaitBlock.value = parser.readIdentifier();
parser.allowWhitespace();
parser.eat('}}', true);
const thenBlock: Node = {
start,
end: null,
type: 'ThenBlock',
children: []
};
awaitBlock.then = thenBlock;
parser.stack.push(thenBlock);
}
} else if (parser.eat('catch')) {
const thenBlock = parser.current();
if (thenBlock.type === 'ThenBlock') {
thenBlock.end = start;
parser.stack.pop();
const awaitBlock = parser.current();
parser.requireWhitespace();
awaitBlock.error = parser.readIdentifier();
parser.allowWhitespace();
parser.eat('}}', true);
const catchBlock: Node = {
start,
end: null,
type: 'CatchBlock',
children: []
};
awaitBlock.catch = catchBlock;
parser.stack.push(catchBlock);
}
} else if (parser.eat('#')) {
// {{#if foo}} or {{#each foo}}
let type;
@ -139,8 +185,10 @@ export default function mustache(parser: Parser) {
type = 'IfBlock';
} else if (parser.eat('each')) {
type = 'EachBlock';
} else if (parser.eat('await')) {
type = 'AwaitBlock';
} else {
parser.error(`Expected if or each`);
parser.error(`Expected if, each or await`);
}
parser.requireWhitespace();
@ -170,13 +218,8 @@ export default function mustache(parser: Parser) {
do {
parser.allowWhitespace();
const start = parser.index;
const destructuredContext = parser.read(validIdentifier);
const destructuredContext = parser.readIdentifier();
if (!destructuredContext) parser.error(`Expected name`);
if (reservedNames.has(destructuredContext)) {
parser.error(`'${destructuredContext}' is a reserved word in JavaScript and cannot be used here`, start);
}
block.destructuredContexts.push(destructuredContext);
parser.allowWhitespace();
@ -188,12 +231,7 @@ export default function mustache(parser: Parser) {
parser.allowWhitespace();
parser.eat(']', true);
} else {
const start = parser.index;
block.context = parser.read(validIdentifier);
if (reservedNames.has(block.context)) {
parser.error(`'${block.context}' is a reserved word in JavaScript and cannot be used here`, start);
}
block.context = parser.readIdentifier();
if (!block.context) parser.error(`Expected name`);
}
@ -201,13 +239,13 @@ export default function mustache(parser: Parser) {
if (parser.eat(',')) {
parser.allowWhitespace();
block.index = parser.read(validIdentifier);
block.index = parser.readIdentifier();
if (!block.index) parser.error(`Expected name`);
parser.allowWhitespace();
}
if (parser.eat('@')) {
block.key = parser.read(validIdentifier);
block.key = parser.readIdentifier();
if (!block.key) parser.error(`Expected name`);
parser.allowWhitespace();
}

@ -41,7 +41,8 @@ describe('parse', () => {
assert.deepEqual(err.loc, expected.loc);
assert.equal(err.pos, expected.pos);
} catch (err2) {
throw err2.code === 'MODULE_NOT_FOUND' ? err : err2;
const e = err2.code === 'MODULE_NOT_FOUND' ? err : err2;
throw e;
}
}
});

@ -0,0 +1,7 @@
{{#await thePromise}}
<p>loading...</p>
{{then theValue}}
<p>the value is {{theValue}}</p>
{{catch theError}}
<p>oh no! {{theError.message}}</p>
{{/await}}

@ -0,0 +1,144 @@
{
"hash": 1040536517,
"html": {
"start": 0,
"end": 158,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 158,
"type": "AwaitBlock",
"expression": {
"type": "Identifier",
"start": 9,
"end": 19,
"name": "thePromise"
},
"children": [
{
"start": 23,
"end": 40,
"type": "Element",
"name": "p",
"attributes": [],
"children": [
{
"start": 26,
"end": 36,
"type": "Text",
"data": "loading..."
}
]
}
],
"value": "theValue",
"then": {
"start": 41,
"end": 93,
"type": "ThenBlock",
"children": [
{
"start": 58,
"end": 60,
"type": "Text",
"data": "\n\t"
},
{
"start": 60,
"end": 92,
"type": "Element",
"name": "p",
"attributes": [],
"children": [
{
"start": 63,
"end": 76,
"type": "Text",
"data": "the value is "
},
{
"start": 76,
"end": 88,
"type": "MustacheTag",
"expression": {
"type": "Identifier",
"start": 78,
"end": 86,
"name": "theValue"
}
}
]
},
{
"start": 92,
"end": 93,
"type": "Text",
"data": "\n"
}
]
},
"error": "theError",
"catch": {
"start": 93,
"end": 148,
"type": "CatchBlock",
"children": [
{
"start": 111,
"end": 113,
"type": "Text",
"data": "\n\t"
},
{
"start": 113,
"end": 147,
"type": "Element",
"name": "p",
"attributes": [],
"children": [
{
"start": 116,
"end": 123,
"type": "Text",
"data": "oh no! "
},
{
"start": 123,
"end": 143,
"type": "MustacheTag",
"expression": {
"type": "MemberExpression",
"start": 125,
"end": 141,
"object": {
"type": "Identifier",
"start": 125,
"end": 133,
"name": "theError"
},
"property": {
"type": "Identifier",
"start": 134,
"end": 141,
"name": "message"
},
"computed": false
}
}
]
},
{
"start": 147,
"end": 148,
"type": "Text",
"data": "\n"
}
]
}
}
]
},
"css": null,
"js": null
}
Loading…
Cancel
Save