Merge in forked recast printer that uses Wadler's algorithm
parent
35d8546d27
commit
9b4535e9f8
146
README.md
146
README.md
|
@ -1,11 +1,153 @@
|
||||||
|
|
||||||
|
# jscodefmt
|
||||||
|
|
||||||
|
This is a JavaScript pretty-printer that is opinionated. All is takes
|
||||||
|
a width to format the code to and it does the rest. Zero config: it
|
||||||
|
just works! Integrate this into your editor to get immediate feedback,
|
||||||
|
or run it across an entire project to format all your files.
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
This is a fork of [recast](https://github.com/benjamn/recast)'s
|
||||||
|
printer because it already handles a lot of edge cases like handling
|
||||||
|
comments. The core algorithm has been rewritten to be based on
|
||||||
|
Wadler's "[A prettier
|
||||||
|
printer](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf)"
|
||||||
|
paper, however. Recast also supported only re-printing nodes that
|
||||||
|
changed from a transformation, but we avoid that and always
|
||||||
|
pretty-print the entire AST so it's always consistent.
|
||||||
|
|
||||||
|
That paper allows a flexible formatting that will break expressions
|
||||||
|
across lines if they get too big. This means you can sloppily write
|
||||||
|
code as you need and just format it, and it will always produce
|
||||||
|
consistent output.
|
||||||
|
|
||||||
|
The core of the algorithm is implemented in pp.js. The printer should
|
||||||
|
use the basic formatting abstractions provided to construct a format
|
||||||
|
when printing a node. Parts of the API only exist to be compatible
|
||||||
|
with recast's previous API to ease migration, but over time we can
|
||||||
|
clean it up.
|
||||||
|
|
||||||
|
The following commands are available:
|
||||||
|
|
||||||
|
* **concat**
|
||||||
|
|
||||||
|
Combine an array into a single string.
|
||||||
|
|
||||||
|
* **group**
|
||||||
|
|
||||||
|
Mark a group of items which the printer should try to fit on one line.
|
||||||
|
This is the basic command to tell the printer when to break. Groups
|
||||||
|
are usually nested, and the printer will try to fit everything on one
|
||||||
|
line, but if it doesn't fit it will break the outermost group first
|
||||||
|
and try again. It will continue breaking groups until everything fits
|
||||||
|
(or there are no more groups to break).
|
||||||
|
|
||||||
|
* **multilineGroup**
|
||||||
|
|
||||||
|
This is the same as `group`, but with an additional behavior: if this
|
||||||
|
group spans any other groups that have hard breaks (see below) this
|
||||||
|
group *always* breaks. Otherwise it acts the same as `group`.
|
||||||
|
|
||||||
|
For example, an array with try to fit on one line:
|
||||||
|
|
||||||
|
```js
|
||||||
|
[1, "foo", { bar: 2 }]
|
||||||
|
```
|
||||||
|
|
||||||
|
However, if any of the items inside the array have a hard break, the
|
||||||
|
array will *always* break as well:
|
||||||
|
|
||||||
|
```js
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
function() {
|
||||||
|
return 2
|
||||||
|
},
|
||||||
|
3
|
||||||
|
]
|
||||||
|
|
||||||
|
Functions always break after the opening curly brace no matter what,
|
||||||
|
so the array breaks as well for consistent formatting. See the
|
||||||
|
implementation of `ArrayExpression` for an example.
|
||||||
|
|
||||||
|
* **join**
|
||||||
|
|
||||||
|
Join an array of items with a separator.
|
||||||
|
|
||||||
|
* **line**
|
||||||
|
|
||||||
|
Specify a line break. If an expression fits on one line, the line
|
||||||
|
break will be replaced with a space. Line breaks always indent the
|
||||||
|
next line with the current level of indentation.
|
||||||
|
|
||||||
|
* **softline**
|
||||||
|
|
||||||
|
Specify a line break. The difference from `line` is that if the
|
||||||
|
expression fits on one line, it will be replaced with nothing.
|
||||||
|
|
||||||
|
* **hardline**
|
||||||
|
|
||||||
|
Specify a line break that is **always** included in the output, no
|
||||||
|
matter if the expression fits on one line or not.
|
||||||
|
|
||||||
|
* **literalline**
|
||||||
|
|
||||||
|
Specify a line break that is **always** included in the output, and
|
||||||
|
don't indent the next line. This is used for template literals.
|
||||||
|
|
||||||
|
* **indent**
|
||||||
|
|
||||||
|
Increase the level of indentation.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
For an example, here's the implementation of the `ArrayExpression` node type:
|
||||||
|
|
||||||
|
```js
|
||||||
|
return multilineGroup(concat([
|
||||||
|
"[",
|
||||||
|
indent(options.tabWidth,
|
||||||
|
concat([
|
||||||
|
line,
|
||||||
|
join(concat([",", line]),
|
||||||
|
path.map(print, "elements"))
|
||||||
|
])),
|
||||||
|
line,
|
||||||
|
"]"
|
||||||
|
]));
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a group with opening and closing brackets, and possibly
|
||||||
|
indented contents. Because it's a `multilineGroup` it will always be
|
||||||
|
broken up if any of the sub-expressions are broken.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
There is a lot to do:
|
||||||
|
|
||||||
|
1. Remove any cruft leftover from recast that we don't need
|
||||||
|
2. Polish the API (it was currently designed to be "usable" and compatible with what recast did before)
|
||||||
|
3. Many node types have not been converted from recast's old ways of doing things, so need to finish converting them.
|
||||||
|
4. Better editor integration
|
||||||
|
5. Better CLI
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/jlongster/jscodefmt.git
|
$ git clone https://github.com/jlongster/jscodefmt.git
|
||||||
$ cd jscodefmt
|
$ cd jscodefmt
|
||||||
$ npm install -g .
|
$ npm install
|
||||||
$ jscodefmt file.js
|
$ ./bin/jscodefmt file.js
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
A few snapshot tests are currently implemented. See `tests`. To run
|
||||||
|
the tests simply cd into the tests directory and run `node index.js`.
|
||||||
|
|
||||||
|
## Editors
|
||||||
|
|
||||||
It's most useful when integrated with your editor, so see `editors` to
|
It's most useful when integrated with your editor, so see `editors` to
|
||||||
for editor support. Atom and Emacs is currently supported.
|
for editor support. Atom and Emacs is currently supported.
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ module.exports = {
|
||||||
if(editor.editorElement) {
|
if(editor.editorElement) {
|
||||||
window.addEventListener("resize", e => {
|
window.addEventListener("resize", e => {
|
||||||
const { width } = window.document.body.getBoundingClientRect();
|
const { width } = window.document.body.getBoundingClientRect();
|
||||||
const columns = (width / editor.editorElement.getDefaultCharacterWidth() | 0) - 10;
|
const columns = (width / editor.editorElement.getDefaultCharacterWidth() | 0);
|
||||||
console.log(width, columns);
|
console.log(width, columns);
|
||||||
this.format({selection: false, printWidth: columns});
|
this.format({selection: false, printWidth: columns});
|
||||||
});
|
});
|
||||||
|
|
5
index.js
5
index.js
|
@ -1,5 +1,6 @@
|
||||||
const recast = require("recast");
|
const recast = require("recast");
|
||||||
const babylon = require("babylon");
|
const babylon = require("babylon");
|
||||||
|
const Printer = require("./src/printer").Printer;
|
||||||
|
|
||||||
var babylonOptions = {
|
var babylonOptions = {
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
|
@ -35,7 +36,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = recast.prettyPrint(ast, { tabWidth, wrapColumn: printWidth });
|
const printer = new Printer({ tabWidth, wrapColumn: printWidth });
|
||||||
return result.code;
|
return printer.printGenerically(ast).code;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
11
package.json
11
package.json
|
@ -1,11 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "jscodefmt",
|
"name": "jscodefmt",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bin": { "jscodefmt": "./bin/jscodefmt" },
|
"bin": {
|
||||||
|
"jscodefmt": "./bin/jscodefmt"
|
||||||
|
},
|
||||||
"main": "./index.js",
|
"main": "./index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babylon": "^6.14.1",
|
"babylon": "git+https://github.com/jlongster/babylon.git#published",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"recast": "git+https://github.com/jlongster/recast.git#print-all!"
|
"recast": "^0.11.18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"glob": "^7.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,356 @@
|
||||||
|
var assert = require("assert");
|
||||||
|
var types = require("ast-types");
|
||||||
|
var n = types.namedTypes;
|
||||||
|
var isArray = types.builtInTypes.array;
|
||||||
|
var isObject = types.builtInTypes.object;
|
||||||
|
var pp = require("./pp");
|
||||||
|
var fromString = pp.fromString;
|
||||||
|
var concat = pp.concat;
|
||||||
|
var line = pp.line;
|
||||||
|
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(line);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
// TODO: Optimize this; if there are no comments we shouldn't
|
||||||
|
// touch the structure
|
||||||
|
return concat(leadingParts);
|
||||||
|
};
|
|
@ -0,0 +1,484 @@
|
||||||
|
var assert = require("assert");
|
||||||
|
var types = require("ast-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];
|
||||||
|
};
|
||||||
|
|
||||||
|
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 parent = this.getParentNode();
|
||||||
|
if (!parent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = this.getName();
|
||||||
|
var node = this.getNode();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
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(parent.type === 'CallExpression' &&
|
||||||
|
name === 'callee') {
|
||||||
|
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();
|
||||||
|
return !n.FunctionExpression.check(node)
|
||||||
|
&& !n.ObjectExpression.check(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
|
@ -0,0 +1,127 @@
|
||||||
|
var 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("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"),
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,254 @@
|
||||||
|
|
||||||
|
function fromString(text) {
|
||||||
|
if(typeof text !== "string") {
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function concat(parts) {
|
||||||
|
return { type: 'concat', parts };
|
||||||
|
}
|
||||||
|
|
||||||
|
function indent(n, contents) {
|
||||||
|
return { type: 'indent', contents, n };
|
||||||
|
}
|
||||||
|
|
||||||
|
function group(contents) {
|
||||||
|
return { type: 'group', contents };
|
||||||
|
}
|
||||||
|
|
||||||
|
function multilineGroup(doc) {
|
||||||
|
const shouldBreak = hasHardLine(doc);
|
||||||
|
return { type: 'group', contents: doc, break: shouldBreak };
|
||||||
|
}
|
||||||
|
|
||||||
|
function iterDoc(topDoc, func) {
|
||||||
|
const docs = [topDoc];
|
||||||
|
|
||||||
|
while(docs.length !== 0) {
|
||||||
|
const doc = docs.pop();
|
||||||
|
let res = undefined;
|
||||||
|
|
||||||
|
if(typeof doc === "string") {
|
||||||
|
const res = func("string", doc);
|
||||||
|
if(res) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const res = func(doc.type, doc);
|
||||||
|
if(res) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(doc.type === "concat") {
|
||||||
|
for(var i = doc.parts.length - 1; i >= 0; i--) {
|
||||||
|
docs.push(doc.parts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(doc.type !== "line") {
|
||||||
|
docs.push(doc.contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const line = { type: 'line' };
|
||||||
|
const softline = { type: 'line', soft: true };
|
||||||
|
const hardline = { type: 'line', hard: true };
|
||||||
|
const literalline = { type: 'line', hard: true, literal: true };
|
||||||
|
|
||||||
|
function indentedLine(n) {
|
||||||
|
return { type: 'line', indent: n };
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmpty(n) {
|
||||||
|
return typeof n === "string" && n.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function join(sep, arr) {
|
||||||
|
var res = [];
|
||||||
|
for(var i=0; i < arr.length; i++) {
|
||||||
|
if(i !== 0) {
|
||||||
|
res.push(sep);
|
||||||
|
}
|
||||||
|
res.push(arr[i]);
|
||||||
|
}
|
||||||
|
return concat(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFirstString(doc) {
|
||||||
|
return iterDoc(doc, (type, doc) => {
|
||||||
|
if(type === "string" && doc.trim().length !== 0) {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasHardLine(doc) {
|
||||||
|
// TODO: If we hit a group, check if it's already marked as a
|
||||||
|
// multiline group because they should be marked bottom-up.
|
||||||
|
return !!iterDoc(doc, (type, doc) => {
|
||||||
|
switch(type) {
|
||||||
|
case "line":
|
||||||
|
if(doc.hard) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _makeIndent(n) {
|
||||||
|
var s = "";
|
||||||
|
for(var i=0; i<n; i++) {
|
||||||
|
s += " ";
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MODE_BREAK = 1;
|
||||||
|
const MODE_FLAT = 2;
|
||||||
|
|
||||||
|
function fits(next, restCommands, width) {
|
||||||
|
let restIdx = restCommands.length;
|
||||||
|
const cmds = [next];
|
||||||
|
|
||||||
|
while(width >= 0) {
|
||||||
|
if(cmds.length === 0) {
|
||||||
|
if(restIdx === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cmds.push(restCommands[restIdx - 1]);
|
||||||
|
restIdx--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [ind, mode, doc] = cmds.pop();
|
||||||
|
|
||||||
|
if(typeof doc === "string") {
|
||||||
|
width -= doc.length;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch(doc.type) {
|
||||||
|
case "concat":
|
||||||
|
for(var i = doc.parts.length - 1; i >= 0; i--) {
|
||||||
|
cmds.push([ind, mode, doc.parts[i]]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "indent":
|
||||||
|
cmds.push([ind + doc.n, mode, doc.contents]);
|
||||||
|
break;
|
||||||
|
case "group":
|
||||||
|
cmds.push([ind, doc.break ? MODE_BREAK : mode, doc.contents]);
|
||||||
|
break;
|
||||||
|
case "line":
|
||||||
|
switch(mode) {
|
||||||
|
case MODE_FLAT:
|
||||||
|
if(!doc.hard) {
|
||||||
|
if(!doc.soft) {
|
||||||
|
width -= 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case MODE_BREAK:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function print(w, doc) {
|
||||||
|
let pos = 0;
|
||||||
|
// cmds is basically a stack. We've turned a recursive call into a
|
||||||
|
// while loop which is much faster. The while loop below adds new
|
||||||
|
// cmds to the array instead of recursively calling `print`.
|
||||||
|
let cmds = [[0, MODE_BREAK, doc]];
|
||||||
|
let out = [];
|
||||||
|
|
||||||
|
while(cmds.length !== 0) {
|
||||||
|
const [ind, mode, doc] = cmds.pop();
|
||||||
|
|
||||||
|
if(typeof doc === "string") {
|
||||||
|
out.push(doc);
|
||||||
|
pos += doc.length;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch(doc.type) {
|
||||||
|
case "concat":
|
||||||
|
for(var i = doc.parts.length - 1; i >= 0; i--) {
|
||||||
|
cmds.push([ind, mode, doc.parts[i]]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "indent":
|
||||||
|
cmds.push([ind + doc.n, mode, doc.contents]);
|
||||||
|
break;
|
||||||
|
case "group":
|
||||||
|
switch(mode) {
|
||||||
|
case MODE_FLAT:
|
||||||
|
cmds.push([ind, doc.break ? MODE_BREAK : MODE_FLAT, doc.contents]);
|
||||||
|
break;
|
||||||
|
case MODE_BREAK:
|
||||||
|
const next = [ind, MODE_FLAT, doc.contents];
|
||||||
|
let rem = w - pos;
|
||||||
|
if(!doc.break && fits(next, cmds, rem)) {
|
||||||
|
cmds.push(next);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cmds.push([ind, MODE_BREAK, doc.contents]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "line":
|
||||||
|
switch(mode) {
|
||||||
|
case MODE_FLAT:
|
||||||
|
if(!doc.hard) {
|
||||||
|
if(!doc.soft) {
|
||||||
|
out.push(" ");
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We need to switch everything back into
|
||||||
|
// the breaking mode because this is
|
||||||
|
// forcing a newline and everything needs
|
||||||
|
// to be re-measured.
|
||||||
|
cmds.forEach(cmd => {
|
||||||
|
cmd[1] = MODE_BREAK;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case MODE_BREAK:
|
||||||
|
if(doc.literal) {
|
||||||
|
out.push("\n");
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
out.push("\n" + _makeIndent(ind));
|
||||||
|
pos = ind;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return out.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fromString, concat, isEmpty, join,
|
||||||
|
line, softline, hardline, literalline, group, multilineGroup,
|
||||||
|
hasHardLine, indent, print, getFirstString
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,306 @@
|
||||||
|
var assert = require("assert");
|
||||||
|
var types = require("ast-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 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 has any comments, their locations might contribute to
|
||||||
|
// the true start/end positions of the node.
|
||||||
|
if (node.comments) {
|
||||||
|
node.comments.forEach(include);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 && util.isExportDeclaration(node) &&
|
||||||
|
node.declaration.decorators) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 === "TemplateLiteral") {
|
||||||
|
fixTemplateLiteral(node, lines);
|
||||||
|
|
||||||
|
} else 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 fixTemplateLiteral(node, lines) {
|
||||||
|
assert.strictEqual(node.type, "TemplateLiteral");
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
x = 10;
|
||||||
|
y = 20;
|
||||||
|
z = 30;
|
||||||
|
x = y = z = w = 50;
|
||||||
|
reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally_long_var = 10;
|
||||||
|
reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally_long_var = x = y = 10;
|
|
@ -0,0 +1,6 @@
|
||||||
|
x = 10;
|
||||||
|
y = 20;
|
||||||
|
z=30;
|
||||||
|
x = y = z = w = 50;
|
||||||
|
reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally_long_var = 10;
|
||||||
|
reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally_long_var = x = y = 10;
|
|
@ -0,0 +1,6 @@
|
||||||
|
x && y || z;
|
||||||
|
x | y & z;
|
||||||
|
x + y + z + w;
|
||||||
|
reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally_long_var ||
|
||||||
|
foo ||
|
||||||
|
bar + z;
|
|
@ -0,0 +1,5 @@
|
||||||
|
x && y || z
|
||||||
|
x | y & z
|
||||||
|
x + y + z +w
|
||||||
|
|
||||||
|
reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally_long_var || foo || bar + z
|
|
@ -0,0 +1,29 @@
|
||||||
|
const fs = require("fs");
|
||||||
|
const { join } = require("path");
|
||||||
|
const glob = require("glob");
|
||||||
|
|
||||||
|
const jscodefmt = require("../");
|
||||||
|
|
||||||
|
glob("./fixtures/**/*.js", function(err, files) {
|
||||||
|
if(err) { throw err };
|
||||||
|
runFixtureTests(files.filter(file => !file.includes(".expected.js")));
|
||||||
|
});
|
||||||
|
|
||||||
|
function runFixtureTests(files) {
|
||||||
|
files.forEach(file => {
|
||||||
|
const expectedFile = file.replace(/\.js$/, ".expected.js");
|
||||||
|
const src = fs.readFileSync(file);
|
||||||
|
const formatted = jscodefmt.format(src, { printWidth: 60 });
|
||||||
|
|
||||||
|
if(fs.existsSync(expectedFile)) {
|
||||||
|
const expected = fs.readFileSync(expectedFile, "utf8");
|
||||||
|
|
||||||
|
if(formatted !== expected) {
|
||||||
|
throw new Error("Failure: " + file + "\n" + formatted + "\ndoes not equal\n" + expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fs.writeFileSync(expectedFile, formatted, "utf8");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue