securityos/node_modules/eslint-plugin-unicorn/rules/no-keyword-prefix.js

200 lines
4.7 KiB
JavaScript
Raw Permalink Normal View History

2024-09-06 15:32:35 +00:00
'use strict';
const isShorthandPropertyAssignmentPatternLeft = require('./utils/is-shorthand-property-assignment-pattern-left.js');
const MESSAGE_ID = 'noKeywordPrefix';
const messages = {
[MESSAGE_ID]: 'Do not prefix identifiers with keyword `{{keyword}}`.',
};
const prepareOptions = ({
disallowedPrefixes,
checkProperties = true,
onlyCamelCase = true,
} = {}) => ({
disallowedPrefixes: (disallowedPrefixes || [
'new',
'class',
]),
checkProperties,
onlyCamelCase,
});
function findKeywordPrefix(name, options) {
return options.disallowedPrefixes.find(keyword => {
const suffix = options.onlyCamelCase ? '[A-Z]' : '.';
const regex = new RegExp(`^${keyword}${suffix}`);
return name.match(regex);
});
}
function checkMemberExpression(report, node, options) {
const {name, parent} = node;
const keyword = findKeywordPrefix(name, options);
const effectiveParent = parent.type === 'MemberExpression' ? parent.parent : parent;
if (!options.checkProperties) {
return;
}
if (parent.object.type === 'Identifier' && parent.object.name === name && Boolean(keyword)) {
report(node, keyword);
} else if (
effectiveParent.type === 'AssignmentExpression'
&& Boolean(keyword)
&& (effectiveParent.right.type !== 'MemberExpression' || effectiveParent.left.type === 'MemberExpression')
&& effectiveParent.left.property.name === name
) {
report(node, keyword);
}
}
function checkObjectPattern(report, node, options) {
const {name, parent} = node;
const keyword = findKeywordPrefix(name, options);
/* c8 ignore next 3 */
if (parent.shorthand && parent.value.left && Boolean(keyword)) {
report(node, keyword);
}
const assignmentKeyEqualsValue = parent.key.name === parent.value.name;
if (Boolean(keyword) && parent.computed) {
report(node, keyword);
}
// Prevent checking right hand side of destructured object
if (parent.key === node && parent.value !== node) {
return true;
}
const valueIsInvalid = parent.value.name && Boolean(keyword);
// Ignore destructuring if the option is set, unless a new identifier is created
if (valueIsInvalid && !assignmentKeyEqualsValue) {
report(node, keyword);
}
return false;
}
// Core logic copied from:
// https://github.com/eslint/eslint/blob/master/lib/rules/camelcase.js
const create = context => {
const options = prepareOptions(context.options[0]);
// Contains reported nodes to avoid reporting twice on destructuring with shorthand notation
const reported = [];
const ALLOWED_PARENT_TYPES = new Set(['CallExpression', 'NewExpression']);
function report(node, keyword) {
if (!reported.includes(node)) {
reported.push(node);
context.report({
node,
messageId: MESSAGE_ID,
data: {
name: node.name,
keyword,
},
});
}
}
return {
Identifier(node) {
const {name, parent} = node;
const keyword = findKeywordPrefix(name, options);
const effectiveParent = parent.type === 'MemberExpression' ? parent.parent : parent;
if (parent.type === 'MemberExpression') {
checkMemberExpression(report, node, options);
} else if (
parent.type === 'Property'
|| parent.type === 'AssignmentPattern'
) {
if (parent.parent.type === 'ObjectPattern') {
const finished = checkObjectPattern(report, node, options);
if (finished) {
return;
}
}
if (
!options.checkProperties
) {
return;
}
// Don't check right hand side of AssignmentExpression to prevent duplicate warnings
if (
Boolean(keyword)
&& !ALLOWED_PARENT_TYPES.has(effectiveParent.type)
&& !(parent.right === node)
&& !isShorthandPropertyAssignmentPatternLeft(node)
) {
report(node, keyword);
}
// Check if it's an import specifier
} else if (
[
'ImportSpecifier',
'ImportNamespaceSpecifier',
'ImportDefaultSpecifier',
].includes(parent.type)
) {
// Report only if the local imported identifier is invalid
if (Boolean(keyword) && parent.local?.name === name) {
report(node, keyword);
}
// Report anything that is invalid that isn't a CallExpression
} else if (
Boolean(keyword)
&& !ALLOWED_PARENT_TYPES.has(effectiveParent.type)
) {
report(node, keyword);
}
},
};
};
const schema = [
{
type: 'object',
additionalProperties: false,
properties: {
disallowedPrefixes: {
type: 'array',
items: [
{
type: 'string',
},
],
minItems: 0,
uniqueItems: true,
},
checkProperties: {
type: 'boolean',
},
onlyCamelCase: {
type: 'boolean',
},
},
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Disallow identifiers starting with `new` or `class`.',
},
schema,
messages,
},
};