UNPKG

3.62 kBJavaScriptView Raw
1/**
2 * @fileoverview Validate JSX maximum depth
3 * @author Chris<wfsr@foxmail.com>
4 */
5
6'use strict';
7
8const has = require('has');
9const variableUtil = require('../util/variable');
10const jsxUtil = require('../util/jsx');
11const docsUrl = require('../util/docsUrl');
12
13// ------------------------------------------------------------------------------
14// Rule Definition
15// ------------------------------------------------------------------------------
16module.exports = {
17 meta: {
18 docs: {
19 description: 'Validate JSX maximum depth',
20 category: 'Stylistic Issues',
21 recommended: false,
22 url: docsUrl('jsx-max-depth')
23 },
24 schema: [
25 {
26 type: 'object',
27 properties: {
28 max: {
29 type: 'integer',
30 minimum: 0
31 }
32 },
33 additionalProperties: false
34 }
35 ]
36 },
37 create(context) {
38 const MESSAGE = 'Expected the depth of nested jsx elements to be <= {{needed}}, but found {{found}}.';
39 const DEFAULT_DEPTH = 2;
40
41 const option = context.options[0] || {};
42 const maxDepth = has(option, 'max') ? option.max : DEFAULT_DEPTH;
43
44 function isExpression(node) {
45 return node.type === 'JSXExpressionContainer';
46 }
47
48 function hasJSX(node) {
49 return jsxUtil.isJSX(node) || isExpression(node) && jsxUtil.isJSX(node.expression);
50 }
51
52 function isLeaf(node) {
53 const children = node.children;
54
55 return !children.length || !children.some(hasJSX);
56 }
57
58 function getDepth(node) {
59 let count = 0;
60
61 while (jsxUtil.isJSX(node.parent) || isExpression(node.parent)) {
62 node = node.parent;
63 if (jsxUtil.isJSX(node)) {
64 count++;
65 }
66 }
67
68 return count;
69 }
70
71 function report(node, depth) {
72 context.report({
73 node,
74 message: MESSAGE,
75 data: {
76 found: depth,
77 needed: maxDepth
78 }
79 });
80 }
81
82 function findJSXElementOrFragment(variables, name) {
83 function find(refs) {
84 let i = refs.length;
85
86 while (--i >= 0) {
87 if (has(refs[i], 'writeExpr')) {
88 const writeExpr = refs[i].writeExpr;
89
90 return jsxUtil.isJSX(writeExpr)
91 && writeExpr
92 || (writeExpr && writeExpr.type === 'Identifier')
93 && findJSXElementOrFragment(variables, writeExpr.name);
94 }
95 }
96
97 return null;
98 }
99
100 const variable = variableUtil.getVariable(variables, name);
101 return variable && variable.references && find(variable.references);
102 }
103
104 function checkDescendant(baseDepth, children) {
105 baseDepth++;
106 (children || []).forEach((node) => {
107 if (!hasJSX(node)) {
108 return;
109 }
110
111 if (baseDepth > maxDepth) {
112 report(node, baseDepth);
113 } else if (!isLeaf(node)) {
114 checkDescendant(baseDepth, node.children);
115 }
116 });
117 }
118
119 function handleJSX(node) {
120 if (!isLeaf(node)) {
121 return;
122 }
123
124 const depth = getDepth(node);
125 if (depth > maxDepth) {
126 report(node, depth);
127 }
128 }
129
130 return {
131 JSXElement: handleJSX,
132 JSXFragment: handleJSX,
133
134 JSXExpressionContainer(node) {
135 if (node.expression.type !== 'Identifier') {
136 return;
137 }
138
139 const variables = variableUtil.variablesInScope(context);
140 const element = findJSXElementOrFragment(variables, node.expression.name);
141
142 if (element) {
143 const baseDepth = getDepth(node);
144 checkDescendant(baseDepth, element.children);
145 }
146 }
147 };
148 }
149};