Enable no-semi mode and protect against ASI failures (#1129)
parent
e3aa878187
commit
36bec87d17
|
@ -241,7 +241,11 @@ prettier.format(source, {
|
|||
jsxBracketSameLine: false,
|
||||
|
||||
// Which parser to use. Valid options are 'flow' and 'babylon'
|
||||
parser: 'babylon'
|
||||
parser: 'babylon',
|
||||
|
||||
// Whether to add a semicolon at the end of every line (semi: true),
|
||||
// or only at the beginning of lines that may introduce ASI failures (semi: false)
|
||||
semi: true
|
||||
});
|
||||
```
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ var defaults = {
|
|||
trailingComma: "none",
|
||||
bracketSpacing: true,
|
||||
jsxBracketSameLine: false,
|
||||
parser: "babylon"
|
||||
parser: "babylon",
|
||||
semi: true
|
||||
};
|
||||
|
||||
var exampleConfig = Object.assign({}, defaults, {
|
||||
|
|
230
src/printer.js
230
src/printer.js
|
@ -109,6 +109,14 @@ function genericPrint(path, options, printPath, args) {
|
|||
needsParens = path.needsParens();
|
||||
}
|
||||
|
||||
if (node.type) {
|
||||
// HACK: ASI prevention in no-semi mode relies on knowledge of whether
|
||||
// or not a paren has been inserted (see `exprNeedsASIProtection()`).
|
||||
// For now, we're just passing that information by mutating the AST here,
|
||||
// but it would be nice to find a cleaner way to do this.
|
||||
node.needsParens = needsParens;
|
||||
}
|
||||
|
||||
if (needsParens) {
|
||||
parts.unshift("(");
|
||||
}
|
||||
|
@ -124,6 +132,9 @@ function genericPrint(path, options, printPath, args) {
|
|||
|
||||
function genericPrintNoParens(path, options, print, args) {
|
||||
var n = path.getValue();
|
||||
const semi = options.semi
|
||||
? ";"
|
||||
: "";
|
||||
|
||||
if (!n) {
|
||||
return "";
|
||||
|
@ -146,7 +157,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
if (n.directives) {
|
||||
path.each(
|
||||
function(childPath) {
|
||||
parts.push(print(childPath), ";", hardline);
|
||||
parts.push(print(childPath), semi, hardline);
|
||||
if (
|
||||
util.isNextLineEmpty(options.originalText, childPath.getValue())
|
||||
) {
|
||||
|
@ -181,7 +192,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
case "EmptyStatement":
|
||||
return "";
|
||||
case "ExpressionStatement":
|
||||
return concat([path.call(print, "expression"), ";"]); // Babel extension.
|
||||
return concat([path.call(print, "expression"), semi]); // Babel extension.
|
||||
case "ParenthesizedExpression":
|
||||
return concat(["(", path.call(print, "expression"), ")"]);
|
||||
case "AssignmentExpression":
|
||||
|
@ -295,17 +306,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
parts.push(path.call(print, "typeParameters"));
|
||||
}
|
||||
|
||||
if (
|
||||
n.params.length === 1 &&
|
||||
!n.rest &&
|
||||
n.params[0].type === "Identifier" &&
|
||||
!n.params[0].typeAnnotation &&
|
||||
!n.params[0].leadingComments &&
|
||||
!n.params[0].trailingComments &&
|
||||
!n.params[0].optional &&
|
||||
!n.predicate &&
|
||||
!n.returnType
|
||||
) {
|
||||
if (canPrintParamsWithoutParens(n)) {
|
||||
parts.push(path.call(print, "params", 0));
|
||||
} else {
|
||||
parts.push(
|
||||
|
@ -461,7 +462,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
parts.push(" as ", path.call(print, "exported"));
|
||||
}
|
||||
|
||||
parts.push(" from ", path.call(print, "source"), ";");
|
||||
parts.push(" from ", path.call(print, "source"), semi);
|
||||
|
||||
return concat(parts);
|
||||
case "ExportNamespaceSpecifier":
|
||||
|
@ -526,7 +527,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
parts.push("{} from ");
|
||||
}
|
||||
|
||||
fromParts.push(path.call(print, "source"), ";");
|
||||
fromParts.push(path.call(print, "source"), semi);
|
||||
|
||||
// If there's a very long import, break the following way:
|
||||
//
|
||||
|
@ -581,7 +582,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
function(childPath) {
|
||||
parts.push(
|
||||
indent(
|
||||
concat([hardline, print(childPath), ";"])
|
||||
concat([hardline, print(childPath), semi])
|
||||
)
|
||||
);
|
||||
},
|
||||
|
@ -647,7 +648,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
);
|
||||
}
|
||||
|
||||
parts.push(";");
|
||||
parts.push(semi);
|
||||
|
||||
return concat(parts);
|
||||
case "CallExpression": {
|
||||
|
@ -986,7 +987,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
namedTypes.ForAwaitStatement.check(parentNode));
|
||||
|
||||
if (!(isParentForLoop && parentNode.body !== n)) {
|
||||
parts.push(";");
|
||||
parts.push(semi);
|
||||
}
|
||||
|
||||
return group(concat(parts));
|
||||
|
@ -1132,9 +1133,9 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
const hasBraces = isCurlyBracket(clause);
|
||||
|
||||
if (hasBraces) parts.push(" while");
|
||||
else parts.push(concat([line, "while"]));
|
||||
else parts.push(concat([hardline, "while"]));
|
||||
|
||||
parts.push(" (", path.call(print, "test"), ");");
|
||||
parts.push(" (", path.call(print, "test"), ")", semi);
|
||||
|
||||
return concat(parts);
|
||||
case "DoExpression":
|
||||
|
@ -1144,7 +1145,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
|
||||
if (n.label) parts.push(" ", path.call(print, "label"));
|
||||
|
||||
parts.push(";");
|
||||
parts.push(semi);
|
||||
|
||||
return concat(parts);
|
||||
case "ContinueStatement":
|
||||
|
@ -1152,7 +1153,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
|
||||
if (n.label) parts.push(" ", path.call(print, "label"));
|
||||
|
||||
parts.push(";");
|
||||
parts.push(semi);
|
||||
|
||||
return concat(parts);
|
||||
case "LabeledStatement":
|
||||
|
@ -1195,7 +1196,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
|
||||
return concat(parts);
|
||||
case "ThrowStatement":
|
||||
return concat(["throw ", path.call(print, "argument"), ";"]);
|
||||
return concat(["throw ", path.call(print, "argument"), semi]);
|
||||
// Note: ignoring n.lexical because it has no printing consequences.
|
||||
case "SwitchStatement":
|
||||
return concat([
|
||||
|
@ -1238,7 +1239,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
return concat(parts);
|
||||
// JSX extensions below.
|
||||
case "DebuggerStatement":
|
||||
return "debugger;";
|
||||
return concat(["debugger", semi]);
|
||||
case "JSXAttribute":
|
||||
parts.push(path.call(print, "name"));
|
||||
|
||||
|
@ -1396,7 +1397,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
case "ClassPropertyDefinition":
|
||||
parts.push("static ", path.call(print, "definition"));
|
||||
|
||||
if (!namedTypes.MethodDefinition.check(n.definition)) parts.push(";");
|
||||
if (!namedTypes.MethodDefinition.check(n.definition)) parts.push(semi);
|
||||
|
||||
return concat(parts);
|
||||
case "ClassProperty":
|
||||
|
@ -1428,7 +1429,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
|
||||
if (n.value) parts.push(" = ", path.call(print, "value"));
|
||||
|
||||
parts.push(";");
|
||||
parts.push(semi);
|
||||
|
||||
return concat(parts);
|
||||
case "ClassDeclaration":
|
||||
|
@ -1572,7 +1573,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
path.call(print, "id"),
|
||||
n.predicate ? " " : "",
|
||||
path.call(print, "predicate"),
|
||||
";"
|
||||
semi
|
||||
]);
|
||||
case "DeclareModule":
|
||||
return printFlowDeclaration(path, [
|
||||
|
@ -1585,10 +1586,10 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
return printFlowDeclaration(path, [
|
||||
"module.exports",
|
||||
path.call(print, "typeAnnotation"),
|
||||
";"
|
||||
semi
|
||||
]);
|
||||
case "DeclareVariable":
|
||||
return printFlowDeclaration(path, ["var ", path.call(print, "id"), ";"]);
|
||||
return printFlowDeclaration(path, ["var ", path.call(print, "id"), semi]);
|
||||
case "DeclareExportAllDeclaration":
|
||||
return concat(["declare export * from ", path.call(print, "source")]);
|
||||
case "DeclareExportDeclaration":
|
||||
|
@ -1828,7 +1829,7 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
concat([hardline, path.call(print, "right")])
|
||||
)
|
||||
: concat([" ", path.call(print, "right")]),
|
||||
";"
|
||||
semi
|
||||
);
|
||||
|
||||
return concat(parts);
|
||||
|
@ -1998,7 +1999,10 @@ function genericPrintNoParens(path, options, print, args) {
|
|||
function printStatementSequence(path, options, print) {
|
||||
let printed = [];
|
||||
|
||||
path.map(stmtPath => {
|
||||
const bodyNode = path.getNode();
|
||||
const isClass = bodyNode.type === "ClassBody";
|
||||
|
||||
path.map((stmtPath, i) => {
|
||||
var stmt = stmtPath.getValue();
|
||||
|
||||
// Just in case the AST has been modified to contain falsy
|
||||
|
@ -2017,8 +2021,24 @@ function printStatementSequence(path, options, print) {
|
|||
const text = options.originalText;
|
||||
const parts = [];
|
||||
|
||||
// in no-semi mode, prepend statement with semicolon if it might break ASI
|
||||
if (!options.semi && !isClass && stmtNeedsASIProtection(stmtPath)) {
|
||||
parts.push(";");
|
||||
}
|
||||
|
||||
parts.push(stmtPrinted);
|
||||
|
||||
if (!options.semi && isClass) {
|
||||
if (classPropMayCauseASIProblems(stmtPath)) {
|
||||
parts.push(";");
|
||||
} else if (stmt.type === "ClassProperty") {
|
||||
const nextChild = bodyNode.body[i + 1];
|
||||
if (classChildNeedsASIProtection(nextChild)) {
|
||||
parts.push(";");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (util.isNextLineEmpty(text, stmt) && !isLastStatement(stmtPath)) {
|
||||
parts.push(hardline);
|
||||
}
|
||||
|
@ -2324,6 +2344,20 @@ function printFunctionParams(path, print, options) {
|
|||
]);
|
||||
}
|
||||
|
||||
function canPrintParamsWithoutParens(node) {
|
||||
return (
|
||||
node.params.length === 1 &&
|
||||
!node.rest &&
|
||||
node.params[0].type === "Identifier" &&
|
||||
!node.params[0].typeAnnotation &&
|
||||
!node.params[0].leadingComments &&
|
||||
!node.params[0].trailingComments &&
|
||||
!node.params[0].optional &&
|
||||
!node.predicate &&
|
||||
!node.returnType
|
||||
);
|
||||
}
|
||||
|
||||
function printFunctionDeclaration(path, print, options) {
|
||||
var n = path.getValue();
|
||||
var parts = [];
|
||||
|
@ -2414,6 +2448,7 @@ function typeIsFunction(type) {
|
|||
|
||||
function printExportDeclaration(path, options, print) {
|
||||
const decl = path.getValue();
|
||||
const semi = options.semi ? ";" : "";
|
||||
let parts = ["export "];
|
||||
|
||||
namedTypes.Declaration.assert(decl);
|
||||
|
@ -2434,7 +2469,7 @@ function printExportDeclaration(path, options, print) {
|
|||
(decl.declaration.type !== "ClassDeclaration" &&
|
||||
decl.declaration.type !== "FunctionDeclaration")
|
||||
) {
|
||||
parts.push(";");
|
||||
parts.push(semi);
|
||||
}
|
||||
} else {
|
||||
if (decl.specifiers && decl.specifiers.length > 0) {
|
||||
|
@ -2496,7 +2531,7 @@ function printExportDeclaration(path, options, print) {
|
|||
parts.push(" from ", path.call(print, "source"));
|
||||
}
|
||||
|
||||
parts.push(";");
|
||||
parts.push(semi);
|
||||
}
|
||||
|
||||
return concat(parts);
|
||||
|
@ -3346,6 +3381,118 @@ function hasLeadingOwnLineComment(text, node) {
|
|||
return res;
|
||||
}
|
||||
|
||||
function hasNakedLeftSide(node) {
|
||||
return (
|
||||
node.type === "AssignmentExpression" ||
|
||||
node.type === "BinaryExpression" ||
|
||||
node.type === "LogicalExpression" ||
|
||||
node.type === "ConditionalExpression" ||
|
||||
node.type === "CallExpression" ||
|
||||
node.type === "MemberExpression" ||
|
||||
node.type === "SequenceExpression" ||
|
||||
node.type === "TaggedTemplateExpression"
|
||||
);
|
||||
}
|
||||
|
||||
function getLeftSide(node) {
|
||||
if (node.expressions) {
|
||||
return node.expressions[0];
|
||||
}
|
||||
return node.left || node.test || node.callee || node.object || node.tag;
|
||||
}
|
||||
|
||||
function exprNeedsASIProtection(node) {
|
||||
// HACK: node.needsParens is added in `genericPrint()` for the sole purpose
|
||||
// of being used here. It'd be preferable to find a cleaner way to do this.
|
||||
const maybeASIProblem = node.needsParens ||
|
||||
node.type === "ParenthesizedExpression" ||
|
||||
node.type === "TypeCastExpression" ||
|
||||
(node.type === "ArrowFunctionExpression" &&
|
||||
!canPrintParamsWithoutParens(node)) ||
|
||||
node.type === "ArrayExpression" ||
|
||||
node.type === "ArrayPattern" ||
|
||||
(node.type === "UnaryExpression" &&
|
||||
node.prefix &&
|
||||
(node.operator === "+" || node.operator === "-")) ||
|
||||
node.type === "TemplateLiteral" ||
|
||||
node.type === "TemplateElement" ||
|
||||
node.type === "JSXElement" ||
|
||||
node.type === "RegExpLiteral" ||
|
||||
(node.type === "Literal" && node.pattern) ||
|
||||
(node.type === "Literal" && node.regex);
|
||||
|
||||
if (maybeASIProblem) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!hasNakedLeftSide(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return exprNeedsASIProtection(getLeftSide(node));
|
||||
}
|
||||
|
||||
function stmtNeedsASIProtection(path) {
|
||||
if (!path) return false;
|
||||
const node = path.getNode();
|
||||
|
||||
if (node.type !== "ExpressionStatement") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return exprNeedsASIProtection(node.expression);
|
||||
}
|
||||
|
||||
function classPropMayCauseASIProblems(path) {
|
||||
const node = path.getNode();
|
||||
|
||||
if (node.type !== "ClassProperty") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const name = node.key && node.key.name;
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// this isn't actually possible yet with most parsers available today
|
||||
// so isn't properly tested yet.
|
||||
if (name === "static" || name === "get" || name === "set") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function classChildNeedsASIProtection(node) {
|
||||
if (!node) return;
|
||||
|
||||
let isAsync, isGenerator;
|
||||
switch (node.type) {
|
||||
case "ClassProperty":
|
||||
return node.computed;
|
||||
|
||||
// flow
|
||||
case "MethodDefinition":
|
||||
// babylon
|
||||
case "ClassMethod": {
|
||||
const isAsync = node.value
|
||||
? node.value.async
|
||||
: node.async;
|
||||
const isGenerator = node.value
|
||||
? node.value.generator
|
||||
: node.generator;
|
||||
if (isAsync || node.static || node.kind === "get" || node.kind === "set") {
|
||||
return false;
|
||||
}
|
||||
if (node.computed || isGenerator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// This recurses the return argument, looking for the first token
|
||||
// (the leftmost leaf node) and, if it (or its parents) has any
|
||||
// leadingComments, returns true (so it can be wrapped in parens).
|
||||
|
@ -3354,22 +3501,7 @@ function returnArgumentHasLeadingComment(options, argument) {
|
|||
return true;
|
||||
}
|
||||
|
||||
const hasCommentableLeftSide = argument.type === "BinaryExpression" ||
|
||||
argument.type === "LogicalExpression" ||
|
||||
argument.type === "ConditionalExpression" ||
|
||||
argument.type === "CallExpression" ||
|
||||
argument.type === "MemberExpression" ||
|
||||
argument.type === "SequenceExpression" ||
|
||||
argument.type === "TaggedTemplateExpression";
|
||||
|
||||
if (hasCommentableLeftSide) {
|
||||
const getLeftSide = (node) => {
|
||||
if (node.expressions) {
|
||||
return node.expressions[0];
|
||||
}
|
||||
return node.left || node.test || node.callee || node.object || node.tag;
|
||||
}
|
||||
|
||||
if (hasNakedLeftSide(argument)) {
|
||||
let leftMost = argument;
|
||||
let newLeftMost;
|
||||
while (newLeftMost = getLeftSide(leftMost)) {
|
||||
|
|
|
@ -0,0 +1,965 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`no-semi.js 1`] = `
|
||||
"
|
||||
// with preexisting semi
|
||||
|
||||
x; [1, 2, 3].forEach(fn)
|
||||
x; [a, b, ...c] = [1, 2]
|
||||
x; /r/i.test('r')
|
||||
x; +1
|
||||
x; - 1
|
||||
x; ('h' + 'i').repeat(10)
|
||||
x; (1, 2)
|
||||
x; (() => {})()
|
||||
x; ({ a: 1 }).entries()
|
||||
x; ({ a: 1 }).entries()
|
||||
x; <Hello />
|
||||
x; \`string\`
|
||||
x; (x, y) => x
|
||||
|
||||
// doesn't have to be preceded by a semicolon
|
||||
|
||||
class X {} [1, 2, 3].forEach(fn)
|
||||
|
||||
// TODO: enable lang features
|
||||
// x; ::b.c
|
||||
// TODO: upgrade parser
|
||||
// class A {
|
||||
// async; // The semicolon is *not* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class B {
|
||||
// static; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// get; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// set; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
|
||||
|
||||
// don't semicolon if it doesn't start statement
|
||||
|
||||
if (true) (() => {})()
|
||||
|
||||
class A {
|
||||
a = 0;
|
||||
[b](){}
|
||||
|
||||
c = 0;
|
||||
*d(){}
|
||||
|
||||
e = 0;
|
||||
[f] = 0
|
||||
|
||||
// none of the semicolons above this comment can be omitted.
|
||||
// none of the semicolons below this comment are necessary.
|
||||
|
||||
q() {};
|
||||
[h](){}
|
||||
|
||||
p() {};
|
||||
*i(){}
|
||||
|
||||
a = 1;
|
||||
get ['y']() {}
|
||||
|
||||
a = 1;
|
||||
static ['y']() {}
|
||||
|
||||
a = 1;
|
||||
set ['z'](z) {}
|
||||
|
||||
a = 1;
|
||||
async ['a']() {}
|
||||
|
||||
a = 1;
|
||||
async *g() {}
|
||||
|
||||
a = 0;
|
||||
b = 1;
|
||||
}
|
||||
|
||||
// being first/last shouldn't break things
|
||||
class G {
|
||||
x = 1
|
||||
}
|
||||
class G {
|
||||
x() {}
|
||||
}
|
||||
class G {
|
||||
*x() {}
|
||||
}
|
||||
class G {
|
||||
[x] = 1
|
||||
}
|
||||
|
||||
// check indentation
|
||||
|
||||
if (true) {
|
||||
x; (() => {})()
|
||||
}
|
||||
|
||||
// flow
|
||||
|
||||
(x: void);
|
||||
(y: void)
|
||||
|
||||
// check statement clauses
|
||||
|
||||
do break; while (false)
|
||||
if (true) do break; while (false)
|
||||
|
||||
if (true) 1; else 2
|
||||
for (;;) ;
|
||||
for (x of y) ;
|
||||
|
||||
debugger
|
||||
|
||||
// check that it doesn't break non-ASI
|
||||
|
||||
1
|
||||
- 1
|
||||
|
||||
1
|
||||
+ 1
|
||||
|
||||
1
|
||||
/ 1
|
||||
|
||||
arr
|
||||
[0]
|
||||
|
||||
fn
|
||||
(x)
|
||||
|
||||
!1
|
||||
|
||||
1
|
||||
< 1
|
||||
|
||||
tag
|
||||
\`string\`
|
||||
|
||||
x; x => x
|
||||
|
||||
while (false)
|
||||
(function(){}())
|
||||
|
||||
aReallyLongLine012345678901234567890123456789012345678901234567890123456789 *
|
||||
(b + c)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// with preexisting semi
|
||||
|
||||
x;
|
||||
[1, 2, 3].forEach(fn);
|
||||
x;
|
||||
[a, b, ...c] = [1, 2];
|
||||
x;
|
||||
/r/i.test(\\"r\\");
|
||||
x;
|
||||
+1;
|
||||
x;
|
||||
-1;
|
||||
x;
|
||||
(\\"h\\" + \\"i\\").repeat(10);
|
||||
x;
|
||||
1, 2;
|
||||
x;
|
||||
(() => {})();
|
||||
x;
|
||||
({ a: 1 }.entries());
|
||||
x;
|
||||
({ a: 1 }.entries());
|
||||
x;
|
||||
<Hello />;
|
||||
x;
|
||||
\`string\`;
|
||||
x;
|
||||
(x, y) => x;
|
||||
|
||||
// doesn't have to be preceded by a semicolon
|
||||
|
||||
class X {}
|
||||
[1, 2, 3].forEach(fn);
|
||||
|
||||
// TODO: enable lang features
|
||||
// x; ::b.c
|
||||
// TODO: upgrade parser
|
||||
// class A {
|
||||
// async; // The semicolon is *not* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class B {
|
||||
// static; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// get; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// set; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
|
||||
// don't semicolon if it doesn't start statement
|
||||
|
||||
if (true) (() => {})();
|
||||
|
||||
class A {
|
||||
a = 0;
|
||||
[b]() {}
|
||||
|
||||
c = 0;
|
||||
*d() {}
|
||||
|
||||
e = 0;
|
||||
[f] = 0;
|
||||
|
||||
// none of the semicolons above this comment can be omitted.
|
||||
// none of the semicolons below this comment are necessary.
|
||||
q() {}
|
||||
[h]() {}
|
||||
|
||||
p() {}
|
||||
*i() {}
|
||||
|
||||
a = 1;
|
||||
get [\\"y\\"]() {}
|
||||
|
||||
a = 1;
|
||||
static [\\"y\\"]() {}
|
||||
|
||||
a = 1;
|
||||
set [\\"z\\"](z) {}
|
||||
|
||||
a = 1;
|
||||
async [\\"a\\"]() {}
|
||||
|
||||
a = 1;
|
||||
async *g() {}
|
||||
|
||||
a = 0;
|
||||
b = 1;
|
||||
}
|
||||
|
||||
// being first/last shouldn't break things
|
||||
class G {
|
||||
x = 1;
|
||||
}
|
||||
class G {
|
||||
x() {}
|
||||
}
|
||||
class G {
|
||||
*x() {}
|
||||
}
|
||||
class G {
|
||||
[x] = 1;
|
||||
}
|
||||
|
||||
// check indentation
|
||||
|
||||
if (true) {
|
||||
x;
|
||||
(() => {})();
|
||||
}
|
||||
|
||||
// flow
|
||||
|
||||
(x: void);
|
||||
(y: void);
|
||||
|
||||
// check statement clauses
|
||||
|
||||
do
|
||||
break;
|
||||
while (false);
|
||||
if (true)
|
||||
do
|
||||
break;
|
||||
while (false);
|
||||
|
||||
if (true) 1;
|
||||
else 2;
|
||||
for (;;);
|
||||
for (x of y);
|
||||
|
||||
debugger;
|
||||
|
||||
// check that it doesn't break non-ASI
|
||||
|
||||
1 - 1;
|
||||
|
||||
1 + 1;
|
||||
|
||||
1 / 1;
|
||||
|
||||
arr[0];
|
||||
|
||||
fn(x);
|
||||
|
||||
!1;
|
||||
|
||||
1 < 1;
|
||||
|
||||
tag\`string\`;
|
||||
|
||||
x;
|
||||
x => x;
|
||||
|
||||
while (false)
|
||||
(function() {})();
|
||||
|
||||
aReallyLongLine012345678901234567890123456789012345678901234567890123456789 *
|
||||
(b + c);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`no-semi.js 2`] = `
|
||||
"
|
||||
// with preexisting semi
|
||||
|
||||
x; [1, 2, 3].forEach(fn)
|
||||
x; [a, b, ...c] = [1, 2]
|
||||
x; /r/i.test('r')
|
||||
x; +1
|
||||
x; - 1
|
||||
x; ('h' + 'i').repeat(10)
|
||||
x; (1, 2)
|
||||
x; (() => {})()
|
||||
x; ({ a: 1 }).entries()
|
||||
x; ({ a: 1 }).entries()
|
||||
x; <Hello />
|
||||
x; \`string\`
|
||||
x; (x, y) => x
|
||||
|
||||
// doesn't have to be preceded by a semicolon
|
||||
|
||||
class X {} [1, 2, 3].forEach(fn)
|
||||
|
||||
// TODO: enable lang features
|
||||
// x; ::b.c
|
||||
// TODO: upgrade parser
|
||||
// class A {
|
||||
// async; // The semicolon is *not* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class B {
|
||||
// static; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// get; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// set; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
|
||||
|
||||
// don't semicolon if it doesn't start statement
|
||||
|
||||
if (true) (() => {})()
|
||||
|
||||
class A {
|
||||
a = 0;
|
||||
[b](){}
|
||||
|
||||
c = 0;
|
||||
*d(){}
|
||||
|
||||
e = 0;
|
||||
[f] = 0
|
||||
|
||||
// none of the semicolons above this comment can be omitted.
|
||||
// none of the semicolons below this comment are necessary.
|
||||
|
||||
q() {};
|
||||
[h](){}
|
||||
|
||||
p() {};
|
||||
*i(){}
|
||||
|
||||
a = 1;
|
||||
get ['y']() {}
|
||||
|
||||
a = 1;
|
||||
static ['y']() {}
|
||||
|
||||
a = 1;
|
||||
set ['z'](z) {}
|
||||
|
||||
a = 1;
|
||||
async ['a']() {}
|
||||
|
||||
a = 1;
|
||||
async *g() {}
|
||||
|
||||
a = 0;
|
||||
b = 1;
|
||||
}
|
||||
|
||||
// being first/last shouldn't break things
|
||||
class G {
|
||||
x = 1
|
||||
}
|
||||
class G {
|
||||
x() {}
|
||||
}
|
||||
class G {
|
||||
*x() {}
|
||||
}
|
||||
class G {
|
||||
[x] = 1
|
||||
}
|
||||
|
||||
// check indentation
|
||||
|
||||
if (true) {
|
||||
x; (() => {})()
|
||||
}
|
||||
|
||||
// flow
|
||||
|
||||
(x: void);
|
||||
(y: void)
|
||||
|
||||
// check statement clauses
|
||||
|
||||
do break; while (false)
|
||||
if (true) do break; while (false)
|
||||
|
||||
if (true) 1; else 2
|
||||
for (;;) ;
|
||||
for (x of y) ;
|
||||
|
||||
debugger
|
||||
|
||||
// check that it doesn't break non-ASI
|
||||
|
||||
1
|
||||
- 1
|
||||
|
||||
1
|
||||
+ 1
|
||||
|
||||
1
|
||||
/ 1
|
||||
|
||||
arr
|
||||
[0]
|
||||
|
||||
fn
|
||||
(x)
|
||||
|
||||
!1
|
||||
|
||||
1
|
||||
< 1
|
||||
|
||||
tag
|
||||
\`string\`
|
||||
|
||||
x; x => x
|
||||
|
||||
while (false)
|
||||
(function(){}())
|
||||
|
||||
aReallyLongLine012345678901234567890123456789012345678901234567890123456789 *
|
||||
(b + c)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// with preexisting semi
|
||||
|
||||
x
|
||||
;[1, 2, 3].forEach(fn)
|
||||
x
|
||||
;[a, b, ...c] = [1, 2]
|
||||
x
|
||||
;/r/i.test(\\"r\\")
|
||||
x
|
||||
;+1
|
||||
x
|
||||
;-1
|
||||
x
|
||||
;(\\"h\\" + \\"i\\").repeat(10)
|
||||
x
|
||||
1, 2
|
||||
x
|
||||
;(() => {})()
|
||||
x
|
||||
;({ a: 1 }.entries())
|
||||
x
|
||||
;({ a: 1 }.entries())
|
||||
x
|
||||
;<Hello />
|
||||
x
|
||||
;\`string\`
|
||||
x
|
||||
;(x, y) => x
|
||||
|
||||
// doesn't have to be preceded by a semicolon
|
||||
|
||||
class X {}
|
||||
;[1, 2, 3].forEach(fn)
|
||||
|
||||
// TODO: enable lang features
|
||||
// x; ::b.c
|
||||
// TODO: upgrade parser
|
||||
// class A {
|
||||
// async; // The semicolon is *not* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class B {
|
||||
// static; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// get; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// set; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
|
||||
// don't semicolon if it doesn't start statement
|
||||
|
||||
if (true) (() => {})()
|
||||
|
||||
class A {
|
||||
a = 0;
|
||||
[b]() {}
|
||||
|
||||
c = 0;
|
||||
*d() {}
|
||||
|
||||
e = 0;
|
||||
[f] = 0
|
||||
|
||||
// none of the semicolons above this comment can be omitted.
|
||||
// none of the semicolons below this comment are necessary.
|
||||
q() {}
|
||||
[h]() {}
|
||||
|
||||
p() {}
|
||||
*i() {}
|
||||
|
||||
a = 1
|
||||
get [\\"y\\"]() {}
|
||||
|
||||
a = 1
|
||||
static [\\"y\\"]() {}
|
||||
|
||||
a = 1
|
||||
set [\\"z\\"](z) {}
|
||||
|
||||
a = 1
|
||||
async [\\"a\\"]() {}
|
||||
|
||||
a = 1
|
||||
async *g() {}
|
||||
|
||||
a = 0
|
||||
b = 1
|
||||
}
|
||||
|
||||
// being first/last shouldn't break things
|
||||
class G {
|
||||
x = 1
|
||||
}
|
||||
class G {
|
||||
x() {}
|
||||
}
|
||||
class G {
|
||||
*x() {}
|
||||
}
|
||||
class G {
|
||||
[x] = 1
|
||||
}
|
||||
|
||||
// check indentation
|
||||
|
||||
if (true) {
|
||||
x
|
||||
;(() => {})()
|
||||
}
|
||||
|
||||
;// flow
|
||||
|
||||
(x: void)
|
||||
;(y: void)
|
||||
|
||||
// check statement clauses
|
||||
|
||||
do
|
||||
break
|
||||
while (false)
|
||||
if (true)
|
||||
do
|
||||
break
|
||||
while (false)
|
||||
|
||||
if (true) 1
|
||||
else 2
|
||||
for (;;);
|
||||
for (x of y);
|
||||
|
||||
debugger
|
||||
|
||||
// check that it doesn't break non-ASI
|
||||
|
||||
1 - 1
|
||||
|
||||
1 + 1
|
||||
|
||||
1 / 1
|
||||
|
||||
arr[0]
|
||||
|
||||
fn(x)
|
||||
|
||||
!1
|
||||
|
||||
1 < 1
|
||||
|
||||
tag\`string\`
|
||||
|
||||
x
|
||||
x => x
|
||||
|
||||
while (false)
|
||||
(function() {})()
|
||||
|
||||
aReallyLongLine012345678901234567890123456789012345678901234567890123456789 *
|
||||
(b + c)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`no-semi.js 3`] = `
|
||||
"
|
||||
// with preexisting semi
|
||||
|
||||
x; [1, 2, 3].forEach(fn)
|
||||
x; [a, b, ...c] = [1, 2]
|
||||
x; /r/i.test('r')
|
||||
x; +1
|
||||
x; - 1
|
||||
x; ('h' + 'i').repeat(10)
|
||||
x; (1, 2)
|
||||
x; (() => {})()
|
||||
x; ({ a: 1 }).entries()
|
||||
x; ({ a: 1 }).entries()
|
||||
x; <Hello />
|
||||
x; \`string\`
|
||||
x; (x, y) => x
|
||||
|
||||
// doesn't have to be preceded by a semicolon
|
||||
|
||||
class X {} [1, 2, 3].forEach(fn)
|
||||
|
||||
// TODO: enable lang features
|
||||
// x; ::b.c
|
||||
// TODO: upgrade parser
|
||||
// class A {
|
||||
// async; // The semicolon is *not* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class B {
|
||||
// static; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// get; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// set; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
|
||||
|
||||
// don't semicolon if it doesn't start statement
|
||||
|
||||
if (true) (() => {})()
|
||||
|
||||
class A {
|
||||
a = 0;
|
||||
[b](){}
|
||||
|
||||
c = 0;
|
||||
*d(){}
|
||||
|
||||
e = 0;
|
||||
[f] = 0
|
||||
|
||||
// none of the semicolons above this comment can be omitted.
|
||||
// none of the semicolons below this comment are necessary.
|
||||
|
||||
q() {};
|
||||
[h](){}
|
||||
|
||||
p() {};
|
||||
*i(){}
|
||||
|
||||
a = 1;
|
||||
get ['y']() {}
|
||||
|
||||
a = 1;
|
||||
static ['y']() {}
|
||||
|
||||
a = 1;
|
||||
set ['z'](z) {}
|
||||
|
||||
a = 1;
|
||||
async ['a']() {}
|
||||
|
||||
a = 1;
|
||||
async *g() {}
|
||||
|
||||
a = 0;
|
||||
b = 1;
|
||||
}
|
||||
|
||||
// being first/last shouldn't break things
|
||||
class G {
|
||||
x = 1
|
||||
}
|
||||
class G {
|
||||
x() {}
|
||||
}
|
||||
class G {
|
||||
*x() {}
|
||||
}
|
||||
class G {
|
||||
[x] = 1
|
||||
}
|
||||
|
||||
// check indentation
|
||||
|
||||
if (true) {
|
||||
x; (() => {})()
|
||||
}
|
||||
|
||||
// flow
|
||||
|
||||
(x: void);
|
||||
(y: void)
|
||||
|
||||
// check statement clauses
|
||||
|
||||
do break; while (false)
|
||||
if (true) do break; while (false)
|
||||
|
||||
if (true) 1; else 2
|
||||
for (;;) ;
|
||||
for (x of y) ;
|
||||
|
||||
debugger
|
||||
|
||||
// check that it doesn't break non-ASI
|
||||
|
||||
1
|
||||
- 1
|
||||
|
||||
1
|
||||
+ 1
|
||||
|
||||
1
|
||||
/ 1
|
||||
|
||||
arr
|
||||
[0]
|
||||
|
||||
fn
|
||||
(x)
|
||||
|
||||
!1
|
||||
|
||||
1
|
||||
< 1
|
||||
|
||||
tag
|
||||
\`string\`
|
||||
|
||||
x; x => x
|
||||
|
||||
while (false)
|
||||
(function(){}())
|
||||
|
||||
aReallyLongLine012345678901234567890123456789012345678901234567890123456789 *
|
||||
(b + c)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// with preexisting semi
|
||||
|
||||
x
|
||||
;[1, 2, 3].forEach(fn)
|
||||
x
|
||||
;[a, b, ...c] = [1, 2]
|
||||
x
|
||||
;/r/i.test(\\"r\\")
|
||||
x
|
||||
;+1
|
||||
x
|
||||
;-1
|
||||
x
|
||||
;(\\"h\\" + \\"i\\").repeat(10)
|
||||
x
|
||||
1, 2
|
||||
x
|
||||
;(() => {})()
|
||||
x
|
||||
;({ a: 1 }.entries())
|
||||
x
|
||||
;({ a: 1 }.entries())
|
||||
x
|
||||
;<Hello />
|
||||
x
|
||||
;\`string\`
|
||||
x
|
||||
;(x, y) => x
|
||||
|
||||
// doesn't have to be preceded by a semicolon
|
||||
|
||||
class X {}
|
||||
;[1, 2, 3].forEach(fn)
|
||||
|
||||
// TODO: enable lang features
|
||||
// x; ::b.c
|
||||
// TODO: upgrade parser
|
||||
// class A {
|
||||
// async; // The semicolon is *not* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class B {
|
||||
// static; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// get; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// set; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
|
||||
// don't semicolon if it doesn't start statement
|
||||
|
||||
if (true) (() => {})()
|
||||
|
||||
class A {
|
||||
a = 0;
|
||||
[b]() {}
|
||||
|
||||
c = 0;
|
||||
*d() {}
|
||||
|
||||
e = 0;
|
||||
[f] = 0
|
||||
|
||||
// none of the semicolons above this comment can be omitted.
|
||||
// none of the semicolons below this comment are necessary.
|
||||
|
||||
q() {}
|
||||
[h]() {}
|
||||
|
||||
p() {}
|
||||
*i() {}
|
||||
|
||||
a = 1
|
||||
get [\\"y\\"]() {}
|
||||
|
||||
a = 1
|
||||
static [\\"y\\"]() {}
|
||||
|
||||
a = 1
|
||||
set [\\"z\\"](z) {}
|
||||
|
||||
a = 1
|
||||
async [\\"a\\"]() {}
|
||||
|
||||
a = 1
|
||||
async *g() {}
|
||||
|
||||
a = 0
|
||||
b = 1
|
||||
}
|
||||
|
||||
// being first/last shouldn't break things
|
||||
class G {
|
||||
x = 1
|
||||
}
|
||||
class G {
|
||||
x() {}
|
||||
}
|
||||
class G {
|
||||
*x() {}
|
||||
}
|
||||
class G {
|
||||
[x] = 1
|
||||
}
|
||||
|
||||
// check indentation
|
||||
|
||||
if (true) {
|
||||
x
|
||||
;(() => {})()
|
||||
}
|
||||
|
||||
;// flow
|
||||
|
||||
(x: void)
|
||||
;(y: void)
|
||||
|
||||
// check statement clauses
|
||||
|
||||
do
|
||||
break
|
||||
while (false)
|
||||
if (true)
|
||||
do
|
||||
break
|
||||
while (false)
|
||||
|
||||
if (true) 1
|
||||
else 2
|
||||
for (;;);
|
||||
for (x of y);
|
||||
|
||||
debugger
|
||||
|
||||
// check that it doesn't break non-ASI
|
||||
|
||||
1 - 1
|
||||
|
||||
1 + 1
|
||||
|
||||
1 / 1
|
||||
|
||||
arr[0]
|
||||
|
||||
fn(x)
|
||||
|
||||
!1
|
||||
|
||||
1 < 1
|
||||
|
||||
tag\`string\`
|
||||
|
||||
x
|
||||
x => x
|
||||
|
||||
while (false)
|
||||
(function() {})()
|
||||
|
||||
aReallyLongLine012345678901234567890123456789012345678901234567890123456789 *
|
||||
(b + c)
|
||||
"
|
||||
`;
|
|
@ -0,0 +1,3 @@
|
|||
run_spec(__dirname);
|
||||
run_spec(__dirname, { semi: false, parser: "flow" });
|
||||
run_spec(__dirname, { semi: false, parser: "babylon" });
|
|
@ -0,0 +1,152 @@
|
|||
|
||||
// with preexisting semi
|
||||
|
||||
x; [1, 2, 3].forEach(fn)
|
||||
x; [a, b, ...c] = [1, 2]
|
||||
x; /r/i.test('r')
|
||||
x; +1
|
||||
x; - 1
|
||||
x; ('h' + 'i').repeat(10)
|
||||
x; (1, 2)
|
||||
x; (() => {})()
|
||||
x; ({ a: 1 }).entries()
|
||||
x; ({ a: 1 }).entries()
|
||||
x; <Hello />
|
||||
x; `string`
|
||||
x; (x, y) => x
|
||||
|
||||
// doesn't have to be preceded by a semicolon
|
||||
|
||||
class X {} [1, 2, 3].forEach(fn)
|
||||
|
||||
// TODO: enable lang features
|
||||
// x; ::b.c
|
||||
// TODO: upgrade parser
|
||||
// class A {
|
||||
// async; // The semicolon is *not* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class B {
|
||||
// static; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// get; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
// class C {
|
||||
// set; // The semicolon *is* necessary
|
||||
// x(){}
|
||||
// }
|
||||
|
||||
|
||||
// don't semicolon if it doesn't start statement
|
||||
|
||||
if (true) (() => {})()
|
||||
|
||||
class A {
|
||||
a = 0;
|
||||
[b](){}
|
||||
|
||||
c = 0;
|
||||
*d(){}
|
||||
|
||||
e = 0;
|
||||
[f] = 0
|
||||
|
||||
// none of the semicolons above this comment can be omitted.
|
||||
// none of the semicolons below this comment are necessary.
|
||||
|
||||
q() {};
|
||||
[h](){}
|
||||
|
||||
p() {};
|
||||
*i(){}
|
||||
|
||||
a = 1;
|
||||
get ['y']() {}
|
||||
|
||||
a = 1;
|
||||
static ['y']() {}
|
||||
|
||||
a = 1;
|
||||
set ['z'](z) {}
|
||||
|
||||
a = 1;
|
||||
async ['a']() {}
|
||||
|
||||
a = 1;
|
||||
async *g() {}
|
||||
|
||||
a = 0;
|
||||
b = 1;
|
||||
}
|
||||
|
||||
// being first/last shouldn't break things
|
||||
class G {
|
||||
x = 1
|
||||
}
|
||||
class G {
|
||||
x() {}
|
||||
}
|
||||
class G {
|
||||
*x() {}
|
||||
}
|
||||
class G {
|
||||
[x] = 1
|
||||
}
|
||||
|
||||
// check indentation
|
||||
|
||||
if (true) {
|
||||
x; (() => {})()
|
||||
}
|
||||
|
||||
// flow
|
||||
|
||||
(x: void);
|
||||
(y: void)
|
||||
|
||||
// check statement clauses
|
||||
|
||||
do break; while (false)
|
||||
if (true) do break; while (false)
|
||||
|
||||
if (true) 1; else 2
|
||||
for (;;) ;
|
||||
for (x of y) ;
|
||||
|
||||
debugger
|
||||
|
||||
// check that it doesn't break non-ASI
|
||||
|
||||
1
|
||||
- 1
|
||||
|
||||
1
|
||||
+ 1
|
||||
|
||||
1
|
||||
/ 1
|
||||
|
||||
arr
|
||||
[0]
|
||||
|
||||
fn
|
||||
(x)
|
||||
|
||||
!1
|
||||
|
||||
1
|
||||
< 1
|
||||
|
||||
tag
|
||||
`string`
|
||||
|
||||
x; x => x
|
||||
|
||||
while (false)
|
||||
(function(){}())
|
||||
|
||||
aReallyLongLine012345678901234567890123456789012345678901234567890123456789 *
|
||||
(b + c)
|
Loading…
Reference in New Issue