Skip to content

Commit

Permalink
Merge pull request #2799 from BYK/id-length
Browse files Browse the repository at this point in the history
New: Add id-length rule (fixes #2784)
  • Loading branch information
nzakas committed Jul 27, 2015
2 parents 74261bd + 715ca62 commit 4e9258f
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/eslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
"generator-star-spacing": 0,
"guard-for-in": 0,
"handle-callback-err": 0,
"id-length": 0,
"indent": 0,
"init-declarations": 0,
"key-spacing": [0, { "beforeColon": false, "afterColon": true }],
Expand Down
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ These rules are purely matters of style and are quite subjective.
* [eol-last](eol-last.md) - enforce newline at the end of file, with no multiple empty lines
* [func-names](func-names.md) - require function expressions to have a name
* [func-style](func-style.md) - enforce use of function declarations or expressions
* [id-length](id-length.md) - this option enforces minimum and maximum identifier lengths (variable names, property names etc.) (off by default)
* [indent](indent.md) - specify tab or space width for your code
* [key-spacing](key-spacing.md) - enforce spacing between keys and values in object literal properties
* [lines-around-comment](lines-around-comment.md) - enforce empty lines around comments
Expand Down
84 changes: 84 additions & 0 deletions docs/rules/id-length.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Limit minimum and maximum length for identifiers (id-length)

Very short identifier names like `e`, `x`, `_t` or very long ones like `hashGeneratorResultOutputContainerObject` usually make the code harder to read and potentially less maintainable. To prevent this, one may enforce a minimum and/or maximum identifier length. (usually min 2-chars)

```js
// id-length: 1 // default is minimum 2-chars ({ min: 2})
var x = 5; // too short
```

## Rule Details

This rule is aimed at increasing code readability and maintainability by enforcing an identifier length convention. It will warn on any type of identifier which doesn't conform to length limits (upper and lower).

It allows the programmers to silently by-pass this check by using "quoted" property names or calculated property access to allow potential server-side data requirements.

The following patterns are considered warnings:

```js
// id-length: 1 // default is minimum 2-chars ({ min: 2})

var x = 5;

obj.e = document.body;

var handler = function (e) { /* do stuff */ };

try {
dangerousStuff();
} catch (e) { // Identifier 'e' is too short. (< 2)
// ignore as many do
}

var myObj = { a: 1 }; // Identifier 'a' is too short. (< 2)
```

The following patterns are not considered warnings:

```js
// id-length: 1 // default is minimum 2-chars ({ min: 2})

var num = 5;

function _f() { return 42; }

function _func() { return 42; }

obj.el = document.body;

var handler = function (evt) { /* do stuff */ };

try {
dangerousStuff();
} catch (error) { // Identifier 'e' is too short. (< 2)
// ignore as many do
}

var myObj = { apple: 1 };

var data = { "x": 1 }; // excused because of quotes

data["y"] = 3; // excused because of calculated property access
```

### Options

The `id-length` rule has no required options and has 4 optional ones that needs to be passed in a single options object:

* **min** *(default: 2)*: The minimum number of characters an identifier name should be, after it is stripped from it is prefixes and suffixes
* **max** *(default: Infinity)*: The maximum number of characters an identifier name should be, after it is stripped from it is prefixes and suffixes
* **exceptions**: An array of identifier names that the rule should not apply to

For example, to specify a minimum identifier length of 3 and maximum of 10 and add `x` to exception list, use the following configuration:

```json
"id-length": [2, {"min": 3, "max": 10, "exceptions": ["x"]}]
```


## Related Rules

* [max-len](max-len.md)
* [new-cap](new-cap.md)
* [func-names](func-names.md)
* [camelcase](camelcase.md)
88 changes: 88 additions & 0 deletions lib/rules/id-length.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @fileoverview Rule that warns when identifier names are shorter or longer
* than the values provided in configuration.
* @author Burak Yigit Kaya aka BYK
* @copyright 2015 Burak Yigit Kaya. All rights reserved.
*/

"use strict";

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

module.exports = function(context) {
var options = context.options[0] || {};
var minLength = typeof options.min !== "undefined" ? options.min : 2;
var maxLength = typeof options.max !== "undefined" ? options.max : Infinity;
var exceptions = (options.exceptions ? options.exceptions : [])
.reduce(function(obj, item) {
obj[item] = true;

return obj;
}, {});

var SUPPORTED_EXPRESSIONS = {
"AssignmentExpression": function(parent, node) {
return parent.left.type === "MemberExpression" &&
!parent.left.computed && parent.left.property === node;
},
"VariableDeclarator": function(parent, node) {
return parent.id === node;
},
"ObjectExpression": function(parent, node) {
return node.parent.key === node;
},
"FunctionExpression": true,
"FunctionDeclaration": true,
"CatchClause": true
};

return {
Identifier: function(node) {
var name = node.name;
var effectiveParent = (node.parent.type === "MemberExpression" || node.parent.type === "Property") ?
node.parent.parent : node.parent;

var isShort = name.length < minLength;
var isLong = name.length > maxLength;
if (!(isShort || isLong) || exceptions[name]) {
return; // Nothing to report
}

var isValidExpression = SUPPORTED_EXPRESSIONS[effectiveParent.type];

if (isValidExpression && (isValidExpression === true || isValidExpression(effectiveParent, node))) {
context.report(
node,
isShort ?
"Identifier name '{{name}}' is too short. (< {{min}})" :
"Identifier name '{{name}}' is too long. (> {{max}})",
{ name: name, min: minLength, max: maxLength }
);
}
}
};
};

module.exports.schema = [
{
"type": "object",
"properties": {
"min": {
"type": "number"
},
"max": {
"type": "number"
},
"exceptions": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string"
}
}
},
"additionalProperties": false
}
];
60 changes: 60 additions & 0 deletions tests/lib/rules/id-length.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @fileoverview Tests for id-length rule.
* @author Burak Yigit Kaya
*/

