120 lines
3.2 KiB
JavaScript
120 lines
3.2 KiB
JavaScript
import {isCommaToken} from '@eslint-community/eslint-utils';
|
|
import {removeObjectProperty} from './fix/index.js';
|
|
import {getParentheses} from './utils/index.js';
|
|
|
|
const MESSAGE_ID = 'require-module-attributes';
|
|
const messages = {
|
|
[MESSAGE_ID]: '{{type}} with empty attribute list is not allowed.',
|
|
};
|
|
|
|
const isWithToken = token => token?.type === 'Keyword' && token.value === 'with';
|
|
|
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
const create = context => {
|
|
const {sourceCode} = context;
|
|
|
|
context.on(['ImportDeclaration', 'ExportNamedDeclaration', 'ExportAllDeclaration'], declaration => {
|
|
const {source, attributes} = declaration;
|
|
|
|
if (!source || (Array.isArray(attributes) && attributes.length > 0)) {
|
|
return;
|
|
}
|
|
|
|
const withToken = sourceCode.getTokenAfter(source);
|
|
|
|
if (!isWithToken(withToken)) {
|
|
return;
|
|
}
|
|
|
|
// `WithStatement` is not possible in modules, so we don't need worry it's not attributes
|
|
|
|
const openingBraceToken = sourceCode.getTokenAfter(withToken);
|
|
const closingBraceToken = sourceCode.getTokenAfter(openingBraceToken);
|
|
|
|
return {
|
|
node: declaration,
|
|
loc: {
|
|
start: sourceCode.getLoc(openingBraceToken).start,
|
|
end: sourceCode.getLoc(closingBraceToken).end,
|
|
},
|
|
messageId: MESSAGE_ID,
|
|
data: {
|
|
type: declaration.type === 'ImportDeclaration' ? 'import statement' : 'export statement',
|
|
},
|
|
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
fix: fixer => [withToken, closingBraceToken, openingBraceToken].map(token => fixer.remove(token)),
|
|
};
|
|
});
|
|
|
|
context.on('ImportExpression', importExpression => {
|
|
const {options: optionsNode} = importExpression;
|
|
|
|
if (optionsNode?.type !== 'ObjectExpression') {
|
|
return;
|
|
}
|
|
|
|
const emptyWithProperty = optionsNode.properties.find(
|
|
property =>
|
|
property.type === 'Property'
|
|
&& !property.method
|
|
&& !property.shorthand
|
|
&& !property.computed
|
|
&& property.kind === 'init'
|
|
&& (
|
|
(
|
|
property.key.type === 'Identifier'
|
|
&& property.key.name === 'with'
|
|
)
|
|
|| (
|
|
property.key.type === 'Literal'
|
|
&& property.key.value === 'with'
|
|
)
|
|
)
|
|
&& property.value.type === 'ObjectExpression'
|
|
&& property.value.properties.length === 0,
|
|
);
|
|
|
|
const nodeToRemove = optionsNode.properties.length === 0 || (emptyWithProperty && optionsNode.properties.length === 1)
|
|
? optionsNode
|
|
: emptyWithProperty;
|
|
|
|
if (!nodeToRemove) {
|
|
return;
|
|
}
|
|
|
|
const isProperty = nodeToRemove.type === 'Property';
|
|
|
|
return {
|
|
node: emptyWithProperty?.value ?? nodeToRemove,
|
|
messageId: MESSAGE_ID,
|
|
data: {
|
|
type: 'import expression',
|
|
},
|
|
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
fix: fixer => isProperty
|
|
? removeObjectProperty(fixer, nodeToRemove, context)
|
|
: [
|
|
// Comma token before
|
|
sourceCode.getTokenBefore(nodeToRemove, isCommaToken),
|
|
...sourceCode.getTokens(nodeToRemove),
|
|
...getParentheses(nodeToRemove, context),
|
|
].map(token => fixer.remove(token)),
|
|
};
|
|
});
|
|
};
|
|
|
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
const config = {
|
|
create,
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
description: 'Require non-empty module attributes for imports and exports',
|
|
recommended: 'unopinionated',
|
|
},
|
|
fixable: 'code',
|
|
messages,
|
|
},
|
|
};
|
|
|
|
export default config;
|