Skip to content

Commit

Permalink
New: no-unused-labels rule (fixes #5052)
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea committed Jan 28, 2016
1 parent 6f91f60 commit d67bfdd
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/eslint.json
Expand Up @@ -107,6 +107,7 @@
"no-unneeded-ternary": 0,
"no-unreachable": 2,
"no-unused-expressions": 0,
"no-unused-labels": 0,
"no-unused-vars": 2,
"no-use-before-define": 0,
"no-useless-call": 0,
Expand Down
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -93,6 +93,7 @@ These are rules designed to prevent you from making mistakes. They either prescr
* [no-throw-literal](no-throw-literal.md) - restrict what can be thrown as an exception
* [no-unmodified-loop-condition](no-unmodified-loop-condition.md) - disallow unmodified conditions of loops
* [no-unused-expressions](no-unused-expressions.md) - disallow usage of expressions in statement position
* [no-unused-labels](no-unused-labels.md) - disallow unused labels
* [no-useless-call](no-useless-call.md) - disallow unnecessary `.call()` and `.apply()`
* [no-useless-concat](no-useless-concat.md) - disallow unnecessary concatenation of literals or template literals
* [no-void](no-void.md) - disallow use of the `void` operator
Expand Down
10 changes: 10 additions & 0 deletions docs/rules/no-empty-label.md
Expand Up @@ -26,3 +26,13 @@ for (var i=10; i; i--) {
// ...
}
```

## When Not To Use It

If you don't want to be notified about usage of labels, then it's safe to disable this rule.

## Related Rules

* [no-labels](./no-labels.md)
* [no-label-var](./no-label-var.md)
* [no-unused-labels](./no-unused-labels.md)
10 changes: 10 additions & 0 deletions docs/rules/no-label-var.md
Expand Up @@ -37,6 +37,16 @@ q:
}
```

## When Not To Use It

If you don't want to be notified about usage of labels, then it's safe to disable this rule.

## Related Rules

* [no-empty-label](./no-empty-label.md)
* [no-labels](./no-labels.md)
* [no-unused-labels](./no-unused-labels.md)

## Further Reading

* ['{a}' is a statement label](http://jslinterrors.com/a-is-a-statement-label/)
6 changes: 6 additions & 0 deletions docs/rules/no-labels.md
Expand Up @@ -62,3 +62,9 @@ while (true) {
## When Not To Use It

If you need to use labeled statements, then you can safely disable this rule.

## Related Rules

* [no-empty-label](./no-empty-label.md)
* [no-label-var](./no-label-var.md)
* [no-unused-labels](./no-unused-labels.md)
68 changes: 68 additions & 0 deletions docs/rules/no-unused-labels.md
@@ -0,0 +1,68 @@
# Disallow Unused Labels (no-unused-labels)

Labels that are declared and not used anywhere in the code are most likely an error due to incomplete refactoring.

```js
OUTER_LOOP:
for (const student of students) {
if (checkScores(student.scores)) {
continue;
}
doSomething(student);
}
```

In this case, probably removing `OUTER_LOOP:` had been forgotten.
Such labels take up space in the code and can lead to confusion by readers.

## Rule Details

This rule is aimed at eliminating unused labels.

The following patterns are considered problems:

```js
/*eslint no-unused-labels: 2*/

A: var foo = 0; /*error 'A:' is defined but never used.*/

B: { /*error 'B:' is defined but never used.*/
foo();
}

C: /*error 'C:' is defined but never used.*/
for (let i = 0; i < 10; ++i) {
foo();
}
```

The following patterns are considered not problems:

```js
/*eslint no-unused-labels: 2*/

A: {
if (foo()) {
break A;
}
bar();
}

B:
for (let i = 0; i < 10; ++i) {
if (foo()) {
break B;
}
bar();
}
```

## When Not To Use It

If you don't want to be notified about unused labels, then it's safe to disable this rule.

## Related Rules

* [no-empty-label](./no-empty-label.md)
* [no-labels](./no-labels.md)
* [no-label-var](./no-label-var.md)
81 changes: 81 additions & 0 deletions lib/rules/no-unused-labels.js
@@ -0,0 +1,81 @@
/**
* @fileoverview Rule to disallow unused labels.
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/

"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function(context) {
var scopeInfo = null;

/**
* Adds a scope info to the stack.
*
* @param {ASTNode} node - A node to add. This is a LabeledStatement.
* @returns {void}
*/
function enterLabeledScope(node) {
scopeInfo = {
label: node.label.name,
used: false,
upper: scopeInfo
};
}

/**
* Removes the top of the stack.
* At the same time, this reports the label if it's never used.
*
* @param {ASTNode} node - A node to report. This is a LabeledStatement.
* @returns {void}
*/
function exitLabeledScope(node) {
if (!scopeInfo.used) {
context.report({
node: node.label,
message: "'{{name}}:' is defined but never used.",
data: node.label
});
}

scopeInfo = scopeInfo.upper;
}

/**
* Marks the label of a given node as used.
*
* @param {ASTNode} node - A node to mark. This is a BreakStatement or
* ContinueStatement.
* @returns {void}
*/
function markAsUsed(node) {
if (!node.label) {
return;
}

var label = node.label.name;
var info = scopeInfo;
while (info) {
if (info.label === label) {
info.used = true;
break;
}
info = info.upper;
}
}

return {
"LabeledStatement": enterLabeledScope,
"LabeledStatement:exit": exitLabeledScope,
"BreakStatement": markAsUsed,
"ContinueStatement": markAsUsed
};
};

module.exports.schema = [];
47 changes: 47 additions & 0 deletions tests/lib/rules/no-unused-labels.js
@@ -0,0 +1,47 @@
/**
* @fileoverview Tests for no-unused-labels rule.
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

var rule = require("../../../lib/rules/no-unused-labels"),
RuleTester = require("../../../lib/testers/rule-tester");

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

var ruleTester = new RuleTester();
ruleTester.run("no-unused-labels", rule, {
valid: [
"A: break A;",
"A: { foo(); break A; bar(); }",
"A: if (a) { foo(); if (b) break A; bar(); }",
"A: for (var i = 0; i < 10; ++i) { foo(); if (a) break A; bar(); }",
"A: for (var i = 0; i < 10; ++i) { foo(); if (a) continue A; bar(); }",
"A: { B: break B; C: for (var i = 0; i < 10; ++i) { foo(); if (a) break A; if (c) continue C; bar(); } }",
"A: { var A = 0; console.log(A); break A; console.log(A); }"
],
invalid: [
{code: "A: var foo = 0;", errors: ["'A:' is defined but never used."]},
{code: "A: { foo(); bar(); }", errors: ["'A:' is defined but never used."]},
{code: "A: if (a) { foo(); bar(); }", errors: ["'A:' is defined but never used."]},
{code: "A: for (var i = 0; i < 10; ++i) { foo(); if (a) break; bar(); }", errors: ["'A:' is defined but never used."]},
{code: "A: for (var i = 0; i < 10; ++i) { foo(); if (a) continue; bar(); }", errors: ["'A:' is defined but never used."]},
{code: "A: for (var i = 0; i < 10; ++i) { B: break A; }", errors: ["'B:' is defined but never used."]},
{code: "A: { var A = 0; console.log(A); }", errors: ["'A:' is defined but never used."]}

// Below is fatal errors.
// "A: break B",
// "A: function foo() { break A; }",
// "A: class Foo { foo() { break A; } }",
// "A: { A: { break A; } }"
]
});

0 comments on commit d67bfdd

Please sign in to comment.