htmlNesting.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /**
  2. * Copied from https://github.com/MananTank/validate-html-nesting
  3. * with ISC license
  4. *
  5. * To avoid runtime dependency on validate-html-nesting
  6. * This file should not change very often in the original repo
  7. * but we may need to keep it up-to-date from time to time.
  8. */
  9. /**
  10. * returns true if given parent-child nesting is valid HTML
  11. */
  12. export function isValidHTMLNesting(parent: string, child: string): boolean {
  13. // if the parent is a template, it can have any child
  14. if (parent === 'template') {
  15. return true
  16. }
  17. // if we know the list of children that are the only valid children for the given parent
  18. if (parent in onlyValidChildren) {
  19. return onlyValidChildren[parent].has(child)
  20. }
  21. // if we know the list of parents that are the only valid parents for the given child
  22. if (child in onlyValidParents) {
  23. return onlyValidParents[child].has(parent)
  24. }
  25. // if we know the list of children that are NOT valid for the given parent
  26. if (parent in knownInvalidChildren) {
  27. // check if the child is in the list of invalid children
  28. // if so, return false
  29. if (knownInvalidChildren[parent].has(child)) return false
  30. }
  31. // if we know the list of parents that are NOT valid for the given child
  32. if (child in knownInvalidParents) {
  33. // check if the parent is in the list of invalid parents
  34. // if so, return false
  35. if (knownInvalidParents[child].has(parent)) return false
  36. }
  37. return true
  38. }
  39. const headings = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
  40. const emptySet = new Set([])
  41. /**
  42. * maps element to set of elements that can be it's children, no other */
  43. const onlyValidChildren: Record<string, Set<string>> = {
  44. head: new Set([
  45. 'base',
  46. 'basefront',
  47. 'bgsound',
  48. 'link',
  49. 'meta',
  50. 'title',
  51. 'noscript',
  52. 'noframes',
  53. 'style',
  54. 'script',
  55. 'template',
  56. ]),
  57. optgroup: new Set(['option']),
  58. select: new Set(['optgroup', 'option', 'hr']),
  59. // table
  60. table: new Set(['caption', 'colgroup', 'tbody', 'tfoot', 'thead']),
  61. tr: new Set(['td', 'th']),
  62. colgroup: new Set(['col']),
  63. tbody: new Set(['tr']),
  64. thead: new Set(['tr']),
  65. tfoot: new Set(['tr']),
  66. // these elements can not have any children elements
  67. script: emptySet,
  68. iframe: emptySet,
  69. option: emptySet,
  70. textarea: emptySet,
  71. style: emptySet,
  72. title: emptySet,
  73. }
  74. /** maps elements to set of elements which can be it's parent, no other */
  75. const onlyValidParents: Record<string, Set<string>> = {
  76. // sections
  77. html: emptySet,
  78. body: new Set(['html']),
  79. head: new Set(['html']),
  80. // table
  81. td: new Set(['tr']),
  82. colgroup: new Set(['table']),
  83. caption: new Set(['table']),
  84. tbody: new Set(['table']),
  85. tfoot: new Set(['table']),
  86. col: new Set(['colgroup']),
  87. th: new Set(['tr']),
  88. thead: new Set(['table']),
  89. tr: new Set(['tbody', 'thead', 'tfoot']),
  90. // data list
  91. dd: new Set(['dl', 'div']),
  92. dt: new Set(['dl', 'div']),
  93. // other
  94. figcaption: new Set(['figure']),
  95. // li: new Set(["ul", "ol"]),
  96. summary: new Set(['details']),
  97. area: new Set(['map']),
  98. } as const
  99. /** maps element to set of elements that can not be it's children, others can */
  100. const knownInvalidChildren: Record<string, Set<string>> = {
  101. p: new Set([
  102. 'address',
  103. 'article',
  104. 'aside',
  105. 'blockquote',
  106. 'center',
  107. 'details',
  108. 'dialog',
  109. 'dir',
  110. 'div',
  111. 'dl',
  112. 'fieldset',
  113. 'figure',
  114. 'footer',
  115. 'form',
  116. 'h1',
  117. 'h2',
  118. 'h3',
  119. 'h4',
  120. 'h5',
  121. 'h6',
  122. 'header',
  123. 'hgroup',
  124. 'hr',
  125. 'li',
  126. 'main',
  127. 'nav',
  128. 'menu',
  129. 'ol',
  130. 'p',
  131. 'pre',
  132. 'section',
  133. 'table',
  134. 'ul',
  135. ]),
  136. svg: new Set([
  137. 'b',
  138. 'blockquote',
  139. 'br',
  140. 'code',
  141. 'dd',
  142. 'div',
  143. 'dl',
  144. 'dt',
  145. 'em',
  146. 'embed',
  147. 'h1',
  148. 'h2',
  149. 'h3',
  150. 'h4',
  151. 'h5',
  152. 'h6',
  153. 'hr',
  154. 'i',
  155. 'img',
  156. 'li',
  157. 'menu',
  158. 'meta',
  159. 'ol',
  160. 'p',
  161. 'pre',
  162. 'ruby',
  163. 's',
  164. 'small',
  165. 'span',
  166. 'strong',
  167. 'sub',
  168. 'sup',
  169. 'table',
  170. 'u',
  171. 'ul',
  172. 'var',
  173. ]),
  174. } as const
  175. /** maps element to set of elements that can not be it's parent, others can */
  176. const knownInvalidParents: Record<string, Set<string>> = {
  177. a: new Set(['a']),
  178. button: new Set(['button']),
  179. dd: new Set(['dd', 'dt']),
  180. dt: new Set(['dd', 'dt']),
  181. form: new Set(['form']),
  182. li: new Set(['li']),
  183. h1: headings,
  184. h2: headings,
  185. h3: headings,
  186. h4: headings,
  187. h5: headings,
  188. h6: headings,
  189. }