Skip to content

Commit

Permalink
Merge pull request #4750 from eslint/issue4385
Browse files Browse the repository at this point in the history
Update: Allow to omit semi for one-line blocks (fixes #4385)
  • Loading branch information
nzakas committed Dec 23, 2015
2 parents 755de2f + 68dbc64 commit fbd3c4d
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 15 deletions.
38 changes: 38 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 or two options. The fists one is a string, which could be "always" or "never". The default is "always". The second one is an object for more fine-grained configuration when the first option 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,38 @@ object.method = function() {
};
```

##### Fine-grained control

When setting the first option as "always", an additional option can be added to omit the last semicolon in a one-line block, that is, a block in which its braces (and therefore the content of the block) are in the same line:

```json
semi: [2, "always", { "omitLastInOneLineBlock": true}]
```

The following patterns are considered problems:

```js
/*eslint semi: [2, "always", { "omitLastInOneLineBlock": true}] */

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

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

The following patterns are not considered problems:

```js
/*eslint semi: [2, "always", { "omitLastInOneLineBlock": true}] */

if (foo) { bar() }

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

#### "never"

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

```json
Expand Down
79 changes: 64 additions & 15 deletions lib/rules/semi.js
Expand Up @@ -13,8 +13,9 @@
module.exports = function(context) {

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

var always = context.options[0] !== "never",
var options = context.options[1];
var never = context.options[0] === "never",
exceptOneLine = options && options.omitLastInOneLineBlock === true,
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 @@ -158,8 +181,34 @@ module.exports = function(context) {

};

module.exports.schema = [
{
"enum": ["always", "never"]
}
];
module.exports.schema = {
"anyOf": [
{
"type": "array",
"items": [
{
"enum": ["never"]
}
],
"minItems": 0,
"maxItems": 1
},
{
"type": "array",
"items": [
{
"enum": ["always"]
},
{
"type": "object",
"properties": {
"omitLastInOneLineBlock": {"type": "boolean"}
},
"additionalProperties": false
}
],
"minItems": 0,
"maxItems": 2
}
]
};
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", { "omitLastInOneLineBlock": true}] },
{ code: "if (foo) { bar(); baz() }", options: ["always", { "omitLastInOneLineBlock": true}] },


// 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", { "omitLastInOneLineBlock": true}], errors: [{ message: "Missing semicolon."}] },
{ code: "if (foo) {\n bar() }", options: ["always", { "omitLastInOneLineBlock": true}], errors: [{ message: "Missing semicolon."}] },
{ code: "if (foo) {\n bar(); baz() }", options: ["always", { "omitLastInOneLineBlock": true}], errors: [{ message: "Missing semicolon."}] },
{ code: "if (foo) { bar(); }", options: ["always", { "omitLastInOneLineBlock": true}], 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 fbd3c4d

Please sign in to comment.