UNPKG

4.72 kBJavaScriptView Raw
1'use strict';
2
3const getDocumentationUrl = require('./utils/get-documentation-url');
4
5const MESSAGE_ID = 'noKeywordPrefix';
6
7const prepareOptions = ({
8 blacklist,
9 checkProperties = true,
10 onlyCamelCase = true
11} = {}) => {
12 return {
13 blacklist: (blacklist || [
14 'new',
15 'class'
16 ]),
17 checkProperties,
18 onlyCamelCase
19 };
20};
21
22function findKeywordPrefix(name, options) {
23 return options.blacklist.find(keyword => {
24 const suffix = options.onlyCamelCase ? '[A-Z]' : '.';
25 const regex = new RegExp(`^${keyword}${suffix}`);
26 return name.match(regex);
27 });
28}
29
30function checkMemberExpression(report, node, options) {
31 const {name} = node;
32 const keyword = findKeywordPrefix(name, options);
33 const effectiveParent = (node.parent.type === 'MemberExpression') ? node.parent.parent : node.parent;
34
35 if (!options.checkProperties) {
36 return;
37 }
38
39 if (node.parent.object.type === 'Identifier' && node.parent.object.name === node.name && Boolean(keyword)) {
40 report(node, keyword);
41 } else if (
42 effectiveParent.type === 'AssignmentExpression' &&
43 Boolean(keyword) &&
44 (effectiveParent.right.type !== 'MemberExpression' || effectiveParent.left.type === 'MemberExpression') &&
45 effectiveParent.left.property.name === node.name
46 ) {
47 report(node, keyword);
48 }
49}
50
51function checkObjectPattern(report, node, options) {
52 const {name} = node;
53 const keyword = findKeywordPrefix(name, options);
54
55 if (node.parent.shorthand && node.parent.value.left && Boolean(keyword)) {
56 report(node, keyword);
57 }
58
59 const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
60
61 if (Boolean(keyword) && node.parent.computed) {
62 report(node, keyword);
63 }
64
65 // Prevent checking righthand side of destructured object
66 if (node.parent.key === node && node.parent.value !== node) {
67 return true;
68 }
69
70 const valueIsInvalid = node.parent.value.name && Boolean(keyword);
71
72 // Ignore destructuring if the option is set, unless a new identifier is created
73 if (valueIsInvalid && !assignmentKeyEqualsValue) {
74 report(node, keyword);
75 }
76
77 return false;
78}
79
80// Core logic copied from:
81// https://github.com/eslint/eslint/blob/master/lib/rules/camelcase.js
82const create = context => {
83 const options = prepareOptions(context.options[0]);
84
85 // Contains reported nodes to avoid reporting twice on destructuring with shorthand notation
86 const reported = [];
87 const ALLOWED_PARENT_TYPES = new Set(['CallExpression', 'NewExpression']);
88
89 function report(node, keyword) {
90 if (!reported.includes(node)) {
91 reported.push(node);
92 context.report({
93 node,
94 messageId: MESSAGE_ID,
95 data: {
96 name: node.name,
97 keyword
98 }
99 });
100 }
101 }
102
103 return {
104 Identifier: node => {
105 const {name} = node;
106 const keyword = findKeywordPrefix(name, options);
107 const effectiveParent = (node.parent.type === 'MemberExpression') ? node.parent.parent : node.parent;
108
109 if (node.parent.type === 'MemberExpression') {
110 checkMemberExpression(report, node, options);
111 } else if (
112 node.parent.type === 'Property' ||
113 node.parent.type === 'AssignmentPattern'
114 ) {
115 if (node.parent.parent && node.parent.parent.type === 'ObjectPattern') {
116 const finished = checkObjectPattern(report, node, options);
117 if (finished) {
118 return;
119 }
120 }
121
122 if (
123 !options.checkProperties
124 ) {
125 return;
126 }
127
128 // Don't check right hand side of AssignmentExpression to prevent duplicate warnings
129 if (
130 Boolean(keyword) &&
131 !ALLOWED_PARENT_TYPES.has(effectiveParent.type) &&
132 !(node.parent.right === node)
133 ) {
134 report(node, keyword);
135 }
136
137 // Check if it's an import specifier
138 } else if (
139 [
140 'ImportSpecifier',
141 'ImportNamespaceSpecifier',
142 'ImportDefaultSpecifier'
143 ].includes(node.parent.type)
144 ) {
145 // Report only if the local imported identifier is invalid
146 if (
147 Boolean(keyword) &&
148 node.parent.local &&
149 node.parent.local.name === node.name
150 ) {
151 report(node, keyword);
152 }
153
154 // Report anything that is invalid that isn't a CallExpression
155 } else if (
156 Boolean(keyword) &&
157 !ALLOWED_PARENT_TYPES.has(effectiveParent.type)
158 ) {
159 report(node, keyword);
160 }
161 }
162 };
163};
164
165const schema = [
166 {
167 type: 'object',
168 properties: {
169 blacklist: {
170 type: 'array',
171 items: [
172 {
173 type: 'string'
174 }
175 ],
176 minItems: 0,
177 uniqueItems: true
178 },
179 checkProperties: {
180 type: 'boolean'
181 },
182 onlyCamelCase: {
183 type: 'boolean'
184 }
185 },
186 additionalProperties: false
187 }
188];
189
190module.exports = {
191 create,
192 meta: {
193 type: 'suggestion',
194 docs: {
195 url: getDocumentationUrl(__filename)
196 },
197 schema,
198 messages: {
199 [MESSAGE_ID]: 'Do not prefix identifiers with keyword `{{keyword}}`.'
200 }
201 }
202};