Skip to content

Commit

Permalink
Fix: edge-cases of semi-style (#9560)
Browse files Browse the repository at this point in the history
* Fix: edge-cases of semi-style

* Fix: remove EmptyStatement entirely
  • Loading branch information
mysticatea authored and aladdin-add committed Nov 7, 2017
1 parent e5a37ce commit 7e3bf6a
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 20 deletions.
63 changes: 44 additions & 19 deletions lib/rules/semi-style.js
Expand Up @@ -18,13 +18,51 @@ const astUtils = require("../ast-utils");
const SELECTOR = `:matches(${
[
"BreakStatement", "ContinueStatement", "DebuggerStatement",
"DoWhileStatement", "EmptyStatement", "ExportAllDeclaration",
"DoWhileStatement", "ExportAllDeclaration",
"ExportDefaultDeclaration", "ExportNamedDeclaration",
"ExpressionStatement", "ImportDeclaration", "ReturnStatement",
"ThrowStatement", "VariableDeclaration"
].join(",")
})`;

/**
* Get the child node list of a given node.
* This returns `Program#body`, `BlockStatement#body`, or `SwitchCase#consequent`.
* This is used to check whether a node is the first/last child.
* @param {Node} node A node to get child node list.
* @returns {Node[]|null} The child node list.
*/
function getChildren(node) {
const t = node.type;

if (t === "BlockStatement" || t === "Program") {
return node.body;
}
if (t === "SwitchCase") {
return node.consequent;
}
return null;
}

/**
* Check whether a given node is the last statement in the parent block.
* @param {Node} node A node to check.
* @returns {boolean} `true` if the node is the last statement in the parent block.
*/
function isLastChild(node) {
const t = node.parent.type;

if (t === "IfStatement" && node.parent.consequent === node && node.parent.alternate) { // before `else` keyword.
return true;
}
if (t === "DoWhileStatement") { // before `while` keyword.
return true;
}
const nodeList = getChildren(node.parent);

return nodeList !== null && nodeList[nodeList.length - 1] === node; // before `}` or etc.
}

module.exports = {
meta: {
docs: {
Expand All @@ -40,23 +78,6 @@ module.exports = {
const sourceCode = context.getSourceCode();
const option = context.options[0] || "last";

/**
* Check whether comments exist between the given 2 tokens.
* @param {Token} left The left token to check.
* @param {Token} right The right token to check.
* @returns {boolean} `true` if comments exist between the given 2 tokens.
*/
function commentsExistBetween(left, right) {
return sourceCode.getFirstTokenBetween(
left,
right,
{
includeComments: true,
filter: astUtils.isCommentToken
}
) !== null;
}

/**
* Check the given semicolon token.
* @param {Token} semiToken The semicolon token to check.
Expand All @@ -79,7 +100,7 @@ module.exports = {
: "the beginning of the next line"
},
fix(fixer) {
if (prevToken && nextToken && commentsExistBetween(prevToken, nextToken)) {
if (prevToken && nextToken && sourceCode.commentsExistBetween(prevToken, nextToken)) {
return null;
}

Expand All @@ -95,6 +116,10 @@ module.exports = {

return {
[SELECTOR](node) {
if (option === "first" && isLastChild(node)) {
return;
}

const lastToken = sourceCode.getLastToken(node);

if (astUtils.isSemicolonToken(lastToken)) {
Expand Down
87 changes: 86 additions & 1 deletion tests/lib/rules/semi-style.js
Expand Up @@ -27,18 +27,103 @@ ruleTester.run("semi-style", rule, {
"for(a;\nb;\nc);",
"for((a\n);\n(b\n);\n(c));",
"if(a)foo;\nbar",
{ code: ";", options: ["last"] },
{ code: ";foo;bar;baz;", options: ["last"] },
{ code: "foo;\nbar;", options: ["last"] },
{ code: "for(a;b;c);", options: ["last"] },
{ code: "for(a;\nb;\nc);", options: ["last"] },
{ code: "for((a\n);\n(b\n);\n(c));", options: ["last"] },
{ code: "if(a)foo;\nbar", options: ["last"] },
{ code: ";", options: ["first"] },
{ code: ";foo;bar;baz;", options: ["first"] },
{ code: "foo\n;bar;", options: ["first"] },
{ code: "for(a;b;c);", options: ["first"] },
{ code: "for(a;\nb;\nc);", options: ["first"] },
{ code: "for((a\n);\n(b\n);\n(c));", options: ["first"] },
{ code: "if(a)foo\n;bar", options: ["first"] }

// edge cases
{
code: `
{
;
}
`,
options: ["first"]
},
{
code: `
while (a)
;
foo
`,
options: ["first"]
},
{
code: `
do
;
while (a)
`,
options: ["first"]
},
{
code: `
do
foo;
while (a)
`,
options: ["first"]
},
{
code: `
if (a)
foo;
else
bar
`,
options: ["first"]
},
{
code: `
if (a)
foo
;bar
`,
options: ["first"]
},
{
code: `
{
;
}
`,
options: ["last"]
},
{
code: `
switch (a) {
case 1:
;foo
}
`,
options: ["last"]
},
{
code: `
while (a)
;
foo
`,
options: ["last"]
},
{
code: `
do
;
while (a)
`,
options: ["last"]
}
],
invalid: [
{
Expand Down

0 comments on commit 7e3bf6a

Please sign in to comment.