UNPKG

eslint-plugin-validate-jsx-nesting

Version:
265 lines (248 loc) 5.95 kB
'use strict'; const headings = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']); const emptySet = new Set([]); /** * maps element to set of elements that can be it's children, no other */ const onlyValidChildren$1 = { head: new Set([ 'base', 'basefront', 'bgsound', 'link', 'meta', 'title', 'noscript', 'noframes', 'style', 'script', 'template', ]), optgroup: new Set(['option']), select: new Set(['optgroup', 'option']), math: new Set(['mrow']), script: new Set(), // table table: new Set(['caption', 'colgroup', 'tbody', 'tfoot', 'thead']), tr: new Set(['td', 'th']), colgroup: new Set(['col']), tbody: new Set(['tr']), thead: new Set(['tr']), tfoot: new Set(['tr']), // these elements can not have any children elements iframe: emptySet, option: emptySet, textarea: emptySet, style: emptySet, title: emptySet, }; /** maps elements to set of elements which can be it's parent, no other */ const onlyValidParents$1 = { // sections html: emptySet, body: new Set(['html']), head: new Set(['html']), // table td: new Set(['tr']), colgroup: new Set(['table']), caption: new Set(['table']), tbody: new Set(['table']), tfoot: new Set(['table']), col: new Set(['colgroup']), th: new Set(['tr']), thead: new Set(['table']), tr: new Set(['tbody', 'thead', 'tfoot']), // data list dd: new Set(['dl', 'div']), dt: new Set(['dl', 'div']), // other figcaption: new Set(['figure']), // li: new Set(["ul", "ol"]), summary: new Set(['details']), area: new Set(['map']), }; /** maps element to set of elements that can not be it's children, others can */ const knownInvalidChildren$1 = { p: new Set([ 'address', 'article', 'aside', 'blockquote', 'center', 'details', 'dialog', 'dir', 'div', 'dl', 'fieldset', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'li', 'main', 'nav', 'menu', 'ol', 'p', 'pre', 'section', 'table', 'ul', ]), svg: new Set([ 'b', 'blockquote', 'br', 'code', 'dd', 'div', 'dl', 'dt', 'em', 'embed', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'li', 'menu', 'meta', 'ol', 'p', 'pre', 'ruby', 's', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'u', 'ul', 'var', ]), }; /** maps element to set of elements that can not be it's parent, others can */ const knownInvalidParents$1 = { a: new Set(['a']), button: new Set(['button']), dd: new Set(['dd', 'dt']), dt: new Set(['dd', 'dt']), form: new Set(['form']), li: new Set(['li']), h1: headings, h2: headings, h3: headings, h4: headings, h5: headings, h6: headings, }; var mapping = { onlyValidChildren: onlyValidChildren$1, onlyValidParents: onlyValidParents$1, knownInvalidChildren: knownInvalidChildren$1, knownInvalidParents: knownInvalidParents$1, }; const { onlyValidChildren, onlyValidParents, knownInvalidChildren, knownInvalidParents, } = mapping; /** * returns true if given parent-child nesting is valid HTML * @param {string} child * @param {string} parent * @returns {boolean} */ function isValidHTMLNesting(parent, child) { // if we know the list of children that are the only valid children for the given parent if (parent in onlyValidChildren) { return onlyValidChildren[parent].has(child); } // if we know the list of parents that are the only valid parents for the given child if (child in onlyValidParents) { return onlyValidParents[child].has(parent); } // if we know the list of children that are NOT valid for the given parent if (parent in knownInvalidChildren) { // check if the child is in the list of invalid children // if so, return false if (knownInvalidChildren[parent].has(child)) return false; } // if we know the list of parents that are NOT valid for the given child if (child in knownInvalidParents) { // check if the parent is in the list of invalid parents // if so, return false if (knownInvalidParents[child].has(parent)) return false; } return true; } var src = { isValidHTMLNesting, }; /** If the name starts with uppercase, it's a component name */ function isCompName(str) { return str[0] === str[0].toUpperCase(); } /** node is JSXElement if it's type is JSXElement */ function isJSXElement(node) { return typeof node === 'object' && node !== null && node.type === 'JSXElement'; } const noInvalidJSXNesting = { meta: { type: 'problem', }, create(context) { return { JSXElement(node) { // get node and it's parent const jsxElement = node; const parent = node.parent; // return if node is not a native element const elName = jsxElement.openingElement.name; if (elName.type !== 'JSXIdentifier') return; if (isCompName(elName.name)) return; // return if parent is not a native element if (!isJSXElement(parent)) return; const parentElName = parent.openingElement.name; if (parentElName.type !== 'JSXIdentifier') return; if (isCompName(parentElName.name)) return; // if both are native elements, check if the nesting is valid // return if nesting is valid if (src.isValidHTMLNesting(parentElName.name, elName.name)) return; // report error if nesting is invalid context.report({ node, message: `Invalid HTML nesting: <${elName.name}> can not be child of <${parentElName.name}>`, }); }, }; }, }; var index = { rules: { 'no-invalid-jsx-nesting': noInvalidJSXNesting, }, }; module.exports = index;