158 lines
4.3 KiB
JavaScript
158 lines
4.3 KiB
JavaScript
import {isClosingBraceToken} from '@eslint-community/eslint-utils';
|
|
|
|
const MESSAGE_ID_ERROR = 'error';
|
|
const MESSAGE_ID_SUGGESTION_REMOVE_DECLARATION = 'suggestion/remove-declaration';
|
|
const MESSAGE_ID_SUGGESTION_TO_SIDE_EFFECT_IMPORT = 'suggestion/to-side-effect-import';
|
|
const messages = {
|
|
[MESSAGE_ID_ERROR]: '{{type}} statement without specifiers is not allowed.',
|
|
[MESSAGE_ID_SUGGESTION_REMOVE_DECLARATION]: 'Remove this {{type}} statement.',
|
|
[MESSAGE_ID_SUGGESTION_TO_SIDE_EFFECT_IMPORT]: 'Switch to side effect import.',
|
|
};
|
|
|
|
const isFromToken = token => token.type === 'Identifier' && token.value === 'from';
|
|
|
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
const create = context => {
|
|
const {sourceCode} = context;
|
|
|
|
context.on('ImportDeclaration', importDeclaration => {
|
|
const {specifiers} = importDeclaration;
|
|
|
|
if (specifiers.some(node => node.type === 'ImportSpecifier' || node.type === 'ImportNamespaceSpecifier')) {
|
|
return;
|
|
}
|
|
|
|
const {source, importKind} = importDeclaration;
|
|
const fromToken = sourceCode.getTokenBefore(source);
|
|
if (!isFromToken(fromToken)) {
|
|
return;
|
|
}
|
|
|
|
const closingBraceToken = sourceCode.getTokenBefore(fromToken);
|
|
if (!isClosingBraceToken(closingBraceToken)) {
|
|
return;
|
|
}
|
|
|
|
const openingBraceToken = sourceCode.getTokenBefore(closingBraceToken);
|
|
|
|
const problem = {
|
|
node: importDeclaration,
|
|
loc: {
|
|
start: sourceCode.getLoc(openingBraceToken).start,
|
|
end: sourceCode.getLoc(closingBraceToken).end,
|
|
},
|
|
messageId: MESSAGE_ID_ERROR,
|
|
data: {
|
|
type: 'import',
|
|
},
|
|
};
|
|
|
|
// If there is a `ImportDefaultSpecifier`, it has to be the first.
|
|
const importDefaultSpecifier = specifiers.length === 1 ? specifiers[0] : undefined;
|
|
if (importKind === 'type' && !importDefaultSpecifier) {
|
|
problem.fix = fixer => fixer.remove(importDeclaration);
|
|
return problem;
|
|
}
|
|
|
|
if (importDefaultSpecifier) {
|
|
problem.fix = function * (fixer) {
|
|
yield fixer.remove(closingBraceToken);
|
|
yield fixer.remove(openingBraceToken);
|
|
|
|
const commaToken = sourceCode.getTokenBefore(openingBraceToken);
|
|
yield fixer.remove(commaToken);
|
|
|
|
if (sourceCode.getRange(closingBraceToken)[1] === sourceCode.getRange(fromToken)[0]) {
|
|
yield fixer.insertTextBefore(fromToken, ' ');
|
|
}
|
|
};
|
|
|
|
return problem;
|
|
}
|
|
|
|
problem.suggest = [
|
|
{
|
|
messageId: MESSAGE_ID_SUGGESTION_REMOVE_DECLARATION,
|
|
fix: fixer => fixer.remove(importDeclaration),
|
|
},
|
|
{
|
|
messageId: MESSAGE_ID_SUGGESTION_TO_SIDE_EFFECT_IMPORT,
|
|
* fix(fixer) {
|
|
yield fixer.remove(openingBraceToken);
|
|
yield fixer.remove(closingBraceToken);
|
|
yield fixer.remove(fromToken);
|
|
},
|
|
},
|
|
];
|
|
|
|
return problem;
|
|
});
|
|
|
|
context.on('ExportNamedDeclaration', exportDeclaration => {
|
|
const {specifiers, declaration} = exportDeclaration;
|
|
|
|
if (declaration || specifiers.length > 0) {
|
|
return;
|
|
}
|
|
|
|
const {source, exportKind} = exportDeclaration;
|
|
const fromToken = source ? sourceCode.getTokenBefore(source) : undefined;
|
|
const closingBraceToken = fromToken
|
|
? sourceCode.getTokenBefore(fromToken)
|
|
: sourceCode.getLastToken(exportDeclaration, isClosingBraceToken);
|
|
const openingBraceToken = sourceCode.getTokenBefore(closingBraceToken);
|
|
|
|
const problem = {
|
|
node: exportDeclaration,
|
|
loc: {
|
|
start: sourceCode.getLoc(openingBraceToken).start,
|
|
end: sourceCode.getLoc(closingBraceToken).end,
|
|
},
|
|
messageId: MESSAGE_ID_ERROR,
|
|
data: {
|
|
type: 'export',
|
|
},
|
|
};
|
|
|
|
if (!source || exportKind === 'type') {
|
|
problem.fix = fixer => fixer.remove(exportDeclaration);
|
|
return problem;
|
|
}
|
|
|
|
problem.suggest = [
|
|
{
|
|
messageId: MESSAGE_ID_SUGGESTION_REMOVE_DECLARATION,
|
|
fix: fixer => fixer.remove(exportDeclaration),
|
|
},
|
|
{
|
|
messageId: MESSAGE_ID_SUGGESTION_TO_SIDE_EFFECT_IMPORT,
|
|
* fix(fixer) {
|
|
const exportToken = sourceCode.getFirstToken(exportDeclaration);
|
|
yield fixer.replaceText(exportToken, 'import');
|
|
yield fixer.remove(openingBraceToken);
|
|
yield fixer.remove(closingBraceToken);
|
|
yield fixer.remove(fromToken);
|
|
},
|
|
},
|
|
];
|
|
|
|
return problem;
|
|
});
|
|
};
|
|
|
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
const config = {
|
|
create,
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
description: 'Require non-empty specifier list in import and export statements.',
|
|
recommended: 'unopinionated',
|
|
},
|
|
fixable: 'code',
|
|
hasSuggestions: true,
|
|
messages,
|
|
},
|
|
};
|
|
|
|
export default config;
|