123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- '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);
- /* istanbul ignore next: Can't find a case to cover this line */
- 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 righthand 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 && 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
- && 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,
- },
- };
|