pax_global_header00006660000000000000000000000064132443436520014520gustar00rootroot0000000000000052 comment=208cbdbb3a6d4a9c0a4ee63d20b5435a6a2d064b recast-0.14.4/000077500000000000000000000000001324434365200130675ustar00rootroot00000000000000recast-0.14.4/.editorconfig000066400000000000000000000000341324434365200155410ustar00rootroot00000000000000[*.js] indent_style = space recast-0.14.4/.gitignore000066400000000000000000000001221324434365200150520ustar00rootroot00000000000000/node_modules /test/data/babylon-typescript-fixtures /test/data/graphql-tools-src recast-0.14.4/.npmignore000066400000000000000000000000241324434365200150620ustar00rootroot00000000000000/node_modules /test recast-0.14.4/.travis.yml000066400000000000000000000002031324434365200151730ustar00rootroot00000000000000language: node_js node_js: - "9" - "8" - "7" - "6" - "5" - "4" # Allow Travis tests to run in containers. sudo: false recast-0.14.4/LICENSE000066400000000000000000000020631324434365200140750ustar00rootroot00000000000000Copyright (c) 2012 Ben Newman 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. recast-0.14.4/README.md000066400000000000000000000225251324434365200143540ustar00rootroot00000000000000# recast, _v_. [![Build Status](https://travis-ci.org/benjamn/recast.svg?branch=master)](https://travis-ci.org/benjamn/recast) [![Join the chat at https://gitter.im/benjamn/recast](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/benjamn/recast?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Greenkeeper badge](https://badges.greenkeeper.io/benjamn/recast.svg)](https://greenkeeper.io/) 1. to give (a metal object) a different form by melting it down and reshaping it. 1. to form, fashion, or arrange again. 1. to remodel or reconstruct (a literary work, document, sentence, etc.). 1. to supply (a theater or opera work) with a new cast. Installation --- From NPM: npm install recast From GitHub: cd path/to/node_modules git clone git://github.com/benjamn/recast.git cd recast npm install . Usage --- In less poetic terms, Recast exposes two essential interfaces, one for parsing JavaScript code (`require("recast").parse`) and the other for reprinting modified syntax trees (`require("recast").print`). Here's a simple but non-trivial example of how you might use `.parse` and `.print`: ```js var recast = require("recast"); // Let's turn this function declaration into a variable declaration. var code = [ "function add(a, b) {", " return a +", " // Weird formatting, huh?", " b;", "}" ].join("\n"); // Parse the code using an interface similar to require("esprima").parse. var ast = recast.parse(code); ``` Now do *whatever* you want to `ast`. Really, anything at all! See [ast-types](https://github.com/benjamn/ast-types) (especially the [def/core.js](https://github.com/benjamn/ast-types/blob/master/def/core.js)) module for a thorough overview of the `ast` api. ```js // Grab a reference to the function declaration we just parsed. var add = ast.program.body[0]; // Make sure it's a FunctionDeclaration (optional). var n = recast.types.namedTypes; n.FunctionDeclaration.assert(add); // If you choose to use recast.builders to construct new AST nodes, all builder // arguments will be dynamically type-checked against the Mozilla Parser API. var b = recast.types.builders; // This kind of manipulation should seem familiar if you've used Esprima or the // Mozilla Parser API before. ast.program.body[0] = b.variableDeclaration("var", [ b.variableDeclarator(add.id, b.functionExpression( null, // Anonymize the function expression. add.params, add.body )) ]); // Just for fun, because addition is commutative: add.params.push(add.params.shift()); ``` When you finish manipulating the AST, let `recast.print` work its magic: ```js var output = recast.print(ast).code; ``` The `output` string now looks exactly like this, weird formatting and all: ```js var add = function(b, a) { return a + // Weird formatting, huh? b; } ``` The magic of Recast is that it reprints only those parts of the syntax tree that you modify. In other words, the following identity is guaranteed: ```js recast.print(recast.parse(source)).code === source ``` Whenever Recast cannot reprint a modified node using the original source code, it falls back to using a generic pretty printer. So the worst that can happen is that your changes trigger some harmless reformatting of your code. If you really don't care about preserving the original formatting, you can access the pretty printer directly: ```js var output = recast.prettyPrint(ast, { tabWidth: 2 }).code; ``` And here's the exact `output`: ```js var add = function(b, a) { return a + b; } ``` Note that the weird formatting was discarded, yet the behavior and abstract structure of the code remain the same. Using a different parser --- By default, Recast uses the [Esprima JavaScript parser](https://www.npmjs.com/package/esprima) when you call `recast.parse(code)`. While Esprima supports almost all modern ECMAScript syntax, you may want to use a different parser to enable TypeScript or Flow syntax, or just because you want to match other compilation tools you might be using. In order to get any benefits from Recast's conservative pretty-printing, **it is very important that you continue to call `recast.parse`** (rather than parsing the AST yourself using a different parser), and simply instruct `recast.parse` to use a different parser: ```js const acornAst = recast.parse(source, { parser: require("acorn") }); ``` Why is this so important? When you call `recast.parse`, it makes a shadow copy of the AST before returning it to you, giving every copied AST node a reference back to the original through a special `.original` property. This information is what enables `recast.print` to detect where the AST has been modified, so that it can preserve formatting for parts of the AST that were not modified. Any `parser` object that supports a `parser.parse(source)` method will work here; however, if your parser requires additional options, you can always implement your own `parse` method that invokes your parser with custom options: ```js const acornAst = recast.parse(source, { parser: { parse(source) { return require("acorn").parse(source, { // additional options }); } } }); ``` To take some of the guesswork out of configuring common parsers, Recast provides [several preconfigured parsers](https://github.com/benjamn/recast/tree/master/parsers), so you can parse TypeScript (for example) without worrying about the configuration details: ```js const tsAst = recast.parse(source, { parser: require("recast/parsers/typescript") }); ``` **Note:** Some of these parsers import npm packages that Recast does not directly depend upon, so please be aware you may have to run `npm install babylon@next` to use the `typescript`, `flow`, or `babylon` parsers, or `npm install acorn` to use the `acorn` parser. Only Esprima is installed by default when Recast is installed. Source maps --- One of the coolest consequences of tracking and reusing original source code during reprinting is that it's pretty easy to generate a high-resolution mapping between the original code and the generated code—completely automatically! With every `slice`, `join`, and re-`indent`-ation, the reprinting process maintains exact knowledge of which character sequences are original, and where in the original source they came from. All you have to think about is how to manipulate the syntax tree, and Recast will give you a [source map](https://github.com/mozilla/source-map) in exchange for specifying the names of your source file(s) and the desired name of the map: ```js var result = recast.print(transform(recast.parse(source, { sourceFileName: "source.js" })), { sourceMapName: "map.json" }); console.log(result.code); // Resulting string of code. console.log(result.map); // JSON source map. var SourceMapConsumer = require("source-map").SourceMapConsumer; var smc = new SourceMapConsumer(result.map); console.log(smc.originalPositionFor({ line: 3, column: 15 })); // { source: 'source.js', // line: 2, // column: 10, // name: null } ``` Note that you are free to mix and match syntax trees parsed from different source files, and the resulting source map will automatically keep track of the separate file origins for you. Note also that the source maps generated by Recast are character-by-character maps, so meaningful identifier names are not recorded at this time. This approach leads to higher-resolution debugging in modern browsers, at the expense of somewhat larger map sizes. Striking the perfect balance here is an area for future exploration, but such improvements will not require any breaking changes to the interface demonstrated above. Options --- All Recast API functions take second parameter with configuration options, documented in [options.js](https://github.com/benjamn/recast/blob/master/lib/options.js) Motivation --- The more code you have, the harder it becomes to make big, sweeping changes quickly and confidently. Even if you trust yourself not to make too many mistakes, and no matter how proficient you are with your text editor, changing tens of thousands of lines of code takes precious, non-refundable time. Is there a better way? Not always! When a task requires you to alter the semantics of many different pieces of code in subtly different ways, your brain inevitably becomes the bottleneck, and there is little hope of completely automating the process. Your best bet is to plan carefully, buckle down, and get it right the first time. Love it or loathe it, that's the way programming goes sometimes. What I hope to eliminate are the brain-wasting tasks, the tasks that are bottlenecked by keystrokes, the tasks that can be expressed as operations on the _syntactic structure_ of your code. Specifically, my goal is to make it possible for you to run your code through a parser, manipulate the abstract syntax tree directly, subject only to the constraints of your imagination, and then automatically translate those modifications back into source code, without upsetting the formatting of unmodified code. And here's the best part: when you're done running a Recast script, if you're not completely satisfied with the results, blow them away with `git reset --hard`, tweak the script, and just run it again. Change your mind as many times as you like. Instead of typing yourself into a nasty case of [RSI](http://en.wikipedia.org/wiki/Repetitive_strain_injury), gaze upon your new wells of free time and ask yourself: what next? recast-0.14.4/example/000077500000000000000000000000001324434365200145225ustar00rootroot00000000000000recast-0.14.4/example/add-braces000077500000000000000000000016741324434365200164450ustar00rootroot00000000000000#!/usr/bin/env node var recast = require("recast"); var types = recast.types; var n = types.namedTypes; var b = types.builders; require("recast").run(function(ast, callback) { recast.visit(ast, { visitIfStatement: function(path) { var stmt = path.node; stmt.consequent = fix(stmt.consequent); var alt = stmt.alternate; if (!n.IfStatement.check(alt)) { stmt.alternate = fix(alt); } this.traverse(path); }, visitWhileStatement: visitLoop, visitForStatement: visitLoop, visitForInStatement: visitLoop }); callback(ast); }); function visitLoop(path) { var loop = path.node; loop.body = fix(loop.body); this.traverse(path); } function fix(clause) { if (clause) { if (!n.BlockStatement.check(clause)) { clause = b.blockStatement([clause]); } } return clause; } recast-0.14.4/example/generic-identity000077500000000000000000000006211324434365200177120ustar00rootroot00000000000000#!/usr/bin/env node // This script should reprint the contents of the given file without // reusing the original source, but with identical AST structure. var recast = require("recast"); recast.run(function(ast, callback) { recast.visit(ast, { visitNode: function(path) { this.traverse(path); path.node.original = null; } }); callback(ast); }); recast-0.14.4/example/identity000077500000000000000000000002601324434365200162770ustar00rootroot00000000000000#!/usr/bin/env node // This script should echo the contents of the given file without // modification. require("recast").run(function(ast, callback) { callback(ast); }); recast-0.14.4/example/to-while000077500000000000000000000043001324434365200161750ustar00rootroot00000000000000#!/usr/bin/env node // This script converts for and do-while loops into equivalent while loops. // Note that for-in statements are left unmodified, as they do not have a // simple analogy to while loops. Also note that labeled continue statements // are not correctly handled at this point, and will trigger an assertion // failure if encountered. var assert = require("assert"); var recast = require("recast"); var types = recast.types; var n = types.namedTypes; var b = types.builders; recast.run(function(ast, callback) { recast.visit(ast, { visitForStatement: function(path) { var fst = path.node; path.replace( fst.init, b.whileStatement( fst.test, insertBeforeLoopback(fst, fst.update) ) ); this.traverse(path); }, visitDoWhileStatement: function(path) { var dwst = path.node; return b.whileStatement( b.literal(true), insertBeforeLoopback( dwst, b.ifStatement( dwst.test, b.breakStatement() ) ) ); } }); callback(ast); }); function insertBeforeLoopback(loop, toInsert) { var body = loop.body; if (!n.Statement.check(toInsert)) { toInsert = b.expressionStatement(toInsert); } if (n.BlockStatement.check(body)) { body.body.push(toInsert); } else { body = b.blockStatement([body, toInsert]); loop.body = body; } recast.visit(body, { visitContinueStatement: function(path) { var cst = path.node; assert.equal( cst.label, null, "Labeled continue statements are not yet supported." ); path.replace(toInsert, path.node); return false; }, // Do not descend into nested loops. visitWhileStatement: function() {}, visitForStatement: function() {}, visitForInStatement: function() {}, visitDoWhileStatement: function() {} }); return body; } recast-0.14.4/lib/000077500000000000000000000000001324434365200136355ustar00rootroot00000000000000recast-0.14.4/lib/comments.js000066400000000000000000000264671324434365200160370ustar00rootroot00000000000000var assert = require("assert"); var types = require("./types"); var n = types.namedTypes; var isArray = types.builtInTypes.array; var isObject = types.builtInTypes.object; var linesModule = require("./lines"); var fromString = linesModule.fromString; var Lines = linesModule.Lines; var concat = linesModule.concat; var util = require("./util"); var comparePos = util.comparePos; var childNodesCacheKey = require("private").makeUniqueKey(); // TODO Move a non-caching implementation of this function into ast-types, // and implement a caching wrapper function here. function getSortedChildNodes(node, lines, resultArray) { if (!node) { return; } // The .loc checks below are sensitive to some of the problems that // are fixed by this utility function. Specifically, if it decides to // set node.loc to null, indicating that the node's .loc information // is unreliable, then we don't want to add node to the resultArray. util.fixFaultyLocations(node, lines); if (resultArray) { if (n.Node.check(node) && n.SourceLocation.check(node.loc)) { // This reverse insertion sort almost always takes constant // time because we almost always (maybe always?) append the // nodes in order anyway. for (var i = resultArray.length - 1; i >= 0; --i) { if (comparePos(resultArray[i].loc.end, node.loc.start) <= 0) { break; } } resultArray.splice(i + 1, 0, node); return; } } else if (node[childNodesCacheKey]) { return node[childNodesCacheKey]; } var names; if (isArray.check(node)) { names = Object.keys(node); } else if (isObject.check(node)) { names = types.getFieldNames(node); } else { return; } if (!resultArray) { Object.defineProperty(node, childNodesCacheKey, { value: resultArray = [], enumerable: false }); } for (var i = 0, nameCount = names.length; i < nameCount; ++i) { getSortedChildNodes(node[names[i]], lines, resultArray); } return resultArray; } // As efficiently as possible, decorate the comment object with // .precedingNode, .enclosingNode, and/or .followingNode properties, at // least one of which is guaranteed to be defined. function decorateComment(node, comment, lines) { var childNodes = getSortedChildNodes(node, lines); // Time to dust off the old binary search robes and wizard hat. var left = 0, right = childNodes.length; while (left < right) { var middle = (left + right) >> 1; var child = childNodes[middle]; if (comparePos(child.loc.start, comment.loc.start) <= 0 && comparePos(comment.loc.end, child.loc.end) <= 0) { // The comment is completely contained by this child node. decorateComment(comment.enclosingNode = child, comment, lines); return; // Abandon the binary search at this level. } if (comparePos(child.loc.end, comment.loc.start) <= 0) { // This child node falls completely before the comment. // Because we will never consider this node or any nodes // before it again, this node must be the closest preceding // node we have encountered so far. var precedingNode = child; left = middle + 1; continue; } if (comparePos(comment.loc.end, child.loc.start) <= 0) { // This child node falls completely after the comment. // Because we will never consider this node or any nodes after // it again, this node must be the closest following node we // have encountered so far. var followingNode = child; right = middle; continue; } throw new Error("Comment location overlaps with node location"); } if (precedingNode) { comment.precedingNode = precedingNode; } if (followingNode) { comment.followingNode = followingNode; } } exports.attach = function(comments, ast, lines) { if (!isArray.check(comments)) { return; } var tiesToBreak = []; comments.forEach(function(comment) { comment.loc.lines = lines; decorateComment(ast, comment, lines); var pn = comment.precedingNode; var en = comment.enclosingNode; var fn = comment.followingNode; if (pn && fn) { var tieCount = tiesToBreak.length; if (tieCount > 0) { var lastTie = tiesToBreak[tieCount - 1]; assert.strictEqual( lastTie.precedingNode === comment.precedingNode, lastTie.followingNode === comment.followingNode ); if (lastTie.followingNode !== comment.followingNode) { breakTies(tiesToBreak, lines); } } tiesToBreak.push(comment); } else if (pn) { // No contest: we have a trailing comment. breakTies(tiesToBreak, lines); addTrailingComment(pn, comment); } else if (fn) { // No contest: we have a leading comment. breakTies(tiesToBreak, lines); addLeadingComment(fn, comment); } else if (en) { // The enclosing node has no child nodes at all, so what we // have here is a dangling comment, e.g. [/* crickets */]. breakTies(tiesToBreak, lines); addDanglingComment(en, comment); } else { throw new Error("AST contains no nodes at all?"); } }); breakTies(tiesToBreak, lines); comments.forEach(function(comment) { // These node references were useful for breaking ties, but we // don't need them anymore, and they create cycles in the AST that // may lead to infinite recursion if we don't delete them here. delete comment.precedingNode; delete comment.enclosingNode; delete comment.followingNode; }); }; function breakTies(tiesToBreak, lines) { var tieCount = tiesToBreak.length; if (tieCount === 0) { return; } var pn = tiesToBreak[0].precedingNode; var fn = tiesToBreak[0].followingNode; var gapEndPos = fn.loc.start; // Iterate backwards through tiesToBreak, examining the gaps // between the tied comments. In order to qualify as leading, a // comment must be separated from fn by an unbroken series of // whitespace-only gaps (or other comments). for (var indexOfFirstLeadingComment = tieCount; indexOfFirstLeadingComment > 0; --indexOfFirstLeadingComment) { var comment = tiesToBreak[indexOfFirstLeadingComment - 1]; assert.strictEqual(comment.precedingNode, pn); assert.strictEqual(comment.followingNode, fn); var gap = lines.sliceString(comment.loc.end, gapEndPos); if (/\S/.test(gap)) { // The gap string contained something other than whitespace. break; } gapEndPos = comment.loc.start; } while (indexOfFirstLeadingComment <= tieCount && (comment = tiesToBreak[indexOfFirstLeadingComment]) && // If the comment is a //-style comment and indented more // deeply than the node itself, reconsider it as trailing. (comment.type === "Line" || comment.type === "CommentLine") && comment.loc.start.column > fn.loc.start.column) { ++indexOfFirstLeadingComment; } tiesToBreak.forEach(function(comment, i) { if (i < indexOfFirstLeadingComment) { addTrailingComment(pn, comment); } else { addLeadingComment(fn, comment); } }); tiesToBreak.length = 0; } function addCommentHelper(node, comment) { var comments = node.comments || (node.comments = []); comments.push(comment); } function addLeadingComment(node, comment) { comment.leading = true; comment.trailing = false; addCommentHelper(node, comment); } function addDanglingComment(node, comment) { comment.leading = false; comment.trailing = false; addCommentHelper(node, comment); } function addTrailingComment(node, comment) { comment.leading = false; comment.trailing = true; addCommentHelper(node, comment); } function printLeadingComment(commentPath, print) { var comment = commentPath.getValue(); n.Comment.assert(comment); var loc = comment.loc; var lines = loc && loc.lines; var parts = [print(commentPath)]; if (comment.trailing) { // When we print trailing comments as leading comments, we don't // want to bring any trailing spaces along. parts.push("\n"); } else if (lines instanceof Lines) { var trailingSpace = lines.slice( loc.end, lines.skipSpaces(loc.end) ); if (trailingSpace.length === 1) { // If the trailing space contains no newlines, then we want to // preserve it exactly as we found it. parts.push(trailingSpace); } else { // If the trailing space contains newlines, then replace it // with just that many newlines, with all other spaces removed. parts.push(new Array(trailingSpace.length).join("\n")); } } else { parts.push("\n"); } return concat(parts); } function printTrailingComment(commentPath, print) { var comment = commentPath.getValue(commentPath); n.Comment.assert(comment); var loc = comment.loc; var lines = loc && loc.lines; var parts = []; if (lines instanceof Lines) { var fromPos = lines.skipSpaces(loc.start, true) || lines.firstPos(); var leadingSpace = lines.slice(fromPos, loc.start); if (leadingSpace.length === 1) { // If the leading space contains no newlines, then we want to // preserve it exactly as we found it. parts.push(leadingSpace); } else { // If the leading space contains newlines, then replace it // with just that many newlines, sans all other spaces. parts.push(new Array(leadingSpace.length).join("\n")); } } parts.push(print(commentPath)); return concat(parts); } exports.printComments = function(path, print) { var value = path.getValue(); var innerLines = print(path); var comments = n.Node.check(value) && types.getFieldValue(value, "comments"); if (!comments || comments.length === 0) { return innerLines; } var leadingParts = []; var trailingParts = [innerLines]; path.each(function(commentPath) { var comment = commentPath.getValue(); var leading = types.getFieldValue(comment, "leading"); var trailing = types.getFieldValue(comment, "trailing"); if (leading || (trailing && !(n.Statement.check(value) || comment.type === "Block" || comment.type === "CommentBlock"))) { leadingParts.push(printLeadingComment(commentPath, print)); } else if (trailing) { trailingParts.push(printTrailingComment(commentPath, print)); } }, "comments"); leadingParts.push.apply(leadingParts, trailingParts); return concat(leadingParts); }; recast-0.14.4/lib/fast-path.js000066400000000000000000000311021324434365200160570ustar00rootroot00000000000000var assert = require("assert"); var types = require("./types"); var n = types.namedTypes; var Node = n.Node; var isArray = types.builtInTypes.array; var isNumber = types.builtInTypes.number; function FastPath(value) { assert.ok(this instanceof FastPath); this.stack = [value]; } var FPp = FastPath.prototype; module.exports = FastPath; // Static convenience function for coercing a value to a FastPath. FastPath.from = function(obj) { if (obj instanceof FastPath) { // Return a defensive copy of any existing FastPath instances. return obj.copy(); } if (obj instanceof types.NodePath) { // For backwards compatibility, unroll NodePath instances into // lightweight FastPath [..., name, value] stacks. var copy = Object.create(FastPath.prototype); var stack = [obj.value]; for (var pp; (pp = obj.parentPath); obj = pp) stack.push(obj.name, pp.value); copy.stack = stack.reverse(); return copy; } // Otherwise use obj as the value of the new FastPath instance. return new FastPath(obj); }; FPp.copy = function copy() { var copy = Object.create(FastPath.prototype); copy.stack = this.stack.slice(0); return copy; }; // The name of the current property is always the penultimate element of // this.stack, and always a String. FPp.getName = function getName() { var s = this.stack; var len = s.length; if (len > 1) { return s[len - 2]; } // Since the name is always a string, null is a safe sentinel value to // return if we do not know the name of the (root) value. return null; }; // The value of the current property is always the final element of // this.stack. FPp.getValue = function getValue() { var s = this.stack; return s[s.length - 1]; }; FPp.valueIsDuplicate = function () { var s = this.stack; var valueIndex = s.length - 1; return s.lastIndexOf(s[valueIndex], valueIndex - 1) >= 0; }; function getNodeHelper(path, count) { var s = path.stack; for (var i = s.length - 1; i >= 0; i -= 2) { var value = s[i]; if (n.Node.check(value) && --count < 0) { return value; } } return null; } FPp.getNode = function getNode(count) { return getNodeHelper(this, ~~count); }; FPp.getParentNode = function getParentNode(count) { return getNodeHelper(this, ~~count + 1); }; // The length of the stack can be either even or odd, depending on whether // or not we have a name for the root value. The difference between the // index of the root value and the index of the final value is always // even, though, which allows us to return the root value in constant time // (i.e. without iterating backwards through the stack). FPp.getRootValue = function getRootValue() { var s = this.stack; if (s.length % 2 === 0) { return s[1]; } return s[0]; }; // Temporarily push properties named by string arguments given after the // callback function onto this.stack, then call the callback with a // reference to this (modified) FastPath object. Note that the stack will // be restored to its original state after the callback is finished, so it // is probably a mistake to retain a reference to the path. FPp.call = function call(callback/*, name1, name2, ... */) { var s = this.stack; var origLen = s.length; var value = s[origLen - 1]; var argc = arguments.length; for (var i = 1; i < argc; ++i) { var name = arguments[i]; value = value[name]; s.push(name, value); } var result = callback(this); s.length = origLen; return result; }; // Similar to FastPath.prototype.call, except that the value obtained by // accessing this.getValue()[name1][name2]... should be array-like. The // callback will be called with a reference to this path object for each // element of the array. FPp.each = function each(callback/*, name1, name2, ... */) { var s = this.stack; var origLen = s.length; var value = s[origLen - 1]; var argc = arguments.length; for (var i = 1; i < argc; ++i) { var name = arguments[i]; value = value[name]; s.push(name, value); } for (var i = 0; i < value.length; ++i) { if (i in value) { s.push(i, value[i]); // If the callback needs to know the value of i, call // path.getName(), assuming path is the parameter name. callback(this); s.length -= 2; } } s.length = origLen; }; // Similar to FastPath.prototype.each, except that the results of the // callback function invocations are stored in an array and returned at // the end of the iteration. FPp.map = function map(callback/*, name1, name2, ... */) { var s = this.stack; var origLen = s.length; var value = s[origLen - 1]; var argc = arguments.length; for (var i = 1; i < argc; ++i) { var name = arguments[i]; value = value[name]; s.push(name, value); } var result = new Array(value.length); for (var i = 0; i < value.length; ++i) { if (i in value) { s.push(i, value[i]); result[i] = callback(this, i); s.length -= 2; } } s.length = origLen; return result; }; // Inspired by require("ast-types").NodePath.prototype.needsParens, but // more efficient because we're iterating backwards through a stack. FPp.needsParens = function(assumeExpressionContext) { var node = this.getNode(); // This needs to come before `if (!parent) { return false }` because // an object destructuring assignment requires parens for // correctness even when it's the topmost expression. if (node.type === "AssignmentExpression" && node.left.type === 'ObjectPattern') { return true; } var parent = this.getParentNode(); if (!parent) { return false; } var name = this.getName(); // If the value of this path is some child of a Node and not a Node // itself, then it doesn't need parentheses. Only Node objects (in fact, // only Expression nodes) need parentheses. if (this.getValue() !== node) { return false; } // Only statements don't need parentheses. if (n.Statement.check(node)) { return false; } // Identifiers never need parentheses. if (node.type === "Identifier") { return false; } if (parent.type === "ParenthesizedExpression") { return false; } switch (node.type) { case "UnaryExpression": case "SpreadElement": case "SpreadProperty": return parent.type === "MemberExpression" && name === "object" && parent.object === node; case "BinaryExpression": case "LogicalExpression": switch (parent.type) { case "CallExpression": return name === "callee" && parent.callee === node; case "UnaryExpression": case "SpreadElement": case "SpreadProperty": return true; case "MemberExpression": return name === "object" && parent.object === node; case "BinaryExpression": case "LogicalExpression": var po = parent.operator; var pp = PRECEDENCE[po]; var no = node.operator; var np = PRECEDENCE[no]; if (pp > np) { return true; } if (pp === np && name === "right") { assert.strictEqual(parent.right, node); return true; } default: return false; } case "SequenceExpression": switch (parent.type) { case "ReturnStatement": return false; case "ForStatement": // Although parentheses wouldn't hurt around sequence expressions in // the head of for loops, traditional style dictates that e.g. i++, // j++ should not be wrapped with parentheses. return false; case "ExpressionStatement": return name !== "expression"; default: // Otherwise err on the side of overparenthesization, adding // explicit exceptions above if this proves overzealous. return true; } case "YieldExpression": switch (parent.type) { case "BinaryExpression": case "LogicalExpression": case "UnaryExpression": case "SpreadElement": case "SpreadProperty": case "CallExpression": case "MemberExpression": case "NewExpression": case "ConditionalExpression": case "YieldExpression": return true; default: return false; } case "IntersectionTypeAnnotation": case "UnionTypeAnnotation": return parent.type === "NullableTypeAnnotation"; case "Literal": return parent.type === "MemberExpression" && isNumber.check(node.value) && name === "object" && parent.object === node; // Babel 6 Literal split case "NumericLiteral": return parent.type === "MemberExpression" && name === "object" && parent.object === node; case "AssignmentExpression": case "ConditionalExpression": switch (parent.type) { case "UnaryExpression": case "SpreadElement": case "SpreadProperty": case "BinaryExpression": case "LogicalExpression": return true; case "CallExpression": return name === "callee" && parent.callee === node; case "ConditionalExpression": return name === "test" && parent.test === node; case "MemberExpression": return name === "object" && parent.object === node; default: return false; } case "ArrowFunctionExpression": if(n.CallExpression.check(parent) && name === 'callee') { return true; } if(n.MemberExpression.check(parent) && name === 'object') { return true; } return isBinary(parent); case "ObjectExpression": if (parent.type === "ArrowFunctionExpression" && name === "body") { return true; } default: if (parent.type === "NewExpression" && name === "callee" && parent.callee === node) { return containsCallExpression(node); } } if (assumeExpressionContext !== true && !this.canBeFirstInStatement() && this.firstInStatement()) return true; return false; }; function isBinary(node) { return n.BinaryExpression.check(node) || n.LogicalExpression.check(node); } function isUnaryLike(node) { return n.UnaryExpression.check(node) // I considered making SpreadElement and SpreadProperty subtypes of // UnaryExpression, but they're not really Expression nodes. || (n.SpreadElement && n.SpreadElement.check(node)) || (n.SpreadProperty && n.SpreadProperty.check(node)); } var PRECEDENCE = {}; [["||"], ["&&"], ["|"], ["^"], ["&"], ["==", "===", "!=", "!=="], ["<", ">", "<=", ">=", "in", "instanceof"], [">>", "<<", ">>>"], ["+", "-"], ["*", "/", "%", "**"] ].forEach(function(tier, i) { tier.forEach(function(op) { PRECEDENCE[op] = i; }); }); function containsCallExpression(node) { if (n.CallExpression.check(node)) { return true; } if (isArray.check(node)) { return node.some(containsCallExpression); } if (n.Node.check(node)) { return types.someField(node, function(name, child) { return containsCallExpression(child); }); } return false; } FPp.canBeFirstInStatement = function() { var node = this.getNode(); if (n.FunctionExpression.check(node)) { return false; } if (n.ObjectExpression.check(node)) { return false; } if (n.ClassExpression.check(node)) { return false; } return true; }; FPp.firstInStatement = function() { var s = this.stack; var parentName, parent; var childName, child; for (var i = s.length - 1; i >= 0; i -= 2) { if (n.Node.check(s[i])) { childName = parentName; child = parent; parentName = s[i - 1]; parent = s[i]; } if (!parent || !child) { continue; } if (n.BlockStatement.check(parent) && parentName === "body" && childName === 0) { assert.strictEqual(parent.body[0], child); return true; } if (n.ExpressionStatement.check(parent) && childName === "expression") { assert.strictEqual(parent.expression, child); return true; } if (n.SequenceExpression.check(parent) && parentName === "expressions" && childName === 0) { assert.strictEqual(parent.expressions[0], child); continue; } if (n.CallExpression.check(parent) && childName === "callee") { assert.strictEqual(parent.callee, child); continue; } if (n.MemberExpression.check(parent) && childName === "object") { assert.strictEqual(parent.object, child); continue; } if (n.ConditionalExpression.check(parent) && childName === "test") { assert.strictEqual(parent.test, child); continue; } if (isBinary(parent) && childName === "left") { assert.strictEqual(parent.left, child); continue; } if (n.UnaryExpression.check(parent) && !parent.prefix && childName === "argument") { assert.strictEqual(parent.argument, child); continue; } return false; } return true; }; recast-0.14.4/lib/lines.js000066400000000000000000000521321324434365200153100ustar00rootroot00000000000000var assert = require("assert"); var sourceMap = require("source-map"); var normalizeOptions = require("./options").normalize; var secretKey = require("private").makeUniqueKey(); var types = require("./types"); var isString = types.builtInTypes.string; var comparePos = require("./util").comparePos; var Mapping = require("./mapping"); // Goals: // 1. Minimize new string creation. // 2. Keep (de)identation O(lines) time. // 3. Permit negative indentations. // 4. Enforce immutability. // 5. No newline characters. var useSymbol = typeof Symbol === "function"; var secretKey = "recastLinesSecret"; if (useSymbol) { secretKey = Symbol.for(secretKey); } function getSecret(lines) { return lines[secretKey]; } function Lines(infos, sourceFileName) { assert.ok(this instanceof Lines); assert.ok(infos.length > 0); if (sourceFileName) { isString.assert(sourceFileName); } else { sourceFileName = null; } setSymbolOrKey(this, secretKey, { infos: infos, mappings: [], name: sourceFileName, cachedSourceMap: null }); this.length = infos.length; this.name = sourceFileName; if (sourceFileName) { getSecret(this).mappings.push(new Mapping(this, { start: this.firstPos(), end: this.lastPos() })); } } function setSymbolOrKey(obj, key, value) { if (useSymbol) { return obj[key] = value; } Object.defineProperty(obj, key, { value: value, enumerable: false, writable: false, configurable: true }); return value; } // Exposed for instanceof checks. The fromString function should be used // to create new Lines objects. exports.Lines = Lines; var Lp = Lines.prototype; function copyLineInfo(info) { return { line: info.line, indent: info.indent, locked: info.locked, sliceStart: info.sliceStart, sliceEnd: info.sliceEnd }; } var fromStringCache = {}; var hasOwn = fromStringCache.hasOwnProperty; var maxCacheKeyLen = 10; function countSpaces(spaces, tabWidth) { var count = 0; var len = spaces.length; for (var i = 0; i < len; ++i) { switch (spaces.charCodeAt(i)) { case 9: // '\t' assert.strictEqual(typeof tabWidth, "number"); assert.ok(tabWidth > 0); var next = Math.ceil(count / tabWidth) * tabWidth; if (next === count) { count += tabWidth; } else { count = next; } break; case 11: // '\v' case 12: // '\f' case 13: // '\r' case 0xfeff: // zero-width non-breaking space // These characters contribute nothing to indentation. break; case 32: // ' ' default: // Treat all other whitespace like ' '. count += 1; break; } } return count; } exports.countSpaces = countSpaces; var leadingSpaceExp = /^\s*/; // As specified here: http://www.ecma-international.org/ecma-262/6.0/#sec-line-terminators var lineTerminatorSeqExp = /\u000D\u000A|\u000D(?!\u000A)|\u000A|\u2028|\u2029/; /** * @param {Object} options - Options object that configures printing. */ function fromString(string, options) { if (string instanceof Lines) return string; string += ""; var tabWidth = options && options.tabWidth; var tabless = string.indexOf("\t") < 0; var locked = !! (options && options.locked); var cacheable = !options && tabless && (string.length <= maxCacheKeyLen); assert.ok(tabWidth || tabless, "No tab width specified but encountered tabs in string\n" + string); if (cacheable && hasOwn.call(fromStringCache, string)) return fromStringCache[string]; var lines = new Lines(string.split(lineTerminatorSeqExp).map(function(line) { var spaces = leadingSpaceExp.exec(line)[0]; return { line: line, indent: countSpaces(spaces, tabWidth), // Boolean indicating whether this line can be reindented. locked: locked, sliceStart: spaces.length, sliceEnd: line.length }; }), normalizeOptions(options).sourceFileName); if (cacheable) fromStringCache[string] = lines; return lines; } exports.fromString = fromString; function isOnlyWhitespace(string) { return !/\S/.test(string); } Lp.toString = function(options) { return this.sliceString(this.firstPos(), this.lastPos(), options); }; Lp.getSourceMap = function(sourceMapName, sourceRoot) { if (!sourceMapName) { // Although we could make up a name or generate an anonymous // source map, instead we assume that any consumer who does not // provide a name does not actually want a source map. return null; } var targetLines = this; function updateJSON(json) { json = json || {}; isString.assert(sourceMapName); json.file = sourceMapName; if (sourceRoot) { isString.assert(sourceRoot); json.sourceRoot = sourceRoot; } return json; } var secret = getSecret(targetLines); if (secret.cachedSourceMap) { // Since Lines objects are immutable, we can reuse any source map // that was previously generated. Nevertheless, we return a new // JSON object here to protect the cached source map from outside // modification. return updateJSON(secret.cachedSourceMap.toJSON()); } var smg = new sourceMap.SourceMapGenerator(updateJSON()); var sourcesToContents = {}; secret.mappings.forEach(function(mapping) { var sourceCursor = mapping.sourceLines.skipSpaces( mapping.sourceLoc.start ) || mapping.sourceLines.lastPos(); var targetCursor = targetLines.skipSpaces( mapping.targetLoc.start ) || targetLines.lastPos(); while (comparePos(sourceCursor, mapping.sourceLoc.end) < 0 && comparePos(targetCursor, mapping.targetLoc.end) < 0) { var sourceChar = mapping.sourceLines.charAt(sourceCursor); var targetChar = targetLines.charAt(targetCursor); assert.strictEqual(sourceChar, targetChar); var sourceName = mapping.sourceLines.name; // Add mappings one character at a time for maximum resolution. smg.addMapping({ source: sourceName, original: { line: sourceCursor.line, column: sourceCursor.column }, generated: { line: targetCursor.line, column: targetCursor.column } }); if (!hasOwn.call(sourcesToContents, sourceName)) { var sourceContent = mapping.sourceLines.toString(); smg.setSourceContent(sourceName, sourceContent); sourcesToContents[sourceName] = sourceContent; } targetLines.nextPos(targetCursor, true); mapping.sourceLines.nextPos(sourceCursor, true); } }); secret.cachedSourceMap = smg; return smg.toJSON(); }; Lp.bootstrapCharAt = function(pos) { assert.strictEqual(typeof pos, "object"); assert.strictEqual(typeof pos.line, "number"); assert.strictEqual(typeof pos.column, "number"); var line = pos.line, column = pos.column, strings = this.toString().split(lineTerminatorSeqExp), string = strings[line - 1]; if (typeof string === "undefined") return ""; if (column === string.length && line < strings.length) return "\n"; if (column >= string.length) return ""; return string.charAt(column); }; Lp.charAt = function(pos) { assert.strictEqual(typeof pos, "object"); assert.strictEqual(typeof pos.line, "number"); assert.strictEqual(typeof pos.column, "number"); var line = pos.line, column = pos.column, secret = getSecret(this), infos = secret.infos, info = infos[line - 1], c = column; if (typeof info === "undefined" || c < 0) return ""; var indent = this.getIndentAt(line); if (c < indent) return " "; c += info.sliceStart - indent; if (c === info.sliceEnd && line < this.length) return "\n"; if (c >= info.sliceEnd) return ""; return info.line.charAt(c); }; Lp.stripMargin = function(width, skipFirstLine) { if (width === 0) return this; assert.ok(width > 0, "negative margin: " + width); if (skipFirstLine && this.length === 1) return this; var secret = getSecret(this); var lines = new Lines(secret.infos.map(function(info, i) { if (info.line && (i > 0 || !skipFirstLine)) { info = copyLineInfo(info); info.indent = Math.max(0, info.indent - width); } return info; })); if (secret.mappings.length > 0) { var newMappings = getSecret(lines).mappings; assert.strictEqual(newMappings.length, 0); secret.mappings.forEach(function(mapping) { newMappings.push(mapping.indent(width, skipFirstLine, true)); }); } return lines; }; Lp.indent = function(by) { if (by === 0) return this; var secret = getSecret(this); var lines = new Lines(secret.infos.map(function(info) { if (info.line && ! info.locked) { info = copyLineInfo(info); info.indent += by; } return info })); if (secret.mappings.length > 0) { var newMappings = getSecret(lines).mappings; assert.strictEqual(newMappings.length, 0); secret.mappings.forEach(function(mapping) { newMappings.push(mapping.indent(by)); }); } return lines; }; Lp.indentTail = function(by) { if (by === 0) return this; if (this.length < 2) return this; var secret = getSecret(this); var lines = new Lines(secret.infos.map(function(info, i) { if (i > 0 && info.line && ! info.locked) { info = copyLineInfo(info); info.indent += by; } return info; })); if (secret.mappings.length > 0) { var newMappings = getSecret(lines).mappings; assert.strictEqual(newMappings.length, 0); secret.mappings.forEach(function(mapping) { newMappings.push(mapping.indent(by, true)); }); } return lines; }; Lp.lockIndentTail = function () { if (this.length < 2) { return this; } var infos = getSecret(this).infos; return new Lines(infos.map(function (info, i) { info = copyLineInfo(info); info.locked = i > 0; return info; })); }; Lp.getIndentAt = function(line) { assert.ok(line >= 1, "no line " + line + " (line numbers start from 1)"); var secret = getSecret(this), info = secret.infos[line - 1]; return Math.max(info.indent, 0); }; Lp.guessTabWidth = function() { var secret = getSecret(this); if (hasOwn.call(secret, "cachedTabWidth")) { return secret.cachedTabWidth; } var counts = []; // Sparse array. var lastIndent = 0; for (var line = 1, last = this.length; line <= last; ++line) { var info = secret.infos[line - 1]; var sliced = info.line.slice(info.sliceStart, info.sliceEnd); // Whitespace-only lines don't tell us much about the likely tab // width of this code. if (isOnlyWhitespace(sliced)) { continue; } var diff = Math.abs(info.indent - lastIndent); counts[diff] = ~~counts[diff] + 1; lastIndent = info.indent; } var maxCount = -1; var result = 2; for (var tabWidth = 1; tabWidth < counts.length; tabWidth += 1) { if (hasOwn.call(counts, tabWidth) && counts[tabWidth] > maxCount) { maxCount = counts[tabWidth]; result = tabWidth; } } return secret.cachedTabWidth = result; }; // Determine if the list of lines has a first line that starts with a // // or /* comment. If this is the case, the code may need to be wrapped in // parens to avoid ASI issues. Lp.startsWithComment = function () { var secret = getSecret(this); if (secret.infos.length === 0) { return false; } var firstLineInfo = secret.infos[0], sliceStart = firstLineInfo.sliceStart, sliceEnd = firstLineInfo.sliceEnd, firstLine = firstLineInfo.line.slice(sliceStart, sliceEnd).trim(); return firstLine.length === 0 || firstLine.slice(0, 2) === "//" || firstLine.slice(0, 2) === "/*"; }; Lp.isOnlyWhitespace = function() { return isOnlyWhitespace(this.toString()); }; Lp.isPrecededOnlyByWhitespace = function(pos) { var secret = getSecret(this); var info = secret.infos[pos.line - 1]; var indent = Math.max(info.indent, 0); var diff = pos.column - indent; if (diff <= 0) { // If pos.column does not exceed the indentation amount, then // there must be only whitespace before it. return true; } var start = info.sliceStart; var end = Math.min(start + diff, info.sliceEnd); var prefix = info.line.slice(start, end); return isOnlyWhitespace(prefix); }; Lp.getLineLength = function(line) { var secret = getSecret(this), info = secret.infos[line - 1]; return this.getIndentAt(line) + info.sliceEnd - info.sliceStart; }; Lp.nextPos = function(pos, skipSpaces) { var l = Math.max(pos.line, 0), c = Math.max(pos.column, 0); if (c < this.getLineLength(l)) { pos.column += 1; return skipSpaces ? !!this.skipSpaces(pos, false, true) : true; } if (l < this.length) { pos.line += 1; pos.column = 0; return skipSpaces ? !!this.skipSpaces(pos, false, true) : true; } return false; }; Lp.prevPos = function(pos, skipSpaces) { var l = pos.line, c = pos.column; if (c < 1) { l -= 1; if (l < 1) return false; c = this.getLineLength(l); } else { c = Math.min(c - 1, this.getLineLength(l)); } pos.line = l; pos.column = c; return skipSpaces ? !!this.skipSpaces(pos, true, true) : true; }; Lp.firstPos = function() { // Trivial, but provided for completeness. return { line: 1, column: 0 }; }; Lp.lastPos = function() { return { line: this.length, column: this.getLineLength(this.length) }; }; Lp.skipSpaces = function(pos, backward, modifyInPlace) { if (pos) { pos = modifyInPlace ? pos : { line: pos.line, column: pos.column }; } else if (backward) { pos = this.lastPos(); } else { pos = this.firstPos(); } if (backward) { while (this.prevPos(pos)) { if (!isOnlyWhitespace(this.charAt(pos)) && this.nextPos(pos)) { return pos; } } return null; } else { while (isOnlyWhitespace(this.charAt(pos))) { if (!this.nextPos(pos)) { return null; } } return pos; } }; Lp.trimLeft = function() { var pos = this.skipSpaces(this.firstPos(), false, true); return pos ? this.slice(pos) : emptyLines; }; Lp.trimRight = function() { var pos = this.skipSpaces(this.lastPos(), true, true); return pos ? this.slice(this.firstPos(), pos) : emptyLines; }; Lp.trim = function() { var start = this.skipSpaces(this.firstPos(), false, true); if (start === null) return emptyLines; var end = this.skipSpaces(this.lastPos(), true, true); assert.notStrictEqual(end, null); return this.slice(start, end); }; Lp.eachPos = function(callback, startPos, skipSpaces) { var pos = this.firstPos(); if (startPos) { pos.line = startPos.line, pos.column = startPos.column } if (skipSpaces && !this.skipSpaces(pos, false, true)) { return; // Encountered nothing but spaces. } do callback.call(this, pos); while (this.nextPos(pos, skipSpaces)); }; Lp.bootstrapSlice = function(start, end) { var strings = this.toString().split( lineTerminatorSeqExp ).slice( start.line - 1, end.line ); strings.push(strings.pop().slice(0, end.column)); strings[0] = strings[0].slice(start.column); return fromString(strings.join("\n")); }; Lp.slice = function(start, end) { if (!end) { if (!start) { // The client seems to want a copy of this Lines object, but // Lines objects are immutable, so it's perfectly adequate to // return the same object. return this; } // Slice to the end if no end position was provided. end = this.lastPos(); } var secret = getSecret(this); var sliced = secret.infos.slice(start.line - 1, end.line); if (start.line === end.line) { sliced[0] = sliceInfo(sliced[0], start.column, end.column); } else { assert.ok(start.line < end.line); sliced[0] = sliceInfo(sliced[0], start.column); sliced.push(sliceInfo(sliced.pop(), 0, end.column)); } var lines = new Lines(sliced); if (secret.mappings.length > 0) { var newMappings = getSecret(lines).mappings; assert.strictEqual(newMappings.length, 0); secret.mappings.forEach(function(mapping) { var sliced = mapping.slice(this, start, end); if (sliced) { newMappings.push(sliced); } }, this); } return lines; }; function sliceInfo(info, startCol, endCol) { var sliceStart = info.sliceStart; var sliceEnd = info.sliceEnd; var indent = Math.max(info.indent, 0); var lineLength = indent + sliceEnd - sliceStart; if (typeof endCol === "undefined") { endCol = lineLength; } startCol = Math.max(startCol, 0); endCol = Math.min(endCol, lineLength); endCol = Math.max(endCol, startCol); if (endCol < indent) { indent = endCol; sliceEnd = sliceStart; } else { sliceEnd -= lineLength - endCol; } lineLength = endCol; lineLength -= startCol; if (startCol < indent) { indent -= startCol; } else { startCol -= indent; indent = 0; sliceStart += startCol; } assert.ok(indent >= 0); assert.ok(sliceStart <= sliceEnd); assert.strictEqual(lineLength, indent + sliceEnd - sliceStart); if (info.indent === indent && info.sliceStart === sliceStart && info.sliceEnd === sliceEnd) { return info; } return { line: info.line, indent: indent, // A destructive slice always unlocks indentation. locked: false, sliceStart: sliceStart, sliceEnd: sliceEnd }; } Lp.bootstrapSliceString = function(start, end, options) { return this.slice(start, end).toString(options); }; Lp.sliceString = function(start, end, options) { if (!end) { if (!start) { // The client seems to want a copy of this Lines object, but // Lines objects are immutable, so it's perfectly adequate to // return the same object. return this; } // Slice to the end if no end position was provided. end = this.lastPos(); } options = normalizeOptions(options); var infos = getSecret(this).infos; var parts = []; var tabWidth = options.tabWidth; for (var line = start.line; line <= end.line; ++line) { var info = infos[line - 1]; if (line === start.line) { if (line === end.line) { info = sliceInfo(info, start.column, end.column); } else { info = sliceInfo(info, start.column); } } else if (line === end.line) { info = sliceInfo(info, 0, end.column); } var indent = Math.max(info.indent, 0); var before = info.line.slice(0, info.sliceStart); if (options.reuseWhitespace && isOnlyWhitespace(before) && countSpaces(before, options.tabWidth) === indent) { // Reuse original spaces if the indentation is correct. parts.push(info.line.slice(0, info.sliceEnd)); continue; } var tabs = 0; var spaces = indent; if (options.useTabs) { tabs = Math.floor(indent / tabWidth); spaces -= tabs * tabWidth; } var result = ""; if (tabs > 0) { result += new Array(tabs + 1).join("\t"); } if (spaces > 0) { result += new Array(spaces + 1).join(" "); } result += info.line.slice(info.sliceStart, info.sliceEnd); parts.push(result); } return parts.join(options.lineTerminator); }; Lp.isEmpty = function() { return this.length < 2 && this.getLineLength(1) < 1; }; Lp.join = function(elements) { var separator = this; var separatorSecret = getSecret(separator); var infos = []; var mappings = []; var prevInfo; function appendSecret(secret) { if (secret === null) return; if (prevInfo) { var info = secret.infos[0]; var indent = new Array(info.indent + 1).join(" "); var prevLine = infos.length; var prevColumn = Math.max(prevInfo.indent, 0) + prevInfo.sliceEnd - prevInfo.sliceStart; prevInfo.line = prevInfo.line.slice( 0, prevInfo.sliceEnd) + indent + info.line.slice( info.sliceStart, info.sliceEnd); // If any part of a line is indentation-locked, the whole line // will be indentation-locked. prevInfo.locked = prevInfo.locked || info.locked; prevInfo.sliceEnd = prevInfo.line.length; if (secret.mappings.length > 0) { secret.mappings.forEach(function(mapping) { mappings.push(mapping.add(prevLine, prevColumn)); }); } } else if (secret.mappings.length > 0) { mappings.push.apply(mappings, secret.mappings); } secret.infos.forEach(function(info, i) { if (!prevInfo || i > 0) { prevInfo = copyLineInfo(info); infos.push(prevInfo); } }); } function appendWithSeparator(secret, i) { if (i > 0) appendSecret(separatorSecret); appendSecret(secret); } elements.map(function(elem) { var lines = fromString(elem); if (lines.isEmpty()) return null; return getSecret(lines); }).forEach(separator.isEmpty() ? appendSecret : appendWithSeparator); if (infos.length < 1) return emptyLines; var lines = new Lines(infos); getSecret(lines).mappings = mappings; return lines; }; exports.concat = function(elements) { return emptyLines.join(elements); }; Lp.concat = function(other) { var args = arguments, list = [this]; list.push.apply(list, args); assert.strictEqual(list.length, args.length + 1); return emptyLines.join(list); }; // The emptyLines object needs to be created all the way down here so that // Lines.prototype will be fully populated. var emptyLines = fromString(""); recast-0.14.4/lib/mapping.js000066400000000000000000000201161324434365200156260ustar00rootroot00000000000000var assert = require("assert"); var types = require("./types"); var isString = types.builtInTypes.string; var isNumber = types.builtInTypes.number; var SourceLocation = types.namedTypes.SourceLocation; var Position = types.namedTypes.Position; var linesModule = require("./lines"); var comparePos = require("./util").comparePos; function Mapping(sourceLines, sourceLoc, targetLoc) { assert.ok(this instanceof Mapping); assert.ok(sourceLines instanceof linesModule.Lines); SourceLocation.assert(sourceLoc); if (targetLoc) { // In certain cases it's possible for targetLoc.{start,end}.column // values to be negative, which technically makes them no longer // valid SourceLocation nodes, so we need to be more forgiving. assert.ok( isNumber.check(targetLoc.start.line) && isNumber.check(targetLoc.start.column) && isNumber.check(targetLoc.end.line) && isNumber.check(targetLoc.end.column) ); } else { // Assume identity mapping if no targetLoc specified. targetLoc = sourceLoc; } Object.defineProperties(this, { sourceLines: { value: sourceLines }, sourceLoc: { value: sourceLoc }, targetLoc: { value: targetLoc } }); } var Mp = Mapping.prototype; module.exports = Mapping; Mp.slice = function(lines, start, end) { assert.ok(lines instanceof linesModule.Lines); Position.assert(start); if (end) { Position.assert(end); } else { end = lines.lastPos(); } var sourceLines = this.sourceLines; var sourceLoc = this.sourceLoc; var targetLoc = this.targetLoc; function skip(name) { var sourceFromPos = sourceLoc[name]; var targetFromPos = targetLoc[name]; var targetToPos = start; if (name === "end") { targetToPos = end; } else { assert.strictEqual(name, "start"); } return skipChars( sourceLines, sourceFromPos, lines, targetFromPos, targetToPos ); } if (comparePos(start, targetLoc.start) <= 0) { if (comparePos(targetLoc.end, end) <= 0) { targetLoc = { start: subtractPos(targetLoc.start, start.line, start.column), end: subtractPos(targetLoc.end, start.line, start.column) }; // The sourceLoc can stay the same because the contents of the // targetLoc have not changed. } else if (comparePos(end, targetLoc.start) <= 0) { return null; } else { sourceLoc = { start: sourceLoc.start, end: skip("end") }; targetLoc = { start: subtractPos(targetLoc.start, start.line, start.column), end: subtractPos(end, start.line, start.column) }; } } else { if (comparePos(targetLoc.end, start) <= 0) { return null; } if (comparePos(targetLoc.end, end) <= 0) { sourceLoc = { start: skip("start"), end: sourceLoc.end }; targetLoc = { // Same as subtractPos(start, start.line, start.column): start: { line: 1, column: 0 }, end: subtractPos(targetLoc.end, start.line, start.column) }; } else { sourceLoc = { start: skip("start"), end: skip("end") }; targetLoc = { // Same as subtractPos(start, start.line, start.column): start: { line: 1, column: 0 }, end: subtractPos(end, start.line, start.column) }; } } return new Mapping(this.sourceLines, sourceLoc, targetLoc); }; Mp.add = function(line, column) { return new Mapping(this.sourceLines, this.sourceLoc, { start: addPos(this.targetLoc.start, line, column), end: addPos(this.targetLoc.end, line, column) }); }; function addPos(toPos, line, column) { return { line: toPos.line + line - 1, column: (toPos.line === 1) ? toPos.column + column : toPos.column }; } Mp.subtract = function(line, column) { return new Mapping(this.sourceLines, this.sourceLoc, { start: subtractPos(this.targetLoc.start, line, column), end: subtractPos(this.targetLoc.end, line, column) }); }; function subtractPos(fromPos, line, column) { return { line: fromPos.line - line + 1, column: (fromPos.line === line) ? fromPos.column - column : fromPos.column }; } Mp.indent = function(by, skipFirstLine, noNegativeColumns) { if (by === 0) { return this; } var targetLoc = this.targetLoc; var startLine = targetLoc.start.line; var endLine = targetLoc.end.line; if (skipFirstLine && startLine === 1 && endLine === 1) { return this; } targetLoc = { start: targetLoc.start, end: targetLoc.end }; if (!skipFirstLine || startLine > 1) { var startColumn = targetLoc.start.column + by; targetLoc.start = { line: startLine, column: noNegativeColumns ? Math.max(0, startColumn) : startColumn }; } if (!skipFirstLine || endLine > 1) { var endColumn = targetLoc.end.column + by; targetLoc.end = { line: endLine, column: noNegativeColumns ? Math.max(0, endColumn) : endColumn }; } return new Mapping(this.sourceLines, this.sourceLoc, targetLoc); }; function skipChars( sourceLines, sourceFromPos, targetLines, targetFromPos, targetToPos ) { assert.ok(sourceLines instanceof linesModule.Lines); assert.ok(targetLines instanceof linesModule.Lines); Position.assert(sourceFromPos); Position.assert(targetFromPos); Position.assert(targetToPos); var targetComparison = comparePos(targetFromPos, targetToPos); if (targetComparison === 0) { // Trivial case: no characters to skip. return sourceFromPos; } if (targetComparison < 0) { // Skipping forward. var sourceCursor = sourceLines.skipSpaces(sourceFromPos); var targetCursor = targetLines.skipSpaces(targetFromPos); var lineDiff = targetToPos.line - targetCursor.line; sourceCursor.line += lineDiff; targetCursor.line += lineDiff; if (lineDiff > 0) { // If jumping to later lines, reset columns to the beginnings // of those lines. sourceCursor.column = 0; targetCursor.column = 0; } else { assert.strictEqual(lineDiff, 0); } while (comparePos(targetCursor, targetToPos) < 0 && targetLines.nextPos(targetCursor, true)) { assert.ok(sourceLines.nextPos(sourceCursor, true)); assert.strictEqual( sourceLines.charAt(sourceCursor), targetLines.charAt(targetCursor) ); } } else { // Skipping backward. var sourceCursor = sourceLines.skipSpaces(sourceFromPos, true); var targetCursor = targetLines.skipSpaces(targetFromPos, true); var lineDiff = targetToPos.line - targetCursor.line; sourceCursor.line += lineDiff; targetCursor.line += lineDiff; if (lineDiff < 0) { // If jumping to earlier lines, reset columns to the ends of // those lines. sourceCursor.column = sourceLines.getLineLength(sourceCursor.line); targetCursor.column = targetLines.getLineLength(targetCursor.line); } else { assert.strictEqual(lineDiff, 0); } while (comparePos(targetToPos, targetCursor) < 0 && targetLines.prevPos(targetCursor, true)) { assert.ok(sourceLines.prevPos(sourceCursor, true)); assert.strictEqual( sourceLines.charAt(sourceCursor), targetLines.charAt(targetCursor) ); } } return sourceCursor; } recast-0.14.4/lib/options.js000066400000000000000000000115651324434365200156760ustar00rootroot00000000000000var defaults = { // If you want to use a different branch of esprima, or any other // module that supports a .parse function, pass that module object to // recast.parse as options.parser (legacy synonym: options.esprima). parser: require("../parsers/esprima"), // Number of spaces the pretty-printer should use per tab for // indentation. If you do not pass this option explicitly, it will be // (quite reliably!) inferred from the original code. tabWidth: 4, // If you really want the pretty-printer to use tabs instead of // spaces, make this option true. useTabs: false, // The reprinting code leaves leading whitespace untouched unless it // has to reindent a line, or you pass false for this option. reuseWhitespace: true, // Override this option to use a different line terminator, e.g. \r\n. lineTerminator: require("os").EOL, // Some of the pretty-printer code (such as that for printing function // parameter lists) makes a valiant attempt to prevent really long // lines. You can adjust the limit by changing this option; however, // there is no guarantee that line length will fit inside this limit. wrapColumn: 74, // Aspirational for now. // Pass a string as options.sourceFileName to recast.parse to tell the // reprinter to keep track of reused code so that it can construct a // source map automatically. sourceFileName: null, // Pass a string as options.sourceMapName to recast.print, and // (provided you passed options.sourceFileName earlier) the // PrintResult of recast.print will have a .map property for the // generated source map. sourceMapName: null, // If provided, this option will be passed along to the source map // generator as a root directory for relative source file paths. sourceRoot: null, // If you provide a source map that was generated from a previous call // to recast.print as options.inputSourceMap, the old source map will // be composed with the new source map. inputSourceMap: null, // If you want esprima to generate .range information (recast only // uses .loc internally), pass true for this option. range: false, // If you want esprima not to throw exceptions when it encounters // non-fatal errors, keep this option true. tolerant: true, // If you want to override the quotes used in string literals, specify // either "single", "double", or "auto" here ("auto" will select the one // which results in the shorter literal) // Otherwise, double quotes are used. quote: null, // Controls the printing of trailing commas in object literals, // array expressions and function parameters. // // This option could either be: // * Boolean - enable/disable in all contexts (objects, arrays and function params). // * Object - enable/disable per context. // // Example: // trailingComma: { // objects: true, // arrays: true, // parameters: false, // } trailingComma: false, // Controls the printing of spaces inside array brackets. // See: http://eslint.org/docs/rules/array-bracket-spacing arrayBracketSpacing: false, // Controls the printing of spaces inside object literals, // destructuring assignments, and import/export specifiers. // See: http://eslint.org/docs/rules/object-curly-spacing objectCurlySpacing: true, // If you want parenthesis to wrap single-argument arrow function parameter // lists, pass true for this option. arrowParensAlways: false, // There are 2 supported syntaxes (`,` and `;`) in Flow Object Types; // The use of commas is in line with the more popular style and matches // how objects are defined in JS, making it a bit more natural to write. flowObjectCommas: true, }, hasOwn = defaults.hasOwnProperty; // Copy options and fill in default values. exports.normalize = function(options) { options = options || defaults; function get(key) { return hasOwn.call(options, key) ? options[key] : defaults[key]; } return { tabWidth: +get("tabWidth"), useTabs: !!get("useTabs"), reuseWhitespace: !!get("reuseWhitespace"), lineTerminator: get("lineTerminator"), wrapColumn: Math.max(get("wrapColumn"), 0), sourceFileName: get("sourceFileName"), sourceMapName: get("sourceMapName"), sourceRoot: get("sourceRoot"), inputSourceMap: get("inputSourceMap"), parser: get("esprima") || get("parser"), range: get("range"), tolerant: get("tolerant"), quote: get("quote"), trailingComma: get("trailingComma"), arrayBracketSpacing: get("arrayBracketSpacing"), objectCurlySpacing: get("objectCurlySpacing"), arrowParensAlways: get("arrowParensAlways"), flowObjectCommas: get("flowObjectCommas"), }; }; recast-0.14.4/lib/parser.js000066400000000000000000000120271324434365200154710ustar00rootroot00000000000000"use strict"; var assert = require("assert"); var types = require("./types"); var n = types.namedTypes; var b = types.builders; var isObject = types.builtInTypes.object; var isArray = types.builtInTypes.array; var isFunction = types.builtInTypes.function; var Patcher = require("./patcher").Patcher; var normalizeOptions = require("./options").normalize; var fromString = require("./lines").fromString; var attachComments = require("./comments").attach; var util = require("./util"); exports.parse = function parse(source, options) { options = normalizeOptions(options); const lines = fromString(source, options); const sourceWithoutTabs = lines.toString({ tabWidth: options.tabWidth, reuseWhitespace: false, useTabs: false }); let comments = []; const ast = options.parser.parse(sourceWithoutTabs, { jsx: true, loc: true, locations: true, range: options.range, comment: true, onComment: comments, tolerant: util.getOption(options, "tolerant", true), ecmaVersion: 6, sourceType: util.getOption(options, "sourceType", "module") }); if (Array.isArray(ast.comments)) { comments = ast.comments; delete ast.comments; } if (ast.loc) { // If the source was empty, some parsers give loc.{start,end}.line // values of 0, instead of the minimum of 1. util.fixFaultyLocations(ast, lines); } else { ast.loc = { start: lines.firstPos(), end: lines.lastPos() }; } ast.loc.lines = lines; ast.loc.indent = 0; let file; let program; if (ast.type === "Program") { program = ast; // In order to ensure we reprint leading and trailing program // comments, wrap the original Program node with a File node. Only // ESTree parsers (Acorn and Esprima) return a Program as the root AST // node. Most other (Babylon-like) parsers return a File. file = b.file(ast, options.sourceFileName || null); file.loc = { start: lines.firstPos(), end: lines.lastPos(), lines: lines, indent: 0 }; } else if (ast.type === "File") { file = ast; program = file.program; } // Expand the Program's .loc to include all comments (not just those // attached to the Program node, as its children may have comments as // well), since sometimes program.loc.{start,end} will coincide with the // .loc.{start,end} of the first and last *statements*, mistakenly // excluding comments that fall outside that region. var trueProgramLoc = util.getTrueLoc({ type: program.type, loc: program.loc, body: [], comments }, lines); program.loc.start = trueProgramLoc.start; program.loc.end = trueProgramLoc.end; // Passing file.program here instead of just file means that initial // comments will be attached to program.body[0] instead of program. attachComments( comments, program.body.length ? file.program : file, lines ); // Return a copy of the original AST so that any changes made may be // compared to the original. return new TreeCopier(lines).copy(file); }; function TreeCopier(lines) { assert.ok(this instanceof TreeCopier); this.lines = lines; this.indent = 0; this.seen = new Map; } var TCp = TreeCopier.prototype; TCp.copy = function(node) { if (this.seen.has(node)) { return this.seen.get(node); } if (isArray.check(node)) { var copy = new Array(node.length); this.seen.set(node, copy); node.forEach(function (item, i) { copy[i] = this.copy(item); }, this); return copy; } if (!isObject.check(node)) { return node; } util.fixFaultyLocations(node, this.lines); var copy = Object.create(Object.getPrototypeOf(node), { original: { // Provide a link from the copy to the original. value: node, configurable: false, enumerable: false, writable: true } }); this.seen.set(node, copy); var loc = node.loc; var oldIndent = this.indent; var newIndent = oldIndent; if (loc) { // When node is a comment, we set node.loc.indent to // node.loc.start.column so that, when/if we print the comment by // itself, we can strip that much whitespace from the left margin of // the comment. This only really matters for multiline Block comments, // but it doesn't hurt for Line comments. if (node.type === "Block" || node.type === "Line" || node.type === "CommentBlock" || node.type === "CommentLine" || this.lines.isPrecededOnlyByWhitespace(loc.start)) { newIndent = this.indent = loc.start.column; } loc.lines = this.lines; loc.indent = newIndent; } var keys = Object.keys(node); var keyCount = keys.length; for (var i = 0; i < keyCount; ++i) { var key = keys[i]; if (key === "loc") { copy[key] = node[key]; } else if (key === "tokens" && node.type === "File") { // Preserve file.tokens (uncopied) in case client code cares about // it, even though Recast ignores it when reprinting. copy[key] = node[key]; } else { copy[key] = this.copy(node[key]); } } this.indent = oldIndent; return copy; }; recast-0.14.4/lib/patcher.js000066400000000000000000000375631324434365200156370ustar00rootroot00000000000000var assert = require("assert"); var linesModule = require("./lines"); var types = require("./types"); var getFieldValue = types.getFieldValue; var Node = types.namedTypes.Node; var Printable = types.namedTypes.Printable; var Expression = types.namedTypes.Expression; var ReturnStatement = types.namedTypes.ReturnStatement; var SourceLocation = types.namedTypes.SourceLocation; var util = require("./util"); var comparePos = util.comparePos; var FastPath = require("./fast-path"); var isObject = types.builtInTypes.object; var isArray = types.builtInTypes.array; var isString = types.builtInTypes.string; var riskyAdjoiningCharExp = /[0-9a-z_$]/i; function Patcher(lines) { assert.ok(this instanceof Patcher); assert.ok(lines instanceof linesModule.Lines); var self = this, replacements = []; self.replace = function(loc, lines) { if (isString.check(lines)) lines = linesModule.fromString(lines); replacements.push({ lines: lines, start: loc.start, end: loc.end }); }; self.get = function(loc) { // If no location is provided, return the complete Lines object. loc = loc || { start: { line: 1, column: 0 }, end: { line: lines.length, column: lines.getLineLength(lines.length) } }; var sliceFrom = loc.start, toConcat = []; function pushSlice(from, to) { assert.ok(comparePos(from, to) <= 0); toConcat.push(lines.slice(from, to)); } replacements.sort(function(a, b) { return comparePos(a.start, b.start); }).forEach(function(rep) { if (comparePos(sliceFrom, rep.start) > 0) { // Ignore nested replacement ranges. } else { pushSlice(sliceFrom, rep.start); toConcat.push(rep.lines); sliceFrom = rep.end; } }); pushSlice(sliceFrom, loc.end); return linesModule.concat(toConcat); }; } exports.Patcher = Patcher; var Pp = Patcher.prototype; Pp.tryToReprintComments = function(newNode, oldNode, print) { var patcher = this; if (!newNode.comments && !oldNode.comments) { // We were (vacuously) able to reprint all the comments! return true; } var newPath = FastPath.from(newNode); var oldPath = FastPath.from(oldNode); newPath.stack.push("comments", getSurroundingComments(newNode)); oldPath.stack.push("comments", getSurroundingComments(oldNode)); var reprints = []; var ableToReprintComments = findArrayReprints(newPath, oldPath, reprints); // No need to pop anything from newPath.stack or oldPath.stack, since // newPath and oldPath are fresh local variables. if (ableToReprintComments && reprints.length > 0) { reprints.forEach(function(reprint) { var oldComment = reprint.oldPath.getValue(); assert.ok(oldComment.leading || oldComment.trailing); patcher.replace( oldComment.loc, // Comments can't have .comments, so it doesn't matter whether we // print with comments or without. print(reprint.newPath).indentTail(oldComment.loc.indent) ); }); } return ableToReprintComments; }; // Get all comments that are either leading or trailing, ignoring any // comments that occur inside node.loc. Returns an empty array for nodes // with no leading or trailing comments. function getSurroundingComments(node) { var result = []; if (node.comments && node.comments.length > 0) { node.comments.forEach(function(comment) { if (comment.leading || comment.trailing) { result.push(comment); } }); } return result; } Pp.deleteComments = function(node) { if (!node.comments) { return; } var patcher = this; node.comments.forEach(function(comment) { if (comment.leading) { // Delete leading comments along with any trailing whitespace they // might have. patcher.replace({ start: comment.loc.start, end: node.loc.lines.skipSpaces( comment.loc.end, false, false) }, ""); } else if (comment.trailing) { // Delete trailing comments along with any leading whitespace they // might have. patcher.replace({ start: node.loc.lines.skipSpaces( comment.loc.start, true, false), end: comment.loc.end }, ""); } }); }; exports.getReprinter = function(path) { assert.ok(path instanceof FastPath); // Make sure that this path refers specifically to a Node, rather than // some non-Node subproperty of a Node. var node = path.getValue(); if (!Printable.check(node)) return; var orig = node.original; var origLoc = orig && orig.loc; var lines = origLoc && origLoc.lines; var reprints = []; if (!lines || !findReprints(path, reprints)) return; return function(print) { var patcher = new Patcher(lines); reprints.forEach(function(reprint) { var newNode = reprint.newPath.getValue(); var oldNode = reprint.oldPath.getValue(); SourceLocation.assert(oldNode.loc, true); var needToPrintNewPathWithComments = !patcher.tryToReprintComments(newNode, oldNode, print) if (needToPrintNewPathWithComments) { // Since we were not able to preserve all leading/trailing // comments, we delete oldNode's comments, print newPath with // comments, and then patch the resulting lines where oldNode used // to be. patcher.deleteComments(oldNode); } var newLines = print( reprint.newPath, needToPrintNewPathWithComments ).indentTail(oldNode.loc.indent); var nls = needsLeadingSpace(lines, oldNode.loc, newLines); var nts = needsTrailingSpace(lines, oldNode.loc, newLines); // If we try to replace the argument of a ReturnStatement like // return"asdf" with e.g. a literal null expression, we run the risk // of ending up with returnnull, so we need to add an extra leading // space in situations where that might happen. Likewise for // "asdf"in obj. See #170. if (nls || nts) { var newParts = []; nls && newParts.push(" "); newParts.push(newLines); nts && newParts.push(" "); newLines = linesModule.concat(newParts); } patcher.replace(oldNode.loc, newLines); }); // Recall that origLoc is the .loc of an ancestor node that is // guaranteed to contain all the reprinted nodes and comments. return patcher.get(origLoc).indentTail(-orig.loc.indent); }; }; // If the last character before oldLoc and the first character of newLines // are both identifier characters, they must be separated by a space, // otherwise they will most likely get fused together into a single token. function needsLeadingSpace(oldLines, oldLoc, newLines) { var posBeforeOldLoc = util.copyPos(oldLoc.start); // The character just before the location occupied by oldNode. var charBeforeOldLoc = oldLines.prevPos(posBeforeOldLoc) && oldLines.charAt(posBeforeOldLoc); // First character of the reprinted node. var newFirstChar = newLines.charAt(newLines.firstPos()); return charBeforeOldLoc && riskyAdjoiningCharExp.test(charBeforeOldLoc) && newFirstChar && riskyAdjoiningCharExp.test(newFirstChar); } // If the last character of newLines and the first character after oldLoc // are both identifier characters, they must be separated by a space, // otherwise they will most likely get fused together into a single token. function needsTrailingSpace(oldLines, oldLoc, newLines) { // The character just after the location occupied by oldNode. var charAfterOldLoc = oldLines.charAt(oldLoc.end); var newLastPos = newLines.lastPos(); // Last character of the reprinted node. var newLastChar = newLines.prevPos(newLastPos) && newLines.charAt(newLastPos); return newLastChar && riskyAdjoiningCharExp.test(newLastChar) && charAfterOldLoc && riskyAdjoiningCharExp.test(charAfterOldLoc); } function findReprints(newPath, reprints) { var newNode = newPath.getValue(); Printable.assert(newNode); var oldNode = newNode.original; Printable.assert(oldNode); assert.deepEqual(reprints, []); if (newNode.type !== oldNode.type) { return false; } var oldPath = new FastPath(oldNode); var canReprint = findChildReprints(newPath, oldPath, reprints); if (!canReprint) { // Make absolutely sure the calling code does not attempt to reprint // any nodes. reprints.length = 0; } return canReprint; } function findAnyReprints(newPath, oldPath, reprints) { var newNode = newPath.getValue(); var oldNode = oldPath.getValue(); if (newNode === oldNode) return true; if (isArray.check(newNode)) return findArrayReprints(newPath, oldPath, reprints); if (isObject.check(newNode)) return findObjectReprints(newPath, oldPath, reprints); return false; } function findArrayReprints(newPath, oldPath, reprints) { var newNode = newPath.getValue(); var oldNode = oldPath.getValue(); if (newNode === oldNode || newPath.valueIsDuplicate() || oldPath.valueIsDuplicate()) { return true; } isArray.assert(newNode); var len = newNode.length; if (!(isArray.check(oldNode) && oldNode.length === len)) return false; for (var i = 0; i < len; ++i) { newPath.stack.push(i, newNode[i]); oldPath.stack.push(i, oldNode[i]); var canReprint = findAnyReprints(newPath, oldPath, reprints); newPath.stack.length -= 2; oldPath.stack.length -= 2; if (!canReprint) { return false; } } return true; } function findObjectReprints(newPath, oldPath, reprints) { var newNode = newPath.getValue(); isObject.assert(newNode); if (newNode.original === null) { // If newNode.original node was set to null, reprint the node. return false; } var oldNode = oldPath.getValue(); if (!isObject.check(oldNode)) return false; if (newNode === oldNode || newPath.valueIsDuplicate() || oldPath.valueIsDuplicate()) { return true; } if (Printable.check(newNode)) { if (!Printable.check(oldNode)) { return false; } // Here we need to decide whether the reprinted code for newNode is // appropriate for patching into the location of oldNode. if (newNode.type === oldNode.type) { var childReprints = []; if (findChildReprints(newPath, oldPath, childReprints)) { reprints.push.apply(reprints, childReprints); } else if (oldNode.loc) { // If we have no .loc information for oldNode, then we won't be // able to reprint it. reprints.push({ oldPath: oldPath.copy(), newPath: newPath.copy() }); } else { return false; } return true; } if (Expression.check(newNode) && Expression.check(oldNode) && // If we have no .loc information for oldNode, then we won't be // able to reprint it. oldNode.loc) { // If both nodes are subtypes of Expression, then we should be able // to fill the location occupied by the old node with code printed // for the new node with no ill consequences. reprints.push({ oldPath: oldPath.copy(), newPath: newPath.copy() }); return true; } // The nodes have different types, and at least one of the types is // not a subtype of the Expression type, so we cannot safely assume // the nodes are syntactically interchangeable. return false; } return findChildReprints(newPath, oldPath, reprints); } // This object is reused in hasOpeningParen and hasClosingParen to avoid // having to allocate a temporary object. var reusablePos = { line: 1, column: 0 }; var nonSpaceExp = /\S/; function hasOpeningParen(oldPath) { var oldNode = oldPath.getValue(); var loc = oldNode.loc; var lines = loc && loc.lines; if (lines) { var pos = reusablePos; pos.line = loc.start.line; pos.column = loc.start.column; while (lines.prevPos(pos)) { var ch = lines.charAt(pos); if (ch === "(") { // If we found an opening parenthesis but it occurred before the // start of the original subtree for this reprinting, then we must // not return true for hasOpeningParen(oldPath). return comparePos(oldPath.getRootValue().loc.start, pos) <= 0; } if (nonSpaceExp.test(ch)) { return false; } } } return false; } function hasClosingParen(oldPath) { var oldNode = oldPath.getValue(); var loc = oldNode.loc; var lines = loc && loc.lines; if (lines) { var pos = reusablePos; pos.line = loc.end.line; pos.column = loc.end.column; do { var ch = lines.charAt(pos); if (ch === ")") { // If we found a closing parenthesis but it occurred after the end // of the original subtree for this reprinting, then we must not // return true for hasClosingParen(oldPath). return comparePos(pos, oldPath.getRootValue().loc.end) <= 0; } if (nonSpaceExp.test(ch)) { return false; } } while (lines.nextPos(pos)); } return false; } function hasParens(oldPath) { // This logic can technically be fooled if the node has parentheses but // there are comments intervening between the parentheses and the // node. In such cases the node will be harmlessly wrapped in an // additional layer of parentheses. return hasOpeningParen(oldPath) && hasClosingParen(oldPath); } function findChildReprints(newPath, oldPath, reprints) { var newNode = newPath.getValue(); var oldNode = oldPath.getValue(); isObject.assert(newNode); isObject.assert(oldNode); if (newNode.original === null) { // If newNode.original node was set to null, reprint the node. return false; } // If this type of node cannot come lexically first in its enclosing // statement (e.g. a function expression or object literal), and it // seems to be doing so, then the only way we can ignore this problem // and save ourselves from falling back to the pretty printer is if an // opening parenthesis happens to precede the node. For example, // (function(){ ... }()); does not need to be reprinted, even though the // FunctionExpression comes lexically first in the enclosing // ExpressionStatement and fails the hasParens test, because the parent // CallExpression passes the hasParens test. If we relied on the // path.needsParens() && !hasParens(oldNode) check below, the absence of // a closing parenthesis after the FunctionExpression would trigger // pretty-printing unnecessarily. if (Node.check(newNode) && !newPath.canBeFirstInStatement() && newPath.firstInStatement() && !hasOpeningParen(oldPath)) { return false; } // If this node needs parentheses and will not be wrapped with // parentheses when reprinted, then return false to skip reprinting and // let it be printed generically. if (newPath.needsParens(true) && !hasParens(oldPath)) { return false; } var keys = util.getUnionOfKeys(oldNode, newNode); if (oldNode.type === "File" || newNode.type === "File") { // Don't bother traversing file.tokens, an often very large array // returned by Babylon, and useless for our purposes. delete keys.tokens; } // Don't bother traversing .loc objects looking for reprintable nodes. delete keys.loc; var originalReprintCount = reprints.length; for (var k in keys) { if (k.charAt(0) === "_") { // Ignore "private" AST properties added by e.g. Babel plugins and // parsers like Babylon. continue; } newPath.stack.push(k, types.getFieldValue(newNode, k)); oldPath.stack.push(k, types.getFieldValue(oldNode, k)); var canReprint = findAnyReprints(newPath, oldPath, reprints); newPath.stack.length -= 2; oldPath.stack.length -= 2; if (!canReprint) { return false; } } // Return statements might end up running into ASI issues due to // comments inserted deep within the tree, so reprint them if anything // changed within them. if (ReturnStatement.check(newPath.getNode()) && reprints.length > originalReprintCount) { return false; } return true; } recast-0.14.4/lib/printer.js000066400000000000000000002206611324434365200156650ustar00rootroot00000000000000"use strict"; var assert = require("assert"); var sourceMap = require("source-map"); var printComments = require("./comments").printComments; var linesModule = require("./lines"); var fromString = linesModule.fromString; var concat = linesModule.concat; var normalizeOptions = require("./options").normalize; var getReprinter = require("./patcher").getReprinter; var types = require("./types"); var namedTypes = types.namedTypes; var isString = types.builtInTypes.string; var isObject = types.builtInTypes.object; var FastPath = require("./fast-path"); var util = require("./util"); function PrintResult(code, sourceMap) { assert.ok(this instanceof PrintResult); isString.assert(code); this.code = code; if (sourceMap) { isObject.assert(sourceMap); this.map = sourceMap; } } var PRp = PrintResult.prototype; var warnedAboutToString = false; PRp.toString = function() { if (!warnedAboutToString) { console.warn( "Deprecation warning: recast.print now returns an object with " + "a .code property. You appear to be treating the object as a " + "string, which might still work but is strongly discouraged." ); warnedAboutToString = true; } return this.code; }; var emptyPrintResult = new PrintResult(""); function Printer(originalOptions) { assert.ok(this instanceof Printer); var explicitTabWidth = originalOptions && originalOptions.tabWidth; var options = normalizeOptions(originalOptions); assert.notStrictEqual(options, originalOptions); // It's common for client code to pass the same options into both // recast.parse and recast.print, but the Printer doesn't need (and // can be confused by) options.sourceFileName, so we null it out. options.sourceFileName = null; function printWithComments(path) { assert.ok(path instanceof FastPath); return printComments(path, print); } function print(path, includeComments) { if (includeComments) return printWithComments(path); assert.ok(path instanceof FastPath); if (!explicitTabWidth) { var oldTabWidth = options.tabWidth; var loc = path.getNode().loc; if (loc && loc.lines && loc.lines.guessTabWidth) { options.tabWidth = loc.lines.guessTabWidth(); var lines = maybeReprint(path); options.tabWidth = oldTabWidth; return lines; } } return maybeReprint(path); } function maybeReprint(path) { var reprinter = getReprinter(path); if (reprinter) { // Since the print function that we pass to the reprinter will // be used to print "new" nodes, it's tempting to think we // should pass printRootGenerically instead of print, to avoid // calling maybeReprint again, but that would be a mistake // because the new nodes might not be entirely new, but merely // moved from elsewhere in the AST. The print function is the // right choice because it gives us the opportunity to reprint // such nodes using their original source. return maybeAddParens(path, reprinter(print)); } return printRootGenerically(path); } // Print the root node generically, but then resume reprinting its // children non-generically. function printRootGenerically(path, includeComments) { return includeComments ? printComments(path, printRootGenerically) : genericPrint(path, options, printWithComments); } // Print the entire AST generically. function printGenerically(path) { return genericPrint(path, options, printGenerically); } this.print = function(ast) { if (!ast) { return emptyPrintResult; } var lines = print(FastPath.from(ast), true); return new PrintResult( lines.toString(options), util.composeSourceMaps( options.inputSourceMap, lines.getSourceMap( options.sourceMapName, options.sourceRoot ) ) ); }; this.printGenerically = function(ast) { if (!ast) { return emptyPrintResult; } var path = FastPath.from(ast); var oldReuseWhitespace = options.reuseWhitespace; // Do not reuse whitespace (or anything else, for that matter) // when printing generically. options.reuseWhitespace = false; // TODO Allow printing of comments? var pr = new PrintResult(printGenerically(path).toString(options)); options.reuseWhitespace = oldReuseWhitespace; return pr; }; } exports.Printer = Printer; function maybeAddParens(path, lines) { return path.needsParens() ? concat(["(", lines, ")"]) : lines; } function genericPrint(path, options, printPath) { assert.ok(path instanceof FastPath); var node = path.getValue(); var parts = []; var needsParens = false; var linesWithoutParens = genericPrintNoParens(path, options, printPath); if (! node || linesWithoutParens.isEmpty()) { return linesWithoutParens; } if (node.decorators && node.decorators.length > 0 && // If the parent node is an export declaration, it will be // responsible for printing node.decorators. ! util.getParentExportDeclaration(path)) { path.each(function(decoratorPath) { parts.push(printPath(decoratorPath), "\n"); }, "decorators"); } else if (util.isExportDeclaration(node) && node.declaration && node.declaration.decorators) { // Export declarations are responsible for printing any decorators // that logically apply to node.declaration. path.each(function(decoratorPath) { parts.push(printPath(decoratorPath), "\n"); }, "declaration", "decorators"); } else { // Nodes with decorators can't have parentheses, so we can avoid // computing path.needsParens() except in this case. needsParens = path.needsParens(); } if (needsParens) { parts.unshift("("); } parts.push(linesWithoutParens); if (needsParens) { parts.push(")"); } return concat(parts); } function genericPrintNoParens(path, options, print) { var n = path.getValue(); if (!n) { return fromString(""); } if (typeof n === "string") { return fromString(n, options); } namedTypes.Printable.assert(n); var parts = []; switch (n.type) { case "File": return path.call(print, "program"); case "Program": // Babel 6 if (n.directives) { path.each(function(childPath) { parts.push(print(childPath), ";\n"); }, "directives"); } parts.push(path.call(function(bodyPath) { return printStatementSequence(bodyPath, options, print); }, "body")); return concat(parts); case "Noop": // Babel extension. case "EmptyStatement": return fromString(""); case "ExpressionStatement": return concat([path.call(print, "expression"), ";"]); case "ParenthesizedExpression": // Babel extension. return concat(["(", path.call(print, "expression"), ")"]); case "BinaryExpression": case "LogicalExpression": case "AssignmentExpression": return fromString(" ").join([ path.call(print, "left"), n.operator, path.call(print, "right") ]); case "AssignmentPattern": return concat([ path.call(print, "left"), " = ", path.call(print, "right") ]); case "MemberExpression": parts.push(path.call(print, "object")); var property = path.call(print, "property"); if (n.computed) { parts.push("[", property, "]"); } else { parts.push(".", property); } return concat(parts); case "MetaProperty": return concat([ path.call(print, "meta"), ".", path.call(print, "property") ]); case "BindExpression": if (n.object) { parts.push(path.call(print, "object")); } parts.push("::", path.call(print, "callee")); return concat(parts); case "Path": return fromString(".").join(n.body); case "Identifier": return concat([ fromString(n.name, options), n.optional ? "?" : "", path.call(print, "typeAnnotation") ]); case "SpreadElement": case "SpreadElementPattern": case "RestProperty": // Babel 6 for ObjectPattern case "SpreadProperty": case "SpreadPropertyPattern": case "ObjectTypeSpreadProperty": case "RestElement": return concat([ "...", path.call(print, "argument"), path.call(print, "typeAnnotation") ]); case "FunctionDeclaration": case "FunctionExpression": case "TSDeclareFunction": if (n.declare) { parts.push("declare "); } if (n.async) { parts.push("async "); } parts.push("function"); if (n.generator) parts.push("*"); if (n.id) { parts.push( " ", path.call(print, "id"), path.call(print, "typeParameters") ); } parts.push( "(", printFunctionParams(path, options, print), ")", path.call(print, "returnType") ); if (n.body) { parts.push(" ", path.call(print, "body")); } return concat(parts); case "ArrowFunctionExpression": if (n.async) { parts.push("async "); } if (n.typeParameters) { parts.push(path.call(print, "typeParameters")); } if (! options.arrowParensAlways && n.params.length === 1 && ! n.rest && n.params[0].type === 'Identifier' && ! n.params[0].typeAnnotation && ! n.returnType) { parts.push(path.call(print, "params", 0)); } else { parts.push( "(", printFunctionParams(path, options, print), ")", path.call(print, "returnType") ); } parts.push(" => ", path.call(print, "body")); return concat(parts); case "MethodDefinition": return printMethod(path, options, print); case "YieldExpression": parts.push("yield"); if (n.delegate) parts.push("*"); if (n.argument) parts.push(" ", path.call(print, "argument")); return concat(parts); case "AwaitExpression": parts.push("await"); if (n.all) parts.push("*"); if (n.argument) parts.push(" ", path.call(print, "argument")); return concat(parts); case "ModuleDeclaration": parts.push("module", path.call(print, "id")); if (n.source) { assert.ok(!n.body); parts.push("from", path.call(print, "source")); } else { parts.push(path.call(print, "body")); } return fromString(" ").join(parts); case "ImportSpecifier": if (n.importKind && n.importKind !== "value") { parts.push(n.importKind + " "); } if (n.imported) { parts.push(path.call(print, "imported")); if (n.local && n.local.name !== n.imported.name) { parts.push(" as ", path.call(print, "local")); } } else if (n.id) { parts.push(path.call(print, "id")); if (n.name) { parts.push(" as ", path.call(print, "name")); } } return concat(parts); case "ExportSpecifier": if (n.local) { parts.push(path.call(print, "local")); if (n.exported && n.exported.name !== n.local.name) { parts.push(" as ", path.call(print, "exported")); } } else if (n.id) { parts.push(path.call(print, "id")); if (n.name) { parts.push(" as ", path.call(print, "name")); } } return concat(parts); case "ExportBatchSpecifier": return fromString("*"); case "ImportNamespaceSpecifier": parts.push("* as "); if (n.local) { parts.push(path.call(print, "local")); } else if (n.id) { parts.push(path.call(print, "id")); } return concat(parts); case "ImportDefaultSpecifier": if (n.local) { return path.call(print, "local"); } return path.call(print, "id"); case "TSExportAssignment": return concat(["export = ", path.call(print, "expression")]); case "ExportDeclaration": case "ExportDefaultDeclaration": case "ExportNamedDeclaration": return printExportDeclaration(path, options, print); case "ExportAllDeclaration": parts.push("export *"); if (n.exported) { parts.push(" as ", path.call(print, "exported")); } parts.push( " from ", path.call(print, "source") ); return concat(parts); case "TSNamespaceExportDeclaration": parts.push("export as namespace ", path.call(print, "id")); return maybeAddSemicolon(concat(parts)); case "ExportNamespaceSpecifier": return concat(["* as ", path.call(print, "exported")]); case "ExportDefaultSpecifier": return path.call(print, "exported"); case "Import": return fromString("import", options); case "ImportDeclaration": { parts.push("import "); if (n.importKind && n.importKind !== "value") { parts.push(n.importKind + " "); } if (n.specifiers && n.specifiers.length > 0) { const unbracedSpecifiers = []; const bracedSpecifiers = []; path.each(function (specifierPath) { const spec = specifierPath.getValue(); if (spec.type === "ImportSpecifier") { bracedSpecifiers.push(print(specifierPath)); } else if (spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") { unbracedSpecifiers.push(print(specifierPath)); } }, "specifiers"); unbracedSpecifiers.forEach((lines, i) => { if (i > 0) { parts.push(", "); } parts.push(lines); }); if (bracedSpecifiers.length > 0) { let lines = fromString(", ").join(bracedSpecifiers); if (lines.getLineLength(1) > options.wrapColumn) { lines = concat([ fromString(",\n").join( bracedSpecifiers ).indent(options.tabWidth), "," ]); } if (unbracedSpecifiers.length > 0) { parts.push(", "); } if (lines.length > 1) { parts.push("{\n", lines, "\n}"); } else if (options.objectCurlySpacing) { parts.push("{ ", lines, " }"); } else { parts.push("{", lines, "}"); } } parts.push(" from "); } parts.push(path.call(print, "source"), ";"); return concat(parts); } case "BlockStatement": var naked = path.call(function(bodyPath) { return printStatementSequence(bodyPath, options, print); }, "body"); if (naked.isEmpty()) { if (!n.directives || n.directives.length === 0) { return fromString("{}"); } } parts.push("{\n"); // Babel 6 if (n.directives) { path.each(function(childPath) { parts.push( print(childPath).indent(options.tabWidth), ";", n.directives.length > 1 || !naked.isEmpty() ? "\n" : "" ); }, "directives"); } parts.push(naked.indent(options.tabWidth)); parts.push("\n}"); return concat(parts); case "ReturnStatement": parts.push("return"); if (n.argument) { var argLines = path.call(print, "argument"); if (argLines.startsWithComment() || (argLines.length > 1 && namedTypes.JSXElement && namedTypes.JSXElement.check(n.argument) )) { parts.push( " (\n", argLines.indent(options.tabWidth), "\n)" ); } else { parts.push(" ", argLines); } } parts.push(";"); return concat(parts); case "CallExpression": return concat([ path.call(print, "callee"), printArgumentsList(path, options, print) ]); case "ObjectExpression": case "ObjectPattern": case "ObjectTypeAnnotation": var allowBreak = false; var isTypeAnnotation = n.type === "ObjectTypeAnnotation"; var separator = options.flowObjectCommas ? "," : (isTypeAnnotation ? ";" : ","); var fields = []; if (isTypeAnnotation) { fields.push("indexers", "callProperties"); } fields.push("properties"); var len = 0; fields.forEach(function(field) { len += n[field].length; }); var oneLine = (isTypeAnnotation && len === 1) || len === 0; var leftBrace = n.exact ? "{|" : "{"; var rightBrace = n.exact ? "|}" : "}"; parts.push(oneLine ? leftBrace : leftBrace + "\n"); var leftBraceIndex = parts.length - 1; var i = 0; fields.forEach(function(field) { path.each(function(childPath) { var lines = print(childPath); if (!oneLine) { lines = lines.indent(options.tabWidth); } var multiLine = !isTypeAnnotation && lines.length > 1; if (multiLine && allowBreak) { // Similar to the logic for BlockStatement. parts.push("\n"); } parts.push(lines); if (i < len - 1) { // Add an extra line break if the previous object property // had a multi-line value. parts.push(separator + (multiLine ? "\n\n" : "\n")); allowBreak = !multiLine; } else if (len !== 1 && isTypeAnnotation) { parts.push(separator); } else if (!oneLine && util.isTrailingCommaEnabled(options, "objects")) { parts.push(separator); } i++; }, field); }); parts.push(oneLine ? rightBrace : "\n" + rightBrace); if (i !== 0 && oneLine && options.objectCurlySpacing) { parts[leftBraceIndex] = leftBrace + " "; parts[parts.length - 1] = " " + rightBrace; } return concat(parts); case "PropertyPattern": return concat([ path.call(print, "key"), ": ", path.call(print, "pattern") ]); case "ObjectProperty": // Babel 6 case "Property": // Non-standard AST node type. if (n.method || n.kind === "get" || n.kind === "set") { return printMethod(path, options, print); } var key = path.call(print, "key"); if (n.computed) { parts.push("[", key, "]"); } else { parts.push(key); } if (! n.shorthand) { parts.push(": ", path.call(print, "value")); } return concat(parts); case "ClassMethod": // Babel 6 case "ObjectMethod": // Babel 6 case "TSDeclareMethod": return printMethod(path, options, print); case "Decorator": return concat(["@", path.call(print, "expression")]); case "ArrayExpression": case "ArrayPattern": var elems = n.elements, len = elems.length; var printed = path.map(print, "elements"); var joined = fromString(", ").join(printed); var oneLine = joined.getLineLength(1) <= options.wrapColumn; if (oneLine) { if (options.arrayBracketSpacing) { parts.push("[ "); } else { parts.push("["); } } else { parts.push("[\n"); } path.each(function(elemPath) { var i = elemPath.getName(); var elem = elemPath.getValue(); if (!elem) { // If the array expression ends with a hole, that hole // will be ignored by the interpreter, but if it ends with // two (or more) holes, we need to write out two (or more) // commas so that the resulting code is interpreted with // both (all) of the holes. parts.push(","); } else { var lines = printed[i]; if (oneLine) { if (i > 0) parts.push(" "); } else { lines = lines.indent(options.tabWidth); } parts.push(lines); if (i < len - 1 || (!oneLine && util.isTrailingCommaEnabled(options, "arrays"))) parts.push(","); if (!oneLine) parts.push("\n"); } }, "elements"); if (oneLine && options.arrayBracketSpacing) { parts.push(" ]"); } else { parts.push("]"); } return concat(parts); case "SequenceExpression": return fromString(", ").join(path.map(print, "expressions")); case "ThisExpression": return fromString("this"); case "Super": return fromString("super"); case "NullLiteral": // Babel 6 Literal split return fromString("null"); case "RegExpLiteral": // Babel 6 Literal split return fromString(n.extra.raw); case "BigIntLiteral": // Babel 7 Literal split return fromString(n.value + "n"); case "NumericLiteral": // Babel 6 Literal Split // Keep original representation for numeric values not in base 10. if (n.extra && typeof n.extra.raw === "string" && Number(n.extra.raw) === n.value) { return fromString(n.extra.raw, options); } return fromString(n.value, options); case "BooleanLiteral": // Babel 6 Literal split case "StringLiteral": // Babel 6 Literal split case "Literal": // Numeric values may be in bases other than 10. Use their raw // representation if equivalent. if (typeof n.value === "number" && typeof n.raw === "string" && Number(n.raw) === n.value) { return fromString(n.raw, options); } if (typeof n.value !== "string") { return fromString(n.value, options); } return fromString(nodeStr(n.value, options), options); case "Directive": // Babel 6 return path.call(print, "value"); case "DirectiveLiteral": // Babel 6 return fromString(nodeStr(n.value, options)); case "ModuleSpecifier": if (n.local) { throw new Error( "The ESTree ModuleSpecifier type should be abstract" ); } // The Esprima ModuleSpecifier type is just a string-valued // Literal identifying the imported-from module. return fromString(nodeStr(n.value, options), options); case "UnaryExpression": parts.push(n.operator); if (/[a-z]$/.test(n.operator)) parts.push(" "); parts.push(path.call(print, "argument")); return concat(parts); case "UpdateExpression": parts.push( path.call(print, "argument"), n.operator ); if (n.prefix) parts.reverse(); return concat(parts); case "ConditionalExpression": return concat([ "(", path.call(print, "test"), " ? ", path.call(print, "consequent"), " : ", path.call(print, "alternate"), ")" ]); case "NewExpression": parts.push("new ", path.call(print, "callee")); var args = n.arguments; if (args) { parts.push(printArgumentsList(path, options, print)); } return concat(parts); case "VariableDeclaration": if (n.declare) { parts.push("declare "); } parts.push(n.kind, " "); var maxLen = 0; var printed = path.map(function(childPath) { var lines = print(childPath); maxLen = Math.max(lines.length, maxLen); return lines; }, "declarations"); if (maxLen === 1) { parts.push(fromString(", ").join(printed)); } else if (printed.length > 1 ) { parts.push( fromString(",\n").join(printed) .indentTail(n.kind.length + 1) ); } else { parts.push(printed[0]); } // We generally want to terminate all variable declarations with a // semicolon, except when they are children of for loops. var parentNode = path.getParentNode(); if (!namedTypes.ForStatement.check(parentNode) && !namedTypes.ForInStatement.check(parentNode) && !(namedTypes.ForOfStatement && namedTypes.ForOfStatement.check(parentNode)) && !(namedTypes.ForAwaitStatement && namedTypes.ForAwaitStatement.check(parentNode))) { parts.push(";"); } return concat(parts); case "VariableDeclarator": return n.init ? fromString(" = ").join([ path.call(print, "id"), path.call(print, "init") ]) : path.call(print, "id"); case "WithStatement": return concat([ "with (", path.call(print, "object"), ") ", path.call(print, "body") ]); case "IfStatement": var con = adjustClause(path.call(print, "consequent"), options), parts = ["if (", path.call(print, "test"), ")", con]; if (n.alternate) parts.push( endsWithBrace(con) ? " else" : "\nelse", adjustClause(path.call(print, "alternate"), options)); return concat(parts); case "ForStatement": // TODO Get the for (;;) case right. var init = path.call(print, "init"), sep = init.length > 1 ? ";\n" : "; ", forParen = "for (", indented = fromString(sep).join([ init, path.call(print, "test"), path.call(print, "update") ]).indentTail(forParen.length), head = concat([forParen, indented, ")"]), clause = adjustClause(path.call(print, "body"), options), parts = [head]; if (head.length > 1) { parts.push("\n"); clause = clause.trimLeft(); } parts.push(clause); return concat(parts); case "WhileStatement": return concat([ "while (", path.call(print, "test"), ")", adjustClause(path.call(print, "body"), options) ]); case "ForInStatement": // Note: esprima can't actually parse "for each (". return concat([ n.each ? "for each (" : "for (", path.call(print, "left"), " in ", path.call(print, "right"), ")", adjustClause(path.call(print, "body"), options) ]); case "ForOfStatement": case "ForAwaitStatement": parts.push("for "); if (n.await || n.type === "ForAwaitStatement") { parts.push("await "); } parts.push( "(", path.call(print, "left"), " of ", path.call(print, "right"), ")", adjustClause(path.call(print, "body"), options) ); return concat(parts); case "DoWhileStatement": var doBody = concat([ "do", adjustClause(path.call(print, "body"), options) ]), parts = [doBody]; if (endsWithBrace(doBody)) parts.push(" while"); else parts.push("\nwhile"); parts.push(" (", path.call(print, "test"), ");"); return concat(parts); case "DoExpression": var statements = path.call(function(bodyPath) { return printStatementSequence(bodyPath, options, print); }, "body"); return concat([ "do {\n", statements.indent(options.tabWidth), "\n}" ]); case "BreakStatement": parts.push("break"); if (n.label) parts.push(" ", path.call(print, "label")); parts.push(";"); return concat(parts); case "ContinueStatement": parts.push("continue"); if (n.label) parts.push(" ", path.call(print, "label")); parts.push(";"); return concat(parts); case "LabeledStatement": return concat([ path.call(print, "label"), ":\n", path.call(print, "body") ]); case "TryStatement": parts.push( "try ", path.call(print, "block") ); if (n.handler) { parts.push(" ", path.call(print, "handler")); } else if (n.handlers) { path.each(function(handlerPath) { parts.push(" ", print(handlerPath)); }, "handlers"); } if (n.finalizer) { parts.push(" finally ", path.call(print, "finalizer")); } return concat(parts); case "CatchClause": parts.push("catch "); if (n.param) { parts.push("(", path.call(print, "param")); } if (n.guard) { // Note: esprima does not recognize conditional catch clauses. parts.push(" if ", path.call(print, "guard")); } if (n.param) { parts.push(") "); } parts.push(path.call(print, "body")); return concat(parts); case "ThrowStatement": return concat(["throw ", path.call(print, "argument"), ";"]); case "SwitchStatement": return concat([ "switch (", path.call(print, "discriminant"), ") {\n", fromString("\n").join(path.map(print, "cases")), "\n}" ]); // Note: ignoring n.lexical because it has no printing consequences. case "SwitchCase": if (n.test) parts.push("case ", path.call(print, "test"), ":"); else parts.push("default:"); if (n.consequent.length > 0) { parts.push("\n", path.call(function(consequentPath) { return printStatementSequence(consequentPath, options, print); }, "consequent").indent(options.tabWidth)); } return concat(parts); case "DebuggerStatement": return fromString("debugger;"); // JSX extensions below. case "JSXAttribute": parts.push(path.call(print, "name")); if (n.value) parts.push("=", path.call(print, "value")); return concat(parts); case "JSXIdentifier": return fromString(n.name, options); case "JSXNamespacedName": return fromString(":").join([ path.call(print, "namespace"), path.call(print, "name") ]); case "JSXMemberExpression": return fromString(".").join([ path.call(print, "object"), path.call(print, "property") ]); case "JSXSpreadAttribute": return concat(["{...", path.call(print, "argument"), "}"]); case "JSXSpreadChild": return concat(["{...", path.call(print, "expression"), "}"]); case "JSXExpressionContainer": return concat(["{", path.call(print, "expression"), "}"]); case "JSXElement": case "JSXFragment": var openingPropName = "opening" + ( n.type === "JSXElement" ? "Element" : "Fragment"); var closingPropName = "closing" + ( n.type === "JSXElement" ? "Element" : "Fragment"); var openingLines = path.call(print, openingPropName); if (n[openingPropName].selfClosing) { assert.ok(!n[closingPropName]); return openingLines; } var childLines = concat( path.map(function(childPath) { var child = childPath.getValue(); if (namedTypes.Literal.check(child) && typeof child.value === "string") { if (/\S/.test(child.value)) { return child.value.replace(/^\s+|\s+$/g, ""); } else if (/\n/.test(child.value)) { return "\n"; } } return print(childPath); }, "children") ).indentTail(options.tabWidth); var closingLines = path.call(print, closingPropName); return concat([ openingLines, childLines, closingLines ]); case "JSXOpeningElement": parts.push("<", path.call(print, "name")); var attrParts = []; path.each(function(attrPath) { attrParts.push(" ", print(attrPath)); }, "attributes"); var attrLines = concat(attrParts); var needLineWrap = ( attrLines.length > 1 || attrLines.getLineLength(1) > options.wrapColumn ); if (needLineWrap) { attrParts.forEach(function(part, i) { if (part === " ") { assert.strictEqual(i % 2, 0); attrParts[i] = "\n"; } }); attrLines = concat(attrParts).indentTail(options.tabWidth); } parts.push(attrLines, n.selfClosing ? " />" : ">"); return concat(parts); case "JSXClosingElement": return concat([""]); case "JSXOpeningFragment": return fromString("<>"); case "JSXClosingFragment": return fromString("") case "JSXText": return fromString(n.value, options); case "JSXEmptyExpression": return fromString(""); case "TypeAnnotatedIdentifier": return concat([ path.call(print, "annotation"), " ", path.call(print, "identifier") ]); case "ClassBody": if (n.body.length === 0) { return fromString("{}"); } return concat([ "{\n", path.call(function(bodyPath) { return printStatementSequence(bodyPath, options, print); }, "body").indent(options.tabWidth), "\n}" ]); case "ClassPropertyDefinition": parts.push("static ", path.call(print, "definition")); if (!namedTypes.MethodDefinition.check(n.definition)) parts.push(";"); return concat(parts); case "ClassProperty": if (typeof n.accessibility === "string") { parts.push(n.accessibility, " "); } if (n.static) { parts.push("static "); } if (n.abstract) { parts.push("abstract "); } if (n.readonly) { parts.push("readonly "); } var key = path.call(print, "key"); if (n.computed) { key = concat(["[", key, "]"]); } if (n.variance) { key = concat([printVariance(path, print), key]); } parts.push(key); if (n.optional) { parts.push("?"); } if (n.typeAnnotation) { parts.push(path.call(print, "typeAnnotation")); } if (n.value) { parts.push(" = ", path.call(print, "value")); } parts.push(";"); return concat(parts); case "ClassDeclaration": case "ClassExpression": if (n.declare) { parts.push("declare "); } if (n.abstract) { parts.push("abstract "); } parts.push("class"); if (n.id) { parts.push( " ", path.call(print, "id") ); } if (n.typeParameters) { parts.push(path.call(print, "typeParameters")); } if (n.superClass) { parts.push( " extends ", path.call(print, "superClass"), path.call(print, "superTypeParameters") ); } if (n["implements"] && n['implements'].length > 0) { parts.push( " implements ", fromString(", ").join(path.map(print, "implements")) ); } parts.push(" ", path.call(print, "body")); return concat(parts); case "TemplateElement": return fromString(n.value.raw, options).lockIndentTail(); case "TemplateLiteral": var expressions = path.map(print, "expressions"); parts.push("`"); path.each(function(childPath) { var i = childPath.getName(); parts.push(print(childPath)); if (i < expressions.length) { parts.push("${", expressions[i], "}"); } }, "quasis"); parts.push("`"); return concat(parts).lockIndentTail(); case "TaggedTemplateExpression": return concat([ path.call(print, "tag"), path.call(print, "quasi") ]); // These types are unprintable because they serve as abstract // supertypes for other (printable) types. case "Node": case "Printable": case "SourceLocation": case "Position": case "Statement": case "Function": case "Pattern": case "Expression": case "Declaration": case "Specifier": case "NamedSpecifier": case "Comment": // Supertype of Block and Line. case "MemberTypeAnnotation": // Flow case "TupleTypeAnnotation": // Flow case "Type": // Flow case "TSHasOptionalTypeParameters": case "TSHasOptionalTypeAnnotation": throw new Error("unprintable type: " + JSON.stringify(n.type)); case "CommentBlock": // Babel block comment. case "Block": // Esprima block comment. return concat(["/*", fromString(n.value, options), "*/"]); case "CommentLine": // Babel line comment. case "Line": // Esprima line comment. return concat(["//", fromString(n.value, options)]); // Type Annotations for Facebook Flow, typically stripped out or // transformed away before printing. case "TypeAnnotation": if (n.typeAnnotation) { if (n.typeAnnotation.type !== "FunctionTypeAnnotation") { parts.push(": "); } parts.push(path.call(print, "typeAnnotation")); return concat(parts); } return fromString(""); case "ExistentialTypeParam": case "ExistsTypeAnnotation": return fromString("*", options); case "EmptyTypeAnnotation": return fromString("empty", options); case "AnyTypeAnnotation": return fromString("any", options); case "MixedTypeAnnotation": return fromString("mixed", options); case "ArrayTypeAnnotation": return concat([ path.call(print, "elementType"), "[]" ]); case "BooleanTypeAnnotation": return fromString("boolean", options); case "BooleanLiteralTypeAnnotation": assert.strictEqual(typeof n.value, "boolean"); return fromString("" + n.value, options); case "DeclareClass": return printFlowDeclaration(path, [ "class ", path.call(print, "id"), " ", path.call(print, "body"), ]); case "DeclareFunction": return printFlowDeclaration(path, [ "function ", path.call(print, "id"), ";" ]); case "DeclareModule": return printFlowDeclaration(path, [ "module ", path.call(print, "id"), " ", path.call(print, "body"), ]); case "DeclareModuleExports": return printFlowDeclaration(path, [ "module.exports", path.call(print, "typeAnnotation"), ]); case "DeclareVariable": return printFlowDeclaration(path, [ "var ", path.call(print, "id"), ";" ]); case "DeclareExportDeclaration": case "DeclareExportAllDeclaration": return concat([ "declare ", printExportDeclaration(path, options, print) ]); case "FunctionTypeAnnotation": // FunctionTypeAnnotation is ambiguous: // declare function(a: B): void; OR // var A: (a: B) => void; var parent = path.getParentNode(0); var isArrowFunctionTypeAnnotation = !( namedTypes.ObjectTypeCallProperty.check(parent) || namedTypes.DeclareFunction.check(path.getParentNode(2)) ); var needsColon = isArrowFunctionTypeAnnotation && !namedTypes.FunctionTypeParam.check(parent); if (needsColon) { parts.push(": "); } parts.push( "(", fromString(", ").join(path.map(print, "params")), ")" ); // The returnType is not wrapped in a TypeAnnotation, so the colon // needs to be added separately. if (n.returnType) { parts.push( isArrowFunctionTypeAnnotation ? " => " : ": ", path.call(print, "returnType") ); } return concat(parts); case "FunctionTypeParam": return concat([ path.call(print, "name"), n.optional ? '?' : '', ": ", path.call(print, "typeAnnotation"), ]); case "GenericTypeAnnotation": return concat([ path.call(print, "id"), path.call(print, "typeParameters") ]); case "DeclareInterface": parts.push("declare "); // Fall through to InterfaceDeclaration... case "InterfaceDeclaration": case "TSInterfaceDeclaration": if (n.declare) { parts.push("declare "); } parts.push( "interface ", path.call(print, "id"), path.call(print, "typeParameters"), " " ); if (n["extends"] && n["extends"].length > 0) { parts.push( "extends ", fromString(", ").join(path.map(print, "extends")), " " ); } if (n.body) { parts.push(path.call(print, "body")); } return concat(parts); case "ClassImplements": case "InterfaceExtends": return concat([ path.call(print, "id"), path.call(print, "typeParameters") ]); case "IntersectionTypeAnnotation": return fromString(" & ").join(path.map(print, "types")); case "NullableTypeAnnotation": return concat([ "?", path.call(print, "typeAnnotation") ]); case "NullLiteralTypeAnnotation": return fromString("null", options); case "ThisTypeAnnotation": return fromString("this", options); case "NumberTypeAnnotation": return fromString("number", options); case "ObjectTypeCallProperty": return path.call(print, "value"); case "ObjectTypeIndexer": return concat([ printVariance(path, print), "[", path.call(print, "id"), ": ", path.call(print, "key"), "]: ", path.call(print, "value") ]); case "ObjectTypeProperty": return concat([ printVariance(path, print), path.call(print, "key"), n.optional ? "?" : "", ": ", path.call(print, "value") ]); case "QualifiedTypeIdentifier": return concat([ path.call(print, "qualification"), ".", path.call(print, "id") ]); case "StringLiteralTypeAnnotation": return fromString(nodeStr(n.value, options), options); case "NumberLiteralTypeAnnotation": case "NumericLiteralTypeAnnotation": assert.strictEqual(typeof n.value, "number"); return fromString(JSON.stringify(n.value), options); case "StringTypeAnnotation": return fromString("string", options); case "DeclareTypeAlias": parts.push("declare "); // Fall through to TypeAlias... case "TypeAlias": return concat([ "type ", path.call(print, "id"), path.call(print, "typeParameters"), " = ", path.call(print, "right"), ";" ]); case "DeclareOpaqueType": parts.push("declare "); // Fall through to OpaqueType... case "OpaqueType": parts.push( "opaque type ", path.call(print, "id"), path.call(print, "typeParameters") ); if (n["supertype"]) { parts.push(": ", path.call(print, "supertype")); } if (n["impltype"]) { parts.push(" = ", path.call(print, "impltype")); } parts.push(";"); return concat(parts); case "TypeCastExpression": return concat([ "(", path.call(print, "expression"), path.call(print, "typeAnnotation"), ")" ]); case "TypeParameterDeclaration": case "TypeParameterInstantiation": return concat([ "<", fromString(", ").join(path.map(print, "params")), ">" ]); case "Variance": if (n.kind === "plus") { return fromString("+"); } if (n.kind === "minus") { return fromString("-"); } return fromString(""); case "TypeParameter": if (n.variance) { parts.push(printVariance(path, print)); } parts.push(path.call(print, 'name')); if (n.bound) { parts.push(path.call(print, 'bound')); } if (n['default']) { parts.push('=', path.call(print, 'default')); } return concat(parts); case "TypeofTypeAnnotation": return concat([ fromString("typeof ", options), path.call(print, "argument") ]); case "UnionTypeAnnotation": return fromString(" | ").join(path.map(print, "types")); case "VoidTypeAnnotation": return fromString("void", options); case "NullTypeAnnotation": return fromString("null", options); // Type Annotations for TypeScript (when using Babylon as parser) case "TSType": throw new Error("unprintable type: " + JSON.stringify(n.type)); case "TSNumberKeyword": return fromString("number", options); case "TSObjectKeyword": return fromString("object", options); case "TSBooleanKeyword": return fromString("boolean", options); case "TSStringKeyword": return fromString("string", options); case "TSSymbolKeyword": return fromString("symbol", options); case "TSAnyKeyword": return fromString("any", options); case "TSVoidKeyword": return fromString("void", options); case "TSThisType": return fromString("this", options); case "TSNullKeyword": return fromString("null", options); case "TSUndefinedKeyword": return fromString("undefined", options); case "TSNeverKeyword": return fromString("never", options); case "TSArrayType": return concat([ path.call(print, "elementType"), "[]" ]); case "TSLiteralType": return path.call(print, "literal") case "TSUnionType": return fromString(" | ").join(path.map(print, "types")); case "TSIntersectionType": return fromString(" & ").join(path.map(print, "types")); case "TSConditionalType": parts.push( path.call(print, "checkType"), " extends ", path.call(print, "extendsType"), " ? ", path.call(print, "trueType"), " : ", path.call(print, "falseType") ); return concat(parts); case "TSInferType": parts.push( "infer ", path.call(print, "typeParameter") ); return concat(parts); case "TSParenthesizedType": return concat([ "(", path.call(print, "typeAnnotation"), ")" ]); case "TSFunctionType": case "TSConstructorType": return concat([ path.call(print, "typeParameters"), "(", printFunctionParams(path, options, print), ")", path.call(print, "typeAnnotation") ]); case "TSMappedType": { parts.push( n.readonly ? "readonly " : "", "[", path.call(print, "typeParameter"), "]", n.optional ? "?" : "" ); if (n.typeAnnotation) { parts.push(": ", path.call(print, "typeAnnotation"), ";"); } return concat([ "{\n", concat(parts).indent(options.tabWidth), "\n}", ]); } case "TSTupleType": return concat([ "[", fromString(", ").join(path.map(print, "elementTypes")), "]" ]); case "TSIndexedAccessType": return concat([ path.call(print, "objectType"), "[", path.call(print, "indexType"), "]" ]); case "TSTypeOperator": return concat([ path.call(print, "operator"), " ", path.call(print, "typeAnnotation") ]); case "TSTypeLiteral": { const memberLines = fromString(",\n").join(path.map(print, "members")); if (memberLines.isEmpty()) { return fromString("{}", options); } parts.push( "{\n", memberLines.indent(options.tabWidth), "\n}" ); return concat(parts); } case "TSEnumMember": parts.push(path.call(print, "id")); if (n.initializer) { parts.push( " = ", path.call(print, "initializer") ); } return concat(parts); case "TSTypeQuery": return concat([ "typeof ", path.call(print, "exprName"), ]); case "TSParameterProperty": if (n.accessibility) { parts.push(n.accessibility, " "); } if (n.export) { parts.push("export "); } if (n.static) { parts.push("static "); } if (n.readonly) { parts.push("readonly "); } parts.push(path.call(print, "parameter")); return concat(parts); case "TSTypeReference": return concat([ path.call(print, "typeName"), path.call(print, "typeParameters") ]); case "TSQualifiedName": return concat([ path.call(print, "left"), ".", path.call(print, "right") ]); case "TSAsExpression": { var withParens = n.extra && n.extra.parenthesized === true; parts = []; if (withParens) parts.push("("); parts.push( path.call(print, "expression"), fromString(" as "), path.call(print, "typeAnnotation") ); if (withParens) parts.push(")"); return concat(parts); } case "TSNonNullExpression": return concat([ path.call(print, "expression"), "!" ]); case "TSTypeAnnotation": { // similar to flow's FunctionTypeAnnotation, this can be // ambiguous: it can be prefixed by => or : // in a type predicate, it takes the for u is U var parent = path.getParentNode(0); var prefix = ": "; var isFunctionType = namedTypes.TSFunctionType.check(parent); if (namedTypes.TSFunctionType.check(parent)) { prefix = " => "; } if (namedTypes.TSTypePredicate.check(parent)) { prefix = " is "; } return concat([ prefix, path.call(print, "typeAnnotation") ]); } case "TSIndexSignature": return concat([ n.readonly ? "readonly " : "", "[", path.map(print, "parameters"), "]", path.call(print, "typeAnnotation") ]); case "TSPropertySignature": parts.push( printVariance(path, print), n.readonly ? "readonly " : "" ); if (n.computed) { parts.push( "[", path.call(print, "key"), "]" ); } else { parts.push(path.call(print, "key")); } parts.push( n.optional ? "?" : "", path.call(print, "typeAnnotation") ); return concat(parts); case "TSMethodSignature": if (n.computed) { parts.push( "[", path.call(print, "key"), "]" ); } else { parts.push(path.call(print, "key")); } if (n.optional) { parts.push("?"); } parts.push( path.call(print, "typeParameters"), "(", printFunctionParams(path, options, print), ")", path.call(print, "typeAnnotation") ); return concat(parts); case "TSTypePredicate": return concat([ path.call(print, "parameterName"), path.call(print, "typeAnnotation") ]); case "TSCallSignatureDeclaration": return concat([ path.call(print, "typeParameters"), "(", printFunctionParams(path, options, print), ")", path.call(print, "typeAnnotation") ]); case "TSConstructSignatureDeclaration": if (n.typeParameters) { parts.push( "new", path.call(print, "typeParameters") ); } else { parts.push("new "); } parts.push( "(", printFunctionParams(path, options, print), ")", path.call(print, "typeAnnotation") ); return concat(parts); case "TSTypeAliasDeclaration": return concat([ "type ", path.call(print, "id"), path.call(print, "typeParameters"), " = ", path.call(print, "typeAnnotation"), ";" ]); case "TSTypeParameter": parts.push(path.call(print, "name")); // ambiguous because of TSMappedType var parent = path.getParentNode(0); var isInMappedType = namedTypes.TSMappedType.check(parent); if (n.constraint) { parts.push( isInMappedType ? " in " : " extends ", path.call(print, "constraint") ); } if (n["default"]) { parts.push(" = ", path.call(print, "default")); } return concat(parts); case "TSTypeAssertion": var withParens = n.extra && n.extra.parenthesized === true; if (withParens) { parts.push("("); } parts.push( "<", path.call(print, "typeAnnotation"), "> ", path.call(print, "expression") ); if (withParens) { parts.push(")"); } return concat(parts); case "TSTypeParameterDeclaration": case "TSTypeParameterInstantiation": return concat([ "<", fromString(", ").join(path.map(print, "params")), ">" ]); case "TSEnumDeclaration": parts.push( n.declare ? "declare " : "", n.const ? "const " : "", "enum ", path.call(print, "id") ); const memberLines = fromString(",\n").join(path.map(print, "members")); if (memberLines.isEmpty()) { parts.push(" {}"); } else { parts.push( " {\n", memberLines.indent(options.tabWidth), "\n}" ); } return concat(parts); case "TSExpressionWithTypeArguments": return concat([ path.call(print, "expression"), path.call(print, "typeParameters") ]); case "TSInterfaceBody": var lines = fromString(";\n").join(path.map(print, "body")); if (lines.isEmpty()) { return fromString("{}", options); } return concat([ "{\n", lines.indent(options.tabWidth), ";", "\n}", ]); case "TSImportEqualsDeclaration": if (n.isExport) { parts.push("export "); } parts.push( "import ", path.call(print, "id"), " = ", path.call(print, "moduleReference") ); return maybeAddSemicolon(concat(parts)); case "TSExternalModuleReference": return concat(["require(", path.call(print, "expression"), ")"]); case "TSModuleDeclaration": { const parent = path.getParentNode(); if (parent.type === "TSModuleDeclaration") { parts.push("."); } else { if (n.declare) { parts.push("declare "); } if (! n.global) { const isExternal = n.id.type === "StringLiteral" || (n.id.type === "Literal" && typeof n.id.value === "string"); if (isExternal) { parts.push("module "); } else if (n.loc && n.loc.lines && n.id.loc) { const prefix = n.loc.lines.sliceString( n.loc.start, n.id.loc.start ); // These keywords are fundamentally ambiguous in the // Babylon parser, and not reflected in the AST, so // the best we can do is to match the original code, // when possible. if (prefix.indexOf("module") >= 0) { parts.push("module "); } else { parts.push("namespace "); } } else { parts.push("namespace "); } } } parts.push(path.call(print, "id")); if (n.body && n.body.type === "TSModuleDeclaration") { parts.push(path.call(print, "body")); } else if (n.body) { const bodyLines = path.call(print, "body"); if (bodyLines.isEmpty()) { parts.push(" {}"); } else { parts.push( " {\n", bodyLines.indent(options.tabWidth), "\n}" ); } } return concat(parts); } case "TSModuleBlock": return path.call(function (bodyPath) { return printStatementSequence(bodyPath, options, print); }, "body"); // Unhandled types below. If encountered, nodes of these types should // be either left alone or desugared into AST types that are fully // supported by the pretty-printer. case "ClassHeritage": // TODO case "ComprehensionBlock": // TODO case "ComprehensionExpression": // TODO case "Glob": // TODO case "GeneratorExpression": // TODO case "LetStatement": // TODO case "LetExpression": // TODO case "GraphExpression": // TODO case "GraphIndexExpression": // TODO // XML types that nobody cares about or needs to print. case "XMLDefaultDeclaration": case "XMLAnyName": case "XMLQualifiedIdentifier": case "XMLFunctionQualifiedIdentifier": case "XMLAttributeSelector": case "XMLFilterExpression": case "XML": case "XMLElement": case "XMLList": case "XMLEscape": case "XMLText": case "XMLStartTag": case "XMLEndTag": case "XMLPointTag": case "XMLName": case "XMLAttribute": case "XMLCdata": case "XMLComment": case "XMLProcessingInstruction": default: debugger; throw new Error("unknown type: " + JSON.stringify(n.type)); } return p; } function printStatementSequence(path, options, print) { var inClassBody = namedTypes.ClassBody && namedTypes.ClassBody.check(path.getParentNode()); var filtered = []; var sawComment = false; var sawStatement = false; path.each(function(stmtPath) { var i = stmtPath.getName(); var stmt = stmtPath.getValue(); // Just in case the AST has been modified to contain falsy // "statements," it's safer simply to skip them. if (!stmt) { return; } // Skip printing EmptyStatement nodes to avoid leaving stray // semicolons lying around. if (stmt.type === "EmptyStatement") { return; } if (namedTypes.Comment.check(stmt)) { // The pretty printer allows a dangling Comment node to act as // a Statement when the Comment can't be attached to any other // non-Comment node in the tree. sawComment = true; } else if (namedTypes.Statement.check(stmt)) { sawStatement = true; } else { // When the pretty printer encounters a string instead of an // AST node, it just prints the string. This behavior can be // useful for fine-grained formatting decisions like inserting // blank lines. isString.assert(stmt); } // We can't hang onto stmtPath outside of this function, because // it's just a reference to a mutable FastPath object, so we have // to go ahead and print it here. filtered.push({ node: stmt, printed: print(stmtPath) }); }); if (sawComment) { assert.strictEqual( sawStatement, false, "Comments may appear as statements in otherwise empty statement " + "lists, but may not coexist with non-Comment nodes." ); } var prevTrailingSpace = null; var len = filtered.length; var parts = []; filtered.forEach(function(info, i) { var printed = info.printed; var stmt = info.node; var multiLine = printed.length > 1; var notFirst = i > 0; var notLast = i < len - 1; var leadingSpace; var trailingSpace; var lines = stmt && stmt.loc && stmt.loc.lines; var trueLoc = lines && options.reuseWhitespace && util.getTrueLoc(stmt, lines); if (notFirst) { if (trueLoc) { var beforeStart = lines.skipSpaces(trueLoc.start, true); var beforeStartLine = beforeStart ? beforeStart.line : 1; var leadingGap = trueLoc.start.line - beforeStartLine; leadingSpace = Array(leadingGap + 1).join("\n"); } else { leadingSpace = multiLine ? "\n\n" : "\n"; } } else { leadingSpace = ""; } if (notLast) { if (trueLoc) { var afterEnd = lines.skipSpaces(trueLoc.end); var afterEndLine = afterEnd ? afterEnd.line : lines.length; var trailingGap = afterEndLine - trueLoc.end.line; trailingSpace = Array(trailingGap + 1).join("\n"); } else { trailingSpace = multiLine ? "\n\n" : "\n"; } } else { trailingSpace = ""; } parts.push( maxSpace(prevTrailingSpace, leadingSpace), printed ); if (notLast) { prevTrailingSpace = trailingSpace; } else if (trailingSpace) { parts.push(trailingSpace); } }); return concat(parts); } function maxSpace(s1, s2) { if (!s1 && !s2) { return fromString(""); } if (!s1) { return fromString(s2); } if (!s2) { return fromString(s1); } var spaceLines1 = fromString(s1); var spaceLines2 = fromString(s2); if (spaceLines2.length > spaceLines1.length) { return spaceLines2; } return spaceLines1; } function printMethod(path, options, print) { var node = path.getNode(); var kind = node.kind; var parts = []; var nodeValue = node.value; if (! namedTypes.FunctionExpression.check(nodeValue)) { nodeValue = node; } var access = node.accessibility || node.access; if (typeof access === "string") { parts.push(access, " "); } if (node.static) { parts.push("static "); } if (node.abstract) { parts.push("abstract "); } if (node.readonly) { parts.push("readonly "); } if (nodeValue.async) { parts.push("async "); } if (nodeValue.generator) { parts.push("*"); } if (kind === "get" || kind === "set") { parts.push(kind, " "); } var key = path.call(print, "key"); if (node.computed) { key = concat(["[", key, "]"]); } parts.push(key); if (node.optional) { parts.push("?"); } if (node === nodeValue) { parts.push( path.call(print, "typeParameters"), "(", printFunctionParams(path, options, print), ")", path.call(print, "returnType") ); if (node.body) { parts.push(" ", path.call(print, "body")); } else { parts.push(";"); } } else { parts.push( path.call(print, "value", "typeParameters"), "(", path.call(function(valuePath) { return printFunctionParams(valuePath, options, print); }, "value"), ")", path.call(print, "value", "returnType") ); if (nodeValue.body) { parts.push(" ", path.call(print, "value", "body")); } else { parts.push(";"); } } return concat(parts); } function printArgumentsList(path, options, print) { var printed = path.map(print, "arguments"); var trailingComma = util.isTrailingCommaEnabled(options, "parameters"); var joined = fromString(", ").join(printed); if (joined.getLineLength(1) > options.wrapColumn) { joined = fromString(",\n").join(printed); return concat([ "(\n", joined.indent(options.tabWidth), trailingComma ? ",\n)" : "\n)" ]); } return concat(["(", joined, ")"]); } function printFunctionParams(path, options, print) { var fun = path.getValue(); if (fun.params) { var params = fun.params; var printed = path.map(print, "params"); } else if (fun.parameters) { params = fun.parameters; printed = path.map(print, "parameters"); } if (fun.defaults) { path.each(function(defExprPath) { var i = defExprPath.getName(); var p = printed[i]; if (p && defExprPath.getValue()) { printed[i] = concat([p, " = ", print(defExprPath)]); } }, "defaults"); } if (fun.rest) { printed.push(concat(["...", path.call(print, "rest")])); } var joined = fromString(", ").join(printed); if (joined.length > 1 || joined.getLineLength(1) > options.wrapColumn) { joined = fromString(",\n").join(printed); if (util.isTrailingCommaEnabled(options, "parameters") && !fun.rest && params[params.length - 1].type !== 'RestElement') { joined = concat([joined, ",\n"]); } else { joined = concat([joined, "\n"]); } return concat(["\n", joined.indent(options.tabWidth)]); } return joined; } function printExportDeclaration(path, options, print) { var decl = path.getValue(); var parts = ["export "]; if (decl.exportKind && decl.exportKind !== "value") { parts.push(decl.exportKind + " "); } var shouldPrintSpaces = options.objectCurlySpacing; namedTypes.Declaration.assert(decl); if (decl["default"] || decl.type === "ExportDefaultDeclaration") { parts.push("default "); } if (decl.declaration) { parts.push(path.call(print, "declaration")); } else if (decl.specifiers && decl.specifiers.length > 0) { if (decl.specifiers.length === 1 && decl.specifiers[0].type === "ExportBatchSpecifier") { parts.push("*"); } else { parts.push( shouldPrintSpaces ? "{ " : "{", fromString(", ").join(path.map(print, "specifiers")), shouldPrintSpaces ? " }" : "}" ); } if (decl.source) { parts.push(" from ", path.call(print, "source")); } } var lines = concat(parts); if (lastNonSpaceCharacter(lines) !== ";" && ! (decl.declaration && (decl.declaration.type === "FunctionDeclaration" || decl.declaration.type === "ClassDeclaration" || decl.declaration.type === "TSModuleDeclaration" || decl.declaration.type === "TSInterfaceDeclaration" || decl.declaration.type === "TSEnumDeclaration"))) { lines = concat([lines, ";"]); } return lines; } function printFlowDeclaration(path, parts) { var parentExportDecl = util.getParentExportDeclaration(path); if (parentExportDecl) { assert.strictEqual( parentExportDecl.type, "DeclareExportDeclaration" ); } else { // If the parent node has type DeclareExportDeclaration, then it // will be responsible for printing the "declare" token. Otherwise // it needs to be printed with this non-exported declaration node. parts.unshift("declare "); } return concat(parts); } function printVariance(path, print) { return path.call(function (variancePath) { var value = variancePath.getValue(); if (value) { if (value === "plus") { return fromString("+"); } if (value === "minus") { return fromString("-"); } return print(variancePath); } return fromString(""); }, "variance"); } function adjustClause(clause, options) { if (clause.length > 1) return concat([" ", clause]); return concat([ "\n", maybeAddSemicolon(clause).indent(options.tabWidth) ]); } function lastNonSpaceCharacter(lines) { var pos = lines.lastPos(); do { var ch = lines.charAt(pos); if (/\S/.test(ch)) return ch; } while (lines.prevPos(pos)); } function endsWithBrace(lines) { return lastNonSpaceCharacter(lines) === "}"; } function swapQuotes(str) { return str.replace(/['"]/g, function(m) { return m === '"' ? '\'' : '"'; }); } function nodeStr(str, options) { isString.assert(str); switch (options.quote) { case "auto": var double = JSON.stringify(str); var single = swapQuotes(JSON.stringify(swapQuotes(str))); return double.length > single.length ? single : double; case "single": return swapQuotes(JSON.stringify(swapQuotes(str))); case "double": default: return JSON.stringify(str); } } function maybeAddSemicolon(lines) { var eoc = lastNonSpaceCharacter(lines); if (!eoc || "\n};".indexOf(eoc) < 0) return concat([lines, ";"]); return lines; } recast-0.14.4/lib/types.js000066400000000000000000000005141324434365200153370ustar00rootroot00000000000000// This module was originally created so that Recast could add its own // custom types to the AST type system (in particular, the File type), but // those types are now incorporated into ast-types, so this module doesn't // have much to do anymore. Still, it might prove useful in the future. module.exports = require("ast-types"); recast-0.14.4/lib/util.js000066400000000000000000000243751324434365200151630ustar00rootroot00000000000000var assert = require("assert"); var types = require("./types"); var getFieldValue = types.getFieldValue; var n = types.namedTypes; var sourceMap = require("source-map"); var SourceMapConsumer = sourceMap.SourceMapConsumer; var SourceMapGenerator = sourceMap.SourceMapGenerator; var hasOwn = Object.prototype.hasOwnProperty; var util = exports; function getOption(options, key, defaultValue) { if (options && hasOwn.call(options, key)) { return options[key]; } return defaultValue; } util.getOption = getOption; function getUnionOfKeys() { var result = {}; var argc = arguments.length; for (var i = 0; i < argc; ++i) { var keys = Object.keys(arguments[i]); var keyCount = keys.length; for (var j = 0; j < keyCount; ++j) { result[keys[j]] = true; } } return result; } util.getUnionOfKeys = getUnionOfKeys; function comparePos(pos1, pos2) { return (pos1.line - pos2.line) || (pos1.column - pos2.column); } util.comparePos = comparePos; function copyPos(pos) { return { line: pos.line, column: pos.column }; } util.copyPos = copyPos; util.composeSourceMaps = function(formerMap, latterMap) { if (formerMap) { if (!latterMap) { return formerMap; } } else { return latterMap || null; } var smcFormer = new SourceMapConsumer(formerMap); var smcLatter = new SourceMapConsumer(latterMap); var smg = new SourceMapGenerator({ file: latterMap.file, sourceRoot: latterMap.sourceRoot }); var sourcesToContents = {}; smcLatter.eachMapping(function(mapping) { var origPos = smcFormer.originalPositionFor({ line: mapping.originalLine, column: mapping.originalColumn }); var sourceName = origPos.source; if (sourceName === null) { return; } smg.addMapping({ source: sourceName, original: copyPos(origPos), generated: { line: mapping.generatedLine, column: mapping.generatedColumn }, name: mapping.name }); var sourceContent = smcFormer.sourceContentFor(sourceName); if (sourceContent && !hasOwn.call(sourcesToContents, sourceName)) { sourcesToContents[sourceName] = sourceContent; smg.setSourceContent(sourceName, sourceContent); } }); return smg.toJSON(); }; util.getTrueLoc = function(node, lines) { // It's possible that node is newly-created (not parsed by Esprima), // in which case it probably won't have a .loc property (or an // .original property for that matter). That's fine; we'll just // pretty-print it as usual. if (!node.loc) { return null; } var result = { start: node.loc.start, end: node.loc.end }; function include(node) { expandLoc(result, node.loc); } // If the node is an export declaration and its .declaration has any // decorators, their locations might contribute to the true start/end // positions of the export declaration node. if (node.declaration && node.declaration.decorators && util.isExportDeclaration(node)) { node.declaration.decorators.forEach(include); } if (comparePos(result.start, result.end) < 0) { // Trim leading whitespace. result.start = copyPos(result.start); lines.skipSpaces(result.start, false, true); if (comparePos(result.start, result.end) < 0) { // Trim trailing whitespace, if the end location is not already the // same as the start location. result.end = copyPos(result.end); lines.skipSpaces(result.end, true, true); } } // If the node has any comments, their locations might contribute to // the true start/end positions of the node. if (node.comments) { node.comments.forEach(include); } return result; }; function expandLoc(parentLoc, childLoc) { if (parentLoc && childLoc) { if (comparePos(childLoc.start, parentLoc.start) < 0) { parentLoc.start = childLoc.start; } if (comparePos(parentLoc.end, childLoc.end) < 0) { parentLoc.end = childLoc.end; } } } util.fixFaultyLocations = function(node, lines) { var loc = node.loc; if (loc) { if (loc.start.line < 1) { loc.start.line = 1; } if (loc.end.line < 1) { loc.end.line = 1; } } if (node.type === "File") { // Babylon returns File nodes whose .loc.{start,end} do not include // leading or trailing whitespace. loc.start = lines.firstPos(); loc.end = lines.lastPos(); } fixForLoopHead(node, lines); fixTemplateLiteral(node, lines); if (loc && node.decorators) { // Expand the .loc of the node responsible for printing the decorators // (here, the decorated node) so that it includes node.decorators. node.decorators.forEach(function (decorator) { expandLoc(loc, decorator.loc); }); } else if (node.declaration && util.isExportDeclaration(node)) { // Nullify .loc information for the child declaration so that we never // try to reprint it without also reprinting the export declaration. node.declaration.loc = null; // Expand the .loc of the node responsible for printing the decorators // (here, the export declaration) so that it includes node.decorators. var decorators = node.declaration.decorators; if (decorators) { decorators.forEach(function (decorator) { expandLoc(loc, decorator.loc); }); } } else if ((n.MethodDefinition && n.MethodDefinition.check(node)) || (n.Property.check(node) && (node.method || node.shorthand))) { // If the node is a MethodDefinition or a .method or .shorthand // Property, then the location information stored in // node.value.loc is very likely untrustworthy (just the {body} // part of a method, or nothing in the case of shorthand // properties), so we null out that information to prevent // accidental reuse of bogus source code during reprinting. node.value.loc = null; if (n.FunctionExpression.check(node.value)) { // FunctionExpression method values should be anonymous, // because their .id fields are ignored anyway. node.value.id = null; } } else if (node.type === "ObjectTypeProperty") { var loc = node.loc; var end = loc && loc.end; if (end) { end = copyPos(end); if (lines.prevPos(end) && lines.charAt(end) === ",") { // Some parsers accidentally include trailing commas in the // .loc.end information for ObjectTypeProperty nodes. if ((end = lines.skipSpaces(end, true, true))) { loc.end = end; } } } } }; function fixForLoopHead(node, lines) { if (node.type !== "ForStatement") { return; } function fix(child) { var loc = child && child.loc; var start = loc && loc.start; var end = loc && copyPos(loc.end); while (start && end && comparePos(start, end) < 0) { lines.prevPos(end); if (lines.charAt(end) === ";") { // Update child.loc.end to *exclude* the ';' character. loc.end.line = end.line; loc.end.column = end.column; } else { break; } } } fix(node.init); fix(node.test); fix(node.update); } function fixTemplateLiteral(node, lines) { if (node.type !== "TemplateLiteral") { return; } if (node.quasis.length === 0) { // If there are no quasi elements, then there is nothing to fix. return; } // First we need to exclude the opening ` from the .loc of the first // quasi element, in case the parser accidentally decided to include it. var afterLeftBackTickPos = copyPos(node.loc.start); assert.strictEqual(lines.charAt(afterLeftBackTickPos), "`"); assert.ok(lines.nextPos(afterLeftBackTickPos)); var firstQuasi = node.quasis[0]; if (comparePos(firstQuasi.loc.start, afterLeftBackTickPos) < 0) { firstQuasi.loc.start = afterLeftBackTickPos; } // Next we need to exclude the closing ` from the .loc of the last quasi // element, in case the parser accidentally decided to include it. var rightBackTickPos = copyPos(node.loc.end); assert.ok(lines.prevPos(rightBackTickPos)); assert.strictEqual(lines.charAt(rightBackTickPos), "`"); var lastQuasi = node.quasis[node.quasis.length - 1]; if (comparePos(rightBackTickPos, lastQuasi.loc.end) < 0) { lastQuasi.loc.end = rightBackTickPos; } // Now we need to exclude ${ and } characters from the .loc's of all // quasi elements, since some parsers accidentally include them. node.expressions.forEach(function (expr, i) { // Rewind from expr.loc.start over any whitespace and the ${ that // precedes the expression. The position of the $ should be the same // as the .loc.end of the preceding quasi element, but some parsers // accidentally include the ${ in the .loc of the quasi element. var dollarCurlyPos = lines.skipSpaces(expr.loc.start, true, false); if (lines.prevPos(dollarCurlyPos) && lines.charAt(dollarCurlyPos) === "{" && lines.prevPos(dollarCurlyPos) && lines.charAt(dollarCurlyPos) === "$") { var quasiBefore = node.quasis[i]; if (comparePos(dollarCurlyPos, quasiBefore.loc.end) < 0) { quasiBefore.loc.end = dollarCurlyPos; } } // Likewise, some parsers accidentally include the } that follows // the expression in the .loc of the following quasi element. var rightCurlyPos = lines.skipSpaces(expr.loc.end, false, false); if (lines.charAt(rightCurlyPos) === "}") { assert.ok(lines.nextPos(rightCurlyPos)); // Now rightCurlyPos is technically the position just after the }. var quasiAfter = node.quasis[i + 1]; if (comparePos(quasiAfter.loc.start, rightCurlyPos) < 0) { quasiAfter.loc.start = rightCurlyPos; } } }); } util.isExportDeclaration = function (node) { if (node) switch (node.type) { case "ExportDeclaration": case "ExportDefaultDeclaration": case "ExportDefaultSpecifier": case "DeclareExportDeclaration": case "ExportNamedDeclaration": case "ExportAllDeclaration": return true; } return false; }; util.getParentExportDeclaration = function (path) { var parentNode = path.getParentNode(); if (path.getName() === "declaration" && util.isExportDeclaration(parentNode)) { return parentNode; } return null; }; util.isTrailingCommaEnabled = function(options, context) { var trailingComma = options.trailingComma; if (typeof trailingComma === "object") { return !!trailingComma[context]; } return !!trailingComma; }; recast-0.14.4/main.js000066400000000000000000000043471324434365200143610ustar00rootroot00000000000000var types = require("./lib/types"); var parse = require("./lib/parser").parse; var Printer = require("./lib/printer").Printer; function print(node, options) { return new Printer(options).print(node); } function prettyPrint(node, options) { return new Printer(options).printGenerically(node); } function run(transformer, options) { return runFile(process.argv[2], transformer, options); } function runFile(path, transformer, options) { require("fs").readFile(path, "utf-8", function(err, code) { if (err) { console.error(err); return; } runString(code, transformer, options); }); } function defaultWriteback(output) { process.stdout.write(output); } function runString(code, transformer, options) { var writeback = options && options.writeback || defaultWriteback; transformer(parse(code, options), function(node) { writeback(print(node, options).code); }); } Object.defineProperties(exports, { /** * Parse a string of code into an augmented syntax tree suitable for * arbitrary modification and reprinting. */ parse: { enumerable: true, value: parse }, /** * Traverse and potentially modify an abstract syntax tree using a * convenient visitor syntax: * * recast.visit(ast, { * names: [], * visitIdentifier: function(path) { * var node = path.value; * this.visitor.names.push(node.name); * this.traverse(path); * } * }); */ visit: { enumerable: true, value: types.visit }, /** * Reprint a modified syntax tree using as much of the original source * code as possible. */ print: { enumerable: true, value: print }, /** * Print without attempting to reuse any original source code. */ prettyPrint: { enumerable: false, value: prettyPrint }, /** * Customized version of require("ast-types"). */ types: { enumerable: false, value: types }, /** * Convenient command-line interface (see e.g. example/add-braces). */ run: { enumerable: false, value: run } }); recast-0.14.4/package-lock.json000066400000000000000000001450361324434365200163140ustar00rootroot00000000000000{ "name": "recast", "version": "0.14.4", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.40.tgz", "integrity": "sha512-eVXQSbu/RimU6OKcK2/gDJVTFcxXJI4sHbIqw2mhwMZeQ2as/8AhS9DGkEDoHMBBNJZ5B0US63lF56x+KDcxiA==", "dev": true, "requires": { "@babel/highlight": "7.0.0-beta.40" } }, "@babel/core": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.0-beta.40.tgz", "integrity": "sha512-jJMjn/EMg89xDGv7uq4BoFg+fHEchSeqNc9YUMnGuAi/FWKBkSsDbhh2y5euw4qaGOFD2jw1le0rvCu5gPUc6Q==", "dev": true, "requires": { "@babel/code-frame": "7.0.0-beta.40", "@babel/generator": "7.0.0-beta.40", "@babel/helpers": "7.0.0-beta.40", "@babel/template": "7.0.0-beta.40", "@babel/traverse": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40", "babylon": "7.0.0-beta.40", "convert-source-map": "1.5.1", "debug": "3.1.0", "json5": "0.5.1", "lodash": "4.17.5", "micromatch": "2.3.11", "resolve": "1.5.0", "source-map": "0.5.7" }, "dependencies": { "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true } } }, "@babel/generator": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.40.tgz", "integrity": "sha512-c91BQcXyTq/5aFV4afgOionxZS1dxWt8OghEx5Q52SKssdGRFSiMKnk9tGkev1pYULPJBqjSDZU2Pcuc58ffZw==", "dev": true, "requires": { "@babel/types": "7.0.0-beta.40", "jsesc": "2.5.1", "lodash": "4.17.5", "source-map": "0.5.7", "trim-right": "1.0.1" }, "dependencies": { "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true } } }, "@babel/helper-annotate-as-pure": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-beta.40.tgz", "integrity": "sha512-bJd92d70QTlcqCO9WiE8C94r7NwVzJx1V6Yz7rYi4IQ53P0jbh9jjKL2zl8YoU2S8M/KX1jpu+yIgXbx+LOruQ==", "dev": true, "requires": { "@babel/types": "7.0.0-beta.40" } }, "@babel/helper-call-delegate": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-beta.40.tgz", "integrity": "sha512-kfLlTpTayyCwj3/Rq4zDaK85GVPzRIR433QLhuNb0qjJfMQgLit2UEfBHUPPMRvKlb0FelrlXGTxXfsHLmfgzw==", "dev": true, "requires": { "@babel/helper-hoist-variables": "7.0.0-beta.40", "@babel/traverse": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40" } }, "@babel/helper-define-map": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.0.0-beta.40.tgz", "integrity": "sha512-hDg3sFSAxYQ/CSXzIBzGeNRD4yp89MkC3wkwvGBH80LXobL6csEdQpzCPhwpL0K8RNB07awRnck1OtPqjeCpgA==", "dev": true, "requires": { "@babel/helper-function-name": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40", "lodash": "4.17.5" } }, "@babel/helper-function-name": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.40.tgz", "integrity": "sha512-cK9BVLtOfisSISTTHXKGvBc2OBh65tjEk4PgXhsSnnH0i8RP2v+5RCxoSlh2y/i+l2fxQqKqv++Qo5RMiwmRCA==", "dev": true, "requires": { "@babel/helper-get-function-arity": "7.0.0-beta.40", "@babel/template": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40" } }, "@babel/helper-get-function-arity": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.40.tgz", "integrity": "sha512-MwquaPznI4cUoZEgHC/XGkddOXtqKqD4DvZDOyJK2LR9Qi6TbMbAhc6IaFoRX7CRTFCmtGeu8gdXW2dBotBBTA==", "dev": true, "requires": { "@babel/types": "7.0.0-beta.40" } }, "@babel/helper-hoist-variables": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-beta.40.tgz", "integrity": "sha512-ghnJxUUEmqK8mssF7Y7R5jNzF5xDu4hmWQ1aZghZtLNJSymmj3HrXCLl5m1dBYpq9gGk7TlZK8stIvIJsCGmTQ==", "dev": true, "requires": { "@babel/types": "7.0.0-beta.40" } }, "@babel/helper-module-imports": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.40.tgz", "integrity": "sha512-QFOskAKWbqJSBbGIl/Y1igJI4mW0A+wD5NFqsgDJj85KSvj/dHM4wNGIeqCi85nN9aMa4DgTBBrzUK4zSMsN2Q==", "dev": true, "requires": { "@babel/types": "7.0.0-beta.40", "lodash": "4.17.5" } }, "@babel/helper-module-transforms": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-beta.40.tgz", "integrity": "sha512-1H7cBk7kUWJpTepPH77TIRGwKILRGpu1yXmz1OjOruR6y2z0qfbp7ZzzZ3/xg6NlLDENLArEyO2+J0mO+VyQsg==", "dev": true, "requires": { "@babel/helper-module-imports": "7.0.0-beta.40", "@babel/helper-simple-access": "7.0.0-beta.40", "@babel/template": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40", "lodash": "4.17.5" } }, "@babel/helper-optimise-call-expression": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-beta.40.tgz", "integrity": "sha512-2f4ZKEkvdnKiTUA/Nhju+oEoRcyHcpf6lFuQI5cxbo1Toxqa8E9HBO5tiOWwlIwuak7RZPYSnxnrJQy/0d4YUw==", "dev": true, "requires": { "@babel/types": "7.0.0-beta.40" } }, "@babel/helper-regex": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0-beta.40.tgz", "integrity": "sha512-75zi8hZSdWLT7upmEyAemfN0hJ7522svItPGbIj4Pi2T/C5vbgNFXLy8o/iOCX4FzB+yVKhz0zWC3SzN9hyigA==", "dev": true, "requires": { "lodash": "4.17.5" } }, "@babel/helper-replace-supers": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-beta.40.tgz", "integrity": "sha512-Nu/5wpUV3rG35RzOq/upZlm61cP0lSAtmNkJLFfO5k2zOGCiHRczD1Y/xKqYOMl5f2iZmYw9fANi1jE4odMIIQ==", "dev": true, "requires": { "@babel/helper-optimise-call-expression": "7.0.0-beta.40", "@babel/template": "7.0.0-beta.40", "@babel/traverse": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40" } }, "@babel/helper-simple-access": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.40.tgz", "integrity": "sha512-hEKOIXUZFOiyqUPiGydGc+Jr0s8mVCFrD1OtAw2BDkXf1BaR+PxVEVcBAWcJVLOjqrr7oVZL9SENjR4B/Y+yEw==", "dev": true, "requires": { "@babel/template": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40", "lodash": "4.17.5" } }, "@babel/helpers": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0-beta.40.tgz", "integrity": "sha512-NK/mM/I16inThgXmKPxoqrg+N6OCLt+e9Zsmy8TJ93/zMx4Eddd679I231YwDP2J1Z12UgkfWCLbbvauU5TLlQ==", "dev": true, "requires": { "@babel/template": "7.0.0-beta.40", "@babel/traverse": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40" } }, "@babel/highlight": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.40.tgz", "integrity": "sha512-mOhhTrzieV6VO7odgzFGFapiwRK0ei8RZRhfzHhb6cpX3QM8XXuCLXWjN8qBB7JReDdUR80V3LFfFrGUYevhNg==", "dev": true, "requires": { "chalk": "2.3.1", "esutils": "2.0.2", "js-tokens": "3.0.2" } }, "@babel/plugin-transform-arrow-functions": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0-beta.40.tgz", "integrity": "sha512-B6wh62BErLWS3XInOUHhLcqBSK1QGdBph8E2K82EEFgJdQvphy30QXb0vwLUr8YU1efYyZXTsRA0JZ12jcm30Q==", "dev": true }, "@babel/plugin-transform-block-scoped-functions": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0-beta.40.tgz", "integrity": "sha512-GC64FqQfGJ5Wt3i0zSMcwRxmnZwgrx8fVLCeONNNm3BlK7Ui5Usuc7WubygM3bDq47UiHfeKo8ih54pr/POsFw==", "dev": true }, "@babel/plugin-transform-block-scoping": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-beta.40.tgz", "integrity": "sha512-8QpOK9lXdzrq1QIrP3Hfx/BmGPaCKjBORd2QSjdghPNNRlQFZmO2l3kb0I6yC7w75U1M5q26KvUbAcPrE68E4w==", "dev": true, "requires": { "lodash": "4.17.5" } }, "@babel/plugin-transform-classes": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.0.0-beta.40.tgz", "integrity": "sha512-yjViyoOYJtt2vLDai8jluxl9quOtq/Xq4GTjT9uzy+mOfUTE77dcJySMGkWHE52Mu3n0TSI09ENBFYykpvXXDw==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "7.0.0-beta.40", "@babel/helper-define-map": "7.0.0-beta.40", "@babel/helper-function-name": "7.0.0-beta.40", "@babel/helper-optimise-call-expression": "7.0.0-beta.40", "@babel/helper-replace-supers": "7.0.0-beta.40", "globals": "11.3.0" } }, "@babel/plugin-transform-computed-properties": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-beta.40.tgz", "integrity": "sha512-1VBpE+6YN4bj72MtbQoIwXZxoI5VfPLutQ5uhOx/tIrjf1KbLKsFR0epPPGx4nZ13u++lUR8CjUFUHGJ6RJirA==", "dev": true }, "@babel/plugin-transform-destructuring": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-beta.40.tgz", "integrity": "sha512-/FJq+WUAw4R5kg+2XWkmk0rDJqVs76rNNSIPpxeE0SiJvp8tvou7y8u0D1IhoO29ZgC+53jbdL+MkVN7mrH/iQ==", "dev": true }, "@babel/plugin-transform-duplicate-keys": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0-beta.40.tgz", "integrity": "sha512-rxEyRbU/iEGR99oBMoer5QeGWLMhT3Kq4a8B03DFLCBpGLv3XirpSGC/Ys1YhUKAmEio4jIcVVI8dRBbcVeyDw==", "dev": true }, "@babel/plugin-transform-for-of": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-beta.40.tgz", "integrity": "sha512-ArDbLAGMzI++G5Ut8HIbLvnAxZNOC5tFzMXiud51JJTHRaeFB7AwX+duY9x/Hu/KypISXjels3BYVYCV/EH+ow==", "dev": true }, "@babel/plugin-transform-function-name": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-beta.40.tgz", "integrity": "sha512-wvpswFciLQ2eAnHAs6/NSWymPg88LhHH87BljjXDxNnyGBzckip/iEa051Dz6lDumVUUjXLukw3D2fv5NBitVA==", "dev": true, "requires": { "@babel/helper-function-name": "7.0.0-beta.40" } }, "@babel/plugin-transform-instanceof": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-instanceof/-/plugin-transform-instanceof-7.0.0-beta.40.tgz", "integrity": "sha512-ORqBkINVkU09q/K2ALP1H6sSFkoipyFN+LX5cR81FKEFvyP4q74MLdGfDFi3wATExms9qFECnaIPz10XhzyRdw==", "dev": true }, "@babel/plugin-transform-literals": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-beta.40.tgz", "integrity": "sha512-p7VlTod2r7srx0uKVrKqMJR1f6iyvDAnlLdTEDGrLHpP9pXXvIc/bP8xZTxVPn+IziSFh6FvOzHXXLMtnRKnow==", "dev": true }, "@babel/plugin-transform-modules-amd": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0-beta.40.tgz", "integrity": "sha512-o/XTve9C+M9203MVxGRBOXNx4f9DZGiPLbwPPeDobdtw3NKHUCymFNbh9xxMJy0MPMEe8JldxbVwGy2f8DY/3w==", "dev": true, "requires": { "@babel/helper-module-transforms": "7.0.0-beta.40" } }, "@babel/plugin-transform-modules-commonjs": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-beta.40.tgz", "integrity": "sha512-1kRhaQP3K9kRiJhhLpP7J5NsMV+SiKWSsli6TUR6uxbuHHNAleRtlsZ76JgCRMaufBgPMLxq5pp7yibUhwTn8w==", "dev": true, "requires": { "@babel/helper-module-transforms": "7.0.0-beta.40", "@babel/helper-simple-access": "7.0.0-beta.40" } }, "@babel/plugin-transform-modules-systemjs": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.0.0-beta.40.tgz", "integrity": "sha512-q5IpFXNlzrK2ObpHkH5jzTCqRVzoNzmH8RoE8ZHQvLLiaIT346u8ynNv/BH1ltA49SPUPWyYpA+Z7OqCM4d3NA==", "dev": true, "requires": { "@babel/helper-hoist-variables": "7.0.0-beta.40" } }, "@babel/plugin-transform-modules-umd": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.0.0-beta.40.tgz", "integrity": "sha512-LHKqJFwo7x/CeEwjLyUE99SlG/kbTl8LS1DQ26fWctVnW5JuPt3hwYrggnmo1L/g/dal7EP2IL56+UezDMpJUQ==", "dev": true, "requires": { "@babel/helper-module-transforms": "7.0.0-beta.40" } }, "@babel/plugin-transform-object-super": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0-beta.40.tgz", "integrity": "sha512-a9kXy4amuvAz7eFuntXiyjg0eKXej1FH++xQg37ugh24zozD0cmfr3pvRbYOGlmbmOeZWJnlq+O6X8BSfLSycw==", "dev": true, "requires": { "@babel/helper-replace-supers": "7.0.0-beta.40" } }, "@babel/plugin-transform-parameters": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-beta.40.tgz", "integrity": "sha512-JShFDeKEzwwTB+pHcUuLdX9zPi98sRekvtdCEOt8UoF5pzW02k1XdsVOckp/PzcEdoGAgZiiI1PFkJZ+xanfPg==", "dev": true, "requires": { "@babel/helper-call-delegate": "7.0.0-beta.40", "@babel/helper-get-function-arity": "7.0.0-beta.40" } }, "@babel/plugin-transform-regenerator": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-beta.40.tgz", "integrity": "sha512-hFj52wAXbEpXwwfKsMol5Y967D3L8tz46Jin9n/gYPgcNWugvsw6d7g+HknBJ8FzaUESrDruFRkGPXgD+FyjvQ==", "dev": true, "requires": { "regenerator-transform": "0.12.3" } }, "@babel/plugin-transform-shorthand-properties": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0-beta.40.tgz", "integrity": "sha512-1leHn9ST0PKFHwH7klJqGA76YPoqs3cR5zeJK6YGZETeX89YiAVtR+5JTSGhfI/1RR0Vcg9Tl1LnPpf7LmYlng==", "dev": true }, "@babel/plugin-transform-spread": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-beta.40.tgz", "integrity": "sha512-RPrIpV+h8OqoqyMic7CNeM8TdSDk7ec+T6jM97vMb9XQQrRInAUWlwWvG6d36v72xobFtHoPA28VN/0aVsbQDg==", "dev": true }, "@babel/plugin-transform-sticky-regex": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.40.tgz", "integrity": "sha512-dJPUaV2D5SwSXypaDFRJd+LIhabeaWhZ3McmNo0COn+lBINJ9iL7mYuPxnqwhM/KoBNv+vYIoFFZzT/I27K6AQ==", "dev": true, "requires": { "@babel/helper-regex": "7.0.0-beta.40" } }, "@babel/plugin-transform-template-literals": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.40.tgz", "integrity": "sha512-ScGHntym1y5FweT751OJxGW4rydxdLA9BwkHfJ5o6RcCoq+LRubDeGu2HeuX4SMEvAw0MnZeSk8vw5TwIOzEIQ==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "7.0.0-beta.40" } }, "@babel/plugin-transform-typeof-symbol": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0-beta.40.tgz", "integrity": "sha512-y+mXC0tIlTZj04ZD9326grEIvFjI/IeLSIVVKMIf8nSodLDCgipuM6zXhxqXVvjcTrvvUKuxPrvPeSuht0eeMg==", "dev": true }, "@babel/plugin-transform-unicode-regex": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0-beta.40.tgz", "integrity": "sha512-+eProDq93qiYnXOy+LDSMoKF2lEQVQ+r6DF3ZZXJV5QJ3f2+vwpSqGIQy61sSkVMEaoNtYL/Jy+G8HrWFw9p3w==", "dev": true, "requires": { "@babel/helper-regex": "7.0.0-beta.40", "regexpu-core": "4.1.3" } }, "@babel/preset-es2015": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/preset-es2015/-/preset-es2015-7.0.0-beta.40.tgz", "integrity": "sha512-qt3UcEUbN2IwyN2RzMlOYnup98CpaXkd/6sQh3I0k1ezEQXYQDnTdc44fpWP+JTY07kDGT1VTePIy1GUpMQKHw==", "dev": true, "requires": { "@babel/plugin-transform-arrow-functions": "7.0.0-beta.40", "@babel/plugin-transform-block-scoped-functions": "7.0.0-beta.40", "@babel/plugin-transform-block-scoping": "7.0.0-beta.40", "@babel/plugin-transform-classes": "7.0.0-beta.40", "@babel/plugin-transform-computed-properties": "7.0.0-beta.40", "@babel/plugin-transform-destructuring": "7.0.0-beta.40", "@babel/plugin-transform-duplicate-keys": "7.0.0-beta.40", "@babel/plugin-transform-for-of": "7.0.0-beta.40", "@babel/plugin-transform-function-name": "7.0.0-beta.40", "@babel/plugin-transform-instanceof": "7.0.0-beta.40", "@babel/plugin-transform-literals": "7.0.0-beta.40", "@babel/plugin-transform-modules-amd": "7.0.0-beta.40", "@babel/plugin-transform-modules-commonjs": "7.0.0-beta.40", "@babel/plugin-transform-modules-systemjs": "7.0.0-beta.40", "@babel/plugin-transform-modules-umd": "7.0.0-beta.40", "@babel/plugin-transform-object-super": "7.0.0-beta.40", "@babel/plugin-transform-parameters": "7.0.0-beta.40", "@babel/plugin-transform-regenerator": "7.0.0-beta.40", "@babel/plugin-transform-shorthand-properties": "7.0.0-beta.40", "@babel/plugin-transform-spread": "7.0.0-beta.40", "@babel/plugin-transform-sticky-regex": "7.0.0-beta.40", "@babel/plugin-transform-template-literals": "7.0.0-beta.40", "@babel/plugin-transform-typeof-symbol": "7.0.0-beta.40", "@babel/plugin-transform-unicode-regex": "7.0.0-beta.40" } }, "@babel/template": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.40.tgz", "integrity": "sha512-RlQiVB7eL7fxsKN6JvnCCwEwEL28CBYalXSgWWULuFlEHjtMoXBqQanSie3bNyhrANJx67sb+Sd/vuGivoMwLQ==", "dev": true, "requires": { "@babel/code-frame": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40", "babylon": "7.0.0-beta.40", "lodash": "4.17.5" } }, "@babel/traverse": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.40.tgz", "integrity": "sha512-h96SQorjvdSuxQ6hHFIuAa3oxnad1TA5bU1Zz88+XqzwmM5QM0/k2D+heXGGy/76gT5ajl7xYLKGiPA/KTyVhQ==", "dev": true, "requires": { "@babel/code-frame": "7.0.0-beta.40", "@babel/generator": "7.0.0-beta.40", "@babel/helper-function-name": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40", "babylon": "7.0.0-beta.40", "debug": "3.1.0", "globals": "11.3.0", "invariant": "2.2.3", "lodash": "4.17.5" } }, "@babel/types": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.40.tgz", "integrity": "sha512-uXCGCzTgMZxcSUzutCPtZmXbVC+cvENgS2e0tRuhn+Y1hZnMb8IHP0Trq7Q2MB/eFmG5pKrAeTIUfQIe5kA4Tg==", "dev": true, "requires": { "esutils": "2.0.2", "lodash": "4.17.5", "to-fast-properties": "2.0.0" } }, "acorn": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==", "dev": true }, "ansi-styles": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", "dev": true, "requires": { "color-convert": "1.9.1" } }, "arr-diff": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { "arr-flatten": "1.1.0" } }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "array-unique": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", "dev": true }, "ast-types": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.2.tgz", "integrity": "sha512-aL+pcOQ+6dpWd0xrUe+Obo2CgdkFvsntkXEmzZKqEN4cR0PStF+1MBuc4V+YZsv4Q36luvyjG7F4lc+wH2bmag==" }, "babylon": { "version": "7.0.0-beta.40", "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.40.tgz", "integrity": "sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg==", "dev": true }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { "expand-range": "1.8.2", "preserve": "0.2.0", "repeat-element": "1.1.2" } }, "browser-stdout": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", "dev": true }, "chalk": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", "supports-color": "5.2.0" } }, "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "dev": true, "requires": { "color-name": "1.1.3" } }, "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "convert-source-map": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", "dev": true }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" } }, "diff": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "esprima": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" }, "esprima-fb": { "version": "15001.1001.0-dev-harmony-fb", "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz", "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk=", "dev": true }, "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, "expand-brackets": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { "is-posix-bracket": "0.1.1" } }, "expand-range": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { "fill-range": "2.2.3" } }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { "is-extglob": "1.0.0" } }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", "dev": true }, "fill-range": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", "dev": true, "requires": { "is-number": "2.1.0", "isobject": "2.1.0", "randomatic": "1.1.7", "repeat-element": "1.1.2", "repeat-string": "1.6.1" } }, "flow-parser": { "version": "0.66.0", "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.66.0.tgz", "integrity": "sha1-vlg/77ARkqpRZEFdMaYkGzVxiYM=", "dev": true }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, "for-own": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { "for-in": "1.0.2" } }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", "inherits": "2.0.3", "minimatch": "3.0.4", "once": "1.4.0", "path-is-absolute": "1.0.1" } }, "glob-base": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, "requires": { "glob-parent": "2.0.0", "is-glob": "2.0.1" } }, "glob-parent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { "is-glob": "2.0.1" } }, "globals": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.3.0.tgz", "integrity": "sha512-kkpcKNlmQan9Z5ZmgqKH/SMbSmjxQ7QjyNqfXVc8VJcoBV2UEg+sxQD15GQofGRh2hfpwUb70VC31DR7Rq5Hdw==", "dev": true }, "growl": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", "dev": true }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" } }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "invariant": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.3.tgz", "integrity": "sha512-7Z5PPegwDTyjbaeCnV0efcyS6vdKAU51kpEmS7QFib3P4822l8ICYyMn7qvJnc+WzLoDsuI9gPMKbJ8pCu8XtA==", "dev": true, "requires": { "loose-envify": "1.3.1" } }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-dotfile": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", "dev": true }, "is-equal-shallow": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, "requires": { "is-primitive": "2.0.0" } }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", "dev": true }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { "is-extglob": "1.0.0" } }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { "kind-of": "3.2.2" } }, "is-posix-bracket": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", "dev": true }, "is-primitive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", "dev": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isobject": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, "requires": { "isarray": "1.0.0" } }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "dev": true }, "jsesc": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", "dev": true }, "json5": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { "is-buffer": "1.1.6" } }, "lodash": { "version": "4.17.5", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", "dev": true }, "loose-envify": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "dev": true, "requires": { "js-tokens": "3.0.2" } }, "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { "arr-diff": "2.0.0", "array-unique": "0.2.1", "braces": "1.8.5", "expand-brackets": "0.1.5", "extglob": "0.3.2", "filename-regex": "2.0.1", "is-extglob": "1.0.0", "is-glob": "2.0.1", "kind-of": "3.2.2", "normalize-path": "2.1.1", "object.omit": "2.0.1", "parse-glob": "3.0.4", "regex-cache": "0.4.4" } }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "1.1.11" } }, "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "minipass": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.1.tgz", "integrity": "sha512-u1aUllxPJUI07cOqzR7reGmQxmCqlH88uIIsf6XZFEWgw7gXKpJdR+5R9Y3KEDmWYkdIz9wXZs3C0jOPxejk/Q==", "dev": true, "requires": { "yallist": "3.0.2" } }, "minizlib": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "dev": true, "requires": { "minipass": "2.2.1" } }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" } }, "mocha": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.1.tgz", "integrity": "sha512-SpwyojlnE/WRBNGtvJSNfllfm5PqEDFxcWluSIgLeSBJtXG4DmoX2NNAeEA7rP5kK+79VgtVq8nG6HskaL1ykg==", "dev": true, "requires": { "browser-stdout": "1.3.0", "commander": "2.11.0", "debug": "3.1.0", "diff": "3.3.1", "escape-string-regexp": "1.0.5", "glob": "7.1.2", "growl": "1.10.3", "he": "1.1.1", "mkdirp": "0.5.1", "supports-color": "4.4.0" }, "dependencies": { "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", "dev": true }, "supports-color": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", "dev": true, "requires": { "has-flag": "2.0.0" } } } }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { "remove-trailing-separator": "1.1.0" } }, "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "requires": { "for-own": "0.1.5", "is-extendable": "0.1.1" } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1.0.2" } }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "requires": { "glob-base": "0.3.0", "is-dotfile": "1.0.3", "is-extglob": "1.0.0", "is-glob": "2.0.1" } }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-parse": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, "preserve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" }, "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", "dev": true, "requires": { "is-number": "3.0.0", "kind-of": "4.0.0" }, "dependencies": { "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { "kind-of": "3.2.2" }, "dependencies": { "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { "is-buffer": "1.1.6" } } } }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { "is-buffer": "1.1.6" } } } }, "regenerate": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", "dev": true }, "regenerate-unicode-properties": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-5.1.3.tgz", "integrity": "sha512-Yjy6t7jFQczDhYE+WVm7pg6gWYE258q4sUkk9qDErwXJIqx7jU9jGrMFHutJK/SRfcg7MEkXjGaYiVlOZyev/A==", "dev": true, "requires": { "regenerate": "1.3.3" } }, "regenerator-transform": { "version": "0.12.3", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.12.3.tgz", "integrity": "sha512-y2uxO/6u+tVmtEDIKo+tLCtI0GcbQr0OreosKgCd7HP4VypGjtTrw79DezuwT+W5QX0YWuvpeBOgumrepwM1kA==", "dev": true, "requires": { "private": "0.1.8" } }, "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "requires": { "is-equal-shallow": "0.1.3" } }, "regexpu-core": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.1.3.tgz", "integrity": "sha512-mB+njEzO7oezA57IbQxxd6fVPOeWKDmnGvJ485CwmfNchjHe5jWwqKepapmzUEj41yxIAqOg+C4LbXuJlkiO8A==", "dev": true, "requires": { "regenerate": "1.3.3", "regenerate-unicode-properties": "5.1.3", "regjsgen": "0.3.0", "regjsparser": "0.2.1", "unicode-match-property-ecmascript": "1.0.3", "unicode-match-property-value-ecmascript": "1.0.1" } }, "regjsgen": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.3.0.tgz", "integrity": "sha1-DuSj6SdkMM2iXx54nqbBW4ewy0M=", "dev": true }, "regjsparser": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.2.1.tgz", "integrity": "sha1-w3h1U/rwTndcMCEC7zRtmVAA7Bw=", "dev": true, "requires": { "jsesc": "0.5.0" }, "dependencies": { "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true } } }, "reify": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/reify/-/reify-0.14.2.tgz", "integrity": "sha512-gJA67pCidAROSmr4/8SQuMQXtZwtSvoVIDR6KjoWxnO4LSD8iYNjMcRrHuQQ/c8cXYaoCpEQh0rO9CNNu1z5lA==", "dev": true, "requires": { "acorn": "5.4.1", "minizlib": "1.1.0", "semver": "5.5.0" } }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, "repeat-element": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", "dev": true }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, "resolve": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", "dev": true, "requires": { "path-parse": "1.0.5" } }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "supports-color": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "dev": true, "requires": { "has-flag": "3.0.0" } }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.3.tgz", "integrity": "sha512-iG/2t0F2LAU8aZYPkX5gi7ebukHnr3sWFESpb+zPQeeaQwOkfoO6ZW17YX7MdRPNG9pCy+tjzGill+Ah0Em0HA==", "dev": true }, "unicode-match-property-ecmascript": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.3.tgz", "integrity": "sha512-nFcaBFcr08UQNF15ZgI5ISh3yUnQm7SJRRxwYrL5VYX46pS+6Q7TCTv4zbK+j6/l7rQt0mMiTL2zpmeygny6rA==", "dev": true, "requires": { "unicode-canonical-property-names-ecmascript": "1.0.3", "unicode-property-aliases-ecmascript": "1.0.3" } }, "unicode-match-property-value-ecmascript": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.1.tgz", "integrity": "sha512-lM8B0FDZQh9yYGgiabRQcyWicB27VLOolSBRIxsO7FeQPtg+79Oe7sC8Mzr8BObDs+G9CeYmC/shHo6OggNEog==", "dev": true }, "unicode-property-aliases-ecmascript": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.3.tgz", "integrity": "sha512-TdDmDOTxEf2ad1g3ZBpM6cqKIb2nJpVlz1Q++casDryKz18tpeMBhSng9hjC1CTQCkOV9Rw2knlSB6iRo7ad1w==", "dev": true }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "yallist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", "dev": true } } } recast-0.14.4/package.json000066400000000000000000000022111324434365200153510ustar00rootroot00000000000000{ "author": "Ben Newman ", "name": "recast", "description": "JavaScript syntax tree transformer, nondestructive pretty-printer, and automatic source map generator", "keywords": [ "ast", "rewriting", "refactoring", "codegen", "syntax", "transformation", "parsing", "pretty-printing" ], "version": "0.14.4", "homepage": "http://github.com/benjamn/recast", "repository": { "type": "git", "url": "git://github.com/benjamn/recast.git" }, "license": "MIT", "main": "main.js", "scripts": { "test": "test/run.sh", "debug": "test/run.sh --inspect-brk" }, "browser": { "fs": false }, "dependencies": { "ast-types": "0.11.2", "esprima": "~4.0.0", "private": "~0.1.5", "source-map": "~0.6.1" }, "devDependencies": { "@babel/core": "^7.0.0-beta.40", "@babel/preset-es2015": "^7.0.0-beta.40", "babylon": "^7.0.0-beta.40", "esprima-fb": "^15001.1001.0-dev-harmony-fb", "flow-parser": "^0.66.0", "glob": "^7.1.2", "mocha": "~5.0.0", "reify": "^0.14.1", "semver": "^5.3.0" }, "engines": { "node": ">= 4" } } recast-0.14.4/parsers/000077500000000000000000000000001324434365200145465ustar00rootroot00000000000000recast-0.14.4/parsers/_babylon_options.js000066400000000000000000000022451324434365200204470ustar00rootroot00000000000000const getOption = require("../lib/util.js").getOption; module.exports = function (options) { // The goal here is to tolerate as much syntax as possible, since Recast // is not in the business of forbidding anything. If you want your // parser to be more restrictive for some reason, you can always pass // your own parser object to recast.parse. return { sourceType: getOption(options, "sourceType", "module"), strictMode: getOption(options, "strictMode", false), allowImportExportEverywhere: true, allowReturnOutsideFunction: true, startLine: 1, tokens: getOption(options, "tokens", true), plugins: [ "asyncGenerators", "bigInt", "classPrivateMethods", "classPrivateProperties", "classProperties", "decorators", "doExpressions", "dynamicImport", "exportDefaultFrom", "exportExtensions", "exportNamespaceFrom", "functionBind", "functionSent", "importMeta", "nullishCoalescingOperator", "numericSeparator", "objectRestSpread", "optionalCatchBinding", "optionalChaining", "pipelineOperator", "throwExpressions", ] }; }; recast-0.14.4/parsers/acorn.js000066400000000000000000000015311324434365200162060ustar00rootroot00000000000000"use strict"; // This module is suitable for passing as options.parser when calling // recast.parse to process JavaScript code with Acorn: // // const ast = recast.parse(source, { // parser: require("recast/parsers/acorn") // }); // const getOption = require("../lib/util.js").getOption; exports.parse = function parse(source, options) { const comments = []; const tokens = []; const ast = require("acorn").parse(source, { allowHashBang: true, allowImportExportEverywhere: true, allowReturnOutsideFunction: true, ecmaVersion: getOption(options, "ecmaVersion", 8), sourceType: getOption(options, "sourceType", "module"), locations: true, onComment: comments, onToken: tokens, }); if (! ast.comments) { ast.comments = comments; } if (! ast.tokens) { ast.tokens = tokens; } return ast; }; recast-0.14.4/parsers/babylon.js000066400000000000000000000006661324434365200165420ustar00rootroot00000000000000"use strict"; // This module is suitable for passing as options.parser when calling // recast.parse to process JavaScript code with Babel: // // const ast = recast.parse(source, { // parser: require("recast/parsers/babylon") // }); // exports.parse = function (source, options) { options = require("./_babylon_options.js")(options); options.plugins.push("jsx", "flow"); return require("babylon").parse(source, options); }; recast-0.14.4/parsers/esprima.js000066400000000000000000000012671324434365200165520ustar00rootroot00000000000000"use strict"; // This module is suitable for passing as options.parser when calling // recast.parse to process ECMAScript code with Esprima: // // const ast = recast.parse(source, { // parser: require("recast/parsers/esprima") // }); // const getOption = require("../lib/util.js").getOption; exports.parse = function (source, options) { const comments = []; const ast = require("esprima").parse(source, { loc: true, locations: true, comment: true, onComment: comments, tolerant: getOption(options, "tolerant", true), tokens: getOption(options, "tokens", true) }); if (! Array.isArray(ast.comments)) { ast.comments = comments; } return ast; }; recast-0.14.4/parsers/flow.js000066400000000000000000000006471324434365200160620ustar00rootroot00000000000000"use strict"; // This module is suitable for passing as options.parser when calling // recast.parse to process Flow code: // // const ast = recast.parse(source, { // parser: require("recast/parsers/flow") // }); // exports.parse = function parse(source, options) { options = require("./_babylon_options.js")(options); options.plugins.push("jsx", "flow"); return require("babylon").parse(source, options); }; recast-0.14.4/parsers/typescript.js000066400000000000000000000006621324434365200173160ustar00rootroot00000000000000"use strict"; // This module is suitable for passing as options.parser when calling // recast.parse to process TypeScript code: // // const ast = recast.parse(source, { // parser: require("recast/parsers/typescript") // }); // exports.parse = function parse(source, options) { options = require("./_babylon_options.js")(options); options.plugins.push("typescript"); return require("babylon").parse(source, options); }; recast-0.14.4/test/000077500000000000000000000000001324434365200140465ustar00rootroot00000000000000recast-0.14.4/test/babylon.js000066400000000000000000000241761324434365200160440ustar00rootroot00000000000000var assert = require("assert"); var recast = require("../main.js"); var n = recast.types.namedTypes; var b = recast.types.builders; var eol = require("os").EOL; describe("Babel", function () { var babelTransform = require("@babel/core").transform; var babelPresetES2015 = require("@babel/preset-es2015"); var parseOptions = {}; try { parseOptions.parser = require("../parsers/babylon"); } catch (e) { if (require("semver").gte(process.version, "4.0.0")) { throw e; } return; } it("basic printing", function () { function check(lines) { var code = lines.join(eol); var ast = recast.parse(code, parseOptions); var output = recast.prettyPrint(ast, { tabWidth: 2 }).code; assert.strictEqual(output, code); } check([ '"use strict";', // Directive, DirectiveLiteral in Program '"use strict";', // Directive, DirectiveLiteral in BlockStatement 'function a() {', ' "use strict";', '}', ]); check([ 'function a() {', ' "use strict";', ' b;', '}', ]); check([ '() => {', ' "use strict";', '};', ]); check([ '() => {', ' "use strict";', ' b;', '};', ]); check([ 'var a = function a() {', ' "use strict";', '};', ]); check([ 'var a = function a() {', ' "use strict";', ' b;', '};', ]); check([ 'null;', // NullLiteral '"asdf";', // StringLiteral '/a/;', // RegExpLiteral 'false;', // BooleanLiteral '1;', // NumericLiteral 'const find2 = () => {};', // typeParameters ]); check([ 'class A {', ' a;', ' a = 1;', ' [a] = 1;', // computed in ClassProperty '}', ]); check([ 'function f(x: empty): T {', // EmptyTypeAnnotation ' return x;', '}', ]); check([ 'var a: {| numVal: number |};', // exact 'const bar1 = (x: number): string => {};', 'declare module.exports: { foo: string }', // DeclareModuleExports 'type Maybe = _Maybe;', // ExistentialTypeParam // 'declare class B { foo: () => number }', // interesting failure ref https://github.com/babel/babel/pull/3663 'declare function foo(): number;', 'var A: (a: B) => void;', ]); check([ 'async function* a() {', // async in Function ' for await (let x of y) {', // ForAwaitStatement ' x;', ' }', '}', ]); check([ 'class C2<+T, -U> {', // variance ' +p: T = e;', '}', ]); check([ 'type T = { -p: T };', 'type T = { +[k: K]: V };', ]); check([ 'class A {', ' static async *z(a, b): number {', // ClassMethod ' b;', ' }', '', ' static get y(): number {', ' return 1;', ' }', '', ' static set x(a): void {', ' return 1;', ' }', '', ' static async *[d](a, b): number {', ' return 1;', ' }', '}', ]); check([ '({', ' async *a() {', // ObjectMethod ' b;', ' },', '', ' get a() {', ' return 1;', ' },', '', ' set a(b) {', ' return 1;', ' },', '', ' async *[d](c) {', ' return 1;', ' },', '', ' a: 3,', ' [a]: 3,', ' 1: 3,', ' "1": 3,', ' 1() {},', ' "1"() {}', '});', ]); }); it("babel 6: should not wrap IIFE when reusing nodes", function () { var code = [ '(function(...c) {', ' c();', '})();', ].join(eol); var ast = recast.parse(code, parseOptions); var output = recast.print(ast, { tabWidth: 2 }).code; assert.strictEqual(output, code); }); it("should not disappear when surrounding code changes", function () { var code = [ 'import foo from "foo";', 'import React from "react";', '', '@component', '@callExpression({foo: "bar"})', 'class DebugPanel extends React.Component {', ' render() {', ' return (', '
test
', ' );', ' }', '}', '', 'export default DebugPanel;', ].join(eol); var ast = recast.parse(code, parseOptions); assert.strictEqual(recast.print(ast).code, code); var root = new recast.types.NodePath(ast); var reactImportPath = root.get("program", "body", 1); n.ImportDeclaration.assert(reactImportPath.value); // Remove the second import statement. reactImportPath.prune(); var reprinted = recast.print(ast).code; assert.ok(reprinted.match(/@component/)); assert.ok(reprinted.match(/@callExpression/)); assert.strictEqual( reprinted, code.split(eol).filter(function (line) { return ! line.match(/^import React from/); }).join(eol) ); }); it("should not disappear when an import is added and `export` is used inline", function () { var code = [ 'import foo from "foo";', 'import React from "react";', '', '@component', '@callExpression({foo: "bar"})', '@callExpressionMultiLine({', ' foo: "bar",', '})', 'export class DebugPanel extends React.Component {', ' render() {', ' return (', '
test
', ' );', ' }', '}', ].join(eol); var ast = recast.parse(code, parseOptions); assert.strictEqual(recast.print(ast).code, code); var root = new recast.types.NodePath(ast); var body = root.get("program", "body"); // add a new import statement body.unshift(b.importDeclaration([ b.importDefaultSpecifier(b.identifier('x')), ], b.literal('x'))); var reprinted = recast.print(ast).code; assert.ok(reprinted.match(/@component/)); assert.ok(reprinted.match(/@callExpression/)); assert.strictEqual( reprinted, ['import x from "x";'].concat(code.split(eol)).join(eol) ); }); it("should not disappear when an import is added and `export default` is used inline", function () { var code = [ 'import foo from "foo";', 'import React from "react";', '', '@component', '@callExpression({foo: "bar"})', '@callExpressionMultiLine({', ' foo: "bar",', '})', 'export default class DebugPanel extends React.Component {', ' render() {', ' return (', '
test
', ' );', ' }', '}', ].join(eol); var ast = recast.parse(code, parseOptions); assert.strictEqual(recast.print(ast).code, code); var root = new recast.types.NodePath(ast); var body = root.get("program", "body"); // add a new import statement body.unshift(b.importDeclaration([ b.importDefaultSpecifier(b.identifier('x')), ], b.literal('x'))); var reprinted = recast.print(ast).code; assert.ok(reprinted.match(/@component/)); assert.ok(reprinted.match(/@callExpression/)); assert.strictEqual( reprinted, ['import x from "x";'].concat(code.split(eol)).join(eol) ); }); it("should not print delimiters with type annotations", function () { var code = [ 'type X = {', ' a: number,', ' b: number,', '};', ].join('\n'); var ast = recast.parse(code, parseOptions) var root = new recast.types.NodePath(ast); root.get('program', 'body', 0, 'right', 'properties', 0).prune(); assert.strictEqual( recast.print(ast).code, "type X = { b: number };" ); }); function parseExpression(code) { return recast.parse(code, parseOptions).program.body[0].expression; } it("should parenthesize ** operator arguments when lower precedence", function () { var ast = recast.parse('a ** b;', parseOptions); ast.program.body[0].expression.left = parseExpression('x + y'); ast.program.body[0].expression.right = parseExpression('x || y'); assert.strictEqual( recast.print(ast).code, '(x + y) ** (x || y);' ); }); it("should parenthesize ** operator arguments as needed when same precedence", function () { var ast = recast.parse('a ** b;', parseOptions); ast.program.body[0].expression.left = parseExpression('x * y'); ast.program.body[0].expression.right = parseExpression('x / y'); assert.strictEqual( recast.print(ast).code, 'x * y ** (x / y);' ); }); it("should be able to replace top-level statements with leading empty lines", function () { var code = [ '', 'if (test) {', ' console.log(test);', '}', ].join('\n'); var ast = recast.parse(code, parseOptions); var replacement = b.expressionStatement( b.callExpression( b.identifier('fn'), [b.identifier('test'), b.literal(true)] ) ); ast.program.body[0] = replacement; assert.strictEqual( recast.print(ast).code, '\nfn(test, true);' ); recast.types.visit(ast, { visitIfStatement: function(path) { path.replace(replacement); return false; } }); assert.strictEqual( recast.print(ast).code, '\nfn(test, true);' ); }); it("should parse and print dynamic import(...)", function () { var code = 'wait(import("oyez"));'; var ast = recast.parse(code, parseOptions); assert.strictEqual( recast.prettyPrint(ast).code, code ); }); it("tolerates circular references", function () { var code = "function foo(bar = true) {}"; var ast = recast.parse(code, { parser: { parse: function (source) { return babelTransform(source, { code: false, ast: true, sourceMap: false, presets: [babelPresetES2015] }).ast; } } }); }); it("prints numbers in bases other than 10 without converting them", function() { var code = [ 'let decimal = 6;', 'let hex = 0xf00d;', 'let binary = 0b1010;', 'let octal = 0o744;' ].join(eol); var ast = recast.parse(code, parseOptions); var output = recast.print(ast, { tabWidth: 2 }).code; assert.strictEqual(output, code); }); }); recast-0.14.4/test/comments.js000066400000000000000000000503101324434365200162300ustar00rootroot00000000000000"use strict"; var recast = require("../main"); var n = recast.types.namedTypes; var b = recast.types.builders; var Printer = require("../lib/printer").Printer; var fromString = require("../lib/lines").fromString; var assert = require("assert"); var printer = new Printer; var eol = require("os").EOL; var annotated = [ "function dup(/* string */ s,", " /* int */ n) /* string */", "{", " // Use an array full of holes.", " return Array(n + /*", " * off-by-*/ 1).join(s);", "}" ]; describe("comments", function() { ["../parsers/acorn", "../parsers/babylon", "../parsers/esprima", "../parsers/flow", "../parsers/typescript", ].forEach(runTestsForParser); }); function runTestsForParser(parserId) { const parserName = parserId.split("/").pop(); const parser = require(parserId); function pit(message, callback) { it("[" + parserName + "] " + message, callback); } pit("attachment and reprinting", function() { var code = annotated.join(eol); var ast = recast.parse(code, { parser }); var dup = ast.program.body[0]; n.FunctionDeclaration.assert(dup); assert.strictEqual(dup.id.name, "dup"); // More of a basic sanity test than a comment test. assert.strictEqual(recast.print(ast).code, code); assert.strictEqual(recast.print(ast.program).code, code); assert.strictEqual(recast.print(dup).code, code); assert.strictEqual( recast.print(dup.params[0]).code, "/* string */ s" ); assert.strictEqual( recast.print(dup.params[1]).code, "/* int */ n" ); assert.strictEqual( recast.print(dup.body).code, ["/* string */"].concat(annotated.slice(2)).join(eol) ); var retStmt = dup.body.body[0]; n.ReturnStatement.assert(retStmt); var indented = annotated.slice(3, 6).join(eol); var flush = fromString(indented).indent(-2); assert.strictEqual( recast.print(retStmt).code, flush.toString() ); var join = retStmt.argument; n.CallExpression.assert(join); var one = join.callee.object.arguments[0].right; n.Literal.assert(one); assert.strictEqual(one.value, 1); assert.strictEqual(recast.print(one).code, [ "/*", " * off-by-*/ 1" ].join(eol)); }); var trailing = [ "Foo.prototype = {", "// Copyright (c) 2013 Ben Newman ", "", " /**", " * Leading comment.", " */", " constructor: Foo, // Important for instanceof", " // to work in all browsers.", ' bar: "baz", // Just in case we need it.', " qux: { // Here is an object literal.", " zxcv: 42", " // Put more properties here when you think of them.", " } // There was an object literal...", " // ... and here I am continuing this comment.", "", "};" ]; var trailingExpected = [ "Foo.prototype = {", " // Copyright (c) 2013 Ben Newman ", "", " /**", " * Leading comment.", " */", " // Important for instanceof", " // to work in all browsers.", " constructor: Foo,", "", " // Just in case we need it.", ' bar: "baz",', "", " // There was an object literal...", " // ... and here I am continuing this comment.", " qux: {", " // Here is an object literal.", " // Put more properties here when you think of them.", " zxcv: 42,", "", " asdf: 43", " },", "", ' extra: "property"', "};" ]; pit("TrailingComments", function() { var code = trailing.join(eol); var ast = recast.parse(code, { parser }); assert.strictEqual(recast.print(ast).code, code); // Drop all original source information to force reprinting. recast.visit(ast, { visitNode: function(path) { this.traverse(path); path.value.original = null; } }); var assign = ast.program.body[0].expression; n.AssignmentExpression.assert(assign); const esprimaInfo = { Property: n.Property, propBuilder(key, value) { return b.property("init", key, value); }, literalBuilder(value) { return b.literal(value); } }; const babylonInfo = { Property: n.ObjectProperty, propBuilder(key, value) { return b.objectProperty(key, value); }, literalBuilder(value) { if (typeof value === "string") { return b.stringLiteral(value); } if (typeof value === "number") { return b.numericLiteral(value); } throw new Error("unexpected literal: " + value); } }; const info = { acorn: esprimaInfo, babylon: babylonInfo, esprima: esprimaInfo, flow: babylonInfo, typescript: babylonInfo }[parserName]; var props = assign.right.properties; info.Property.arrayOf().assert(props); props.push(info.propBuilder( b.identifier("extra"), info.literalBuilder("property") )); var quxVal = props[2].value; n.ObjectExpression.assert(quxVal); quxVal.properties.push(info.propBuilder( b.identifier("asdf"), info.literalBuilder(43) )); var actual = recast.print(ast, { tabWidth: 2 }).code; var expected = trailingExpected.join(eol); // Check semantic equivalence: recast.types.astNodesAreEquivalent.assert( ast, recast.parse(actual, { parser }) ); assert.strictEqual(actual, expected); }); var bodyTrailing = [ "module.exports = {};", "/**", " * Trailing comment.", " */" ]; var bodyTrailingExpected = [ "module.exports = {};", "/**", " * Trailing comment.", " */" ]; pit("BodyTrailingComments", function() { var code = bodyTrailing.join(eol); var ast = recast.parse(code, { parser }); // Drop all original source information to force reprinting. recast.visit(ast, { visitNode: function(path) { this.traverse(path); path.value.original = null; } }); var actual = recast.print(ast, { tabWidth: 2 }).code; var expected = bodyTrailingExpected.join(eol); assert.strictEqual(actual, expected); }); var paramTrailing = [ "function foo(bar, baz /* = null */) {", " assert.strictEqual(baz, null);", "}" ]; var paramTrailingExpected = [ "function foo(zxcv, bar, baz /* = null */) {", " assert.strictEqual(baz, null);", "}" ]; pit("ParamTrailingComments", function() { var code = paramTrailing.join(eol); var ast = recast.parse(code, { parser }); var func = ast.program.body[0]; n.FunctionDeclaration.assert(func); func.params.unshift(b.identifier("zxcv")); var actual = recast.print(ast, { tabWidth: 2 }).code; var expected = paramTrailingExpected.join(eol); assert.strictEqual(actual, expected); }); var statementTrailing = [ "if (true) {", " f();", " // trailing 1", " /* trailing 2 */", " // trailing 3", " /* trailing 4 */", "}" ]; var statementTrailingExpected = [ "if (true) {", " e();", " f();", " // trailing 1", " /* trailing 2 */", " // trailing 3", " /* trailing 4 */", "}" ]; pit("StatementTrailingComments", function() { var code = statementTrailing.join(eol); var ast = recast.parse(code, { parser }); var block = ast.program.body[0].consequent; n.BlockStatement.assert(block); block.body.unshift(b.expressionStatement( b.callExpression(b.identifier("e"), []))); var actual = recast.print(ast, { tabWidth: 2 }).code; var expected = statementTrailingExpected.join(eol); assert.strictEqual(actual, expected); }); var protoAssign = [ "A.prototype.foo = function() {", " return this.bar();", "}", // Lack of semicolon screws up location info. "", "// Comment about the bar method.", "A.prototype.bar = function() {", " return this.foo();", "}" ]; pit("ProtoAssignComment", function() { var code = protoAssign.join(eol); var ast = recast.parse(code, { parser }); var foo = ast.program.body[0]; var bar = ast.program.body[1]; n.ExpressionStatement.assert(foo); n.ExpressionStatement.assert(bar); assert.strictEqual(foo.expression.left.property.name, "foo"); assert.strictEqual(bar.expression.left.property.name, "bar"); assert.ok(!foo.comments); assert.ok(bar.comments); assert.strictEqual(bar.comments.length, 1); var barComment = bar.comments[0]; assert.strictEqual(barComment.leading, true); assert.strictEqual(barComment.trailing, false); assert.strictEqual( barComment.value, " Comment about the bar method." ); }); var conciseMethods = [ "var obj = {", " a(/*before*/ param) {},", " b(param /*after*/) {},", " c(param) /*body*/ {}", "};", ]; pit("should correctly attach to concise methods", function() { var code = conciseMethods.join(eol); var ast = recast.parse(code, { parser }); var objExpr = ast.program.body[0].declarations[0].init; n.ObjectExpression.assert(objExpr); var a = objExpr.properties[0]; n.Identifier.assert(a.key); assert.strictEqual(a.key.name, "a"); var aComments = (a.value || a).params[0].comments; assert.strictEqual(aComments.length, 1); var aComment = aComments[0]; assert.strictEqual(aComment.leading, true); assert.strictEqual(aComment.trailing, false); assert.ok(aComment.type.endsWith("Block")); assert.strictEqual(aComment.value, "before"); assert.strictEqual( recast.print(a).code, "a(/*before*/ param) {}" ); var b = objExpr.properties[1]; n.Identifier.assert(b.key); assert.strictEqual(b.key.name, "b"); var bComments = (b.value || b).params[0].comments; assert.strictEqual(bComments.length, 1); var bComment = bComments[0]; assert.strictEqual(bComment.leading, false); assert.strictEqual(bComment.trailing, true); assert.ok(bComment.type.endsWith("Block")); assert.strictEqual(bComment.value, "after"); assert.strictEqual( recast.print(b).code, "b(param /*after*/) {}" ); var c = objExpr.properties[2]; n.Identifier.assert(c.key); assert.strictEqual(c.key.name, "c"); var cComments = (c.value || c).body.comments; assert.strictEqual(cComments.length, 1); var cComment = cComments[0]; assert.strictEqual(cComment.leading, true); assert.strictEqual(cComment.trailing, false); assert.ok(cComment.type.endsWith("Block")); assert.strictEqual(cComment.value, "body"); assert.strictEqual( recast.print(c).code, "c(param) /*body*/ {}" ); }); pit("should attach comments as configurable", function() { // Given var simpleCommentedCode = [ "// A comment", "var obj = {", "};", ]; var code = simpleCommentedCode.join(eol); var ast = recast.parse(code, { parser }); // When Object.defineProperty(ast.program, 'comments', { value: undefined, enumerable: false }); // Then // An exception will be thrown if `comments` aren't configurable. }); pit("should be reprinted when modified", function() { var code = [ "foo;", "// bar", "bar;" ].join(eol); var ast = recast.parse(code, { parser }); var comments = ast.program.body[1].comments; assert.strictEqual(comments.length, 1); var comment = comments[0]; assert.ok(comment.type.endsWith("Line")); assert.strictEqual(comment.value, " bar"); comment.value = " barbara"; assert.strictEqual(recast.print(ast).code, [ "foo;", "// barbara", "bar;" ].join(eol)); ast.program.body[0].comments = comments; delete ast.program.body[1].comments; assert.strictEqual(recast.print(ast).code, [ "// barbara", "foo;", "bar;" ].join(eol)); ast.program.body[0] = b.blockStatement([ ast.program.body[0] ]); assert.strictEqual(recast.print(ast).code, [ "{", " // barbara", " foo;", "}", "", "bar;" ].join(eol)); var comment = ast.program.body[0].body[0].comments[0]; comment.type = "Block"; assert.strictEqual(recast.print(ast).code, [ "{", " /* barbara*/", " foo;", "}", "", "bar;" ].join(eol)); comment.value += "\n * babar\n "; assert.strictEqual(recast.print(ast).code, [ "{", " /* barbara", " * babar", " */", " foo;", "}", "", "bar;" ].join(eol)); ast.program.body[1].comments = [comment]; assert.strictEqual(recast.print(ast).code, [ "{", " /* barbara", " * babar", " */", " foo;", "}", "", "/* barbara", " * babar", " */", "bar;" ].join(eol)); delete ast.program.body[0].body[0].comments; ast.program.comments = [b.line(" program comment")]; assert.strictEqual(recast.print(ast).code, [ "// program comment", "{", " foo;", "}", "", "/* barbara", " * babar", " */", "bar;" ].join(eol)); ast.program.body.push( ast.program.body.shift() ); assert.strictEqual(recast.print(ast).code, [ "// program comment", "/* barbara", " * babar", " */", "bar;", "", "{", " foo;", "}" ].join(eol)); recast.visit(ast, { visitNode: function(path) { delete path.value.comments; this.traverse(path); } }); assert.strictEqual(recast.print(ast).code, [ "bar;", "", "{", " foo;", "}" ].join(eol)); ast.program.body[1] = ast.program.body[1].body[0]; assert.strictEqual(recast.print(ast).code, [ "bar;", "foo;" ].join(eol)); }); pit("should preserve stray non-comment syntax", function() { var code = [ "[", " foo", " , /* comma */", " /* hole */", " , /* comma */", " bar", "]" ].join(eol); var ast = recast.parse(code, { parser }); assert.strictEqual(recast.print(ast).code, code); var elems = ast.program.body[0].expression.elements; elems[0].comments.push(b.line(" line comment", true, false)); assert.strictEqual(recast.print(ast).code, [ "[", " // line comment", " foo /* comma */", " /* hole */", " ,", " , /* comma */", " bar", "]" ].join(eol)); }); pit("should be reprinted even if dangling", function() { const code = "[/*dangling*/] // array literal"; const ast = recast.parse(code, { parser }); const array = ast.program.body[0].expression; const stmt = ast.program.body[0]; let danglingComment; let trailingComment; function handleComment(comment) { if (comment.trailing) { trailingComment = comment; } else if (! comment.leading) { danglingComment = comment; } } (stmt.comments || []).forEach(handleComment); (stmt.expression.comments || []).forEach(handleComment); assert.strictEqual(danglingComment.leading, false); assert.strictEqual(danglingComment.trailing, false); assert.strictEqual(trailingComment.leading, false); assert.strictEqual(trailingComment.trailing, true); danglingComment.value = " neither leading nor trailing "; assert.strictEqual(recast.print(ast).code, [ "[/* neither leading nor trailing */] // array literal" ].join(eol)); trailingComment.value = " trailing"; assert.strictEqual(recast.print(ast).code, [ "[/* neither leading nor trailing */] // trailing" ].join(eol)); // Unfortuantely altering the elements of the array leads to // reprinting which blows away the dangling comment. array.elements.push(b.literal(1)); assert.strictEqual( recast.print(ast).code, "[1] // trailing" ); }); pit("should attach to program.body[0] instead of program", function() { var code = [ "// comment 1", "var a;", "// comment 2", "var b;", "if (true) {", " // comment 3", " var c;", "}" ].join('\n'); var ast = recast.parse(code, { parser }); assert.ok(!ast.program.comments); var aDecl = ast.program.body[0]; n.VariableDeclaration.assert(aDecl); assert.strictEqual(aDecl.comments.length, 1); assert.strictEqual(aDecl.comments[0].leading, true); assert.strictEqual(aDecl.comments[0].trailing, false); assert.strictEqual(aDecl.comments[0].value, " comment 1"); var bDecl = ast.program.body[1]; n.VariableDeclaration.assert(bDecl); assert.strictEqual(bDecl.comments.length, 1); assert.strictEqual(bDecl.comments[0].leading, true); assert.strictEqual(bDecl.comments[0].trailing, false); assert.strictEqual(bDecl.comments[0].value, " comment 2"); var cDecl = ast.program.body[2].consequent.body[0]; n.VariableDeclaration.assert(cDecl); assert.strictEqual(cDecl.comments.length, 1); assert.strictEqual(cDecl.comments[0].leading, true); assert.strictEqual(cDecl.comments[0].trailing, false); assert.strictEqual(cDecl.comments[0].value, " comment 3"); }); pit("should not collapse multi line function definitions", function() { var code = [ "var obj = {", " a(", " /*before*/ param", " ) /*after*/ {", " },", "};", ].join(eol); var ast = recast.parse(code, { parser }); var printer = new Printer({ tabWidth: 2 }); assert.strictEqual( printer.print(ast).code, code ); }); pit("should be pretty-printable in illegal positions", function() { var code = [ "var sum = function /*anonymous*/(/*...args*/) /*int*/ {", " // TODO", "};" ].join(eol); var ast = recast.parse(code, { parser }); var funExp = ast.program.body[0].declarations[0].init; n.FunctionExpression.assert(funExp); funExp.original = null; var comments = funExp.body.comments; assert.strictEqual(comments.length, 4); funExp.id = comments.shift(); funExp.params.push(comments.shift()); funExp.body.body.push(comments.pop()); assert.strictEqual( recast.print(ast).code, code ); }); pit("should preserve correctness when a return expression has a comment", function () { var code = [ "function f() {", " return 3;", "}" ].join(eol); var ast = recast.parse(code, { parser }); ast.program.body[0].body.body[0].argument.comments = [b.line('Foo')]; assert.strictEqual(recast.print(ast).code, [ "function f() {", " return (", " //Foo", " 3", " );", "}" ].join(eol)); }); pit("should wrap in parens when the return expression has nested leftmost comment", function () { var code = [ "function f() {", " return 1 + 2;", "}" ].join(eol); var ast = recast.parse(code, { parser }); ast.program.body[0].body.body[0].argument.left.comments = [b.line('Foo')]; assert.strictEqual(recast.print(ast).code, [ "function f() {", " return (", " //Foo", " 1 + 2", " );", "}" ].join(eol)); }); pit("should not wrap in parens when the return expression has an interior comment", function () { var code = [ "function f() {", " return 1 + 2;", "}" ].join(eol); var ast = recast.parse(code, { parser }); ast.program.body[0].body.body[0].argument.right.comments = [b.line('Foo')]; assert.strictEqual(recast.print(ast).code, [ "function f() {", " return 1 + //Foo", " 2;", "}" ].join(eol)); }); pit("should not reformat a return statement that is not modified", function () { var code = [ "function f() {", " return {", " a: 1,", " b: 2,", " };", "}" ].join(eol); var ast = recast.parse(code, { parser }); assert.strictEqual(recast.print(ast).code, code); }); pit("should correctly handle a removing the argument from a return", function () { var code = [ "function f() {", " return 'foo';", "}" ].join(eol); var ast = recast.parse(code, { parser }); ast.program.body[0].body.body[0].argument = null; assert.strictEqual(recast.print(ast).code, [ "function f() {", " return;", "}" ].join(eol)); }); } recast-0.14.4/test/data/000077500000000000000000000000001324434365200147575ustar00rootroot00000000000000recast-0.14.4/test/data/backbone.js000066400000000000000000001647111324434365200170730ustar00rootroot00000000000000// Backbone.js 1.0.0 // (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc. // (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org (function(){ // Initial Setup // ------------- // Save a reference to the global object (`window` in the browser, `exports` // on the server). var root = this; // Save the previous value of the `Backbone` variable, so that it can be // restored later on, if `noConflict` is used. var previousBackbone = root.Backbone; // Create local references to array methods we'll want to use later. var array = []; var push = array.push; var slice = array.slice; var splice = array.splice; // The top-level namespace. All public Backbone classes and modules will // be attached to this. Exported for both the browser and the server. var Backbone; if (typeof exports !== 'undefined') { Backbone = exports; } else { Backbone = root.Backbone = {}; } // Current version of the library. Keep in sync with `package.json`. Backbone.VERSION = '1.0.0'; // Require Underscore, if we're on the server, and it's not already present. var _ = root._; if (!_ && (typeof require !== 'undefined')) _ = require('underscore'); // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns // the `$` variable. Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$; // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable // to its previous owner. Returns a reference to this Backbone object. Backbone.noConflict = function() { root.Backbone = previousBackbone; return this; }; // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and // set a `X-Http-Method-Override` header. Backbone.emulateHTTP = false; // Turn on `emulateJSON` to support legacy servers that can't deal with direct // `application/json` requests ... will encode the body as // `application/x-www-form-urlencoded` instead and will send the model in a // form param named `model`. Backbone.emulateJSON = false; // Backbone.Events // --------------- // A module that can be mixed in to *any object* in order to provide it with // custom events. You may bind with `on` or remove with `off` callback // functions to an event; `trigger`-ing an event fires all callbacks in // succession. // // var object = {}; // _.extend(object, Backbone.Events); // object.on('expand', function(){ alert('expanded'); }); // object.trigger('expand'); // var Events = Backbone.Events = { // Bind an event to a `callback` function. Passing `"all"` will bind // the callback to all events fired. on: function(name, callback, context) { if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; this._events || (this._events = {}); var events = this._events[name] || (this._events[name] = []); events.push({callback: callback, context: context, ctx: context || this}); return this; }, // Bind an event to only be triggered a single time. After the first time // the callback is invoked, it will be removed. once: function(name, callback, context) { if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; var self = this; var once = _.once(function() { self.off(name, once); callback.apply(this, arguments); }); once._callback = callback; return this.on(name, once, context); }, // Remove one or many callbacks. If `context` is null, removes all // callbacks with that function. If `callback` is null, removes all // callbacks for the event. If `name` is null, removes all bound // callbacks for all events. off: function(name, callback, context) { var retain, ev, events, names, i, l, j, k; if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; if (!name && !callback && !context) { this._events = {}; return this; } names = name ? [name] : _.keys(this._events); for (i = 0, l = names.length; i < l; i++) { name = names[i]; if (events = this._events[name]) { this._events[name] = retain = []; if (callback || context) { for (j = 0, k = events.length; j < k; j++) { ev = events[j]; if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || (context && context !== ev.context)) { retain.push(ev); } } } if (!retain.length) delete this._events[name]; } } return this; }, // Trigger one or many events, firing all bound callbacks. Callbacks are // passed the same arguments as `trigger` is, apart from the event name // (unless you're listening on `"all"`, which will cause your callback to // receive the true name of the event as the first argument). trigger: function(name) { if (!this._events) return this; var args = slice.call(arguments, 1); if (!eventsApi(this, 'trigger', name, args)) return this; var events = this._events[name]; var allEvents = this._events.all; if (events) triggerEvents(events, args); if (allEvents) triggerEvents(allEvents, arguments); return this; }, // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. stopListening: function(obj, name, callback) { var listeners = this._listeners; if (!listeners) return this; var deleteListener = !name && !callback; if (typeof name === 'object') callback = this; if (obj) (listeners = {})[obj._listenerId] = obj; for (var id in listeners) { listeners[id].off(name, callback, this); if (deleteListener) delete this._listeners[id]; } return this; } }; // Regular expression used to split event strings. var eventSplitter = /\s+/; // Implement fancy features of the Events API such as multiple event // names `"change blur"` and jQuery-style event maps `{change: action}` // in terms of the existing API. var eventsApi = function(obj, action, name, rest) { if (!name) return true; // Handle event maps. if (typeof name === 'object') { for (var key in name) { obj[action].apply(obj, [key, name[key]].concat(rest)); } return false; } // Handle space separated event names. if (eventSplitter.test(name)) { var names = name.split(eventSplitter); for (var i = 0, l = names.length; i < l; i++) { obj[action].apply(obj, [names[i]].concat(rest)); } return false; } return true; }; // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // Backbone events have 3 arguments). var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); } }; var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; // Inversion-of-control versions of `on` and `once`. Tell *this* object to // listen to an event in another object ... keeping track of what it's // listening to. _.each(listenMethods, function(implementation, method) { Events[method] = function(obj, name, callback) { var listeners = this._listeners || (this._listeners = {}); var id = obj._listenerId || (obj._listenerId = _.uniqueId('l')); listeners[id] = obj; if (typeof name === 'object') callback = this; obj[implementation](name, callback, this); return this; }; }); // Aliases for backwards compatibility. Events.bind = Events.on; Events.unbind = Events.off; // Allow the `Backbone` object to serve as a global event bus, for folks who // want global "pubsub" in a convenient place. _.extend(Backbone, Events); // Backbone.Model // -------------- // Backbone **Models** are the basic data object in the framework -- // frequently representing a row in a table in a database on your server. // A discrete chunk of data and a bunch of useful, related methods for // performing computations and transformations on that data. // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. var Model = Backbone.Model = function(attributes, options) { var defaults; var attrs = attributes || {}; options || (options = {}); this.cid = _.uniqueId('c'); this.attributes = {}; if (options.collection) this.collection = options.collection; if (options.parse) attrs = this.parse(attrs, options) || {}; options._attrs || (options._attrs = attrs); if (defaults = _.result(this, 'defaults')) { attrs = _.defaults({}, attrs, defaults); } this.set(attrs, options); this.changed = {}; this.initialize.apply(this, arguments); }; // Attach all inheritable methods to the Model prototype. _.extend(Model.prototype, Events, { // A hash of attributes whose current and previous value differ. changed: null, // The value returned during the last failed validation. validationError: null, // The default name for the JSON `id` attribute is `"id"`. MongoDB and // CouchDB users may want to set this to `"_id"`. idAttribute: 'id', // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // Return a copy of the model's `attributes` object. toJSON: function(options) { return _.clone(this.attributes); }, // Proxy `Backbone.sync` by default -- but override this if you need // custom syncing semantics for *this* particular model. sync: function() { return Backbone.sync.apply(this, arguments); }, // Get the value of an attribute. get: function(attr) { return this.attributes[attr]; }, // Get the HTML-escaped value of an attribute. escape: function(attr) { return _.escape(this.get(attr)); }, // Returns `true` if the attribute contains a value that is not null // or undefined. has: function(attr) { return this.get(attr) != null; }, // Set a hash of model attributes on the object, firing `"change"`. This is // the core primitive operation of a model, updating the data and notifying // anyone who needs to know about the change in state. The heart of the beast. set: function(key, val, options) { var attr, attrs, unset, changes, silent, changing, prev, current; if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments. if (typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options || (options = {}); // Run validation. if (!this._validate(attrs, options)) return false; // Extract attributes and options. unset = options.unset; silent = options.silent; changes = []; changing = this._changing; this._changing = true; if (!changing) { this._previousAttributes = _.clone(this.attributes); this.changed = {}; } current = this.attributes, prev = this._previousAttributes; // Check for changes of `id`. if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; // For each `set` attribute, update or delete the current value. for (attr in attrs) { val = attrs[attr]; if (!_.isEqual(current[attr], val)) changes.push(attr); if (!_.isEqual(prev[attr], val)) { this.changed[attr] = val; } else { delete this.changed[attr]; } unset ? delete current[attr] : current[attr] = val; } // Trigger all relevant attribute changes. if (!silent) { if (changes.length) this._pending = true; for (var i = 0, l = changes.length; i < l; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } // You might be wondering why there's a `while` loop here. Changes can // be recursively nested within `"change"` events. if (changing) return this; if (!silent) { while (this._pending) { this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; }, // Remove an attribute from the model, firing `"change"`. `unset` is a noop // if the attribute doesn't exist. unset: function(attr, options) { return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, // Clear all attributes on the model, firing `"change"`. clear: function(options) { var attrs = {}; for (var key in this.attributes) attrs[key] = void 0; return this.set(attrs, _.extend({}, options, {unset: true})); }, // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. hasChanged: function(attr) { if (attr == null) return !_.isEmpty(this.changed); return _.has(this.changed, attr); }, // Return an object containing all the attributes that have changed, or // false if there are no changed attributes. Useful for determining what // parts of a view need to be updated and/or what attributes need to be // persisted to the server. Unset attributes will be set to undefined. // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. changedAttributes: function(diff) { if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; var val, changed = false; var old = this._changing ? this._previousAttributes : this.attributes; for (var attr in diff) { if (_.isEqual(old[attr], (val = diff[attr]))) continue; (changed || (changed = {}))[attr] = val; } return changed; }, // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. previous: function(attr) { if (attr == null || !this._previousAttributes) return null; return this._previousAttributes[attr]; }, // Get all of the attributes of the model at the time of the previous // `"change"` event. previousAttributes: function() { return _.clone(this._previousAttributes); }, // Fetch the model from the server. If the server's representation of the // model differs from its current attributes, they will be overridden, // triggering a `"change"` event. fetch: function(options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; options.success = function(resp) { if (!model.set(model.parse(resp, options), options)) return false; if (success) success(model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. save: function(key, val, options) { var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments. if (key == null || typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options = _.extend({validate: true}, options); // If we're not waiting and attributes exist, save acts as // `set(attr).save(null, opts)` with validation. Otherwise, check if // the model will be valid when the attributes, if any, are set. if (attrs && !options.wait) { if (!this.set(attrs, options)) return false; } else { if (!this._validate(attrs, options)) return false; } // Set temporary attributes if `{wait: true}`. if (attrs && options.wait) { this.attributes = _.extend({}, attributes, attrs); } // After a successful server-side save, the client is (optionally) // updated with the server-side state. if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; options.success = function(resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes; var serverAttrs = model.parse(resp, options); if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) { return false; } if (success) success(model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); if (method === 'patch') options.attrs = attrs; xhr = this.sync(method, this, options); // Restore attributes. if (attrs && options.wait) this.attributes = attributes; return xhr; }, // Destroy this model on the server if it was already persisted. // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. destroy: function(options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; var destroy = function() { model.trigger('destroy', model, model.collection, options); }; options.success = function(resp) { if (options.wait || model.isNew()) destroy(); if (success) success(model, resp, options); if (!model.isNew()) model.trigger('sync', model, resp, options); }; if (this.isNew()) { options.success(); return false; } wrapError(this, options); var xhr = this.sync('delete', this, options); if (!options.wait) destroy(); return xhr; }, // Default URL for the model's representation on the server -- if you're // using Backbone's restful methods, override this to change the endpoint // that will be called. url: function() { var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); if (this.isNew()) return base; return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id); }, // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. parse: function(resp, options) { return resp; }, // Create a new model with identical attributes to this one. clone: function() { return new this.constructor(this.attributes); }, // A model is new if it has never been saved to the server, and lacks an id. isNew: function() { return this.id == null; }, // Check if the model is currently in a valid state. isValid: function(options) { return this._validate({}, _.extend(options || {}, { validate: true })); }, // Run validation against the next complete set of model attributes, // returning `true` if all is well. Otherwise, fire an `"invalid"` event. _validate: function(attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; if (!error) return true; this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error})); return false; } }); // Underscore methods that we want to implement on the Model. var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; // Mix in each Underscore method as a proxy to `Model#attributes`. _.each(modelMethods, function(method) { Model.prototype[method] = function() { var args = slice.call(arguments); args.unshift(this.attributes); return _[method].apply(_, args); }; }); // Backbone.Collection // ------------------- // If models tend to represent a single row of data, a Backbone Collection is // more analagous to a table full of data ... or a small slice or page of that // table, or a collection of rows that belong together for a particular reason // -- all of the messages in this particular folder, all of the documents // belonging to this particular author, and so on. Collections maintain // indexes of their models, both in order, and for lookup by `id`. // Create a new **Collection**, perhaps to contain a specific type of `model`. // If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. var Collection = Backbone.Collection = function(models, options) { options || (options = {}); if (options.model) this.model = options.model; if (options.comparator !== void 0) this.comparator = options.comparator; this._reset(); this.initialize.apply(this, arguments); if (models) this.reset(models, _.extend({silent: true}, options)); }; // Default options for `Collection#set`. var setOptions = {add: true, remove: true, merge: true}; var addOptions = {add: true, remove: false}; // Define the Collection's inheritable methods. _.extend(Collection.prototype, Events, { // The default model for a collection is just a **Backbone.Model**. // This should be overridden in most cases. model: Model, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // The JSON representation of a Collection is an array of the // models' attributes. toJSON: function(options) { return this.map(function(model){ return model.toJSON(options); }); }, // Proxy `Backbone.sync` by default. sync: function() { return Backbone.sync.apply(this, arguments); }, // Add a model, or list of models to the set. add: function(models, options) { return this.set(models, _.extend({merge: false}, options, addOptions)); }, // Remove a model, or a list of models from the set. remove: function(models, options) { models = _.isArray(models) ? models.slice() : [models]; options || (options = {}); var i, l, index, model; for (i = 0, l = models.length; i < l; i++) { model = this.get(models[i]); if (!model) continue; delete this._byId[model.id]; delete this._byId[model.cid]; index = this.indexOf(model); this.models.splice(index, 1); this.length--; if (!options.silent) { options.index = index; model.trigger('remove', model, this, options); } this._removeReference(model); } return this; }, // Update a collection by `set`-ing a new list of models, adding new ones, // removing models that are no longer present, and merging models that // already exist in the collection, as necessary. Similar to **Model#set**, // the core operation for updating the data contained by the collection. set: function(models, options) { options = _.defaults({}, options, setOptions); if (options.parse) models = this.parse(models, options); if (!_.isArray(models)) models = models ? [models] : []; var i, l, model, attrs, existing, sort; var at = options.at; var sortable = this.comparator && (at == null) && options.sort !== false; var sortAttr = _.isString(this.comparator) ? this.comparator : null; var toAdd = [], toRemove = [], modelMap = {}; var add = options.add, merge = options.merge, remove = options.remove; var order = !sortable && add && remove ? [] : false; // Turn bare objects into model references, and prevent invalid models // from being added. for (i = 0, l = models.length; i < l; i++) { if (!(model = this._prepareModel(attrs = models[i], options))) continue; // If a duplicate is found, prevent it from being added and // optionally merge it into the existing model. if (existing = this.get(model)) { if (remove) modelMap[existing.cid] = true; if (merge) { attrs = attrs === model ? model.attributes : options._attrs; existing.set(attrs, options); if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; } // This is a new model, push it to the `toAdd` list. } else if (add) { toAdd.push(model); // Listen to added models' events, and index models for lookup by // `id` and by `cid`. model.on('all', this._onModelEvent, this); this._byId[model.cid] = model; if (model.id != null) this._byId[model.id] = model; } if (order) order.push(existing || model); delete options._attrs; } // Remove nonexistent models if appropriate. if (remove) { for (i = 0, l = this.length; i < l; ++i) { if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); } if (toRemove.length) this.remove(toRemove, options); } // See if sorting is needed, update `length` and splice in new models. if (toAdd.length || (order && order.length)) { if (sortable) sort = true; this.length += toAdd.length; if (at != null) { splice.apply(this.models, [at, 0].concat(toAdd)); } else { if (order) this.models.length = 0; push.apply(this.models, order || toAdd); } } // Silently sort the collection if appropriate. if (sort) this.sort({silent: true}); if (options.silent) return this; // Trigger `add` events. for (i = 0, l = toAdd.length; i < l; i++) { (model = toAdd[i]).trigger('add', model, this, options); } // Trigger `sort` if the collection was sorted. if (sort || (order && order.length)) this.trigger('sort', this, options); return this; }, // When you have more items than you want to add or remove individually, // you can reset the entire set with a new list of models, without firing // any granular `add` or `remove` events. Fires `reset` when finished. // Useful for bulk operations and optimizations. reset: function(models, options) { options || (options = {}); for (var i = 0, l = this.models.length; i < l; i++) { this._removeReference(this.models[i]); } options.previousModels = this.models; this._reset(); this.add(models, _.extend({silent: true}, options)); if (!options.silent) this.trigger('reset', this, options); return this; }, // Add a model to the end of the collection. push: function(model, options) { model = this._prepareModel(model, options); this.add(model, _.extend({at: this.length}, options)); return model; }, // Remove a model from the end of the collection. pop: function(options) { var model = this.at(this.length - 1); this.remove(model, options); return model; }, // Add a model to the beginning of the collection. unshift: function(model, options) { model = this._prepareModel(model, options); this.add(model, _.extend({at: 0}, options)); return model; }, // Remove a model from the beginning of the collection. shift: function(options) { var model = this.at(0); this.remove(model, options); return model; }, // Slice out a sub-array of models from the collection. slice: function() { return slice.apply(this.models, arguments); }, // Get a model from the set by id. get: function(obj) { if (obj == null) return void 0; return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj]; }, // Get the model at the given index. at: function(index) { return this.models[index]; }, // Return models with matching attributes. Useful for simple cases of // `filter`. where: function(attrs, first) { if (_.isEmpty(attrs)) return first ? void 0 : []; return this[first ? 'find' : 'filter'](function(model) { for (var key in attrs) { if (attrs[key] !== model.get(key)) return false; } return true; }); }, // Return the first model with matching attributes. Useful for simple cases // of `find`. findWhere: function(attrs) { return this.where(attrs, true); }, // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. sort: function(options) { if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); options || (options = {}); // Run sort based on type of `comparator`. if (_.isString(this.comparator) || this.comparator.length === 1) { this.models = this.sortBy(this.comparator, this); } else { this.models.sort(_.bind(this.comparator, this)); } if (!options.silent) this.trigger('sort', this, options); return this; }, // Figure out the smallest index at which a model should be inserted so as // to maintain order. sortedIndex: function(model, value, context) { value || (value = this.comparator); var iterator = _.isFunction(value) ? value : function(model) { return model.get(value); }; return _.sortedIndex(this.models, model, iterator, context); }, // Pluck an attribute from each model in the collection. pluck: function(attr) { return _.invoke(this.models, 'get', attr); }, // Fetch the default set of models for this collection, resetting the // collection when they arrive. If `reset: true` is passed, the response // data will be passed through the `reset` method instead of `set`. fetch: function(options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var success = options.success; var collection = this; options.success = function(resp) { var method = options.reset ? 'reset' : 'set'; collection[method](resp, options); if (success) success(collection, resp, options); collection.trigger('sync', collection, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, // Create a new instance of a model in this collection. Add the model to the // collection immediately, unless `wait: true` is passed, in which case we // wait for the server to agree. create: function(model, options) { options = options ? _.clone(options) : {}; if (!(model = this._prepareModel(model, options))) return false; if (!options.wait) this.add(model, options); var collection = this; var success = options.success; options.success = function(model, resp, options) { if (options.wait) collection.add(model, options); if (success) success(model, resp, options); }; model.save(null, options); return model; }, // **parse** converts a response into a list of models to be added to the // collection. The default implementation is just to pass it through. parse: function(resp, options) { return resp; }, // Create a new collection with an identical list of models as this one. clone: function() { return new this.constructor(this.models); }, // Private method to reset all internal state. Called when the collection // is first initialized or reset. _reset: function() { this.length = 0; this.models = []; this._byId = {}; }, // Prepare a hash of attributes (or other model) to be added to this // collection. _prepareModel: function(attrs, options) { if (attrs instanceof Model) { if (!attrs.collection) attrs.collection = this; return attrs; } options || (options = {}); options.collection = this; var model = new this.model(attrs, options); if (!model.validationError) return model; this.trigger('invalid', this, attrs, options); return false; }, // Internal method to sever a model's ties to a collection. _removeReference: function(model) { if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); }, // Internal method called every time a model in the set fires an event. // Sets need to update their indexes when models change ids. All other // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. _onModelEvent: function(event, model, collection, options) { if ((event === 'add' || event === 'remove') && collection !== this) return; if (event === 'destroy') this.remove(model, options); if (model && event === 'change:' + model.idAttribute) { delete this._byId[model.previous(model.idAttribute)]; if (model.id != null) this._byId[model.id] = model; } this.trigger.apply(this, arguments); } }); // Underscore methods that we want to implement on the Collection. // 90% of the core usefulness of Backbone Collections is actually implemented // right here: var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle', 'lastIndexOf', 'isEmpty', 'chain']; // Mix in each Underscore method as a proxy to `Collection#models`. _.each(methods, function(method) { Collection.prototype[method] = function() { var args = slice.call(arguments); args.unshift(this.models); return _[method].apply(_, args); }; }); // Underscore methods that take a property name as an argument. var attributeMethods = ['groupBy', 'countBy', 'sortBy']; // Use attributes instead of properties. _.each(attributeMethods, function(method) { Collection.prototype[method] = function(value, context) { var iterator = _.isFunction(value) ? value : function(model) { return model.get(value); }; return _[method](this.models, iterator, context); }; }); // Backbone.View // ------------- // Backbone Views are almost more convention than they are actual code. A View // is simply a JavaScript object that represents a logical chunk of UI in the // DOM. This might be a single item, an entire list, a sidebar or panel, or // even the surrounding frame which wraps your whole app. Defining a chunk of // UI as a **View** allows you to define your DOM events declaratively, without // having to worry about render order ... and makes it easy for the view to // react to specific changes in the state of your models. // Options with special meaning *(e.g. model, collection, id, className)* are // attached directly to the view. See `viewOptions` for an exhaustive // list. // Creating a Backbone.View creates its initial element outside of the DOM, // if an existing element is not provided... var View = Backbone.View = function(options) { this.cid = _.uniqueId('view'); options || (options = {}); _.extend(this, _.pick(options, viewOptions)); this._ensureElement(); this.initialize.apply(this, arguments); this.delegateEvents(); }; // Cached regex to split keys for `delegate`. var delegateEventSplitter = /^(\S+)\s*(.*)$/; // List of view options to be merged as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; // Set up all inheritable **Backbone.View** properties and methods. _.extend(View.prototype, Events, { // The default `tagName` of a View's element is `"div"`. tagName: 'div', // jQuery delegate for element lookup, scoped to DOM elements within the // current view. This should be prefered to global lookups where possible. $: function(selector) { return this.$el.find(selector); }, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // **render** is the core function that your view should override, in order // to populate its element (`this.el`), with the appropriate HTML. The // convention is for **render** to always return `this`. render: function() { return this; }, // Remove this view by taking the element out of the DOM, and removing any // applicable Backbone.Events listeners. remove: function() { this.$el.remove(); this.stopListening(); return this; }, // Change the view's element (`this.el` property), including event // re-delegation. setElement: function(element, delegate) { if (this.$el) this.undelegateEvents(); this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); this.el = this.$el[0]; if (delegate !== false) this.delegateEvents(); return this; }, // Set callbacks, where `this.events` is a hash of // // *{"event selector": "callback"}* // // { // 'mousedown .title': 'edit', // 'click .button': 'save' // 'click .open': function(e) { ... } // } // // pairs. Callbacks will be bound to the view, with `this` set properly. // Uses event delegation for efficiency. // Omitting the selector binds the event to `this.el`. // This only works for delegate-able events: not `focus`, `blur`, and // not `change`, `submit`, and `reset` in Internet Explorer. delegateEvents: function(events) { if (!(events || (events = _.result(this, 'events')))) return this; this.undelegateEvents(); for (var key in events) { var method = events[key]; if (!_.isFunction(method)) method = this[events[key]]; if (!method) continue; var match = key.match(delegateEventSplitter); var eventName = match[1], selector = match[2]; method = _.bind(method, this); eventName += '.delegateEvents' + this.cid; if (selector === '') { this.$el.on(eventName, method); } else { this.$el.on(eventName, selector, method); } } return this; }, // Clears all callbacks previously bound to the view with `delegateEvents`. // You usually don't need to use this, but may wish to if you have multiple // Backbone views attached to the same DOM element. undelegateEvents: function() { this.$el.off('.delegateEvents' + this.cid); return this; }, // Ensure that the View has a DOM element to render into. // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` properties. _ensureElement: function() { if (!this.el) { var attrs = _.extend({}, _.result(this, 'attributes')); if (this.id) attrs.id = _.result(this, 'id'); if (this.className) attrs['class'] = _.result(this, 'className'); var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); this.setElement($el, false); } else { this.setElement(_.result(this, 'el'), false); } } }); // Backbone.sync // ------------- // Override this function to change the manner in which Backbone persists // models to the server. You will be passed the type of request, and the // model in question. By default, makes a RESTful Ajax request // to the model's `url()`. Some possible customizations could be: // // * Use `setTimeout` to batch rapid-fire updates into a single request. // * Send up the models as XML instead of JSON. // * Persist models via WebSockets instead of Ajax. // // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests // as `POST`, with a `_method` parameter containing the true HTTP method, // as well as all requests with the body as `application/x-www-form-urlencoded` // instead of `application/json` with the model in a param named `model`. // Useful when interfacing with server-side languages like **PHP** that make // it difficult to read the body of `PUT` requests. Backbone.sync = function(method, model, options) { var type = methodMap[method]; // Default options, unless specified. _.defaults(options || (options = {}), { emulateHTTP: Backbone.emulateHTTP, emulateJSON: Backbone.emulateJSON }); // Default JSON-request options. var params = {type: type, dataType: 'json'}; // Ensure that we have a URL. if (!options.url) { params.url = _.result(model, 'url') || urlError(); } // Ensure that we have the appropriate request data. if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { params.contentType = 'application/json'; params.data = JSON.stringify(options.attrs || model.toJSON(options)); } // For older servers, emulate JSON by encoding the request into an HTML-form. if (options.emulateJSON) { params.contentType = 'application/x-www-form-urlencoded'; params.data = params.data ? {model: params.data} : {}; } // For older servers, emulate HTTP by mimicking the HTTP method with `_method` // And an `X-HTTP-Method-Override` header. if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { params.type = 'POST'; if (options.emulateJSON) params.data._method = type; var beforeSend = options.beforeSend; options.beforeSend = function(xhr) { xhr.setRequestHeader('X-HTTP-Method-Override', type); if (beforeSend) return beforeSend.apply(this, arguments); }; } // Don't process data on a non-GET request. if (params.type !== 'GET' && !options.emulateJSON) { params.processData = false; } // If we're sending a `PATCH` request, and we're in an old Internet Explorer // that still has ActiveX enabled by default, override jQuery to use that // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8. if (params.type === 'PATCH' && noXhrPatch) { params.xhr = function() { return new ActiveXObject("Microsoft.XMLHTTP"); }; } // Make the request, allowing the user to override any Ajax options. var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); model.trigger('request', model, xhr, options); return xhr; }; var noXhrPatch = typeof window !== 'undefined' && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent); // Map from CRUD to HTTP for our default `Backbone.sync` implementation. var methodMap = { 'create': 'POST', 'update': 'PUT', 'patch': 'PATCH', 'delete': 'DELETE', 'read': 'GET' }; // Set the default implementation of `Backbone.ajax` to proxy through to `$`. // Override this if you'd like to use a different library. Backbone.ajax = function() { return Backbone.$.ajax.apply(Backbone.$, arguments); }; // Backbone.Router // --------------- // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. var Router = Backbone.Router = function(options) { options || (options = {}); if (options.routes) this.routes = options.routes; this._bindRoutes(); this.initialize.apply(this, arguments); }; // Cached regular expressions for matching named param parts and splatted // parts of route strings. var optionalParam = /\((.*?)\)/g; var namedParam = /(\(\?)?:\w+/g; var splatParam = /\*\w+/g; var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // Set up all inheritable **Backbone.Router** properties and methods. _.extend(Router.prototype, Events, { // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // Manually bind a single named route to a callback. For example: // // this.route('search/:query/p:num', 'search', function(query, num) { // ... // }); // route: function(route, name, callback) { if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (_.isFunction(name)) { callback = name; name = ''; } if (!callback) callback = this[name]; var router = this; Backbone.history.route(route, function(fragment) { var args = router._extractParameters(route, fragment); callback && callback.apply(router, args); router.trigger.apply(router, ['route:' + name].concat(args)); router.trigger('route', name, args); Backbone.history.trigger('route', router, name, args); }); return this; }, // Simple proxy to `Backbone.history` to save a fragment into the history. navigate: function(fragment, options) { Backbone.history.navigate(fragment, options); return this; }, // Bind all defined routes to `Backbone.history`. We have to reverse the // order of the routes here to support behavior where the most general // routes can be defined at the bottom of the route map. _bindRoutes: function() { if (!this.routes) return; this.routes = _.result(this, 'routes'); var route, routes = _.keys(this.routes); while ((route = routes.pop()) != null) { this.route(route, this.routes[route]); } }, // Convert a route string into a regular expression, suitable for matching // against the current location hash. _routeToRegExp: function(route) { route = route.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') .replace(namedParam, function(match, optional){ return optional ? match : '([^\/]+)'; }) .replace(splatParam, '(.*?)'); return new RegExp('^' + route + '$'); }, // Given a route, and a URL fragment that it matches, return the array of // extracted decoded parameters. Empty or unmatched parameters will be // treated as `null` to normalize cross-browser behavior. _extractParameters: function(route, fragment) { var params = route.exec(fragment).slice(1); return _.map(params, function(param) { return param ? decodeURIComponent(param) : null; }); } }); // Backbone.History // ---------------- // Handles cross-browser history management, based on either // [pushState](http://diveintohtml5.info/history.html) and real URLs, or // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) // and URL fragments. If the browser supports neither (old IE, natch), // falls back to polling. var History = Backbone.History = function() { this.handlers = []; _.bindAll(this, 'checkUrl'); // Ensure that `History` can be used outside of the browser. if (typeof window !== 'undefined') { this.location = window.location; this.history = window.history; } }; // Cached regex for stripping a leading hash/slash and trailing space. var routeStripper = /^[#\/]|\s+$/g; // Cached regex for stripping leading and trailing slashes. var rootStripper = /^\/+|\/+$/g; // Cached regex for detecting MSIE. var isExplorer = /msie [\w.]+/; // Cached regex for removing a trailing slash. var trailingSlash = /\/$/; // Has the history handling already been started? History.started = false; // Set up all inheritable **Backbone.History** properties and methods. _.extend(History.prototype, Events, { // The default interval to poll for hash changes, if necessary, is // twenty times a second. interval: 50, // Gets the true hash value. Cannot use location.hash directly due to bug // in Firefox where location.hash will always be decoded. getHash: function(window) { var match = (window || this).location.href.match(/#(.*)$/); return match ? match[1] : ''; }, // Get the cross-browser normalized URL fragment, either from the URL, // the hash, or the override. getFragment: function(fragment, forcePushState) { if (fragment == null) { if (this._hasPushState || !this._wantsHashChange || forcePushState) { fragment = this.location.pathname; var root = this.root.replace(trailingSlash, ''); if (!fragment.indexOf(root)) fragment = fragment.substr(root.length); } else { fragment = this.getHash(); } } return fragment.replace(routeStripper, ''); }, // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. start: function(options) { if (History.started) throw new Error("Backbone.history has already been started"); History.started = true; // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? this.options = _.extend({}, {root: '/'}, this.options, options); this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; this._wantsPushState = !!this.options.pushState; this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); var fragment = this.getFragment(); var docMode = document.documentMode; var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); // Normalize root to always include a leading and trailing slash. this.root = ('/' + this.root + '/').replace(rootStripper, '/'); if (oldIE && this._wantsHashChange) { this.iframe = Backbone.$('