Skip to content

Commit

Permalink
Update: Allow to omit semi for one-line blocks (fixes #4385)
Browse files Browse the repository at this point in the history
  • Loading branch information
alberto committed Dec 20, 2015
1 parent b6853a6 commit dfbfe21
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 10 deletions.
56 changes: 56 additions & 0 deletions docs/rules/semi.md
Expand Up @@ -61,6 +61,12 @@ This rule is aimed at ensuring consistent use of semicolons. You can decide whet

### Options

The rule takes one option, a string, which could be "always", "never" or "always-except-oneline". The default is "always".

You can set the option in configuration like this:

#### "always"

By using the default option, semicolons must be used any place where they are valid.

```json
Expand Down Expand Up @@ -91,6 +97,8 @@ object.method = function() {
};
```

#### "never"

If you want to enforce that semicolons are never used, switch the configuration to:

```json
Expand Down Expand Up @@ -133,6 +141,54 @@ var name = "ESLint"
})()
```

#### "always-except-oneline"

This option allows to omit the last semicolon when a block (including its braces) is contained in a single line.

By configuring the rule as:

```json
semi: [2, "always-except-oneline"]
```

The following patterns are considered problems:

```js
/*eslint semi: [2, "always-except-oneline"]*/

if (foo) { bar(); } /*error Extra semicolon.*/

if (foo) { bar(); baz(); } /*error Extra semicolon.*/

if (foo) {
bar() /*error Missing semicolon.*/
}

if (foo) {
bar();
baz() /*error Missing semicolon.*/
}
```

And the following patterns are not considered problems:

```js
/*eslint semi: [2, "always-except-oneline"]*/

if (foo) { bar() }

if (foo) { bar(); baz() }

if (foo) {
bar();
}

if (foo) {
bar();
baz();
}
```

## When Not To Use It

If you do not want to enforce semicolon usage (or omission) in any particular way, then you can turn this rule off.
Expand Down
43 changes: 33 additions & 10 deletions lib/rules/semi.js
Expand Up @@ -14,7 +14,8 @@ module.exports = function(context) {

var OPT_OUT_PATTERN = /[\[\(\/\+\-]/; // One of [(/+-

var always = context.options[0] !== "never",
var never = context.options[0] === "never",
exceptOneLine = context.options[0] === "always-except-oneline",
sourceCode = context.getSourceCode();

//--------------------------------------------------------------------------
Expand All @@ -24,15 +25,16 @@ module.exports = function(context) {
/**
* Reports a semicolon error with appropriate location and message.
* @param {ASTNode} node The node with an extra or missing semicolon.
* @param {boolean} missing True if the semicolon is missing.
* @returns {void}
*/
function report(node) {
function report(node, missing) {
var message,
fix,
lastToken = sourceCode.getLastToken(node),
loc = lastToken.loc;

if (always) {
if (!missing) {
message = "Missing semicolon.";
loc = loc.end;
fix = function(fixer) {
Expand Down Expand Up @@ -92,6 +94,22 @@ module.exports = function(context) {
return (lastTokenLine !== nextTokenLine && !isOptOutToken) || isDivider;
}

/**
* Checks a node to see if it's in a one-liner block statement.
* @param {ASTNode} node The node to check.
* @returns {boolean} whether the node is in a one-liner block statement.
*/
function isOneLinerBlock(node) {
var nextToken = context.getTokenAfter(node);
if (!nextToken || nextToken.value !== "}") {
return false;
}

var parent = node.parent;
return parent && parent.type === "BlockStatement" &&
parent.loc.start.line === parent.loc.end.line;
}

/**
* Checks a node to see if it's followed by a semicolon.
* @param {ASTNode} node The node to check.
Expand All @@ -100,13 +118,19 @@ module.exports = function(context) {
function checkForSemicolon(node) {
var lastToken = context.getLastToken(node);

if (always) {
if (!isSemicolon(lastToken)) {
report(node);
if (never) {
if (isUnnecessarySemicolon(lastToken)) {
report(node, true);
}
} else {
if (isUnnecessarySemicolon(lastToken)) {
report(node);
if (!isSemicolon(lastToken)) {
if (!exceptOneLine || !isOneLinerBlock(node)) {
report(node);
}
} else {
if (exceptOneLine && isOneLinerBlock(node)) {
report(node, true);
}
}
}
}
Expand All @@ -133,7 +157,6 @@ module.exports = function(context) {
//--------------------------------------------------------------------------

return {

"VariableDeclaration": checkForSemicolonForVariableDeclaration,
"ExpressionStatement": checkForSemicolon,
"ReturnStatement": checkForSemicolon,
Expand All @@ -160,6 +183,6 @@ module.exports = function(context) {

module.exports.schema = [
{
"enum": ["always", "never"]
"enum": ["always", "never", "always-except-oneline"]
}
];
10 changes: 10 additions & 0 deletions tests/lib/rules/semi.js
Expand Up @@ -46,6 +46,10 @@ ruleTester.run("semi", rule, {
{ code: "do{}while(true)", options: ["never"] },
{ code: "do{}while(true);", options: ["always"] },

{ code: "if (foo) { bar() }", options: ["always-except-oneline"] },
{ code: "if (foo) { bar(); baz() }", options: ["always-except-oneline"] },


// method definitions don't have a semicolon.
{ code: "class A { a() {} b() {} }", parserOptions: { ecmaVersion: 6 }},
{ code: "var A = class { a() {} b() {} };", parserOptions: { ecmaVersion: 6 }},
Expand Down Expand Up @@ -132,6 +136,12 @@ ruleTester.run("semi", rule, {
{ code: "import theDefault, { named1, named2 } from 'src/mylib';", output: "import theDefault, { named1, named2 } from 'src/mylib'", options: ["never"], parserOptions: { sourceType: "module" }, errors: [{ message: "Extra semicolon.", type: "ImportDeclaration"}] },
{ code: "do{}while(true);", output: "do{}while(true)", options: ["never"], errors: [{ message: "Extra semicolon.", type: "DoWhileStatement", line: 1}] },

{ code: "if (foo) { bar()\n }", options: ["always-except-oneline"], errors: [{ message: "Missing semicolon."}] },
{ code: "if (foo) {\n bar() }", options: ["always-except-oneline"], errors: [{ message: "Missing semicolon."}] },
{ code: "if (foo) {\n bar(); baz() }", options: ["always-except-oneline"], errors: [{ message: "Missing semicolon."}] },
{ code: "if (foo) { bar(); }", options: ["always-except-oneline"], errors: [{ message: "Extra semicolon."}] },


// exports, "always"
{ code: "export * from 'foo'", output: "export * from 'foo';", parserOptions: { sourceType: "module" }, errors: [{ message: "Missing semicolon.", type: "ExportAllDeclaration" }] },
{ code: "export { foo } from 'foo'", output: "export { foo } from 'foo';", parserOptions: { sourceType: "module" }, errors: [{ message: "Missing semicolon.", type: "ExportNamedDeclaration" }] },
Expand Down

0 comments on commit dfbfe21

Please sign in to comment.