UNPKG

3.38 kBJavaScriptView Raw
1/**
2 * @fileoverview Enforce PascalCase for user-defined JSX components
3 * @author Jake Marsh
4 */
5
6'use strict';
7
8const elementType = require('jsx-ast-utils/elementType');
9const docsUrl = require('../util/docsUrl');
10const jsxUtil = require('../util/jsx');
11
12function testDigit(char) {
13 const charCode = char.charCodeAt(0);
14 return charCode >= 48 && charCode <= 57;
15}
16
17function testUpperCase(char) {
18 const upperCase = char.toUpperCase();
19 return char === upperCase && upperCase !== char.toLowerCase();
20}
21
22function testLowerCase(char) {
23 const lowerCase = char.toLowerCase();
24 return char === lowerCase && lowerCase !== char.toUpperCase();
25}
26
27function testPascalCase(name) {
28 if (!testUpperCase(name.charAt(0))) {
29 return false;
30 }
31 const anyNonAlphaNumeric = Array.prototype.some.call(
32 name.slice(1),
33 (char) => char.toLowerCase() === char.toUpperCase() && !testDigit(char)
34 );
35 if (anyNonAlphaNumeric) {
36 return false;
37 }
38 return Array.prototype.some.call(
39 name.slice(1),
40 (char) => testLowerCase(char) || testDigit(char)
41 );
42}
43
44function testAllCaps(name) {
45 const firstChar = name.charAt(0);
46 if (!(testUpperCase(firstChar) || testDigit(firstChar))) {
47 return false;
48 }
49 for (let i = 1; i < name.length - 1; i += 1) {
50 const char = name.charAt(i);
51 if (!(testUpperCase(char) || testDigit(char) || char === '_')) {
52 return false;
53 }
54 }
55 const lastChar = name.charAt(name.length - 1);
56 if (!(testUpperCase(lastChar) || testDigit(lastChar))) {
57 return false;
58 }
59 return true;
60}
61
62// ------------------------------------------------------------------------------
63// Rule Definition
64// ------------------------------------------------------------------------------
65
66module.exports = {
67 meta: {
68 docs: {
69 description: 'Enforce PascalCase for user-defined JSX components',
70 category: 'Stylistic Issues',
71 recommended: false,
72 url: docsUrl('jsx-pascal-case')
73 },
74
75 schema: [{
76 type: 'object',
77 properties: {
78 allowAllCaps: {
79 type: 'boolean'
80 },
81 ignore: {
82 type: 'array'
83 }
84 },
85 additionalProperties: false
86 }]
87 },
88
89 create(context) {
90 const configuration = context.options[0] || {};
91 const allowAllCaps = configuration.allowAllCaps || false;
92 const ignore = configuration.ignore || [];
93
94 return {
95 JSXOpeningElement(node) {
96 const isCompatTag = jsxUtil.isDOMComponent(node);
97 if (isCompatTag) return undefined;
98
99 let name = elementType(node);
100 if (name.length === 1) return undefined;
101
102 // Get JSXIdentifier if the type is JSXNamespacedName or JSXMemberExpression
103 if (name.lastIndexOf(':') > -1) {
104 name = name.substring(name.lastIndexOf(':') + 1);
105 } else if (name.lastIndexOf('.') > -1) {
106 name = name.substring(name.lastIndexOf('.') + 1);
107 }
108
109 const isPascalCase = testPascalCase(name);
110 const isAllowedAllCaps = allowAllCaps && testAllCaps(name);
111 const isIgnored = ignore.indexOf(name) !== -1;
112
113 if (!isPascalCase && !isAllowedAllCaps && !isIgnored) {
114 let message = `Imported JSX component ${name} must be in PascalCase`;
115
116 if (allowAllCaps) {
117 message += ' or SCREAMING_SNAKE_CASE';
118 }
119
120 context.report({node, message});
121 }
122 }
123 };
124 }
125};