Skip to content
This repository was archived by the owner on Sep 13, 2020. It is now read-only.

Commit 41b39a1

Browse files
committed
initial commit
1 parent ad3bf16 commit 41b39a1

File tree

5 files changed

+278
-1
lines changed

5 files changed

+278
-1
lines changed

.travis.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
sudo: false
2+
language: node_js
3+
node_js:
4+
- "0.10"
5+
- "0.12"
6+
- "4"
7+
- "5"
8+
- "iojs"
9+
- "iojs-v1"
10+
- "iojs-v2"

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,24 @@
11
# hapi-for-you
2-
hapi style guide ESLint rule dealing with for loops
2+
3+
[![Current Version](https://img.shields.io/npm/v/hapi-for-you.svg)](https://www.npmjs.org/package/hapi-for-you)
4+
[![Build Status via Travis CI](https://travis-ci.org/continuationlabs/hapi-for-you.svg?branch=master)](https://travis-ci.org/continuationlabs/hapi-for-you)
5+
![Dependencies](http://img.shields.io/david/continuationlabs/hapi-for-you.svg)
6+
7+
hapi style guide ESLint rule dealing with for loops. This rule enforces the following:
8+
9+
- Restrict iterator variable names. `for` loop iterator variables should be named `i`. Nested loops should use the variables `j`, `k`, and so on.
10+
- Restrict loop nesting. You can restrict the maximum nesting of `for` loops. By default, this limit is three.
11+
- Prevent postfix increment and decrement operators. The hapi style guide does not allow postfix increment and decrement operators in `for` loop updates. The prefix version of these operators should be used instead.
12+
- Single variable declaration in initialization section. A single `var i = 0;` is allowed in the initialization section. This only applies to variable declarations, not assignments to existing variables. This means that `for (i = 0, j = 0)` is allowed if `i` and `j` are existing variables. Variable declarations involving destructuring are not allowed.
13+
14+
## Rule options
15+
16+
This rule can be configured by providing a single options object. The object supports the following keys.
17+
18+
### `maxDepth`
19+
20+
A number representing the maximum allowed nesting of `for` loops. Defaults to three.
21+
22+
### `startIterator`
23+
24+
The first variable iterator name to use. This defaults to `'i'`.

lib/index.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use strict';
2+
3+
module.exports = function (context) {
4+
var options = context.options[0] || {};
5+
var maxDepth = options.maxDepth || 3;
6+
var startIterator = options.startIterator || 'i';
7+
var stack = [];
8+
9+
function check (node) {
10+
stack.push(node);
11+
12+
// Make sure that for loops are not nested excessively
13+
if (stack.length > maxDepth) {
14+
context.report(node, 'Too many nested for loops.');
15+
}
16+
17+
var init = node.init;
18+
19+
if (init !== null && init.type === 'VariableDeclaration') {
20+
// Verify that there is 1 initialized variable at most
21+
if (init.declarations.length > 1) {
22+
context.report(node, 'Only one variable can be initialized per loop.');
23+
}
24+
25+
var declaration = init.declarations[0];
26+
27+
// Verify that this is a normal variable declaration, not destructuring
28+
if (declaration.id.type !== 'Identifier') {
29+
context.report(node, 'Left hand side of initializer must be a single variable.');
30+
} else {
31+
var iteratorVar = declaration.id.name;
32+
var designatedIter = getIteratorVariable(stack.length - 1);
33+
34+
// Verify that the iterator variable has the expected value
35+
if (iteratorVar !== designatedIter) {
36+
context.report(node, 'Expected iterator \'' + designatedIter + '\', but got \'' + iteratorVar + '\'.');
37+
}
38+
}
39+
}
40+
41+
var update = node.update;
42+
43+
// Verify that postfix increment/decrement are not used
44+
if (update && update.type === 'UpdateExpression' && !update.prefix) {
45+
context.report(node, 'Update to iterator should use prefix operator.');
46+
}
47+
}
48+
49+
function getIteratorVariable (offset) {
50+
return String.fromCharCode(startIterator.charCodeAt(0) + offset);
51+
}
52+
53+
function popStack () {
54+
stack.pop();
55+
}
56+
57+
return {
58+
'ForStatement': check,
59+
'ForStatement:exit': popStack
60+
};
61+
};
62+
63+
module.exports.esLintRuleName = 'hapi-for-you';

package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "hapi-for-you",
3+
"version": "1.0.0",
4+
"description": "hapi style guide ESLint rule dealing with for loops",
5+
"author": "Continuation Labs <contact@continuation.io> (http://continuation.io/)",
6+
"main": "lib/index.js",
7+
"scripts": {
8+
"test": "belly-button && lab -v -t 100 -a code"
9+
},
10+
"repository": {
11+
"type": "git",
12+
"url": "https://github.com/continuationlabs/hapi-for-you.git"
13+
},
14+
"keywords": [
15+
"eslint",
16+
"rule",
17+
"for",
18+
"loop",
19+
"for loop"
20+
],
21+
"license": "MIT",
22+
"bugs": {
23+
"url": "https://github.com/continuationlabs/hapi-for-you/issues"
24+
},
25+
"homepage": "https://github.com/continuationlabs/hapi-for-you",
26+
"devDependencies": {
27+
"belly-button": "1.x.x",
28+
"code": "1.x.x",
29+
"eslint": "1.x.x",
30+
"lab": "6.x.x"
31+
}
32+
}

test/index.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
'use strict';
2+
3+
var Code = require('code');
4+
var ESLint = require('eslint');
5+
var Lab = require('lab');
6+
var HapiForYou = require('../lib');
7+
8+
var lab = exports.lab = Lab.script();
9+
var describe = lab.describe;
10+
var it = lab.it;
11+
var RuleTester = ESLint.RuleTester;
12+
13+
Code.settings.truncateMessages = false;
14+
15+
describe('ESLint Rule', function () {
16+
it('enforces iterator variable naming', function (done) {
17+
var ruleTester = new RuleTester();
18+
var valids = [
19+
{
20+
code: 'for (var i = 0; i < a.length; ++i) { for (var j = 0; j < b.length; ++j) {} }'
21+
},
22+
{
23+
code: 'for (var j = 0; j < a.length; ++j) { for (var k = 0; k < b.length; ++k) {} }',
24+
options: [{startIterator: 'j'}]
25+
},
26+
{
27+
code: 'for (var i = 0; i < a.length; ++i) {}; for (var i = 0; i < a.length; ++i) {}'
28+
},
29+
{
30+
code: 'for (;;) {}'
31+
}
32+
];
33+
var invalids = [
34+
{
35+
code: 'for (var j = 0; j < a.length; ++j) {}',
36+
errors: [{message: 'Expected iterator \'i\', but got \'j\'.'}]
37+
},
38+
{
39+
code: 'for (var i = 0; i < a.length; ++i) {}',
40+
options: [{startIterator: 'j'}],
41+
errors: [{message: 'Expected iterator \'j\', but got \'i\'.'}]
42+
}
43+
];
44+
45+
ruleTester.run(HapiForYou.esLintRuleName, HapiForYou, {
46+
valid: valids,
47+
invalid: invalids
48+
});
49+
done();
50+
});
51+
52+
it('enforces a maximum of one variable initialized per loop', function (done) {
53+
var ruleTester = new RuleTester();
54+
var valids = [
55+
{
56+
code: 'for (var i = 0; i < a.length; ++i) {}'
57+
},
58+
{
59+
code: 'for (i = 0, j = 1; i < a.length; ++i) {}'
60+
},
61+
{
62+
code: 'for (; i < a.length; ++i) {}'
63+
}
64+
];
65+
var invalids = [
66+
{
67+
code: 'for (var i = 0, j; i < a.length; ++i) {}',
68+
errors: [{message: 'Only one variable can be initialized per loop.'}]
69+
},
70+
{
71+
code: 'for (var [i] = [0]; i < a.length; ++i) {}',
72+
ecmaFeatures: {destructuring: true},
73+
errors: [{message: 'Left hand side of initializer must be a single variable.'}]
74+
}
75+
];
76+
77+
ruleTester.run(HapiForYou.esLintRuleName, HapiForYou, {
78+
valid: valids,
79+
invalid: invalids
80+
});
81+
done();
82+
});
83+
84+
it('enforces the maximum number of nested for loops', function (done) {
85+
var ruleTester = new RuleTester();
86+
var valids = [
87+
{
88+
code: 'for (var i = 0; i < a.length; ++i) {}'
89+
},
90+
{
91+
code: 'for (var i = 0; i < a.length; ++i) { for (var j = 0; j < b.length; ++j) { for (var k = 0; k < c.length; ++k) { for (var l = 0; l < d.length; ++l) {} } } }',
92+
options: [{maxDepth: 4}]
93+
}
94+
];
95+
var invalids = [
96+
{
97+
code: 'for (var i = 0; i < a.length; ++i) { for (var j = 0; j < b.length; ++j) { for (var k = 0; k < c.length; ++k) { for (var l = 0; l < d.length; ++l) {} } } }',
98+
errors: [{message: 'Too many nested for loops.'}]
99+
},
100+
{
101+
code: 'for (var i = 0; i < a.length; ++i) { for (var j = 0; j < b.length; ++j) {} }',
102+
options: [{maxDepth: 1}],
103+
errors: [{message: 'Too many nested for loops.'}]
104+
}
105+
];
106+
107+
ruleTester.run(HapiForYou.esLintRuleName, HapiForYou, {
108+
valid: valids,
109+
invalid: invalids
110+
});
111+
done();
112+
});
113+
114+
it('prevents post-increment and post-decrement', function (done) {
115+
var ruleTester = new RuleTester();
116+
var valids = [
117+
{
118+
code: 'for (var i = 0; i < a.length; ++i) {}'
119+
},
120+
{
121+
code: 'for (var i = 0; i < a.length; --i) {}'
122+
},
123+
{
124+
code: 'for (var i = 0; i < a.length; i += 1) {}'
125+
},
126+
{
127+
code: 'for (var i = 0; i < a.length; i = i + 1) {}'
128+
},
129+
{
130+
code: 'for (var i = 0; i < a.length;) {}'
131+
}
132+
];
133+
var invalids = [
134+
{
135+
code: 'for (var i = 0; i < a.length; i++) {}',
136+
errors: [{message: 'Update to iterator should use prefix operator.'}]
137+
},
138+
{
139+
code: 'for (var i = 0; i < a.length; i--) {}',
140+
errors: [{message: 'Update to iterator should use prefix operator.'}]
141+
}
142+
];
143+
144+
ruleTester.run(HapiForYou.esLintRuleName, HapiForYou, {
145+
valid: valids,
146+
invalid: invalids
147+
});
148+
done();
149+
});
150+
});

0 commit comments

Comments
 (0)