prettier
Advanced tools
Comparing version 0.11.0 to 0.13.1
#!/usr/bin/env node | ||
"use strict"; | ||
const fs = require("fs"); | ||
@@ -17,4 +18,8 @@ const getStdin = require("get-stdin"); | ||
"bracket-spacing", | ||
// See https://github.com/chalk/supports-color/#info | ||
// The supports-color package (a sub sub dependency) looks directly at | ||
// `process.argv` for `--no-color` and such-like options. The reason it is | ||
// listed here is to avoid "Ignored unknown option: --no-color" warnings. | ||
// See https://github.com/chalk/supports-color/#info for more information. | ||
"color", | ||
"help", | ||
"version", | ||
@@ -25,7 +30,9 @@ "debug-print-doc", | ||
], | ||
string: [ "print-width", "tab-width", "parser" ], | ||
string: ["print-width", "tab-width", "parser"], | ||
default: { color: true, "bracket-spacing": true, parser: "babylon" }, | ||
alias: { help: "h", version: "v" }, | ||
unknown: param => { | ||
if (param.startsWith("-")) { | ||
console.warn("Ignored unknown option: " + param + "\n"); | ||
return false; | ||
} | ||
@@ -42,5 +49,5 @@ } | ||
const write = argv["write"]; | ||
const stdin = argv["stdin"]; | ||
const stdin = argv["stdin"] || !filepatterns.length && !process.stdin.isTTY; | ||
if (!filepatterns.length && !stdin) { | ||
if (argv["help"] || !filepatterns.length && !stdin) { | ||
console.log( | ||
@@ -64,6 +71,13 @@ "Usage: prettier [opts] [filename ...]\n\n" + | ||
); | ||
process.exit(1); | ||
process.exit(argv["help"] ? 0 : 1); | ||
} | ||
function getParser() { | ||
function getParserOption() { | ||
const optionName = "parser"; | ||
const value = argv[optionName]; | ||
if (value === undefined) { | ||
return value; | ||
} | ||
// For backward compatibility. Deprecated in 0.0.10 | ||
@@ -75,14 +89,42 @@ if (argv["flow-parser"]) { | ||
if (argv["parser"] === "flow") { | ||
return "flow"; | ||
if (value === "flow" || value === "babylon") { | ||
return value; | ||
} | ||
console.warn( | ||
"Ignoring unknown --" + | ||
optionName + | ||
' value, falling back to "babylon":\n' + | ||
' Expected "flow" or "babylon", but received: ' + | ||
JSON.stringify(value) | ||
); | ||
return "babylon"; | ||
} | ||
function getIntOption(optionName) { | ||
const value = argv[optionName]; | ||
if (value === undefined) { | ||
return value; | ||
} | ||
if (/^\d+$/.test(value)) { | ||
return Number(value); | ||
} | ||
console.error( | ||
"Invalid --" + | ||
optionName + | ||
" value. Expected an integer, but received: " + | ||
JSON.stringify(value) | ||
); | ||
process.exit(1); | ||
} | ||
const options = { | ||
printWidth: argv["print-width"], | ||
tabWidth: argv["tab-width"], | ||
printWidth: getIntOption("print-width"), | ||
tabWidth: getIntOption("tab-width"), | ||
bracketSpacing: argv["bracket-spacing"], | ||
parser: getParser(), | ||
parser: getParserOption(), | ||
singleQuote: argv["single-quote"], | ||
@@ -100,9 +142,33 @@ trailingComma: argv["trailing-comma"] | ||
function handleError(filename, e) { | ||
const isParseError = Boolean(e && e.loc); | ||
const isValidationError = /Validation Error/.test(e && e.message); | ||
// For parse errors and validation errors, we only want to show the error | ||
// message formatted in a nice way. `String(e)` takes care of that. Other | ||
// (unexpected) errors are passed as-is as a separate argument to | ||
// `console.error`. That includes the stack trace (if any), and shows a nice | ||
// `util.inspect` of throws things that aren't `Error` objects. (The Flow | ||
// parser has mistakenly thrown arrays sometimes.) | ||
if (isParseError) { | ||
console.error(filename + ": " + String(e)); | ||
} else if (isValidationError) { | ||
console.error(String(e)); | ||
// If validation fails for one file, it will fail for all of them. | ||
process.exit(1); | ||
} else { | ||
console.error(filename + ":", e); | ||
} | ||
// Don't exit the process if one file failed | ||
process.exitCode = 2; | ||
} | ||
if (stdin) { | ||
getStdin().then(input => { | ||
try { | ||
console.log(format(input)); | ||
// Don't use `console.log` here since it adds an extra newline at the end. | ||
process.stdout.write(format(input)); | ||
} catch (e) { | ||
process.exitCode = 2; | ||
console.error("stdin: " + e); | ||
handleError("stdin", e); | ||
return; | ||
@@ -129,4 +195,3 @@ } | ||
} catch (e) { | ||
process.exitCode = 2; | ||
console.error(filename + ": " + e); | ||
handleError(filename, e); | ||
return; | ||
@@ -144,3 +209,4 @@ } | ||
} else { | ||
console.log(output); | ||
// Don't use `console.log` here since it adds an extra newline at the end. | ||
process.stdout.write(output); | ||
} | ||
@@ -147,0 +213,0 @@ }); |
@@ -0,1 +1,52 @@ | ||
# 0.13.0 | ||
[link](https://github.com/jlongster/prettier/compare/0.12.0...0.13.0) | ||
* Simplify arrow functions that use blocks (#496) | ||
* Properly print comments for BinaryExpression (#494) | ||
* Preserve empty line after comment (#493) | ||
* [JSX] Handle each line of text separately (#455) | ||
* Proper support for dangling comments (#492) | ||
# 0.12.0 | ||
[link](https://github.com/jlongster/prettier/compare/0.11.0...0.12.0) | ||
* [WIP] Add rationale document (#372) | ||
* Proper parenthesis for yield and await in conditional (#436) | ||
* Don't print double newlines at EOF to stdout (#437) | ||
* Explain the `--color` option in a comment (#434) | ||
* Validate user-provided config with jest-validate (#301) | ||
* Propagate breaks upwards automatically, introduce `breakParent` (#440) | ||
* Fix typo in variable name (#441) | ||
* Refactor traversal (#442) | ||
* Do not put a newline on empty `{}` for functions (#447) | ||
* Better error message for assertDoc (#449) | ||
* Remove `multilineGroup` (#450) | ||
* Ability to break on `:` for objects (#314) | ||
* Update snapshots | ||
* [RFC] Do not put spacing inside of arrays with bracketSpacing (#446) | ||
* Fix integer CLI arguments (#452) | ||
* Move tests around (#454) | ||
* Update package.json, use ast-types 0.9.4 (#453) | ||
* Update lockfile | ||
* Support printing import("a") (#458) | ||
* Explain that you can pass options to the spec runner (#460) | ||
* Fix spurious whitespace (#463) | ||
* Preserve new lines after directives (#464) | ||
* Put decorators on the same line (#459) | ||
* docs: add related projects (#456) | ||
* Add break points for class declaration (#466) | ||
* Added parens around in operator in for loops 🚀. (#468) | ||
* CLI improvements (#478) | ||
* [RFC] Hug Conditionals in JSX (#473) | ||
* Refactor comment algorithm and improve newline/spaces detection (#482) | ||
* Indent ternaries (#484) | ||
* Indent computed member (#471) | ||
* Maintain windows line ending (#472) | ||
* Don't break up JSXOpeningElement if it only has a single text (#488) | ||
* Add CallExpression to the last argument expansion whitelist (#470) | ||
* Mention eslint-plugin-prettier in Related Projects (#490) | ||
* Stop using conditionalGroup inside of UnionTypeAnnotation (#491) | ||
# 0.11.0 | ||
@@ -52,3 +103,2 @@ | ||
# 0.0.10 | ||
@@ -55,0 +105,0 @@ |
@@ -27,8 +27,9 @@ | ||
### multilineGroup | ||
A document can force parent groups to break by including `breakParent` | ||
(see below). A hard and literal line automatically include this so | ||
they always break parent groups. Breaks are propagated to all parent | ||
groups, so if a deeply nested expression has a hard break, everything | ||
with break. This only matters for "hard" breaks, i.e. newlines that | ||
are printed no matter what and can be statically analyzed. | ||
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 will try to fit on one line: | ||
@@ -57,2 +58,16 @@ | ||
### breakParent | ||
Include this anywhere to force all parent groups to break. See `group` | ||
for more info. Example: | ||
```js | ||
group(concat([ | ||
" ", | ||
expr, | ||
" ", | ||
breakParent | ||
])) | ||
``` | ||
### join | ||
@@ -59,0 +74,0 @@ |
15
index.js
"use strict"; | ||
const codeFrame = require("babel-code-frame"); | ||
@@ -11,2 +12,10 @@ const comments = require("./src/comments"); | ||
function guessLineEnding(text) { | ||
const index = text.indexOf("\n"); | ||
if (index >= 0 && text.charAt(index - 1) === "\r") { | ||
return "\r\n"; | ||
} | ||
return "\n"; | ||
} | ||
function parse(text, opts) { | ||
@@ -46,3 +55,3 @@ const parseFunction = opts.parser === "flow" | ||
const doc = printAstToDoc(ast, opts); | ||
const str = printDocToString(doc, opts.printWidth); | ||
const str = printDocToString(doc, opts.printWidth, guessLineEnding(text)); | ||
return str; | ||
@@ -60,5 +69,5 @@ } | ||
const nextChar = text.charAt(index + 1); | ||
const addNewline = nextChar == "\n" || nextChar == "\r"; | ||
const newLine = nextChar === "\n" ? "\n" : nextChar === "\r" ? "\r\n" : ""; | ||
return shebang + (addNewline ? "\n" : "") + format(programText, opts); | ||
return shebang + newLine + format(programText, opts); | ||
} | ||
@@ -65,0 +74,0 @@ |
{ | ||
"name": "prettier", | ||
"version": "0.11.0", | ||
"version": "0.13.1", | ||
"description": "Prettier is an opinionated JavaScript formatter", | ||
@@ -19,3 +19,3 @@ "bin": { | ||
"dependencies": { | ||
"ast-types": "git+https://github.com/jlongster/ast-types.git", | ||
"ast-types": "0.9.4", | ||
"babel-code-frame": "6.22.0", | ||
@@ -27,2 +27,3 @@ "babylon": "6.15.0", | ||
"glob": "7.1.1", | ||
"jest-validate": "18.2.0", | ||
"minimist": "1.2.0" | ||
@@ -29,0 +30,0 @@ }, |
@@ -36,3 +36,3 @@ # Prettier | ||
```js | ||
foo(arg1, arg2, arg3); | ||
foo(arg1, arg2, arg3, arg4); | ||
``` | ||
@@ -211,2 +211,13 @@ | ||
## Related Projects | ||
- [`eslint-plugin-prettier`](https://github.com/not-an-aardvark/eslint-plugin-prettier) plugs `prettier` into your `eslint` workflow | ||
- [`prettier-eslint`](https://github.com/kentcdodds/prettier-eslint) | ||
passes `prettier` output to `eslint --fix` | ||
- [`prettier-standard-formatter`](https://github.com/dtinth/prettier-standard-formatter) | ||
passes `prettier` output to `standard --fix` | ||
- [`prettier-with-tabs`](https://github.com/arijs/prettier-with-tabs) | ||
allows you to configure prettier to use `tabs` | ||
## Technical Details | ||
@@ -256,1 +267,2 @@ | ||
original one and makes sure they are semantically equivalent. | ||
* Each test folder has a `jsfmt.spec.js` that runs the tests. Normally you can just put `run_spec(__dirname);` there but if you want to pass specific options you can add the options object as the 2nd parameter like: `run_spec(__dirname, { parser: 'babylon' });` |
@@ -10,5 +10,8 @@ var assert = require("assert"); | ||
var hardline = docBuilders.hardline; | ||
var breakParent = docBuilders.breakParent; | ||
var indent = docBuilders.indent; | ||
var lineSuffix = docBuilders.lineSuffix; | ||
var util = require("./util"); | ||
var comparePos = util.comparePos; | ||
var childNodesCacheKey = Symbol('child-nodes'); | ||
var childNodesCacheKey = Symbol("child-nodes"); | ||
var locStart = util.locStart; | ||
@@ -73,3 +76,3 @@ var locEnd = util.locEnd; | ||
var childNodes = getSortedChildNodes(node, text); | ||
var precedingNode, followingNode; | ||
// Time to dust off the old binary search robes and wizard hat. | ||
@@ -87,2 +90,10 @@ var left = 0, right = childNodes.length; | ||
comment.enclosingNode = child; | ||
if (middle > 0) { | ||
// Store the global preceding node separately from | ||
// `precedingNode` because they mean different things. This is | ||
// the previous node, ignoring any delimiters/blocks/etc. | ||
comment.globalPrecedingNode = childNodes[middle - 1]; | ||
} | ||
decorateComment(child, comment, text); | ||
@@ -97,3 +108,3 @@ return; // Abandon the binary search at this level. | ||
// node we have encountered so far. | ||
var precedingNode = child; | ||
precedingNode = child; | ||
left = middle + 1; | ||
@@ -108,3 +119,3 @@ continue; | ||
// have encountered so far. | ||
var followingNode = child; | ||
followingNode = child; | ||
right = middle; | ||
@@ -136,36 +147,60 @@ continue; | ||
var pn = comment.precedingNode; | ||
var en = comment.enclosingNode; | ||
var fn = comment.followingNode; | ||
const precedingNode = comment.precedingNode; | ||
const globalPrecedingNode = comment.globalPrecedingNode; | ||
const enclosingNode = comment.enclosingNode; | ||
const followingNode = comment.followingNode; | ||
const isStartOfFile = comment.loc.start.line === 1; | ||
if (pn && fn) { | ||
var tieCount = tiesToBreak.length; | ||
if (tieCount > 0) { | ||
var lastTie = tiesToBreak[tieCount - 1]; | ||
if ( | ||
util.hasNewline(text, locStart(comment), { backwards: true }) || | ||
isStartOfFile | ||
) { | ||
// If a comment exists on its own line, prefer a leading comment. | ||
// We also need to check if it's the first line of the file. | ||
assert.strictEqual( | ||
lastTie.precedingNode === comment.precedingNode, | ||
lastTie.followingNode === comment.followingNode | ||
); | ||
if (lastTie.followingNode !== comment.followingNode) { | ||
breakTies(tiesToBreak, text); | ||
if (followingNode) { | ||
// Always a leading comment. | ||
addLeadingComment(followingNode, comment); | ||
} else if (precedingNode) { | ||
addTrailingComment(precedingNode, comment); | ||
} else if (enclosingNode) { | ||
addDanglingComment(enclosingNode, comment); | ||
} else { | ||
// TODO: If there are no nodes at all, we should still somehow | ||
// print the comment. | ||
} | ||
} else if (util.hasNewline(text, locEnd(comment))) { | ||
// There is content before this comment on the same line, but | ||
// none after it, so prefer a trailing comment. A trailing | ||
// comment *always* attaches itself to the previous node, no | ||
// matter if there's any syntax between them. | ||
const lastNode = precedingNode || globalPrecedingNode; | ||
if (lastNode) { | ||
// Always a trailing comment | ||
addTrailingComment(lastNode, comment); | ||
} else { | ||
throw new Error("Preceding node not found"); | ||
} | ||
} else { | ||
// Otherwise, text exists both before and after the comment on | ||
// the same line. If there is both a preceding and following | ||
// node, use a tie-breaking algorithm to determine if it should | ||
// be attached to the next or previous node. In the last case, | ||
// simply attach the right node; | ||
if (precedingNode && followingNode) { | ||
const tieCount = tiesToBreak.length; | ||
if (tieCount > 0) { | ||
var lastTie = tiesToBreak[tieCount - 1]; | ||
if (lastTie.followingNode !== comment.followingNode) { | ||
breakTies(tiesToBreak, text); | ||
} | ||
} | ||
tiesToBreak.push(comment); | ||
} else if (precedingNode) { | ||
addTrailingComment(precedingNode, comment); | ||
} else if (followingNode) { | ||
addLeadingComment(followingNode, comment); | ||
} else if (enclosingNode) { | ||
addDanglingComment(enclosingNode, comment); | ||
} | ||
tiesToBreak.push(comment); | ||
} else if (pn) { | ||
// No contest: we have a trailing comment. | ||
breakTies(tiesToBreak, text); | ||
addTrailingComment(pn, comment); | ||
} else if (fn) { | ||
// No contest: we have a leading comment. | ||
breakTies(tiesToBreak, text); | ||
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, text); | ||
addDanglingComment(en, comment); | ||
} else { | ||
} | ||
@@ -192,9 +227,9 @@ }); | ||
var pn = tiesToBreak[0].precedingNode; | ||
var fn = tiesToBreak[0].followingNode; | ||
var gapEndPos = locStart(fn); | ||
var precedingNode = tiesToBreak[0].precedingNode; | ||
var followingNode = tiesToBreak[0].followingNode; | ||
var gapEndPos = locStart(followingNode); | ||
// 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 | ||
// comment must be separated from followingNode by an unbroken series of | ||
// whitespace-only gaps (or other comments). | ||
@@ -207,4 +242,4 @@ for ( | ||
var comment = tiesToBreak[indexOfFirstLeadingComment - 1]; | ||
assert.strictEqual(comment.precedingNode, pn); | ||
assert.strictEqual(comment.followingNode, fn); | ||
assert.strictEqual(comment.precedingNode, precedingNode); | ||
assert.strictEqual(comment.followingNode, followingNode); | ||
@@ -220,15 +255,7 @@ var gap = text.slice(locEnd(comment), gapEndPos); | ||
// 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); | ||
addTrailingComment(precedingNode, comment); | ||
} else { | ||
addLeadingComment(fn, comment); | ||
addLeadingComment(followingNode, comment); | ||
} | ||
@@ -263,23 +290,74 @@ }); | ||
function printLeadingComment(commentPath, print) { | ||
var comment = commentPath.getValue(); | ||
n.Comment.assert(comment); | ||
return concat([ print(commentPath), hardline ]); | ||
function printComment(commentPath) { | ||
const comment = commentPath.getValue(); | ||
switch (comment.type) { | ||
case "CommentBlock": | ||
case "Block": | ||
return "/*" + comment.value + "*/"; | ||
case "CommentLine": | ||
case "Line": | ||
return "//" + comment.value; | ||
default: | ||
throw new Error("Not a comment: " + JSON.stringify(comment)); | ||
} | ||
} | ||
function printTrailingComment(commentPath, print, options) { | ||
const comment = commentPath.getValue(commentPath); | ||
n.Comment.assert(comment); | ||
function printLeadingComment(commentPath, print, options) { | ||
const comment = commentPath.getValue(); | ||
const contents = printComment(commentPath); | ||
const text = options.originalText; | ||
const isBlock = comment.type === "Block" || comment.type === "CommentBlock"; | ||
return concat([ | ||
util.newlineExistsBefore(text, locStart(comment)) ? hardline : " ", | ||
print(commentPath) | ||
]); | ||
// Leading block comments should see if they need to stay on the | ||
// same line or not. | ||
if (isBlock) { | ||
return concat([ | ||
contents, | ||
util.hasNewline(options.originalText, locEnd(comment)) ? hardline : " " | ||
]); | ||
} | ||
return concat([contents, hardline]); | ||
} | ||
function printDanglingComments(path, print, options) { | ||
function printTrailingComment(commentPath, print, options, parentNode) { | ||
const comment = commentPath.getValue(); | ||
const contents = printComment(commentPath); | ||
const isBlock = comment.type === "Block" || comment.type === "CommentBlock"; | ||
if ( | ||
util.hasNewline(options.originalText, locStart(comment), { | ||
backwards: true | ||
}) | ||
) { | ||
// This allows comments at the end of nested structures: | ||
// { | ||
// x: 1, | ||
// y: 2 | ||
// // A comment | ||
// } | ||
// Those kinds of comments are almost always leading comments, but | ||
// here it doesn't go "outside" the block and turns it into a | ||
// trailing comment for `2`. We can simulate the above by checking | ||
// if this a comment on its own line; normal trailing comments are | ||
// always at the end of another expression. | ||
return concat([hardline, contents]); | ||
} else if (isBlock) { | ||
// Trailing block comments never need a newline | ||
return concat([" ", contents]); | ||
} | ||
return concat([lineSuffix(" " + contents), !isBlock ? breakParent : ""]); | ||
} | ||
function printDanglingComments(path, options, noIndent) { | ||
const text = options.originalText; | ||
const parts = []; | ||
const node = path.getValue(); | ||
const parts = []; | ||
if (!node || !node.comments) { | ||
return ""; | ||
} | ||
path.each( | ||
@@ -289,6 +367,6 @@ commentPath => { | ||
if (!comment.leading && !comment.trailing) { | ||
parts.push( | ||
util.newlineExistsBefore(text, locStart(comment)) ? hardline : " " | ||
); | ||
parts.push(commentPath.call(print)); | ||
if (util.hasNewline(text, locStart(comment), { backwards: true })) { | ||
parts.push(hardline); | ||
} | ||
parts.push(printComment(commentPath)); | ||
} | ||
@@ -298,2 +376,6 @@ }, | ||
); | ||
if (!noIndent) { | ||
return indent(options.tabWidth, concat(parts)); | ||
} | ||
return concat(parts); | ||
@@ -307,3 +389,2 @@ } | ||
var comments = n.Node.check(value) && types.getFieldValue(value, "comments"); | ||
var isFirstInProgram = n.Program.check(parent) && parent.body[0] === value; | ||
@@ -315,3 +396,3 @@ if (!comments || comments.length === 0) { | ||
var leadingParts = []; | ||
var trailingParts = [ printed ]; | ||
var trailingParts = [printed]; | ||
@@ -324,17 +405,8 @@ path.each( | ||
if ( | ||
leading || | ||
trailing && | ||
!(n.Statement.check(value) || | ||
comment.type === "Block" || | ||
comment.type === "CommentBlock") | ||
) { | ||
leadingParts.push(printLeadingComment(commentPath, print)); | ||
if (leading) { | ||
leadingParts.push(printLeadingComment(commentPath, print, options)); | ||
// Support a special case where a comment exists at the very top | ||
// of the file. Allow the user to add spacing between that file | ||
// and any code beneath it. | ||
const text = options.originalText; | ||
if ( | ||
isFirstInProgram && | ||
util.newlineExistsAfter(options.originalText, util.locEnd(comment)) | ||
util.hasNewline(text, util.skipNewline(text, util.locEnd(comment))) | ||
) { | ||
@@ -344,3 +416,5 @@ leadingParts.push(hardline); | ||
} else if (trailing) { | ||
trailingParts.push(printTrailingComment(commentPath, print, options)); | ||
trailingParts.push( | ||
printTrailingComment(commentPath, print, options, parent) | ||
); | ||
} | ||
@@ -351,6 +425,5 @@ }, | ||
leadingParts.push.apply(leadingParts, trailingParts); | ||
return concat(leadingParts); | ||
return concat(leadingParts.concat(trailingParts)); | ||
} | ||
module.exports = { attach, printComments, printDanglingComments }; |
"use strict"; | ||
const assert = require("assert"); | ||
const utils = require("./doc-utils"); | ||
const hasHardLine = utils.hasHardLine; | ||
const willBreak = utils.willBreak; | ||
function assertDoc(val) { | ||
assert( | ||
typeof val === "string" || val != null && typeof val.type === "string", | ||
"Value is a valid document" | ||
); | ||
if ( | ||
!(typeof val === "string" || val != null && typeof val.type === "string") | ||
) { | ||
throw new Error( | ||
"Value " + JSON.stringify(val) + " is not a valid document" | ||
); | ||
} | ||
} | ||
@@ -16,2 +19,8 @@ | ||
// We cannot do this until we change `printJSXElement` to not | ||
// access the internals of a document directly. | ||
// if(parts.length === 1) { | ||
// // If it's a single document, no need to concat it. | ||
// return parts[0]; | ||
// } | ||
return { type: "concat", parts }; | ||
@@ -39,9 +48,2 @@ } | ||
function multilineGroup(contents, opts) { | ||
return group( | ||
contents, | ||
Object.assign(opts || {}, { shouldBreak: hasHardLine(contents) }) | ||
); | ||
} | ||
function conditionalGroup(states, opts) { | ||
@@ -65,6 +67,19 @@ return group( | ||
function lineSuffix(contents) { | ||
if (typeof contents !== "string") { | ||
throw new Error( | ||
"lineSuffix only takes a string, but given: " + JSON.stringify(contents) | ||
); | ||
} | ||
return { type: "line-suffix", contents }; | ||
} | ||
const breakParent = { type: "break-parent" }; | ||
const line = { type: "line" }; | ||
const softline = { type: "line", soft: true }; | ||
const hardline = { type: "line", hard: true }; | ||
const literalline = { type: "line", hard: true, literal: true }; | ||
const hardline = concat([{ type: "line", hard: true }, breakParent]); | ||
const literalline = concat([ | ||
{ type: "line", hard: true, literal: true }, | ||
breakParent | ||
]); | ||
@@ -93,6 +108,7 @@ function join(sep, arr) { | ||
group, | ||
multilineGroup, | ||
conditionalGroup, | ||
lineSuffix, | ||
breakParent, | ||
ifBreak, | ||
indent | ||
}; |
"use strict"; | ||
function flattenDoc(doc) { | ||
if (!doc || typeof doc === "string" || doc.type === "line") { | ||
return doc; | ||
} | ||
if (doc.type === "concat") { | ||
@@ -24,22 +20,24 @@ var res = []; | ||
return Object.assign({}, doc, { parts: res }); | ||
} | ||
if (doc.type === "indent") { | ||
return Object.assign({}, doc, { contents: flattenDoc(doc.contents) }); | ||
} | ||
if (doc.type === "if-break") { | ||
} else if (doc.type === "if-break") { | ||
return Object.assign({}, doc, { | ||
breakContents: flattenDoc(doc.breakContents), | ||
flatContents: flattenDoc(doc.flatContents) | ||
breakContents: ( | ||
doc.breakContents != null ? flattenDoc(doc.breakContents) : null | ||
), | ||
flatContents: ( | ||
doc.flatContents != null ? flattenDoc(doc.flatContents) : null | ||
) | ||
}); | ||
} | ||
if (doc.type === "group") { | ||
} else if (doc.type === "group") { | ||
return Object.assign({}, doc, { | ||
contents: flattenDoc(doc.contents), | ||
expandedStates: doc.expandedStates | ||
? doc.expandedStates.map(flattenDoc) | ||
: doc.expandedStates | ||
expandedStates: ( | ||
doc.expandedStates | ||
? doc.expandedStates.map(flattenDoc) | ||
: doc.expandedStates | ||
) | ||
}); | ||
} else if (doc.contents) { | ||
return Object.assign({}, doc, { contents: flattenDoc(doc.contents) }); | ||
} else { | ||
return doc; | ||
} | ||
@@ -66,2 +64,6 @@ } | ||
if (doc.type === "break-parent") { | ||
return "breakParent"; | ||
} | ||
if (doc.type === "concat") { | ||
@@ -83,10 +85,12 @@ return "[" + doc.parts.map(printDoc).join(", ") + "]"; | ||
if (doc.type === "group") { | ||
return (doc.break | ||
? "multilineGroup" | ||
: doc.expandedStates ? "conditionalGroup" : "group") + | ||
if (doc.expandedStates) { | ||
return "conditionalGroup(" + | ||
"[" + | ||
doc.expandedStates.map(printDoc).join(",") + | ||
"])"; | ||
} | ||
return (doc.break ? "wrappedGroup" : "group") + | ||
"(" + | ||
printDoc(doc.contents) + | ||
(doc.expandedStates | ||
? ", [" + doc.expandedStates.map(printDoc).join(",") + "]" | ||
: "") + | ||
")"; | ||
@@ -93,0 +97,0 @@ } |
"use strict"; | ||
const MODE_BREAK = 1; | ||
@@ -7,3 +8,3 @@ const MODE_FLAT = 2; | ||
let restIdx = restCommands.length; | ||
const cmds = [ next ]; | ||
const cmds = [next]; | ||
while (width >= 0) { | ||
@@ -33,3 +34,3 @@ if (cmds.length === 0) { | ||
for (var i = doc.parts.length - 1; i >= 0; i--) { | ||
cmds.push([ ind, mode, doc.parts[i] ]); | ||
cmds.push([ind, mode, doc.parts[i]]); | ||
} | ||
@@ -39,7 +40,7 @@ | ||
case "indent": | ||
cmds.push([ ind + doc.n, mode, doc.contents ]); | ||
cmds.push([ind + doc.n, mode, doc.contents]); | ||
break; | ||
case "group": | ||
cmds.push([ ind, doc.break ? MODE_BREAK : mode, doc.contents ]); | ||
cmds.push([ind, doc.break ? MODE_BREAK : mode, doc.contents]); | ||
@@ -50,3 +51,3 @@ break; | ||
if (doc.breakContents) { | ||
cmds.push([ ind, mode, doc.breakContents ]); | ||
cmds.push([ind, mode, doc.breakContents]); | ||
} | ||
@@ -56,3 +57,3 @@ } | ||
if (doc.flatContents) { | ||
cmds.push([ ind, mode, doc.flatContents ]); | ||
cmds.push([ind, mode, doc.flatContents]); | ||
} | ||
@@ -84,3 +85,5 @@ } | ||
function printDocToString(doc, w) { | ||
function printDocToString(doc, width, newLine) { | ||
newLine = newLine || "\n"; | ||
let pos = 0; | ||
@@ -90,5 +93,7 @@ // cmds is basically a stack. We've turned a recursive call into a | ||
// cmds to the array instead of recursively calling `print`. | ||
let cmds = [ [ 0, MODE_BREAK, doc ] ]; | ||
let cmds = [[0, MODE_BREAK, doc]]; | ||
let out = []; | ||
let shouldRemeasure = false; | ||
let lineSuffix = ""; | ||
while (cmds.length !== 0) { | ||
@@ -108,3 +113,3 @@ const x = cmds.pop(); | ||
for (var i = doc.parts.length - 1; i >= 0; i--) { | ||
cmds.push([ ind, mode, doc.parts[i] ]); | ||
cmds.push([ind, mode, doc.parts[i]]); | ||
} | ||
@@ -114,3 +119,3 @@ | ||
case "indent": | ||
cmds.push([ ind + doc.n, mode, doc.contents ]); | ||
cmds.push([ind + doc.n, mode, doc.contents]); | ||
@@ -135,4 +140,4 @@ break; | ||
const next = [ ind, MODE_FLAT, doc.contents ]; | ||
let rem = w - pos; | ||
const next = [ind, MODE_FLAT, doc.contents]; | ||
let rem = width - pos; | ||
@@ -150,7 +155,8 @@ if (!doc.break && fits(next, cmds, rem)) { | ||
if (doc.expandedStates) { | ||
const mostExpanded = doc.expandedStates[doc.expandedStates.length - | ||
1]; | ||
const mostExpanded = doc.expandedStates[ | ||
doc.expandedStates.length - 1 | ||
]; | ||
if (doc.break) { | ||
cmds.push([ ind, MODE_BREAK, mostExpanded ]); | ||
cmds.push([ind, MODE_BREAK, mostExpanded]); | ||
@@ -161,3 +167,3 @@ break; | ||
if (i >= doc.expandedStates.length) { | ||
cmds.push([ ind, MODE_BREAK, mostExpanded ]); | ||
cmds.push([ind, MODE_BREAK, mostExpanded]); | ||
@@ -167,3 +173,3 @@ break; | ||
const state = doc.expandedStates[i]; | ||
const cmd = [ ind, MODE_FLAT, state ]; | ||
const cmd = [ind, MODE_FLAT, state]; | ||
@@ -179,3 +185,3 @@ if (fits(cmd, cmds, rem)) { | ||
} else { | ||
cmds.push([ ind, MODE_BREAK, doc.contents ]); | ||
cmds.push([ind, MODE_BREAK, doc.contents]); | ||
} | ||
@@ -190,3 +196,3 @@ } | ||
if (doc.breakContents) { | ||
cmds.push([ ind, mode, doc.breakContents ]); | ||
cmds.push([ind, mode, doc.breakContents]); | ||
} | ||
@@ -196,3 +202,3 @@ } | ||
if (doc.flatContents) { | ||
cmds.push([ ind, mode, doc.flatContents ]); | ||
cmds.push([ind, mode, doc.flatContents]); | ||
} | ||
@@ -202,2 +208,5 @@ } | ||
break; | ||
case "line-suffix": | ||
lineSuffix += doc.contents; | ||
break; | ||
case "line": | ||
@@ -227,4 +236,3 @@ switch (mode) { | ||
if (doc.literal) { | ||
out.push("\n"); | ||
out.push(lineSuffix + newLine); | ||
pos = 0; | ||
@@ -240,7 +248,7 @@ } else { | ||
out.push("\n" + " ".repeat(ind)); | ||
out.push(lineSuffix + newLine + " ".repeat(ind)); | ||
pos = ind; | ||
} | ||
lineSuffix = ""; | ||
break; | ||
@@ -247,0 +255,0 @@ } |
"use strict"; | ||
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); | ||
function traverseDoc(doc, onEnter, onExit) { | ||
var hasStopped = false; | ||
function traverseDocRec(doc) { | ||
if (onEnter) { | ||
hasStopped = hasStopped || onEnter(doc) === false; | ||
} | ||
if (hasStopped) { | ||
return; | ||
} | ||
if (res) { | ||
return res; | ||
if (doc.type === "concat") { | ||
for (var i = 0; i < doc.parts.length; i++) { | ||
traverseDocRec(doc.parts[i]); | ||
} | ||
} else { | ||
const res = func(doc.type, doc); | ||
if (res) { | ||
return res; | ||
} else if (doc.type === "if-break") { | ||
if (doc.breakContents) { | ||
traverseDocRec(doc.breakContents); | ||
} | ||
if (doc.type === "concat") { | ||
for (var i = doc.parts.length - 1; i >= 0; i--) { | ||
docs.push(doc.parts[i]); | ||
} | ||
} else if (doc.type === "if-break") { | ||
if (doc.breakContents) { | ||
docs.push(doc.breakContents); | ||
} | ||
if (doc.flatContents) { | ||
docs.push(doc.flatContents); | ||
} | ||
} else if (doc.type !== "line") { | ||
docs.push(doc.contents); | ||
if (doc.flatContents) { | ||
traverseDocRec(doc.flatContents); | ||
} | ||
} else if (doc.contents) { | ||
traverseDocRec(doc.contents); | ||
} | ||
if (onExit) { | ||
onExit(doc); | ||
} | ||
} | ||
traverseDocRec(doc); | ||
} | ||
function findInDoc(doc, fn, defaultValue) { | ||
var result = defaultValue; | ||
traverseDoc(doc, function(doc) { | ||
var maybeResult = fn(doc); | ||
if (maybeResult !== undefined) { | ||
result = maybeResult; | ||
return false; | ||
} | ||
}); | ||
return result; | ||
} | ||
function isEmpty(n) { | ||
@@ -44,24 +53,85 @@ return typeof n === "string" && n.length === 0; | ||
function getFirstString(doc) { | ||
return iterDoc(doc, (type, doc) => { | ||
if (type === "string" && doc.trim().length !== 0) { | ||
return doc; | ||
return findInDoc( | ||
doc, | ||
doc => { | ||
if (typeof doc === "string" && doc.trim().length !== 0) { | ||
return doc; | ||
} | ||
}, | ||
null | ||
); | ||
} | ||
function isLineNext(doc) { | ||
return findInDoc( | ||
doc, | ||
doc => { | ||
if (typeof doc === "string") { | ||
return false; | ||
} | ||
if (doc.type === "line") { | ||
return true; | ||
} | ||
}, | ||
false | ||
); | ||
} | ||
function willBreak(doc) { | ||
return findInDoc( | ||
doc, | ||
doc => { | ||
if (doc.type === "group" && doc.break) { | ||
return true; | ||
} | ||
if (doc.type === "line" && doc.hard) { | ||
return true; | ||
} | ||
}, | ||
false | ||
); | ||
} | ||
function breakParentGroup(groupStack) { | ||
if (groupStack.length > 0) { | ||
const parentGroup = groupStack[groupStack.length - 1]; | ||
// Breaks are not propagated through conditional groups because | ||
// the user is expected to manually handle what breaks. | ||
if (!parentGroup.expandedStates) { | ||
parentGroup.break = true; | ||
} | ||
}); | ||
} | ||
return null; | ||
} | ||
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; | ||
function propagateBreaks(doc) { | ||
const groupStack = []; | ||
traverseDoc( | ||
doc, | ||
doc => { | ||
if (doc.type === "break-parent") { | ||
breakParentGroup(groupStack); | ||
} | ||
if (doc.type === "group") { | ||
groupStack.push(doc); | ||
} | ||
}, | ||
doc => { | ||
if (doc.type === "group") { | ||
const group = groupStack.pop(); | ||
if (group.break) { | ||
breakParentGroup(groupStack); | ||
} | ||
break; | ||
} | ||
} | ||
}); | ||
); | ||
} | ||
module.exports = { isEmpty, getFirstString, hasHardLine }; | ||
module.exports = { | ||
isEmpty, | ||
getFirstString, | ||
willBreak, | ||
isLineNext, | ||
traverseDoc, | ||
propagateBreaks | ||
}; |
@@ -11,3 +11,3 @@ var assert = require("assert"); | ||
assert.ok(this instanceof FastPath); | ||
this.stack = [ value ]; | ||
this.stack = [value]; | ||
} | ||
@@ -28,4 +28,5 @@ | ||
var copy = Object.create(FastPath.prototype); | ||
var stack = [ obj.value ]; | ||
for (var pp; pp = obj.parentPath; obj = pp) stack.push(obj.name, pp.value); | ||
var stack = [obj.value]; | ||
for (var pp; pp = obj.parentPath; obj = pp) | ||
stack.push(obj.name, pp.value); | ||
copy.stack = stack.reverse(); | ||
@@ -86,2 +87,18 @@ return copy; | ||
FPp.isLast = function isLast() { | ||
var s = this.stack; | ||
if (this.getParentNode()) { | ||
var idx = s[s.length - 2]; | ||
// The name of this node should be an index | ||
assert.ok(typeof idx === "number"); | ||
const arr = s[s.length - 3]; | ||
// We should have an array as a parent node | ||
assert.ok(Array.isArray(arr)); | ||
return idx === arr.length - 1; | ||
} | ||
return false; | ||
}; | ||
// Temporarily push properties named by string arguments given after the | ||
@@ -202,4 +219,4 @@ // callback function onto this.stack, then call the callback with a | ||
(parent.type === "ClassDeclaration" || parent.type === "ClassExpression") && | ||
parent.superClass === node && ( | ||
node.type === "ArrowFunctionExpression" || | ||
parent.superClass === node && | ||
(node.type === "ArrowFunctionExpression" || | ||
node.type === "AssignmentExpression" || | ||
@@ -217,4 +234,3 @@ node.type === "AwaitExpression" || | ||
node.type === "UpdateExpression" || | ||
node.type === "YieldExpression" | ||
) | ||
node.type === "YieldExpression") | ||
) { | ||
@@ -255,9 +271,11 @@ return true; | ||
case "UnaryExpression": | ||
if (node.prefix && | ||
((node.operator === '++' && parent.operator === '+') || | ||
(node.operator === '--' && parent.operator === '-'))) { | ||
if ( | ||
node.prefix && | ||
(node.operator === "++" && parent.operator === "+" || | ||
node.operator === "--" && parent.operator === "-") | ||
) { | ||
return true; | ||
} | ||
return false; | ||
return false; | ||
} | ||
@@ -276,6 +294,14 @@ | ||
case "BinaryExpression": | ||
if (node.operator === "in" && parent.type === "ForStatement" && parent.init === node) { | ||
if ( | ||
node.operator === "in" && | ||
parent.type === "ForStatement" && | ||
parent.init === node | ||
) { | ||
return true; | ||
} | ||
if (node.operator === "in" && parent.type === "AssignmentExpression") { | ||
return true; | ||
} | ||
case "LogicalExpression": | ||
@@ -338,7 +364,2 @@ switch (parent.type) { | ||
case "YieldExpression": | ||
if (parent.type === "ConditionalExpression" && | ||
parent.test === node && | ||
!node.argument) { | ||
return true; | ||
} | ||
case "AwaitExpression": | ||
@@ -359,2 +380,5 @@ switch (parent.type) { | ||
case "ConditionalExpression": | ||
return parent.test === node; | ||
default: | ||
@@ -388,5 +412,7 @@ return false; | ||
case "AssignmentExpression": | ||
if (parent.type === "ArrowFunctionExpression" && | ||
if ( | ||
parent.type === "ArrowFunctionExpression" && | ||
parent.body === node && | ||
node.left.type === "ObjectPattern") { | ||
node.left.type === "ObjectPattern" | ||
) { | ||
return true; | ||
@@ -393,0 +419,0 @@ } |
"use strict"; | ||
var validate = require("jest-validate").validate; | ||
var deprecatedConfig = require("./deprecated"); | ||
var defaults = { | ||
@@ -17,4 +21,11 @@ // Number of spaces the pretty-printer should use per tab | ||
var exampleConfig = Object.assign({}, defaults, { | ||
filename: "testFilename", | ||
printWidth: 80, | ||
originalText: "text" | ||
}); | ||
// Copy options and fill in default values. | ||
function normalize(options) { | ||
validate(options, { exampleConfig, deprecatedConfig }); | ||
const normalized = Object.assign({}, options || {}); | ||
@@ -24,3 +35,2 @@ | ||
if ("useFlowParser" in normalized) { | ||
console.warn('The `"useFlowParser": true/false` option is deprecated. Use `parser: "flow"` instead.'); | ||
normalized.parser = normalized.useFlowParser ? "flow" : "babylon"; | ||
@@ -27,0 +37,0 @@ delete normalized.useFlowParser; |
"use strict"; | ||
function parseWithFlow(text) { | ||
@@ -16,6 +17,10 @@ // Inline the require to avoid loading all the JS if we don't use it | ||
line: ast.errors[0].loc.start.line, | ||
column: ast.errors[0].loc.start.column, | ||
} | ||
column: ast.errors[0].loc.start.column | ||
}; | ||
const msg = ast.errors[0].message + | ||
" (" + loc.line + ":" + loc.column + ")"; | ||
" (" + | ||
loc.line + | ||
":" + | ||
loc.column + | ||
")"; | ||
const error = new SyntaxError(msg); | ||
@@ -22,0 +27,0 @@ error.loc = loc; |
141
src/util.js
"use strict"; | ||
var assert = require("assert"); | ||
@@ -58,3 +59,4 @@ var types = require("ast-types"); | ||
function isExportDeclaration(node) { | ||
if (node) switch (node.type) { | ||
if (node) | ||
switch (node.type) { | ||
case "ExportDeclaration": | ||
@@ -88,9 +90,58 @@ case "ExportDefaultDeclaration": | ||
function skipNewLineForward(text, index) { | ||
// What the "end" location points to is not consistent in parsers. | ||
// For some statement/expressions, it's the character immediately | ||
// afterward. For others, it's the last character in it. We need to | ||
// scan until we hit a newline in order to skip it. | ||
while (index < text.length) { | ||
function skip(chars) { | ||
return (text, index, opts) => { | ||
const backwards = opts && opts.backwards; | ||
// Allow `skip` functions to be threaded together without having | ||
// to check for failures (did someone say monads?). | ||
if (index === false) { | ||
return false; | ||
} | ||
const length = text.length; | ||
let cursor = index; | ||
while (cursor >= 0 && cursor < length) { | ||
const c = text.charAt(cursor); | ||
if (chars instanceof RegExp) { | ||
if (!chars.test(c)) { | ||
return cursor; | ||
} | ||
} else if (chars.indexOf(c) === -1) { | ||
return cursor; | ||
} | ||
backwards ? cursor-- : cursor++; | ||
} | ||
if (cursor === -1 || cursor === length) { | ||
// If we reached the beginning or end of the file, return the | ||
// out-of-bounds cursor. It's up to the caller to handle this | ||
// correctly. We don't want to indicate `false` though if it | ||
// actually skipped valid characters. | ||
return cursor; | ||
} | ||
return false; | ||
}; | ||
} | ||
const skipWhitespace = skip(/\s/); | ||
const skipSpaces = skip(" \t"); | ||
const skipToLineEnd = skip("; \t"); | ||
// This one doesn't use the above helper function because it wants to | ||
// test \r\n in order and `skip` doesn't support ordering and we only | ||
// want to skip one newline. It's simple to implement. | ||
function skipNewline(text, index, opts) { | ||
const backwards = opts && opts.backwards; | ||
if (index === false) { | ||
return false; | ||
} else if (backwards) { | ||
if (text.charAt(index) === "\n") { | ||
return index - 1; | ||
} | ||
if (text.charAt(index - 1) === "\r" && text.charAt(index) === "\n") { | ||
return index - 2; | ||
} | ||
} else { | ||
if (text.charAt(index) === "\n") { | ||
return index + 1; | ||
@@ -101,49 +152,20 @@ } | ||
} | ||
index++; | ||
} | ||
return index; | ||
} | ||
function findNewline(text, index, backwards) { | ||
const length = text.length; | ||
let cursor = backwards ? index - 1 : skipNewLineForward(text, index); | ||
// Look forward and see if there is a newline after/before this code | ||
// by scanning up/back to the next non-indentation character. | ||
while (cursor > 0 && cursor < length) { | ||
const c = text.charAt(cursor); | ||
// Skip any indentation characters | ||
if (c !== " " && c !== "\t") { | ||
// Check if the next non-indentation character is a newline or | ||
// not. | ||
return c === "\n" || c === "\r"; | ||
} | ||
backwards ? cursor-- : cursor++; | ||
} | ||
return false; | ||
function hasNewline(text, index, opts) { | ||
opts = opts || {}; | ||
const idx = skipSpaces(text, opts.backwards ? index - 1 : index, opts); | ||
const idx2 = skipNewline(text, idx, opts); | ||
return idx !== idx2; | ||
} | ||
function newlineExistsBefore(text, index) { | ||
return findNewline(text, index, true); | ||
function hasSpaces(text, index, opts) { | ||
opts = opts || {}; | ||
const idx = skipSpaces(text, opts.backwards ? index - 1 : index, opts); | ||
return idx !== index; | ||
} | ||
function newlineExistsAfter(text, index) { | ||
return findNewline(text, index); | ||
} | ||
function skipSpaces(text, index, backwards) { | ||
const length = text.length; | ||
let cursor = backwards ? index - 1 : index; | ||
// Look forward and see if there is a newline after/before this code | ||
// by scanning up/back to the next non-indentation character. | ||
while (cursor > 0 && cursor < length) { | ||
const c = text.charAt(cursor); | ||
// Skip any whitespace chars | ||
if (!c.match(/\S/)) { | ||
return cursor; | ||
} | ||
backwards ? cursor-- : cursor++; | ||
} | ||
return false; | ||
} | ||
function locStart(node) { | ||
@@ -201,12 +223,12 @@ if (node.range) { | ||
[ | ||
[ "||" ], | ||
[ "&&" ], | ||
[ "|" ], | ||
[ "^" ], | ||
[ "&" ], | ||
[ "==", "===", "!=", "!==" ], | ||
[ "<", ">", "<=", ">=", "in", "instanceof" ], | ||
[ ">>", "<<", ">>>" ], | ||
[ "+", "-" ], | ||
[ "*", "/", "%", "**" ] | ||
["||"], | ||
["&&"], | ||
["|"], | ||
["^"], | ||
["&"], | ||
["==", "===", "!=", "!=="], | ||
["<", ">", "<=", ">=", "in", "instanceof"], | ||
[">>", "<<", ">>>"], | ||
["+", "-"], | ||
["*", "/", "%", "**"] | ||
].forEach(function(tier, i) { | ||
@@ -229,5 +251,8 @@ tier.forEach(function(op) { | ||
getLast, | ||
newlineExistsBefore, | ||
newlineExistsAfter, | ||
skipWhitespace, | ||
skipSpaces, | ||
skipToLineEnd, | ||
skipNewline, | ||
hasNewline, | ||
hasSpaces, | ||
locStart, | ||
@@ -234,0 +259,0 @@ locEnd, |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Git dependency
Supply chain riskContains a dependency which resolves to a remote git URL. Dependencies fetched from git URLs are not immutable can be used to inject untrusted code or reduce the likelihood of a reproducible install.
Found 1 instance in 1 package
861319
27
6911
266
0
9
+ Addedjest-validate@18.2.0
+ Addedast-types@0.9.4(transitive)
+ Addedjest-matcher-utils@18.1.0(transitive)
+ Addedjest-validate@18.2.0(transitive)
+ Addedleven@2.1.0(transitive)
+ Addedpretty-format@18.1.0(transitive)
Updatedast-types@0.9.4