From e2fe6d009ce68d087c1b90ae7c8acaefdb7c3399 Mon Sep 17 00:00:00 2001 From: benedikt willi Date: Mon, 7 Apr 2014 17:20:18 +0200 Subject: [PATCH] create the annotation string with acorn instead of regexps --- main.js | 270 +- thirdparty/acorn/LICENSE | 23 + thirdparty/acorn/README.md | 218 + thirdparty/acorn/acorn.js | 1784 ++ thirdparty/acorn/acorn_loose.js | 780 + thirdparty/acorn/bin/acorn | 39 + thirdparty/acorn/docco.css | 192 + thirdparty/acorn/index.html | 1346 + thirdparty/acorn/package.json | 17 + thirdparty/acorn/test/bench.html | 90 + thirdparty/acorn/test/codemirror-string.js | 3995 +++ thirdparty/acorn/test/compare/esprima.js | 3838 +++ thirdparty/acorn/test/compare/uglifyjs.js | 1372 + thirdparty/acorn/test/compare/uglifyjs2.js | 2494 ++ thirdparty/acorn/test/driver.js | 92 + thirdparty/acorn/test/index.html | 24 + thirdparty/acorn/test/jquery-string.js | 9047 +++++++ thirdparty/acorn/test/run.js | 14 + thirdparty/acorn/test/tests.js | 26998 +++++++++++++++++++ thirdparty/acorn/util/walk.js | 330 + 20 files changed, 52823 insertions(+), 140 deletions(-) create mode 100755 thirdparty/acorn/LICENSE create mode 100755 thirdparty/acorn/README.md create mode 100755 thirdparty/acorn/acorn.js create mode 100755 thirdparty/acorn/acorn_loose.js create mode 100755 thirdparty/acorn/bin/acorn create mode 100755 thirdparty/acorn/docco.css create mode 100755 thirdparty/acorn/index.html create mode 100755 thirdparty/acorn/package.json create mode 100755 thirdparty/acorn/test/bench.html create mode 100755 thirdparty/acorn/test/codemirror-string.js create mode 100755 thirdparty/acorn/test/compare/esprima.js create mode 100755 thirdparty/acorn/test/compare/uglifyjs.js create mode 100755 thirdparty/acorn/test/compare/uglifyjs2.js create mode 100755 thirdparty/acorn/test/driver.js create mode 100755 thirdparty/acorn/test/index.html create mode 100755 thirdparty/acorn/test/jquery-string.js create mode 100755 thirdparty/acorn/test/run.js create mode 100755 thirdparty/acorn/test/tests.js create mode 100755 thirdparty/acorn/util/walk.js diff --git a/main.js b/main.js index c5e2b59..f2ac7e8 100644 --- a/main.js +++ b/main.js @@ -26,160 +26,150 @@ /** extension to generate JSDoc annotations for functions */ define(function (require, exports, module) { - 'use strict'; - var CommandManager = brackets.getModule("command/CommandManager"), - EditorManager = brackets.getModule("editor/EditorManager"), - KeyBindingManager = brackets.getModule("command/KeyBindingManager"), - Menus = brackets.getModule("command/Menus"); + var AppInit = brackets.getModule("utils/AppInit"), + CommandManager = brackets.getModule("command/CommandManager"), + EditorManager = brackets.getModule("editor/EditorManager"), + KeyBindingManager = brackets.getModule("command/KeyBindingManager"), + Menus = brackets.getModule("command/Menus"), + Acorn_loose = require("thirdparty/acorn/acorn_loose"), + Walker = require("thirdparty/acorn/util/walk"); + var EMPTY_MSG = "No function found"; + var COMMAND_ID = "annotate.annotate"; + var MENU_NAME = "Annotate function"; - var EMPTY_MSG = "No function found"; - var COMMAND_ID = "annotate.annotate"; - var MENU_NAME = "Annotate function"; + // Global editor instance + var _editor = {}; + var _output = {}; - var REGEX_PATTERNS = { - comment: '\\/\\*.*\\*\\/', - jsVariable: '[$A-Za-z_][0-9A-Za-z_$]*' + + /** + * Create a jsdoc annotation and prepend it in the active document + */ + var annotate = function () { + // Get current active editor + _editor = EditorManager.getCurrentFullEditor(); + + //Get cursor position and set it to the beginning of the line + var pos = _editor.getCursorPos(); + pos.ch = 0; + + // Get the text from the start of the document to the current cursor position and count it's length' + var txtTo = _editor._codeMirror.getRange({ + line: 0, + ch: 0 + }, pos); + var cursorPosition = txtTo.length; + + // Get full txt + var fullTxt = _editor._codeMirror.getValue(); + + // Parse text + var acornTxtFull = Acorn_loose.parse_dammit(fullTxt, { + locations: true + }); + + // Find next function + var found = new Walker.findNodeAfter(acornTxtFull, cursorPosition, "Function"); + + if (found) { + // There was a result, so build jsdoc + _output = {}; + _output.loc = found.node.loc; + _output.prefix = ""; + _output.name = found.node.id ? found.node.id.name : null; + _output.params = []; + _output.returnValue = undefined; + + // Add parameters to the _output object + found.node.params.forEach(function (param) { + _output.params.push(param.name); + }); + + // Find and add return value + var foundReturnValue = new Walker.findNodeAfter(found.node, 0, "ReturnStatement"); + _output.returnValue = foundReturnValue.node ? foundReturnValue.node.argument.name : undefined; + + // set prefix (find first none whitespace character) + var codeLine = _editor._codeMirror.getLine(_output.loc.start.line - 1); + _output.prefix = codeLine.substr(0, codeLine.length - codeLine.trimLeft().length).replace(/[^\s\n]/g, ' '); + + // build annotation string + var _outputString = _getJSDocString(_output); + + // insertJsdoc string into editor + _insertJSDocString(_outputString, _output.loc); + } else { + // No function definition found + window.alert(EMPTY_MSG); + } }; - function insert(input) { - - var editor = EditorManager.getCurrentFullEditor(); - var pos = editor.getCursorPos(); - pos.ch = 0; - - editor._codeMirror.replaceRange(input, pos); + /** + * Get a functions name + */ + var _getName = function () { + //Todo + }; + + /** + * Get a functions return value + */ + var _getReturnValue = function () { + //Todo + }; + + /** + * Build the string representation of the + * @param {object} jsdoc object containing jsdoc properties + * @returns {string} annotation as a string + */ + var _getJSDocString = function (jsdoc) { + var jsdocString = jsdoc.prefix + "/**\n"; + + if (jsdoc.name && jsdoc.name.charAt(0) === "_") { + jsdocString += jsdoc.prefix + " * @private \n"; + } + + // Add description + jsdocString += jsdoc.prefix + " * Description \n"; + + jsdoc.params.forEach(function (param) { + jsdocString += jsdoc.prefix + " * @param {type} " + param + " Description \n"; + }); + if (jsdoc.returnValue) + jsdocString += jsdoc.prefix + " * @returns {type} Description \n"; + + jsdocString += jsdoc.prefix + " */ \n"; + + return jsdocString; + }; + + /** + * Insert the JSDoc annotation string to the document + * @param {string} jSDocString The JSDoc annotation string + * @param {location} loc location of the function found + */ + var _insertJSDocString = function (jSDocString, loc) { + var pos = { + line: loc.start.line - 1, + ch: 0 + }; + + // Place jsdocString in the editor + _editor._codeMirror.replaceRange(jSDocString, pos); EditorManager.focusEditor(); - - } - - /** - * get the whitespace characters from line start to beginning of function def - * @param string input lines from start of the function definition - * @param string match function definition start - */ - function getPrefix(input, match) { - - var indexOf = input.indexOf(match), - prefix = ""; - if (indexOf !== -1) { - prefix = input.substr(0, indexOf).replace(/[^\s\n]/g, ' '); - } - - return prefix; - - } - - function getTarget() { - - var editor = EditorManager.getCurrentFullEditor(), - pos = editor.getCursorPos(), - functionDeclarationRegex = new RegExp('^[a-z0-9]*\\s*\\n*\\bfunction\\b\\s*' + REGEX_PATTERNS.jsVariable + '\\s*\\(\\s*(' + - REGEX_PATTERNS.jsVariable + '\\s*,?)*\\s*\\)','g'), - - functionExpresionRegex = new RegExp('^[a-z0-9]*\\s*\\n*(var|(' + REGEX_PATTERNS.jsVariable + '.)*(' + REGEX_PATTERNS.jsVariable + ')?)?\\s*'+ REGEX_PATTERNS.jsVariable + '\\s*(=|:)\\s*function\\s*\\(\\s*(' + - REGEX_PATTERNS.jsVariable + '\\s*(,\\s*)?)*\\s*\\)\\s*','g'); - - pos.ch = 0; - - // Take the text of the document, starting with the current cursor line - var txtFrom = editor._codeMirror.getRange(pos, {line: editor._codeMirror.lineCount() }); - - //checks if there is a return value - var returnsValue = txtFrom.substr( txtFrom.indexOf('{'), txtFrom.indexOf('}')).search('return') !== -1; - - txtFrom = txtFrom.substr(0, txtFrom.indexOf("{")); - - //take any comment off - txtFrom = txtFrom.replace(new RegExp(REGEX_PATTERNS.comment,'g'), ''); - - var results = txtFrom.match(new RegExp(REGEX_PATTERNS.jsVariable,'g')); - switch(true) { - case functionExpresionRegex.test(txtFrom): - return { - //check for 'var' - name:results[results.indexOf('function')-1], - params:results.slice(results.indexOf('function')+1), - prefix: getPrefix(txtFrom, results[0]), - returnsValue:returnsValue - }; - case functionDeclarationRegex.test(txtFrom): - //console.log(results[1]); - return { - name:results[1], - params:results.slice(2), - prefix: getPrefix(txtFrom, results[0]), - returnsValue:returnsValue - }; - default: - return null; - } - } - - - /** - * Generate comment block - * @param string fname function name - * @param string params function parameters - * @param string prefix whitespace prefix for comment block lines - */ - function generateComment(fname, params,returnsValue, prefix) { - - var output = []; - output.push("/**"); - - // Assume function is private if it starts with an underscore - if (fname.charAt(0) === "_") { - output.push(" * @private"); - } - - // Add description - output.push(" * Description"); - - // Add parameters - if (params.length > 0) { - var i; - for (i = 0; i < params.length; i++) { - var param = params[i]; - output.push(" * @param {type} " + param + " Description"); - } - } - - if (returnsValue) output.push(" * @returns {type} Description"); - - // TODO use if 'return' is found in the function body? - //output += " * @return {type} ???\n"; - output.push(" */"); - - return prefix + output.join("\n" + prefix) + "\n"; - } - - - - function annotate() { - - var target = getTarget(); - - if (target === null) { - window.alert(EMPTY_MSG); - return; - } - - var comment = generateComment(target.name, target.params, target.returnsValue, target.prefix); - - insert(comment); - } - + }; + // Register stuff when brackets finished loading CommandManager.register(MENU_NAME, COMMAND_ID, annotate); KeyBindingManager.addBinding(COMMAND_ID, "Ctrl-Alt-A"); var menu = Menus.getMenu(Menus.AppMenuBar.EDIT_MENU); menu.addMenuDivider(); - menu.addMenuItem(COMMAND_ID);//"menu-edit-annotate", - + menu.addMenuItem(COMMAND_ID); //"menu-edit-annotate", }); \ No newline at end of file diff --git a/thirdparty/acorn/LICENSE b/thirdparty/acorn/LICENSE new file mode 100755 index 0000000..0eed648 --- /dev/null +++ b/thirdparty/acorn/LICENSE @@ -0,0 +1,23 @@ +Copyright (C) 2012-2014 by Marijn Haverbeke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Please note that some subdirectories of the CodeMirror distribution +include their own LICENSE files, and are released under different +licences. diff --git a/thirdparty/acorn/README.md b/thirdparty/acorn/README.md new file mode 100755 index 0000000..e16e165 --- /dev/null +++ b/thirdparty/acorn/README.md @@ -0,0 +1,218 @@ +# Acorn + +A tiny, fast JavaScript parser, written completely in JavaScript. + +## Installation + +The easiest way to install acorn is with [`npm`][npm]. + +[npm]: http://npmjs.org + +```sh +npm install acorn +``` + +Alternately, download the source. + +```sh +git clone https://github.com/marijnh/acorn.git +``` + +## Components + +When run in a CommonJS (node.js) or AMD environment, exported values +appear in the interfaces exposed by the individual files, as usual. +When loaded in the browser without any kind of module management, a +single global object `acorn` will be defined, and all the exported +properties will be added to that. + +### acorn.js + +This file contains the actual parser (and is what you get when you +`require("acorn")` in node.js). + +**parse**`(input, options)` is used to parse a JavaScript program. +The `input` parameter is a string, `options` can be undefined or an +object setting some of the options listed below. The return value will +be an abstract syntax tree object as specified by the +[Mozilla Parser API][mozapi]. + +When encountering a syntax error, the parser will raise a +`SyntaxError` object with a meaningful message. The error object will +have a `pos` property that indicates the character offset at which the +error occurred, and a `loc` object that contains a `{line, column}` +object referring to that same position. + +[mozapi]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API + +- **ecmaVersion**: Indicates the ECMAScript version to parse. Must be + either 3 or 5. This influences support for strict mode, the set of + reserved words, and support for getters and setter. Default is 5. + +- **strictSemicolons**: If `true`, prevents the parser from doing + automatic semicolon insertion, and statements that do not end with + a semicolon will generate an error. Defaults to `false`. + +- **allowTrailingCommas**: If `false`, the parser will not allow + trailing commas in array and object literals. Default is `true`. + +- **forbidReserved**: If `true`, using a reserved word will generate + an error. Defaults to `false`. When given the value `"everywhere"`, + reserved words and keywords can also not be used as property names + (as in Internet Explorer's old parser). + +- **allowReturnOutsideFunction**: By default, a return statement at + the top level raises an error. Set this to `true` to accept such + code. + +- **locations**: When `true`, each node has a `loc` object attached + with `start` and `end` subobjects, each of which contains the + one-based line and zero-based column numbers in `{line, column}` + form. Default is `false`. + +- **onComment**: If a function is passed for this option, whenever a + comment is encountered the function will be called with the + following parameters: + + - `block`: `true` if the comment is a block comment, false if it + is a line comment. + - `text`: The content of the comment. + - `start`: Character offset of the start of the comment. + - `end`: Character offset of the end of the comment. + + When the `locations` options is on, the `{line, column}` locations + of the comment’s start and end are passed as two additional + parameters. + + Note that you are not allowed to call the parser from the + callback—that will corrupt its internal state. + +- **ranges**: Nodes have their start and end characters offsets + recorded in `start` and `end` properties (directly on the node, + rather than the `loc` object, which holds line/column data. To also + add a [semi-standardized][range] "range" property holding a + `[start, end]` array with the same numbers, set the `ranges` option + to `true`. + +- **program**: It is possible to parse multiple files into a single + AST by passing the tree produced by parsing the first file as the + `program` option in subsequent parses. This will add the toplevel + forms of the parsed file to the "Program" (top) node of an existing + parse tree. + +- **sourceFile**: When the `locations` option is `true`, you can pass + this option to add a `sourceFile` attribute in every node’s `loc` + object. Note that the contents of this option are not examined or + processed in any way; you are free to use whatever format you + choose. + +- **directSourceFile**: Like `sourceFile`, but the property will be + added directly to the nodes, rather than to a `loc` object. + +[range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 + +**getLineInfo**`(input, offset)` can be used to get a `{line, +column}` object for a given program string and character offset. + +**tokenize**`(input, options)` exports a primitive interface to +Acorn's tokenizer. The function takes an input string and options +similar to `parse` (though only some options are meaningful here), and +returns a function that can be called repeatedly to read a single +token, and returns a `{start, end, type, value}` object (with added +`startLoc` and `endLoc` properties when the `locations` option is +enabled). This object will be reused (updated) for each token, so you +can't count on it staying stable. + +**tokTypes** holds an object mapping names to the token type objects +that end up in the `type` properties of tokens. + +### acorn_loose.js ### + +This file implements an error-tolerant parser. It exposes a single +function. + +**parse_dammit**`(input, options)` takes the same arguments and +returns the same syntax tree as the `parse` function in `acorn.js`, +but never raises an error, and will do its best to parse syntactically +invalid code in as meaningful a way as it can. It'll insert identifier +nodes with name `"✖"` as placeholders in places where it can't make +sense of the input. Depends on `acorn.js`, because it uses the same +tokenizer. + +### util/walk.js ### + +Implements an abstract syntax tree walker. Will store its interface in +`acorn.walk` when used without a module system. + +**simple**`(node, visitors, base, state)` does a 'simple' walk over +a tree. `node` should be the AST node to walk, and `visitors` an +object with properties whose names correspond to node types in the +[Mozilla Parser API][mozapi]. The properties should contain functions +that will be called with the node object and, if applicable the state +at that point. The last two arguments are optional. `base` is a walker +algorithm, and `state` is a start state. The default walker will +simply visit all statements and expressions and not produce a +meaningful state. (An example of a use of state it to track scope at +each point in the tree.) + +**ancestor**`(node, visitors, base, state)` does a 'simple' walk over +a tree, building up an array of ancestor nodes (including the current node) +and passing the array to callbacks in the `state` parameter. + +**recursive**`(node, state, functions, base)` does a 'recursive' +walk, where the walker functions are responsible for continuing the +walk on the child nodes of their target node. `state` is the start +state, and `functions` should contain an object that maps node types +to walker functions. Such functions are called with `(node, state, c)` +arguments, and can cause the walk to continue on a sub-node by calling +the `c` argument on it with `(node, state)` arguments. The optional +`base` argument provides the fallback walker functions for node types +that aren't handled in the `functions` object. If not given, the +default walkers will be used. + +**make**`(functions, base)` builds a new walker object by using the +walker functions in `functions` and filling in the missing ones by +taking defaults from `base`. + +**findNodeAt**`(node, start, end, test, base, state)` tries to +locate a node in a tree at the given start and/or end offsets, which +satisfies the predicate `test`. `start` end `end` can be either `null` +(as wildcard) or a number. `test` may be a string (indicating a node +type) or a function that takes `(nodeType, node)` arguments and +returns a boolean indicating whether this node is interesting. `base` +and `state` are optional, and can be used to specify a custom walker. +Nodes are tested from inner to outer, so if two nodes match the +boundaries, the inner one will be preferred. + +**findNodeAround**`(node, pos, test, base, state)` is a lot like +`findNodeAt`, but will match any node that exists 'around' (spanning) +the given position. + +**findNodeAfter**`(node, pos, test, base, state)` is similar to +`findNodeAround`, but will match all nodes *after* the given position +(testing outer nodes before inner nodes). + +## Command line interface + +The `bin/acorn` utility can be used to parse a file from the command +line. It accepts as arguments its input file and the following +options: + +- `--ecma3|--ecma5`: Sets the ECMAScript version to parse. Default is + version 5. + +- `--strictSemicolons`: Prevents the parser from doing automatic + semicolon insertion. Statements that do not end in semicolons will + generate an error. + +- `--locations`: Attaches a "loc" object to each node with "start" and + "end" subobjects, each of which contains the one-based line and + zero-based column numbers in `{line, column}` form. + +- `--compact`: No whitespace is used in the AST output. + +- `--silent`: Do not output the AST, just return the exit status. + +- `--help`: Print the usage information and quit. + +The utility spits out the syntax tree as JSON data. diff --git a/thirdparty/acorn/acorn.js b/thirdparty/acorn/acorn.js new file mode 100755 index 0000000..31935f4 --- /dev/null +++ b/thirdparty/acorn/acorn.js @@ -0,0 +1,1784 @@ +// Acorn is a tiny, fast JavaScript parser written in JavaScript. +// +// Acorn was written by Marijn Haverbeke and released under an MIT +// license. The Unicode regexps (for identifiers and whitespace) were +// taken from [Esprima](http://esprima.org) by Ariya Hidayat. +// +// Git repositories for Acorn are available at +// +// http://marijnhaverbeke.nl/git/acorn +// https://github.com/marijnh/acorn.git +// +// Please use the [github bug tracker][ghbt] to report issues. +// +// [ghbt]: https://github.com/marijnh/acorn/issues +// +// This file defines the main parser interface. The library also comes +// with a [error-tolerant parser][dammit] and an +// [abstract syntax tree walker][walk], defined in other files. +// +// [dammit]: acorn_loose.js +// [walk]: util/walk.js + +(function(root, mod) { + if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS + if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD + mod(root.acorn || (root.acorn = {})); // Plain browser env +})(this, function(exports) { + "use strict"; + + exports.version = "0.5.1"; + + // The main exported interface (under `self.acorn` when in the + // browser) is a `parse` function that takes a code string and + // returns an abstract syntax tree as specified by [Mozilla parser + // API][api], with the caveat that the SpiderMonkey-specific syntax + // (`let`, `yield`, inline XML, etc) is not recognized. + // + // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API + + var options, input, inputLen, sourceFile; + + exports.parse = function(inpt, opts) { + input = String(inpt); inputLen = input.length; + setOptions(opts); + initTokenState(); + return parseTopLevel(options.program); + }; + + // A second optional argument can be given to further configure + // the parser process. These options are recognized: + + var defaultOptions = exports.defaultOptions = { + // `ecmaVersion` indicates the ECMAScript version to parse. Must + // be either 3 or 5. This + // influences support for strict mode, the set of reserved words, and + // support for getters and setter. + ecmaVersion: 5, + // Turn on `strictSemicolons` to prevent the parser from doing + // automatic semicolon insertion. + strictSemicolons: false, + // When `allowTrailingCommas` is false, the parser will not allow + // trailing commas in array and object literals. + allowTrailingCommas: true, + // By default, reserved words are not enforced. Enable + // `forbidReserved` to enforce them. When this option has the + // value "everywhere", reserved words and keywords can also not be + // used as property names. + forbidReserved: false, + // When enabled, a return at the top level is not considered an + // error. + allowReturnOutsideFunction: false, + // When `locations` is on, `loc` properties holding objects with + // `start` and `end` properties in `{line, column}` form (with + // line being 1-based and column 0-based) will be attached to the + // nodes. + locations: false, + // A function can be passed as `onComment` option, which will + // cause Acorn to call that function with `(block, text, start, + // end)` parameters whenever a comment is skipped. `block` is a + // boolean indicating whether this is a block (`/* */`) comment, + // `text` is the content of the comment, and `start` and `end` are + // character offsets that denote the start and end of the comment. + // When the `locations` option is on, two more parameters are + // passed, the full `{line, column}` locations of the start and + // end of the comments. Note that you are not allowed to call the + // parser from the callback—that will corrupt its internal state. + onComment: null, + // Nodes have their start and end characters offsets recorded in + // `start` and `end` properties (directly on the node, rather than + // the `loc` object, which holds line/column data. To also add a + // [semi-standardized][range] `range` property holding a `[start, + // end]` array with the same numbers, set the `ranges` option to + // `true`. + // + // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 + ranges: false, + // It is possible to parse multiple files into a single AST by + // passing the tree produced by parsing the first file as + // `program` option in subsequent parses. This will add the + // toplevel forms of the parsed file to the `Program` (top) node + // of an existing parse tree. + program: null, + // When `locations` is on, you can pass this to record the source + // file in every node's `loc` object. + sourceFile: null, + // This value, if given, is stored in every node, whether + // `locations` is on or off. + directSourceFile: null + }; + + function setOptions(opts) { + options = opts || {}; + for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt)) + options[opt] = defaultOptions[opt]; + sourceFile = options.sourceFile || null; + } + + // The `getLineInfo` function is mostly useful when the + // `locations` option is off (for performance reasons) and you + // want to find the line/column position for a given character + // offset. `input` should be the code string that the offset refers + // into. + + var getLineInfo = exports.getLineInfo = function(input, offset) { + for (var line = 1, cur = 0;;) { + lineBreak.lastIndex = cur; + var match = lineBreak.exec(input); + if (match && match.index < offset) { + ++line; + cur = match.index + match[0].length; + } else break; + } + return {line: line, column: offset - cur}; + }; + + // Acorn is organized as a tokenizer and a recursive-descent parser. + // The `tokenize` export provides an interface to the tokenizer. + // Because the tokenizer is optimized for being efficiently used by + // the Acorn parser itself, this interface is somewhat crude and not + // very modular. Performing another parse or call to `tokenize` will + // reset the internal state, and invalidate existing tokenizers. + + exports.tokenize = function(inpt, opts) { + input = String(inpt); inputLen = input.length; + setOptions(opts); + initTokenState(); + + var t = {}; + function getToken(forceRegexp) { + lastEnd = tokEnd; + readToken(forceRegexp); + t.start = tokStart; t.end = tokEnd; + t.startLoc = tokStartLoc; t.endLoc = tokEndLoc; + t.type = tokType; t.value = tokVal; + return t; + } + getToken.jumpTo = function(pos, reAllowed) { + tokPos = pos; + if (options.locations) { + tokCurLine = 1; + tokLineStart = lineBreak.lastIndex = 0; + var match; + while ((match = lineBreak.exec(input)) && match.index < pos) { + ++tokCurLine; + tokLineStart = match.index + match[0].length; + } + } + tokRegexpAllowed = reAllowed; + skipSpace(); + }; + return getToken; + }; + + // State is kept in (closure-)global variables. We already saw the + // `options`, `input`, and `inputLen` variables above. + + // The current position of the tokenizer in the input. + + var tokPos; + + // The start and end offsets of the current token. + + var tokStart, tokEnd; + + // When `options.locations` is true, these hold objects + // containing the tokens start and end line/column pairs. + + var tokStartLoc, tokEndLoc; + + // The type and value of the current token. Token types are objects, + // named by variables against which they can be compared, and + // holding properties that describe them (indicating, for example, + // the precedence of an infix operator, and the original name of a + // keyword token). The kind of value that's held in `tokVal` depends + // on the type of the token. For literals, it is the literal value, + // for operators, the operator name, and so on. + + var tokType, tokVal; + + // Interal state for the tokenizer. To distinguish between division + // operators and regular expressions, it remembers whether the last + // token was one that is allowed to be followed by an expression. + // (If it is, a slash is probably a regexp, if it isn't it's a + // division operator. See the `parseStatement` function for a + // caveat.) + + var tokRegexpAllowed; + + // When `options.locations` is true, these are used to keep + // track of the current line, and know when a new line has been + // entered. + + var tokCurLine, tokLineStart; + + // These store the position of the previous token, which is useful + // when finishing a node and assigning its `end` position. + + var lastStart, lastEnd, lastEndLoc; + + // This is the parser's state. `inFunction` is used to reject + // `return` statements outside of functions, `labels` to verify that + // `break` and `continue` have somewhere to jump to, and `strict` + // indicates whether strict mode is on. + + var inFunction, labels, strict; + + // This function is used to raise exceptions on parse errors. It + // takes an offset integer (into the current `input`) to indicate + // the location of the error, attaches the position to the end + // of the error message, and then raises a `SyntaxError` with that + // message. + + function raise(pos, message) { + var loc = getLineInfo(input, pos); + message += " (" + loc.line + ":" + loc.column + ")"; + var err = new SyntaxError(message); + err.pos = pos; err.loc = loc; err.raisedAt = tokPos; + throw err; + } + + // Reused empty array added for node fields that are always empty. + + var empty = []; + + // ## Token types + + // The assignment of fine-grained, information-carrying type objects + // allows the tokenizer to store the information it has about a + // token in a way that is very cheap for the parser to look up. + + // All token type variables start with an underscore, to make them + // easy to recognize. + + // These are the general types. The `type` property is only used to + // make them recognizeable when debugging. + + var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"}; + var _name = {type: "name"}, _eof = {type: "eof"}; + + // Keyword tokens. The `keyword` property (also used in keyword-like + // operators) indicates that the token originated from an + // identifier-like word, which is used when parsing property names. + // + // The `beforeExpr` property is used to disambiguate between regular + // expressions and divisions. It is set on all token types that can + // be followed by an expression (thus, a slash after them would be a + // regular expression). + // + // `isLoop` marks a keyword as starting a loop, which is important + // to know when parsing a label, in order to allow or disallow + // continue jumps to that label. + + var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"}; + var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"}; + var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true}; + var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"}; + var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"}; + var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"}; + var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true}; + var _this = {keyword: "this"}; + + // The keywords that denote values. + + var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true}; + var _false = {keyword: "false", atomValue: false}; + + // Some keywords are treated as regular operators. `in` sometimes + // (when parsing `for`) needs to be tested against specifically, so + // we assign a variable name to it for quick comparing. + + var _in = {keyword: "in", binop: 7, beforeExpr: true}; + + // Map keyword names to token types. + + var keywordTypes = {"break": _break, "case": _case, "catch": _catch, + "continue": _continue, "debugger": _debugger, "default": _default, + "do": _do, "else": _else, "finally": _finally, "for": _for, + "function": _function, "if": _if, "return": _return, "switch": _switch, + "throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with, + "null": _null, "true": _true, "false": _false, "new": _new, "in": _in, + "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this, + "typeof": {keyword: "typeof", prefix: true, beforeExpr: true}, + "void": {keyword: "void", prefix: true, beforeExpr: true}, + "delete": {keyword: "delete", prefix: true, beforeExpr: true}}; + + // Punctuation token types. Again, the `type` property is purely for debugging. + + var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true}; + var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; + var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; + var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; + + // Operators. These carry several kinds of properties to help the + // parser use them properly (the presence of these properties is + // what categorizes them as operators). + // + // `binop`, when present, specifies that this operator is a binary + // operator, and will refer to its precedence. + // + // `prefix` and `postfix` mark the operator as a prefix or postfix + // unary operator. `isUpdate` specifies that the node produced by + // the operator should be of type UpdateExpression rather than + // simply UnaryExpression (`++` and `--`). + // + // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as + // binary operators with a very low precedence, that should result + // in AssignmentExpression nodes. + + var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true}; + var _assign = {isAssign: true, beforeExpr: true}; + var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true}; + var _logicalOR = {binop: 1, beforeExpr: true}; + var _logicalAND = {binop: 2, beforeExpr: true}; + var _bitwiseOR = {binop: 3, beforeExpr: true}; + var _bitwiseXOR = {binop: 4, beforeExpr: true}; + var _bitwiseAND = {binop: 5, beforeExpr: true}; + var _equality = {binop: 6, beforeExpr: true}; + var _relational = {binop: 7, beforeExpr: true}; + var _bitShift = {binop: 8, beforeExpr: true}; + var _plusMin = {binop: 9, prefix: true, beforeExpr: true}; + var _multiplyModulo = {binop: 10, beforeExpr: true}; + + // Provide access to the token types for external users of the + // tokenizer. + + exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR, + parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon, + dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof, + num: _num, regexp: _regexp, string: _string}; + for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; + + // 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 + // a regular expression, and on Chrome the two are about on par. + // This function uses `eval` (non-lexical) to produce such a + // predicate from a space-separated string of words. + // + // It starts by sorting the words by length. + + function makePredicate(words) { + words = words.split(" "); + var f = "", cats = []; + out: for (var i = 0; i < words.length; ++i) { + for (var j = 0; j < cats.length; ++j) + if (cats[j][0].length == words[i].length) { + cats[j].push(words[i]); + continue out; + } + cats.push([words[i]]); + } + function compareTo(arr) { + if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";"; + f += "switch(str){"; + for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":"; + f += "return true}return false;"; + } + + // When there are more than three length categories, an outer + // switch first dispatches on the lengths, to save on comparisons. + + if (cats.length > 3) { + cats.sort(function(a, b) {return b.length - a.length;}); + f += "switch(str.length){"; + for (var i = 0; i < cats.length; ++i) { + var cat = cats[i]; + f += "case " + cat[0].length + ":"; + compareTo(cat); + } + f += "}"; + + // Otherwise, simply generate a flat `switch` statement. + + } else { + compareTo(words); + } + return new Function("str", f); + } + + // The ECMAScript 3 reserved word list. + + var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile"); + + // ECMAScript 5 reserved words. + + var isReservedWord5 = makePredicate("class enum extends super const export import"); + + // The additional reserved words in strict mode. + + var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield"); + + // The forbidden variable names in strict mode. + + var isStrictBadIdWord = makePredicate("eval arguments"); + + // And the keywords. + + var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"); + + // ## Character categories + + // Big ugly regular expressions that match characters in the + // whitespace, identifier, and identifier-start categories. These + // are only applied when a character is found to actually have a + // code point above 128. + + var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/; + var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; + var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; + var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); + var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); + + // Whether a single character denotes a newline. + + var newline = /[\n\r\u2028\u2029]/; + + // Matches a whole line break (where CRLF is considered a single + // line break). Used to count lines. + + var lineBreak = /\r\n|[\n\r\u2028\u2029]/g; + + // Test whether a given character code starts an identifier. + + var isIdentifierStart = exports.isIdentifierStart = function(code) { + if (code < 65) return code === 36; + if (code < 91) return true; + if (code < 97) return code === 95; + if (code < 123)return true; + return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); + }; + + // Test whether a given character is part of an identifier. + + var isIdentifierChar = exports.isIdentifierChar = function(code) { + if (code < 48) return code === 36; + if (code < 58) return true; + if (code < 65) return false; + if (code < 91) return true; + if (code < 97) return code === 95; + if (code < 123)return true; + return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); + }; + + // ## Tokenizer + + // These are used when `options.locations` is on, for the + // `tokStartLoc` and `tokEndLoc` properties. + + function Position() { + this.line = tokCurLine; + this.column = tokPos - tokLineStart; + } + + // Reset the token state. Used at the start of a parse. + + function initTokenState() { + tokCurLine = 1; + tokPos = tokLineStart = 0; + tokRegexpAllowed = true; + skipSpace(); + } + + // Called at the end of every token. Sets `tokEnd`, `tokVal`, and + // `tokRegexpAllowed`, and skips the space after the token, so that + // the next one's `tokStart` will point at the right position. + + function finishToken(type, val) { + tokEnd = tokPos; + if (options.locations) tokEndLoc = new Position; + tokType = type; + skipSpace(); + tokVal = val; + tokRegexpAllowed = type.beforeExpr; + } + + function skipBlockComment() { + var startLoc = options.onComment && options.locations && new Position; + var start = tokPos, end = input.indexOf("*/", tokPos += 2); + if (end === -1) raise(tokPos - 2, "Unterminated comment"); + tokPos = end + 2; + if (options.locations) { + lineBreak.lastIndex = start; + var match; + while ((match = lineBreak.exec(input)) && match.index < tokPos) { + ++tokCurLine; + tokLineStart = match.index + match[0].length; + } + } + if (options.onComment) + options.onComment(true, input.slice(start + 2, end), start, tokPos, + startLoc, options.locations && new Position); + } + + function skipLineComment() { + var start = tokPos; + var startLoc = options.onComment && options.locations && new Position; + var ch = input.charCodeAt(tokPos+=2); + while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { + ++tokPos; + ch = input.charCodeAt(tokPos); + } + if (options.onComment) + options.onComment(false, input.slice(start + 2, tokPos), start, tokPos, + startLoc, options.locations && new Position); + } + + // Called at the start of the parse and after every token. Skips + // whitespace and comments, and. + + function skipSpace() { + while (tokPos < inputLen) { + var ch = input.charCodeAt(tokPos); + if (ch === 32) { // ' ' + ++tokPos; + } else if (ch === 13) { + ++tokPos; + var next = input.charCodeAt(tokPos); + if (next === 10) { + ++tokPos; + } + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + } else if (ch === 10 || ch === 8232 || ch === 8233) { + ++tokPos; + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + } else if (ch > 8 && ch < 14) { + ++tokPos; + } else if (ch === 47) { // '/' + var next = input.charCodeAt(tokPos + 1); + if (next === 42) { // '*' + skipBlockComment(); + } else if (next === 47) { // '/' + skipLineComment(); + } else break; + } else if (ch === 160) { // '\xa0' + ++tokPos; + } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { + ++tokPos; + } else { + break; + } + } + } + + // ### Token reading + + // This is the function that is called to fetch the next token. It + // is somewhat obscure, because it works in character codes rather + // than characters, and because operator parsing has been inlined + // into it. + // + // All in the name of speed. + // + // The `forceRegexp` parameter is used in the one case where the + // `tokRegexpAllowed` trick does not work. See `parseStatement`. + + function readToken_dot() { + var next = input.charCodeAt(tokPos + 1); + if (next >= 48 && next <= 57) return readNumber(true); + ++tokPos; + return finishToken(_dot); + } + + function readToken_slash() { // '/' + var next = input.charCodeAt(tokPos + 1); + if (tokRegexpAllowed) {++tokPos; return readRegexp();} + if (next === 61) return finishOp(_assign, 2); + return finishOp(_slash, 1); + } + + function readToken_mult_modulo() { // '%*' + var next = input.charCodeAt(tokPos + 1); + if (next === 61) return finishOp(_assign, 2); + return finishOp(_multiplyModulo, 1); + } + + function readToken_pipe_amp(code) { // '|&' + var next = input.charCodeAt(tokPos + 1); + if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2); + if (next === 61) return finishOp(_assign, 2); + return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1); + } + + function readToken_caret() { // '^' + var next = input.charCodeAt(tokPos + 1); + if (next === 61) return finishOp(_assign, 2); + return finishOp(_bitwiseXOR, 1); + } + + function readToken_plus_min(code) { // '+-' + var next = input.charCodeAt(tokPos + 1); + if (next === code) { + if (next == 45 && input.charCodeAt(tokPos + 2) == 62 && + newline.test(input.slice(lastEnd, tokPos))) { + // A `-->` line comment + tokPos += 3; + skipLineComment(); + skipSpace(); + return readToken(); + } + return finishOp(_incDec, 2); + } + if (next === 61) return finishOp(_assign, 2); + return finishOp(_plusMin, 1); + } + + function readToken_lt_gt(code) { // '<>' + var next = input.charCodeAt(tokPos + 1); + var size = 1; + if (next === code) { + size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2; + if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1); + return finishOp(_bitShift, size); + } + if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 && + input.charCodeAt(tokPos + 3) == 45) { + // `10;\n --> nothing", { + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AssignmentExpression", + operator: "=", + left: { + type: "Identifier", + name: "x" + }, + right: { + type: "BinaryExpression", + left: { + type: "UpdateExpression", + operator: "--", + prefix: false, + argument: { + type: "Identifier", + name: "y" + } + }, + operator: ">", + right: { + type: "Literal", + value: 10 + } + } + } + } + ] +}); + +test("'use strict';\nobject.static();", { + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "Literal", + value: "use strict", + raw: "'use strict'" + } + }, + { + type: "ExpressionStatement", + expression: { + type: "CallExpression", + callee: { + type: "MemberExpression", + object: { + type: "Identifier", + name: "object" + }, + property: { + type: "Identifier", + name: "static" + }, + computed: false + }, + arguments: [] + } + } + ] +}); + +// Failure tests + +testFail("{", + "Unexpected token (1:1)"); + +testFail("}", + "Unexpected token (1:0)"); + +testFail("3ea", + "Invalid number (1:0)"); + +testFail("3in []", + "Identifier directly after number (1:1)"); + +testFail("3e", + "Invalid number (1:0)"); + +testFail("3e+", + "Invalid number (1:0)"); + +testFail("3e-", + "Invalid number (1:0)"); + +testFail("3x", + "Identifier directly after number (1:1)"); + +testFail("3x0", + "Identifier directly after number (1:1)"); + +testFail("0x", + "Expected hexadecimal number (1:2)"); + +testFail("09", + "Invalid number (1:0)"); + +testFail("018", + "Invalid number (1:0)"); + +testFail("01a", + "Identifier directly after number (1:2)"); + +testFail("3in[]", + "Identifier directly after number (1:1)"); + +testFail("0x3in[]", + "Identifier directly after number (1:3)"); + +testFail("\"Hello\nWorld\"", + "Unterminated string constant (1:0)"); + +testFail("x\\", + "Expecting Unicode escape sequence \\uXXXX (1:2)"); + +testFail("x\\u005c", + "Invalid Unicode escape (1:3)"); + +testFail("x\\u002a", + "Invalid Unicode escape (1:3)"); + +testFail("/", + "Unterminated regular expression (1:1)"); + +testFail("/test", + "Unterminated regular expression (1:1)"); + +testFail("var x = /[a-z]/\\ux", + "Bad character escape sequence (1:8)"); + +testFail("3 = 4", + "Assigning to rvalue (1:0)"); + +testFail("func() = 4", + "Assigning to rvalue (1:0)"); + +testFail("(1 + 1) = 10", + "Assigning to rvalue (1:0)"); + +testFail("1++", + "Assigning to rvalue (1:0)"); + +testFail("1--", + "Assigning to rvalue (1:0)"); + +testFail("++1", + "Assigning to rvalue (1:2)"); + +testFail("--1", + "Assigning to rvalue (1:2)"); + +testFail("for((1 + 1) in list) process(x);", + "Assigning to rvalue (1:4)"); + +testFail("[", + "Unexpected token (1:1)"); + +testFail("[,", + "Unexpected token (1:2)"); + +testFail("1 + {", + "Unexpected token (1:5)"); + +testFail("1 + { t:t ", + "Unexpected token (1:10)"); + +testFail("1 + { t:t,", + "Unexpected token (1:10)"); + +testFail("var x = /\n/", + "Unterminated regular expression (1:9)"); + +testFail("var x = \"\n", + "Unterminated string constant (1:8)"); + +testFail("var if = 42", + "Unexpected token (1:4)"); + +testFail("i + 2 = 42", + "Assigning to rvalue (1:0)"); + +testFail("+i = 42", + "Assigning to rvalue (1:0)"); + +testFail("1 + (", + "Unexpected token (1:5)"); + +testFail("\n\n\n{", + "Unexpected token (4:1)"); + +testFail("\n/* Some multiline\ncomment */\n)", + "Unexpected token (4:0)"); + +testFail("{ set 1 }", + "Unexpected token (1:6)"); + +testFail("{ get 2 }", + "Unexpected token (1:6)"); + +testFail("({ set: s(if) { } })", + "Unexpected token (1:10)"); + +testFail("({ set s(.) { } })", + "Unexpected token (1:9)"); + +testFail("({ set: s() { } })", + "Unexpected token (1:12)"); + +testFail("({ set: s(a, b) { } })", + "Unexpected token (1:16)"); + +testFail("({ get: g(d) { } })", + "Unexpected token (1:13)"); + +testFail("({ get i() { }, i: 42 })", + "Redefinition of property (1:16)"); + +testFail("({ i: 42, get i() { } })", + "Redefinition of property (1:14)"); + +testFail("({ set i(x) { }, i: 42 })", + "Redefinition of property (1:17)"); + +testFail("({ i: 42, set i(x) { } })", + "Redefinition of property (1:14)"); + +testFail("({ get i() { }, get i() { } })", + "Redefinition of property (1:20)"); + +testFail("({ set i(x) { }, set i(x) { } })", + "Redefinition of property (1:21)"); + +testFail("function t(if) { }", + "Unexpected token (1:11)"); + +testFail("function t(true) { }", + "Unexpected token (1:11)"); + +testFail("function t(false) { }", + "Unexpected token (1:11)"); + +testFail("function t(null) { }", + "Unexpected token (1:11)"); + +testFail("function null() { }", + "Unexpected token (1:9)"); + +testFail("function true() { }", + "Unexpected token (1:9)"); + +testFail("function false() { }", + "Unexpected token (1:9)"); + +testFail("function if() { }", + "Unexpected token (1:9)"); + +testFail("a b;", + "Unexpected token (1:2)"); + +testFail("if.a;", + "Unexpected token (1:2)"); + +testFail("a if;", + "Unexpected token (1:2)"); + +testFail("a class;", + "Unexpected token (1:2)"); + +testFail("break\n", + "Unsyntactic break (1:0)"); + +testFail("break 1;", + "Unexpected token (1:6)"); + +testFail("continue\n", + "Unsyntactic continue (1:0)"); + +testFail("continue 2;", + "Unexpected token (1:9)"); + +testFail("throw", + "Unexpected token (1:5)"); + +testFail("throw;", + "Unexpected token (1:5)"); + +testFail("for (var i, i2 in {});", + "Unexpected token (1:15)"); + +testFail("for ((i in {}));", + "Unexpected token (1:14)"); + +testFail("for (i + 1 in {});", + "Assigning to rvalue (1:5)"); + +testFail("for (+i in {});", + "Assigning to rvalue (1:5)"); + +testFail("if(false)", + "Unexpected token (1:9)"); + +testFail("if(false) doThis(); else", + "Unexpected token (1:24)"); + +testFail("do", + "Unexpected token (1:2)"); + +testFail("while(false)", + "Unexpected token (1:12)"); + +testFail("for(;;)", + "Unexpected token (1:7)"); + +testFail("with(x)", + "Unexpected token (1:7)"); + +testFail("try { }", + "Missing catch or finally clause (1:0)"); + +testFail("‿ = 10", + "Unexpected character '‿' (1:0)"); + +testFail("if(true) let a = 1;", + "Unexpected token (1:13)"); + +testFail("switch (c) { default: default: }", + "Multiple default clauses (1:22)"); + +testFail("new X().\"s\"", + "Unexpected token (1:8)"); + +testFail("/*", + "Unterminated comment (1:0)"); + +testFail("/*\n\n\n", + "Unterminated comment (1:0)"); + +testFail("/**", + "Unterminated comment (1:0)"); + +testFail("/*\n\n*", + "Unterminated comment (1:0)"); + +testFail("/*hello", + "Unterminated comment (1:0)"); + +testFail("/*hello *", + "Unterminated comment (1:0)"); + +testFail("\n]", + "Unexpected token (2:0)"); + +testFail("\r]", + "Unexpected token (2:0)"); + +testFail("\r\n]", + "Unexpected token (2:0)"); + +testFail("\n\r]", + "Unexpected token (3:0)"); + +testFail("//\r\n]", + "Unexpected token (2:0)"); + +testFail("//\n\r]", + "Unexpected token (3:0)"); + +testFail("/a\\\n/", + "Unterminated regular expression (1:1)"); + +testFail("//\r \n]", + "Unexpected token (3:0)"); + +testFail("/*\r\n*/]", + "Unexpected token (2:2)"); + +testFail("/*\n\r*/]", + "Unexpected token (3:2)"); + +testFail("/*\r \n*/]", + "Unexpected token (3:2)"); + +testFail("\\\\", + "Expecting Unicode escape sequence \\uXXXX (1:1)"); + +testFail("\\u005c", + "Invalid Unicode escape (1:2)"); + +testFail("\\x", + "Expecting Unicode escape sequence \\uXXXX (1:1)"); + +testFail("\\u0000", + "Invalid Unicode escape (1:2)"); + +testFail("‌ = []", + "Unexpected character '‌' (1:0)"); + +testFail("‍ = []", + "Unexpected character '‍' (1:0)"); + +testFail("\"\\", + "Unterminated string constant (1:0)"); + +testFail("\"\\u", + "Bad character escape sequence (1:0)"); + +testFail("return", + "'return' outside of function (1:0)"); + +testFail("break", + "Unsyntactic break (1:0)"); + +testFail("continue", + "Unsyntactic continue (1:0)"); + +testFail("switch (x) { default: continue; }", + "Unsyntactic continue (1:22)"); + +testFail("do { x } *", + "Unexpected token (1:9)"); + +testFail("while (true) { break x; }", + "Unsyntactic break (1:15)"); + +testFail("while (true) { continue x; }", + "Unsyntactic continue (1:15)"); + +testFail("x: while (true) { (function () { break x; }); }", + "Unsyntactic break (1:33)"); + +testFail("x: while (true) { (function () { continue x; }); }", + "Unsyntactic continue (1:33)"); + +testFail("x: while (true) { (function () { break; }); }", + "Unsyntactic break (1:33)"); + +testFail("x: while (true) { (function () { continue; }); }", + "Unsyntactic continue (1:33)"); + +testFail("x: while (true) { x: while (true) { } }", + "Label 'x' is already declared (1:18)"); + +testFail("(function () { 'use strict'; delete i; }())", + "Deleting local variable in strict mode (1:29)"); + +testFail("(function () { 'use strict'; with (i); }())", + "'with' in strict mode (1:29)"); + +testFail("function hello() {'use strict'; ({ i: 42, i: 42 }) }", + "Redefinition of property (1:42)"); + +testFail("function hello() {'use strict'; ({ hasOwnProperty: 42, hasOwnProperty: 42 }) }", + "Redefinition of property (1:55)"); + +testFail("function hello() {'use strict'; var eval = 10; }", + "Binding eval in strict mode (1:36)"); + +testFail("function hello() {'use strict'; var arguments = 10; }", + "Binding arguments in strict mode (1:36)"); + +testFail("function hello() {'use strict'; try { } catch (eval) { } }", + "Binding eval in strict mode (1:47)"); + +testFail("function hello() {'use strict'; try { } catch (arguments) { } }", + "Binding arguments in strict mode (1:47)"); + +testFail("function hello() {'use strict'; eval = 10; }", + "Assigning to eval in strict mode (1:32)"); + +testFail("function hello() {'use strict'; arguments = 10; }", + "Assigning to arguments in strict mode (1:32)"); + +testFail("function hello() {'use strict'; ++eval; }", + "Assigning to eval in strict mode (1:34)"); + +testFail("function hello() {'use strict'; --eval; }", + "Assigning to eval in strict mode (1:34)"); + +testFail("function hello() {'use strict'; ++arguments; }", + "Assigning to arguments in strict mode (1:34)"); + +testFail("function hello() {'use strict'; --arguments; }", + "Assigning to arguments in strict mode (1:34)"); + +testFail("function hello() {'use strict'; eval++; }", + "Assigning to eval in strict mode (1:32)"); + +testFail("function hello() {'use strict'; eval--; }", + "Assigning to eval in strict mode (1:32)"); + +testFail("function hello() {'use strict'; arguments++; }", + "Assigning to arguments in strict mode (1:32)"); + +testFail("function hello() {'use strict'; arguments--; }", + "Assigning to arguments in strict mode (1:32)"); + +testFail("function hello() {'use strict'; function eval() { } }", + "Defining 'eval' in strict mode (1:41)"); + +testFail("function hello() {'use strict'; function arguments() { } }", + "Defining 'arguments' in strict mode (1:41)"); + +testFail("function eval() {'use strict'; }", + "Defining 'eval' in strict mode (1:9)"); + +testFail("function arguments() {'use strict'; }", + "Defining 'arguments' in strict mode (1:9)"); + +testFail("function hello() {'use strict'; (function eval() { }()) }", + "Defining 'eval' in strict mode (1:42)"); + +testFail("function hello() {'use strict'; (function arguments() { }()) }", + "Defining 'arguments' in strict mode (1:42)"); + +testFail("(function eval() {'use strict'; })()", + "Defining 'eval' in strict mode (1:10)"); + +testFail("(function arguments() {'use strict'; })()", + "Defining 'arguments' in strict mode (1:10)"); + +testFail("function hello() {'use strict'; ({ s: function eval() { } }); }", + "Defining 'eval' in strict mode (1:47)"); + +testFail("(function package() {'use strict'; })()", + "Defining 'package' in strict mode (1:10)"); + +testFail("function hello() {'use strict'; ({ i: 10, set s(eval) { } }); }", + "Defining 'eval' in strict mode (1:48)"); + +testFail("function hello() {'use strict'; ({ set s(eval) { } }); }", + "Defining 'eval' in strict mode (1:41)"); + +testFail("function hello() {'use strict'; ({ s: function s(eval) { } }); }", + "Defining 'eval' in strict mode (1:49)"); + +testFail("function hello(eval) {'use strict';}", + "Defining 'eval' in strict mode (1:15)"); + +testFail("function hello(arguments) {'use strict';}", + "Defining 'arguments' in strict mode (1:15)"); + +testFail("function hello() { 'use strict'; function inner(eval) {} }", + "Defining 'eval' in strict mode (1:48)"); + +testFail("function hello() { 'use strict'; function inner(arguments) {} }", + "Defining 'arguments' in strict mode (1:48)"); + +testFail("function hello() { 'use strict'; \"\\1\"; }", + "Octal literal in strict mode (1:34)"); + +testFail("function hello() { 'use strict'; 021; }", + "Invalid number (1:33)"); + +testFail("function hello() { 'use strict'; ({ \"\\1\": 42 }); }", + "Octal literal in strict mode (1:37)"); + +testFail("function hello() { 'use strict'; ({ 021: 42 }); }", + "Invalid number (1:36)"); + +testFail("function hello() { \"use strict\"; function inner() { \"octal directive\\1\"; } }", + "Octal literal in strict mode (1:68)"); + +testFail("function hello() { \"use strict\"; var implements; }", + "The keyword 'implements' is reserved (1:37)"); + +testFail("function hello() { \"use strict\"; var interface; }", + "The keyword 'interface' is reserved (1:37)"); + +testFail("function hello() { \"use strict\"; var package; }", + "The keyword 'package' is reserved (1:37)"); + +testFail("function hello() { \"use strict\"; var private; }", + "The keyword 'private' is reserved (1:37)"); + +testFail("function hello() { \"use strict\"; var protected; }", + "The keyword 'protected' is reserved (1:37)"); + +testFail("function hello() { \"use strict\"; var public; }", + "The keyword 'public' is reserved (1:37)"); + +testFail("function hello() { \"use strict\"; var static; }", + "The keyword 'static' is reserved (1:37)"); + +testFail("function hello(static) { \"use strict\"; }", + "Defining 'static' in strict mode (1:15)"); + +testFail("function static() { \"use strict\"; }", + "Defining 'static' in strict mode (1:9)"); + +testFail("\"use strict\"; function static() { }", + "The keyword 'static' is reserved (1:23)"); + +testFail("function a(t, t) { \"use strict\"; }", + "Argument name clash in strict mode (1:14)"); + +testFail("function a(eval) { \"use strict\"; }", + "Defining 'eval' in strict mode (1:11)"); + +testFail("function a(package) { \"use strict\"; }", + "Defining 'package' in strict mode (1:11)"); + +testFail("function a() { \"use strict\"; function b(t, t) { }; }", + "Argument name clash in strict mode (1:43)"); + +testFail("(function a(t, t) { \"use strict\"; })", + "Argument name clash in strict mode (1:15)"); + +testFail("function a() { \"use strict\"; (function b(t, t) { }); }", + "Argument name clash in strict mode (1:44)"); + +testFail("(function a(eval) { \"use strict\"; })", + "Defining 'eval' in strict mode (1:12)"); + +testFail("(function a(package) { \"use strict\"; })", + "Defining 'package' in strict mode (1:12)"); + +testFail("var this = 10;", "Unexpected token (1:4)"); + +testFail("throw\n10;", "Illegal newline after throw (1:5)"); + +// Assertion Tests +(function() { + var actualComments = [], + expectedComments = [ + " Bear class", + " Whatever", + [" 1", + " 2", + " 3" + ].join('\n'), + "stuff" + ]; + testAssert( + function TestComments() { + // Bear class + function Bear(x,y,z) { + this.position = [x||0,y||0,z||0] + } + + Bear.prototype.roar = function(message) { + return 'RAWWW: ' + message; // Whatever + }; + + function Cat() { + /* 1 + 2 + 3*/ + } + + Cat.prototype.roar = function(message) { + return 'MEOOWW: ' + /*stuff*/ message; + }; + }.toString(), + function assert(ast) { + if (actualComments.length !== expectedComments.length) { + return JSON.stringify(actualComments) + " !== " + JSON.stringify(expectedComments); + } else { + for (var i=0, n=actualComments.length; i < n; i++) { + if (actualComments[i] !== expectedComments[i]) + return JSON.stringify(actualComments[i]) + ' !== ' + JSON.stringify(expectedComments[i]); + } + } + }, + { + onComment: function(isMultiline, text) { + actualComments.push(text); + } + } + ); +})(); + +(function() { + var comments = 0; + testAssert("\nfunction plop() {\n'use strict';\n/* Comment */\n}", function() { + if (comments != 1) return "Comment after strict counted twice."; + }, {onComment: function() {++comments;}}); +})(); diff --git a/thirdparty/acorn/util/walk.js b/thirdparty/acorn/util/walk.js new file mode 100755 index 0000000..f8c697c --- /dev/null +++ b/thirdparty/acorn/util/walk.js @@ -0,0 +1,330 @@ +// AST walker module for Mozilla Parser API compatible trees + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS + if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD + mod((this.acorn || (this.acorn = {})).walk = {}); // Plain browser env +})(function(exports) { + "use strict"; + + // A simple walk is one where you simply specify callbacks to be + // called on specific nodes. The last two arguments are optional. A + // simple use would be + // + // walk.simple(myTree, { + // Expression: function(node) { ... } + // }); + // + // to do something with all expressions. All Parser API node types + // can be used to identify node types, as well as Expression, + // Statement, and ScopeBody, which denote categories of nodes. + // + // The base argument can be used to pass a custom (recursive) + // walker, and state can be used to give this walked an initial + // state. + exports.simple = function(node, visitors, base, state) { + if (!base) base = exports.base; + function c(node, st, override) { + var type = override || node.type, found = visitors[type]; + base[type](node, st, c); + if (found) found(node, st); + } + c(node, state); + }; + + // An ancestor walk builds up an array of ancestor nodes (including + // the current node) and passes them to the callback as the state parameter. + exports.ancestor = function(node, visitors, base, state) { + if (!base) base = exports.base; + if (!state) state = []; + function c(node, st, override) { + var type = override || node.type, found = visitors[type]; + if (node != st[st.length - 1]) { + st = st.slice(); + st.push(node); + } + base[type](node, st, c); + if (found) found(node, st); + } + c(node, state); + }; + + // A recursive walk is one where your functions override the default + // walkers. They can modify and replace the state parameter that's + // threaded through the walk, and can opt how and whether to walk + // their child nodes (by calling their third argument on these + // nodes). + exports.recursive = function(node, state, funcs, base) { + var visitor = funcs ? exports.make(funcs, base) : base; + function c(node, st, override) { + visitor[override || node.type](node, st, c); + } + c(node, state); + }; + + function makeTest(test) { + if (typeof test == "string") + return function(type) { return type == test; }; + else if (!test) + return function() { return true; }; + else + return test; + } + + function Found(node, state) { this.node = node; this.state = state; } + + // Find a node with a given start, end, and type (all are optional, + // null can be used as wildcard). Returns a {node, state} object, or + // undefined when it doesn't find a matching node. + exports.findNodeAt = function(node, start, end, test, base, state) { + test = makeTest(test); + try { + if (!base) base = exports.base; + var c = function(node, st, override) { + var type = override || node.type; + if ((start == null || node.start <= start) && + (end == null || node.end >= end)) + base[type](node, st, c); + if (test(type, node) && + (start == null || node.start == start) && + (end == null || node.end == end)) + throw new Found(node, st); + }; + c(node, state); + } catch (e) { + if (e instanceof Found) return e; + throw e; + } + }; + + // Find the innermost node of a given type that contains the given + // position. Interface similar to findNodeAt. + exports.findNodeAround = function(node, pos, test, base, state) { + test = makeTest(test); + try { + if (!base) base = exports.base; + var c = function(node, st, override) { + var type = override || node.type; + if (node.start > pos || node.end < pos) return; + base[type](node, st, c); + if (test(type, node)) throw new Found(node, st); + }; + c(node, state); + } catch (e) { + if (e instanceof Found) return e; + throw e; + } + }; + + // Find the outermost matching node after a given position. + exports.findNodeAfter = function(node, pos, test, base, state) { + test = makeTest(test); + try { + if (!base) base = exports.base; + var c = function(node, st, override) { + if (node.end < pos) return; + var type = override || node.type; + if (node.start >= pos && test(type, node)) throw new Found(node, st); + base[type](node, st, c); + }; + c(node, state); + } catch (e) { + if (e instanceof Found) return e; + throw e; + } + }; + + // Find the outermost matching node before a given position. + exports.findNodeBefore = function(node, pos, test, base, state) { + test = makeTest(test); + if (!base) base = exports.base; + var max; + var c = function(node, st, override) { + if (node.start > pos) return; + var type = override || node.type; + if (node.end <= pos && (!max || max.node.end < node.end) && test(type, node)) + max = new Found(node, st); + base[type](node, st, c); + }; + c(node, state); + return max; + }; + + // Used to create a custom walker. Will fill in all missing node + // type properties with the defaults. + exports.make = function(funcs, base) { + if (!base) base = exports.base; + var visitor = {}; + for (var type in base) visitor[type] = base[type]; + for (var type in funcs) visitor[type] = funcs[type]; + return visitor; + }; + + function skipThrough(node, st, c) { c(node, st); } + function ignore(_node, _st, _c) {} + + // Node walkers. + + var base = exports.base = {}; + base.Program = base.BlockStatement = function(node, st, c) { + for (var i = 0; i < node.body.length; ++i) + c(node.body[i], st, "Statement"); + }; + base.Statement = skipThrough; + base.EmptyStatement = ignore; + base.ExpressionStatement = function(node, st, c) { + c(node.expression, st, "Expression"); + }; + base.IfStatement = function(node, st, c) { + c(node.test, st, "Expression"); + c(node.consequent, st, "Statement"); + if (node.alternate) c(node.alternate, st, "Statement"); + }; + base.LabeledStatement = function(node, st, c) { + c(node.body, st, "Statement"); + }; + base.BreakStatement = base.ContinueStatement = ignore; + base.WithStatement = function(node, st, c) { + c(node.object, st, "Expression"); + c(node.body, st, "Statement"); + }; + base.SwitchStatement = function(node, st, c) { + c(node.discriminant, st, "Expression"); + for (var i = 0; i < node.cases.length; ++i) { + var cs = node.cases[i]; + if (cs.test) c(cs.test, st, "Expression"); + for (var j = 0; j < cs.consequent.length; ++j) + c(cs.consequent[j], st, "Statement"); + } + }; + base.ReturnStatement = function(node, st, c) { + if (node.argument) c(node.argument, st, "Expression"); + }; + base.ThrowStatement = function(node, st, c) { + c(node.argument, st, "Expression"); + }; + base.TryStatement = function(node, st, c) { + c(node.block, st, "Statement"); + if (node.handler) c(node.handler.body, st, "ScopeBody"); + if (node.finalizer) c(node.finalizer, st, "Statement"); + }; + base.WhileStatement = function(node, st, c) { + c(node.test, st, "Expression"); + c(node.body, st, "Statement"); + }; + base.DoWhileStatement = base.WhileStatement; + base.ForStatement = function(node, st, c) { + if (node.init) c(node.init, st, "ForInit"); + if (node.test) c(node.test, st, "Expression"); + if (node.update) c(node.update, st, "Expression"); + c(node.body, st, "Statement"); + }; + base.ForInStatement = function(node, st, c) { + c(node.left, st, "ForInit"); + c(node.right, st, "Expression"); + c(node.body, st, "Statement"); + }; + base.ForInit = function(node, st, c) { + if (node.type == "VariableDeclaration") c(node, st); + else c(node, st, "Expression"); + }; + base.DebuggerStatement = ignore; + + base.FunctionDeclaration = function(node, st, c) { + c(node, st, "Function"); + }; + base.VariableDeclaration = function(node, st, c) { + for (var i = 0; i < node.declarations.length; ++i) { + var decl = node.declarations[i]; + if (decl.init) c(decl.init, st, "Expression"); + } + }; + + base.Function = function(node, st, c) { + c(node.body, st, "ScopeBody"); + }; + base.ScopeBody = function(node, st, c) { + c(node, st, "Statement"); + }; + + base.Expression = skipThrough; + base.ThisExpression = ignore; + base.ArrayExpression = function(node, st, c) { + for (var i = 0; i < node.elements.length; ++i) { + var elt = node.elements[i]; + if (elt) c(elt, st, "Expression"); + } + }; + base.ObjectExpression = function(node, st, c) { + for (var i = 0; i < node.properties.length; ++i) + c(node.properties[i].value, st, "Expression"); + }; + base.FunctionExpression = base.FunctionDeclaration; + base.SequenceExpression = function(node, st, c) { + for (var i = 0; i < node.expressions.length; ++i) + c(node.expressions[i], st, "Expression"); + }; + base.UnaryExpression = base.UpdateExpression = function(node, st, c) { + c(node.argument, st, "Expression"); + }; + base.BinaryExpression = base.AssignmentExpression = base.LogicalExpression = function(node, st, c) { + c(node.left, st, "Expression"); + c(node.right, st, "Expression"); + }; + base.ConditionalExpression = function(node, st, c) { + c(node.test, st, "Expression"); + c(node.consequent, st, "Expression"); + c(node.alternate, st, "Expression"); + }; + base.NewExpression = base.CallExpression = function(node, st, c) { + c(node.callee, st, "Expression"); + if (node.arguments) for (var i = 0; i < node.arguments.length; ++i) + c(node.arguments[i], st, "Expression"); + }; + base.MemberExpression = function(node, st, c) { + c(node.object, st, "Expression"); + if (node.computed) c(node.property, st, "Expression"); + }; + base.Identifier = base.Literal = ignore; + + // A custom walker that keeps track of the scope chain and the + // variables defined in it. + function makeScope(prev, isCatch) { + return {vars: Object.create(null), prev: prev, isCatch: isCatch}; + } + function normalScope(scope) { + while (scope.isCatch) scope = scope.prev; + return scope; + } + exports.scopeVisitor = exports.make({ + Function: function(node, scope, c) { + var inner = makeScope(scope); + for (var i = 0; i < node.params.length; ++i) + inner.vars[node.params[i].name] = {type: "argument", node: node.params[i]}; + if (node.id) { + var decl = node.type == "FunctionDeclaration"; + (decl ? normalScope(scope) : inner).vars[node.id.name] = + {type: decl ? "function" : "function name", node: node.id}; + } + c(node.body, inner, "ScopeBody"); + }, + TryStatement: function(node, scope, c) { + c(node.block, scope, "Statement"); + if (node.handler) { + var inner = makeScope(scope, true); + inner.vars[node.handler.param.name] = {type: "catch clause", node: node.handler.param}; + c(node.handler.body, inner, "ScopeBody"); + } + if (node.finalizer) c(node.finalizer, scope, "Statement"); + }, + VariableDeclaration: function(node, scope, c) { + var target = normalScope(scope); + for (var i = 0; i < node.declarations.length; ++i) { + var decl = node.declarations[i]; + target.vars[decl.id.name] = {type: "var", node: decl.id}; + if (decl.init) c(decl.init, scope, "Expression"); + } + } + }); + +});