source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.js@ 0c6b92a

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

  • Property mode set to 100644
File size: 13.0 KB
RevLine 
[d565449]1/**
2 * @fileoverview Enforces consistent naming for boolean props
3 * @author Ev Haus
4 */
5
6'use strict';
7
8const flatMap = require('array.prototype.flatmap');
9const values = require('object.values');
10
11const Components = require('../util/Components');
12const propsUtil = require('../util/props');
[0c6b92a]13const astUtil = require('../util/ast');
[d565449]14const docsUrl = require('../util/docsUrl');
15const propWrapperUtil = require('../util/propWrapper');
16const report = require('../util/report');
17const eslintUtil = require('../util/eslint');
18
19const getSourceCode = eslintUtil.getSourceCode;
20const getText = eslintUtil.getText;
21
[0c6b92a]22/**
23 * Checks if prop is nested
24 * @param {Object} prop Property object, single prop type declaration
25 * @returns {boolean}
26 */
27function nestedPropTypes(prop) {
28 return (
29 prop.type === 'Property'
30 && astUtil.isCallExpression(prop.value)
31 );
32}
33
[d565449]34// ------------------------------------------------------------------------------
35// Rule Definition
36// ------------------------------------------------------------------------------
37
38const messages = {
39 patternMismatch: 'Prop name `{{propName}}` doesn’t match rule `{{pattern}}`',
40};
41
42/** @type {import('eslint').Rule.RuleModule} */
43module.exports = {
44 meta: {
45 docs: {
46 category: 'Stylistic Issues',
47 description: 'Enforces consistent naming for boolean props',
48 recommended: false,
49 url: docsUrl('boolean-prop-naming'),
50 },
51
52 messages,
53
54 schema: [{
55 additionalProperties: false,
56 properties: {
57 propTypeNames: {
58 items: {
59 type: 'string',
60 },
61 minItems: 1,
62 type: 'array',
63 uniqueItems: true,
64 },
65 rule: {
66 default: '^(is|has)[A-Z]([A-Za-z0-9]?)+',
67 minLength: 1,
68 type: 'string',
69 },
70 message: {
71 minLength: 1,
72 type: 'string',
73 },
74 validateNested: {
75 default: false,
76 type: 'boolean',
77 },
78 },
79 type: 'object',
80 }],
81 },
82
83 create: Components.detect((context, components, utils) => {
84 const config = context.options[0] || {};
85 const rule = config.rule ? new RegExp(config.rule) : null;
86 const propTypeNames = config.propTypeNames || ['bool'];
87
88 // Remembers all Flowtype object definitions
89 const objectTypeAnnotations = new Map();
90
91 /**
92 * Returns the prop key to ensure we handle the following cases:
93 * propTypes: {
94 * full: React.PropTypes.bool,
95 * short: PropTypes.bool,
96 * direct: bool,
97 * required: PropTypes.bool.isRequired
98 * }
99 * @param {Object} node The node we're getting the name of
100 * @returns {string | null}
101 */
102 function getPropKey(node) {
103 // Check for `ExperimentalSpreadProperty` (eslint 3/4) and `SpreadElement` (eslint 5)
104 // so we can skip validation of those fields.
105 // Otherwise it will look for `node.value.property` which doesn't exist and breaks eslint.
106 if (node.type === 'ExperimentalSpreadProperty' || node.type === 'SpreadElement') {
107 return null;
108 }
109 if (node.value && node.value.property) {
110 const name = node.value.property.name;
111 if (name === 'isRequired') {
112 if (node.value.object && node.value.object.property) {
113 return node.value.object.property.name;
114 }
115 return null;
116 }
117 return name;
118 }
119 if (node.value && node.value.type === 'Identifier') {
120 return node.value.name;
121 }
122 return null;
123 }
124
125 /**
126 * Returns the name of the given node (prop)
127 * @param {Object} node The node we're getting the name of
128 * @returns {string}
129 */
130 function getPropName(node) {
131 // Due to this bug https://github.com/babel/babel-eslint/issues/307
132 // we can't get the name of the Flow object key name. So we have
133 // to hack around it for now.
134 if (node.type === 'ObjectTypeProperty') {
135 return getSourceCode(context).getFirstToken(node).value;
136 }
137
138 return node.key.name;
139 }
140
141 /**
142 * Checks if prop is declared in flow way
143 * @param {Object} prop Property object, single prop type declaration
[0c6b92a]144 * @returns {boolean}
[d565449]145 */
146 function flowCheck(prop) {
147 return (
148 prop.type === 'ObjectTypeProperty'
149 && prop.value.type === 'BooleanTypeAnnotation'
150 && rule.test(getPropName(prop)) === false
151 );
152 }
153
154 /**
155 * Checks if prop is declared in regular way
156 * @param {Object} prop Property object, single prop type declaration
[0c6b92a]157 * @returns {boolean}
[d565449]158 */
159 function regularCheck(prop) {
160 const propKey = getPropKey(prop);
161 return (
162 propKey
163 && propTypeNames.indexOf(propKey) >= 0
164 && rule.test(getPropName(prop)) === false
165 );
166 }
167
168 function tsCheck(prop) {
169 if (prop.type !== 'TSPropertySignature') return false;
170 const typeAnnotation = (prop.typeAnnotation || {}).typeAnnotation;
171 return (
172 typeAnnotation
173 && typeAnnotation.type === 'TSBooleanKeyword'
174 && rule.test(getPropName(prop)) === false
175 );
176 }
177
178 /**
179 * Runs recursive check on all proptypes
180 * @param {Array} proptypes A list of Property object (for each proptype defined)
181 * @param {Function} addInvalidProp callback to run for each error
182 */
183 function runCheck(proptypes, addInvalidProp) {
[0c6b92a]184 if (proptypes) {
185 proptypes.forEach((prop) => {
186 if (config.validateNested && nestedPropTypes(prop)) {
187 runCheck(prop.value.arguments[0].properties, addInvalidProp);
188 return;
189 }
190 if (flowCheck(prop) || regularCheck(prop) || tsCheck(prop)) {
191 addInvalidProp(prop);
192 }
193 });
194 }
[d565449]195 }
196
197 /**
198 * Checks and mark props with invalid naming
199 * @param {Object} node The component node we're testing
200 * @param {Array} proptypes A list of Property object (for each proptype defined)
201 */
202 function validatePropNaming(node, proptypes) {
203 const component = components.get(node) || node;
204 const invalidProps = component.invalidProps || [];
205
206 runCheck(proptypes, (prop) => {
207 invalidProps.push(prop);
208 });
209
210 components.set(node, {
211 invalidProps,
212 });
213 }
214
215 /**
216 * Reports invalid prop naming
217 * @param {Object} component The component to process
218 */
219 function reportInvalidNaming(component) {
220 component.invalidProps.forEach((propNode) => {
221 const propName = getPropName(propNode);
222 report(context, config.message || messages.patternMismatch, !config.message && 'patternMismatch', {
223 node: propNode,
224 data: {
225 component: propName,
226 propName,
227 pattern: config.rule,
228 },
229 });
230 });
231 }
232
233 function checkPropWrapperArguments(node, args) {
234 if (!node || !Array.isArray(args)) {
235 return;
236 }
237 args.filter((arg) => arg.type === 'ObjectExpression').forEach((object) => validatePropNaming(node, object.properties));
238 }
239
240 function getComponentTypeAnnotation(component) {
241 // If this is a functional component that uses a global type, check it
242 if (
243 (component.node.type === 'FunctionDeclaration' || component.node.type === 'ArrowFunctionExpression')
244 && component.node.params
245 && component.node.params.length > 0
246 && component.node.params[0].typeAnnotation
247 ) {
248 return component.node.params[0].typeAnnotation.typeAnnotation;
249 }
250
251 if (
252 !component.node.parent
253 || component.node.parent.type !== 'VariableDeclarator'
254 || !component.node.parent.id
255 || component.node.parent.id.type !== 'Identifier'
256 || !component.node.parent.id.typeAnnotation
257 || !component.node.parent.id.typeAnnotation.typeAnnotation
258 ) {
259 return;
260 }
261
[0c6b92a]262 const annotationTypeArguments = propsUtil.getTypeArguments(
263 component.node.parent.id.typeAnnotation.typeAnnotation
264 );
[d565449]265 if (
[0c6b92a]266 annotationTypeArguments && (
267 annotationTypeArguments.type === 'TSTypeParameterInstantiation'
268 || annotationTypeArguments.type === 'TypeParameterInstantiation'
[d565449]269 )
270 ) {
[0c6b92a]271 return annotationTypeArguments.params.find(
[d565449]272 (param) => param.type === 'TSTypeReference' || param.type === 'GenericTypeAnnotation'
273 );
274 }
275 }
276
277 function findAllTypeAnnotations(identifier, node) {
278 if (node.type === 'TSTypeLiteral' || node.type === 'ObjectTypeAnnotation' || node.type === 'TSInterfaceBody') {
279 const currentNode = [].concat(
280 objectTypeAnnotations.get(identifier.name) || [],
281 node
282 );
283 objectTypeAnnotations.set(identifier.name, currentNode);
284 } else if (
285 node.type === 'TSParenthesizedType'
286 && (
287 node.typeAnnotation.type === 'TSIntersectionType'
288 || node.typeAnnotation.type === 'TSUnionType'
289 )
290 ) {
291 node.typeAnnotation.types.forEach((type) => {
292 findAllTypeAnnotations(identifier, type);
293 });
294 } else if (
295 node.type === 'TSIntersectionType'
296 || node.type === 'TSUnionType'
297 || node.type === 'IntersectionTypeAnnotation'
298 || node.type === 'UnionTypeAnnotation'
299 ) {
300 node.types.forEach((type) => {
301 findAllTypeAnnotations(identifier, type);
302 });
303 }
304 }
305
306 // --------------------------------------------------------------------------
307 // Public
308 // --------------------------------------------------------------------------
309
310 return {
311 'ClassProperty, PropertyDefinition'(node) {
312 if (!rule || !propsUtil.isPropTypesDeclaration(node)) {
313 return;
314 }
315 if (
316 node.value
[0c6b92a]317 && astUtil.isCallExpression(node.value)
[d565449]318 && propWrapperUtil.isPropWrapperFunction(
319 context,
320 getText(context, node.value.callee)
321 )
322 ) {
323 checkPropWrapperArguments(node, node.value.arguments);
324 }
325 if (node.value && node.value.properties) {
326 validatePropNaming(node, node.value.properties);
327 }
328 if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
329 validatePropNaming(node, node.typeAnnotation.typeAnnotation.properties);
330 }
331 },
332
333 MemberExpression(node) {
334 if (!rule || !propsUtil.isPropTypesDeclaration(node)) {
335 return;
336 }
337 const component = utils.getRelatedComponent(node);
338 if (!component || !node.parent.right) {
339 return;
340 }
341 const right = node.parent.right;
342 if (
[0c6b92a]343 astUtil.isCallExpression(right)
[d565449]344 && propWrapperUtil.isPropWrapperFunction(
345 context,
346 getText(context, right.callee)
347 )
348 ) {
349 checkPropWrapperArguments(component.node, right.arguments);
350 return;
351 }
352 validatePropNaming(component.node, node.parent.right.properties);
353 },
354
355 ObjectExpression(node) {
356 if (!rule) {
357 return;
358 }
359
360 // Search for the proptypes declaration
361 node.properties.forEach((property) => {
362 if (!propsUtil.isPropTypesDeclaration(property)) {
363 return;
364 }
365 validatePropNaming(node, property.value.properties);
366 });
367 },
368
369 TypeAlias(node) {
370 findAllTypeAnnotations(node.id, node.right);
371 },
372
373 TSTypeAliasDeclaration(node) {
374 findAllTypeAnnotations(node.id, node.typeAnnotation);
375 },
376
377 TSInterfaceDeclaration(node) {
378 findAllTypeAnnotations(node.id, node.body);
379 },
380
381 // eslint-disable-next-line object-shorthand
382 'Program:exit'() {
383 if (!rule) {
384 return;
385 }
386
387 values(components.list()).forEach((component) => {
388 const annotation = getComponentTypeAnnotation(component);
389
390 if (annotation) {
391 let propType;
392 if (annotation.type === 'GenericTypeAnnotation') {
393 propType = objectTypeAnnotations.get(annotation.id.name);
394 } else if (annotation.type === 'ObjectTypeAnnotation' || annotation.type === 'TSTypeLiteral') {
395 propType = annotation;
396 } else if (annotation.type === 'TSTypeReference') {
397 propType = objectTypeAnnotations.get(annotation.typeName.name);
398 } else if (annotation.type === 'TSIntersectionType') {
399 propType = flatMap(annotation.types, (type) => (
400 type.type === 'TSTypeReference'
401 ? objectTypeAnnotations.get(type.typeName.name)
402 : type
403 ));
404 }
405
406 if (propType) {
407 [].concat(propType).filter(Boolean).forEach((prop) => {
408 validatePropNaming(
409 component.node,
410 prop.properties || prop.members || prop.body
411 );
412 });
413 }
414 }
415
416 if (component.invalidProps && component.invalidProps.length > 0) {
417 reportInvalidNaming(component);
418 }
419 });
420
421 // Reset cache
422 objectTypeAnnotations.clear();
423 },
424 };
425 }),
426};
Note: See TracBrowser for help on using the repository browser.