You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
3.5 KiB
150 lines
3.5 KiB
/** |
|
* @fileoverview Validate JSX maximum depth |
|
* @author Chris<wfsr@foxmail.com> |
|
*/ |
|
|
|
'use strict'; |
|
|
|
const has = require('has'); |
|
const variableUtil = require('../util/variable'); |
|
const jsxUtil = require('../util/jsx'); |
|
const docsUrl = require('../util/docsUrl'); |
|
|
|
// ------------------------------------------------------------------------------ |
|
// Rule Definition |
|
// ------------------------------------------------------------------------------ |
|
module.exports = { |
|
meta: { |
|
docs: { |
|
description: 'Validate JSX maximum depth', |
|
category: 'Stylistic Issues', |
|
recommended: false, |
|
url: docsUrl('jsx-max-depth') |
|
}, |
|
schema: [ |
|
{ |
|
type: 'object', |
|
properties: { |
|
max: { |
|
type: 'integer', |
|
minimum: 0 |
|
} |
|
}, |
|
additionalProperties: false |
|
} |
|
] |
|
}, |
|
create(context) { |
|
const MESSAGE = 'Expected the depth of nested jsx elements to be <= {{needed}}, but found {{found}}.'; |
|
const DEFAULT_DEPTH = 2; |
|
|
|
const option = context.options[0] || {}; |
|
const maxDepth = has(option, 'max') ? option.max : DEFAULT_DEPTH; |
|
|
|
function isExpression(node) { |
|
return node.type === 'JSXExpressionContainer'; |
|
} |
|
|
|
function hasJSX(node) { |
|
return jsxUtil.isJSX(node) || isExpression(node) && jsxUtil.isJSX(node.expression); |
|
} |
|
|
|
function isLeaf(node) { |
|
const children = node.children; |
|
|
|
return !children.length || !children.some(hasJSX); |
|
} |
|
|
|
function getDepth(node) { |
|
let count = 0; |
|
|
|
while (jsxUtil.isJSX(node.parent) || isExpression(node.parent)) { |
|
node = node.parent; |
|
if (jsxUtil.isJSX(node)) { |
|
count++; |
|
} |
|
} |
|
|
|
return count; |
|
} |
|
|
|
|
|
function report(node, depth) { |
|
context.report({ |
|
node, |
|
message: MESSAGE, |
|
data: { |
|
found: depth, |
|
needed: maxDepth |
|
} |
|
}); |
|
} |
|
|
|
function findJSXElementOrFragment(variables, name) { |
|
function find(refs) { |
|
let i = refs.length; |
|
|
|
while (--i >= 0) { |
|
if (has(refs[i], 'writeExpr')) { |
|
const writeExpr = refs[i].writeExpr; |
|
|
|
return jsxUtil.isJSX(writeExpr) && |
|
writeExpr || |
|
(writeExpr && writeExpr.type === 'Identifier') && |
|
findJSXElementOrFragment(variables, writeExpr.name); |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
const variable = variableUtil.getVariable(variables, name); |
|
return variable && variable.references && find(variable.references); |
|
} |
|
|
|
function checkDescendant(baseDepth, children) { |
|
baseDepth++; |
|
(children || []).forEach((node) => { |
|
if (!hasJSX(node)) { |
|
return; |
|
} |
|
|
|
if (baseDepth > maxDepth) { |
|
report(node, baseDepth); |
|
} else if (!isLeaf(node)) { |
|
checkDescendant(baseDepth, node.children); |
|
} |
|
}); |
|
} |
|
|
|
function handleJSX(node) { |
|
if (!isLeaf(node)) { |
|
return; |
|
} |
|
|
|
const depth = getDepth(node); |
|
if (depth > maxDepth) { |
|
report(node, depth); |
|
} |
|
} |
|
|
|
return { |
|
JSXElement: handleJSX, |
|
JSXFragment: handleJSX, |
|
|
|
JSXExpressionContainer(node) { |
|
if (node.expression.type !== 'Identifier') { |
|
return; |
|
} |
|
|
|
const variables = variableUtil.variablesInScope(context); |
|
const element = findJSXElementOrFragment(variables, node.expression.name); |
|
|
|
if (element) { |
|
const baseDepth = getDepth(node); |
|
checkDescendant(baseDepth, element.children); |
|
} |
|
} |
|
}; |
|
} |
|
};
|
|
|