diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index f0a1a00..0000000 --- a/.eslintrc +++ /dev/null @@ -1,24 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module" - }, - "env": { - "es6": true, - "node": true - }, - "rules": { - "brace-style": ["error", "1tbs"], - "eqeqeq": ["error", "always"], - "indent": ["error", 4, { - "CallExpression": {"arguments": 1}, - "MemberExpression": "off", - "SwitchCase": 1 - }], - "quotes": ["error", "double"], - "semi": ["error", "always"], - "space-infix-ops": ["error"], - "no-tabs": "error", - "no-undef": "error" - } -} diff --git a/.gitattributes b/.gitattributes index 4bf2ed7..1e28c29 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1 @@ -*.ftl eol=lf -test/fixtures/crlf.ftl eol=crlf -test/fixtures/cr.ftl eol=cr +**.ftl eol=lf diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..ac8d76d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "bracketSpacing": false, + "trailingComma": "es5", + "printWidth": 100, + "tabWidth": 4 +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e72c0b3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "editor.formatOnSave": true, + "[javascript]": { + "editor.formatOnSave": true + }, + "[typescript]": { + "editor.formatOnSave": true + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..30abe81 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": [ + "$tsc-watch" + ], + "isBackground": true + } + ] +} diff --git a/README.md b/README.md index 590d7e2..15d3380 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ validate your work: npm test # Test the parser against JSON AST fixtures. npm run lint # Lint the parser code. - npm run generate:ebnf # Generate the EBNF from syntax/grammar.mjs. + npm run generate:ebnf # Generate the EBNF from syntax/grammar.js. npm run generate:fixtures # Generate test fixtures (FTL → JSON AST). npm run build:guide # Build the HTML version of the Guide. diff --git a/bin/ebnf.mjs b/bin/ebnf.js similarity index 78% rename from bin/ebnf.mjs rename to bin/ebnf.js index b755b70..355f9a3 100644 --- a/bin/ebnf.mjs +++ b/bin/ebnf.js @@ -1,7 +1,7 @@ import fs from "fs"; import readline from "readline"; import parse_args from "minimist"; -import ebnf from "../lib/ebnf.mjs"; +import ebnf from "../lib/ebnf.js"; const argv = parse_args(process.argv.slice(2), { boolean: ["help"], @@ -26,14 +26,14 @@ if (file_path === "-") { function exit_help(exit_code) { console.log(` - Usage: node --experimental-modules ebnf.mjs [OPTIONS] + Usage: node -r esm ebnf.js [OPTIONS] When FILE is "-", read text from stdin. Examples: - node --experimental-modules ebnf.mjs path/to/grammar.mjs - cat path/to/grammar.mjs | node --experimental-modules ebnf.mjs - + node -r esm ebnf.js path/to/grammar.js + cat path/to/grammar.js | node -r esm ebnf.js - Options: @@ -52,8 +52,7 @@ function from_stdin() { const lines = []; rl.on("line", line => lines.push(line)); - rl.on("close", () => - print_ebnf(lines.join("\n") + "\n")); + rl.on("close", () => print_ebnf(lines.join("\n") + "\n")); } function from_file(file_path) { diff --git a/bin/format.ts b/bin/format.ts new file mode 100644 index 0000000..0bcc446 --- /dev/null +++ b/bin/format.ts @@ -0,0 +1,55 @@ +import parseArgs from "minimist"; +import {fromStdin, fromFile} from "../lib/input"; +import {formatGroups, formatResource, parseString} from "../lib/tools"; + +const argv = parseArgs(process.argv.slice(2), { + boolean: ["help", "group"], + alias: { + help: "h", + group: "g", + }, +}); + +if (argv.help) { + exitHelp(0); +} + +const [filePath] = argv._; + +if (filePath === "-") { + fromStdin(print); +} else if (filePath) { + fromFile(filePath, print); +} else { + exitHelp(1); +} + +function exitHelp(exitCode: number) { + console.log(` + Usage: node format.js [OPTIONS] + + When FILE is "-", read text from stdin. + + Examples: + + node format.js path/to/file.ftl + cat path/to/file.ftl | node format.js - + + Options: + + -h, --help Display help and quit. + -g, --group Treat each group as a separate resource. +`); + process.exit(exitCode); +} + +function print(source: string) { + let resource = parseString(source); + if (argv.group) { + let results = formatGroups(resource); + console.log(JSON.stringify(results, null, 4)); + } else { + let results = formatResource(resource, new Map()); + console.log(JSON.stringify(results, null, 4)); + } +} diff --git a/bin/parse.mjs b/bin/parse.js similarity index 76% rename from bin/parse.mjs rename to bin/parse.js index e71b3a1..88f7e89 100644 --- a/bin/parse.mjs +++ b/bin/parse.js @@ -1,7 +1,7 @@ import fs from "fs"; import readline from "readline"; import parse_args from "minimist"; -import {Resource} from "../syntax/grammar.mjs"; +import {Resource} from "../syntax/grammar.js"; const argv = parse_args(process.argv.slice(2), { boolean: ["help"], @@ -26,14 +26,14 @@ if (file_path === "-") { function exit_help(exit_code) { console.log(` - Usage: node --experimental-modules parse.mjs [OPTIONS] + Usage: node -r esm parse.js [OPTIONS] When FILE is "-", read text from stdin. Examples: - node --experimental-modules parse.mjs path/to/file.ftl - cat path/to/file.ftl | node --experimental-modules parse.mjs - + node -r esm parse.js path/to/file.ftl + cat path/to/file.ftl | node -r esm parse.js - Options: @@ -52,8 +52,7 @@ function parse_stdin() { const lines = []; rl.on("line", line => lines.push(line)); - rl.on("close", () => - parse(lines.join("\n") + "\n")); + rl.on("close", () => parse(lines.join("\n") + "\n")); } function parse_file(file_path) { @@ -66,9 +65,9 @@ function parse_file(file_path) { }); } - function parse(ftl) { Resource.run(ftl).fold( ast => console.log(JSON.stringify(ast, null, 4)), - err => console.error(err)); + err => console.error(err) + ); } diff --git a/lib/README.md b/lib/README.md index 0872778..0ce9faf 100644 --- a/lib/README.md +++ b/lib/README.md @@ -1,19 +1,19 @@ # Fluent Specification Support Code -This directory contains support code for the parser-combinator underlying the syntax code, as well as the code to transform `grammar.mjs` to `fluent.ebnf`. The combination allows for a succinct formulation of the syntax in `grammar.mjs` and a readable representation of that in `fluent.ebnf`. +This directory contains support code for the parser-combinator underlying the syntax code, as well as the code to transform `grammar.js` to `fluent.ebnf`. The combination allows for a succinct formulation of the syntax in `grammar.js` and a readable representation of that in `fluent.ebnf`. ## Parser-Combinator -**`parser.mjs`** is the base parser class, **`stream.mjs`** holds a iterator over the strings to be parsed. +**`parser.js`** is the base parser class, **`stream.js`** holds a iterator over the strings to be parsed. -**`combinators.mjs`** holds the actual basic grammar productions and logical combinations of grammar productions. +**`combinators.js`** holds the actual basic grammar productions and logical combinations of grammar productions. -Both use `Success` and `Failure` from **`result.mjs`** to pass success and failure conditions forward. +Both use `Success` and `Failure` from **`result.js`** to pass success and failure conditions forward. -After the definition of grammar productions, the utilities in **`mappers.mjs`** are used to concat, flatten, and extract the matched data to prepare it for AST generation. +After the definition of grammar productions, the utilities in **`mappers.js`** are used to concat, flatten, and extract the matched data to prepare it for AST generation. ## EBNF Generation -The `fluent.ebnf` is created by parsing the `grammar.mjs` and transpilation to an EBNF file. The `babylon` JavaScript parser is used to load a Babel AST. +The `fluent.ebnf` is created by parsing the `grammar.js` and transpilation to an EBNF file. The `babylon` JavaScript parser is used to load a Babel AST. -**`ebnf.mjs`** is the top-level entry point used by `bin/ebnf.mjs`. It uses the iteration logic from **`walker.mjs`** to go over the Babel AST and to extract the information relevant to the EBNF via the Visitor in **`visitor.mjs`**. The resulting data is then serialiazed to EBNF via **`serializer.mjs`**. +**`ebnf.js`** is the top-level entry point used by `bin/ebnf.js`. It uses the iteration logic from **`walker.js`** to go over the Babel AST and to extract the information relevant to the EBNF via the Visitor in **`visitor.js`**. The resulting data is then serialiazed to EBNF via **`serializer.js`**. diff --git a/lib/combinators.mjs b/lib/combinators.js similarity index 59% rename from lib/combinators.mjs rename to lib/combinators.js index c0da4b6..dee0b94 100644 --- a/lib/combinators.mjs +++ b/lib/combinators.js @@ -1,19 +1,19 @@ -import Parser from "./parser.mjs"; -import {Success, Failure} from "./result.mjs"; -import {join} from "./mappers.mjs"; +import Parser from "./parser.js"; +import {Success, Failure} from "./result.js"; +import {join} from "./mappers.js"; export function defer(fn) { // Parsers may be defined as defer(() => parser) to avoid cyclic // dependecies. - return new Parser(stream => - fn().run(stream)); + return new Parser(stream => fn().run(stream)); } export function string(str) { return new Parser(stream => stream.head(str.length) === str ? new Success(str, stream.move(str.length)) - : new Failure(`${str} not found`, stream)); + : new Failure(`${str} not found`, stream) + ); } export function regex(re) { @@ -38,16 +38,16 @@ export function eof() { return new Parser(stream => stream.head() === Symbol.for("eof") ? new Success(stream.head(), stream.move(1)) - : new Failure("not at EOF", stream)); + : new Failure("not at EOF", stream) + ); } export function lookahead(parser) { return new Parser(stream => parser .run(stream) - .fold( - value => new Success(value, stream), - value => new Failure(value, stream))); + .fold(value => new Success(value, stream), value => new Failure(value, stream)) + ); } export function not(parser) { @@ -56,13 +56,14 @@ export function not(parser) { .run(stream) .fold( (value, tail) => new Failure("not failed", stream), - (value, tail) => new Success(null, stream))); + (value, tail) => new Success(null, stream) + ) + ); } export function and(...parsers) { const final = parsers.pop(); - return sequence(...parsers.map(lookahead), final) - .map(results => results[results.length - 1]); + return sequence(...parsers.map(lookahead), final).map(results => results[results.length - 1]); } export function either(...parsers) { @@ -91,45 +92,43 @@ export function maybe(parser) { .run(stream) .fold( (value, tail) => new Success(value, tail), - (value, tail) => new Success(null, stream))); + (value, tail) => new Success(null, stream) + ) + ); } export function append(p1, p2) { - return p1.chain(values => - p2.map(value => values.concat([value]))); + return p1.chain(values => p2.map(value => values.concat([value]))); } export function after(prefix, parser) { - return sequence(prefix, parser) - .map(([left, value]) => value); + return sequence(prefix, parser).map(([left, value]) => value); } export function sequence(...parsers) { - return parsers.reduce( - (acc, parser) => append(acc, parser), - always([])); + return parsers.reduce((acc, parser) => append(acc, parser), always([])); } export function repeat(parser) { return new Parser(stream => - parser - .run(stream) - .fold( - (value, tail) => - repeat(parser) - .map(rest => [value].concat(rest)) - .run(tail), - (value, tail) => new Success([], stream))); + parser.run(stream).fold( + (value, tail) => + repeat(parser) + .map(rest => [value].concat(rest)) + .run(tail), + (value, tail) => new Success([], stream) + ) + ); } export function repeat1(parser) { return new Parser(stream => - parser - .run(stream) - .fold( - (value, tail) => - repeat(parser) - .map(rest => [value].concat(rest)) - .run(tail), - (value, tail) => new Failure("repeat1 failed", stream))); + parser.run(stream).fold( + (value, tail) => + repeat(parser) + .map(rest => [value].concat(rest)) + .run(tail), + (value, tail) => new Failure("repeat1 failed", stream) + ) + ); } diff --git a/lib/ebnf.js b/lib/ebnf.js new file mode 100644 index 0000000..ecb6cc7 --- /dev/null +++ b/lib/ebnf.js @@ -0,0 +1,13 @@ +import {parse} from "babylon"; +import walk from "./walker.js"; +import visitor from "./visitor.js"; +import serialize from "./serializer.js"; + +export default function ebnf(source, min_name_length = 0) { + let grammar_ast = parse(source, {sourceType: "module"}); + let rules = walk(grammar_ast, visitor); + let state = { + max_name_length: Math.max(min_name_length, ...rules.map(rule => rule.name.length)), + }; + return rules.map(rule => serialize(rule, state)).join(""); +} diff --git a/lib/ebnf.mjs b/lib/ebnf.mjs deleted file mode 100644 index 9cbae73..0000000 --- a/lib/ebnf.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import babylon from "babylon"; -import walk from "./walker.mjs"; -import visitor from "./visitor.mjs"; -import serialize from "./serializer.mjs"; - -export default -function ebnf(source, min_name_length = 0) { - let grammar_ast = babylon.parse(source, {sourceType: "module"}); - let rules = walk(grammar_ast, visitor); - let state = { - max_name_length: Math.max( - min_name_length, - ...rules.map(rule => rule.name.length)), - }; - return rules - .map(rule => serialize(rule, state)) - .join(""); -} - diff --git a/lib/input.ts b/lib/input.ts new file mode 100644 index 0000000..0fede02 --- /dev/null +++ b/lib/input.ts @@ -0,0 +1,27 @@ +import fs from "fs"; +import readline from "readline"; + +export function fromStdin(callback: (value: string) => void) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + prompt: "fluent>", + }); + + let lines: Array = []; + + rl.on("line", line => lines.push(line)); + rl.on("close", () => callback(lines.join("\n") + "\n")); +} + +export function fromFile(filePath: string, callback: (value: string) => void) { + fs.readFile(filePath, "utf8", (err: NodeJS.ErrnoException | null, content: string | Buffer) => { + if (err) { + throw err; + } + + if (typeof content === "string") { + callback(content); + } + }); +} diff --git a/lib/mappers.js b/lib/mappers.js new file mode 100644 index 0000000..b689e52 --- /dev/null +++ b/lib/mappers.js @@ -0,0 +1,30 @@ +import {Abstract} from "./parser.js"; + +// Flatten a list up to a given depth. +// This is useful when a parser uses nested sequences and repeats. +export const flatten = depth => list => + list.reduce( + (acc, cur) => + acc.concat(!Array.isArray(cur) || depth === 1 ? cur : flatten(depth - 1)(cur)), + [] + ); + +// Mutate an object by merging properties of another object into it. +export const mutate = state => obj => Object.assign(obj, state); + +// Join the list of parsed values into a string. +export const join = list => list.filter(value => value !== Symbol.for("eof")).join(""); + +// Prune unmatched maybes from a list. +export const prune = list => list.filter(value => value !== null); + +// Map a list of {name, value} aliases into an array of values. +export const keep_abstract = list => + list.filter(value => value instanceof Abstract).map(({value}) => value); + +// Map a list to the element at the specified index. Useful for parsers which +// define a prefix or a surrounding delimiter. +export const element_at = index => list => list[index]; + +// Print the parse result of a parser. +export const print = x => (console.log(JSON.stringify(x, null, 4)), x); diff --git a/lib/mappers.mjs b/lib/mappers.mjs deleted file mode 100644 index b9945d7..0000000 --- a/lib/mappers.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import {Abstract} from "./parser.mjs"; - -// Flatten a list up to a given depth. -// This is useful when a parser uses nested sequences and repeats. -export const flatten = depth => - list => list.reduce( - (acc, cur) => acc.concat( - !Array.isArray(cur) || depth === 1 - ? cur - : flatten(depth - 1)(cur)), - []); - -// Mutate an object by merging properties of another object into it. -export const mutate = state => - obj => Object.assign(obj, state); - -// Join the list of parsed values into a string. -export const join = list => - list - .filter(value => value !== Symbol.for("eof")) - .join(""); - -// Prune unmatched maybes from a list. -export const prune = list => - list.filter(value => value !== null); - -// Map a list of {name, value} aliases into an array of values. -export const keep_abstract = list => - list - .filter(value => value instanceof Abstract) - .map(({value}) => value); - -// Map a list to the element at the specified index. Useful for parsers which -// define a prefix or a surrounding delimiter. -export const element_at = index => list => list[index]; - -// Print the parse result of a parser. -export const print = x => - (console.log(JSON.stringify(x, null, 4)), x); diff --git a/lib/parser.mjs b/lib/parser.js similarity index 66% rename from lib/parser.mjs rename to lib/parser.js index fed57d8..70bd3b3 100644 --- a/lib/parser.mjs +++ b/lib/parser.js @@ -1,5 +1,5 @@ -import Stream from "./stream.mjs"; -import {Failure} from "./result.mjs"; +import Stream from "./stream.js"; +import {Failure} from "./result.js"; export class Abstract { constructor(value) { @@ -13,9 +13,7 @@ export default class Parser { } run(iterable) { - let stream = iterable instanceof Stream - ? iterable - : new Stream(iterable); + let stream = iterable instanceof Stream ? iterable : new Stream(iterable); return this.parse(stream); } @@ -32,9 +30,7 @@ export default class Parser { } chain(f) { - return new Parser(stream => - this.run(stream).chain( - (value, tail) => f(value).run(tail))); + return new Parser(stream => this.run(stream).chain((value, tail) => f(value).run(tail))); } fold(s, f) { diff --git a/lib/result.mjs b/lib/result.js similarity index 100% rename from lib/result.mjs rename to lib/result.js diff --git a/lib/serializer.mjs b/lib/serializer.js similarity index 84% rename from lib/serializer.mjs rename to lib/serializer.js index 7a147c8..9240d5f 100644 --- a/lib/serializer.mjs +++ b/lib/serializer.js @@ -1,5 +1,4 @@ -export default -function serialize_rule(rule, state) { +export default function serialize_rule(rule, state) { let {name, expression, block_comments} = rule; let lhs = name.padEnd(state.max_name_length); let rhs = serialize_expression(expression, state); @@ -16,9 +15,7 @@ function serialize_comments(comments) { contents.push(`/*${value}*/`); } } - return contents.length - ? contents.join("\n") + "\n" - : ""; + return contents.length ? contents.join("\n") + "\n" : ""; } function serialize_expression(expression, state) { @@ -33,8 +30,7 @@ function serialize_expression(expression, state) { } function serialize_operator({name, args}, state) { - let serialized_args = args.map(arg => - serialize_expression(arg, {...state, parent: name})); + let serialized_args = args.map(arg => serialize_expression(arg, {...state, parent: name})); function ensure_prec(text) { return state.parent ? `(${text})` : text; @@ -45,8 +41,7 @@ function serialize_operator({name, args}, state) { return null; } case "and": { - return ensure_prec( - serialized_args.reverse().join(" ")); + return ensure_prec(serialized_args.reverse().join(" ")); } case "char": { return `"${serialized_args[0]}"`; @@ -85,8 +80,7 @@ function serialize_operator({name, args}, state) { return `${serialized_args[0]}+`; } case "sequence": { - return ensure_prec( - serialized_args.filter(Boolean).join(" ")); + return ensure_prec(serialized_args.filter(Boolean).join(" ")); } case "string": { return `"${serialized_args[0]}"`; diff --git a/lib/stream.mjs b/lib/stream.js similarity index 80% rename from lib/stream.mjs rename to lib/stream.js index 92aa386..6d2c95a 100644 --- a/lib/stream.mjs +++ b/lib/stream.js @@ -2,9 +2,7 @@ export default class Stream { constructor(iterable, cursor, length) { this.iterable = iterable; this.cursor = cursor || 0; - this.length = length === undefined - ? iterable.length - this.cursor - : length; + this.length = length === undefined ? iterable.length - this.cursor : length; } // Get the element at the cursor. @@ -33,9 +31,6 @@ export default class Stream { // Consume the stream by moving the cursor. move(distance) { - return new Stream( - this.iterable, - this.cursor + distance, - this.length - distance); + return new Stream(this.iterable, this.cursor + distance, this.length - distance); } } diff --git a/lib/tools.ts b/lib/tools.ts new file mode 100644 index 0000000..470536f --- /dev/null +++ b/lib/tools.ts @@ -0,0 +1,96 @@ +import {Resource as ResourceParser} from "../syntax/grammar"; +import {Resource, NodeType, GroupComment} from "../resolver/ast"; +import {Bundle} from "../resolver/bundle"; +import {ErrorKind} from "../resolver/error"; +import {Value, StringValue, NumberValue} from "../resolver/value"; + +export function parseString(input: string) { + return ResourceParser.run(input).fold( + resource => resource, + err => { + throw err; + } + ); +} + +type Variables = Map; +type FormatResult = { + value: string | null; + errors: Array<{kind: string; arg: string}>; +}; + +export function formatResource(resource: Resource, variables: Variables) { + let bundle = new Bundle(); + bundle.addResource(resource); + + let results: Array = []; + for (let entry of resource.body) { + if (entry.type !== NodeType.Message) { + continue; + } + let message = bundle.getMessage(entry.id.name); + if (message) { + let {value, errors} = bundle.formatValue(message, variables); + results.push({ + value, + errors: errors.map(error => ({ + kind: ErrorKind[error.kind], + arg: error.arg, + })), + } as FormatResult); + } + } + return results; +} + +interface Group { + resource: Resource; + variables: Map; +} + +const RE_VARIABLE = /^\$([a-zA-Z]*): (string|number) = (.*)$/gm; +function parseVariables(comment: GroupComment) { + let variables = new Map(); + let match; + while ((match = RE_VARIABLE.exec(comment.content))) { + let [, name, type, value] = match; + switch (type) { + case "string": + variables.set(name, new StringValue(value)); + break; + case "number": + variables.set(name, new NumberValue(parseFloat(value))); + break; + } + } + return variables; +} + +export function formatGroups(resource: Resource) { + let groups: Array = []; + for (let entry of resource.body) { + if (entry.type === NodeType.GroupComment) { + groups.push({ + resource: { + type: NodeType.Resource, + body: [], + }, + variables: parseVariables(entry), + }); + } else if (groups.length > 0) { + let currentGroup = groups[groups.length - 1]; + currentGroup.resource.body.push(entry); + } + } + + let results: Array = []; + for (let group of groups) { + let groupResults = formatResource(group.resource, group.variables); + if (groupResults.length > 0) { + let lastResult = groupResults[groupResults.length - 1]; + results.push(lastResult); + } + } + + return results; +} diff --git a/lib/visitor.mjs b/lib/visitor.js similarity index 85% rename from lib/visitor.mjs rename to lib/visitor.js index 933a50b..4085399 100644 --- a/lib/visitor.mjs +++ b/lib/visitor.js @@ -12,7 +12,7 @@ export default { let comments = leadingComments && { block_comments: leadingComments .filter(comm => comm.type === "CommentBlock") - .map(comm => cont(comm, state)) + .map(comm => cont(comm, state)), }; return cont(declaration, {...state, ...comments}); }, @@ -22,12 +22,15 @@ export default { let comments = leadingComments && { block_comments: leadingComments .filter(comm => comm.type === "CommentBlock") - .map(comm => cont(comm, state)) + .map(comm => cont(comm, state)), }; return cont(declaration, {...state, ...comments}); }, VariableDeclarator(node, state, cont) { - let {id: {name}, init} = node; + let { + id: {name}, + init, + } = node; let {block_comments = []} = state; let expression = cont(init, state); return {type: "Rule", name, expression, block_comments}; @@ -78,12 +81,14 @@ export default { }; function escape(str) { - return str - // Escape backslash and double quote, which are special in EBNF. - .replace(/\\/g, "\\\\") - .replace(/"/g, "\\\"") - // Replace all Control and non-Basic Latin characters. - .replace(/([^\u0021-\u007E])/g, unicode_sequence); + return ( + str + // Escape backslash and double quote, which are special in EBNF. + .replace(/\\/g, "\\\\") + .replace(/"/g, '\\"') + // Replace all Control and non-Basic Latin characters. + .replace(/([^\u0021-\u007E])/g, unicode_sequence) + ); } function unicode_sequence(char) { diff --git a/lib/walker.mjs b/lib/walker.js similarity index 78% rename from lib/walker.mjs rename to lib/walker.js index 1b44c45..cde9f3a 100644 --- a/lib/walker.mjs +++ b/lib/walker.js @@ -1,5 +1,4 @@ -export default -function walk(node, visitor, state) { +export default function walk(node, visitor, state) { function cont(node, state) { let visit = visitor[node.type]; if (visit) { diff --git a/package-lock.json b/package-lock.json index bd66aba..cfc0a38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,54 +1,31 @@ { "name": "fluent-spec", - "version": "0.5.0", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { - "acorn": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", - "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", "dev": true }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "@types/node": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.4.tgz", + "integrity": "sha512-j8YL2C0fXq7IONwl/Ud5Kt0PeXw22zGERt+HSSnwbKOJVsAGkEz3sFCYwaF9IOuoG1HOtE0vKCj6sXF7Q0+Vaw==", "dev": true }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, "ansi-regex": { @@ -58,9 +35,18 @@ "dev": true }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", "dev": true }, "argparse": { @@ -87,54 +73,13 @@ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "lodash": "^4.14.0" - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "lodash": "^4.17.10" } }, "babylon": { @@ -149,12 +94,6 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "base64url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", - "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=", - "dev": true - }, "bash-color": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/bash-color/-/bash-color-0.0.4.tgz", @@ -171,31 +110,28 @@ "concat-map": "0.0.1" } }, - "buffer-from": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", - "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -203,19 +139,10 @@ "supports-color": "^5.3.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -223,60 +150,44 @@ } } }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, "cli-color": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.2.0.tgz", - "integrity": "sha1-OlrnT9drYmevZm5p4q+70B3vNNE=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", + "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", "dev": true, "requires": { "ansi-regex": "^2.1.1", "d": "1", - "es5-ext": "^0.10.12", - "es6-iterator": "2", - "memoizee": "^0.4.3", - "timers-ext": "0.1" + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.5" } }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -286,9 +197,9 @@ "dev": true }, "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, "concat-map": { @@ -297,33 +208,25 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "d": { @@ -336,35 +239,35 @@ } }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "object-keys": "^1.0.12" } }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "difflib": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", @@ -374,15 +277,6 @@ "heap": ">= 0.2.0" } }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, "dreamopt": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/dreamopt/-/dreamopt-0.6.0.tgz", @@ -392,15 +286,55 @@ "wordwrap": ">=0.0.2" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es5-ext": { - "version": "0.10.42", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz", - "integrity": "sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA==", + "version": "0.10.50", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", + "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", "dev": true, "requires": { "es6-iterator": "~2.0.3", "es6-symbol": "~3.1.1", - "next-tick": "1" + "next-tick": "^1.0.0" } }, "es6-iterator": { @@ -442,112 +376,15 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, - "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", - "dev": true, - "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", - "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", - "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" - } - }, - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } + "esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "event-emitter": { @@ -560,64 +397,64 @@ "es5-ext": "~0.10.14" } }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "filename-reserved-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", + "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=", "dev": true }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "filenamify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", + "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=", + "dev": true, + "requires": { + "filename-reserved-regex": "^1.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + } }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "filenamify-url": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz", + "integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "filenamify": "^1.0.0", + "humanize-url": "^1.0.0" } }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "locate-path": "^3.0.0" } }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" + "is-buffer": "~2.0.3" } }, "fluent": { @@ -633,9 +470,9 @@ "dev": true }, "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -649,40 +486,40 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "gh-pages": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-1.1.0.tgz", - "integrity": "sha512-ZpDkeOVmIrN5mz+sBWDz5zmTqcbNJzI/updCwEv/7rrSdpTNlj1B5GhBqG7f4Q8p5sJOdnBV0SIqxJrxtZQ9FA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-1.2.0.tgz", + "integrity": "sha512-cGLYAvxtlQ1iTwAS4g7FreZPXoE/g62Fsxln2mmR19mgs4zZI+XJ+wVVUhBFCF/0+Nmvbq+abyTWue1m1BSnmg==", "dev": true, "requires": { - "async": "2.6.0", - "base64url": "^2.0.0", - "commander": "2.11.0", - "fs-extra": "^4.0.2", + "async": "2.6.1", + "commander": "2.15.1", + "filenamify-url": "^1.0.0", + "fs-extra": "^5.0.0", "globby": "^6.1.0", "graceful-fs": "4.1.11", "rimraf": "^2.6.2" - }, - "dependencies": { - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - } } }, "gitbook-cli": { @@ -704,6 +541,12 @@ "user-home": "2.0.0" }, "dependencies": { + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, "fs-extra": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", @@ -729,28 +572,13 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", "dev": true - }, - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "dev": true - }, - "tmp": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", - "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } } } }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -761,20 +589,13 @@ "path-is-absolute": "^1.0.0" } }, - "globals": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz", - "integrity": "sha512-hYyf+kI8dm3nORsiiXUQigOU62hDLfJ9G01uyGMxhc6BKsircrUhC4uJPQPUSuq2GrTmiiEt7ewxlMdBewfmKQ==", - "dev": true - }, "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { "array-union": "^1.0.1", - "arrify": "^1.0.0", "glob": "^7.0.3", "object-assign": "^4.0.1", "pify": "^2.0.0", @@ -787,13 +608,19 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "function-bind": "^1.1.1" } }, "has-flag": { @@ -802,33 +629,34 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "heap": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=", "dev": true }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "humanize-url": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz", + "integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "normalize-url": "^1.0.0", + "strip-url-auth": "^1.0.0" } }, - "ignore": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz", - "integrity": "sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -845,27 +673,29 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - } + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -873,47 +703,41 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "dev": true }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, - "is-resolvable": { + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } }, "isexe": { "version": "2.0.0", @@ -921,16 +745,10 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, "js-yaml": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", - "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -938,9 +756,9 @@ } }, "json-diff": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-0.5.2.tgz", - "integrity": "sha512-N7oapTQdD4rLMUtA7d1HATCPY/BpHuSNL1mhvIuoS0u5NideDvyR+gB/ntXB7ejFz/LM0XzPLNUJQcC68n5sBw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-0.5.4.tgz", + "integrity": "sha512-q5Xmx9QXNOzOzIlMoYtLrLiu4Jl/Ce2bn0CNcv54PhyH89CI4GWlGVDye8ei2Ijt9R3U+vsWPsXpLUNob8bs8Q==", "dev": true, "requires": { "cli-color": "~0.1.6", @@ -965,18 +783,6 @@ } } }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -986,30 +792,38 @@ "graceful-fs": "^4.1.6" } }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "chalk": "^2.0.1" } }, "lru-queue": { @@ -1021,26 +835,52 @@ "es5-ext": "~0.10.2" } }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, "memoizee": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.12.tgz", - "integrity": "sha512-sprBu6nwxBWBvBOh5v2jcsGqiGLlL2xr2dLub3vR8dnE8YB17omwtm/0NSHl8jjNbcsJd5GMWJAnTSVe/O0Wfg==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", "dev": true, "requires": { "d": "1", - "es5-ext": "^0.10.30", + "es5-ext": "^0.10.45", "es6-weak-map": "^2.0.2", "event-emitter": "^0.3.5", "is-promise": "^2.1", "lru-queue": "0.1", "next-tick": "1", - "timers-ext": "^0.1.2" + "timers-ext": "^0.1.5" } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "minimatch": { @@ -1074,22 +914,57 @@ } } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true + "mocha": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", + "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, "next-tick": { @@ -1098,6 +973,42 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, "npm": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm/-/npm-5.1.0.tgz", @@ -4047,6 +3958,15 @@ } } }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, "npmi": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/npmi/-/npmi-1.0.1.tgz", @@ -5675,12 +5595,46 @@ } } }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5690,15 +5644,6 @@ "wrappy": "1" } }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", @@ -5714,51 +5659,90 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true } } }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "pify": { @@ -5782,35 +5766,27 @@ "pinkie": "^2.0.0" } }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, - "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "prettier": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.1.tgz", + "integrity": "sha512-TzGRNvuUSmPgwivDqkZ9tM/qTGW9hqDKWOE9YHiyQdixlKbv7kvEqsmDPrcHJTKwthU774TQwZXVtaQ/mMsvjg==", "dev": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } }, "q": { "version": "1.5.0", @@ -5818,102 +5794,47 @@ "integrity": "sha1-3QG6ydBtMObyGa7LglPunr3DCPE=", "dev": true }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", - "dev": true - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", "dev": true, "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" } }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "restore-cursor": { + "require-main-filename": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "rx-lite": "*" + "glob": "^7.1.3" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "shebang-command": { @@ -5937,13 +5858,29 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, "sprintf-js": { @@ -5952,6 +5889,12 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -5962,15 +5905,6 @@ "strip-ansi": "^4.0.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -5988,82 +5922,101 @@ } } }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", "dev": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "escape-string-regexp": "^1.0.2" } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "strip-url-auth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz", + "integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=", "dev": true }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } }, "timers-ext": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.5.tgz", - "integrity": "sha512-tsEStd7kmACHENhsUPaxb8Jf8/+GZZxyNFQbZD07HQOyooOa6At1rQqjffgvg7n+dxscQa9cjjMdWhJtsP2sxg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", "dev": true, "requires": { - "es5-ext": "~0.10.14", + "es5-ext": "~0.10.46", "next-tick": "1" } }, "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "escape-string-regexp": "^1.0.2" } }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "ts-node": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.2.0.tgz", + "integrity": "sha512-m8XQwUurkbYqXrKqr3WHCW310utRNvV5OnRVeISeea7LoCWVcdfeB/Ntl8JYWFh+WRoUAdBgESrzKochQt7sMw==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + }, + "dependencies": { + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true + } } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "typescript": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", + "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", "dev": true }, "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, "user-home": { @@ -6075,46 +6028,205 @@ "os-homedir": "^1.0.0" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { "isexe": "^2.0.0" } }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", "dev": true, "requires": { - "mkdirp": "^0.5.1" + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } } }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "yn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", + "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==", "dev": true } } diff --git a/package.json b/package.json index da30f18..2188383 100644 --- a/package.json +++ b/package.json @@ -1,46 +1,59 @@ { - "name": "fluent-spec", - "description": "Specification and documentation for Fluent", - "version": "1.0.0", - "private": true, - "scripts": { - "bench": "node --experimental-modules --harmony-async-iteration ./test/bench.mjs ./test/benchmarks/gecko_strings.ftl", - "build:guide": "gitbook build guide build/guide", - "build": "npm run --silent build:guide", - "ci": "npm run --silent lint && npm test && npm run --silent test:ebnf", - "clean": "rm -rf build", - "deploy": "gh-pages -d build", - "generate:ebnf": "node --experimental-modules bin/ebnf.mjs ./syntax/grammar.mjs > ./spec/fluent.ebnf", - "generate:fixtures": "make -sC test/fixtures", - "generate": "npm run --silent generate:ebnf && npm run --silent generate:fixtures", - "lint": "eslint **/*.mjs", - "test:ebnf": "node --experimental-modules test/ebnf.mjs ./syntax/grammar.mjs ./spec/fluent.ebnf", - "test:fixtures": "node --experimental-modules test/parser.mjs ./test/fixtures", - "test:unit": "node --experimental-modules test/literals.mjs", - "test": "npm run --silent test:fixtures && npm run --silent test:unit" - }, - "homepage": "https://projectfluent.org", - "repository": { - "type": "git", - "url": "git+https://github.com/projectfluent/fluent.git" - }, - "author": "Mozilla ", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/projectfluent/fluent/issues" - }, - "devDependencies": { - "babylon": "^6.18.0", - "cli-color": "^1.2.0", - "difflib": "^0.2.4", - "eslint": "^4.19.1", - "fluent": "^0.6.4", - "fluent-syntax": "^0.7.0", - "gh-pages": "^1.1.0", - "gitbook-cli": "^2.3.0", - "json-diff": "^0.5.2" - }, - "dependencies": { - "minimist": "^1.2.0" - } + "name": "fluent-spec", + "description": "Specification and documentation for Fluent", + "version": "1.0.0", + "private": true, + "scripts": { + "bench": "node -r esm --harmony-async-iteration ./test/benchmarks/bench.js ./test/benchmarks/gecko_strings.ftl", + "build:impl": "tsc", + "build:guide": "gitbook build guide build/guide", + "build": "npm run --silent build:impl && npm run --silent build:guide", + "ci": "npm run --silent lint && npm run --silent build:impl && npm run --silent test:syntax && npm run --silent test:resolver", + "clean": "rm -rf build", + "deploy": "gh-pages -d build", + "generate:ebnf": "node -r esm bin/ebnf.js ./syntax/grammar.js > ./spec/fluent.ebnf", + "generate:fixtures": "make -sC test/syntax/fixtures && make -sC test/resolver/fixtures", + "generate": "npm run --silent generate:ebnf && npm run --silent generate:fixtures", + "lint": "prettier --check '{bin,lib,resolver,syntax,test}/**/*.js'", + "pretty": "prettier --write '{bin,lib,resolver,syntax,test}/**/*.js'", + "test:syntax:ebnf": "node -r esm test/syntax/ebnf.js ./syntax/grammar.js ./spec/fluent.ebnf", + "test:syntax:fixtures": "node -r esm test/syntax/test_fixtures.js ./test/syntax/fixtures", + "test:syntax:unit": "node -r esm test/syntax/literals.js", + "test:syntax": "npm run --silent test:syntax:ebnf && npm run --silent test:syntax:fixtures && npm run --silent test:syntax:unit", + "test:resolver:fixtures": "node -r esm ./build/test/resolver/test_fixtures.js ./test/resolver/fixtures", + "test:resolver:unit": "mocha -u tdd ./build/test/resolver/**/*_test.js", + "test:resolver": "npm run --silent test:resolver:fixtures && npm run --silent test:resolver:unit", + "test": "npm run --silent build:impl && npm run --silent test:syntax:fixtures && npm run --silent test:syntax:unit && npm run --silent test:resolver", + "watch": "tsc --watch" + }, + "homepage": "https://projectfluent.org", + "repository": { + "type": "git", + "url": "git+https://github.com/projectfluent/fluent.git" + }, + "author": "Mozilla ", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/projectfluent/fluent/issues" + }, + "devDependencies": { + "@types/minimist": "^1.2.0", + "@types/mocha": "^5.2.7", + "@types/node": "^12.0.4", + "babylon": "^6.18.0", + "cli-color": "^1.2.0", + "difflib": "^0.2.4", + "fluent": "^0.6.4", + "fluent-syntax": "^0.7.0", + "gh-pages": "^1.1.0", + "gitbook-cli": "^2.3.0", + "json-diff": "^0.5.2", + "prettier": "^1.17.1", + "mocha": "^6.1.4", + "typescript": "^3.5.1" + }, + "dependencies": { + "esm": "^3.2.25", + "minimist": "^1.2.0" + } } diff --git a/resolver/ast.ts b/resolver/ast.ts new file mode 100644 index 0000000..46b963e --- /dev/null +++ b/resolver/ast.ts @@ -0,0 +1,83 @@ +export enum NodeType { + Identifer = "Identifier", + VariableReference = "VariableReference", + MessageReference = "MessageReference", + SelectExpression = "SelectExpression", + TextElement = "TextElement", + Placeable = "Placeable", + Variant = "Variant", + Pattern = "Pattern", + Message = "Message", + GroupComment = "GroupComment", + Resource = "Resource", +} + +export interface SyntaxNode { + readonly type: NodeType; +} + +export interface Identifier extends SyntaxNode { + readonly type: NodeType.Identifer; + readonly name: string; +} + +export interface VariableReference extends SyntaxNode { + readonly type: NodeType.VariableReference; + readonly id: Identifier; +} + +export interface MessageReference extends SyntaxNode { + readonly type: NodeType.MessageReference; + readonly id: Identifier; + readonly attribute: Identifier | null; +} + +export interface SelectExpression extends SyntaxNode { + readonly type: NodeType.SelectExpression; + readonly selector: InlineExpression; + readonly variants: Array; +} + +export type InlineExpression = VariableReference | MessageReference; + +export type Expression = InlineExpression | SelectExpression; + +export interface TextElement extends SyntaxNode { + readonly type: NodeType.TextElement; + readonly value: string; +} + +export interface Placeable extends SyntaxNode { + readonly type: NodeType.Placeable; + readonly expression: Expression; +} + +export type PatternElement = TextElement | Placeable; + +export interface Variant extends SyntaxNode { + readonly type: NodeType.Variant; + readonly key: Identifier; + readonly value: Pattern; + readonly default: boolean; +} + +export interface Pattern extends SyntaxNode { + readonly type: NodeType.Pattern; + readonly elements: Array; +} + +export interface Message extends SyntaxNode { + readonly type: NodeType.Message; + readonly id: Identifier; + readonly value: Pattern | null; +} + +export interface GroupComment extends SyntaxNode { + readonly type: NodeType.GroupComment; + readonly content: string; +} + +export interface Resource extends SyntaxNode { + readonly type: NodeType.Resource; + readonly body: Array; +} diff --git a/resolver/bundle.ts b/resolver/bundle.ts new file mode 100644 index 0000000..ff11723 --- /dev/null +++ b/resolver/bundle.ts @@ -0,0 +1,39 @@ +import {Scope} from "./scope"; +import {Value, NoneValue} from "./value"; +import {Message} from "./message"; +import {ScopeError} from "./error"; +import {Resource, NodeType} from "./ast"; + +export interface Formatted { + readonly value: string | null; + readonly errors: Array; +} + +export class Bundle { + public readonly messages: Map = new Map(); + + addResource(resource: Resource) { + for (let message of resource.body) { + if (message.type === NodeType.Message) { + this.messages.set(message.id.name, new Message(message)); + } + } + } + + getMessage(id: string) { + return this.messages.get(id); + } + + private createScope(variables: Map) { + return new Scope(this.messages, variables); + } + + formatValue(message: Message, variables: Map): Formatted { + let scope = this.createScope(variables); + let value = message + .resolveValue(scope) + .unwrapOrElse(() => new NoneValue()) + .format(scope); + return {value, errors: scope.errors}; + } +} diff --git a/resolver/error.ts b/resolver/error.ts new file mode 100644 index 0000000..85bc1c9 --- /dev/null +++ b/resolver/error.ts @@ -0,0 +1,16 @@ +export enum ErrorKind { + UnknownVariable, + UnknownMessage, + MissingValue, +} + +export class ScopeError extends Error { + public kind: ErrorKind; + public arg: string; + + constructor(kind: ErrorKind, arg: string) { + super(); + this.kind = kind; + this.arg = arg; + } +} diff --git a/resolver/message.ts b/resolver/message.ts new file mode 100644 index 0000000..e5ac00f --- /dev/null +++ b/resolver/message.ts @@ -0,0 +1,24 @@ +import * as ast from "./ast"; +import {Scope} from "./scope"; +import {Failure, Result} from "./result"; +import {Value, StringValue} from "./value"; +import {ScopeError, ErrorKind} from "./error"; + +export class Message { + private readonly id: string; + private readonly value: ast.Pattern | null; + + constructor(node: ast.Message) { + this.id = node.id.name; + this.value = node.value; + } + + resolveValue(scope: Scope): Result { + if (this.value !== null) { + return scope.resolvePattern(this.value); + } else { + scope.errors.push(new ScopeError(ErrorKind.MissingValue, this.id)); + return new Failure(new StringValue(this.id)); + } + } +} diff --git a/resolver/result.ts b/resolver/result.ts new file mode 100644 index 0000000..9ce843f --- /dev/null +++ b/resolver/result.ts @@ -0,0 +1,39 @@ +interface IResult { + andThen(fn: (value: T) => IResult): IResult; + orElse(fn: (value: T) => IResult): IResult; + unwrapOrElse(fn: (value: T) => T): T; +} + +export type Result = Success | Failure; + +export class Success implements IResult { + private readonly value: T; + constructor(value: T) { + this.value = value; + } + andThen(fn: (value: T) => Result): Result { + return fn(this.value); + } + orElse(fn: (value: T) => Result): Result { + return this; + } + unwrapOrElse(fn: (value: T) => T) { + return this.value; + } +} + +export class Failure implements IResult { + private readonly value: T; + constructor(value: T) { + this.value = value; + } + andThen(fn: (value: T) => Result): Result { + return this; + } + orElse(fn: (value: T) => Result): Result { + return fn(this.value); + } + unwrapOrElse(fn: (value: T) => T) { + return fn(this.value); + } +} diff --git a/resolver/scope.ts b/resolver/scope.ts new file mode 100644 index 0000000..78824a7 --- /dev/null +++ b/resolver/scope.ts @@ -0,0 +1,98 @@ +import * as ast from "./ast"; +import {Message} from "./message"; +import {Value, StringValue} from "./value"; +import {Result, Success, Failure} from "./result"; +import {ScopeError, ErrorKind} from "./error"; + +export class Scope { + private readonly messages: Map; + private readonly variables: Map; + public errors: Array; + + constructor(messages: Map, variables: Map) { + this.messages = messages; + this.variables = variables; + this.errors = []; + } + + resolveExpression(node: ast.Expression): Result { + switch (node.type) { + case ast.NodeType.VariableReference: + return this.resolveVariableReference(node as ast.VariableReference); + case ast.NodeType.MessageReference: + return this.resolveMessageReference(node as ast.MessageReference); + case ast.NodeType.SelectExpression: + return this.resolveSelectExpression(node as ast.SelectExpression); + default: + throw new TypeError("Unknown node type."); + } + } + + resolveVariableReference(node: ast.VariableReference): Result { + let value = this.variables.get(node.id.name); + if (value !== undefined) { + return new Success(value); + } else { + let name = `$${node.id.name}`; + this.errors.push(new ScopeError(ErrorKind.UnknownVariable, name)); + return new Failure(new StringValue(name)); + } + } + + resolveMessageReference(node: ast.MessageReference): Result { + let message = this.messages.get(node.id.name); + if (message !== undefined) { + return message.resolveValue(this); + } else { + this.errors.push(new ScopeError(ErrorKind.UnknownMessage, node.id.name)); + return new Failure(new StringValue(`${node.id.name}`)); + } + } + + resolveDefaultVariant(node: ast.SelectExpression): Result { + for (let variant of node.variants) { + if (variant.default) { + return this.resolvePattern(variant.value); + } + } + throw new RangeError("Missing default variant."); + } + + resolveSelectExpression(node: ast.SelectExpression): Result { + return this.resolveExpression(node.selector) + .andThen(selector => { + for (let variant of node.variants) { + if (variant.key.name === selector.value) { + return this.resolvePattern(variant.value); + } + } + return this.resolveDefaultVariant(node); + }) + .orElse(() => this.resolveDefaultVariant(node)); + } + + resolvePatternElement(node: ast.PatternElement): Result { + switch (node.type) { + case ast.NodeType.TextElement: + return new Success(new StringValue(node.value)); + case ast.NodeType.Placeable: + return this.resolveExpression(node.expression); + default: + throw new TypeError("Unknown node type."); + } + } + + resolvePattern(node: ast.Pattern): Result { + return new Success( + new StringValue( + node.elements + .map(element => + this.resolvePatternElement(element) + .unwrapOrElse(value => new StringValue(`{${value.value}}`)) + .format(this) + ) + .join("") + ) + ); + } +} diff --git a/resolver/value.ts b/resolver/value.ts new file mode 100644 index 0000000..75ee5a1 --- /dev/null +++ b/resolver/value.ts @@ -0,0 +1,38 @@ +import {Scope} from "./scope"; + +export interface Value { + readonly value: null | string | number; + format(scope: Scope): null | string; +} + +export class NoneValue implements Value { + readonly value = null; + + format(scope: Scope) { + return null; + } +} + +export class StringValue implements Value { + readonly value: string; + + constructor(value: string) { + this.value = value; + } + + format(scope: Scope) { + return this.value; + } +} + +export class NumberValue implements Value { + readonly value: number; + + constructor(value: number) { + this.value = value; + } + + format(scope: Scope) { + return this.value.toString(); + } +} diff --git a/spec/CHANGELOG.md b/spec/CHANGELOG.md index b4458a9..9e7d4d4 100644 --- a/spec/CHANGELOG.md +++ b/spec/CHANGELOG.md @@ -233,7 +233,7 @@ compatibility strategy for future releases. The `Function` production and its corresponding AST node have been removed. The logic validating that function names are all upper-case has - been moved to `abstract.mjs`. + been moved to `abstract.js`. ## 0.7.0 (October 15, 2018) @@ -282,11 +282,11 @@ compatibility strategy for future releases. correctness at a cost of reduced performance. The ASDL description of the AST has been removed in favor of - `syntax/ast.mjs` which defines the actual AST nodes returned by the + `syntax/ast.js` which defines the actual AST nodes returned by the reference parser. The EBNF is now auto-generated from the reference parser's - `syntax/grammar.mjs` file. It provides an easy to read overview of the + `syntax/grammar.js` file. It provides an easy to read overview of the grammar and will continue to be updated in the future. Going forward, all changes to the grammar will be implemented in the diff --git a/spec/fluent.ebnf b/spec/fluent.ebnf index abc12b1..ec29c8c 100644 --- a/spec/fluent.ebnf +++ b/spec/fluent.ebnf @@ -49,7 +49,7 @@ block_placeable ::= blank_block blank_inline? inline_placeable /* Rules for validating expressions in Placeables and as selectors of * SelectExpressions are documented in spec/valid.md and enforced in - * syntax/abstract.mjs. */ + * syntax/abstract.js. */ InlineExpression ::= StringLiteral | NumberLiteral | FunctionReference diff --git a/spec/valid.md b/spec/valid.md index f374dbd..c21aff9 100644 --- a/spec/valid.md +++ b/spec/valid.md @@ -4,12 +4,12 @@ Fluent Syntax distinguishes between well-formed and valid resources. - Well-formed Fluent resources conform to the Fluent grammar described by the Fluent EBNF (`spec/fluent.ebnf`). The EBNF is auto-generated from - `syntax/grammar.mjs`. + `syntax/grammar.js`. - Valid Fluent resources must be well-formed and are additionally checked for semantic correctness. The validation process may reject syntax which is well-formed. The validation rules are expressed in code in - `syntax/abstract.mjs`. + `syntax/abstract.js`. For example, the `message.attr(param: "value")` syntax is _well-formed_. `message.attr` is an `AttributeExpression` which may be the callee of a diff --git a/syntax/abstract.mjs b/syntax/abstract.js similarity index 65% rename from syntax/abstract.mjs rename to syntax/abstract.js index 708803e..a0acaa9 100644 --- a/syntax/abstract.mjs +++ b/syntax/abstract.js @@ -1,12 +1,12 @@ /* * AST Validation * - * The parse result of the grammar.mjs parser is a well-formed AST which is + * The parse result of the grammar.js parser is a well-formed AST which is * validated according to the rules documented in `spec/valid.md`. */ -import * as FTL from "./ast.mjs"; -import {always, never} from "../lib/combinators.mjs"; +import * as FTL from "./ast.js"; +import {always, never} from "../lib/combinators.js"; export function list_into(Type) { switch (Type) { @@ -30,35 +30,41 @@ export function list_into(Type) { return always(new Type(identifier, args)); } return never( - `Invalid function name: ${identifier.name}. ` - + "Function names must be all upper-case ASCII letters."); + `Invalid function name: ${identifier.name}. ` + + "Function names must be all upper-case ASCII letters." + ); }; case FTL.Pattern: return elements => - always(new FTL.Pattern( - dedent(elements) - .reduce(join_adjacent(FTL.TextElement), []) - .map(trim_text_at_extremes) - .filter(remove_empty_text))); + always( + new FTL.Pattern( + dedent(elements) + .reduce(join_adjacent(FTL.TextElement), []) + .map(trim_text_at_extremes) + .filter(remove_empty_text) + ) + ); case FTL.Resource: return entries => - always(new FTL.Resource( - entries - .reduce(join_adjacent( - FTL.Comment, - FTL.GroupComment, - FTL.ResourceComment), []) - .reduce(attach_comments, []) - .filter(remove_blank_lines))); + always( + new FTL.Resource( + entries + .reduce( + join_adjacent(FTL.Comment, FTL.GroupComment, FTL.ResourceComment), + [] + ) + .reduce(attach_comments, []) + .filter(remove_blank_lines) + ) + ); case FTL.SelectExpression: return ([selector, variants]) => { let selector_is_valid = - selector instanceof FTL.StringLiteral - || selector instanceof FTL.NumberLiteral - || selector instanceof FTL.VariableReference - || selector instanceof FTL.FunctionReference - || (selector instanceof FTL.TermReference - && selector.attribute); + selector instanceof FTL.StringLiteral || + selector instanceof FTL.NumberLiteral || + selector instanceof FTL.VariableReference || + selector instanceof FTL.FunctionReference || + (selector instanceof FTL.TermReference && selector.attribute); if (!selector_is_valid) { return never(`Invalid selector type: ${selector.type}.`); } @@ -66,8 +72,7 @@ export function list_into(Type) { return always(new Type(selector, variants)); }; default: - return elements => - always(new Type(...elements)); + return elements => always(new Type(...elements)); } } @@ -85,27 +90,22 @@ export function into(Type) { } named.set(name, arg); } else if (named.size > 0) { - return never("Positional arguments must not follow " - + "named arguments"); + return never("Positional arguments must not follow " + "named arguments"); } else { positional.push(arg); } } - return always(new Type( - positional, Array.from(named.values()))); + return always(new Type(positional, Array.from(named.values()))); }; case FTL.Placeable: return expression => { - if (expression instanceof FTL.TermReference - && expression.attribute) { - return never( - "Term attributes may not be used as placeables."); + if (expression instanceof FTL.TermReference && expression.attribute) { + return never("Term attributes may not be used as placeables."); } return always(new Type(expression)); }; default: - return (...args) => - always(new Type(...args)); + return (...args) => always(new Type(...args)); } } @@ -131,21 +131,17 @@ function join_of_type(Type, ...elements) { // TODO Join annotations and spans. switch (Type) { case FTL.TextElement: - return elements.reduce((a, b) => - new Type(a.value + b.value)); + return elements.reduce((a, b) => new Type(a.value + b.value)); case FTL.Comment: case FTL.GroupComment: case FTL.ResourceComment: - return elements.reduce((a, b) => - new Type(a.content + "\n" + b.content)); + return elements.reduce((a, b) => new Type(a.content + "\n" + b.content)); } } function attach_comments(acc, cur) { let prev = acc[acc.length - 1]; - if (prev instanceof FTL.Comment - && (cur instanceof FTL.Message - || cur instanceof FTL.Term)) { + if (prev instanceof FTL.Comment && (cur instanceof FTL.Message || cur instanceof FTL.Term)) { cur.comment = prev; acc[acc.length - 1] = cur; return acc; @@ -155,7 +151,7 @@ function attach_comments(acc, cur) { } // Remove the largest common indentation from a list of elements of a Pattern. -// The indents are parsed in grammar.mjs and passed to abstract.mjs as string +// The indents are parsed in grammar.js and passed to abstract.js as string // primitives along with other PatternElements. function dedent(elements) { // Calculate the maximum common indent. @@ -180,22 +176,19 @@ const TRAILING_BLANK_INLINE = / *$/; function trim_text_at_extremes(element, index, array) { if (element instanceof FTL.TextElement) { if (index === 0) { - element.value = element.value.replace( - LEADING_BLANK_BLOCK, ""); + element.value = element.value.replace(LEADING_BLANK_BLOCK, ""); } if (index === array.length - 1) { - element.value = element.value.replace( - TRAILING_BLANK_INLINE, ""); + element.value = element.value.replace(TRAILING_BLANK_INLINE, ""); } } return element; } function remove_empty_text(element) { - return !(element instanceof FTL.TextElement) - || element.value !== ""; + return !(element instanceof FTL.TextElement) || element.value !== ""; } function remove_blank_lines(element) { - return typeof(element) !== "string"; + return typeof element !== "string"; } diff --git a/syntax/ast.mjs b/syntax/ast.js similarity index 95% rename from syntax/ast.mjs rename to syntax/ast.js index 1a645eb..0431e06 100644 --- a/syntax/ast.mjs +++ b/syntax/ast.js @@ -97,18 +97,17 @@ export class StringLiteral extends Literal { parse() { // Backslash backslash, backslash double quote, uHHHH, UHHHHHH. - const KNOWN_ESCAPES = - /(?:\\\\|\\\"|\\u([0-9a-fA-F]{4})|\\U([0-9a-fA-F]{6}))/g; + const KNOWN_ESCAPES = /(?:\\\\|\\\"|\\u([0-9a-fA-F]{4})|\\U([0-9a-fA-F]{6}))/g; function from_escape_sequence(match, codepoint4, codepoint6) { switch (match) { case "\\\\": return "\\"; - case "\\\"": - return "\""; + case '\\"': + return '"'; default: let codepoint = parseInt(codepoint4 || codepoint6, 16); - if (codepoint <= 0xD7FF || 0xE000 <= codepoint) { + if (codepoint <= 0xd7ff || 0xe000 <= codepoint) { // It's a Unicode scalar value. return String.fromCodePoint(codepoint); } @@ -133,9 +132,7 @@ export class NumberLiteral extends Literal { parse() { let value = parseFloat(this.value); let decimal_position = this.value.indexOf("."); - let precision = decimal_position > 0 - ? this.value.length - decimal_position - 1 - : 0; + let precision = decimal_position > 0 ? this.value.length - decimal_position - 1 : 0; return {value, precision}; } } diff --git a/syntax/grammar.d.ts b/syntax/grammar.d.ts new file mode 100644 index 0000000..cb8a63d --- /dev/null +++ b/syntax/grammar.d.ts @@ -0,0 +1,10 @@ +import {Resource} from "../resolver/ast"; +interface Result { + fold(s: (value: T) => T, f: (err: E) => never): T; +} + +interface Parser { + run(input: string): Result; +} + +export declare let Resource: Parser; diff --git a/syntax/grammar.js b/syntax/grammar.js new file mode 100644 index 0000000..f215b33 --- /dev/null +++ b/syntax/grammar.js @@ -0,0 +1,408 @@ +import * as FTL from "./ast.js"; +import {list_into, into} from "./abstract.js"; +import { + always, + and, + charset, + defer, + either, + eof, + maybe, + not, + regex, + repeat, + repeat1, + sequence, + string, +} from "../lib/combinators.js"; +import {element_at, flatten, join, keep_abstract, mutate, print, prune} from "../lib/mappers.js"; + +/* ----------------------------------------------------- */ +/* An FTL file defines a Resource consisting of Entries. */ +export let Resource = defer(() => + repeat(either(Entry, blank_block, Junk)).chain(list_into(FTL.Resource)) +); + +/* ------------------------------------------------------------------------- */ +/* Entries are the main building blocks of Fluent. They define translations and + * contextual and semantic information about the translations. During the AST + * construction, adjacent comment lines of the same comment type (defined by + * the number of #) are joined together. Single-# comments directly preceding + * Messages and Terms are attached to the Message or Term and are not + * standalone Entries. */ +export let Entry = defer(() => + either( + sequence(Message, line_end).map(element_at(0)), + sequence(Term, line_end).map(element_at(0)), + CommentLine + ) +); + +let Message = defer(() => + sequence( + Identifier.abstract, + maybe(blank_inline), + string("="), + maybe(blank_inline), + either( + sequence(Pattern.abstract, repeat(Attribute).abstract), + sequence(always(null).abstract, repeat1(Attribute).abstract) + ) + ) + .map(flatten(1)) + .map(keep_abstract) + .chain(list_into(FTL.Message)) +); + +let Term = defer(() => + sequence( + string("-"), + Identifier.abstract, + maybe(blank_inline), + string("="), + maybe(blank_inline), + Pattern.abstract, + repeat(Attribute).abstract + ) + .map(keep_abstract) + .chain(list_into(FTL.Term)) +); + +/* -------------------------------------------------------------------------- */ +/* Adjacent comment lines of the same comment type are joined together during + * the AST construction. */ +let CommentLine = defer(() => + sequence( + either(string("###"), string("##"), string("#")).abstract, + maybe(sequence(string(" "), repeat(comment_char).map(join).abstract)), + line_end + ) + .map(flatten(1)) + .map(keep_abstract) + .chain(list_into(FTL.Comment)) +); + +let comment_char = defer(() => and(not(line_end), any_char)); + +/* -------------------------------------------------------------------------- */ +/* Junk represents unparsed content. + * + * Junk is parsed line-by-line until a line is found which looks like it might + * be a beginning of a new message, term, or a comment. Any whitespace + * following a broken Entry is also considered part of Junk. + */ +let Junk = defer(() => + sequence( + junk_line, + repeat(and(not(charset("a-zA-Z")), not(string("-")), not(string("#")), junk_line)) + ) + .map(flatten(1)) + .map(join) + .chain(into(FTL.Junk)) +); + +let junk_line = sequence(regex(/[^\n]*/), either(string("\u000A"), eof())).map(join); + +/* --------------------------------- */ +/* Attributes of Messages and Terms. */ +let Attribute = defer(() => + sequence( + line_end, + maybe(blank), + string("."), + Identifier.abstract, + maybe(blank_inline), + string("="), + maybe(blank_inline), + Pattern.abstract + ) + .map(keep_abstract) + .chain(list_into(FTL.Attribute)) +); + +/* ---------------------------------------------------------------- */ +/* Patterns are values of Messages, Terms, Attributes and Variants. */ +let Pattern = defer(() => + repeat1(PatternElement) + // Flatten block_text and block_placeable which return lists. + .map(flatten(1)) + .chain(list_into(FTL.Pattern)) +); + +/* ----------------------------------------------------------------- */ +/* TextElement and Placeable can occur inline or as block. + * Text needs to be indented and start with a non-special character. + * Placeables can start at the beginning of the line or be indented. + * Adjacent TextElements are joined in AST creation. */ + +let PatternElement = defer(() => + either(inline_text, block_text, inline_placeable, block_placeable) +); + +let inline_text = defer(() => + repeat1(text_char) + .map(join) + .chain(into(FTL.TextElement)) +); + +let block_text = defer(() => + sequence( + blank_block.chain(into(FTL.TextElement)), + blank_inline, + indented_char.chain(into(FTL.TextElement)), + maybe(inline_text) + ).map(prune) +); + +let inline_placeable = defer(() => + sequence( + string("{"), + maybe(blank), + either( + // Order matters! + SelectExpression, + InlineExpression + ), + maybe(blank), + string("}") + ) + .map(element_at(2)) + .chain(into(FTL.Placeable)) +); + +let block_placeable = defer(() => + sequence( + blank_block.chain(into(FTL.TextElement)), + // No indent before a placeable counts as 0 in dedention logic. + maybe(blank_inline).map(s => s || ""), + inline_placeable + ) +); + +/* ------------------------------------------------------------------- */ +/* Rules for validating expressions in Placeables and as selectors of + * SelectExpressions are documented in spec/valid.md and enforced in + * syntax/abstract.js. */ +let InlineExpression = defer(() => + either( + StringLiteral, + NumberLiteral, + FunctionReference, + MessageReference, + TermReference, + VariableReference, + inline_placeable + ) +); + +/* -------- */ +/* Literals */ +export let StringLiteral = defer(() => + sequence(string('"'), repeat(quoted_char), string('"')) + .map(element_at(1)) + .map(join) + .chain(into(FTL.StringLiteral)) +); + +export let NumberLiteral = defer(() => + sequence(maybe(string("-")), digits, maybe(sequence(string("."), digits))) + .map(flatten(1)) + .map(join) + .chain(into(FTL.NumberLiteral)) +); + +/* ------------------ */ +/* Inline Expressions */ +let FunctionReference = defer(() => + sequence(Identifier, CallArguments).chain(list_into(FTL.FunctionReference)) +); + +let MessageReference = defer(() => + sequence(Identifier, maybe(AttributeAccessor)).chain(list_into(FTL.MessageReference)) +); + +let TermReference = defer(() => + sequence( + string("-"), + Identifier.abstract, + maybe(AttributeAccessor).abstract, + maybe(CallArguments).abstract + ) + .map(keep_abstract) + .chain(list_into(FTL.TermReference)) +); + +let VariableReference = defer(() => + sequence(string("$"), Identifier) + .map(element_at(1)) + .chain(into(FTL.VariableReference)) +); + +let AttributeAccessor = defer(() => sequence(string("."), Identifier)).map(element_at(1)); + +let CallArguments = defer(() => + sequence(maybe(blank), string("("), maybe(blank), argument_list, maybe(blank), string(")")) + .map(element_at(3)) + .chain(into(FTL.CallArguments)) +); + +let argument_list = defer(() => + sequence( + repeat(sequence(Argument.abstract, maybe(blank), string(","), maybe(blank))), + maybe(Argument.abstract) + ) + .map(flatten(2)) + .map(keep_abstract) +); + +let Argument = defer(() => either(NamedArgument, InlineExpression)); + +let NamedArgument = defer(() => + sequence( + Identifier.abstract, + maybe(blank), + string(":"), + maybe(blank), + either(StringLiteral, NumberLiteral).abstract + ) + .map(keep_abstract) + .chain(list_into(FTL.NamedArgument)) +); + +/* ----------------- */ +/* Block Expressions */ +let SelectExpression = defer(() => + sequence( + InlineExpression.abstract, + maybe(blank), + string("->"), + maybe(blank_inline), + variant_list.abstract + ) + .map(keep_abstract) + .chain(list_into(FTL.SelectExpression)) +); + +let variant_list = defer(() => + sequence(repeat(Variant).abstract, DefaultVariant.abstract, repeat(Variant).abstract, line_end) + .map(keep_abstract) + .map(flatten(1)) +); + +let Variant = defer(() => + sequence(line_end, maybe(blank), VariantKey.abstract, maybe(blank_inline), Pattern.abstract) + .map(keep_abstract) + .chain(list_into(FTL.Variant)) +); + +let DefaultVariant = defer(() => + sequence( + line_end, + maybe(blank), + string("*"), + VariantKey.abstract, + maybe(blank_inline), + Pattern.abstract + ) + .map(keep_abstract) + .chain(list_into(FTL.Variant)) + .map(mutate({default: true})) +); + +let VariantKey = defer(() => + sequence( + string("["), + maybe(blank), + either(NumberLiteral, Identifier), + maybe(blank), + string("]") + ).map(element_at(2)) +); + +/* ---------- */ +/* Identifier */ + +let Identifier = sequence(charset("a-zA-Z"), repeat(charset("a-zA-Z0-9_-"))) + .map(flatten(1)) + .map(join) + .chain(into(FTL.Identifier)); + +/* -------------------------------------------------------------------------- */ +/* Content Characters + * + * Translation content can be written using any Unicode characters. However, + * some characters are considered special depending on the type of content + * they're in. See text_char and quoted_char for more information. + * + * Some Unicode characters, even if allowed, should be avoided in Fluent + * resources. See spec/recommendations.md. + */ + +let any_char = charset("\\u{0}-\\u{10FFFF}"); + +/* -------------------------------------------------------------------------- */ +/* Text elements + * + * The primary storage for content are text elements. Text elements are not + * delimited with quotes and may span multiple lines as long as all lines are + * indented. The opening brace ({) marks a start of a placeable in the pattern + * and may not be used in text elements verbatim. Due to the indentation + * requirement some text characters may not appear as the first character on a + * new line. + */ + +let special_text_char = either(string("{"), string("}")); + +let text_char = defer(() => and(not(line_end), not(special_text_char), any_char)); + +let indented_char = and(not(string(".")), not(string("*")), not(string("[")), text_char); + +/* -------------------------------------------------------------------------- */ +/* String literals + * + * For special-purpose content, quoted string literals can be used where text + * elements are not a good fit. String literals are delimited with double + * quotes and may not contain line breaks. String literals use the backslash + * (\) as the escape character. The literal double quote can be inserted via + * the \" escape sequence. The literal backslash can be inserted with \\. The + * literal opening brace ({) is allowed in string literals because they may not + * comprise placeables. + */ + +let special_quoted_char = either(string('"'), string("\\")); + +let special_escape = sequence(string("\\"), special_quoted_char).map(join); + +let unicode_escape = either( + sequence(string("\\u"), regex(/[0-9a-fA-F]{4}/)), + sequence(string("\\U"), regex(/[0-9a-fA-F]{6}/)) +).map(join); + +let quoted_char = defer(() => + either(and(not(line_end), not(special_quoted_char), any_char), special_escape, unicode_escape) +); + +/* ------- */ +/* Numbers */ + +let digits = repeat1(charset("0-9")).map(join); + +/* ---------- */ +/* Whitespace */ +let blank_inline = repeat1(string("\u0020")).map(join); + +let line_end = either( + // Normalize CRLF to LF. + string("\u000D\u000A").map(() => "\n"), + string("\u000A"), + eof() +); + +let blank_block = repeat1(sequence(maybe(blank_inline), line_end.abstract)) + .map(flatten(1)) + // Discard the indents and only keep the newlines + // for multiline Patterns. + .map(keep_abstract) + .map(join); + +let blank = repeat1(either(blank_inline, line_end)); diff --git a/syntax/grammar.mjs b/syntax/grammar.mjs deleted file mode 100644 index 4836eb8..0000000 --- a/syntax/grammar.mjs +++ /dev/null @@ -1,494 +0,0 @@ -import * as FTL from "./ast.mjs"; -import {list_into, into} from "./abstract.mjs"; -import { - always, and, charset, defer, either, eof, maybe, not, - regex, repeat, repeat1, sequence, string -} from "../lib/combinators.mjs"; -import { - element_at, flatten, join, keep_abstract, mutate, print, prune -} from "../lib/mappers.mjs"; - -/* ----------------------------------------------------- */ -/* An FTL file defines a Resource consisting of Entries. */ -export -let Resource = defer(() => - repeat( - either( - Entry, - blank_block, - Junk)) - .chain(list_into(FTL.Resource))); - -/* ------------------------------------------------------------------------- */ -/* Entries are the main building blocks of Fluent. They define translations and - * contextual and semantic information about the translations. During the AST - * construction, adjacent comment lines of the same comment type (defined by - * the number of #) are joined together. Single-# comments directly preceding - * Messages and Terms are attached to the Message or Term and are not - * standalone Entries. */ -export -let Entry = defer(() => - either( - sequence( - Message, - line_end).map(element_at(0)), - sequence( - Term, - line_end).map(element_at(0)), - CommentLine)); - -let Message = defer(() => - sequence( - Identifier.abstract, - maybe(blank_inline), - string("="), - maybe(blank_inline), - either( - sequence( - Pattern.abstract, - repeat(Attribute).abstract), - sequence( - always(null).abstract, - repeat1(Attribute).abstract))) - .map(flatten(1)) - .map(keep_abstract) - .chain(list_into(FTL.Message))); - -let Term = defer(() => - sequence( - string("-"), - Identifier.abstract, - maybe(blank_inline), - string("="), - maybe(blank_inline), - Pattern.abstract, - repeat(Attribute).abstract) - .map(keep_abstract) - .chain(list_into(FTL.Term))); - -/* -------------------------------------------------------------------------- */ -/* Adjacent comment lines of the same comment type are joined together during - * the AST construction. */ -let CommentLine = defer(() => - sequence( - either( - string("###"), - string("##"), - string("#")).abstract, - maybe( - sequence( - string(" "), - repeat(comment_char) - .map(join).abstract)), - line_end) - .map(flatten(1)) - .map(keep_abstract) - .chain(list_into(FTL.Comment))); - -let comment_char = defer(() => - and( - not(line_end), - any_char)); - -/* -------------------------------------------------------------------------- */ -/* Junk represents unparsed content. - * - * Junk is parsed line-by-line until a line is found which looks like it might - * be a beginning of a new message, term, or a comment. Any whitespace - * following a broken Entry is also considered part of Junk. - */ -let Junk = defer(() => - sequence( - junk_line, - repeat( - and( - not(charset("a-zA-Z")), - not(string("-")), - not(string("#")), - junk_line))) - .map(flatten(1)) - .map(join) - .chain(into(FTL.Junk))); - -let junk_line = - sequence( - regex(/[^\n]*/), - either( - string("\u000A"), - eof())) - .map(join); - -/* --------------------------------- */ -/* Attributes of Messages and Terms. */ -let Attribute = defer(() => - sequence( - line_end, - maybe(blank), - string("."), - Identifier.abstract, - maybe(blank_inline), - string("="), - maybe(blank_inline), - Pattern.abstract) - .map(keep_abstract) - .chain(list_into(FTL.Attribute))); - -/* ---------------------------------------------------------------- */ -/* Patterns are values of Messages, Terms, Attributes and Variants. */ -let Pattern = defer(() => - repeat1( - PatternElement) - // Flatten block_text and block_placeable which return lists. - .map(flatten(1)) - .chain(list_into(FTL.Pattern))); - -/* ----------------------------------------------------------------- */ -/* TextElement and Placeable can occur inline or as block. - * Text needs to be indented and start with a non-special character. - * Placeables can start at the beginning of the line or be indented. - * Adjacent TextElements are joined in AST creation. */ - -let PatternElement = defer(() => - either( - inline_text, - block_text, - inline_placeable, - block_placeable)); - -let inline_text = defer(() => - repeat1(text_char) - .map(join) - .chain(into(FTL.TextElement))); - -let block_text = defer(() => - sequence( - blank_block.chain(into(FTL.TextElement)), - blank_inline, - indented_char.chain(into(FTL.TextElement)), - maybe(inline_text)) - .map(prune)); - -let inline_placeable = defer(() => - sequence( - string("{"), - maybe(blank), - either( - // Order matters! - SelectExpression, - InlineExpression), - maybe(blank), - string("}")) - .map(element_at(2)) - .chain(into(FTL.Placeable))); - -let block_placeable = defer(() => - sequence( - blank_block.chain(into(FTL.TextElement)), - // No indent before a placeable counts as 0 in dedention logic. - maybe(blank_inline).map(s => s || ""), - inline_placeable)); - -/* ------------------------------------------------------------------- */ -/* Rules for validating expressions in Placeables and as selectors of - * SelectExpressions are documented in spec/valid.md and enforced in - * syntax/abstract.mjs. */ -let InlineExpression = defer(() => - either( - StringLiteral, - NumberLiteral, - FunctionReference, - MessageReference, - TermReference, - VariableReference, - inline_placeable)); - -/* -------- */ -/* Literals */ -export -let StringLiteral = defer(() => - sequence( - string("\""), - repeat(quoted_char), - string("\"")) - .map(element_at(1)) - .map(join) - .chain(into(FTL.StringLiteral))); - -export -let NumberLiteral = defer(() => - sequence( - maybe(string("-")), - digits, - maybe( - sequence( - string("."), - digits))) - .map(flatten(1)) - .map(join) - .chain(into(FTL.NumberLiteral))); - -/* ------------------ */ -/* Inline Expressions */ -let FunctionReference = defer(() => - sequence( - Identifier, - CallArguments) - .chain(list_into(FTL.FunctionReference))); - -let MessageReference = defer(() => - sequence( - Identifier, - maybe(AttributeAccessor)) - .chain(list_into(FTL.MessageReference))); - -let TermReference = defer(() => - sequence( - string("-"), - Identifier.abstract, - maybe(AttributeAccessor).abstract, - maybe(CallArguments).abstract) - .map(keep_abstract) - .chain(list_into(FTL.TermReference))); - -let VariableReference = defer(() => - sequence( - string("$"), - Identifier) - .map(element_at(1)) - .chain(into(FTL.VariableReference))); - -let AttributeAccessor = defer(() => - sequence( - string("."), - Identifier)) - .map(element_at(1)); - -let CallArguments = defer(() => - sequence( - maybe(blank), - string("("), - maybe(blank), - argument_list, - maybe(blank), - string(")")) - .map(element_at(3)) - .chain(into(FTL.CallArguments))); - -let argument_list = defer(() => - sequence( - repeat( - sequence( - Argument.abstract, - maybe(blank), - string(","), - maybe(blank))), - maybe(Argument.abstract)) - .map(flatten(2)) - .map(keep_abstract)); - -let Argument = defer(() => - either( - NamedArgument, - InlineExpression)); - -let NamedArgument = defer(() => - sequence( - Identifier.abstract, - maybe(blank), - string(":"), - maybe(blank), - either( - StringLiteral, - NumberLiteral).abstract) - .map(keep_abstract) - .chain(list_into(FTL.NamedArgument))); - -/* ----------------- */ -/* Block Expressions */ -let SelectExpression = defer(() => - sequence( - InlineExpression.abstract, - maybe(blank), - string("->"), - maybe(blank_inline), - variant_list.abstract) - .map(keep_abstract) - .chain(list_into(FTL.SelectExpression))); - -let variant_list = defer(() => - sequence( - repeat(Variant).abstract, - DefaultVariant.abstract, - repeat(Variant).abstract, - line_end) - .map(keep_abstract) - .map(flatten(1))); - -let Variant = defer(() => - sequence( - line_end, - maybe(blank), - VariantKey.abstract, - maybe(blank_inline), - Pattern.abstract) - .map(keep_abstract) - .chain(list_into(FTL.Variant))); - -let DefaultVariant = defer(() => - sequence( - line_end, - maybe(blank), - string("*"), - VariantKey.abstract, - maybe(blank_inline), - Pattern.abstract) - .map(keep_abstract) - .chain(list_into(FTL.Variant)) - .map(mutate({default: true}))); - -let VariantKey = defer(() => - sequence( - string("["), - maybe(blank), - either( - NumberLiteral, - Identifier), - maybe(blank), - string("]")) - .map(element_at(2))); - -/* ---------- */ -/* Identifier */ - -let Identifier = - sequence( - charset("a-zA-Z"), - repeat( - charset("a-zA-Z0-9_-"))) - .map(flatten(1)) - .map(join) - .chain(into(FTL.Identifier)); - -/* -------------------------------------------------------------------------- */ -/* Content Characters - * - * Translation content can be written using any Unicode characters. However, - * some characters are considered special depending on the type of content - * they're in. See text_char and quoted_char for more information. - * - * Some Unicode characters, even if allowed, should be avoided in Fluent - * resources. See spec/recommendations.md. - */ - -let any_char = - charset("\\u{0}-\\u{10FFFF}"); - -/* -------------------------------------------------------------------------- */ -/* Text elements - * - * The primary storage for content are text elements. Text elements are not - * delimited with quotes and may span multiple lines as long as all lines are - * indented. The opening brace ({) marks a start of a placeable in the pattern - * and may not be used in text elements verbatim. Due to the indentation - * requirement some text characters may not appear as the first character on a - * new line. - */ - -let special_text_char = - either( - string("{"), - string("}")); - -let text_char = defer(() => - and( - not(line_end), - not(special_text_char), - any_char)); - -let indented_char = - and( - not(string(".")), - not(string("*")), - not(string("[")), - text_char); - -/* -------------------------------------------------------------------------- */ -/* String literals - * - * For special-purpose content, quoted string literals can be used where text - * elements are not a good fit. String literals are delimited with double - * quotes and may not contain line breaks. String literals use the backslash - * (\) as the escape character. The literal double quote can be inserted via - * the \" escape sequence. The literal backslash can be inserted with \\. The - * literal opening brace ({) is allowed in string literals because they may not - * comprise placeables. - */ - -let special_quoted_char = - either( - string("\""), - string("\\")); - -let special_escape = - sequence( - string("\\"), - special_quoted_char) - .map(join); - -let unicode_escape = - either( - sequence( - string("\\u"), - regex(/[0-9a-fA-F]{4}/)), - sequence( - string("\\U"), - regex(/[0-9a-fA-F]{6}/))) - .map(join); - -let quoted_char = defer(() => - either( - and( - not(line_end), - not(special_quoted_char), - any_char), - special_escape, - unicode_escape)); - -/* ------- */ -/* Numbers */ - -let digits = - repeat1( - charset("0-9")) - .map(join); - -/* ---------- */ -/* Whitespace */ -let blank_inline = - repeat1( - string("\u0020")) - .map(join); - -let line_end = - either( - // Normalize CRLF to LF. - string("\u000D\u000A").map(() => "\n"), - string("\u000A"), - eof()); - -let blank_block = - repeat1( - sequence( - maybe(blank_inline), - line_end.abstract)) - .map(flatten(1)) - // Discard the indents and only keep the newlines - // for multiline Patterns. - .map(keep_abstract) - .map(join); - -let blank = - repeat1( - either( - blank_inline, - line_end)); diff --git a/test/bench.mjs b/test/benchmarks/bench.js similarity index 74% rename from test/bench.mjs rename to test/benchmarks/bench.js index b8c1da0..18028cb 100644 --- a/test/bench.mjs +++ b/test/benchmarks/bench.js @@ -1,18 +1,17 @@ -import fs from "fs"; import perf from "perf_hooks"; const {PerformanceObserver, performance} = perf; -import FluentSyntax from "fluent-syntax"; -import FluentRuntime from "fluent"; -import {Resource} from "../syntax/grammar.mjs"; -import {readfile} from "./util.mjs"; +import {parse} from "fluent-syntax"; +import {_parse} from "fluent"; +import {Resource} from "../../syntax/grammar.js"; +import {readfile} from "../harness/util.js"; let args = process.argv.slice(2); if (args.length < 1 || 2 < args.length) { console.error( - "Usage: node --experimental-modules --harmony-async-iteration " + - "bench.mjs FTL_FILE [SAMPLE SIZE = 30]"); + "Usage: node -r esm --harmony-async-iteration " + "bench.js FTL_FILE [SAMPLE SIZE = 30]" + ); process.exit(1); } @@ -31,8 +30,8 @@ async function main(ftl_file, sample_size = 30) { let subjects = new Map([ ["Reference", new Subject("Reference", () => Resource.run(ftl))], - ["Tooling", new Subject("Tooling", () => FluentSyntax.parse(ftl))], - ["Runtime", new Subject("Runtime", () => FluentRuntime._parse(ftl))], + ["Tooling", new Subject("Tooling", () => parse(ftl))], + ["Runtime", new Subject("Runtime", () => _parse(ftl))], ]); new PerformanceObserver(items => { @@ -72,13 +71,11 @@ function shuffle(...elements) { } function mean(elements) { - let miu = elements.reduce((acc, cur) => acc + cur) - / elements.length; + let miu = elements.reduce((acc, cur) => acc + cur) / elements.length; return +miu.toFixed(2); } function stdev(elements, mean) { - let sigma = elements.reduce((acc, cur) => acc + (cur - mean) ** 2, 0) - / (elements.length - 1); + let sigma = elements.reduce((acc, cur) => acc + (cur - mean) ** 2, 0) / (elements.length - 1); return +Math.sqrt(sigma).toFixed(2); } diff --git a/test/parser.mjs b/test/harness/fixture.js similarity index 65% rename from test/parser.mjs rename to test/harness/fixture.js index f71e7e2..91c154b 100644 --- a/test/parser.mjs +++ b/test/harness/fixture.js @@ -1,27 +1,15 @@ import assert from "assert"; import path from "path"; -import {Resource} from "../syntax/grammar.mjs"; -import {readdir, readfile, diff, PASS, FAIL} from "./util.mjs"; +import {readdir, readfile, diff, PASS, FAIL} from "./util.js"; -const fixtures_dir = process.argv[2]; - -if (!fixtures_dir) { - console.error( - "Usage: node --experimental-modules parser.mjs FIXTURE"); - process.exit(1); -} - -main(fixtures_dir); - -async function main(fixtures_dir) { +export async function test_fixtures(fixtures_dir, compare_fn) { if (fixtures_dir.endsWith(".ftl")) { // Actually, this is a filepath, split the path and the dir. var ftls = [path.basename(fixtures_dir)]; fixtures_dir = path.dirname(fixtures_dir); } else { let files = await readdir(fixtures_dir); - var ftls = files.filter( - filename => filename.endsWith(".ftl")); + var ftls = files.filter(filename => filename.endsWith(".ftl")); } // Collect all AssertionErrors. @@ -30,31 +18,25 @@ async function main(fixtures_dir) { // Parse each FTL fixture and compare against the expected AST. for (const file_name of ftls) { const ftl_path = path.join(fixtures_dir, file_name); - const ast_path = ftl_path.replace(/ftl$/, "json"); + const res_path = ftl_path.replace(/ftl$/, "json"); process.stdout.write(`${ftl_path} `); try { var ftl_source = await readfile(ftl_path); - var expected_ast = await readfile(ast_path); + var expected_result = await readfile(res_path); } catch (err) { errors.set(ftl_path, err); console.log(FAIL); continue; } - Resource.run(ftl_source).fold( - assert_equal, - err => assert.fail(err)); - - function assert_equal(ast) { - try { - validate(ast, expected_ast); - console.log(PASS); - } catch (err) { - errors.set(ftl_path, err); - console.log(FAIL); - } + try { + compare_fn(ftl_source, expected_result); + console.log(PASS); + } catch (err) { + errors.set(ftl_path, err); + console.log(FAIL); } } @@ -70,7 +52,7 @@ async function main(fixtures_dir) { exit_summary(errors.size); } -function validate(actual_ast, expected_serialized) { +export function validate_as_json(actual_ast, expected_serialized) { const actual_json = JSON.parse(JSON.stringify(actual_ast)); const expected_json = JSON.parse(expected_serialized); assert.deepEqual(actual_json, expected_json); @@ -95,9 +77,7 @@ ${err.message} } function exit_summary(error_count) { - const message = error_count - ? `Tests ${FAIL}: ${error_count}.` - : `All tests ${PASS}.`; + const message = error_count ? `Tests ${FAIL}: ${error_count}.` : `All tests ${PASS}.`; console.log(` ======================================================================== ${message} diff --git a/test/suite.mjs b/test/harness/suite.js similarity index 84% rename from test/suite.mjs rename to test/harness/suite.js index 94de928..8be5e71 100644 --- a/test/suite.mjs +++ b/test/harness/suite.js @@ -1,9 +1,7 @@ import assert from "assert"; -import color from "cli-color"; -import {diff, PASS, FAIL} from "./util.mjs"; +import {diff, PASS, FAIL} from "./util.js"; -export default -function suite(fn) { +export default function suite(fn) { let errors = new Map(); fn(create_tester(errors)); @@ -45,9 +43,7 @@ ${diff(err.expected, err.actual)} } function exit_summary(error_count) { - const message = error_count - ? `Tests ${FAIL}: ${error_count}.` - : `All tests ${PASS}.`; + const message = error_count ? `Tests ${FAIL}: ${error_count}.` : `All tests ${PASS}.`; console.log(` ======================================================================== ${message} diff --git a/test/util.mjs b/test/harness/util.js similarity index 100% rename from test/util.mjs rename to test/harness/util.js diff --git a/test/resolver/fixtures/Makefile b/test/resolver/fixtures/Makefile new file mode 100644 index 0000000..8fc4b47 --- /dev/null +++ b/test/resolver/fixtures/Makefile @@ -0,0 +1,11 @@ +FTL_FIXTURES := $(wildcard *.ftl) +AST_FIXTURES := $(FTL_FIXTURES:%.ftl=%.json) + +all: $(AST_FIXTURES) + +.PHONY: $(AST_FIXTURES) +$(AST_FIXTURES): %.json: %.ftl + @node ../../../build/bin/format.js --group $< \ + 2> /dev/null \ + 1> $@; + @echo "resolver/$< → $@" diff --git a/test/resolver/fixtures/message_reference.ftl b/test/resolver/fixtures/message_reference.ftl new file mode 100644 index 0000000..d2d34f7 --- /dev/null +++ b/test/resolver/fixtures/message_reference.ftl @@ -0,0 +1,4 @@ +## + +foo = Foo +bar = {foo} Bar diff --git a/test/resolver/fixtures/message_reference.json b/test/resolver/fixtures/message_reference.json new file mode 100644 index 0000000..7df6b20 --- /dev/null +++ b/test/resolver/fixtures/message_reference.json @@ -0,0 +1,6 @@ +[ + { + "value": "Foo Bar", + "errors": [] + } +] diff --git a/test/resolver/fixtures/variable_reference.ftl b/test/resolver/fixtures/variable_reference.ftl new file mode 100644 index 0000000..1e8b4e8 --- /dev/null +++ b/test/resolver/fixtures/variable_reference.ftl @@ -0,0 +1,8 @@ +## Unknown variable + +hello = Hello, {$world}! + +## Variable set to a string +## $world: string = World + +hello = Hello, {$world}! diff --git a/test/resolver/fixtures/variable_reference.json b/test/resolver/fixtures/variable_reference.json new file mode 100644 index 0000000..3e5f68d --- /dev/null +++ b/test/resolver/fixtures/variable_reference.json @@ -0,0 +1,15 @@ +[ + { + "value": "Hello, {$world}!", + "errors": [ + { + "kind": "UnknownVariable", + "arg": "$world" + } + ] + }, + { + "value": "Hello, World!", + "errors": [] + } +] diff --git a/test/resolver/sample_fixtures.ts b/test/resolver/sample_fixtures.ts new file mode 100644 index 0000000..775936d --- /dev/null +++ b/test/resolver/sample_fixtures.ts @@ -0,0 +1,144 @@ +// hello = Hello, {$world} +export let hello = { + type: "Message", + id: { + type: "Identifer", + name: "hello", + }, + value: { + type: "Pattern", + elements: [ + { + type: "TextElement", + value: "Hello, ", + }, + { + type: "Placeable", + expression: { + type: "VariableReference", + id: { + type: "Identifier", + name: "world", + }, + }, + }, + ], + }, +}; + +// exclamation = {hello}! +export let exclamation = { + type: "Message", + id: { + type: "Identifier", + name: "exclamation", + }, + value: { + type: "Pattern", + elements: [ + { + type: "Placeable", + expression: { + type: "MessageReference", + id: { + type: "Identifier", + name: "hello", + }, + attribute: null, + }, + }, + { + type: "TextElement", + value: "!", + }, + ], + }, +}; + +// select = {$selector -> +// *[a] (a) {hello} +// [b] (b) {exclamation} +// } +export let select = { + type: "Message", + id: { + type: "Identifier", + name: "select", + }, + value: { + type: "Pattern", + elements: [ + { + type: "Placeable", + expression: { + type: "SelectExpression", + selector: { + type: "VariableReference", + id: { + type: "Identifier", + name: "selector", + }, + }, + variants: [ + { + type: "Variant", + key: { + type: "Identifier", + name: "a", + }, + value: { + type: "Pattern", + elements: [ + { + type: "TextElement", + value: "(a) ", + }, + { + type: "Placeable", + expression: { + type: "MessageReference", + id: { + type: "Identifier", + name: "hello", + }, + attribute: null, + }, + }, + ], + }, + default: true, + }, + { + type: "Variant", + key: { + type: "Identifier", + name: "b", + }, + value: { + type: "Pattern", + elements: [ + { + type: "TextElement", + value: "(b) ", + }, + { + type: "Placeable", + expression: { + type: "MessageReference", + id: { + type: "Identifier", + name: "exclamation", + }, + attribute: null, + }, + }, + ], + }, + default: false, + }, + ], + }, + }, + ], + }, +}; diff --git a/test/resolver/sample_test.ts b/test/resolver/sample_test.ts new file mode 100644 index 0000000..bed21de --- /dev/null +++ b/test/resolver/sample_test.ts @@ -0,0 +1,162 @@ +import * as assert from "assert"; +import {hello, exclamation, select} from "./sample_fixtures"; +import {Bundle} from "../../resolver/bundle"; +import {StringValue} from "../../resolver/value"; +import {ScopeError, ErrorKind} from "../../resolver/error"; + +suite("Sample suite", function() { + suiteSetup(function() { + this.bundle = new Bundle(); + this.bundle.addResource({ + body: [hello, exclamation, select], + }); + }); + + test("hello with a variable", function() { + let message = this.bundle.getMessage("hello"); + if (message) { + let {value, errors} = this.bundle.formatValue( + message, + new Map( + Object.entries({ + world: new StringValue("World"), + }) + ) + ); + assert.equal(value, "Hello, World"); + assert.equal(errors.length, 0); + } + }); + + test("hello without a variable", function() { + let message = this.bundle.getMessage("hello"); + if (message) { + let {value, errors} = this.bundle.formatValue(message, new Map()); + assert.equal(value, "Hello, {$world}"); + assert.equal(errors.length, 1); + assert.deepEqual(errors[0], new ScopeError(ErrorKind.UnknownVariable, "$world")); + } + }); + + test("exclamation with a variable", function() { + let message = this.bundle.getMessage("exclamation"); + if (message) { + let {value, errors} = this.bundle.formatValue( + message, + new Map( + Object.entries({ + world: new StringValue("World"), + }) + ) + ); + assert.equal(value, "Hello, World!"); + assert.equal(errors.length, 0); + } + }); + + test("exclamation without a variable", function() { + let message = this.bundle.getMessage("exclamation"); + if (message) { + let {value, errors} = this.bundle.formatValue(message, new Map()); + assert.equal(value, "Hello, {$world}!"); + assert.equal(errors.length, 1); + assert.deepEqual(errors[0], new ScopeError(ErrorKind.UnknownVariable, "$world")); + } + }); + + test("select [a] with a variable", function() { + let message = this.bundle.getMessage("select"); + if (message) { + let {value, errors} = this.bundle.formatValue( + message, + new Map( + Object.entries({ + world: new StringValue("World"), + selector: new StringValue("a"), + }) + ) + ); + assert.equal(value, "(a) Hello, World"); + assert.equal(errors.length, 0); + } + }); + + test("select [a] without a variable", function() { + let message = this.bundle.getMessage("select"); + if (message) { + let {value, errors} = this.bundle.formatValue( + message, + new Map( + Object.entries({ + selector: new StringValue("a"), + }) + ) + ); + assert.equal(value, "(a) Hello, {$world}"); + assert.equal(errors.length, 1); + assert.deepEqual(errors[0], new ScopeError(ErrorKind.UnknownVariable, "$world")); + } + }); + + test("select [b] with a variable", function() { + let message = this.bundle.getMessage("select"); + if (message) { + let {value, errors} = this.bundle.formatValue( + message, + new Map( + Object.entries({ + world: new StringValue("World"), + selector: new StringValue("b"), + }) + ) + ); + assert.equal(value, "(b) Hello, World!"); + assert.equal(errors.length, 0); + } + }); + + test("select [b] without a variable", function() { + let message = this.bundle.getMessage("select"); + if (message) { + let {value, errors} = this.bundle.formatValue( + message, + new Map( + Object.entries({ + selector: new StringValue("b"), + }) + ) + ); + assert.equal(value, "(b) Hello, {$world}!"); + assert.equal(errors.length, 1); + assert.deepEqual(errors[0], new ScopeError(ErrorKind.UnknownVariable, "$world")); + } + }); + + test("select default with a variable", function() { + let message = this.bundle.getMessage("select"); + if (message) { + let {value, errors} = this.bundle.formatValue( + message, + new Map( + Object.entries({ + world: new StringValue("World"), + }) + ) + ); + assert.equal(value, "(a) Hello, World"); + assert.equal(errors.length, 1); + assert.deepEqual(errors[0], new ScopeError(ErrorKind.UnknownVariable, "$selector")); + } + }); + + test("select default without a variable", function() { + let message = this.bundle.getMessage("select"); + if (message) { + let {value, errors} = this.bundle.formatValue(message, new Map()); + assert.equal(value, "(a) Hello, {$world}"); + assert.equal(errors.length, 2); + assert.deepEqual(errors[0], new ScopeError(ErrorKind.UnknownVariable, "$selector")); + assert.deepEqual(errors[1], new ScopeError(ErrorKind.UnknownVariable, "$world")); + } + }); +}); diff --git a/test/resolver/test_fixtures.ts b/test/resolver/test_fixtures.ts new file mode 100644 index 0000000..6faae5f --- /dev/null +++ b/test/resolver/test_fixtures.ts @@ -0,0 +1,15 @@ +import {test_fixtures, validate_as_json} from "../harness/fixture.js"; +import {parseString, formatGroups} from "../../lib/tools.js"; + +const fixtures_dir = process.argv[2]; + +if (!fixtures_dir) { + console.error("Usage: node -r esm test_fixtures.js FIXTURE"); + process.exit(1); +} + +test_fixtures(fixtures_dir, (input: string, expected: string) => { + let resource = parseString(input); + let results = formatGroups(resource); + validate_as_json(results, expected); +}); diff --git a/test/ebnf.mjs b/test/syntax/ebnf.js similarity index 72% rename from test/ebnf.mjs rename to test/syntax/ebnf.js index 19a1a60..563b110 100644 --- a/test/ebnf.mjs +++ b/test/syntax/ebnf.js @@ -1,14 +1,12 @@ import color from "cli-color"; import difflib from "difflib"; -import ebnf from "../lib/ebnf.mjs"; -import {readfile, PASS, FAIL} from "./util.mjs"; +import ebnf from "../../lib/ebnf.js"; +import {readfile, PASS, FAIL} from "../harness/util.js"; let args = process.argv.slice(2); if (args.length !== 2) { - console.error( - "Usage: node --experimental-modules ebnf.mjs " + - "GRAMMAR_FILE EXPECTED_EBNF"); + console.error("Usage: node -r esm ebnf.js " + "GRAMMAR_FILE EXPECTED_EBNF"); process.exit(1); } @@ -18,12 +16,10 @@ async function main(grammar_mjs, fluent_ebnf) { let grammar_source = await readfile(grammar_mjs); let grammar_ebnf = await readfile(fluent_ebnf); - let diffs = difflib.unifiedDiff( - lines(grammar_ebnf), - lines(ebnf(grammar_source)), { - fromfile: "Expected", - tofile: "Actual", - }); + let diffs = difflib.unifiedDiff(lines(grammar_ebnf), lines(ebnf(grammar_source)), { + fromfile: "Expected", + tofile: "Actual", + }); for (let diff of diffs) { if (diff.startsWith("+")) { diff --git a/test/syntax/fixtures/.gitattributes b/test/syntax/fixtures/.gitattributes new file mode 100644 index 0000000..535a845 --- /dev/null +++ b/test/syntax/fixtures/.gitattributes @@ -0,0 +1,2 @@ +/crlf.ftl eol=crlf +/cr.ftl eol=cr diff --git a/test/fixtures/Makefile b/test/syntax/fixtures/Makefile similarity index 71% rename from test/fixtures/Makefile rename to test/syntax/fixtures/Makefile index 49c98e7..cc9ccfa 100644 --- a/test/fixtures/Makefile +++ b/test/syntax/fixtures/Makefile @@ -5,7 +5,7 @@ all: $(AST_FIXTURES) .PHONY: $(AST_FIXTURES) $(AST_FIXTURES): %.json: %.ftl - @node --experimental-modules ../../bin/parse.mjs $< \ + @node -r esm ../../../bin/parse.js $< \ 2> /dev/null \ 1> $@; - @echo "$< → $@" + @echo "syntax/$< → $@" diff --git a/test/fixtures/any_char.ftl b/test/syntax/fixtures/any_char.ftl similarity index 100% rename from test/fixtures/any_char.ftl rename to test/syntax/fixtures/any_char.ftl diff --git a/test/fixtures/any_char.json b/test/syntax/fixtures/any_char.json similarity index 100% rename from test/fixtures/any_char.json rename to test/syntax/fixtures/any_char.json diff --git a/test/fixtures/astral.ftl b/test/syntax/fixtures/astral.ftl similarity index 100% rename from test/fixtures/astral.ftl rename to test/syntax/fixtures/astral.ftl diff --git a/test/fixtures/astral.json b/test/syntax/fixtures/astral.json similarity index 100% rename from test/fixtures/astral.json rename to test/syntax/fixtures/astral.json diff --git a/test/fixtures/call_expressions.ftl b/test/syntax/fixtures/call_expressions.ftl similarity index 100% rename from test/fixtures/call_expressions.ftl rename to test/syntax/fixtures/call_expressions.ftl diff --git a/test/fixtures/call_expressions.json b/test/syntax/fixtures/call_expressions.json similarity index 100% rename from test/fixtures/call_expressions.json rename to test/syntax/fixtures/call_expressions.json diff --git a/test/fixtures/callee_expressions.ftl b/test/syntax/fixtures/callee_expressions.ftl similarity index 100% rename from test/fixtures/callee_expressions.ftl rename to test/syntax/fixtures/callee_expressions.ftl diff --git a/test/fixtures/callee_expressions.json b/test/syntax/fixtures/callee_expressions.json similarity index 100% rename from test/fixtures/callee_expressions.json rename to test/syntax/fixtures/callee_expressions.json diff --git a/test/fixtures/comments.ftl b/test/syntax/fixtures/comments.ftl similarity index 100% rename from test/fixtures/comments.ftl rename to test/syntax/fixtures/comments.ftl diff --git a/test/fixtures/comments.json b/test/syntax/fixtures/comments.json similarity index 100% rename from test/fixtures/comments.json rename to test/syntax/fixtures/comments.json diff --git a/test/fixtures/cr.ftl b/test/syntax/fixtures/cr.ftl similarity index 100% rename from test/fixtures/cr.ftl rename to test/syntax/fixtures/cr.ftl diff --git a/test/fixtures/cr.json b/test/syntax/fixtures/cr.json similarity index 100% rename from test/fixtures/cr.json rename to test/syntax/fixtures/cr.json diff --git a/test/fixtures/crlf.ftl b/test/syntax/fixtures/crlf.ftl similarity index 100% rename from test/fixtures/crlf.ftl rename to test/syntax/fixtures/crlf.ftl diff --git a/test/fixtures/crlf.json b/test/syntax/fixtures/crlf.json similarity index 100% rename from test/fixtures/crlf.json rename to test/syntax/fixtures/crlf.json diff --git a/test/fixtures/eof_comment.ftl b/test/syntax/fixtures/eof_comment.ftl similarity index 100% rename from test/fixtures/eof_comment.ftl rename to test/syntax/fixtures/eof_comment.ftl diff --git a/test/fixtures/eof_comment.json b/test/syntax/fixtures/eof_comment.json similarity index 100% rename from test/fixtures/eof_comment.json rename to test/syntax/fixtures/eof_comment.json diff --git a/test/fixtures/eof_empty.ftl b/test/syntax/fixtures/eof_empty.ftl similarity index 100% rename from test/fixtures/eof_empty.ftl rename to test/syntax/fixtures/eof_empty.ftl diff --git a/test/fixtures/eof_empty.json b/test/syntax/fixtures/eof_empty.json similarity index 100% rename from test/fixtures/eof_empty.json rename to test/syntax/fixtures/eof_empty.json diff --git a/test/fixtures/eof_id.ftl b/test/syntax/fixtures/eof_id.ftl similarity index 100% rename from test/fixtures/eof_id.ftl rename to test/syntax/fixtures/eof_id.ftl diff --git a/test/fixtures/eof_id.json b/test/syntax/fixtures/eof_id.json similarity index 100% rename from test/fixtures/eof_id.json rename to test/syntax/fixtures/eof_id.json diff --git a/test/fixtures/eof_id_equals.ftl b/test/syntax/fixtures/eof_id_equals.ftl similarity index 100% rename from test/fixtures/eof_id_equals.ftl rename to test/syntax/fixtures/eof_id_equals.ftl diff --git a/test/fixtures/eof_id_equals.json b/test/syntax/fixtures/eof_id_equals.json similarity index 100% rename from test/fixtures/eof_id_equals.json rename to test/syntax/fixtures/eof_id_equals.json diff --git a/test/fixtures/eof_junk.ftl b/test/syntax/fixtures/eof_junk.ftl similarity index 100% rename from test/fixtures/eof_junk.ftl rename to test/syntax/fixtures/eof_junk.ftl diff --git a/test/fixtures/eof_junk.json b/test/syntax/fixtures/eof_junk.json similarity index 100% rename from test/fixtures/eof_junk.json rename to test/syntax/fixtures/eof_junk.json diff --git a/test/fixtures/eof_value.ftl b/test/syntax/fixtures/eof_value.ftl similarity index 100% rename from test/fixtures/eof_value.ftl rename to test/syntax/fixtures/eof_value.ftl diff --git a/test/fixtures/eof_value.json b/test/syntax/fixtures/eof_value.json similarity index 100% rename from test/fixtures/eof_value.json rename to test/syntax/fixtures/eof_value.json diff --git a/test/fixtures/escaped_characters.ftl b/test/syntax/fixtures/escaped_characters.ftl similarity index 100% rename from test/fixtures/escaped_characters.ftl rename to test/syntax/fixtures/escaped_characters.ftl diff --git a/test/fixtures/escaped_characters.json b/test/syntax/fixtures/escaped_characters.json similarity index 100% rename from test/fixtures/escaped_characters.json rename to test/syntax/fixtures/escaped_characters.json diff --git a/test/fixtures/junk.ftl b/test/syntax/fixtures/junk.ftl similarity index 100% rename from test/fixtures/junk.ftl rename to test/syntax/fixtures/junk.ftl diff --git a/test/fixtures/junk.json b/test/syntax/fixtures/junk.json similarity index 100% rename from test/fixtures/junk.json rename to test/syntax/fixtures/junk.json diff --git a/test/fixtures/leading_dots.ftl b/test/syntax/fixtures/leading_dots.ftl similarity index 100% rename from test/fixtures/leading_dots.ftl rename to test/syntax/fixtures/leading_dots.ftl diff --git a/test/fixtures/leading_dots.json b/test/syntax/fixtures/leading_dots.json similarity index 100% rename from test/fixtures/leading_dots.json rename to test/syntax/fixtures/leading_dots.json diff --git a/test/fixtures/literal_expressions.ftl b/test/syntax/fixtures/literal_expressions.ftl similarity index 100% rename from test/fixtures/literal_expressions.ftl rename to test/syntax/fixtures/literal_expressions.ftl diff --git a/test/fixtures/literal_expressions.json b/test/syntax/fixtures/literal_expressions.json similarity index 100% rename from test/fixtures/literal_expressions.json rename to test/syntax/fixtures/literal_expressions.json diff --git a/test/fixtures/member_expressions.ftl b/test/syntax/fixtures/member_expressions.ftl similarity index 100% rename from test/fixtures/member_expressions.ftl rename to test/syntax/fixtures/member_expressions.ftl diff --git a/test/fixtures/member_expressions.json b/test/syntax/fixtures/member_expressions.json similarity index 100% rename from test/fixtures/member_expressions.json rename to test/syntax/fixtures/member_expressions.json diff --git a/test/fixtures/messages.ftl b/test/syntax/fixtures/messages.ftl similarity index 100% rename from test/fixtures/messages.ftl rename to test/syntax/fixtures/messages.ftl diff --git a/test/fixtures/messages.json b/test/syntax/fixtures/messages.json similarity index 100% rename from test/fixtures/messages.json rename to test/syntax/fixtures/messages.json diff --git a/test/fixtures/mixed_entries.ftl b/test/syntax/fixtures/mixed_entries.ftl similarity index 100% rename from test/fixtures/mixed_entries.ftl rename to test/syntax/fixtures/mixed_entries.ftl diff --git a/test/fixtures/mixed_entries.json b/test/syntax/fixtures/mixed_entries.json similarity index 100% rename from test/fixtures/mixed_entries.json rename to test/syntax/fixtures/mixed_entries.json diff --git a/test/fixtures/multiline_values.ftl b/test/syntax/fixtures/multiline_values.ftl similarity index 100% rename from test/fixtures/multiline_values.ftl rename to test/syntax/fixtures/multiline_values.ftl diff --git a/test/fixtures/multiline_values.json b/test/syntax/fixtures/multiline_values.json similarity index 100% rename from test/fixtures/multiline_values.json rename to test/syntax/fixtures/multiline_values.json diff --git a/test/fixtures/numbers.ftl b/test/syntax/fixtures/numbers.ftl similarity index 100% rename from test/fixtures/numbers.ftl rename to test/syntax/fixtures/numbers.ftl diff --git a/test/fixtures/numbers.json b/test/syntax/fixtures/numbers.json similarity index 100% rename from test/fixtures/numbers.json rename to test/syntax/fixtures/numbers.json diff --git a/test/fixtures/obsolete.ftl b/test/syntax/fixtures/obsolete.ftl similarity index 100% rename from test/fixtures/obsolete.ftl rename to test/syntax/fixtures/obsolete.ftl diff --git a/test/fixtures/obsolete.json b/test/syntax/fixtures/obsolete.json similarity index 100% rename from test/fixtures/obsolete.json rename to test/syntax/fixtures/obsolete.json diff --git a/test/fixtures/placeables.ftl b/test/syntax/fixtures/placeables.ftl similarity index 100% rename from test/fixtures/placeables.ftl rename to test/syntax/fixtures/placeables.ftl diff --git a/test/fixtures/placeables.json b/test/syntax/fixtures/placeables.json similarity index 100% rename from test/fixtures/placeables.json rename to test/syntax/fixtures/placeables.json diff --git a/test/fixtures/reference_expressions.ftl b/test/syntax/fixtures/reference_expressions.ftl similarity index 100% rename from test/fixtures/reference_expressions.ftl rename to test/syntax/fixtures/reference_expressions.ftl diff --git a/test/fixtures/reference_expressions.json b/test/syntax/fixtures/reference_expressions.json similarity index 100% rename from test/fixtures/reference_expressions.json rename to test/syntax/fixtures/reference_expressions.json diff --git a/test/fixtures/select_expressions.ftl b/test/syntax/fixtures/select_expressions.ftl similarity index 100% rename from test/fixtures/select_expressions.ftl rename to test/syntax/fixtures/select_expressions.ftl diff --git a/test/fixtures/select_expressions.json b/test/syntax/fixtures/select_expressions.json similarity index 100% rename from test/fixtures/select_expressions.json rename to test/syntax/fixtures/select_expressions.json diff --git a/test/fixtures/select_indent.ftl b/test/syntax/fixtures/select_indent.ftl similarity index 100% rename from test/fixtures/select_indent.ftl rename to test/syntax/fixtures/select_indent.ftl diff --git a/test/fixtures/select_indent.json b/test/syntax/fixtures/select_indent.json similarity index 100% rename from test/fixtures/select_indent.json rename to test/syntax/fixtures/select_indent.json diff --git a/test/fixtures/sparse_entries.ftl b/test/syntax/fixtures/sparse_entries.ftl similarity index 100% rename from test/fixtures/sparse_entries.ftl rename to test/syntax/fixtures/sparse_entries.ftl diff --git a/test/fixtures/sparse_entries.json b/test/syntax/fixtures/sparse_entries.json similarity index 100% rename from test/fixtures/sparse_entries.json rename to test/syntax/fixtures/sparse_entries.json diff --git a/test/fixtures/tab.ftl b/test/syntax/fixtures/tab.ftl similarity index 100% rename from test/fixtures/tab.ftl rename to test/syntax/fixtures/tab.ftl diff --git a/test/fixtures/tab.json b/test/syntax/fixtures/tab.json similarity index 100% rename from test/fixtures/tab.json rename to test/syntax/fixtures/tab.json diff --git a/test/fixtures/term_parameters.ftl b/test/syntax/fixtures/term_parameters.ftl similarity index 100% rename from test/fixtures/term_parameters.ftl rename to test/syntax/fixtures/term_parameters.ftl diff --git a/test/fixtures/term_parameters.json b/test/syntax/fixtures/term_parameters.json similarity index 100% rename from test/fixtures/term_parameters.json rename to test/syntax/fixtures/term_parameters.json diff --git a/test/fixtures/terms.ftl b/test/syntax/fixtures/terms.ftl similarity index 100% rename from test/fixtures/terms.ftl rename to test/syntax/fixtures/terms.ftl diff --git a/test/fixtures/terms.json b/test/syntax/fixtures/terms.json similarity index 100% rename from test/fixtures/terms.json rename to test/syntax/fixtures/terms.json diff --git a/test/fixtures/variables.ftl b/test/syntax/fixtures/variables.ftl similarity index 100% rename from test/fixtures/variables.ftl rename to test/syntax/fixtures/variables.ftl diff --git a/test/fixtures/variables.json b/test/syntax/fixtures/variables.json similarity index 100% rename from test/fixtures/variables.json rename to test/syntax/fixtures/variables.json diff --git a/test/fixtures/variant_keys.ftl b/test/syntax/fixtures/variant_keys.ftl similarity index 100% rename from test/fixtures/variant_keys.ftl rename to test/syntax/fixtures/variant_keys.ftl diff --git a/test/fixtures/variant_keys.json b/test/syntax/fixtures/variant_keys.json similarity index 100% rename from test/fixtures/variant_keys.json rename to test/syntax/fixtures/variant_keys.json diff --git a/test/fixtures/whitespace_in_value.ftl b/test/syntax/fixtures/whitespace_in_value.ftl similarity index 100% rename from test/fixtures/whitespace_in_value.ftl rename to test/syntax/fixtures/whitespace_in_value.ftl diff --git a/test/fixtures/whitespace_in_value.json b/test/syntax/fixtures/whitespace_in_value.json similarity index 100% rename from test/fixtures/whitespace_in_value.json rename to test/syntax/fixtures/whitespace_in_value.json diff --git a/test/literals.mjs b/test/syntax/literals.js similarity index 87% rename from test/literals.mjs rename to test/syntax/literals.js index be8a580..4f47c76 100644 --- a/test/literals.mjs +++ b/test/syntax/literals.js @@ -1,23 +1,20 @@ -/* eslint quotes: "off" */ -import suite from "./suite.mjs"; -import {StringLiteral, NumberLiteral} from "../syntax/grammar.mjs"; +import suite from "../harness/suite.js"; +import {StringLiteral, NumberLiteral} from "../../syntax/grammar.js"; if (process.argv.length > 2) { - console.error("Usage: node --experimental-modules literals.mjs"); + console.error("Usage: node -r esm literals.js"); process.exit(1); } suite(tester => { let title = node => `${node.type} {value: "${node.value}"}`; let test = (result, expected) => - result.fold( - node => tester.deep_equal(title(node), node.parse(), expected), - tester.fail); + result.fold(node => tester.deep_equal(title(node), node.parse(), expected), tester.fail); // Unescape raw value of StringLiterals. { test(StringLiteral.run(`"abc"`), {value: "abc"}); - test(StringLiteral.run(`"\\""`), {value: "\""}); + test(StringLiteral.run(`"\\""`), {value: '"'}); test(StringLiteral.run(`"\\\\"`), {value: "\\"}); // Unicode escapes. @@ -33,11 +30,10 @@ suite(tester => { // Literal braces. test(StringLiteral.run(`"{"`), {value: "{"}); test(StringLiteral.run(`"}"`), {value: "}"}); - }; + } // Parse float value and precision of NumberLiterals. { - // Integers. test(NumberLiteral.run("0"), {value: 0, precision: 0}); test(NumberLiteral.run("1"), {value: 1, precision: 0}); @@ -69,6 +65,5 @@ suite(tester => { test(NumberLiteral.run("-01.03"), {value: -1.03, precision: 2}); test(NumberLiteral.run("-1.0300"), {value: -1.03, precision: 4}); test(NumberLiteral.run("-01.0300"), {value: -1.03, precision: 4}); - }; + } }); - diff --git a/test/syntax/test_fixtures.js b/test/syntax/test_fixtures.js new file mode 100644 index 0000000..0a26cfd --- /dev/null +++ b/test/syntax/test_fixtures.js @@ -0,0 +1,19 @@ +import assert from "assert"; +import {Resource} from "../../syntax/grammar.js"; +import {test_fixtures, validate_as_json} from "../harness/fixture.js"; + +const fixtures_dir = process.argv[2]; + +if (!fixtures_dir) { + console.error("Usage: node -r esm test_fixtures.js FIXTURE"); + process.exit(1); +} + +test_fixtures(fixtures_dir, (ftl_source, expected_ast) => { + Resource.run(ftl_source).fold( + ast => { + validate_as_json(ast, expected_ast); + }, + err => assert.fail(err) + ); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..26204a8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "allowJs": true, + "outDir": "./build" + } +}