Enable no-semi mode and protect against ASI failures (#1129)

master
Alex Rattray 2017-04-11 13:50:47 -07:00 committed by Christopher Chedeau
parent e3aa878187
commit 36bec87d17
6 changed files with 1308 additions and 51 deletions

View File

@ -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
});
```

View File

@ -11,7 +11,8 @@ var defaults = {
trailingComma: "none",
bracketSpacing: true,
jsxBracketSameLine: false,
parser: "babylon"
parser: "babylon",
semi: true
};
var exampleConfig = Object.assign({}, defaults, {

View File

@ -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)) {

View File

@ -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)
"
`;

View File

@ -0,0 +1,3 @@
run_spec(__dirname);
run_spec(__dirname, { semi: false, parser: "flow" });
run_spec(__dirname, { semi: false, parser: "babylon" });

152
tests/no-semi/no-semi.js Normal file
View File

@ -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)