Skip to content

Commit 97fba4b

Browse files
committed
feat(eslint-plugin): Add a new blank-line-declaration-usage rule that lints against a missing empty line between variable declaration and its usage
1 parent a5c85d6 commit 97fba4b

File tree

4 files changed

+317
-0
lines changed

4 files changed

+317
-0
lines changed

projects/eslint-plugin/configs/general.js

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ const config = {
9393
'@liferay/aui/no-node': 'error',
9494
'@liferay/aui/no-object': 'error',
9595
'@liferay/aui/no-one': 'error',
96+
'@liferay/blank-line-declaration-usage': 'error',
9697
'@liferay/destructure-requires': 'error',
9798
'@liferay/empty-line-between-elements': 'error',
9899
'@liferay/expect-assert': 'error',

projects/eslint-plugin/rules/general/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
module.exports = {
77
'array-is-array': require('./lib/rules/array-is-array'),
88
'destructure-requires': require('./lib/rules/destructure-requires'),
9+
'blank-line-declaration-usage': require('./lib/rules/blank-line-declaration-usage'),
910
'empty-line-between-elements': require('./lib/rules/empty-line-between-elements'),
1011
'expect-assert': require('./lib/rules/expect-assert'),
1112
'group-imports': require('./lib/rules/group-imports'),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* SPDX-FileCopyrightText: © 2017 Liferay, Inc. <https://liferay.com>
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
const message =
7+
'Expected an empty line between variable declaration and usage.';
8+
9+
module.exports = {
10+
create(context) {
11+
const sourceCode = context.getSourceCode();
12+
13+
return {
14+
VariableDeclaration(node) {
15+
if (
16+
node.parent.type === 'ForStatement' ||
17+
node.parent.type === 'ForInStatement' ||
18+
node.parent.type === 'ForOfStatement' ||
19+
node.parent.type === 'WhileStatement'
20+
) {
21+
return;
22+
}
23+
24+
const varsDeclaredOnLine = context.getDeclaredVariables(node);
25+
26+
const declarationLine =
27+
varsDeclaredOnLine[0].identifiers[0].loc.start.line;
28+
29+
varsDeclaredOnLine.forEach((reference) => {
30+
const varReference = reference.references[1];
31+
32+
if (varReference === undefined) {
33+
return;
34+
}
35+
36+
const referenceLine =
37+
varReference.identifier.loc.start.line;
38+
39+
if (
40+
sourceCode.lines
41+
.slice(declarationLine, referenceLine - 1)
42+
.indexOf('') === -1
43+
) {
44+
context.report({
45+
fix: (fixer) => {
46+
const declarationEndLine = node.loc.end.line;
47+
48+
if (referenceLine === declarationEndLine + 1) {
49+
return fixer.insertTextAfter(node, '\n');
50+
}
51+
},
52+
message,
53+
node: reference.references[1].identifier,
54+
});
55+
}
56+
});
57+
},
58+
};
59+
},
60+
61+
meta: {
62+
docs: {
63+
category: 'Best Practices',
64+
description: message,
65+
recommended: false,
66+
url: 'https://github.com/liferay/eslint-config-liferay/issues/139',
67+
},
68+
fixable: 'code',
69+
schema: [],
70+
type: 'problem',
71+
},
72+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/**
2+
* SPDX-FileCopyrightText: © 2017 Liferay, Inc. <https://liferay.com>
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
const MultiTester = require('../../../../../scripts/MultiTester');
7+
const rule = require('../../../lib/rules/blank-line-declaration-usage');
8+
9+
const parserOptions = {
10+
parserOptions: {
11+
ecmaVersion: 6,
12+
},
13+
};
14+
15+
const message =
16+
'Expected an empty line between variable declaration and usage.';
17+
18+
const ruleTester = new MultiTester(parserOptions);
19+
20+
ruleTester.run('blank-line-declaration-usage', rule, {
21+
invalid: [
22+
{
23+
code: `
24+
const test = 'test';
25+
const testLength = test.length;
26+
`,
27+
errors: [
28+
{
29+
message,
30+
type: 'Identifier',
31+
},
32+
],
33+
output: `
34+
const test = 'test';
35+
36+
const testLength = test.length;
37+
`,
38+
},
39+
{
40+
code: `
41+
const obj = {
42+
foo: 'bar',
43+
bar: 'foo',
44+
life: 'ray',
45+
}
46+
Object.keys(obj)
47+
`,
48+
errors: [
49+
{
50+
message,
51+
type: 'Identifier',
52+
},
53+
],
54+
output: `
55+
const obj = {
56+
foo: 'bar',
57+
bar: 'foo',
58+
life: 'ray',
59+
}
60+
61+
Object.keys(obj)
62+
`,
63+
},
64+
{
65+
code: `
66+
function updateData() {
67+
const obj = {
68+
foo: 'bar',
69+
bar: 'foo',
70+
life: 'ray',
71+
}
72+
73+
const ids = Object.keys(obj).length
74+
? 'superLongStringSoThatItForcesTheLineToBeWrapped'
75+
: 'justASimpleString';
76+
ids.length;
77+
}
78+
`,
79+
errors: [
80+
{
81+
message,
82+
type: 'Identifier',
83+
},
84+
],
85+
output: `
86+
function updateData() {
87+
const obj = {
88+
foo: 'bar',
89+
bar: 'foo',
90+
life: 'ray',
91+
}
92+
93+
const ids = Object.keys(obj).length
94+
? 'superLongStringSoThatItForcesTheLineToBeWrapped'
95+
: 'justASimpleString';
96+
97+
ids.length;
98+
}
99+
`,
100+
},
101+
{
102+
code: `
103+
const obj = {
104+
foo: 'bar',
105+
bar: 'foo',
106+
life: 'ray',
107+
}
108+
109+
const {foo} = obj;
110+
const length = foo.length;
111+
`,
112+
errors: [
113+
{
114+
message,
115+
type: 'Identifier',
116+
},
117+
],
118+
output: `
119+
const obj = {
120+
foo: 'bar',
121+
bar: 'foo',
122+
life: 'ray',
123+
}
124+
125+
const {foo} = obj;
126+
127+
const length = foo.length;
128+
`,
129+
},
130+
{
131+
code: `
132+
const {foo, bar, life} = obj;
133+
const length = life.length;
134+
`,
135+
errors: [
136+
{
137+
message,
138+
type: 'Identifier',
139+
},
140+
],
141+
output: `
142+
const {foo, bar, life} = obj;
143+
144+
const length = life.length;
145+
`,
146+
},
147+
{
148+
code: `
149+
const filePath = context.getFilename();
150+
const filename = path.basename(something, filePath);
151+
`,
152+
errors: [
153+
{
154+
message,
155+
type: 'Identifier',
156+
},
157+
],
158+
output: `
159+
const filePath = context.getFilename();
160+
161+
const filename = path.basename(something, filePath);
162+
`,
163+
},
164+
{
165+
code: `
166+
for (const file of files) {
167+
const contents = readFileAsync(file, 'utf8');
168+
const length = contents.length;
169+
}
170+
`,
171+
errors: [
172+
{
173+
message,
174+
type: 'Identifier',
175+
},
176+
],
177+
output: `
178+
for (const file of files) {
179+
const contents = readFileAsync(file, 'utf8');
180+
181+
const length = contents.length;
182+
}
183+
`,
184+
},
185+
{
186+
code: `
187+
const cbSpy = sinon.spy();
188+
prototype.rows = [cbSpy];
189+
`,
190+
errors: [
191+
{
192+
message,
193+
type: 'Identifier',
194+
},
195+
],
196+
output: `
197+
const cbSpy = sinon.spy();
198+
199+
prototype.rows = [cbSpy];
200+
`,
201+
},
202+
],
203+
valid: [
204+
{
205+
code: `
206+
const test = 'test';
207+
208+
const testLength = test.length;
209+
`,
210+
},
211+
{
212+
code: `
213+
const obj = {
214+
foo: 'bar',
215+
bar: 'foo',
216+
life: 'ray',
217+
}
218+
219+
Object.keys(obj).map(key => {});
220+
`,
221+
},
222+
{
223+
code: `
224+
const obj = {
225+
foo: 'bar',
226+
bar: 'foo',
227+
life: 'ray',
228+
}
229+
230+
const {foo} = obj;
231+
232+
const length = foo.length;
233+
`,
234+
},
235+
{
236+
code: `
237+
for (const file of files) {
238+
const contents = readFileAsync(file, 'utf8');
239+
}
240+
`,
241+
},
242+
],
243+
});

0 commit comments

Comments
 (0)