UNPKG

4.57 kBJavaScriptView Raw
1/**
2 * @fileoverview Prevent using string literals in React component definition
3 * @author Caleb Morris
4 * @author David Buchan-Swanson
5 */
6
7'use strict';
8
9const docsUrl = require('../util/docsUrl');
10
11// ------------------------------------------------------------------------------
12// Rule Definition
13// ------------------------------------------------------------------------------
14
15function trimIfString(val) {
16 return typeof val === 'string' ? val.trim() : val;
17}
18
19module.exports = {
20 meta: {
21 docs: {
22 description: 'Prevent using string literals in React component definition',
23 category: 'Stylistic Issues',
24 recommended: false,
25 url: docsUrl('jsx-no-literals')
26 },
27
28 schema: [{
29 type: 'object',
30 properties: {
31 noStrings: {
32 type: 'boolean'
33 },
34 allowedStrings: {
35 type: 'array',
36 uniqueItems: true,
37 items: {
38 type: 'string'
39 }
40 },
41 ignoreProps: {
42 type: 'boolean'
43 }
44 },
45 additionalProperties: false
46 }]
47 },
48
49 create(context) {
50 const defaults = {noStrings: false, allowedStrings: [], ignoreProps: false};
51 const config = Object.assign({}, defaults, context.options[0] || {});
52 config.allowedStrings = new Set(config.allowedStrings.map(trimIfString));
53
54 const message = config.noStrings
55 ? 'Strings not allowed in JSX files'
56 : 'Missing JSX expression container around literal string';
57
58 function reportLiteralNode(node, customMessage) {
59 const errorMessage = customMessage || message;
60
61 context.report({
62 node,
63 message: `${errorMessage}: “${context.getSourceCode().getText(node).trim()}”`
64 });
65 }
66
67 function getParentIgnoringBinaryExpressions(node) {
68 let current = node;
69 while (current.parent.type === 'BinaryExpression') {
70 current = current.parent;
71 }
72 return current.parent;
73 }
74
75 function getValidation(node) {
76 if (config.allowedStrings.has(trimIfString(node.value))) {
77 return false;
78 }
79 const parent = getParentIgnoringBinaryExpressions(node);
80 const standard = !/^[\s]+$/.test(node.value)
81 && typeof node.value === 'string'
82 && parent.type.indexOf('JSX') !== -1
83 && parent.type !== 'JSXAttribute';
84 if (config.noStrings) {
85 return standard;
86 }
87 return standard && parent.type !== 'JSXExpressionContainer';
88 }
89
90 function getParentAndGrandParentType(node) {
91 const parent = getParentIgnoringBinaryExpressions(node);
92 const parentType = parent.type;
93 const grandParentType = parent.parent.type;
94
95 return {
96 parent,
97 parentType,
98 grandParentType,
99 grandParent: parent.parent
100 };
101 }
102
103 function hasJSXElementParentOrGrandParent(node) {
104 const parents = getParentAndGrandParentType(node);
105 const parentType = parents.parentType;
106 const grandParentType = parents.grandParentType;
107
108 return parentType === 'JSXFragment' || parentType === 'JSXElement' || grandParentType === 'JSXElement';
109 }
110
111 // --------------------------------------------------------------------------
112 // Public
113 // --------------------------------------------------------------------------
114
115 return {
116 Literal(node) {
117 if (getValidation(node) && (hasJSXElementParentOrGrandParent(node) || !config.ignoreProps)) {
118 reportLiteralNode(node);
119 }
120 },
121
122 JSXAttribute(node) {
123 const isNodeValueString = node && node.value && node.value.type === 'Literal' && typeof node.value.value === 'string' && !config.allowedStrings.has(node.value.value);
124
125 if (config.noStrings && !config.ignoreProps && isNodeValueString) {
126 const customMessage = 'Invalid prop value';
127 reportLiteralNode(node, customMessage);
128 }
129 },
130
131 JSXText(node) {
132 if (getValidation(node)) {
133 reportLiteralNode(node);
134 }
135 },
136
137 TemplateLiteral(node) {
138 const parents = getParentAndGrandParentType(node);
139 const parentType = parents.parentType;
140 const grandParentType = parents.grandParentType;
141 const isParentJSXExpressionCont = parentType === 'JSXExpressionContainer';
142 const isParentJSXElement = parentType === 'JSXElement' || grandParentType === 'JSXElement';
143
144 if (isParentJSXExpressionCont && config.noStrings && (isParentJSXElement || !config.ignoreProps)) {
145 reportLiteralNode(node);
146 }
147 }
148 };
149 }
150};