@vkontakte/eslint-plugin
Version:
eslint config from VK
106 lines (89 loc) • 2.27 kB
JavaScript
// @ts-check
const message = `Computed hit cannot be injected`;
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
schema: [
{
type: 'object',
properties: {
cssModulesSuffix: {
type: 'string',
default: '.module.css',
},
},
},
],
},
create(context) {
/** @param node {import('estree').Expression | undefined} */
const checkExpression = (node) => {
if (!node || node.type !== 'Identifier' || !importSpecifiers.has(node.name)) {
return;
}
context.report({
node,
message,
});
};
const options = context.options;
const importSpecifiers = new Set();
/** @type {string} */
let cssModulesSuffix = '.module.css';
options.forEach((option) => {
if ('cssModulesSuffix' in option) {
cssModulesSuffix = option.cssModulesSuffix;
}
});
return {
// import styles from './Component.module.css'
ImportDeclaration(node) {
if (
!node.source.value.endsWith(cssModulesSuffix) ||
node.specifiers.length === 0 ||
node.specifiers[0].type !== 'ImportDefaultSpecifier'
) {
return;
}
importSpecifiers.add(node.specifiers[0].local.name);
},
// styles['a' + 'b']
MemberExpression(node) {
if (
node.object.type !== 'Identifier' ||
!importSpecifiers.has(node.object.name)
) {
return;
}
// ✅ styles['a']
// ✅ styles.a
if (
node.property.type === 'Literal' ||
node.property.type === 'Identifier'
) {
return;
}
context.report({
node,
message,
});
},
// fn(styles)
CallExpression(node) {
node.arguments.forEach(checkExpression);
},
// const cls = styles
VariableDeclarator(node) {
checkExpression(node.init);
},
// const arr = [styles]
ArrayExpression(node) {
node.elements.forEach(checkExpression);
},
// { cls: styles }
Property(node) {
checkExpression(node.value);
},
};
},
};