You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
4.6 KiB
203 lines
4.6 KiB
'use strict';
|
|
|
|
const getDocumentationUrl = require('./utils/get-documentation-url');
|
|
|
|
const MESSAGE_ID = 'noKeywordPrefix';
|
|
|
|
const prepareOptions = ({
|
|
blacklist,
|
|
checkProperties = true,
|
|
onlyCamelCase = true
|
|
} = {}) => {
|
|
return {
|
|
blacklist: (blacklist || [
|
|
'new',
|
|
'class'
|
|
]),
|
|
checkProperties,
|
|
onlyCamelCase
|
|
};
|
|
};
|
|
|
|
function findKeywordPrefix(name, options) {
|
|
return options.blacklist.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} = node;
|
|
const keyword = findKeywordPrefix(name, options);
|
|
const effectiveParent = (node.parent.type === 'MemberExpression') ? node.parent.parent : node.parent;
|
|
|
|
if (!options.checkProperties) {
|
|
return;
|
|
}
|
|
|
|
if (node.parent.object.type === 'Identifier' && node.parent.object.name === node.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 === node.name
|
|
) {
|
|
report(node, keyword);
|
|
}
|
|
}
|
|
|
|
function checkObjectPattern(report, node, options) {
|
|
const {name} = node;
|
|
const keyword = findKeywordPrefix(name, options);
|
|
|
|
if (node.parent.shorthand && node.parent.value.left && Boolean(keyword)) {
|
|
report(node, keyword);
|
|
}
|
|
|
|
const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
|
|
|
|
if (Boolean(keyword) && node.parent.computed) {
|
|
report(node, keyword);
|
|
}
|
|
|
|
// Prevent checking righthand side of destructured object
|
|
if (node.parent.key === node && node.parent.value !== node) {
|
|
return true;
|
|
}
|
|
|
|
const valueIsInvalid = node.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} = node;
|
|
const keyword = findKeywordPrefix(name, options);
|
|
const effectiveParent = (node.parent.type === 'MemberExpression') ? node.parent.parent : node.parent;
|
|
|
|
if (node.parent.type === 'MemberExpression') {
|
|
checkMemberExpression(report, node, options);
|
|
} else if (
|
|
node.parent.type === 'Property' ||
|
|
node.parent.type === 'AssignmentPattern'
|
|
) {
|
|
if (node.parent.parent && node.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) &&
|
|
!(node.parent.right === node)
|
|
) {
|
|
report(node, keyword);
|
|
}
|
|
|
|
// Check if it's an import specifier
|
|
} else if (
|
|
[
|
|
'ImportSpecifier',
|
|
'ImportNamespaceSpecifier',
|
|
'ImportDefaultSpecifier'
|
|
].includes(node.parent.type)
|
|
) {
|
|
// Report only if the local imported identifier is invalid
|
|
if (
|
|
Boolean(keyword) &&
|
|
node.parent.local &&
|
|
node.parent.local.name === node.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',
|
|
properties: {
|
|
blacklist: {
|
|
type: 'array',
|
|
items: [
|
|
{
|
|
type: 'string'
|
|
}
|
|
],
|
|
minItems: 0,
|
|
uniqueItems: true
|
|
},
|
|
checkProperties: {
|
|
type: 'boolean'
|
|
},
|
|
onlyCamelCase: {
|
|
type: 'boolean'
|
|
}
|
|
},
|
|
additionalProperties: false
|
|
}
|
|
];
|
|
|
|
module.exports = {
|
|
create,
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
url: getDocumentationUrl(__filename)
|
|
},
|
|
schema,
|
|
messages: {
|
|
[MESSAGE_ID]: 'Do not prefix identifiers with keyword `{{keyword}}`.'
|
|
}
|
|
}
|
|
};
|