224 lines
No EOL
9.2 KiB
JavaScript
224 lines
No EOL
9.2 KiB
JavaScript
import debug from 'debug';
|
|
import { isStaticRequire, createRule } from '../utils/index.js';
|
|
const log = debug('eslint-plugin-import-x:rules:newline-after-import');
|
|
function containsNodeOrEqual(outerNode, innerNode) {
|
|
return (outerNode.range[0] <= innerNode.range[0] &&
|
|
outerNode.range[1] >= innerNode.range[1]);
|
|
}
|
|
function getScopeBody(scope) {
|
|
if (scope.block.type === 'SwitchStatement') {
|
|
log('SwitchStatement scopes not supported');
|
|
return [];
|
|
}
|
|
const body = 'body' in scope.block ? scope.block.body : null;
|
|
if (body && 'type' in body && body.type === 'BlockStatement') {
|
|
return body.body;
|
|
}
|
|
return Array.isArray(body) ? body : [];
|
|
}
|
|
function findNodeIndexInScopeBody(body, nodeToFind) {
|
|
return body.findIndex(node => containsNodeOrEqual(node, nodeToFind));
|
|
}
|
|
function getLineDifference(node, nextNode) {
|
|
return nextNode.loc.start.line - node.loc.end.line;
|
|
}
|
|
function isClassWithDecorator(node) {
|
|
return node.type === 'ClassDeclaration' && !!node.decorators?.length;
|
|
}
|
|
function isExportDefaultClass(node) {
|
|
return (node.type === 'ExportDefaultDeclaration' &&
|
|
node.declaration.type === 'ClassDeclaration');
|
|
}
|
|
function isExportNameClass(node) {
|
|
return (node.type === 'ExportNamedDeclaration' &&
|
|
node.declaration?.type === 'ClassDeclaration');
|
|
}
|
|
export default createRule({
|
|
name: 'newline-after-import',
|
|
meta: {
|
|
type: 'layout',
|
|
docs: {
|
|
category: 'Style guide',
|
|
description: 'Enforce a newline after import statements.',
|
|
},
|
|
fixable: 'whitespace',
|
|
schema: [
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
count: {
|
|
type: 'integer',
|
|
minimum: 1,
|
|
},
|
|
exactCount: { type: 'boolean' },
|
|
considerComments: { type: 'boolean' },
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
messages: {
|
|
newline: 'Expected {{count}} empty line{{lineSuffix}} after {{type}} statement not followed by another {{type}}.',
|
|
},
|
|
},
|
|
defaultOptions: [],
|
|
create(context) {
|
|
let level = 0;
|
|
const requireCalls = [];
|
|
const options = {
|
|
count: 1,
|
|
exactCount: false,
|
|
considerComments: false,
|
|
...context.options[0],
|
|
};
|
|
function checkForNewLine(node, nextNode, type) {
|
|
if (isExportDefaultClass(nextNode) || isExportNameClass(nextNode)) {
|
|
const classNode = nextNode.declaration;
|
|
if (isClassWithDecorator(classNode)) {
|
|
nextNode = classNode.decorators[0];
|
|
}
|
|
}
|
|
else if (isClassWithDecorator(nextNode)) {
|
|
nextNode = nextNode.decorators[0];
|
|
}
|
|
const lineDifference = getLineDifference(node, nextNode);
|
|
const EXPECTED_LINE_DIFFERENCE = options.count + 1;
|
|
if (lineDifference < EXPECTED_LINE_DIFFERENCE ||
|
|
(options.exactCount && lineDifference !== EXPECTED_LINE_DIFFERENCE)) {
|
|
let column = node.loc.start.column;
|
|
if (node.loc.start.line !== node.loc.end.line) {
|
|
column = 0;
|
|
}
|
|
context.report({
|
|
loc: {
|
|
line: node.loc.end.line,
|
|
column,
|
|
},
|
|
messageId: 'newline',
|
|
data: {
|
|
count: options.count,
|
|
lineSuffix: options.count > 1 ? 's' : '',
|
|
type,
|
|
},
|
|
fix: options.exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference
|
|
? undefined
|
|
: fixer => fixer.insertTextAfter(node, '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference)),
|
|
});
|
|
}
|
|
}
|
|
function commentAfterImport(node, nextComment, type) {
|
|
const lineDifference = getLineDifference(node, nextComment);
|
|
const EXPECTED_LINE_DIFFERENCE = options.count + 1;
|
|
if (lineDifference < EXPECTED_LINE_DIFFERENCE) {
|
|
let column = node.loc.start.column;
|
|
if (node.loc.start.line !== node.loc.end.line) {
|
|
column = 0;
|
|
}
|
|
context.report({
|
|
loc: {
|
|
line: node.loc.end.line,
|
|
column,
|
|
},
|
|
messageId: 'newline',
|
|
data: {
|
|
count: options.count,
|
|
lineSuffix: options.count > 1 ? 's' : '',
|
|
type,
|
|
},
|
|
fix: options.exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference
|
|
? undefined
|
|
: fixer => fixer.insertTextAfter(node, '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference)),
|
|
});
|
|
}
|
|
}
|
|
function incrementLevel() {
|
|
level++;
|
|
}
|
|
function decrementLevel() {
|
|
level--;
|
|
}
|
|
function checkImport(node) {
|
|
const { parent } = node;
|
|
if (!parent || !('body' in parent) || !parent.body) {
|
|
return;
|
|
}
|
|
const root = parent;
|
|
const nodePosition = root.body.indexOf(node);
|
|
const nextNode = root.body[nodePosition + 1];
|
|
const endLine = node.loc.end.line;
|
|
let nextComment;
|
|
if (root.comments !== undefined && options.considerComments) {
|
|
nextComment = root.comments.find(o => o.loc.start.line >= endLine &&
|
|
o.loc.start.line <= endLine + options.count + 1);
|
|
}
|
|
if (node.type === 'TSImportEqualsDeclaration' &&
|
|
node.isExport) {
|
|
return;
|
|
}
|
|
if (nextComment) {
|
|
commentAfterImport(node, nextComment, 'import');
|
|
}
|
|
else if (nextNode &&
|
|
nextNode.type !== 'ImportDeclaration' &&
|
|
(nextNode.type !== 'TSImportEqualsDeclaration' ||
|
|
nextNode.isExport)) {
|
|
checkForNewLine(node, nextNode, 'import');
|
|
}
|
|
}
|
|
return {
|
|
ImportDeclaration: checkImport,
|
|
TSImportEqualsDeclaration: checkImport,
|
|
CallExpression(node) {
|
|
if (isStaticRequire(node) && level === 0) {
|
|
requireCalls.push(node);
|
|
}
|
|
},
|
|
'Program:exit'(node) {
|
|
log('exit processing for', context.physicalFilename);
|
|
const scopeBody = getScopeBody(context.sourceCode.getScope(node));
|
|
log('got scope:', scopeBody);
|
|
for (const [index, node] of requireCalls.entries()) {
|
|
const nodePosition = findNodeIndexInScopeBody(scopeBody, node);
|
|
log('node position in scope:', nodePosition);
|
|
const statementWithRequireCall = scopeBody[nodePosition];
|
|
const nextStatement = scopeBody[nodePosition + 1];
|
|
const nextRequireCall = requireCalls[index + 1];
|
|
if (nextRequireCall &&
|
|
containsNodeOrEqual(statementWithRequireCall, nextRequireCall)) {
|
|
continue;
|
|
}
|
|
if (nextStatement &&
|
|
(!nextRequireCall ||
|
|
!containsNodeOrEqual(nextStatement, nextRequireCall))) {
|
|
let nextComment;
|
|
if ('comments' in statementWithRequireCall.parent &&
|
|
statementWithRequireCall.parent.comments !== undefined &&
|
|
options.considerComments) {
|
|
const endLine = node.loc.end.line;
|
|
nextComment = statementWithRequireCall.parent.comments.find(o => o.loc.start.line >= endLine &&
|
|
o.loc.start.line <= endLine + options.count + 1);
|
|
}
|
|
if (nextComment && nextComment !== undefined) {
|
|
commentAfterImport(statementWithRequireCall, nextComment, 'require');
|
|
}
|
|
else {
|
|
checkForNewLine(statementWithRequireCall, nextStatement, 'require');
|
|
}
|
|
}
|
|
}
|
|
},
|
|
FunctionDeclaration: incrementLevel,
|
|
FunctionExpression: incrementLevel,
|
|
ArrowFunctionExpression: incrementLevel,
|
|
BlockStatement: incrementLevel,
|
|
ObjectExpression: incrementLevel,
|
|
Decorator: incrementLevel,
|
|
'FunctionDeclaration:exit': decrementLevel,
|
|
'FunctionExpression:exit': decrementLevel,
|
|
'ArrowFunctionExpression:exit': decrementLevel,
|
|
'BlockStatement:exit': decrementLevel,
|
|
'ObjectExpression:exit': decrementLevel,
|
|
'Decorator:exit': decrementLevel,
|
|
};
|
|
},
|
|
});
|
|
//# sourceMappingURL=newline-after-import.js.map
|