"use strict";

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

var eslint = require("../../../lib/eslint"),
ESLintTester = require("../../../lib/testers/eslint-tester");

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

var eslintTester = new ESLintTester(eslint);
eslintTester.addRuleTest("lib/rules/id-length", {
valid: [
"var xyz;",
"var xy = 1;",
"function xyz() {};",
"function xyz(abc, de) {};",
"var obj = { abc: 1, de: 2 };",
"var obj = { 'a': 1, bc: 2 };",
"var obj = {}; obj['a'] = 2;",
"abc = d;",
"try { blah(); } catch (err) { /* pass */ }",
"var handler = function ($e) {};",
"var _a = 2",
"var _ad$$ = new $;",
"var xyz = new ΣΣ();",
"unrelatedExpressionThatNeedsToBeIgnored();",
"var obj = { 'a': 1, bc: 2 }; obj.tk = obj.a;",
{ code: "var x = Foo(42)", options: [{"min": 1}] },
{ code: "var x = Foo(42)", options: [{"min": 0}] },
{ code: "foo.$x = Foo(42)", options: [{"min": 1}] },
{ code: "var lalala = Foo(42)", options: [{"max": 6}] },
{ code: "for (var q, h=0; h < 10; h++) { console.log(h); q++;}", options: [{exceptions: ["h", "q"]}] }
],
invalid: [
{ code: "var x = 1;", errors: [{ message: "Identifier name 'x' is too short. (< 2)", type: "Identifier"}] },
{ code: "var x;", errors: [{ message: "Identifier name 'x' is too short. (< 2)", type: "Identifier"}] },
{ code: "function x() {};", errors: [{ message: "Identifier name 'x' is too short. (< 2)", type: "Identifier"}] },
{ code: "function xyz(a) {};", errors: [{ message: "Identifier name 'a' is too short. (< 2)", type: "Identifier"}] },
{ code: "var obj = {a: 1, bc: 2};", errors: [{ message: "Identifier name 'a' is too short. (< 2)", type: "Identifier"}] },
{ code: "try { blah(); } catch (e) { /* pass */ }", errors: [{ message: "Identifier name 'e' is too short. (< 2)", type: "Identifier"}] },
{ code: "var handler = function (e) {};", errors: [{ message: "Identifier name 'e' is too short. (< 2)", type: "Identifier"}] },
{ code: "for (var i=0; i < 10; i++) { console.log(i); }", errors: [{ message: "Identifier name 'i' is too short. (< 2)", type: "Identifier"}] },
{ code: "var j=0; while (j > -10) { console.log(--j); }", errors: [{ message: "Identifier name 'j' is too short. (< 2)", type: "Identifier"}] },
{ code: "var _$xt_$ = Foo(42)", options: [{"min": 2, "max": 4}], errors: [
{ message: "Identifier name '_$xt_$' is too long. (> 4)", type: "Identifier"}
]},
{ code: "var _$x$_t$ = Foo(42)", options: [{"min": 2, "max": 4}], errors: [
{ message: "Identifier name '_$x$_t$' is too long. (> 4)", type: "Identifier"}
] }
]
});

0 comments on commit 4e9258f

Please sign in to comment.