Merge branch 'master' into feat-optional-chaining

This commit is contained in:
Sven SAULEAU 2017-05-29 18:43:40 +02:00
commit d3bc8fcbdf
No known key found for this signature in database
GPG Key ID: 7C3212582FBA1BA2
522 changed files with 19051 additions and 5981 deletions

View File

@ -11,3 +11,4 @@
strip_root=true
suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe
suppress_comment= \\(.\\|\n\\)*\\$FlowIssue
suppress_comment= \\(.\\|\n\\)*\\$FlowIgnore

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ build
coverage
lib
node_modules
npm-debug.log

View File

@ -15,7 +15,89 @@ _Note: Gaps between patch versions are faulty, broken or test releases._
See the [Babel Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md) for the pre-6.8.0 version Changelog.
## 7.0.0-beta.4 (2017-03-01)
## 6.17.1 (2017-05-10)
### :bug: Bug Fix
* Fix typo in flow spread operator error (Brian Ng)
* Fixed invalid number literal parsing ([#473](https://github.com/babel/babylon/pull/473)) (Alex Kuzmenko)
* Fix number parser ([#433](https://github.com/babel/babylon/pull/433)) (Alex Kuzmenko)
* Ensure non pattern shorthand props are checked for reserved words ([#479](https://github.com/babel/babylon/pull/479)) (Brian Ng)
* Remove jsx context when parsing arrow functions ([#475](https://github.com/babel/babylon/pull/475)) (Brian Ng)
* Allow super in class properties ([#499](https://github.com/babel/babylon/pull/499)) (Brian Ng)
* Allow flow class field to be named constructor ([#510](https://github.com/babel/babylon/pull/510)) (Brian Ng)
## 6.17.0 (2017-04-20)
### :bug: Bug Fix
* Cherry-pick #418 to 6.x ([#476](https://github.com/babel/babylon/pull/476)) (Sebastian McKenzie)
* Add support for invalid escapes in tagged templates ([#274](https://github.com/babel/babylon/pull/274)) (Kevin Gibbons)
* Throw error if new.target is used outside of a function ([#402](https://github.com/babel/babylon/pull/402)) (Brian Ng)
* Fix parsing of class properties ([#351](https://github.com/babel/babylon/pull/351)) (Kevin Gibbons)
* Fix parsing yield with dynamicImport ([#383](https://github.com/babel/babylon/pull/383)) (Brian Ng)
* Ensure consistent start args for parseParenItem ([#386](https://github.com/babel/babylon/pull/386)) (Brian Ng)
## 7.0.0-beta.8 (2017-04-04)
### New Feature
* Add support for flow type spread (#418) (Conrad Buck)
* Allow statics in flow interfaces (#427) (Brian Ng)
### Bug Fix
* Fix predicate attachment to match flow parser (#428) (Brian Ng)
* Add extra.raw back to JSXText and JSXAttribute (#344) (Alex Rattray)
* Fix rest parameters with array and objects (#424) (Brian Ng)
* Fix number parser (#433) (Alex Kuzmenko)
### Docs
* Fix CONTRIBUTING.md [skip ci] (#432) (Alex Kuzmenko)
### Internal
* Use babel-register script when running babel smoke tests (#442) (Brian Ng)
## 7.0.0-beta.7 (2017-03-22)
### Spec Compliancy
* Remove babylon plugin for template revision since it's stage-4 (#426) (Henry Zhu)
### Bug Fix
* Fix push-pop logic in flow (#405) (Daniel Tschinder)
## 7.0.0-beta.6 (2017-03-21)
### New Feature
* Add support for invalid escapes in tagged templates (#274) (Kevin Gibbons)
### Polish
* Improves error message when super is called outside of constructor (#408) (Arshabh Kumar Agarwal)
### Docs
* [7.0] Moved value field in spec from ObjectMember to ObjectProperty as ObjectMethod's don't have it (#415) [skip ci] (James Browning)
## 7.0.0-beta.5 (2017-03-21)
### Bug Fix
* Throw error if new.target is used outside of a function (#402) (Brian Ng)
* Fix parsing of class properties (#351) (Kevin Gibbons)
### Other
* Test runner: Detect extra property in 'actual' but not in 'expected'. (#407) (Andy)
* Optimize travis builds (#419) (Daniel Tschinder)
* Update codecov to 2.0 (#412) (Daniel Tschinder)
* Fix spec for ClassMethod: It doesn't have a function, it *is* a function. (#406) [skip ci] (Andy)
* Changed Non-existent RestPattern to RestElement which is what is actually parsed (#409) [skip ci] (James Browning)
* Upgrade flow to 0.41 (Daniel Tschinder)
* Fix watch command (#403) (Brian Ng)
* Update yarn lock (Daniel Tschinder)
* Fix watch command (#403) (Brian Ng)
* chore(package): update flow-bin to version 0.41.0 (#395) (greenkeeper[bot])
* Add estree test for correct order of directives (Daniel Tschinder)
* Add DoExpression to spec (#364) (Alex Kuzmenko)
* Mention cloning of repository in CONTRIBUTING.md (#391) [skip ci] (Sumedh Nimkarde)
* Explain how to run only one test (#389) [skip ci] (Aaron Ang)
## 7.0.0-beta.4 (2017-03-01)
* Don't consume async when checking for async func decl (#377) (Brian Ng)
* add `ranges` option [skip ci] (Henry Zhu)
@ -220,7 +302,7 @@ Update API documentation ([#330](https://github.com/babel/babylon/pull/330)) (Ti
Added keywords to package.json ([#323](https://github.com/babel/babylon/pull/323)) (Dmytro)
AST spec: fix casing of `RegExpLiteral` ([#318](https://github.com/babel/babylon/pull/318)) (Mathias Bynens)
## 6.15.0 (2017-01-10)
### :eyeglasses: Spec Compliancy
@ -361,20 +443,20 @@ type C = { [string]: number };
```
Parse flow nested array type annotations like `number[][]` ([#219](https://github.com/babel/babylon/pull/219)) (Bernhard Häussner)
Supports these form now of specifying array types:
```js
var a: number[][][][];
var b: string[][];
```
### :bug: Bug Fix
Correctly eat semicolon at the end of `DelcareModuleExports` ([#223](https://github.com/babel/babylon/pull/223)) (Daniel Tschinder)
```
declare module "foo" { declare module.exports: number }
declare module "foo" { declare module.exports: number }
declare module "foo" { declare module.exports: number; } // also allowed now
```
@ -462,7 +544,7 @@ Readd missin .eslinignore for IDEs (Daniel Tschinder)
Error on missing expected.json fixture in CI ([#188](https://github.com/babel/babylon/pull/188)) (Moti Zilberman)
Add .gitattributes and .editorconfig for LF line endings ([#179](https://github.com/babel/babylon/pull/179)) (Moti Zilberman)
Fixes two tests that are failing after the merge of #172 ([#177](https://github.com/babel/babylon/pull/177)) (Moti Zilberman)
## v6.12.0 (2016-10-14)
@ -675,7 +757,7 @@ function something({ set = null, get = null }) {}
```js
// regression with duplicate export check
SyntaxError: ./typography.js: `undefined` has already been exported. Exported identifiers must be unique. (22:13)
20 |
20 |
21 | export const { rhythm } = typography;
> 22 | export const { TypographyStyle } = typography
```
@ -980,7 +1062,7 @@ var obj = {
There is also a new node type, `ForAwaitStatement`.
> [Async generators and for-await](https://github.com/tc39/proposal-async-iteration) are now a [stage 2 proposal](https://github.com/tc39/ecma262#current-proposals).
> [Async generators and for-await](https://github.com/tc39/proposal-async-iteration) are now a [stage 2 proposal](https://github.com/tc39/ecma262#current-proposals).
Example:

View File

@ -106,3 +106,15 @@ make test
```
From now on Babel will use your local checkout of Babylon for its tests.
## Creating a new plugin (`spec-new`)
> Example: https://github.com/babel/babylon/pull/541
- Create a new issue that describes the proposal (ex: [#538](https://github.com/babel/babylon/issues/538)). Include any relevant information like proposal repo/author, examples, parsing approaches, meeting notes, presentation slides, and more.
- The pull request should include:
- [ ] An update to the [#plugins](https://github.com/babel/babylon#plugins) part of the readme. Add a new entry to that list for the new plugin flag (and link to the proposal)
- [ ] If any new nodes or modifications need to be added to the AST, update [ast/spec.md](https://github.com/babel/babylon/blob/master/ast/spec.md)
- [ ] Make sure you use the `this.hasPlugin("plugin-name-here")` check so that your new plugin code only runs when that flag is turned on (not default behavior)
- [ ] Add failing/passing tests according to spec behavior
- [ ] Start working about the Babel transform itself!

View File

@ -14,9 +14,13 @@
- The latest ECMAScript version enabled by default (ES2017).
- Comment attachment.
- Support for JSX and Flow.
- Support for JSX, Flow, Typescript (WIP).
- Support for experimental language proposals (accepting PRs for anything at least [stage-0](https://github.com/tc39/proposals/blob/master/stage-0-proposals.md)).
## Contributing
Check out [contributing.md](https://github.com/babel/babylon/blob/master/CONTRIBUTING.md)
## Credits
Heavily based on [acorn](https://github.com/marijnh/acorn) and [acorn-jsx](https://github.com/RReverser/acorn-jsx),
@ -126,12 +130,32 @@ require("babylon").parse("code", {
- `jsx`
- `flow`
- `doExpressions`
- `objectRestSpread`
- `objectRestSpread` ([proposal](https://github.com/tc39/proposal-object-rest-spread))
- `decorators` (Based on an outdated version of the Decorators proposal. Will be removed in a future version of `Babylon`)
- `classProperties`
- `exportExtensions`
- `asyncGenerators`
- `functionBind`
- `classProperties` ([proposal](https://github.com/zenparsing/es-function-bind))
- `exportExtensions` ([proposal 1](https://github.com/leebyron/ecmascript-export-default-from)), ([proposal 2](https://github.com/leebyron/ecmascript-export-ns-from))
- `asyncGenerators` ([proposal](https://github.com/tc39/proposal-async-iteration))
- `functionBind` ([proposal](https://github.com/zenparsing/es-function-bind))
- `functionSent`
- `dynamicImport`
- `dynamicImport` ([proposal](https://github.com/tc39/proposal-dynamic-import))
- `numericSeparator` ([proposal](https://github.com/samuelgoto/proposal-numeric-separator))
### FAQ
#### Will Babylon support a plugin system?
Previous issues: [babel/babel#1351](https://github.com/babel/babel/issues/1351), [#500](https://github.com/babel/babylon/issues/500).
We currently aren't willing to commit to supporting the API for plugins or the resulting ecosystem (there is already enough work maintaining Babel's own plugin system). It's not clear how to make that API effective, and it would limit out ability to refactor and optimize the codebase.
Our current recommendation for those that want to create their own custom syntax is for users to fork Babylon.
To consume your custom parser, you can add to your `.babelrc` via its npm package name or require it if using JavaScript,
```json
{
"parserOpts": {
"parser": "custom-fork-of-babylon-on-npm-here"
}
}
```

View File

@ -3,6 +3,7 @@ These are the core Babylon AST node types.
- [Node objects](#node-objects)
- [Changes](#changes)
- [Identifier](#identifier)
- [PrivateName](#privatename)
- [Literals](#literals)
- [RegExpLiteral](#regexpliteral)
- [NullLiteral](#nullliteral)
@ -90,6 +91,7 @@ These are the core Babylon AST node types.
- [ClassBody](#classbody)
- [ClassMethod](#classmethod)
- [ClassProperty](#classproperty)
- [ClassPrivateProperty](#classprivateproperty)
- [ClassDeclaration](#classdeclaration)
- [ClassExpression](#classexpression)
- [MetaProperty](#metaproperty)
@ -144,13 +146,13 @@ interface Position {
### Babylon 7
Flow: Node renamed from `ExistentialTypeParam` to `ExistsTypeAnnotation` [#322](https://github.com/babel/babylon/pull/322)
Flow: Node renamed from `NumericLiteralTypeAnnotation` to `NumberLiteralTypeAnnotation` [babel/babylon#332](https://github.com/babel/babylon/pull/332)
Flow: Node `Variance` which replaces the string value of the `variance` field on several nodes [babel/babylon#333](https://github.com/babel/babylon/pull/333)
Flow: `ObjectTypeIndexer` location info matches Flow's better [babel/babylon#228](https://github.com/babel/babylon/pull/228)
Node `ForAwaitStatement` has been removed [#349](https://github.com/babel/babylon/pull/349) in favor of modifying `ForOfStatement`
`RestProperty` and `SpreadProperty` have been dropped in favor of `RestElement` and `SpreadElement`.
@ -166,6 +168,18 @@ interface Identifier <: Expression, Pattern {
An identifier. Note that an identifier may be an expression or a destructuring pattern.
# PrivateName
```js
interface PrivateName <: Expression, Pattern {
type: "PrivateName";
name: Identifier;
}
```
A Private Name Identifier.
# Literals
```js
@ -514,7 +528,7 @@ interface FunctionDeclaration <: Function, Declaration {
}
```
A function declaration. Note that unlike in the parent interface `Function`, the `id` cannot be `null`.
A function declaration. Note that unlike in the parent interface `Function`, the `id` cannot be `null`, except when this is the child of an `ExportDefaultDeclaration`.
## VariableDeclaration
@ -856,8 +870,8 @@ A member expression. If `computed` is `true`, the node corresponds to a computed
```js
interface BindExpression <: Expression {
type: "BindExpression";
object: [ Expression | null ];
callee: [ Expression ]
object: Expression | null;
callee: Expression;
}
```
@ -1016,7 +1030,7 @@ interface Class <: Node {
```js
interface ClassBody <: Node {
type: "ClassBody";
body: [ ClassMethod | ClassProperty ];
body: [ ClassMethod | ClassProperty | ClassPrivateProperty ];
}
```
@ -1044,6 +1058,16 @@ interface ClassProperty <: Node {
}
```
## ClassPrivateProperty
```js
interface ClassPrivateProperty <: Node {
type: "ClassPrivateProperty";
key: Identifier;
value: Expression;
}
```
## ClassDeclaration
```js
@ -1167,9 +1191,17 @@ An exported variable binding, e.g., `{foo}` in `export {foo}` or `{bar as foo}`
### ExportDefaultDeclaration
```js
interface OptFunctionDeclaration <: FunctionDeclaration {
id: Identifier | null;
}
interface OptClasDeclaration <: ClassDeclaration {
id: Identifier | null;
}
interface ExportDefaultDeclaration <: ModuleDeclaration {
type: "ExportDefaultDeclaration";
declaration: Declaration | Expression;
declaration: OptFunctionDeclaration | OptClassDeclaration | Expression;
}
```

View File

@ -1,6 +1,6 @@
{
"name": "babylon",
"version": "7.0.0-beta.8",
"version": "7.0.0-beta.11",
"description": "A JavaScript parser",
"author": "Sebastian McKenzie <sebmck@gmail.com>",
"homepage": "https://babeljs.io/",
@ -22,20 +22,20 @@
},
"devDependencies": {
"ava": "^0.19.0",
"babel-cli": "^7.0.0-alpha.6",
"babel-cli": "7.0.0-alpha.9",
"babel-eslint": "^7.0.0",
"babel-helper-fixtures": "^7.0.0-alpha.3",
"babel-plugin-external-helpers": "^7.0.0-alpha.3",
"babel-helper-fixtures": "7.0.0-alpha.9",
"babel-plugin-external-helpers": "7.0.0-alpha.9",
"babel-plugin-istanbul": "^4.0.0",
"babel-plugin-transform-flow-strip-types": "^7.0.0-alpha.3",
"babel-preset-es2015": "^7.0.0-alpha.3",
"babel-preset-stage-0": "^7.0.0-alpha.3",
"babel-plugin-transform-flow-strip-types": "7.0.0-alpha.9",
"babel-preset-es2015": "7.0.0-alpha.9",
"babel-preset-stage-0": "7.0.0-alpha.9",
"chalk": "^1.1.3",
"cross-env": "^4.0.0",
"cross-env": "^5.0.0",
"eslint": "^3.7.1",
"eslint-config-babel": "^6.0.0",
"eslint-plugin-flowtype": "^2.20.0",
"flow-bin": "^0.43.0",
"flow-bin": "^0.47.0",
"nyc": "^10.0.0",
"rimraf": "^2.5.4",
"rollup": "^0.41.0",
@ -76,5 +76,16 @@
"src/**/*.js",
"bin/**/*.js"
]
},
"changelog": {
"repo": "babel/babylon",
"labels": {
"Tag: Breaking Change": ":boom: Breaking Change",
"Tag: Bug Fix": ":bug: Bug Fix",
"Tag: Docs": ":memo: Documentation",
"Tag: Internal": ":house: Internal",
"Tag: New Feature": ":rocket: New Feature",
"Tag: Polish": ":nail_care: Polish"
}
}
}

View File

@ -1,3 +1,6 @@
// @flow
import type { Options } from "./options";
import Parser, { plugins } from "./parser";
import "./parser/util";
import "./parser/statement";
@ -11,6 +14,8 @@ import { types as tokTypes } from "./tokenizer/types";
import "./tokenizer";
import "./tokenizer/context";
import type { Expression, File } from "./types";
import estreePlugin from "./plugins/estree";
import flowPlugin from "./plugins/flow";
import jsxPlugin from "./plugins/jsx";
@ -18,12 +23,12 @@ plugins.estree = estreePlugin;
plugins.flow = flowPlugin;
plugins.jsx = jsxPlugin;
export function parse(input, options) {
return new Parser(options, input).parse();
export function parse(input: string, options?: Options): File {
return getParser(options, input).parse();
}
export function parseExpression(input, options) {
const parser = new Parser(options, input);
export function parseExpression(input: string, options?: Options): Expression {
const parser = getParser(options, input);
if (parser.options.strictMode) {
parser.state.strict = true;
}
@ -32,3 +37,39 @@ export function parseExpression(input, options) {
export { tokTypes };
function getParser(options: ?Options, input: string): Parser {
const cls = options && options.plugins ? getParserClass(options.plugins) : Parser;
return new cls(options, input);
}
const parserClassCache: { [key: string]: Class<Parser> } = {};
/** Get a Parser class with plugins applied. */
function getParserClass(pluginsFromOptions: $ReadOnlyArray<string>): Class<Parser> {
// Filter out just the plugins that have an actual mixin associated with them.
let pluginList = pluginsFromOptions.filter((p) => p === "estree" || p === "flow" || p === "jsx");
if (pluginList.indexOf("flow") >= 0) {
// ensure flow plugin loads last
pluginList = pluginList.filter((plugin) => plugin !== "flow");
pluginList.push("flow");
}
if (pluginList.indexOf("estree") >= 0) {
// ensure estree plugin loads first
pluginList = pluginList.filter((plugin) => plugin !== "estree");
pluginList.unshift("estree");
}
const key = pluginList.join("/");
let cls = parserClassCache[key];
if (!cls) {
cls = Parser;
for (const plugin of pluginList) {
cls = plugins[plugin](cls);
}
parserClassCache[key] = cls;
}
return cls;
}

View File

@ -1,17 +1,21 @@
// @flow
// A second optional argument can be given to further configure
// the parser process. These options are recognized:
export const defaultOptions: {
sourceType: string,
sourceFilename: any,
startLine: number,
allowReturnOutsideFunction: boolean,
allowImportExportEverywhere: boolean,
allowSuperOutsideMethod: boolean,
plugins: Array<string>,
strictMode: any,
ranges: boolean,
} = {
export type Options = {
sourceType: "script" | "module";
sourceFilename?: string;
startLine: number;
allowReturnOutsideFunction: boolean;
allowImportExportEverywhere: boolean;
allowSuperOutsideMethod: boolean;
plugins: $ReadOnlyArray<string>;
strictMode: ?boolean;
ranges: boolean;
};
export const defaultOptions: Options = {
// Source type ("script" or "module") for different semantics
sourceType: "script",
// Source filename.
@ -44,8 +48,8 @@ export const defaultOptions: {
// Interpret and default an options object
export function getOptions(opts?: Object): Object {
const options = {};
export function getOptions(opts: ?Options): Options {
const options: any = {};
for (const key in defaultOptions) {
options[key] = opts && key in opts ? opts[key] : defaultOptions[key];
}

31
src/parser/base.js Normal file
View File

@ -0,0 +1,31 @@
// @flow
import type { Options } from "../options";
import { reservedWords } from "../util/identifier";
import type State from "../tokenizer/state";
export default class BaseParser {
// Properties set by constructor in index.js
options: Options;
inModule: boolean;
plugins: { [key: string]: boolean };
filename: ?string;
// Initialized by Tokenizer
state: State;
input: string;
isReservedWord(word: string): boolean {
if (word === "await") {
return this.inModule;
} else {
return reservedWords[6](word);
}
}
hasPlugin(name: string): boolean {
return !!this.plugins[name];
}
}

View File

@ -1,5 +1,7 @@
/* eslint max-len: 0 */
// @flow
/**
* Based on the comment attachment algorithm used in espree and estraverse.
*
@ -24,133 +26,163 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Parser from "./index";
import BaseParser from "./base";
import type { Comment, Node } from "../types";
function last(stack) {
function last<T>(stack: $ReadOnlyArray<T>): T {
return stack[stack.length - 1];
}
const pp = Parser.prototype;
pp.addComment = function (comment) {
if (this.filename) comment.loc.filename = this.filename;
this.state.trailingComments.push(comment);
this.state.leadingComments.push(comment);
};
pp.processComment = function (node) {
if (node.type === "Program" && node.body.length > 0) return;
const stack = this.state.commentStack;
let lastChild, trailingComments, i, j;
if (this.state.trailingComments.length > 0) {
// If the first comment in trailingComments comes after the
// current node, then we're good - all comments in the array will
// come after the node and so it's safe to add them as official
// trailingComments.
if (this.state.trailingComments[0].start >= node.end) {
trailingComments = this.state.trailingComments;
this.state.trailingComments = [];
} else {
// Otherwise, if the first comment doesn't come after the
// current node, that means we have a mix of leading and trailing
// comments in the array and that leadingComments contains the
// same items as trailingComments. Reset trailingComments to
// zero items and we'll handle this by evaluating leadingComments
// later.
this.state.trailingComments.length = 0;
}
} else {
const lastInStack = last(stack);
if (stack.length > 0 && lastInStack.trailingComments && lastInStack.trailingComments[0].start >= node.end) {
trailingComments = lastInStack.trailingComments;
lastInStack.trailingComments = null;
}
export default class CommentsParser extends BaseParser {
addComment(comment: Comment): void {
if (this.filename) comment.loc.filename = this.filename;
this.state.trailingComments.push(comment);
this.state.leadingComments.push(comment);
}
// Eating the stack.
while (stack.length > 0 && last(stack).start >= node.start) {
lastChild = stack.pop();
}
processComment(node: Node): void {
if (node.type === "Program" && node.body.length > 0) return;
if (lastChild) {
if (lastChild.leadingComments) {
if (lastChild !== node && last(lastChild.leadingComments).end <= node.start) {
node.leadingComments = lastChild.leadingComments;
lastChild.leadingComments = null;
const stack = this.state.commentStack;
let firstChild, lastChild, trailingComments, i, j;
if (this.state.trailingComments.length > 0) {
// If the first comment in trailingComments comes after the
// current node, then we're good - all comments in the array will
// come after the node and so it's safe to add them as official
// trailingComments.
if (this.state.trailingComments[0].start >= node.end) {
trailingComments = this.state.trailingComments;
this.state.trailingComments = [];
} else {
// A leading comment for an anonymous class had been stolen by its first ClassMethod,
// so this takes back the leading comment.
// See also: https://github.com/eslint/espree/issues/158
for (i = lastChild.leadingComments.length - 2; i >= 0; --i) {
if (lastChild.leadingComments[i].end <= node.start) {
node.leadingComments = lastChild.leadingComments.splice(0, i + 1);
// Otherwise, if the first comment doesn't come after the
// current node, that means we have a mix of leading and trailing
// comments in the array and that leadingComments contains the
// same items as trailingComments. Reset trailingComments to
// zero items and we'll handle this by evaluating leadingComments
// later.
this.state.trailingComments.length = 0;
}
} else {
const lastInStack = last(stack);
if (stack.length > 0 && lastInStack.trailingComments && lastInStack.trailingComments[0].start >= node.end) {
trailingComments = lastInStack.trailingComments;
lastInStack.trailingComments = null;
}
}
// Eating the stack.
if (stack.length > 0 && last(stack).start >= node.start) {
firstChild = stack.pop();
}
while (stack.length > 0 && last(stack).start >= node.start) {
lastChild = stack.pop();
}
if (!lastChild && firstChild) lastChild = firstChild;
// Attach comments that follow a trailing comma on the last
// property in an object literal or a trailing comma in function arguments
// as trailing comments
if (firstChild &&
(firstChild.type === "ObjectProperty" ||
(node.type === "CallExpression")) &&
this.state.leadingComments.length > 0) {
const lastComment = last(this.state.leadingComments);
if (lastComment.start >= node.start) {
if (this.state.commentPreviousNode) {
for (j = 0; j < this.state.leadingComments.length; j++) {
if (this.state.leadingComments[j].end < this.state.commentPreviousNode.end) {
this.state.leadingComments.splice(j, 1);
j--;
}
}
if (this.state.leadingComments.length > 0) {
firstChild.trailingComments = this.state.leadingComments;
this.state.leadingComments = [];
}
}
}
}
if (lastChild) {
if (lastChild.leadingComments) {
if (lastChild !== node && last(lastChild.leadingComments).end <= node.start) {
node.leadingComments = lastChild.leadingComments;
lastChild.leadingComments = null;
} else {
// A leading comment for an anonymous class had been stolen by its first ClassMethod,
// so this takes back the leading comment.
// See also: https://github.com/eslint/espree/issues/158
for (i = lastChild.leadingComments.length - 2; i >= 0; --i) {
if (lastChild.leadingComments[i].end <= node.start) {
node.leadingComments = lastChild.leadingComments.splice(0, i + 1);
break;
}
}
}
}
} else if (this.state.leadingComments.length > 0) {
if (last(this.state.leadingComments).end <= node.start) {
if (this.state.commentPreviousNode) {
for (j = 0; j < this.state.leadingComments.length; j++) {
if (this.state.leadingComments[j].end < this.state.commentPreviousNode.end) {
this.state.leadingComments.splice(j, 1);
j--;
}
}
}
if (this.state.leadingComments.length > 0) {
node.leadingComments = this.state.leadingComments;
this.state.leadingComments = [];
}
} else {
// https://github.com/eslint/espree/issues/2
//
// In special cases, such as return (without a value) and
// debugger, all comments will end up as leadingComments and
// will otherwise be eliminated. This step runs when the
// commentStack is empty and there are comments left
// in leadingComments.
//
// This loop figures out the stopping point between the actual
// leading and trailing comments by finding the location of the
// first comment that comes after the given node.
for (i = 0; i < this.state.leadingComments.length; i++) {
if (this.state.leadingComments[i].end > node.start) {
break;
}
}
}
}
} else if (this.state.leadingComments.length > 0) {
if (last(this.state.leadingComments).end <= node.start) {
if (this.state.commentPreviousNode) {
for (j = 0; j < this.state.leadingComments.length; j++) {
if (this.state.leadingComments[j].end < this.state.commentPreviousNode.end) {
this.state.leadingComments.splice(j, 1);
j--;
}
}
}
if (this.state.leadingComments.length > 0) {
node.leadingComments = this.state.leadingComments;
this.state.leadingComments = [];
}
} else {
// https://github.com/eslint/espree/issues/2
//
// In special cases, such as return (without a value) and
// debugger, all comments will end up as leadingComments and
// will otherwise be eliminated. This step runs when the
// commentStack is empty and there are comments left
// in leadingComments.
//
// This loop figures out the stopping point between the actual
// leading and trailing comments by finding the location of the
// first comment that comes after the given node.
for (i = 0; i < this.state.leadingComments.length; i++) {
if (this.state.leadingComments[i].end > node.start) {
break;
// Split the array based on the location of the first comment
// that comes after the node. Keep in mind that this could
// result in an empty array, and if so, the array must be
// deleted.
const leadingComments = this.state.leadingComments.slice(0, i);
node.leadingComments = leadingComments.length === 0 ? null : leadingComments;
// Similarly, trailing comments are attached later. The variable
// must be reset to null if there are no trailing comments.
trailingComments = this.state.leadingComments.slice(i);
if (trailingComments.length === 0) {
trailingComments = null;
}
}
}
// Split the array based on the location of the first comment
// that comes after the node. Keep in mind that this could
// result in an empty array, and if so, the array must be
// deleted.
node.leadingComments = this.state.leadingComments.slice(0, i);
if ((node.leadingComments: Array<any>).length === 0) {
node.leadingComments = null;
}
this.state.commentPreviousNode = node;
// Similarly, trailing comments are attached later. The variable
// must be reset to null if there are no trailing comments.
trailingComments = this.state.leadingComments.slice(i);
if (trailingComments.length === 0) {
trailingComments = null;
if (trailingComments) {
if (trailingComments.length && trailingComments[0].start >= node.start && last(trailingComments).end <= node.end) {
node.innerComments = trailingComments;
} else {
node.trailingComments = trailingComments;
}
}
stack.push(node);
}
this.state.commentPreviousNode = node;
if (trailingComments) {
if (trailingComments.length && trailingComments[0].start >= node.start && last(trailingComments).end <= node.end) {
node.innerComments = trailingComments;
} else {
node.trailingComments = trailingComments;
}
}
stack.push(node);
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,21 @@
import { reservedWords } from "../util/identifier";
// @flow
import type { Options } from "../options";
import type { File } from "../types";
import { getOptions } from "../options";
import Tokenizer from "../tokenizer";
import StatementParser from "./statement";
export const plugins = {};
export const plugins: { [name: string]: (superClass: Class<Parser>) => Class<Parser> } = {};
export default class Parser extends Tokenizer {
constructor(options: Object, input: string) {
export default class Parser extends StatementParser {
constructor(options: ?Options, input: string) {
options = getOptions(options);
super(options, input);
this.options = options;
this.inModule = this.options.sourceType === "module";
this.input = input;
this.plugins = this.loadPlugins(this.options.plugins);
this.plugins = pluginsMap(this.options.plugins);
this.filename = options.sourceFilename;
// If enabled, skip leading hashbang line.
@ -21,59 +24,18 @@ export default class Parser extends Tokenizer {
}
}
isReservedWord(word: string): boolean {
if (word === "await") {
return this.inModule;
} else {
return reservedWords[6](word);
}
}
hasPlugin(name: string): boolean {
return !!this.plugins[name];
}
extend(name: string, f: Function) {
this[name] = f(this[name]);
}
loadPlugins(pluginList: Array<string>): { [key: string]: boolean } {
const pluginMap = {};
if (pluginList.indexOf("flow") >= 0) {
// ensure flow plugin loads last
pluginList = pluginList.filter((plugin) => plugin !== "flow");
pluginList.push("flow");
}
if (pluginList.indexOf("estree") >= 0) {
// ensure estree plugin loads first
pluginList = pluginList.filter((plugin) => plugin !== "estree");
pluginList.unshift("estree");
}
for (const name of pluginList) {
if (!pluginMap[name]) {
pluginMap[name] = true;
const plugin = plugins[name];
if (plugin) plugin(this);
}
}
return pluginMap;
}
parse(): {
type: "File",
program: {
type: "Program",
body: Array<Object>
}
} {
parse(): File {
const file = this.startNode();
const program = this.startNode();
this.nextToken();
return this.parseTopLevel(file, program);
}
}
function pluginsMap(pluginList: $ReadOnlyArray<string>): { [key: string]: boolean } {
const pluginMap = {};
for (const name of pluginList) {
pluginMap[name] = true;
}
return pluginMap;
}

View File

@ -1,7 +1,7 @@
import { getLineInfo } from "../util/location";
import Parser from "./index";
// @flow
const pp = Parser.prototype;
import { getLineInfo } from "../util/location";
import CommentsParser from "./comments";
// This function is used to raise exceptions on parse errors. It
// takes an offset integer (into the current `input`) to indicate
@ -9,11 +9,14 @@ const pp = Parser.prototype;
// of the error message, and then raises a `SyntaxError` with that
// message.
pp.raise = function (pos, message) {
const loc = getLineInfo(this.input, pos);
message += ` (${loc.line}:${loc.column})`;
const err = new SyntaxError(message);
err.pos = pos;
err.loc = loc;
throw err;
};
export default class LocationParser extends CommentsParser {
raise(pos: number, message: string): empty {
const loc = getLineInfo(this.input, pos);
message += ` (${loc.line}:${loc.column})`;
// $FlowIgnore
const err: SyntaxError & { pos: number, loc: Position } = new SyntaxError(message);
err.pos = pos;
err.loc = loc;
throw err;
}
}

View File

@ -1,259 +1,291 @@
import { types as tt } from "../tokenizer/types";
import Parser from "./index";
// @flow
const pp = Parser.prototype;
import { types as tt, type TokenType } from "../tokenizer/types";
import type { Decorator, Expression, Identifier, Node, ObjectExpression, ObjectPattern, Pattern, RestElement,
SpreadElement } from "../types";
import type { Pos, Position } from "../util/location";
import { NodeUtils } from "./node";
// Convert existing expression atom to assignable pattern
// if possible.
export default class LValParser extends NodeUtils {
// Forward-declaration: defined in expression.js
+checkReservedWord: (word: string, startLoc: number, checkKeywords: boolean, isBinding: boolean) => void;
+parseIdentifier: (liberal?: boolean) => Identifier;
+parseMaybeAssign: (
noIn?: ?boolean,
refShorthandDefaultPos?: ?Pos,
afterLeftParse?: Function,
refNeedsArrowPos?: ?Pos) => Expression;
+parseObj: <T : ObjectPattern | ObjectExpression>(isPattern: boolean, refShorthandDefaultPos?: ?Pos) => T;
// Forward-declaration: defined in statement.js
+parseDecorator: () => Decorator;
pp.toAssignable = function (node, isBinding, contextDescription) {
if (node) {
switch (node.type) {
case "Identifier":
case "ObjectPattern":
case "ArrayPattern":
case "AssignmentPattern":
break;
// Convert existing expression atom to assignable pattern
// if possible.
case "ObjectExpression":
node.type = "ObjectPattern";
for (const prop of (node.properties: Array<Object>)) {
if (prop.type === "ObjectMethod") {
if (prop.kind === "get" || prop.kind === "set") {
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
toAssignable(node: Node, isBinding: ?boolean, contextDescription: string): Node {
if (node) {
switch (node.type) {
case "Identifier":
case "PrivateName":
case "ObjectPattern":
case "ArrayPattern":
case "AssignmentPattern":
break;
case "ObjectExpression":
node.type = "ObjectPattern";
for (const prop of node.properties) {
if (prop.type === "ObjectMethod") {
if (prop.kind === "get" || prop.kind === "set") {
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
} else {
this.raise(prop.key.start, "Object pattern can't contain methods");
}
} else {
this.raise(prop.key.start, "Object pattern can't contain methods");
this.toAssignable(prop, isBinding, "object destructuring pattern");
}
}
break;
case "ObjectProperty":
this.toAssignable(node.value, isBinding, contextDescription);
break;
case "SpreadElement":
node.type = "RestElement";
const arg = node.argument;
this.toAssignable(arg, isBinding, contextDescription);
break;
case "ArrayExpression":
node.type = "ArrayPattern";
this.toAssignableList(node.elements, isBinding, contextDescription);
break;
case "AssignmentExpression":
if (node.operator === "=") {
node.type = "AssignmentPattern";
delete node.operator;
} else {
this.toAssignable(prop, isBinding, "object destructuring pattern");
this.raise(node.left.end, "Only '=' operator can be used for specifying default value.");
}
break;
case "MemberExpression":
if (!isBinding) break;
default: {
const message = "Invalid left-hand side" +
(contextDescription ? " in " + contextDescription : /* istanbul ignore next */ "expression");
this.raise(node.start, message);
}
}
}
return node;
}
// Convert list of expression atoms to binding list.
toAssignableList(
exprList: Expression[], isBinding: ?boolean, contextDescription: string): $ReadOnlyArray<Pattern> {
let end = exprList.length;
if (end) {
const last = exprList[end - 1];
if (last && last.type === "RestElement") {
--end;
} else if (last && last.type === "SpreadElement") {
last.type = "RestElement";
const arg = last.argument;
this.toAssignable(arg, isBinding, contextDescription);
if (
arg.type !== "Identifier" &&
arg.type !== "MemberExpression" &&
arg.type !== "ArrayPattern"
) {
this.unexpected(arg.start);
}
--end;
}
}
for (let i = 0; i < end; i++) {
const elt = exprList[i];
if (elt && elt.type === "SpreadElement")
this.raise(elt.start, "The rest element has to be the last element when destructuring");
if (elt) this.toAssignable(elt, isBinding, contextDescription);
}
return exprList;
}
// Convert list of expression atoms to a list of
toReferencedList(exprList: $ReadOnlyArray<?Expression>): $ReadOnlyArray<?Expression> {
return exprList;
}
// Parses spread element.
parseSpread<T : RestElement | SpreadElement>(refShorthandDefaultPos: ?Pos): T {
const node = this.startNode();
this.next();
node.argument = this.parseMaybeAssign(false, refShorthandDefaultPos);
return this.finishNode(node, "SpreadElement");
}
parseRest(): RestElement {
const node = this.startNode();
this.next();
node.argument = this.parseBindingAtom();
return this.finishNode(node, "RestElement");
}
shouldAllowYieldIdentifier(): boolean {
return this.match(tt._yield) && !this.state.strict && !this.state.inGenerator;
}
parseBindingIdentifier(): Identifier {
return this.parseIdentifier(this.shouldAllowYieldIdentifier());
}
// Parses lvalue (assignable) atom.
parseBindingAtom(): Pattern {
switch (this.state.type) {
case tt._yield:
case tt.name:
return this.parseBindingIdentifier();
case tt.bracketL:
const node = this.startNode();
this.next();
node.elements = this.parseBindingList(tt.bracketR, true);
return this.finishNode(node, "ArrayPattern");
case tt.braceL:
return this.parseObj(true);
default:
throw this.unexpected();
}
}
parseBindingList(close: TokenType, allowEmpty?: boolean): $ReadOnlyArray<Pattern> {
const elts = [];
let first = true;
while (!this.eat(close)) {
if (first) {
first = false;
} else {
this.expect(tt.comma);
}
if (allowEmpty && this.match(tt.comma)) {
// $FlowFixMe This method returns `$ReadOnlyArray<?Pattern>` if `allowEmpty` is set.
elts.push(null);
} else if (this.eat(close)) {
break;
} else if (this.match(tt.ellipsis)) {
elts.push(this.parseAssignableListItemTypes(this.parseRest()));
this.expect(close);
break;
} else {
const decorators = [];
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
const left = this.parseMaybeDefault();
if (decorators.length) {
left.decorators = decorators;
}
this.parseAssignableListItemTypes(left);
elts.push(this.parseMaybeDefault(left.start, left.loc.start, left));
}
}
return elts;
}
parseAssignableListItemTypes(param: Pattern): Pattern {
return param;
}
// Parses assignment pattern around given atom if possible.
parseMaybeDefault(startPos?: ?number, startLoc?: ?Position, left?: ?Pattern): Pattern {
startLoc = startLoc || this.state.startLoc;
startPos = startPos || this.state.start;
left = left || this.parseBindingAtom();
if (!this.eat(tt.eq)) return left;
const node = this.startNodeAt(startPos, startLoc);
node.left = left;
node.right = this.parseMaybeAssign();
return this.finishNode(node, "AssignmentPattern");
}
// Verify that a node is an lval — something that can be assigned
// to.
checkLVal(
expr: Expression,
isBinding: ?boolean,
checkClashes: ?{ [key: string]: boolean },
contextDescription: string): void {
switch (expr.type) {
case "PrivateName":
case "Identifier":
this.checkReservedWord(expr.name, expr.start, false, true);
if (checkClashes) {
// we need to prefix this with an underscore for the cases where we have a key of
// `__proto__`. there's a bug in old V8 where the following wouldn't work:
//
// > var obj = Object.create(null);
// undefined
// > obj.__proto__
// null
// > obj.__proto__ = true;
// true
// > obj.__proto__
// null
const key = `_${expr.name}`;
if (checkClashes[key]) {
this.raise(expr.start, "Argument name clash in strict mode");
} else {
checkClashes[key] = true;
}
}
break;
case "ObjectProperty":
this.toAssignable(node.value, isBinding, contextDescription);
case "MemberExpression":
if (isBinding)
this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " member expression");
break;
case "SpreadElement":
node.type = "RestElement";
break;
case "ArrayExpression":
node.type = "ArrayPattern";
this.toAssignableList(node.elements, isBinding, contextDescription);
break;
case "AssignmentExpression":
if (node.operator === "=") {
node.type = "AssignmentPattern";
delete node.operator;
} else {
this.raise(node.left.end, "Only '=' operator can be used for specifying default value.");
case "ObjectPattern":
for (let prop of expr.properties) {
if (prop.type === "ObjectProperty") prop = prop.value;
this.checkLVal(prop, isBinding, checkClashes, "object destructuring pattern");
}
break;
case "MemberExpression":
if (!isBinding) break;
case "ArrayPattern":
for (const elem of expr.elements) {
if (elem) this.checkLVal(elem, isBinding, checkClashes, "array destructuring pattern");
}
break;
case "AssignmentPattern":
this.checkLVal(expr.left, isBinding, checkClashes, "assignment pattern");
break;
case "RestElement":
this.checkLVal(expr.argument, isBinding, checkClashes, "rest element");
break;
default: {
const message = "Invalid left-hand side" +
const message = (isBinding ? /* istanbul ignore next */ "Binding invalid" : "Invalid") +
" left-hand side" +
(contextDescription ? " in " + contextDescription : /* istanbul ignore next */ "expression");
this.raise(node.start, message);
this.raise(expr.start, message);
}
}
}
return node;
};
// Convert list of expression atoms to binding list.
pp.toAssignableList = function (exprList, isBinding, contextDescription) {
let end = exprList.length;
if (end) {
const last = exprList[end - 1];
if (last && last.type === "RestElement") {
--end;
} else if (last && last.type === "SpreadElement") {
last.type = "RestElement";
const arg = last.argument;
this.toAssignable(arg, isBinding, contextDescription);
if (arg.type !== "Identifier" && arg.type !== "MemberExpression" && arg.type !== "ArrayPattern") {
this.unexpected(arg.start);
}
--end;
}
}
for (let i = 0; i < end; i++) {
const elt = exprList[i];
if (elt && elt.type === "SpreadElement")
this.raise(elt.start, "The rest element has to be the last element when destructuring");
if (elt) this.toAssignable(elt, isBinding, contextDescription);
}
return exprList;
};
// Convert list of expression atoms to a list of
pp.toReferencedList = function (exprList) {
return exprList;
};
// Parses spread element.
pp.parseSpread = function (refShorthandDefaultPos) {
const node = this.startNode();
this.next();
node.argument = this.parseMaybeAssign(false, refShorthandDefaultPos);
return this.finishNode(node, "SpreadElement");
};
pp.parseRest = function () {
const node = this.startNode();
this.next();
node.argument = this.parseBindingAtom();
return this.finishNode(node, "RestElement");
};
pp.shouldAllowYieldIdentifier = function () {
return this.match(tt._yield) && !this.state.strict && !this.state.inGenerator;
};
pp.parseBindingIdentifier = function () {
return this.parseIdentifier(this.shouldAllowYieldIdentifier());
};
// Parses lvalue (assignable) atom.
pp.parseBindingAtom = function () {
switch (this.state.type) {
case tt._yield:
case tt.name:
return this.parseBindingIdentifier();
case tt.bracketL:
const node = this.startNode();
this.next();
node.elements = this.parseBindingList(tt.bracketR, true);
return this.finishNode(node, "ArrayPattern");
case tt.braceL:
return this.parseObj(true);
default:
this.unexpected();
}
};
pp.parseBindingList = function (close, allowEmpty) {
const elts = [];
let first = true;
while (!this.eat(close)) {
if (first) {
first = false;
} else {
this.expect(tt.comma);
}
if (allowEmpty && this.match(tt.comma)) {
elts.push(null);
} else if (this.eat(close)) {
break;
} else if (this.match(tt.ellipsis)) {
elts.push(this.parseAssignableListItemTypes(this.parseRest()));
this.expect(close);
break;
} else {
const decorators = [];
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
const left = this.parseMaybeDefault();
if (decorators.length) {
left.decorators = decorators;
}
this.parseAssignableListItemTypes(left);
elts.push(this.parseMaybeDefault(left.start, left.loc.start, left));
}
}
return elts;
};
pp.parseAssignableListItemTypes = function (param) {
return param;
};
// Parses assignment pattern around given atom if possible.
pp.parseMaybeDefault = function (startPos, startLoc, left) {
startLoc = startLoc || this.state.startLoc;
startPos = startPos || this.state.start;
left = left || this.parseBindingAtom();
if (!this.eat(tt.eq)) return left;
const node = this.startNodeAt(startPos, startLoc);
node.left = left;
node.right = this.parseMaybeAssign();
return this.finishNode(node, "AssignmentPattern");
};
// Verify that a node is an lval — something that can be assigned
// to.
pp.checkLVal = function (expr, isBinding, checkClashes, contextDescription) {
switch (expr.type) {
case "Identifier":
this.checkReservedWord(expr.name, expr.start, false, true);
if (checkClashes) {
// we need to prefix this with an underscore for the cases where we have a key of
// `__proto__`. there's a bug in old V8 where the following wouldn't work:
//
// > var obj = Object.create(null);
// undefined
// > obj.__proto__
// null
// > obj.__proto__ = true;
// true
// > obj.__proto__
// null
const key = `_${expr.name}`;
if (checkClashes[key]) {
this.raise(expr.start, "Argument name clash in strict mode");
} else {
checkClashes[key] = true;
}
}
break;
case "MemberExpression":
if (isBinding) this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " member expression");
break;
case "ObjectPattern":
for (let prop of (expr.properties: Array<Object>)) {
if (prop.type === "ObjectProperty") prop = prop.value;
this.checkLVal(prop, isBinding, checkClashes, "object destructuring pattern");
}
break;
case "ArrayPattern":
for (const elem of (expr.elements: Array<Object>)) {
if (elem) this.checkLVal(elem, isBinding, checkClashes, "array destructuring pattern");
}
break;
case "AssignmentPattern":
this.checkLVal(expr.left, isBinding, checkClashes, "assignment pattern");
break;
case "RestElement":
this.checkLVal(expr.argument, isBinding, checkClashes, "rest element");
break;
default: {
const message = (isBinding ? /* istanbul ignore next */ "Binding invalid" : "Invalid") +
" left-hand side" +
(contextDescription ? " in " + contextDescription : /* istanbul ignore next */ "expression");
this.raise(expr.start, message);
}
}
};
}

View File

@ -1,13 +1,16 @@
// @flow
import Parser from "./index";
import UtilParser from "./util";
import { SourceLocation, type Position } from "../util/location";
import type { Comment, Node as NodeType, NodeBase } from "../types";
// Start an AST node, attaching a start offset.
const pp = Parser.prototype;
const commentKeys = ["leadingComments", "trailingComments", "innerComments"];
class Node {
constructor(parser?: Parser, pos?: number, loc?: Position) {
class Node implements NodeBase {
constructor(parser: Parser, pos: number, loc: Position) {
this.type = "";
this.start = pos;
this.end = 0;
@ -17,15 +20,22 @@ class Node {
}
type: string;
start: ?number;
start: number;
end: number;
loc: SourceLocation;
range: [number, number];
leadingComments: ?Array<Comment>;
trailingComments: ?Array<Comment>;
innerComments: ?Array<Comment>;
extra: { [key: string]: any };
__clone(): Node {
const node2 = new Node;
__clone(): this {
// $FlowIgnore
const node2: any = new Node;
for (const key in this) {
// Do not clone comments that are already attached to the node
if (commentKeys.indexOf(key) < 0) {
// $FlowIgnore
node2[key] = this[key];
}
}
@ -34,43 +44,40 @@ class Node {
}
}
pp.startNode = function () {
return new Node(this, this.state.start, this.state.startLoc);
};
export class NodeUtils extends UtilParser {
startNode<T : NodeType>(): T {
// $FlowIgnore
return new Node(this, this.state.start, this.state.startLoc);
}
pp.startNodeAt = function (pos, loc) {
return new Node(this, pos, loc);
};
startNodeAt<T : NodeType>(pos: number, loc: Position): T {
// $FlowIgnore
return new Node(this, pos, loc);
}
function finishNodeAt(node, type, pos, loc) {
node.type = type;
node.end = pos;
node.loc.end = loc;
if (this.options.ranges) node.range[1] = pos;
this.processComment(node);
return node;
// Finish an AST node, adding `type` and `end` properties.
finishNode<T : NodeType>(node: T, type: string): T {
return this.finishNodeAt(node, type, this.state.lastTokEnd, this.state.lastTokEndLoc);
}
// Finish node at given position
finishNodeAt<T : NodeType>(node: T, type: string, pos: number, loc: Position): T {
node.type = type;
node.end = pos;
node.loc.end = loc;
if (this.options.ranges) node.range[1] = pos;
this.processComment(node);
return node;
}
/**
* Reset the start location of node to the start location of locationNode
*/
resetStartLocationFromNode(node: NodeBase, locationNode: NodeBase): void {
node.start = locationNode.start;
node.loc.start = locationNode.loc.start;
if (this.options.ranges) node.range[0] = locationNode.range[0];
}
}
// Finish an AST node, adding `type` and `end` properties.
pp.finishNode = function (node, type) {
return finishNodeAt.call(this, node, type, this.state.lastTokEnd, this.state.lastTokEndLoc);
};
// Finish node at given position
pp.finishNodeAt = function (node, type, pos, loc) {
return finishNodeAt.call(this, node, type, pos, loc);
};
/**
* Reset the start location of node to the start location of locationNode
*/
pp.resetStartLocationFromNode = function (node, locationNode) {
node.start = locationNode.start;
node.loc.start = locationNode.loc.start;
if (this.options.ranges) node.range[0] = locationNode.range[0];
return node;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,88 +1,91 @@
import { types as tt } from "../tokenizer/types";
import Parser from "./index";
import { lineBreak } from "../util/whitespace";
// @flow
const pp = Parser.prototype;
import { types as tt, type TokenType } from "../tokenizer/types";
import Tokenizer from "../tokenizer";
import type { Node } from "../types";
import { lineBreak } from "../util/whitespace";
// ## Parser utilities
// TODO
export default class UtilParser extends Tokenizer {
// TODO
pp.addExtra = function (node, key, val) {
if (!node) return;
addExtra(node: Node, key: string, val: any): void {
if (!node) return;
const extra = node.extra = node.extra || {};
extra[key] = val;
};
// TODO
pp.isRelational = function (op) {
return this.match(tt.relational) && this.state.value === op;
};
// TODO
pp.expectRelational = function (op) {
if (this.isRelational(op)) {
this.next();
} else {
this.unexpected(null, tt.relational);
const extra = node.extra = node.extra || {};
extra[key] = val;
}
};
// Tests whether parsed token is a contextual keyword.
// TODO
pp.isContextual = function (name) {
return this.match(tt.name) && this.state.value === name;
};
// Consumes contextual keyword if possible.
pp.eatContextual = function (name) {
return this.state.value === name && this.eat(tt.name);
};
// Asserts that following token is given contextual keyword.
pp.expectContextual = function (name, message) {
if (!this.eatContextual(name)) this.unexpected(null, message);
};
// Test whether a semicolon can be inserted at the current position.
pp.canInsertSemicolon = function () {
return this.match(tt.eof) ||
this.match(tt.braceR) ||
lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start));
};
// TODO
pp.isLineTerminator = function () {
return this.eat(tt.semi) || this.canInsertSemicolon();
};
// Consume a semicolon, or, failing that, see if we are allowed to
// pretend that there is a semicolon at this position.
pp.semicolon = function () {
if (!this.isLineTerminator()) this.unexpected(null, tt.semi);
};
// Expect a token of a given type. If found, consume it, otherwise,
// raise an unexpected token error at given pos.
pp.expect = function (type, pos) {
return this.eat(type) || this.unexpected(pos, type);
};
// Raise an unexpected token error. Can take the expected token type
// instead of a message string.
pp.unexpected = function (pos, messageOrType = "Unexpected token") {
if (messageOrType && typeof messageOrType === "object" && messageOrType.label) {
messageOrType = `Unexpected token, expected ${messageOrType.label}`;
isRelational(op: "<" | ">"): boolean {
return this.match(tt.relational) && this.state.value === op;
}
this.raise(pos != null ? pos : this.state.start, messageOrType);
};
// TODO
expectRelational(op: "<" | ">"): void {
if (this.isRelational(op)) {
this.next();
} else {
this.unexpected(null, tt.relational);
}
}
// Tests whether parsed token is a contextual keyword.
isContextual(name: string): boolean {
return this.match(tt.name) && this.state.value === name;
}
// Consumes contextual keyword if possible.
eatContextual(name: string): boolean {
return this.state.value === name && this.eat(tt.name);
}
// Asserts that following token is given contextual keyword.
expectContextual(name: string, message?: string): void {
if (!this.eatContextual(name)) this.unexpected(null, message);
}
// Test whether a semicolon can be inserted at the current position.
canInsertSemicolon(): boolean {
return this.match(tt.eof) ||
this.match(tt.braceR) ||
lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start));
}
// TODO
isLineTerminator(): boolean {
return this.eat(tt.semi) || this.canInsertSemicolon();
}
// Consume a semicolon, or, failing that, see if we are allowed to
// pretend that there is a semicolon at this position.
semicolon(): void {
if (!this.isLineTerminator()) this.unexpected(null, tt.semi);
}
// Expect a token of a given type. If found, consume it, otherwise,
// raise an unexpected token error at given pos.
expect(type: TokenType, pos?: ?number): void {
this.eat(type) || this.unexpected(pos, type);
}
// Raise an unexpected token error. Can take the expected token type
// instead of a message string.
unexpected(pos: ?number, messageOrType: string | TokenType = "Unexpected token"): empty {
if (typeof messageOrType !== "string") {
messageOrType = `Unexpected token, expected ${messageOrType.label}`;
}
throw this.raise(pos != null ? pos : this.state.start, messageOrType);
}
}

View File

@ -1,248 +1,234 @@
// @flow
import { types as tt } from "../tokenizer/types";
import Parser from "../parser";
import type Parser from "../parser";
import * as N from "../types";
const pp = Parser.prototype;
pp.estreeParseRegExpLiteral = function ({ pattern, flags }) {
let regex = null;
try {
regex = new RegExp(pattern, flags);
} catch (e) {
// In environments that don't support these flags value will
// be null as the regex can't be represented natively.
}
const node = this.estreeParseLiteral(regex);
node.regex = { pattern, flags };
return node;
};
pp.estreeParseLiteral = function (value) {
return this.parseLiteral(value, "Literal");
};
pp.directiveToStmt = function (directive) {
const directiveLiteral = directive.value;
const stmt = this.startNodeAt(directive.start, directive.loc.start);
const expression = this.startNodeAt(directiveLiteral.start, directiveLiteral.loc.start);
expression.value = directiveLiteral.value;
expression.raw = directiveLiteral.extra.raw;
stmt.expression = this.finishNodeAt(expression, "Literal", directiveLiteral.end, directiveLiteral.loc.end);
stmt.directive = directiveLiteral.extra.raw.slice(1, -1);
return this.finishNodeAt(stmt, "ExpressionStatement", directive.end, directive.loc.end);
};
function isSimpleProperty(node) {
return node &&
function isSimpleProperty(node: N.Node): boolean {
return node != null &&
node.type === "Property" &&
node.kind === "init" &&
node.method === false;
}
export default function (instance) {
instance.extend("checkDeclaration", function(inner) {
return function (node) {
if (isSimpleProperty(node)) {
this.checkDeclaration(node.value);
export default (superClass: Class<Parser>): Class<Parser> => class extends superClass {
estreeParseRegExpLiteral({ pattern, flags }: N.RegExpLiteral): N.Node {
let regex = null;
try {
regex = new RegExp(pattern, flags);
} catch (e) {
// In environments that don't support these flags value will
// be null as the regex can't be represented natively.
}
const node = this.estreeParseLiteral(regex);
node.regex = { pattern, flags };
return node;
}
estreeParseLiteral(value: any): N.Node {
return this.parseLiteral(value, "Literal");
}
directiveToStmt(directive: N.Directive): N.ExpressionStatement {
const directiveLiteral = directive.value;
const stmt = this.startNodeAt(directive.start, directive.loc.start);
const expression = this.startNodeAt(directiveLiteral.start, directiveLiteral.loc.start);
expression.value = directiveLiteral.value;
expression.raw = directiveLiteral.extra.raw;
stmt.expression = this.finishNodeAt(
expression, "Literal", directiveLiteral.end, directiveLiteral.loc.end);
stmt.directive = directiveLiteral.extra.raw.slice(1, -1);
return this.finishNodeAt(stmt, "ExpressionStatement", directive.end, directive.loc.end);
}
// ==================================
// Overrides
// ==================================
checkDeclaration(node: N.Pattern): void {
if (isSimpleProperty(node)) {
// $FlowFixMe
this.checkDeclaration(node.value);
} else {
super.checkDeclaration(node);
}
}
checkGetterSetterParamCount(prop: N.ObjectMethod | N.ClassMethod): void {
const paramCount = prop.kind === "get" ? 0 : 1;
// $FlowFixMe (prop.value present for ObjectMethod, but for ClassMethod should use prop.params?)
if (prop.value.params.length !== paramCount) {
const start = prop.start;
if (prop.kind === "get") {
this.raise(start, "getter should have no params");
} else {
inner.call(this, node);
this.raise(start, "setter should have exactly one param");
}
};
});
}
}
instance.extend("checkGetterSetterParamCount", function() {
return function (prop) {
const paramCount = prop.kind === "get" ? 0 : 1;
if (prop.value.params.length !== paramCount) {
const start = prop.start;
if (prop.kind === "get") {
this.raise(start, "getter should have no params");
checkLVal(
expr: N.Expression, isBinding: ?boolean, checkClashes: ?{ [key: string]: boolean }, ...args): void {
switch (expr.type) {
case "ObjectPattern":
expr.properties.forEach((prop) => {
this.checkLVal(
prop.type === "Property" ? prop.value : prop,
isBinding,
checkClashes,
"object destructuring pattern"
);
});
break;
default:
super.checkLVal(expr, isBinding, checkClashes, ...args);
}
}
checkPropClash(prop: N.ObjectMember, propHash: { [key: string]: boolean }): void {
if (prop.computed || !isSimpleProperty(prop)) return;
const key = prop.key;
// It is either an Identifier or a String/NumericLiteral
const name = key.type === "Identifier" ? key.name : String(key.value);
if (name === "__proto__") {
if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property");
propHash.proto = true;
}
}
isStrictBody(node: { body: N.BlockStatement }, isExpression?: boolean): boolean {
if (!isExpression && node.body.body.length > 0) {
for (const directive of node.body.body) {
if (directive.type === "ExpressionStatement" && directive.expression.type === "Literal") {
if (directive.expression.value === "use strict") return true;
} else {
this.raise(start, "setter should have exactly one param");
}
}
};
});
instance.extend("checkLVal", function(inner) {
return function (expr, isBinding, checkClashes, ...args) {
switch (expr.type) {
case "ObjectPattern":
expr.properties.forEach((prop) => {
this.checkLVal(
prop.type === "Property" ? prop.value : prop,
isBinding,
checkClashes,
"object destructuring pattern"
);
});
// Break for the first non literal expression
break;
default:
inner.call(this, expr, isBinding, checkClashes, ...args);
}
}
};
});
}
instance.extend("checkPropClash", function () {
return function (prop, propHash) {
if (prop.computed || !isSimpleProperty(prop)) return;
return false;
}
const key = prop.key;
// It is either an Identifier or a String/NumericLiteral
const name = key.type === "Identifier" ? key.name : String(key.value);
isValidDirective(stmt: N.Statement): boolean {
return stmt.type === "ExpressionStatement" &&
stmt.expression.type === "Literal" &&
typeof stmt.expression.value === "string" &&
(!stmt.expression.extra || !stmt.expression.extra.parenthesized);
}
if (name === "__proto__") {
if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property");
propHash.proto = true;
}
};
});
parseBlockBody(node: N.BlockStatementLike, ...args): void {
super.parseBlockBody(node, ...args);
instance.extend("isStrictBody", function () {
return function (node, isExpression) {
if (!isExpression && node.body.body.length > 0) {
for (const directive of (node.body.body: Array<Object>)) {
if (directive.type === "ExpressionStatement" && directive.expression.type === "Literal") {
if (directive.expression.value === "use strict") return true;
} else {
// Break for the first non literal expression
break;
}
const directiveStatements = node.directives.map((d) => this.directiveToStmt(d));
node.body = directiveStatements.concat(node.body);
delete node.directives;
}
parseClassMethod(classBody: N.ClassBody, ...args) {
super.parseClassMethod(classBody, ...args);
const body = classBody.body;
// $FlowIgnore
body[body.length - 1].type = "MethodDefinition";
}
parseExprAtom(...args): N.Expression {
switch (this.state.type) {
case tt.regexp:
return this.estreeParseRegExpLiteral(this.state.value);
case tt.num:
case tt.string:
return this.estreeParseLiteral(this.state.value);
case tt._null:
return this.estreeParseLiteral(null);
case tt._true:
return this.estreeParseLiteral(true);
case tt._false:
return this.estreeParseLiteral(false);
default:
return super.parseExprAtom(...args);
}
}
parseLiteral<T : N.Literal>(...args): T {
const node = super.parseLiteral(...args);
node.raw = node.extra.raw;
delete node.extra;
return node;
}
parseMethod(node: N.MethodLike, ...args): N.MethodLike {
let funcNode = this.startNode();
funcNode.kind = node.kind; // provide kind, so super method correctly sets state
funcNode = super.parseMethod(funcNode, ...args);
delete funcNode.kind;
// $FlowIgnore
node.value = this.finishNode(funcNode, "FunctionExpression");
return node;
}
parseObjectMethod(...args): ?N.ObjectMethod {
const node = super.parseObjectMethod(...args);
if (node) {
// $FlowIgnore
if (node.kind === "method") node.kind = "init";
// $FlowIgnore
node.type = "Property";
}
return node;
}
parseObjectProperty(...args): ?N.ObjectProperty {
const node = super.parseObjectProperty(...args);
if (node) {
// $FlowIgnore
node.kind = "init";
// $FlowIgnore
node.type = "Property";
}
return node;
}
toAssignable(node: N.Node, isBinding: ?boolean, ...args): N.Node {
if (isSimpleProperty(node)) {
this.toAssignable(node.value, isBinding, ...args);
return node;
} else if (node.type === "ObjectExpression") {
node.type = "ObjectPattern";
for (const prop of node.properties) {
if (prop.kind === "get" || prop.kind === "set") {
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
} else if (prop.method) {
this.raise(prop.key.start, "Object pattern can't contain methods");
} else {
this.toAssignable(prop, isBinding, "object destructuring pattern");
}
}
return false;
};
});
instance.extend("isValidDirective", function () {
return function (stmt) {
return stmt.type === "ExpressionStatement" &&
stmt.expression.type === "Literal" &&
typeof stmt.expression.value === "string" &&
(!stmt.expression.extra || !stmt.expression.extra.parenthesized);
};
});
instance.extend("parseBlockBody", function (inner) {
return function (node, ...args) {
inner.call(this, node, ...args);
node.directives.reverse().forEach((directive) => {
node.body.unshift(this.directiveToStmt(directive));
});
delete node.directives;
};
});
instance.extend("parseClassMethod", function (inner) {
return function (classBody, ...args) {
inner.call(this, classBody, ...args);
const body = classBody.body;
body[body.length - 1].type = "MethodDefinition";
};
});
instance.extend("parseExprAtom", function(inner) {
return function (...args) {
switch (this.state.type) {
case tt.regexp:
return this.estreeParseRegExpLiteral(this.state.value);
case tt.num:
case tt.string:
return this.estreeParseLiteral(this.state.value);
case tt._null:
return this.estreeParseLiteral(null);
case tt._true:
return this.estreeParseLiteral(true);
case tt._false:
return this.estreeParseLiteral(false);
default:
return inner.call(this, ...args);
}
};
});
instance.extend("parseLiteral", function(inner) {
return function (...args) {
const node = inner.call(this, ...args);
node.raw = node.extra.raw;
delete node.extra;
return node;
};
});
}
instance.extend("parseMethod", function(inner) {
return function (node, ...args) {
let funcNode = this.startNode();
funcNode.kind = node.kind; // provide kind, so inner method correctly sets state
funcNode = inner.call(this, funcNode, ...args);
delete funcNode.kind;
node.value = this.finishNode(funcNode, "FunctionExpression");
return node;
};
});
instance.extend("parseObjectMethod", function(inner) {
return function (...args) {
const node = inner.call(this, ...args);
if (node) {
if (node.kind === "method") node.kind = "init";
node.type = "Property";
}
return node;
};
});
instance.extend("parseObjectProperty", function(inner) {
return function (...args) {
const node = inner.call(this, ...args);
if (node) {
node.kind = "init";
node.type = "Property";
}
return node;
};
});
instance.extend("toAssignable", function(inner) {
return function (node, isBinding, ...args) {
if (isSimpleProperty(node)) {
this.toAssignable(node.value, isBinding, ...args);
return node;
} else if (node.type === "ObjectExpression") {
node.type = "ObjectPattern";
for (const prop of (node.properties: Array<Object>)) {
if (prop.kind === "get" || prop.kind === "set") {
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
} else if (prop.method) {
this.raise(prop.key.start, "Object pattern can't contain methods");
} else {
this.toAssignable(prop, isBinding, "object destructuring pattern");
}
}
return node;
}
return inner.call(this, node, isBinding, ...args);
};
});
}
return super.toAssignable(node, isBinding, ...args);
}
};

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,12 @@
// @flow
import XHTMLEntities from "./xhtml";
import type Parser from "../../parser";
import { TokenType, types as tt } from "../../tokenizer/types";
import { TokContext, types as tc } from "../../tokenizer/context";
import Parser from "../../parser";
import * as N from "../../types";
import { isIdentifierChar, isIdentifierStart } from "../../util/identifier";
import type { Pos, Position } from "../../util/location";
import { isNewLine } from "../../util/whitespace";
const HEX_NUMBER = /^[\da-fA-F]+$/;
@ -33,147 +37,9 @@ tt.jsxTagEnd.updateContext = function(prevType) {
}
};
const pp = Parser.prototype;
// Reads inline JSX contents token.
pp.jsxReadToken = function() {
let out = "";
let chunkStart = this.state.pos;
for (;;) {
if (this.state.pos >= this.input.length) {
this.raise(this.state.start, "Unterminated JSX contents");
}
const ch = this.input.charCodeAt(this.state.pos);
switch (ch) {
case 60: // "<"
case 123: // "{"
if (this.state.pos === this.state.start) {
if (ch === 60 && this.state.exprAllowed) {
++this.state.pos;
return this.finishToken(tt.jsxTagStart);
}
return this.getTokenFromCode(ch);
}
out += this.input.slice(chunkStart, this.state.pos);
return this.finishToken(tt.jsxText, out);
case 38: // "&"
out += this.input.slice(chunkStart, this.state.pos);
out += this.jsxReadEntity();
chunkStart = this.state.pos;
break;
default:
if (isNewLine(ch)) {
out += this.input.slice(chunkStart, this.state.pos);
out += this.jsxReadNewLine(true);
chunkStart = this.state.pos;
} else {
++this.state.pos;
}
}
}
};
pp.jsxReadNewLine = function(normalizeCRLF) {
const ch = this.input.charCodeAt(this.state.pos);
let out;
++this.state.pos;
if (ch === 13 && this.input.charCodeAt(this.state.pos) === 10) {
++this.state.pos;
out = normalizeCRLF ? "\n" : "\r\n";
} else {
out = String.fromCharCode(ch);
}
++this.state.curLine;
this.state.lineStart = this.state.pos;
return out;
};
pp.jsxReadString = function(quote) {
let out = "";
let chunkStart = ++this.state.pos;
for (;;) {
if (this.state.pos >= this.input.length) {
this.raise(this.state.start, "Unterminated string constant");
}
const ch = this.input.charCodeAt(this.state.pos);
if (ch === quote) break;
if (ch === 38) { // "&"
out += this.input.slice(chunkStart, this.state.pos);
out += this.jsxReadEntity();
chunkStart = this.state.pos;
} else if (isNewLine(ch)) {
out += this.input.slice(chunkStart, this.state.pos);
out += this.jsxReadNewLine(false);
chunkStart = this.state.pos;
} else {
++this.state.pos;
}
}
out += this.input.slice(chunkStart, this.state.pos++);
return this.finishToken(tt.string, out);
};
pp.jsxReadEntity = function() {
let str = "";
let count = 0;
let entity;
let ch = this.input[this.state.pos];
const startPos = ++this.state.pos;
while (this.state.pos < this.input.length && count++ < 10) {
ch = this.input[this.state.pos++];
if (ch === ";") {
if (str[0] === "#") {
if (str[1] === "x") {
str = str.substr(2);
if (HEX_NUMBER.test(str))
entity = String.fromCodePoint(parseInt(str, 16));
} else {
str = str.substr(1);
if (DECIMAL_NUMBER.test(str))
entity = String.fromCodePoint(parseInt(str, 10));
}
} else {
entity = XHTMLEntities[str];
}
break;
}
str += ch;
}
if (!entity) {
this.state.pos = startPos;
return "&";
}
return entity;
};
// Read a JSX identifier (valid tag or attribute name).
//
// Optimized version since JSX identifiers can"t contain
// escape characters and so can be read as single slice.
// Also assumes that first character was already checked
// by isIdentifierStart in readToken.
pp.jsxReadWord = function() {
let ch;
const start = this.state.pos;
do {
ch = this.input.charCodeAt(++this.state.pos);
} while (isIdentifierChar(ch) || ch === 45); // "-"
return this.finishToken(tt.jsxName, this.input.slice(start, this.state.pos));
};
// Transforms JSX element name to string.
function getQualifiedJSXName(object) {
function getQualifiedJSXName(object: N.JSXIdentifier | N.JSXNamespacedName | N.JSXMemberExpression): string {
if (object.type === "JSXIdentifier") {
return object.name;
}
@ -185,282 +51,421 @@ function getQualifiedJSXName(object) {
if (object.type === "JSXMemberExpression") {
return getQualifiedJSXName(object.object) + "." + getQualifiedJSXName(object.property);
}
// istanbul ignore next
throw new Error("Node had unexpected type: " + object.type);
}
// Parse next token as JSX identifier
export default (superClass: Class<Parser>): Class<Parser> => class extends superClass {
// Reads inline JSX contents token.
pp.jsxParseIdentifier = function() {
const node = this.startNode();
if (this.match(tt.jsxName)) {
node.name = this.state.value;
} else if (this.state.type.keyword) {
node.name = this.state.type.keyword;
} else {
this.unexpected();
}
this.next();
return this.finishNode(node, "JSXIdentifier");
};
// Parse namespaced identifier.
pp.jsxParseNamespacedName = function() {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
const name = this.jsxParseIdentifier();
if (!this.eat(tt.colon)) return name;
const node = this.startNodeAt(startPos, startLoc);
node.namespace = name;
node.name = this.jsxParseIdentifier();
return this.finishNode(node, "JSXNamespacedName");
};
// Parses element name in any form - namespaced, member
// or single identifier.
pp.jsxParseElementName = function() {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
let node = this.jsxParseNamespacedName();
while (this.eat(tt.dot)) {
const newNode = this.startNodeAt(startPos, startLoc);
newNode.object = node;
newNode.property = this.jsxParseIdentifier();
node = this.finishNode(newNode, "JSXMemberExpression");
}
return node;
};
// Parses any type of JSX attribute value.
pp.jsxParseAttributeValue = function() {
let node;
switch (this.state.type) {
case tt.braceL:
node = this.jsxParseExpressionContainer();
if (node.expression.type === "JSXEmptyExpression") {
this.raise(node.start, "JSX attributes must only be assigned a non-empty expression");
} else {
return node;
jsxReadToken(): void {
let out = "";
let chunkStart = this.state.pos;
for (;;) {
if (this.state.pos >= this.input.length) {
this.raise(this.state.start, "Unterminated JSX contents");
}
case tt.jsxTagStart:
case tt.string:
return this.parseExprAtom();
const ch = this.input.charCodeAt(this.state.pos);
default:
this.raise(this.state.start, "JSX value should be either an expression or a quoted JSX text");
}
};
// JSXEmptyExpression is unique type since it doesn't actually parse anything,
// and so it should start at the end of last read token (left brace) and finish
// at the beginning of the next one (right brace).
pp.jsxParseEmptyExpression = function() {
const node = this.startNodeAt(this.state.lastTokEnd, this.state.lastTokEndLoc);
return this.finishNodeAt(node, "JSXEmptyExpression", this.state.start, this.state.startLoc);
};
// Parse JSX spread child
pp.jsxParseSpreadChild = function() {
const node = this.startNode();
this.expect(tt.braceL);
this.expect(tt.ellipsis);
node.expression = this.parseExpression();
this.expect(tt.braceR);
return this.finishNode(node, "JSXSpreadChild");
};
// Parses JSX expression enclosed into curly brackets.
pp.jsxParseExpressionContainer = function() {
const node = this.startNode();
this.next();
if (this.match(tt.braceR)) {
node.expression = this.jsxParseEmptyExpression();
} else {
node.expression = this.parseExpression();
}
this.expect(tt.braceR);
return this.finishNode(node, "JSXExpressionContainer");
};
// Parses following JSX attribute name-value pair.
pp.jsxParseAttribute = function() {
const node = this.startNode();
if (this.eat(tt.braceL)) {
this.expect(tt.ellipsis);
node.argument = this.parseMaybeAssign();
this.expect(tt.braceR);
return this.finishNode(node, "JSXSpreadAttribute");
}
node.name = this.jsxParseNamespacedName();
node.value = this.eat(tt.eq) ? this.jsxParseAttributeValue() : null;
return this.finishNode(node, "JSXAttribute");
};
// Parses JSX opening tag starting after "<".
pp.jsxParseOpeningElementAt = function(startPos, startLoc) {
const node = this.startNodeAt(startPos, startLoc);
node.attributes = [];
node.name = this.jsxParseElementName();
while (!this.match(tt.slash) && !this.match(tt.jsxTagEnd)) {
node.attributes.push(this.jsxParseAttribute());
}
node.selfClosing = this.eat(tt.slash);
this.expect(tt.jsxTagEnd);
return this.finishNode(node, "JSXOpeningElement");
};
// Parses JSX closing tag starting after "</".
pp.jsxParseClosingElementAt = function(startPos, startLoc) {
const node = this.startNodeAt(startPos, startLoc);
node.name = this.jsxParseElementName();
this.expect(tt.jsxTagEnd);
return this.finishNode(node, "JSXClosingElement");
};
// Parses entire JSX element, including it"s opening tag
// (starting after "<"), attributes, contents and closing tag.
pp.jsxParseElementAt = function(startPos, startLoc) {
const node = this.startNodeAt(startPos, startLoc);
const children = [];
const openingElement = this.jsxParseOpeningElementAt(startPos, startLoc);
let closingElement = null;
if (!openingElement.selfClosing) {
contents: for (;;) {
switch (this.state.type) {
case tt.jsxTagStart:
startPos = this.state.start; startLoc = this.state.startLoc;
this.next();
if (this.eat(tt.slash)) {
closingElement = this.jsxParseClosingElementAt(startPos, startLoc);
break contents;
switch (ch) {
case 60: // "<"
case 123: // "{"
if (this.state.pos === this.state.start) {
if (ch === 60 && this.state.exprAllowed) {
++this.state.pos;
return this.finishToken(tt.jsxTagStart);
}
return this.getTokenFromCode(ch);
}
children.push(this.jsxParseElementAt(startPos, startLoc));
out += this.input.slice(chunkStart, this.state.pos);
return this.finishToken(tt.jsxText, out);
case 38: // "&"
out += this.input.slice(chunkStart, this.state.pos);
out += this.jsxReadEntity();
chunkStart = this.state.pos;
break;
case tt.jsxText:
children.push(this.parseExprAtom());
break;
case tt.braceL:
if (this.lookahead().type === tt.ellipsis) {
children.push(this.jsxParseSpreadChild());
} else {
children.push(this.jsxParseExpressionContainer());
}
break;
// istanbul ignore next - should never happen
default:
this.unexpected();
if (isNewLine(ch)) {
out += this.input.slice(chunkStart, this.state.pos);
out += this.jsxReadNewLine(true);
chunkStart = this.state.pos;
} else {
++this.state.pos;
}
}
}
}
if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {
this.raise(
closingElement.start,
"Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">"
);
jsxReadNewLine(normalizeCRLF: boolean): string {
const ch = this.input.charCodeAt(this.state.pos);
let out;
++this.state.pos;
if (ch === 13 && this.input.charCodeAt(this.state.pos) === 10) {
++this.state.pos;
out = normalizeCRLF ? "\n" : "\r\n";
} else {
out = String.fromCharCode(ch);
}
++this.state.curLine;
this.state.lineStart = this.state.pos;
return out;
}
node.openingElement = openingElement;
node.closingElement = closingElement;
node.children = children;
if (this.match(tt.relational) && this.state.value === "<") {
this.raise(this.state.start, "Adjacent JSX elements must be wrapped in an enclosing tag");
}
return this.finishNode(node, "JSXElement");
};
jsxReadString(quote: number): void {
let out = "";
let chunkStart = ++this.state.pos;
for (;;) {
if (this.state.pos >= this.input.length) {
this.raise(this.state.start, "Unterminated string constant");
}
// Parses entire JSX element from current position.
pp.jsxParseElement = function() {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
this.next();
return this.jsxParseElementAt(startPos, startLoc);
};
export default function(instance) {
instance.extend("parseExprAtom", function(inner) {
return function(refShortHandDefaultPos) {
if (this.match(tt.jsxText)) {
return this.parseLiteral(this.state.value, "JSXText");
} else if (this.match(tt.jsxTagStart)) {
return this.jsxParseElement();
const ch = this.input.charCodeAt(this.state.pos);
if (ch === quote) break;
if (ch === 38) { // "&"
out += this.input.slice(chunkStart, this.state.pos);
out += this.jsxReadEntity();
chunkStart = this.state.pos;
} else if (isNewLine(ch)) {
out += this.input.slice(chunkStart, this.state.pos);
out += this.jsxReadNewLine(false);
chunkStart = this.state.pos;
} else {
return inner.call(this, refShortHandDefaultPos);
}
};
});
instance.extend("readToken", function(inner) {
return function(code) {
if (this.state.inPropertyName) return inner.call(this, code);
const context = this.curContext();
if (context === tc.j_expr) {
return this.jsxReadToken();
}
if (context === tc.j_oTag || context === tc.j_cTag) {
if (isIdentifierStart(code)) {
return this.jsxReadWord();
}
if (code === 62) {
++this.state.pos;
return this.finishToken(tt.jsxTagEnd);
}
if ((code === 34 || code === 39) && context === tc.j_oTag) {
return this.jsxReadString(code);
}
}
if (code === 60 && this.state.exprAllowed) {
++this.state.pos;
return this.finishToken(tt.jsxTagStart);
}
}
out += this.input.slice(chunkStart, this.state.pos++);
return this.finishToken(tt.string, out);
}
return inner.call(this, code);
};
});
jsxReadEntity(): string {
let str = "";
let count = 0;
let entity;
let ch = this.input[this.state.pos];
instance.extend("updateContext", function(inner) {
return function(prevType) {
if (this.match(tt.braceL)) {
const curContext = this.curContext();
if (curContext === tc.j_oTag) {
this.state.context.push(tc.braceExpression);
} else if (curContext === tc.j_expr) {
this.state.context.push(tc.templateQuasi);
const startPos = ++this.state.pos;
while (this.state.pos < this.input.length && count++ < 10) {
ch = this.input[this.state.pos++];
if (ch === ";") {
if (str[0] === "#") {
if (str[1] === "x") {
str = str.substr(2);
if (HEX_NUMBER.test(str))
entity = String.fromCodePoint(parseInt(str, 16));
} else {
str = str.substr(1);
if (DECIMAL_NUMBER.test(str))
entity = String.fromCodePoint(parseInt(str, 10));
}
} else {
inner.call(this, prevType);
entity = XHTMLEntities[str];
}
this.state.exprAllowed = true;
} else if (this.match(tt.slash) && prevType === tt.jsxTagStart) {
this.state.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore
this.state.context.push(tc.j_cTag); // reconsider as closing tag context
this.state.exprAllowed = false;
} else {
return inner.call(this, prevType);
break;
}
};
});
}
str += ch;
}
if (!entity) {
this.state.pos = startPos;
return "&";
}
return entity;
}
// Read a JSX identifier (valid tag or attribute name).
//
// Optimized version since JSX identifiers can"t contain
// escape characters and so can be read as single slice.
// Also assumes that first character was already checked
// by isIdentifierStart in readToken.
jsxReadWord(): void {
let ch;
const start = this.state.pos;
do {
ch = this.input.charCodeAt(++this.state.pos);
} while (isIdentifierChar(ch) || ch === 45); // "-"
return this.finishToken(tt.jsxName, this.input.slice(start, this.state.pos));
}
// Parse next token as JSX identifier
jsxParseIdentifier(): N.JSXIdentifier {
const node = this.startNode();
if (this.match(tt.jsxName)) {
node.name = this.state.value;
} else if (this.state.type.keyword) {
node.name = this.state.type.keyword;
} else {
this.unexpected();
}
this.next();
return this.finishNode(node, "JSXIdentifier");
}
// Parse namespaced identifier.
jsxParseNamespacedName(): N.JSXNamespacedName {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
const name = this.jsxParseIdentifier();
if (!this.eat(tt.colon)) return name;
const node = this.startNodeAt(startPos, startLoc);
node.namespace = name;
node.name = this.jsxParseIdentifier();
return this.finishNode(node, "JSXNamespacedName");
}
// Parses element name in any form - namespaced, member
// or single identifier.
jsxParseElementName(): N.JSXNamespacedName | N.JSXMemberExpression {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
let node = this.jsxParseNamespacedName();
while (this.eat(tt.dot)) {
const newNode = this.startNodeAt(startPos, startLoc);
newNode.object = node;
newNode.property = this.jsxParseIdentifier();
node = this.finishNode(newNode, "JSXMemberExpression");
}
return node;
}
// Parses any type of JSX attribute value.
jsxParseAttributeValue(): N.Expression {
let node;
switch (this.state.type) {
case tt.braceL:
node = this.jsxParseExpressionContainer();
if (node.expression.type === "JSXEmptyExpression") {
throw this.raise(node.start, "JSX attributes must only be assigned a non-empty expression");
} else {
return node;
}
case tt.jsxTagStart:
case tt.string:
return this.parseExprAtom();
default:
throw this.raise(this.state.start, "JSX value should be either an expression or a quoted JSX text");
}
}
// JSXEmptyExpression is unique type since it doesn't actually parse anything,
// and so it should start at the end of last read token (left brace) and finish
// at the beginning of the next one (right brace).
jsxParseEmptyExpression(): N.JSXEmptyExpression {
const node = this.startNodeAt(this.state.lastTokEnd, this.state.lastTokEndLoc);
return this.finishNodeAt(node, "JSXEmptyExpression", this.state.start, this.state.startLoc);
}
// Parse JSX spread child
jsxParseSpreadChild(): N.JSXSpreadChild {
const node = this.startNode();
this.expect(tt.braceL);
this.expect(tt.ellipsis);
node.expression = this.parseExpression();
this.expect(tt.braceR);
return this.finishNode(node, "JSXSpreadChild");
}
// Parses JSX expression enclosed into curly brackets.
jsxParseExpressionContainer(): N.JSXExpressionContainer {
const node = this.startNode();
this.next();
if (this.match(tt.braceR)) {
node.expression = this.jsxParseEmptyExpression();
} else {
node.expression = this.parseExpression();
}
this.expect(tt.braceR);
return this.finishNode(node, "JSXExpressionContainer");
}
// Parses following JSX attribute name-value pair.
jsxParseAttribute(): N.JSXAttribute {
const node = this.startNode();
if (this.eat(tt.braceL)) {
this.expect(tt.ellipsis);
node.argument = this.parseMaybeAssign();
this.expect(tt.braceR);
return this.finishNode(node, "JSXSpreadAttribute");
}
node.name = this.jsxParseNamespacedName();
node.value = this.eat(tt.eq) ? this.jsxParseAttributeValue() : null;
return this.finishNode(node, "JSXAttribute");
}
// Parses JSX opening tag starting after "<".
jsxParseOpeningElementAt(startPos: number, startLoc: Position): N.JSXOpeningElement {
const node = this.startNodeAt(startPos, startLoc);
node.attributes = [];
node.name = this.jsxParseElementName();
while (!this.match(tt.slash) && !this.match(tt.jsxTagEnd)) {
node.attributes.push(this.jsxParseAttribute());
}
node.selfClosing = this.eat(tt.slash);
this.expect(tt.jsxTagEnd);
return this.finishNode(node, "JSXOpeningElement");
}
// Parses JSX closing tag starting after "</".
jsxParseClosingElementAt(startPos: number, startLoc: Position): N.JSXClosingElement {
const node = this.startNodeAt(startPos, startLoc);
node.name = this.jsxParseElementName();
this.expect(tt.jsxTagEnd);
return this.finishNode(node, "JSXClosingElement");
}
// Parses entire JSX element, including it"s opening tag
// (starting after "<"), attributes, contents and closing tag.
jsxParseElementAt(startPos: number, startLoc: Position): N.JSXElement {
const node = this.startNodeAt(startPos, startLoc);
const children = [];
const openingElement = this.jsxParseOpeningElementAt(startPos, startLoc);
let closingElement = null;
if (!openingElement.selfClosing) {
contents: for (;;) {
switch (this.state.type) {
case tt.jsxTagStart:
startPos = this.state.start; startLoc = this.state.startLoc;
this.next();
if (this.eat(tt.slash)) {
closingElement = this.jsxParseClosingElementAt(startPos, startLoc);
break contents;
}
children.push(this.jsxParseElementAt(startPos, startLoc));
break;
case tt.jsxText:
children.push(this.parseExprAtom());
break;
case tt.braceL:
if (this.lookahead().type === tt.ellipsis) {
children.push(this.jsxParseSpreadChild());
} else {
children.push(this.jsxParseExpressionContainer());
}
break;
// istanbul ignore next - should never happen
default:
throw this.unexpected();
}
}
// $FlowIgnore
if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {
this.raise(
// $FlowIgnore
closingElement.start,
"Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">"
);
}
}
node.openingElement = openingElement;
node.closingElement = closingElement;
node.children = children;
if (this.match(tt.relational) && this.state.value === "<") {
this.raise(this.state.start, "Adjacent JSX elements must be wrapped in an enclosing tag");
}
return this.finishNode(node, "JSXElement");
}
// Parses entire JSX element from current position.
jsxParseElement(): N.JSXElement {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
this.next();
return this.jsxParseElementAt(startPos, startLoc);
}
// ==================================
// Overrides
// ==================================
parseExprAtom(refShortHandDefaultPos: ?Pos): N.Expression {
if (this.match(tt.jsxText)) {
return this.parseLiteral(this.state.value, "JSXText");
} else if (this.match(tt.jsxTagStart)) {
return this.jsxParseElement();
} else {
return super.parseExprAtom(refShortHandDefaultPos);
}
}
readToken(code: number): void {
if (this.state.inPropertyName) return super.readToken(code);
const context = this.curContext();
if (context === tc.j_expr) {
return this.jsxReadToken();
}
if (context === tc.j_oTag || context === tc.j_cTag) {
if (isIdentifierStart(code)) {
return this.jsxReadWord();
}
if (code === 62) {
++this.state.pos;
return this.finishToken(tt.jsxTagEnd);
}
if ((code === 34 || code === 39) && context === tc.j_oTag) {
return this.jsxReadString(code);
}
}
if (code === 60 && this.state.exprAllowed) {
++this.state.pos;
return this.finishToken(tt.jsxTagStart);
}
return super.readToken(code);
}
updateContext(prevType: TokenType): void {
if (this.match(tt.braceL)) {
const curContext = this.curContext();
if (curContext === tc.j_oTag) {
this.state.context.push(tc.braceExpression);
} else if (curContext === tc.j_expr) {
this.state.context.push(tc.templateQuasi);
} else {
super.updateContext(prevType);
}
this.state.exprAllowed = true;
} else if (this.match(tt.slash) && prevType === tt.jsxTagStart) {
this.state.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore
this.state.context.push(tc.j_cTag); // reconsider as closing tag context
this.state.exprAllowed = false;
} else {
return super.updateContext(prevType);
}
}
};

View File

@ -1,4 +1,6 @@
export default {
// @flow
const entities: { [name: string]: string } = {
quot: "\u0022",
amp: "&",
apos: "\u0027",
@ -253,3 +255,4 @@ export default {
hearts: "\u2665",
diams: "\u2666"
};
export default entities;

View File

@ -1,3 +1,5 @@
// @flow
// The algorithm used to determine whether a regexp can appear at a
// given point in the program is loosely based on sweet.js' approach.
// See https://github.com/mozilla/sweet.js/wiki/design

View File

@ -1,19 +1,41 @@
/* eslint max-len: 0 */
// @flow
import type { TokenType } from "./types";
import type { Options } from "../options";
import type { Position } from "../util/location";
import { isIdentifierStart, isIdentifierChar, isKeyword } from "../util/identifier";
import { types as tt, keywords as keywordTypes } from "./types";
import { types as ct } from "./context";
import { type TokContext, types as ct } from "./context";
import LocationParser from "../parser/location";
import { SourceLocation } from "../util/location";
import { lineBreak, lineBreakG, isNewLine, nonASCIIwhitespace } from "../util/whitespace";
import State from "./state";
// The following character codes are forbidden from being
// an immediate sibling of NumericLiteralSeparator _
const forbiddenNumericLiteralSeparatorSiblings = [
46, // .
66, // B
69, // E
79, // O
88, // X
95, // _ (multiple separators are not allowed)
98, // b
101, // e
111, // o
120, // x
];
// Object type used to represent tokens. Note that normally, tokens
// simply exist as properties on the parser object. This is only
// used for the onToken callback and the external tokenizer.
export class Token {
constructor(state) {
constructor(state: State) {
this.type = state.type;
this.value = state.value;
this.start = state.start;
@ -30,7 +52,7 @@ export class Token {
// ## Tokenizer
function codePointToString(code) {
function codePointToString(code: number): string {
// UTF-16 Decoding
if (code <= 0xFFFF) {
return String.fromCharCode(code);
@ -39,15 +61,22 @@ function codePointToString(code) {
}
}
export default class Tokenizer {
constructor(options, input) {
export default class Tokenizer extends LocationParser {
// Forward-declarations
// parser/util.js
+unexpected: (pos?: ?number, messageOrType?: string | TokenType) => empty;
isLookahead: boolean;
constructor(options: Options, input: string) {
super();
this.state = new State;
this.state.init(options, input);
}
// Move to the next token
next() {
next(): void {
if (!this.isLookahead) {
this.state.tokens.push(new Token(this.state));
}
@ -61,7 +90,7 @@ export default class Tokenizer {
// TODO
eat(type) {
eat(type: TokenType): boolean {
if (this.match(type)) {
this.next();
return true;
@ -72,19 +101,19 @@ export default class Tokenizer {
// TODO
match(type) {
match(type: TokenType): boolean {
return this.state.type === type;
}
// TODO
isKeyword(word) {
isKeyword(word: string): boolean {
return isKeyword(word);
}
// TODO
lookahead() {
lookahead(): State {
const old = this.state;
this.state = old.clone(true);
@ -100,7 +129,7 @@ export default class Tokenizer {
// Toggle strict mode. Re-reads the next number or string to please
// pedantic tests (`"use strict"; 010;` should fail).
setStrict(strict) {
setStrict(strict: boolean): void {
this.state.strict = strict;
if (!this.match(tt.num) && !this.match(tt.string)) return;
this.state.pos = this.state.start;
@ -111,14 +140,14 @@ export default class Tokenizer {
this.nextToken();
}
curContext() {
curContext(): TokContext {
return this.state.context[this.state.context.length - 1];
}
// Read a single token, updating the parser object's token-related
// properties.
nextToken() {
nextToken(): void {
const curContext = this.curContext();
if (!curContext || !curContext.preserveSpace) this.skipSpace();
@ -135,7 +164,7 @@ export default class Tokenizer {
}
}
readToken(code) {
readToken(code: number): void {
// Identifier or keyword. '\uXXXX' sequences are allowed in
// identifiers, so '\' also dispatches to that.
if (isIdentifierStart(code) || code === 92 /* '\' */) {
@ -145,7 +174,7 @@ export default class Tokenizer {
}
}
fullCharCodeAtPos() {
fullCharCodeAtPos(): number {
const code = this.input.charCodeAt(this.state.pos);
if (code <= 0xd7ff || code >= 0xe000) return code;
@ -153,7 +182,7 @@ export default class Tokenizer {
return (code << 10) + next - 0x35fdc00;
}
pushComment(block, text, start, end, startLoc, endLoc) {
pushComment(block: boolean, text: string, start: number, end: number, startLoc: Position, endLoc: Position): void {
const comment = {
type: block ? "CommentBlock" : "CommentLine",
value: text,
@ -169,7 +198,7 @@ export default class Tokenizer {
}
}
skipBlockComment() {
skipBlockComment(): void {
const startLoc = this.state.curPosition();
const start = this.state.pos;
const end = this.input.indexOf("*/", this.state.pos += 2);
@ -186,7 +215,7 @@ export default class Tokenizer {
this.pushComment(true, this.input.slice(start + 2, end), start, this.state.pos, startLoc, this.state.curPosition());
}
skipLineComment(startSkip) {
skipLineComment(startSkip: number): void {
const start = this.state.pos;
const startLoc = this.state.curPosition();
let ch = this.input.charCodeAt(this.state.pos += startSkip);
@ -201,7 +230,7 @@ export default class Tokenizer {
// Called at the start of the parse and after every token. Skips
// whitespace and comments, and.
skipSpace() {
skipSpace(): void {
loop: while (this.state.pos < this.input.length) {
const ch = this.input.charCodeAt(this.state.pos);
switch (ch) {
@ -250,7 +279,7 @@ export default class Tokenizer {
// the token, so that the next one's `start` will point at the
// right position.
finishToken(type, val) {
finishToken(type: TokenType, val: any): void {
this.state.end = this.state.pos;
this.state.endLoc = this.state.curPosition();
const prevType = this.state.type;
@ -269,7 +298,7 @@ export default class Tokenizer {
//
// All in the name of speed.
//
readToken_dot() {
readToken_dot(): void {
const next = this.input.charCodeAt(this.state.pos + 1);
if (next >= 48 && next <= 57) {
return this.readNumber(true);
@ -285,7 +314,7 @@ export default class Tokenizer {
}
}
readToken_slash() { // '/'
readToken_slash(): void { // '/'
if (this.state.exprAllowed) {
++this.state.pos;
return this.readRegexp();
@ -299,7 +328,7 @@ export default class Tokenizer {
}
}
readToken_mult_modulo(code) { // '%*'
readToken_mult_modulo(code: number): void { // '%*'
let type = code === 42 ? tt.star : tt.modulo;
let width = 1;
let next = this.input.charCodeAt(this.state.pos + 1);
@ -318,7 +347,7 @@ export default class Tokenizer {
return this.finishOp(type, width);
}
readToken_pipe_amp(code) { // '|&'
readToken_pipe_amp(code: number): void { // '|&'
const next = this.input.charCodeAt(this.state.pos + 1);
if (next === code) return this.finishOp(code === 124 ? tt.logicalOR : tt.logicalAND, 2);
if (next === 61) return this.finishOp(tt.assign, 2);
@ -326,7 +355,7 @@ export default class Tokenizer {
return this.finishOp(code === 124 ? tt.bitwiseOR : tt.bitwiseAND, 1);
}
readToken_caret() { // '^'
readToken_caret(): void { // '^'
const next = this.input.charCodeAt(this.state.pos + 1);
if (next === 61) {
return this.finishOp(tt.assign, 2);
@ -335,7 +364,7 @@ export default class Tokenizer {
}
}
readToken_plus_min(code) { // '+-'
readToken_plus_min(code: number): void { // '+-'
const next = this.input.charCodeAt(this.state.pos + 1);
if (next === code) {
@ -355,7 +384,7 @@ export default class Tokenizer {
}
}
readToken_lt_gt(code) { // '<>'
readToken_lt_gt(code: number): void { // '<>'
const next = this.input.charCodeAt(this.state.pos + 1);
let size = 1;
@ -381,7 +410,7 @@ export default class Tokenizer {
return this.finishOp(tt.relational, size);
}
readToken_eq_excl(code) { // '=!'
readToken_eq_excl(code: number): void { // '=!'
const next = this.input.charCodeAt(this.state.pos + 1);
if (next === 61) return this.finishOp(tt.equality, this.input.charCodeAt(this.state.pos + 2) === 61 ? 3 : 2);
if (code === 61 && next === 62) { // '=>'
@ -403,10 +432,19 @@ export default class Tokenizer {
}
}
getTokenFromCode(code) {
getTokenFromCode(code: number): void {
switch (code) {
case 35: // '#'
if (this.hasPlugin("classPrivateProperties") && this.state.inClass) {
++this.state.pos; return this.finishToken(tt.hash);
} else {
this.raise(this.state.pos, `Unexpected character '${codePointToString(code)}'`);
}
// The interpretation of a dot depends on whether it is followed
// by a digit or another two dots.
case 46: // '.'
return this.readToken_dot();
@ -491,13 +529,13 @@ export default class Tokenizer {
this.raise(this.state.pos, `Unexpected character '${codePointToString(code)}'`);
}
finishOp(type, size) {
finishOp(type: TokenType, size: number): void {
const str = this.input.slice(this.state.pos, this.state.pos + size);
this.state.pos += size;
return this.finishToken(type, str);
}
readRegexp() {
readRegexp(): void {
const start = this.state.pos;
let escaped, inClass;
for (;;) {
@ -539,13 +577,30 @@ export default class Tokenizer {
// were read, the integer value otherwise. When `len` is given, this
// will return `null` unless the integer has exactly `len` digits.
readInt(radix, len) {
readInt(radix: number, len?: number): number | null {
const start = this.state.pos;
let total = 0;
for (let i = 0, e = len == null ? Infinity : len; i < e; ++i) {
const code = this.input.charCodeAt(this.state.pos);
let val;
if (this.hasPlugin("numericSeparator")) {
const prev = this.input.charCodeAt(this.state.pos - 1);
const next = this.input.charCodeAt(this.state.pos + 1);
if (code === 95) {
if ((forbiddenNumericLiteralSeparatorSiblings.indexOf(prev) > -1) ||
(forbiddenNumericLiteralSeparatorSiblings.indexOf(next) > -1) ||
Number.isNaN(next)) {
this.raise(this.state.pos, "Invalid NumericLiteralSeparator");
}
// Ignore this _ character
++this.state.pos;
continue;
}
}
if (code >= 97) {
val = code - 97 + 10; // a
} else if (code >= 65) {
@ -564,7 +619,7 @@ export default class Tokenizer {
return total;
}
readRadixNumber(radix) {
readRadixNumber(radix: number): void {
this.state.pos += 2; // 0x
const val = this.readInt(radix);
if (val == null) this.raise(this.state.start + 2, "Expected number in radix " + radix);
@ -574,32 +629,36 @@ export default class Tokenizer {
// Read an integer, octal integer, or floating-point number.
readNumber(startsWithDot) {
readNumber(startsWithDot: boolean): void {
const start = this.state.pos;
const firstIsZero = this.input.charCodeAt(start) === 48; // '0'
let octal = this.input.charCodeAt(start) === 48; // '0'
let isFloat = false;
if (!startsWithDot && this.readInt(10) === null) this.raise(start, "Invalid number");
if (octal && this.state.pos == start + 1) octal = false; // number === 0
let next = this.input.charCodeAt(this.state.pos);
if (next === 46) { // '.'
if (next === 46 && !octal) { // '.'
++this.state.pos;
this.readInt(10);
isFloat = true;
next = this.input.charCodeAt(this.state.pos);
}
if (next === 69 || next === 101) { // 'eE'
if ((next === 69 || next === 101) && !octal) { // 'eE'
next = this.input.charCodeAt(++this.state.pos);
if (next === 43 || next === 45) ++this.state.pos; // '+-'
if (this.readInt(10) === null) this.raise(start, "Invalid number");
isFloat = true;
}
if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.state.pos, "Identifier directly after number");
const str = this.input.slice(start, this.state.pos);
const str = this.input.slice(start, this.state.pos).replace(/_/g, "");
let val;
if (isFloat) {
val = parseFloat(str);
} else if (!firstIsZero || str.length === 1) {
} else if (!octal || str.length === 1) {
val = parseInt(str, 10);
} else if (this.state.strict) {
this.raise(start, "Invalid number");
@ -613,7 +672,7 @@ export default class Tokenizer {
// Read a string value, interpreting backslash-escapes.
readCodePoint(throwOnInvalid) {
readCodePoint(throwOnInvalid: boolean): number | null {
const ch = this.input.charCodeAt(this.state.pos);
let code;
@ -622,6 +681,7 @@ export default class Tokenizer {
code = this.readHexChar(this.input.indexOf("}", this.state.pos) - this.state.pos, throwOnInvalid);
++this.state.pos;
if (code === null) {
// $FlowFixMe (is this always non-null?)
--this.state.invalidTemplateEscapePosition; // to point to the '\'' instead of the 'u'
} else if (code > 0x10FFFF) {
if (throwOnInvalid) {
@ -637,7 +697,7 @@ export default class Tokenizer {
return code;
}
readString(quote) {
readString(quote: number): void {
let out = "", chunkStart = ++this.state.pos;
for (;;) {
if (this.state.pos >= this.input.length) this.raise(this.state.start, "Unterminated string constant");
@ -645,6 +705,7 @@ export default class Tokenizer {
if (ch === quote) break;
if (ch === 92) { // '\'
out += this.input.slice(chunkStart, this.state.pos);
// $FlowFixMe
out += this.readEscapedChar(false);
chunkStart = this.state.pos;
} else {
@ -658,7 +719,7 @@ export default class Tokenizer {
// Reads template string tokens.
readTmplToken() {
readTmplToken(): void {
let out = "", chunkStart = this.state.pos, containsInvalid = false;
for (;;) {
if (this.state.pos >= this.input.length) this.raise(this.state.start, "Unterminated template");
@ -709,7 +770,7 @@ export default class Tokenizer {
// Used to read escaped characters
readEscapedChar(inTemplate) {
readEscapedChar(inTemplate: boolean): string | null {
const throwOnInvalid = !inTemplate;
const ch = this.input.charCodeAt(++this.state.pos);
++this.state.pos;
@ -736,6 +797,7 @@ export default class Tokenizer {
default:
if (ch >= 48 && ch <= 55) {
const codePos = this.state.pos - 1;
// $FlowFixMe
let octalStr = this.input.substr(this.state.pos - 1, 3).match(/^[0-7]+/)[0];
let octal = parseInt(octalStr, 8);
if (octal > 255) {
@ -764,7 +826,7 @@ export default class Tokenizer {
// Used to read character escape sequences ('\x', '\u').
readHexChar(len, throwOnInvalid) {
readHexChar(len: number, throwOnInvalid: boolean): number | null {
const codePos = this.state.pos;
const n = this.readInt(16, len);
if (n === null) {
@ -784,7 +846,7 @@ export default class Tokenizer {
// Incrementally adds only escaped chars, adding other chunks as-is
// as a micro-optimization.
readWord1() {
readWord1(): string {
this.state.containsEsc = false;
let word = "", first = true, chunkStart = this.state.pos;
while (this.state.pos < this.input.length) {
@ -803,10 +865,12 @@ export default class Tokenizer {
++this.state.pos;
const esc = this.readCodePoint(true);
// $FlowFixMe (thinks esc may be null, but throwOnInvalid is true)
if (!(first ? isIdentifierStart : isIdentifierChar)(esc, true)) {
this.raise(escStart, "Invalid Unicode escape");
}
// $FlowFixMe
word += codePointToString(esc);
chunkStart = this.state.pos;
} else {
@ -820,7 +884,7 @@ export default class Tokenizer {
// Read an identifier or keyword token. Will check for reserved
// words when necessary.
readWord() {
readWord(): void {
const word = this.readWord1();
let type = tt.name;
if (!this.state.containsEsc && this.isKeyword(word)) {
@ -829,7 +893,7 @@ export default class Tokenizer {
return this.finishToken(type, word);
}
braceIsBlock(prevType) {
braceIsBlock(prevType: TokenType): boolean {
if (prevType === tt.colon) {
const parent = this.curContext();
if (parent === ct.braceStatement || parent === ct.braceExpression) {
@ -852,7 +916,7 @@ export default class Tokenizer {
return !this.state.exprAllowed;
}
updateContext(prevType) {
updateContext(prevType: TokenType): void {
const type = this.state.type;
let update;

View File

@ -1,11 +1,17 @@
// @flow
import type { Options } from "../options";
import * as N from "../types";
import type { TokContext } from "./context";
import type { Token } from "./index";
import type { TokenType } from "./types";
import { Position } from "../util/location";
import { types as ct } from "./context";
import { types as tt } from "./types";
export default class State {
init(options: Object, input: string) {
init(options: Options, input: string): void {
this.strict = options.strictMode === false ? false : options.sourceType === "module";
this.input = input;
@ -18,6 +24,8 @@ export default class State {
this.inAsync =
this.inPropertyName =
this.inType =
this.inClass =
this.inClassProperty =
this.noAnonFunctionType =
false;
@ -41,6 +49,7 @@ export default class State {
this.start = this.end = this.pos;
this.startLoc = this.endLoc = this.curPosition();
// $FlowIgnore
this.lastTokEndLoc = this.lastTokStartLoc = null;
this.lastTokStart = this.lastTokEnd = this.pos;
@ -53,8 +62,6 @@ export default class State {
this.invalidTemplateEscapePosition = null;
this.exportedIdentifiers = [];
return this;
}
// TODO
@ -69,27 +76,35 @@ export default class State {
// Flags to track whether we are in a function, a generator.
inFunction: boolean;
inGenerator: boolean;
inMethod: boolean;
inMethod: boolean | N.MethodKind;
inAsync: boolean;
inType: boolean;
noAnonFunctionType: boolean;
inPropertyName: boolean;
inClassProperty: boolean;
inClass: boolean;
// Labels in scope.
labels: Array<Object>;
labels: Array<{ kind: ?("loop" | "switch"), statementStart?: number }>;
// Leading decorators.
decorators: Array<Object>;
decorators: Array<N.Decorator>;
// Token store.
tokens: Array<Object>;
tokens: Array<Token | N.Comment>;
// Comment store.
comments: Array<Object>;
comments: Array<N.Comment>;
// Comment attachment store
trailingComments: Array<Object>;
leadingComments: Array<Object>;
commentStack: Array<Object>;
trailingComments: Array<N.Comment>;
leadingComments: Array<N.Comment>;
commentStack: Array<{
start: number;
leadingComments: ?Array<N.Comment>;
trailingComments: ?Array<N.Comment>;
}>;
commentPreviousNode: N.Node;
// The current position of the tokenizer in the input.
pos: number;
@ -113,8 +128,8 @@ export default class State {
endLoc: Position;
// Position information for the previous token
lastTokEndLoc: ?Position;
lastTokStartLoc: ?Position;
lastTokEndLoc: Position;
lastTokStartLoc: Position;
lastTokStart: number;
lastTokEnd: number;
@ -137,19 +152,23 @@ export default class State {
// `export default foo;` and `export { foo as default };`.
exportedIdentifiers: Array<string>;
curPosition() {
invalidTemplateEscapePosition: ?number;
curPosition(): Position {
return new Position(this.curLine, this.pos - this.lineStart);
}
clone(skipArrays?) {
clone(skipArrays?: boolean): State {
const state = new State;
for (const key in this) {
// $FlowIgnore
let val = this[key];
if ((!skipArrays || key === "context") && Array.isArray(val)) {
val = val.slice();
}
// $FlowIgnore
state[key] = val;
}
return state;

View File

@ -1,3 +1,5 @@
// @flow
// ## Token types
// The assignment of fine-grained, information-carrying type objects
@ -23,8 +25,33 @@ const isAssign = true;
const prefix = true;
const postfix = true;
type TokenOptions = {
keyword?: string;
beforeExpr?: boolean;
startsExpr?: boolean;
rightAssociative?: boolean;
isLoop?: boolean;
isAssign?: boolean;
prefix?: boolean;
postfix?: boolean;
binop?: ?number;
};
export class TokenType {
constructor(label, conf = {}) {
label: string;
keyword: ?string;
beforeExpr: boolean;
startsExpr: boolean;
rightAssociative: boolean;
isLoop: boolean;
isAssign: boolean;
prefix: boolean;
postfix: boolean;
binop: ?number;
updateContext: ?((prevType: TokenType) => void);
constructor(label: string, conf: TokenOptions = {}) {
this.label = label;
this.keyword = conf.keyword;
this.beforeExpr = !!conf.beforeExpr;
@ -40,7 +67,7 @@ export class TokenType {
}
class KeywordTokenType extends TokenType {
constructor(name, options = {}) {
constructor(name: string, options: TokenOptions = {}) {
options.keyword = name;
super(name, options);
@ -48,12 +75,12 @@ class KeywordTokenType extends TokenType {
}
export class BinopTokenType extends TokenType {
constructor(name, prec) {
constructor(name: string, prec: number) {
super(name, { beforeExpr, binop: prec });
}
}
export const types = {
export const types: { [name: string]: TokenType } = {
num: new TokenType("num", { startsExpr }),
regexp: new TokenType("regexp", { startsExpr }),
string: new TokenType("string", { startsExpr }),
@ -82,6 +109,7 @@ export const types = {
backQuote: new TokenType("`", { startsExpr }),
dollarBraceL: new TokenType("${", { beforeExpr, startsExpr }),
at: new TokenType("@"),
hash: new TokenType("#"),
// Operators. These carry several kinds of properties to help the
// parser use them properly (the presence of these properties is

726
src/types.js Normal file
View File

@ -0,0 +1,726 @@
// @flow
import type { Token } from "./tokenizer";
import type { SourceLocation } from "./util/location";
/*
* If making any changes to the AST, update:
* - This repository:
* - This file
* - `ast` directory
* - Babel repository:
* - packages/babel-types/src/definitions
* - packages/babel-generators/src/generators
*/
export type Comment = {
type: "CommentBlock" | "CommentLine";
value: string;
start: number;
end: number;
loc: SourceLocation;
};
export interface NodeBase {
start: number;
end: number;
loc: SourceLocation;
range: [number, number];
leadingComments?: ?Array<Comment>;
trailingComments?: ?Array<Comment>;
innerComments?: ?Array<Comment>;
extra: { [key: string]: any };
}
// Using a union type for `Node` makes type-checking too slow.
// Instead, add an index signature to allow a Node to be treated as anything.
export type Node = NodeBase & { [key: string]: any };
export type Expression = Node;
export type Statement = Node;
export type Pattern =
| Identifier
| ObjectPattern
| ArrayPattern
| RestElement
| AssignmentPattern;
export type Declaration =
| VariableDeclaration
| ClassDeclaration
| FunctionDeclaration;
export type DeclarationBase = NodeBase;
// TODO: Not in spec
export type HasDecorators = NodeBase & {
decorators?: $ReadOnlyArray<Decorator>;
};
export type Identifier = PatternBase & {
type: "Identifier";
name: string;
__clone(): Identifier;
};
export type PrivateName = NodeBase & {
type: "PrivateName";
name: string;
};
// Literals
export type Literal = RegExpLiteral | NullLiteral | StringLiteral | BooleanLiteral | NumericLiteral;
export type RegExpLiteral = NodeBase & {
type: "RegExpLiteral";
pattern: string;
flags: RegExp$flags;
};
export type NullLiteral = NodeBase & {
type: "NullLiteral";
}
export type StringLiteral = NodeBase & {
type: "StringLiteral";
value: string;
};
export type BooleanLiteral = NodeBase & {
type: "BooleanLiteral";
value: boolean;
};
export type NumericLiteral = NodeBase & {
type: "NumericLiteral";
value: number;
};
// Programs
export type BlockStatementLike = Program | BlockStatement;
export type File = NodeBase & {
type: "File";
program: Program;
comments: $ReadOnlyArray<Comment>;
tokens: $ReadOnlyArray<Token | Comment>;
};
export type Program = NodeBase & {
type: "Program";
sourceType: "script" | "module";
body: Array<Statement | ModuleDeclaration>; // TODO: $ReadOnlyArray
directives: $ReadOnlyArray<Directive>; // TODO: Not in spec
};
// Functions
export type Function =
NormalFunction | ArrowFunctionExpression | ObjectMethod | ClassMethod;
export type NormalFunction =
FunctionDeclaration | FunctionExpression;
export type FunctionBase = HasDecorators & {
id: ?Identifier;
params: $ReadOnlyArray<Pattern>;
body: BlockStatement;
generator: boolean;
async: boolean;
expression: boolean; // TODO: Not in spec
typeParameters?: ?FlowTypeParameterDeclaration; // TODO: Not in spec
returnType?: ?FlowTypeAnnotation; // TODO: Not in spec
};
// Statements
export type ExpressionStatement = NodeBase & {
type: "ExpressionStatement";
expression: Expression;
};
export type BlockStatement = NodeBase & {
type: "BlockStatement";
body: Array<Statement>; // TODO: $ReadOnlyArray
directives: $ReadOnlyArray<Directive>;
};
export type EmptyStatement = NodeBase & {
type: "EmptyStatement"
};
export type DebuggerStatement = NodeBase & {
type: "DebuggerStatement"
};
export type WithStatement = NodeBase & {
type: "WithStatement";
object: Expression;
body: Statement;
};
export type ReturnStatement = NodeBase & {
type: "ReturnStatement";
argument: ?Expression;
};
export type LabeledStatement = NodeBase & {
type: "LabeledStatement";
label: Identifier;
body: Statement;
};
export type BreakStatement = NodeBase & {
type: "BreakStatement";
label: ?Identifier;
};
export type ContinueStatement = NodeBase & {
type: "ContinueStatement";
label: ?Identifier;
};
// Choice
export type IfStatement = NodeBase & {
type: "IfStatement";
test: Expression;
consequent: Statement;
alternate: ?Statement;
};
export type SwitchStatement = NodeBase & {
type: "SwitchStatement";
discriminant: Expression;
cases: $ReadOnlyArray<SwitchCase>;
};
export type SwitchCase = NodeBase & {
type: "SwitchCase";
test: ?Expression;
consequent: $ReadOnlyArray<Statement>;
};
// Exceptions
export type ThrowStatement = NodeBase & {
type: "ThrowStatement";
argument: Expression;
};
export type TryStatement = NodeBase & {
type: "TryStatement";
block: BlockStatement;
handler: CatchClause | null;
finalizer: BlockStatement | null;
guardedHandlers: $ReadOnlyArray<empty>; // TODO: Not in spec
};
export type CatchClause = NodeBase & {
type: "CatchClause";
param: Pattern;
body: BlockStatement;
};
// Loops
export type WhileStatement = NodeBase & {
type: "WhileStatement";
test: Expression;
body: Statement;
};
export type DoWhileStatement = NodeBase & {
type: "DoWhileStatement";
body: Statement;
test: Expression;
};
export type ForLike = ForStatement | ForInOf;
export type ForStatement = NodeBase & {
type: "ForStatement";
init: ?(VariableDeclaration | Expression);
test: ?Expression;
update: ?Expression;
body: Statement;
};
export type ForInOf = ForInStatement | ForOfStatement;
export type ForInOfBase = NodeBase & {
type: "ForInStatement";
left: VariableDeclaration | Expression;
right: Expression;
body: Statement;
};
export type ForInStatement = ForInOfBase & {
type: "ForInStatement";
// TODO: Shouldn't be here, but have to declare it because it's assigned to a ForInOf unconditionally.
await: boolean;
};
export type ForOfStatement = ForInOfBase & {
type: "ForOfStatement";
await: boolean;
};
// Declarations
export type OptFunctionDeclaration = FunctionBase & DeclarationBase & HasDecorators & {
type: "FunctionDeclaration";
};
export type FunctionDeclaration = OptFunctionDeclaration & {
id: Identifier;
}
export type VariableDeclaration = DeclarationBase & HasDecorators & {
type: "VariableDeclaration";
declarations: $ReadOnlyArray<VariableDeclarator>;
kind: "var" | "let" | "const";
};
export type VariableDeclarator = NodeBase & {
type: "VariableDeclarator";
id: Pattern;
init: ?Expression;
};
// Misc
export type Decorator = NodeBase & {
type: "Decorator";
expression: Expression;
};
export type Directive = NodeBase & {
type: "Directive";
value: DirectiveLiteral;
};
export type DirectiveLiteral = StringLiteral & { type: "DirectiveLiteral" };
// Expressions
export type Super = NodeBase & { type: "Super" };
export type Import = NodeBase & { type: "Import" };
export type ThisExpression = NodeBase & { type: "ThisExpression" };
export type ArrowFunctionExpression = FunctionBase & {
type: "ArrowFunctionExpression";
body: BlockStatement | Expression;
};
export type YieldExpression = NodeBase & {
type: "YieldExpression";
argument: ?Expression;
delegate: boolean;
};
export type AwaitExpression = NodeBase & {
type: "AwaitExpression";
argument: ?Expression;
};
export type ArrayExpression = NodeBase & {
type: "ArrayExpression";
elements: $ReadOnlyArray<?(Expression | SpreadElement)>;
};
export type ObjectExpression = NodeBase & {
type: "ObjectExpression";
properties: $ReadOnlyArray<ObjectProperty | ObjectMethod | SpreadElement>;
};
export type ObjectOrClassMember = ClassMethod | ClassProperty | ClassPrivateProperty | ObjectMember;
export type ObjectMember = ObjectProperty | ObjectMethod;
export type ObjectMemberBase = NodeBase & {
key: Expression;
computed: boolean;
value: Expression;
decorators: $ReadOnlyArray<Decorator>;
kind?: "get" | "set" | "method";
method: boolean; // TODO: Not in spec
variance?: ?FlowVariance; // TODO: Not in spec
};
export type ObjectProperty = ObjectMemberBase & {
type: "ObjectProperty";
shorthand: boolean;
};
export type ObjectMethod = ObjectMemberBase & MethodBase & {
type: "ObjectMethod";
kind: "get" | "set" | "method"; // Never "constructor"
};
export type FunctionExpression = MethodBase & {
kind?: void; // never set
type: "FunctionExpression";
};
// Unary operations
export type UnaryExpression = NodeBase & {
type: "UnaryExpression";
operator: UnaryOperator;
prefix: boolean;
argument: Expression;
};
export type UnaryOperator = "-" | "+" | "!" | "~" | "typeof" | "void" | "delete";
export type UpdateExpression = NodeBase & {
type: "UpdateExpression";
operator: UpdateOperator;
argument: Expression;
prefix: boolean;
};
export type UpdateOperator = "++" | "--";
// Binary operations
export type BinaryExpression = NodeBase & {
type: "BinaryExpression";
operator: BinaryOperator;
left: Expression;
right: Expression;
};
export type BinaryOperator =
| "==" | "!=" | "===" | "!=="
| "<" | "<=" | ">" | ">="
| "<<" | ">>" | ">>>"
| "+" | "-" | "*" | "/" | "%"
| "|" | "^" | "&" | "in"
| "instanceof";
export type AssignmentExpression = NodeBase & {
type: "AssignmentExpression";
operator: AssignmentOperator;
left: Pattern | Expression;
right: Expression;
};
export type AssignmentOperator =
| "=" | "+=" | "-=" | "*=" | "/=" | "%="
| "<<=" | ">>=" | ">>>="
| "|=" | "^=" | "&=";
export type LogicalExpression = NodeBase & {
type: "LogicalExpression";
operator: LogicalOperator;
left: Expression;
right: Expression;
};
export type LogicalOperator = "||" | "&&";
export type SpreadElement = NodeBase & {
type: "SpreadElement";
argument: Expression;
};
export type MemberExpression = NodeBase & {
type: "MemberExpression";
object: Expression | Super;
property: Expression;
computed: boolean;
}
export type BindExpression = NodeBase & {
type: "BindExpression";
object: $ReadOnlyArray<?Expression>;
callee: $ReadOnlyArray<Expression>;
};
export type ConditionalExpression = NodeBase & {
type: "ConditionalExpression";
test: Expression;
alternate: Expression;
consequent: Expression;
};
export type CallOrNewBase = NodeBase & {
callee: Expression | Super | Import;
arguments: Array<Expression | SpreadElement>; // TODO: $ReadOnlyArray
};
export type CallExpression = CallOrNewBase & {
type: "CallExpression";
};
export type NewExpression = CallOrNewBase & {
type: "NewExpression";
};
export type SequenceExpression = NodeBase & {
type: "SequenceExpression";
expressions: $ReadOnlyArray<Expression>;
};
// Template Literals
export type TemplateLiteral = NodeBase & {
type: "TemplateLiteral";
quasis: $ReadOnlyArray<TemplateElement>;
expressions: $ReadOnlyArray<Expression>;
};
export type TaggedTmplateExpression = NodeBase & {
type: "TaggedTemplateExpression";
tag: Expression;
quasi: TemplateLiteral;
};
export type TemplateElement = NodeBase & {
type: "TemplateElement";
tail: boolean;
value: {
cooked: string;
raw: string;
}
};
// Patterns
export type PatternBase = HasDecorators & {
// Flow only:
optional?: true;
typeAnnotation?: ?FlowTypeAnnotation;
};
export type AssignmentProperty = ObjectProperty & {
value: Pattern;
};
export type ObjectPattern = PatternBase & {
type: "ObjectPattern";
properties: $ReadOnlyArray<AssignmentProperty | RestElement>;
};
export type ArrayPattern = PatternBase & {
type: "ArrayPattern";
elements: $ReadOnlyArray<?Pattern>;
};
export type RestElement = PatternBase & {
type: "RestElement";
argument: Pattern;
};
export type AssignmentPattern = PatternBase & {
type: "AssignmentPattern";
left: Pattern;
right: Expression;
};
// Classes
export type Class = ClassDeclaration | ClassExpression;
export type ClassBase = HasDecorators & {
id: ?Identifier;
superClass: ?Expression;
body: ClassBody;
decorators: $ReadOnlyArray<Decorator>;
typeParameters?: ?FlowTypeParameterDeclaration; // TODO: Not in spec
superTypeParameters?: ?FlowTypeParameterInstantiation; // TODO: Not in spec
implements?: $ReadOnlyArray<FlowClassImplements>;
};
export type ClassBody = NodeBase & {
type: "ClassBody";
body: Array<ClassMember>; // TODO: $ReadOnlyArray
};
export type ClassMemberBase = NodeBase & HasDecorators & {
static: boolean;
computed: boolean;
// TypeScript only:
access?: ?Accessibility;
abstract?: ?true;
optional?: ?true;
}
export type Accessibility = "public" | "protected" | "private";
export type ClassMember = ClassMethod | ClassProperty | ClassPrivateProperty;
export type MethodLike = ObjectMethod | FunctionExpression | ClassMethod;
export type MethodBase = FunctionBase & {
+kind?: MethodKind;
};
export type MethodKind = "constructor" | "method" | "get" | "set";
export type ClassMethod = MethodBase & ClassMemberBase & {
type: "ClassMethod";
key: Expression;
kind: MethodKind;
static: boolean;
decorators: $ReadOnlyArray<Decorator>;
variance?: ?FlowVariance; // TODO: Not in spec
};
export type ClassProperty = ClassMemberBase & {
type: "ClassProperty";
key: Identifier;
value: ?Expression; // TODO: Not in spec that this is nullable.
typeAnnotation?: ?FlowTypeAnnotation; // TODO: Not in spec
variance?: ?FlowVariance; // TODO: Not in spec
// TypeScript only: (TODO: Not in spec)
readonly?: true;
};
export type ClassPrivateProperty = NodeBase & {
type: "ClassPrivateProperty";
key: Identifier;
value: ?Expression; // TODO: Not in spec that this is nullable.
};
export type OptClassDeclaration = ClassBase & DeclarationBase & HasDecorators & {
type: "ClassDeclaration";
// TypeScript only
abstract?: ?true;
};
export type ClassDeclaration = OptClassDeclaration & {
id: Identifier;
};
export type ClassExpression = ClassBase & { type: "ClassExpression" };
export type MetaProperty = NodeBase & {
type: "MetaProperty";
meta: Identifier;
property: Identifier;
};
// Modules
export type ModuleDeclaration = AnyImport | AnyExport;
export type AnyImport = ImportDeclaration;
export type AnyExport =
| ExportNamedDeclaration
| ExportDefaultDeclaration
| ExportAllDeclaration;
export type ModuleSpecifier = NodeBase & {
local: Identifier;
};
// Imports
export type ImportDeclaration = NodeBase & {
type: "ImportDeclaration";
// TODO: $ReadOnlyArray
specifiers: Array<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>;
source: Literal;
importKind?: "type" | "typeof" | "value"; // TODO: Not in spec
};
export type ImportSpecifier = ModuleSpecifier & {
type: "ImportSpecifier";
imported: Identifier;
};
export type ImportDefaultSpecifier = ModuleSpecifier & {
type: "ImportDefaultSpecifier"
};
export type ImportNamespaceSpecifier = ModuleSpecifier & {
type: "ImportNamespaceSpecifier"
};
// Exports
export type ExportNamedDeclaration = NodeBase & {
type: "ExportNamedDeclaration";
declaration: ?Declaration;
specifiers: $ReadOnlyArray<ExportSpecifier>;
source: ?Literal;
exportKind?: "type" | "value"; // TODO: Not in spec
};
export type ExportSpecifier = NodeBase & {
type: "ExportSpecifier";
exported: Identifier;
};
export type ExportDefaultDeclaration = NodeBase & {
type: "ExportDefaultDeclaration";
declaration: OptFunctionDeclaration | OptClassDeclaration | Expression;
};
export type ExportAllDeclaration = NodeBase & {
type: "ExportAllDeclaration";
source: Literal;
};
// JSX (TODO: Not in spec)
export type JSXIdentifier = Node;
export type JSXNamespacedName = Node;
export type JSXMemberExpression = Node;
export type JSXEmptyExpression = Node;
export type JSXSpreadChild = Node;
export type JSXExpressionContainer = Node;
export type JSXAttribute = Node;
export type JSXOpeningElement = Node;
export type JSXClosingElement = Node;
export type JSXElement = Node;
// Flow (TODO: Not in spec)
export type FlowType = Node;
export type FlowPredicate = Node;
export type FlowDeclare = Node;
export type FlowDeclareClass = Node;
export type FlowDeclareExportDeclaration = Node;
export type FlowDeclareFunction = Node;
export type FlowDeclareVariable = Node;
export type FlowDeclareModule = Node;
export type FlowDeclareModuleExports = Node;
export type FlowDeclareTypeAlias = Node;
export type FlowDeclareInterface = Node;
export type FlowInterface = Node;
export type FlowInterfaceExtends = Node;
export type FlowTypeAlias = Node;
export type FlowTypeParameter = Node;
export type FlowTypeParameterDeclaration = Node;
export type FlowTypeParameterInstantiation = Node;
export type FlowObjectTypeIndexer = Node;
export type FlowFunctionTypeAnnotation = Node;
export type FlowObjectTypeProperty = Node;
export type FlowObjectTypeSpreadProperty = Node;
export type FlowObjectTypeCallProperty = Node;
export type FlowObjectTypeAnnotation = Node;
export type FlowQualifiedTypeIdentifier = Node;
export type FlowGenericTypeAnnotation = Node;
export type FlowTypeofTypeAnnotation = Node;
export type FlowTupleTypeAnnotation = Node;
export type FlowFunctionTypeParam = Node;
export type FlowTypeAnnotation = Node;
export type FlowVariance = Node;
export type FlowClassImplements = Node;

View File

@ -1,5 +1,7 @@
/* eslint max-len: 0 */
// @flow
// This is a trick taken from Esprima. It turns out that, on
// non-Chrome browsers, to check whether a string is in a set, a
// predicate containing a big ugly `switch` statement is faster than
@ -9,17 +11,17 @@
//
// It starts by sorting the words by length.
function makePredicate(words) {
words = words.split(" ");
function makePredicate(words: string): (str: string) => boolean {
const wordsArr = words.split(" ");
return function (str) {
return words.indexOf(str) >= 0;
return wordsArr.indexOf(str) >= 0;
};
}
// Reserved word lists for various dialects of the language
export const reservedWords = {
6: makePredicate("enum await"),
"6": makePredicate("enum await"),
strict: makePredicate("implements interface let package private protected public static yield"),
strictBind: makePredicate("eval arguments")
};
@ -57,7 +59,7 @@ const astralIdentifierCodes = [509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,1
// This has a complexity linear to the value of the code. The
// assumption is that looking up astral identifier characters is
// rare.
function isInAstralSet(code, set) {
function isInAstralSet(code: number, set: $ReadOnlyArray<number>): boolean {
let pos = 0x10000;
for (let i = 0; i < set.length; i += 2) {
pos += set[i];
@ -66,11 +68,12 @@ function isInAstralSet(code, set) {
pos += set[i + 1];
if (pos >= code) return true;
}
return false;
}
// Test whether a given character code starts an identifier.
export function isIdentifierStart(code) {
export function isIdentifierStart(code: number): boolean {
if (code < 65) return code === 36;
if (code < 91) return true;
if (code < 97) return code === 95;
@ -81,7 +84,7 @@ export function isIdentifierStart(code) {
// Test whether a given character is part of an identifier.
export function isIdentifierChar(code) {
export function isIdentifierChar(code: number): boolean {
if (code < 48) return code === 36;
if (code < 58) return true;
if (code < 65) return false;

View File

@ -1,9 +1,18 @@
// @flow
import { lineBreakG } from "./whitespace";
export type Pos = {
start: number;
}
// These are used when `options.locations` is on, for the
// `startLoc` and `endLoc` properties.
export class Position {
line: number;
column: number;
constructor(line: number, col: number) {
this.line = line;
this.column = col;
@ -11,8 +20,13 @@ export class Position {
}
export class SourceLocation {
start: Position;
end: Position;
filename: string;
constructor(start: Position, end?: Position) {
this.start = start;
// $FlowIgnore (may start as null, but initialized later)
this.end = end;
}
}
@ -23,7 +37,7 @@ export class SourceLocation {
// offset. `input` should be the code string that the offset refers
// into.
export function getLineInfo(input, offset) {
export function getLineInfo(input: string, offset: number): Position {
for (let line = 1, cur = 0; ;) {
lineBreakG.lastIndex = cur;
const match = lineBreakG.exec(input);
@ -34,4 +48,6 @@ export function getLineInfo(input, offset) {
return new Position(line, offset - cur);
}
}
// istanbul ignore next
throw new Error("Unreachable");
}

View File

@ -1,3 +1,5 @@
// @flow
// Matches a whole line break (where CRLF is considered a single
// line break). Used to count lines.

View File

@ -0,0 +1 @@
fn(a, { b }, /* comment */);

View File

@ -0,0 +1,208 @@
{
"type": "File",
"start": 0,
"end": 28,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 28
}
},
"program": {
"type": "Program",
"start": 0,
"end": 28,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 28
}
},
"sourceType": "script",
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 28,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 28
}
},
"expression": {
"type": "CallExpression",
"start": 0,
"end": 27,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 27
}
},
"callee": {
"type": "Identifier",
"start": 0,
"end": 2,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 2
},
"identifierName": "fn"
},
"name": "fn"
},
"arguments": [
{
"type": "Identifier",
"start": 3,
"end": 4,
"loc": {
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 1,
"column": 4
},
"identifierName": "a"
},
"name": "a"
},
{
"type": "ObjectExpression",
"start": 6,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 11
}
},
"properties": [
{
"type": "ObjectProperty",
"start": 8,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 9
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
"start": 8,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 9
},
"identifierName": "b"
},
"name": "b"
},
"value": {
"type": "Identifier",
"start": 8,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 9
},
"identifierName": "b"
},
"name": "b"
},
"extra": {
"shorthand": true
}
}
],
"trailingComments": [
{
"type": "CommentBlock",
"value": " comment ",
"start": 13,
"end": 26,
"loc": {
"start": {
"line": 1,
"column": 13
},
"end": {
"line": 1,
"column": 26
}
}
}
]
}
]
}
}
],
"directives": []
},
"comments": [
{
"type": "CommentBlock",
"value": " comment ",
"start": 13,
"end": 26,
"loc": {
"start": {
"line": 1,
"column": 13
},
"end": {
"line": 1,
"column": 26
}
}
}
]
}

View File

@ -0,0 +1 @@
fn(a, b, /* comment */);

View File

@ -0,0 +1,153 @@
{
"type": "File",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 24
}
},
"program": {
"type": "Program",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 24
}
},
"sourceType": "script",
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 24
}
},
"expression": {
"type": "CallExpression",
"start": 0,
"end": 23,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 23
}
},
"callee": {
"type": "Identifier",
"start": 0,
"end": 2,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 2
},
"identifierName": "fn"
},
"name": "fn"
},
"arguments": [
{
"type": "Identifier",
"start": 3,
"end": 4,
"loc": {
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 1,
"column": 4
},
"identifierName": "a"
},
"name": "a"
},
{
"type": "Identifier",
"start": 6,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 7
},
"identifierName": "b"
},
"name": "b",
"trailingComments": [
{
"type": "CommentBlock",
"value": " comment ",
"start": 9,
"end": 22,
"loc": {
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 22
}
}
}
]
}
]
}
}
],
"directives": []
},
"comments": [
{
"type": "CommentBlock",
"value": " comment ",
"start": 9,
"end": 22,
"loc": {
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 22
}
}
}
]
}

View File

@ -0,0 +1,5 @@
var obj = {
a: '1', // comment 1
b: '2', // comment 2
c: '3', // comment 3
};

View File

@ -0,0 +1,372 @@
{
"type": "File",
"start": 0,
"end": 83,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 5,
"column": 2
}
},
"program": {
"type": "Program",
"start": 0,
"end": 83,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 5,
"column": 2
}
},
"sourceType": "script",
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 83,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 5,
"column": 2
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 4,
"end": 82,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 5,
"column": 1
}
},
"id": {
"type": "Identifier",
"start": 4,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 7
},
"identifierName": "obj"
},
"name": "obj"
},
"init": {
"type": "ObjectExpression",
"start": 10,
"end": 82,
"loc": {
"start": {
"line": 1,
"column": 10
},
"end": {
"line": 5,
"column": 1
}
},
"properties": [
{
"type": "ObjectProperty",
"start": 14,
"end": 20,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 8
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
"start": 14,
"end": 15,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 3
},
"identifierName": "a"
},
"name": "a"
},
"value": {
"type": "StringLiteral",
"start": 17,
"end": 20,
"loc": {
"start": {
"line": 2,
"column": 5
},
"end": {
"line": 2,
"column": 8
}
},
"extra": {
"rawValue": "1",
"raw": "'1'"
},
"value": "1"
}
},
{
"type": "ObjectProperty",
"start": 37,
"end": 43,
"loc": {
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 8
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
"start": 37,
"end": 38,
"loc": {
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 3
},
"identifierName": "b"
},
"name": "b",
"leadingComments": null
},
"value": {
"type": "StringLiteral",
"start": 40,
"end": 43,
"loc": {
"start": {
"line": 3,
"column": 5
},
"end": {
"line": 3,
"column": 8
}
},
"extra": {
"rawValue": "2",
"raw": "'2'"
},
"value": "2"
},
"leadingComments": [
{
"type": "CommentLine",
"value": " comment 1",
"start": 22,
"end": 34,
"loc": {
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 22
}
}
}
]
},
{
"type": "ObjectProperty",
"start": 60,
"end": 66,
"loc": {
"start": {
"line": 4,
"column": 2
},
"end": {
"line": 4,
"column": 8
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
"start": 60,
"end": 61,
"loc": {
"start": {
"line": 4,
"column": 2
},
"end": {
"line": 4,
"column": 3
},
"identifierName": "c"
},
"name": "c",
"leadingComments": null
},
"value": {
"type": "StringLiteral",
"start": 63,
"end": 66,
"loc": {
"start": {
"line": 4,
"column": 5
},
"end": {
"line": 4,
"column": 8
}
},
"extra": {
"rawValue": "3",
"raw": "'3'"
},
"value": "3"
},
"leadingComments": [
{
"type": "CommentLine",
"value": " comment 2",
"start": 45,
"end": 57,
"loc": {
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 22
}
}
}
],
"trailingComments": [
{
"type": "CommentLine",
"value": " comment 3",
"start": 68,
"end": 80,
"loc": {
"start": {
"line": 4,
"column": 10
},
"end": {
"line": 4,
"column": 22
}
}
}
]
}
]
}
}
],
"kind": "var"
}
],
"directives": []
},
"comments": [
{
"type": "CommentLine",
"value": " comment 1",
"start": 22,
"end": 34,
"loc": {
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 22
}
}
},
{
"type": "CommentLine",
"value": " comment 2",
"start": 45,
"end": 57,
"loc": {
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 22
}
}
},
{
"type": "CommentLine",
"value": " comment 3",
"start": 68,
"end": 80,
"loc": {
"start": {
"line": 4,
"column": 10
},
"end": {
"line": 4,
"column": 22
}
}
}
]
}

View File

@ -87,7 +87,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -107,6 +106,7 @@
"name": "spawn",
"leadingComments": null
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 33,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -123,6 +122,7 @@
},
"name": "answer"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 14,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -123,6 +122,7 @@
},
"name": "if"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 10,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -123,6 +122,7 @@
},
"name": "true"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 12,

View File

@ -42,7 +42,6 @@
"column": 26
}
},
"await": false,
"left": {
"type": "Identifier",
"start": 4,

View File

@ -42,7 +42,6 @@
"column": 31
}
},
"await": false,
"left": {
"type": "VariableDeclaration",
"start": 5,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -123,6 +122,7 @@
},
"name": "false"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 13,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -123,6 +122,7 @@
},
"name": "null"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 12,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "StringLiteral",
@ -126,6 +125,7 @@
},
"value": "answer"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 16,

View File

@ -72,7 +72,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -91,6 +90,7 @@
},
"name": "message"
},
"shorthand": false,
"value": {
"type": "StringLiteral",
"start": 17,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -123,6 +122,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 9,
@ -159,7 +159,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -178,6 +177,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 15,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -100,7 +100,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -119,6 +118,7 @@
},
"name": "a"
},
"shorthand": false,
"value": {
"type": "ArrayExpression",
"start": 5,

View File

@ -72,7 +72,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "NumericLiteral",
@ -94,6 +93,7 @@
},
"value": 1
},
"shorthand": false,
"value": {
"type": "BinaryExpression",
"start": 5,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "StringLiteral",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "NumericLiteral",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "StringLiteral",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "NumericLiteral",

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -123,6 +122,7 @@
},
"name": "get"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 11,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -123,6 +122,7 @@
},
"name": "set"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 11,

View File

@ -42,7 +42,6 @@
"column": 31
}
},
"await": false,
"left": {
"type": "VariableDeclaration",
"start": 5,

View File

@ -87,7 +87,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -106,6 +105,7 @@
},
"name": "public"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 8,

View File

@ -87,7 +87,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -106,6 +105,7 @@
},
"name": "arguments"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 8,

View File

@ -0,0 +1 @@
var a = 0123.;

View File

@ -0,0 +1,3 @@
{
"throws": "Unexpected token (1:13)"
}

View File

@ -116,7 +116,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -135,6 +134,7 @@
},
"name": "length"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 9,

View File

@ -123,7 +123,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -142,6 +141,7 @@
},
"name": "title"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 14,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": true,
"key": {
"type": "CallExpression",
@ -139,6 +138,7 @@
},
"arguments": []
},
"shorthand": false,
"value": {
"type": "StringLiteral",
"start": 23,

View File

@ -0,0 +1 @@
({ x, ...{ y, z } } = o)

View File

@ -0,0 +1,300 @@
{
"type": "File",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 24
}
},
"program": {
"type": "Program",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 24
}
},
"sourceType": "script",
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 24
}
},
"expression": {
"type": "AssignmentExpression",
"start": 1,
"end": 23,
"loc": {
"start": {
"line": 1,
"column": 1
},
"end": {
"line": 1,
"column": 23
}
},
"operator": "=",
"left": {
"type": "ObjectPattern",
"start": 1,
"end": 19,
"loc": {
"start": {
"line": 1,
"column": 1
},
"end": {
"line": 1,
"column": 19
}
},
"properties": [
{
"type": "ObjectProperty",
"start": 3,
"end": 4,
"loc": {
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 1,
"column": 4
}
},
"method": false,
"computed": false,
"key": {
"type": "Identifier",
"start": 3,
"end": 4,
"loc": {
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 1,
"column": 4
},
"identifierName": "x"
},
"name": "x"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 3,
"end": 4,
"loc": {
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 1,
"column": 4
},
"identifierName": "x"
},
"name": "x"
},
"extra": {
"shorthand": true
}
},
{
"type": "RestElement",
"start": 6,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 17
}
},
"argument": {
"type": "ObjectPattern",
"start": 9,
"end": 17,
"loc": {
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 17
}
},
"properties": [
{
"type": "ObjectProperty",
"start": 11,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 12
}
},
"method": false,
"computed": false,
"key": {
"type": "Identifier",
"start": 11,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 12
},
"identifierName": "y"
},
"name": "y"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 11,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 12
},
"identifierName": "y"
},
"name": "y"
},
"extra": {
"shorthand": true
}
},
{
"type": "ObjectProperty",
"start": 14,
"end": 15,
"loc": {
"start": {
"line": 1,
"column": 14
},
"end": {
"line": 1,
"column": 15
}
},
"method": false,
"computed": false,
"key": {
"type": "Identifier",
"start": 14,
"end": 15,
"loc": {
"start": {
"line": 1,
"column": 14
},
"end": {
"line": 1,
"column": 15
},
"identifierName": "z"
},
"name": "z"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 14,
"end": 15,
"loc": {
"start": {
"line": 1,
"column": 14
},
"end": {
"line": 1,
"column": 15
},
"identifierName": "z"
},
"name": "z"
},
"extra": {
"shorthand": true
}
}
]
}
}
]
},
"right": {
"type": "Identifier",
"start": 22,
"end": 23,
"loc": {
"start": {
"line": 1,
"column": 22
},
"end": {
"line": 1,
"column": 23
},
"identifierName": "o"
},
"name": "o"
},
"extra": {
"parenthesized": true,
"parenStart": 0
}
}
}
],
"directives": []
}
}

View File

@ -0,0 +1,3 @@
{
"plugins": ["objectRestSpread"]
}

View File

@ -103,7 +103,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -122,6 +121,7 @@
},
"name": "foo"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 15,
@ -242,7 +242,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -261,6 +260,7 @@
},
"name": "foo"
},
"shorthand": false,
"value": {
"type": "Identifier",
"start": 48,
@ -378,7 +378,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -397,6 +396,7 @@
},
"name": "foo"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 82,
@ -427,7 +427,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -446,6 +445,7 @@
},
"name": "baz"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 84,
@ -569,7 +569,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -588,6 +587,7 @@
},
"name": "foo"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 119,
@ -618,7 +618,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -637,6 +636,7 @@
},
"name": "baz"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 127,
@ -667,7 +667,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -686,6 +685,7 @@
},
"name": "qux"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 129,
@ -812,7 +812,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -831,6 +830,7 @@
},
"name": "foo"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 166,
@ -861,7 +861,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -880,6 +879,7 @@
},
"name": "baz"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 174,
@ -910,7 +910,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -929,6 +928,7 @@
},
"name": "qux2"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 176,
@ -971,7 +971,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -990,6 +989,7 @@
},
"name": "foo3"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 186,
@ -1344,7 +1344,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -1363,6 +1362,7 @@
},
"name": "foo"
},
"shorthand": false,
"value": {
"type": "ArrayPattern",
"start": 277,
@ -1430,7 +1430,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -1449,6 +1448,7 @@
},
"name": "foo2"
},
"shorthand": false,
"value": {
"type": "ArrayPattern",
"start": 295,
@ -1585,7 +1585,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -1604,6 +1603,7 @@
},
"name": "foo"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 332,
@ -1634,7 +1634,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -1653,6 +1652,7 @@
},
"name": "baz"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 339,
@ -1683,7 +1683,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -1702,6 +1701,7 @@
},
"name": "qux3"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 341,
@ -1744,7 +1744,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -1763,6 +1762,7 @@
},
"name": "foo2"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 357,
@ -1793,7 +1793,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -1812,6 +1811,7 @@
},
"name": "baz2"
},
"shorthand": false,
"value": {
"type": "ArrayPattern",
"start": 365,
@ -1949,7 +1949,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -1968,6 +1967,7 @@
},
"name": "foo"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 402,
@ -1998,7 +1998,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -2017,6 +2016,7 @@
},
"name": "baz"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 409,
@ -2047,7 +2047,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -2066,6 +2065,7 @@
},
"name": "qux5"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 411,
@ -2108,7 +2108,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -2127,6 +2126,7 @@
},
"name": "foo2"
},
"shorthand": false,
"value": {
"type": "ObjectPattern",
"start": 427,
@ -2157,7 +2157,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -2176,6 +2175,7 @@
},
"name": "baz2"
},
"shorthand": false,
"value": {
"type": "ArrayPattern",
"start": 435,
@ -2221,7 +2221,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -2240,6 +2239,7 @@
},
"name": "qux6"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 437,
@ -2368,7 +2368,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -2387,6 +2386,7 @@
},
"name": "Foo"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 469,
@ -2507,7 +2507,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -2526,6 +2525,7 @@
},
"name": "foo"
},
"shorthand": false,
"value": {
"type": "ArrayPattern",
"start": 502,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": true,
"key": {
"type": "Identifier",
"start": 11,
@ -123,6 +122,7 @@
"name": "async"
},
"computed": false,
"shorthand": true,
"value": {
"type": "Identifier",
"start": 11,
@ -159,7 +159,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -178,6 +177,7 @@
},
"name": "bar"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 18,

View File

@ -0,0 +1 @@
var x = ({ const });

View File

@ -0,0 +1,3 @@
{
"throws": "const is a reserved word (1:11)"
}

View File

@ -0,0 +1 @@
({ get, this, if });

View File

@ -0,0 +1,3 @@
{
"throws": "this is a reserved word (1:8)"
}

View File

@ -104,7 +104,6 @@
}
},
"method": true,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",

View File

@ -72,7 +72,6 @@
}
},
"method": false,
"shorthand": false,
"computed": true,
"key": {
"type": "Identifier",
@ -91,6 +90,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 7,

View File

@ -72,7 +72,6 @@
}
},
"method": false,
"shorthand": false,
"computed": true,
"key": {
"type": "BinaryExpression",
@ -130,6 +129,7 @@
"value": "y"
}
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 15,

View File

@ -72,7 +72,6 @@
}
},
"method": false,
"shorthand": false,
"computed": true,
"key": {
"type": "Identifier",
@ -91,6 +90,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "FunctionExpression",
"start": 7,

View File

@ -72,7 +72,6 @@
}
},
"method": false,
"shorthand": false,
"computed": true,
"key": {
"type": "Identifier",
@ -91,6 +90,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 7,
@ -127,7 +127,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -146,6 +145,7 @@
},
"name": "y"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 14,

View File

@ -72,7 +72,6 @@
}
},
"method": false,
"shorthand": false,
"computed": true,
"key": {
"type": "Identifier",
@ -130,7 +129,6 @@
}
},
"method": false,
"shorthand": false,
"computed": true,
"key": {
"type": "Identifier",

View File

@ -72,7 +72,6 @@
}
},
"method": true,
"shorthand": false,
"computed": true,
"key": {
"type": "Identifier",

View File

@ -87,7 +87,6 @@
}
},
"method": false,
"shorthand": false,
"computed": true,
"key": {
"type": "Identifier",
@ -106,6 +105,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "Identifier",
"start": 10,
@ -156,7 +156,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -175,6 +174,7 @@
},
"name": "y"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 16,

View File

@ -93,7 +93,6 @@
}
},
"method": false,
"shorthand": false,
"computed": true,
"key": {
"type": "Identifier",
@ -112,6 +111,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "Identifier",
"start": 17,

View File

@ -104,7 +104,6 @@
}
},
"method": true,
"shorthand": false,
"computed": true,
"key": {
"type": "Identifier",

View File

@ -107,7 +107,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -126,6 +125,7 @@
},
"name": "x"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 12,
@ -179,7 +179,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -198,6 +197,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 21,

View File

@ -137,7 +137,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -156,6 +155,7 @@
},
"name": "x"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 14,
@ -209,7 +209,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -228,6 +227,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 23,

View File

@ -72,7 +72,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -91,6 +90,7 @@
},
"name": "f"
},
"shorthand": false,
"value": {
"type": "FunctionExpression",
"start": 5,
@ -154,7 +154,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -173,6 +172,7 @@
},
"name": "x"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 15,
@ -226,7 +226,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -245,6 +244,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 24,

View File

@ -72,7 +72,6 @@
}
},
"method": true,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -141,7 +140,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -160,6 +158,7 @@
},
"name": "x"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 5,
@ -213,7 +212,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -232,6 +230,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 14,

View File

@ -156,7 +156,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -175,6 +174,7 @@
},
"name": "x"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 11,
@ -228,7 +228,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -247,6 +246,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 20,

View File

@ -105,7 +105,6 @@
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
@ -124,6 +123,7 @@
},
"name": "x"
},
"shorthand": true,
"value": {
"type": "Identifier",
"start": 3,
@ -177,7 +177,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -196,6 +195,7 @@
},
"name": "x"
},
"shorthand": false,
"value": {
"type": "NumericLiteral",
"start": 12,

View File

@ -104,7 +104,6 @@
}
},
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
@ -123,6 +122,7 @@
},
"name": "f"
},
"shorthand": false,
"value": {
"type": "FunctionExpression",
"start": 9,

Some files were not shown because too many files have changed in this diff Show More