Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epidemian
GitHub Repository: epidemian/eslint-plugin-import
Path: blob/main/src/rules/newline-after-import.js
829 views
1
/**
2
* @fileoverview Rule to enforce new line after import not followed by another import.
3
* @author Radek Benkel
4
*/
5
6
import isStaticRequire from '../core/staticRequire';
7
import docsUrl from '../docsUrl';
8
9
import debug from 'debug';
10
const log = debug('eslint-plugin-import:rules:newline-after-import');
11
12
//------------------------------------------------------------------------------
13
// Rule Definition
14
//------------------------------------------------------------------------------
15
16
function containsNodeOrEqual(outerNode, innerNode) {
17
return outerNode.range[0] <= innerNode.range[0] && outerNode.range[1] >= innerNode.range[1];
18
}
19
20
function getScopeBody(scope) {
21
if (scope.block.type === 'SwitchStatement') {
22
log('SwitchStatement scopes not supported');
23
return null;
24
}
25
26
const { body } = scope.block;
27
if (body && body.type === 'BlockStatement') {
28
return body.body;
29
}
30
31
return body;
32
}
33
34
function findNodeIndexInScopeBody(body, nodeToFind) {
35
return body.findIndex((node) => containsNodeOrEqual(node, nodeToFind));
36
}
37
38
function getLineDifference(node, nextNode) {
39
return nextNode.loc.start.line - node.loc.end.line;
40
}
41
42
function isClassWithDecorator(node) {
43
return node.type === 'ClassDeclaration' && node.decorators && node.decorators.length;
44
}
45
46
function isExportDefaultClass(node) {
47
return node.type === 'ExportDefaultDeclaration' && node.declaration.type === 'ClassDeclaration';
48
}
49
50
function isExportNameClass(node) {
51
52
return node.type === 'ExportNamedDeclaration' && node.declaration && node.declaration.type === 'ClassDeclaration';
53
}
54
55
module.exports = {
56
meta: {
57
type: 'layout',
58
docs: {
59
url: docsUrl('newline-after-import'),
60
},
61
fixable: 'whitespace',
62
schema: [
63
{
64
'type': 'object',
65
'properties': {
66
'count': {
67
'type': 'integer',
68
'minimum': 1,
69
},
70
},
71
'additionalProperties': false,
72
},
73
],
74
},
75
create(context) {
76
let level = 0;
77
const requireCalls = [];
78
79
function checkForNewLine(node, nextNode, type) {
80
if (isExportDefaultClass(nextNode) || isExportNameClass(nextNode)) {
81
const classNode = nextNode.declaration;
82
83
if (isClassWithDecorator(classNode)) {
84
nextNode = classNode.decorators[0];
85
}
86
} else if (isClassWithDecorator(nextNode)) {
87
nextNode = nextNode.decorators[0];
88
}
89
90
const options = context.options[0] || { count: 1 };
91
const lineDifference = getLineDifference(node, nextNode);
92
const EXPECTED_LINE_DIFFERENCE = options.count + 1;
93
94
if (lineDifference < EXPECTED_LINE_DIFFERENCE) {
95
let column = node.loc.start.column;
96
97
if (node.loc.start.line !== node.loc.end.line) {
98
column = 0;
99
}
100
101
context.report({
102
loc: {
103
line: node.loc.end.line,
104
column,
105
},
106
message: `Expected ${options.count} empty line${options.count > 1 ? 's' : ''} \
107
after ${type} statement not followed by another ${type}.`,
108
fix: fixer => fixer.insertTextAfter(
109
node,
110
'\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference),
111
),
112
});
113
}
114
}
115
116
function incrementLevel() {
117
level++;
118
}
119
function decrementLevel() {
120
level--;
121
}
122
123
function checkImport(node) {
124
const { parent } = node;
125
const nodePosition = parent.body.indexOf(node);
126
const nextNode = parent.body[nodePosition + 1];
127
128
// skip "export import"s
129
if (node.type === 'TSImportEqualsDeclaration' && node.isExport) {
130
return;
131
}
132
133
if (nextNode && nextNode.type !== 'ImportDeclaration' && (nextNode.type !== 'TSImportEqualsDeclaration' || nextNode.isExport)) {
134
checkForNewLine(node, nextNode, 'import');
135
}
136
}
137
138
return {
139
ImportDeclaration: checkImport,
140
TSImportEqualsDeclaration: checkImport,
141
CallExpression(node) {
142
if (isStaticRequire(node) && level === 0) {
143
requireCalls.push(node);
144
}
145
},
146
'Program:exit': function () {
147
log('exit processing for', context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename());
148
const scopeBody = getScopeBody(context.getScope());
149
log('got scope:', scopeBody);
150
151
requireCalls.forEach(function (node, index) {
152
const nodePosition = findNodeIndexInScopeBody(scopeBody, node);
153
log('node position in scope:', nodePosition);
154
155
const statementWithRequireCall = scopeBody[nodePosition];
156
const nextStatement = scopeBody[nodePosition + 1];
157
const nextRequireCall = requireCalls[index + 1];
158
159
if (nextRequireCall && containsNodeOrEqual(statementWithRequireCall, nextRequireCall)) {
160
return;
161
}
162
163
if (nextStatement &&
164
(!nextRequireCall || !containsNodeOrEqual(nextStatement, nextRequireCall))) {
165
166
checkForNewLine(statementWithRequireCall, nextStatement, 'require');
167
}
168
});
169
},
170
FunctionDeclaration: incrementLevel,
171
FunctionExpression: incrementLevel,
172
ArrowFunctionExpression: incrementLevel,
173
BlockStatement: incrementLevel,
174
ObjectExpression: incrementLevel,
175
Decorator: incrementLevel,
176
'FunctionDeclaration:exit': decrementLevel,
177
'FunctionExpression:exit': decrementLevel,
178
'ArrowFunctionExpression:exit': decrementLevel,
179
'BlockStatement:exit': decrementLevel,
180
'ObjectExpression:exit': decrementLevel,
181
'Decorator:exit': decrementLevel,
182
};
183
},
184
};
185
186