@redocly/recheck 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (591) hide show
  1. package/README.md +1006 -56
  2. package/dist/cli.js +27 -7
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/markdoc-schema.d.ts +17 -0
  5. package/dist/commands/markdoc-schema.d.ts.map +1 -0
  6. package/dist/commands/markdoc-schema.js +127 -0
  7. package/dist/commands/markdoc-schema.js.map +1 -0
  8. package/dist/commands/run.d.ts +0 -1
  9. package/dist/commands/run.d.ts.map +1 -1
  10. package/dist/commands/run.js +68 -5
  11. package/dist/commands/run.js.map +1 -1
  12. package/dist/config/load.d.ts +11 -0
  13. package/dist/config/load.d.ts.map +1 -1
  14. package/dist/config/load.js +12 -2
  15. package/dist/config/load.js.map +1 -1
  16. package/dist/config/presets/google.d.ts +3 -0
  17. package/dist/config/presets/google.d.ts.map +1 -0
  18. package/dist/config/presets/google.js +1671 -0
  19. package/dist/config/presets/google.js.map +1 -0
  20. package/dist/config/presets/inclusive-language.d.ts +3 -0
  21. package/dist/config/presets/inclusive-language.d.ts.map +1 -0
  22. package/dist/config/presets/inclusive-language.js +321 -0
  23. package/dist/config/presets/inclusive-language.js.map +1 -0
  24. package/dist/config/presets/index.d.ts +67 -0
  25. package/dist/config/presets/index.d.ts.map +1 -0
  26. package/dist/config/presets/index.js +137 -0
  27. package/dist/config/presets/index.js.map +1 -0
  28. package/dist/config/presets/markdoc.d.ts +21 -0
  29. package/dist/config/presets/markdoc.d.ts.map +1 -0
  30. package/dist/config/presets/markdoc.js +101 -0
  31. package/dist/config/presets/markdoc.js.map +1 -0
  32. package/dist/config/presets/markdown-relaxed.d.ts +3 -0
  33. package/dist/config/presets/markdown-relaxed.d.ts.map +1 -0
  34. package/dist/config/presets/markdown-relaxed.js +98 -0
  35. package/dist/config/presets/markdown-relaxed.js.map +1 -0
  36. package/dist/config/presets/markdown.d.ts +43 -0
  37. package/dist/config/presets/markdown.d.ts.map +1 -0
  38. package/dist/config/presets/markdown.js +132 -0
  39. package/dist/config/presets/markdown.js.map +1 -0
  40. package/dist/config/presets/microsoft.d.ts +3 -0
  41. package/dist/config/presets/microsoft.d.ts.map +1 -0
  42. package/dist/config/presets/microsoft.js +2268 -0
  43. package/dist/config/presets/microsoft.js.map +1 -0
  44. package/dist/config/presets/minimal.d.ts +3 -0
  45. package/dist/config/presets/minimal.d.ts.map +1 -0
  46. package/dist/config/presets/minimal.js +21 -0
  47. package/dist/config/presets/minimal.js.map +1 -0
  48. package/dist/config/presets/plain-language.d.ts +3 -0
  49. package/dist/config/presets/plain-language.d.ts.map +1 -0
  50. package/dist/config/presets/plain-language.js +351 -0
  51. package/dist/config/presets/plain-language.js.map +1 -0
  52. package/dist/config/presets/prose.d.ts +52 -0
  53. package/dist/config/presets/prose.d.ts.map +1 -0
  54. package/dist/config/presets/prose.js +138 -0
  55. package/dist/config/presets/prose.js.map +1 -0
  56. package/dist/config/schema.d.ts +128 -22
  57. package/dist/config/schema.d.ts.map +1 -1
  58. package/dist/config/schema.js +105 -21
  59. package/dist/config/schema.js.map +1 -1
  60. package/dist/config/validate.d.ts +12 -2
  61. package/dist/config/validate.d.ts.map +1 -1
  62. package/dist/config/validate.js +1200 -44
  63. package/dist/config/validate.js.map +1 -1
  64. package/dist/core/auto-fix.d.ts +8 -13
  65. package/dist/core/auto-fix.d.ts.map +1 -1
  66. package/dist/core/auto-fix.js +94 -75
  67. package/dist/core/auto-fix.js.map +1 -1
  68. package/dist/core/case-preserve.d.ts +46 -0
  69. package/dist/core/case-preserve.d.ts.map +1 -0
  70. package/dist/core/case-preserve.js +57 -0
  71. package/dist/core/case-preserve.js.map +1 -0
  72. package/dist/core/directives.d.ts +9 -0
  73. package/dist/core/directives.d.ts.map +1 -0
  74. package/dist/core/directives.js +73 -0
  75. package/dist/core/directives.js.map +1 -0
  76. package/dist/core/files.d.ts +63 -0
  77. package/dist/core/files.d.ts.map +1 -1
  78. package/dist/core/files.js +185 -0
  79. package/dist/core/files.js.map +1 -1
  80. package/dist/core/inline-code.d.ts +87 -0
  81. package/dist/core/inline-code.d.ts.map +1 -0
  82. package/dist/core/inline-code.js +104 -0
  83. package/dist/core/inline-code.js.map +1 -0
  84. package/dist/core/line-endings.d.ts +32 -0
  85. package/dist/core/line-endings.d.ts.map +1 -0
  86. package/dist/core/line-endings.js +65 -0
  87. package/dist/core/line-endings.js.map +1 -0
  88. package/dist/core/markdoc-tags.d.ts +79 -0
  89. package/dist/core/markdoc-tags.d.ts.map +1 -0
  90. package/dist/core/markdoc-tags.js +131 -0
  91. package/dist/core/markdoc-tags.js.map +1 -0
  92. package/dist/core/runner.d.ts +92 -3
  93. package/dist/core/runner.d.ts.map +1 -1
  94. package/dist/core/runner.js +348 -110
  95. package/dist/core/runner.js.map +1 -1
  96. package/dist/core/timing.d.ts.map +1 -1
  97. package/dist/data/markdoc-realm-schema.d.ts +3 -0
  98. package/dist/data/markdoc-realm-schema.d.ts.map +1 -0
  99. package/dist/data/markdoc-realm-schema.js +760 -0
  100. package/dist/data/markdoc-realm-schema.js.map +1 -0
  101. package/dist/data/proper-nouns.d.ts +2 -0
  102. package/dist/data/proper-nouns.d.ts.map +1 -0
  103. package/dist/data/proper-nouns.js +208 -0
  104. package/dist/data/proper-nouns.js.map +1 -0
  105. package/dist/index.d.ts +90 -0
  106. package/dist/index.d.ts.map +1 -0
  107. package/dist/index.js +152 -0
  108. package/dist/index.js.map +1 -0
  109. package/dist/metrics/formulas.d.ts +17 -0
  110. package/dist/metrics/formulas.d.ts.map +1 -0
  111. package/dist/metrics/formulas.js +70 -0
  112. package/dist/metrics/formulas.js.map +1 -0
  113. package/dist/metrics/index.d.ts +5 -0
  114. package/dist/metrics/index.d.ts.map +1 -0
  115. package/dist/metrics/index.js +3 -0
  116. package/dist/metrics/index.js.map +1 -0
  117. package/dist/metrics/statistics.d.ts +27 -0
  118. package/dist/metrics/statistics.d.ts.map +1 -0
  119. package/dist/metrics/statistics.js +56 -0
  120. package/dist/metrics/statistics.js.map +1 -0
  121. package/dist/parser/index.d.ts +18 -0
  122. package/dist/parser/index.d.ts.map +1 -0
  123. package/dist/parser/index.js +168 -0
  124. package/dist/parser/index.js.map +1 -0
  125. package/dist/parser/markdoc/extract-statics.d.ts +45 -0
  126. package/dist/parser/markdoc/extract-statics.d.ts.map +1 -0
  127. package/dist/parser/markdoc/extract-statics.js +139 -0
  128. package/dist/parser/markdoc/extract-statics.js.map +1 -0
  129. package/dist/parser/markdoc/pairing.d.ts +63 -0
  130. package/dist/parser/markdoc/pairing.d.ts.map +1 -0
  131. package/dist/parser/markdoc/pairing.js +94 -0
  132. package/dist/parser/markdoc/pairing.js.map +1 -0
  133. package/dist/parser/markdoc/schema.d.ts +85 -0
  134. package/dist/parser/markdoc/schema.d.ts.map +1 -0
  135. package/dist/parser/markdoc/schema.js +86 -0
  136. package/dist/parser/markdoc/schema.js.map +1 -0
  137. package/dist/parser/markdoc/span.d.ts +64 -0
  138. package/dist/parser/markdoc/span.d.ts.map +1 -0
  139. package/dist/parser/markdoc/span.js +729 -0
  140. package/dist/parser/markdoc/span.js.map +1 -0
  141. package/dist/parser/markdoc/structure.d.ts +28 -0
  142. package/dist/parser/markdoc/structure.d.ts.map +1 -0
  143. package/dist/parser/markdoc/structure.js +153 -0
  144. package/dist/parser/markdoc/structure.js.map +1 -0
  145. package/dist/parser/markdoc/syntax.d.ts +44 -0
  146. package/dist/parser/markdoc/syntax.d.ts.map +1 -0
  147. package/dist/parser/markdoc/syntax.js +317 -0
  148. package/dist/parser/markdoc/syntax.js.map +1 -0
  149. package/dist/parser/types.d.ts +18 -0
  150. package/dist/parser/types.d.ts.map +1 -0
  151. package/dist/parser/types.js.map +1 -0
  152. package/dist/reporter/fixes.d.ts.map +1 -1
  153. package/dist/reporter/fixes.js +22 -1
  154. package/dist/reporter/fixes.js.map +1 -1
  155. package/dist/reporter/statistics.d.ts +1 -8
  156. package/dist/reporter/statistics.d.ts.map +1 -1
  157. package/dist/reporter/statistics.js.map +1 -1
  158. package/dist/rules/registry.d.ts +15 -0
  159. package/dist/rules/registry.d.ts.map +1 -0
  160. package/dist/rules/registry.js +81 -0
  161. package/dist/rules/registry.js.map +1 -0
  162. package/dist/rules/scope/capitalization.d.ts +3 -0
  163. package/dist/rules/scope/capitalization.d.ts.map +1 -0
  164. package/dist/rules/scope/capitalization.js +161 -0
  165. package/dist/rules/scope/capitalization.js.map +1 -0
  166. package/dist/rules/scope/conditional.d.ts +3 -0
  167. package/dist/rules/scope/conditional.d.ts.map +1 -0
  168. package/dist/rules/scope/conditional.js +112 -0
  169. package/dist/rules/scope/conditional.js.map +1 -0
  170. package/dist/rules/scope/consistency.d.ts +3 -0
  171. package/dist/rules/scope/consistency.d.ts.map +1 -0
  172. package/dist/rules/scope/consistency.js +178 -0
  173. package/dist/rules/scope/consistency.js.map +1 -0
  174. package/dist/rules/scope/length.d.ts +3 -0
  175. package/dist/rules/scope/length.d.ts.map +1 -0
  176. package/dist/rules/scope/length.js +69 -0
  177. package/dist/rules/scope/length.js.map +1 -0
  178. package/dist/rules/scope/max-image-size.d.ts +3 -0
  179. package/dist/rules/scope/max-image-size.d.ts.map +1 -0
  180. package/dist/rules/scope/max-image-size.js +65 -0
  181. package/dist/rules/scope/max-image-size.js.map +1 -0
  182. package/dist/rules/scope/metric.d.ts +17 -0
  183. package/dist/rules/scope/metric.d.ts.map +1 -0
  184. package/dist/rules/scope/metric.js +216 -0
  185. package/dist/rules/scope/metric.js.map +1 -0
  186. package/dist/rules/scope/occurrence.d.ts +3 -0
  187. package/dist/rules/scope/occurrence.d.ts.map +1 -0
  188. package/dist/rules/scope/occurrence.js +48 -0
  189. package/dist/rules/scope/occurrence.js.map +1 -0
  190. package/dist/rules/scope/pattern.d.ts +3 -0
  191. package/dist/rules/scope/pattern.d.ts.map +1 -0
  192. package/dist/rules/scope/pattern.js +75 -0
  193. package/dist/rules/scope/pattern.js.map +1 -0
  194. package/dist/rules/scope/repetition.d.ts +3 -0
  195. package/dist/rules/scope/repetition.d.ts.map +1 -0
  196. package/dist/rules/scope/repetition.js +139 -0
  197. package/dist/rules/scope/repetition.js.map +1 -0
  198. package/dist/rules/scope/semantic-line-breaks.d.ts +3 -0
  199. package/dist/rules/scope/semantic-line-breaks.d.ts.map +1 -0
  200. package/dist/rules/scope/semantic-line-breaks.js +213 -0
  201. package/dist/rules/scope/semantic-line-breaks.js.map +1 -0
  202. package/dist/rules/scope/spelling.d.ts +27 -0
  203. package/dist/rules/scope/spelling.d.ts.map +1 -0
  204. package/dist/rules/scope/spelling.js +227 -0
  205. package/dist/rules/scope/spelling.js.map +1 -0
  206. package/dist/rules/scope/swap.d.ts +3 -0
  207. package/dist/rules/scope/swap.d.ts.map +1 -0
  208. package/dist/rules/scope/swap.js +149 -0
  209. package/dist/rules/scope/swap.js.map +1 -0
  210. package/dist/rules/scope/title-case.d.ts +42 -0
  211. package/dist/rules/scope/title-case.d.ts.map +1 -0
  212. package/dist/rules/scope/title-case.js +264 -0
  213. package/dist/rules/scope/title-case.js.map +1 -0
  214. package/dist/rules/token/blanks-around-fences.d.ts +3 -0
  215. package/dist/rules/token/blanks-around-fences.d.ts.map +1 -0
  216. package/dist/rules/token/blanks-around-fences.js +44 -0
  217. package/dist/rules/token/blanks-around-fences.js.map +1 -0
  218. package/dist/rules/token/blanks-around-headings.d.ts +3 -0
  219. package/dist/rules/token/blanks-around-headings.d.ts.map +1 -0
  220. package/dist/rules/token/blanks-around-headings.js +108 -0
  221. package/dist/rules/token/blanks-around-headings.js.map +1 -0
  222. package/dist/rules/token/blanks-around-lists.d.ts +3 -0
  223. package/dist/rules/token/blanks-around-lists.d.ts.map +1 -0
  224. package/dist/rules/token/blanks-around-lists.js +56 -0
  225. package/dist/rules/token/blanks-around-lists.js.map +1 -0
  226. package/dist/rules/token/blanks-around-tables.d.ts +3 -0
  227. package/dist/rules/token/blanks-around-tables.d.ts.map +1 -0
  228. package/dist/rules/token/blanks-around-tables.js +42 -0
  229. package/dist/rules/token/blanks-around-tables.js.map +1 -0
  230. package/dist/rules/token/code-block-style.d.ts +3 -0
  231. package/dist/rules/token/code-block-style.d.ts.map +1 -0
  232. package/dist/rules/token/code-block-style.js +30 -0
  233. package/dist/rules/token/code-block-style.js.map +1 -0
  234. package/dist/rules/token/code-fence-style.d.ts +3 -0
  235. package/dist/rules/token/code-fence-style.d.ts.map +1 -0
  236. package/dist/rules/token/code-fence-style.js +35 -0
  237. package/dist/rules/token/code-fence-style.js.map +1 -0
  238. package/dist/rules/token/commands-show-output.d.ts +3 -0
  239. package/dist/rules/token/commands-show-output.d.ts.map +1 -0
  240. package/dist/rules/token/commands-show-output.js +38 -0
  241. package/dist/rules/token/commands-show-output.js.map +1 -0
  242. package/dist/rules/token/descriptive-link-text.d.ts +3 -0
  243. package/dist/rules/token/descriptive-link-text.d.ts.map +1 -0
  244. package/dist/rules/token/descriptive-link-text.js +53 -0
  245. package/dist/rules/token/descriptive-link-text.js.map +1 -0
  246. package/dist/rules/token/emphasis-style.d.ts +3 -0
  247. package/dist/rules/token/emphasis-style.d.ts.map +1 -0
  248. package/dist/rules/token/emphasis-style.js +51 -0
  249. package/dist/rules/token/emphasis-style.js.map +1 -0
  250. package/dist/rules/token/fenced-code-language.d.ts +3 -0
  251. package/dist/rules/token/fenced-code-language.d.ts.map +1 -0
  252. package/dist/rules/token/fenced-code-language.js +35 -0
  253. package/dist/rules/token/fenced-code-language.js.map +1 -0
  254. package/dist/rules/token/first-line-h1.d.ts +3 -0
  255. package/dist/rules/token/first-line-h1.d.ts.map +1 -0
  256. package/dist/rules/token/first-line-h1.js +107 -0
  257. package/dist/rules/token/first-line-h1.js.map +1 -0
  258. package/dist/rules/token/heading-increment.d.ts +3 -0
  259. package/dist/rules/token/heading-increment.d.ts.map +1 -0
  260. package/dist/rules/token/heading-increment.js +27 -0
  261. package/dist/rules/token/heading-increment.js.map +1 -0
  262. package/dist/rules/token/heading-start-left.d.ts +3 -0
  263. package/dist/rules/token/heading-start-left.d.ts.map +1 -0
  264. package/dist/rules/token/heading-start-left.js +31 -0
  265. package/dist/rules/token/heading-start-left.js.map +1 -0
  266. package/dist/rules/token/heading-style.d.ts +3 -0
  267. package/dist/rules/token/heading-style.d.ts.map +1 -0
  268. package/dist/rules/token/heading-style.js +41 -0
  269. package/dist/rules/token/heading-style.js.map +1 -0
  270. package/dist/rules/token/helpers.d.ts +313 -0
  271. package/dist/rules/token/helpers.d.ts.map +1 -0
  272. package/dist/rules/token/helpers.js +746 -0
  273. package/dist/rules/token/helpers.js.map +1 -0
  274. package/dist/rules/token/hr-style.d.ts +3 -0
  275. package/dist/rules/token/hr-style.d.ts.map +1 -0
  276. package/dist/rules/token/hr-style.js +28 -0
  277. package/dist/rules/token/hr-style.js.map +1 -0
  278. package/dist/rules/token/index.d.ts +75 -0
  279. package/dist/rules/token/index.d.ts.map +1 -0
  280. package/dist/rules/token/index.js +226 -0
  281. package/dist/rules/token/index.js.map +1 -0
  282. package/dist/rules/token/line-length.d.ts +3 -0
  283. package/dist/rules/token/line-length.d.ts.map +1 -0
  284. package/dist/rules/token/line-length.js +120 -0
  285. package/dist/rules/token/line-length.js.map +1 -0
  286. package/dist/rules/token/link-fragments.d.ts +3 -0
  287. package/dist/rules/token/link-fragments.d.ts.map +1 -0
  288. package/dist/rules/token/link-fragments.js +145 -0
  289. package/dist/rules/token/link-fragments.js.map +1 -0
  290. package/dist/rules/token/link-image-reference-definitions.d.ts +3 -0
  291. package/dist/rules/token/link-image-reference-definitions.d.ts.map +1 -0
  292. package/dist/rules/token/link-image-reference-definitions.js +50 -0
  293. package/dist/rules/token/link-image-reference-definitions.js.map +1 -0
  294. package/dist/rules/token/link-image-style.d.ts +3 -0
  295. package/dist/rules/token/link-image-style.d.ts.map +1 -0
  296. package/dist/rules/token/link-image-style.js +131 -0
  297. package/dist/rules/token/link-image-style.js.map +1 -0
  298. package/dist/rules/token/list-indent.d.ts +3 -0
  299. package/dist/rules/token/list-indent.d.ts.map +1 -0
  300. package/dist/rules/token/list-indent.js +60 -0
  301. package/dist/rules/token/list-indent.js.map +1 -0
  302. package/dist/rules/token/list-length.d.ts +3 -0
  303. package/dist/rules/token/list-length.d.ts.map +1 -0
  304. package/dist/rules/token/list-length.js +55 -0
  305. package/dist/rules/token/list-length.js.map +1 -0
  306. package/dist/rules/token/list-marker-space.d.ts +3 -0
  307. package/dist/rules/token/list-marker-space.d.ts.map +1 -0
  308. package/dist/rules/token/list-marker-space.js +52 -0
  309. package/dist/rules/token/list-marker-space.js.map +1 -0
  310. package/dist/rules/token/markdoc-attributes.d.ts +3 -0
  311. package/dist/rules/token/markdoc-attributes.d.ts.map +1 -0
  312. package/dist/rules/token/markdoc-attributes.js +269 -0
  313. package/dist/rules/token/markdoc-attributes.js.map +1 -0
  314. package/dist/rules/token/markdoc-pairing.d.ts +3 -0
  315. package/dist/rules/token/markdoc-pairing.d.ts.map +1 -0
  316. package/dist/rules/token/markdoc-pairing.js +73 -0
  317. package/dist/rules/token/markdoc-pairing.js.map +1 -0
  318. package/dist/rules/token/markdoc-syntax.d.ts +3 -0
  319. package/dist/rules/token/markdoc-syntax.d.ts.map +1 -0
  320. package/dist/rules/token/markdoc-syntax.js +119 -0
  321. package/dist/rules/token/markdoc-syntax.js.map +1 -0
  322. package/dist/rules/token/markdoc-unknown-tag.d.ts +3 -0
  323. package/dist/rules/token/markdoc-unknown-tag.d.ts.map +1 -0
  324. package/dist/rules/token/markdoc-unknown-tag.js +64 -0
  325. package/dist/rules/token/markdoc-unknown-tag.js.map +1 -0
  326. package/dist/rules/token/messages.d.ts +4 -0
  327. package/dist/rules/token/messages.d.ts.map +1 -0
  328. package/dist/rules/token/messages.js +20 -0
  329. package/dist/rules/token/messages.js.map +1 -0
  330. package/dist/rules/token/no-alt-text.d.ts +3 -0
  331. package/dist/rules/token/no-alt-text.d.ts.map +1 -0
  332. package/dist/rules/token/no-alt-text.js +47 -0
  333. package/dist/rules/token/no-alt-text.js.map +1 -0
  334. package/dist/rules/token/no-bare-urls.d.ts +3 -0
  335. package/dist/rules/token/no-bare-urls.d.ts.map +1 -0
  336. package/dist/rules/token/no-bare-urls.js +88 -0
  337. package/dist/rules/token/no-bare-urls.js.map +1 -0
  338. package/dist/rules/token/no-blanks-blockquote.d.ts +3 -0
  339. package/dist/rules/token/no-blanks-blockquote.d.ts.map +1 -0
  340. package/dist/rules/token/no-blanks-blockquote.js +39 -0
  341. package/dist/rules/token/no-blanks-blockquote.js.map +1 -0
  342. package/dist/rules/token/no-duplicate-heading.d.ts +3 -0
  343. package/dist/rules/token/no-duplicate-heading.d.ts.map +1 -0
  344. package/dist/rules/token/no-duplicate-heading.js +101 -0
  345. package/dist/rules/token/no-duplicate-heading.js.map +1 -0
  346. package/dist/rules/token/no-duplicate-link-destinations.d.ts +3 -0
  347. package/dist/rules/token/no-duplicate-link-destinations.d.ts.map +1 -0
  348. package/dist/rules/token/no-duplicate-link-destinations.js +65 -0
  349. package/dist/rules/token/no-duplicate-link-destinations.js.map +1 -0
  350. package/dist/rules/token/no-emphasis-as-heading.d.ts +3 -0
  351. package/dist/rules/token/no-emphasis-as-heading.d.ts.map +1 -0
  352. package/dist/rules/token/no-emphasis-as-heading.js +44 -0
  353. package/dist/rules/token/no-emphasis-as-heading.js.map +1 -0
  354. package/dist/rules/token/no-empty-headings.d.ts +3 -0
  355. package/dist/rules/token/no-empty-headings.d.ts.map +1 -0
  356. package/dist/rules/token/no-empty-headings.js +28 -0
  357. package/dist/rules/token/no-empty-headings.js.map +1 -0
  358. package/dist/rules/token/no-empty-links.d.ts +3 -0
  359. package/dist/rules/token/no-empty-links.d.ts.map +1 -0
  360. package/dist/rules/token/no-empty-links.js +67 -0
  361. package/dist/rules/token/no-empty-links.js.map +1 -0
  362. package/dist/rules/token/no-hard-tabs.d.ts +3 -0
  363. package/dist/rules/token/no-hard-tabs.d.ts.map +1 -0
  364. package/dist/rules/token/no-hard-tabs.js +76 -0
  365. package/dist/rules/token/no-hard-tabs.js.map +1 -0
  366. package/dist/rules/token/no-inline-html.d.ts +3 -0
  367. package/dist/rules/token/no-inline-html.d.ts.map +1 -0
  368. package/dist/rules/token/no-inline-html.js +45 -0
  369. package/dist/rules/token/no-inline-html.js.map +1 -0
  370. package/dist/rules/token/no-missing-space-atx.d.ts +3 -0
  371. package/dist/rules/token/no-missing-space-atx.d.ts.map +1 -0
  372. package/dist/rules/token/no-missing-space-atx.js +36 -0
  373. package/dist/rules/token/no-missing-space-atx.js.map +1 -0
  374. package/dist/rules/token/no-missing-space-closed-atx.d.ts +3 -0
  375. package/dist/rules/token/no-missing-space-closed-atx.d.ts.map +1 -0
  376. package/dist/rules/token/no-missing-space-closed-atx.js +45 -0
  377. package/dist/rules/token/no-missing-space-closed-atx.js.map +1 -0
  378. package/dist/rules/token/no-multiple-blanks.d.ts +3 -0
  379. package/dist/rules/token/no-multiple-blanks.d.ts.map +1 -0
  380. package/dist/rules/token/no-multiple-blanks.js +35 -0
  381. package/dist/rules/token/no-multiple-blanks.js.map +1 -0
  382. package/dist/rules/token/no-multiple-space-atx.d.ts +13 -0
  383. package/dist/rules/token/no-multiple-space-atx.d.ts.map +1 -0
  384. package/dist/rules/token/no-multiple-space-atx.js +50 -0
  385. package/dist/rules/token/no-multiple-space-atx.js.map +1 -0
  386. package/dist/rules/token/no-multiple-space-blockquote.d.ts +3 -0
  387. package/dist/rules/token/no-multiple-space-blockquote.d.ts.map +1 -0
  388. package/dist/rules/token/no-multiple-space-blockquote.js +43 -0
  389. package/dist/rules/token/no-multiple-space-blockquote.js.map +1 -0
  390. package/dist/rules/token/no-multiple-space-closed-atx.d.ts +3 -0
  391. package/dist/rules/token/no-multiple-space-closed-atx.d.ts.map +1 -0
  392. package/dist/rules/token/no-multiple-space-closed-atx.js +19 -0
  393. package/dist/rules/token/no-multiple-space-closed-atx.js.map +1 -0
  394. package/dist/rules/token/no-reversed-links.d.ts +3 -0
  395. package/dist/rules/token/no-reversed-links.d.ts.map +1 -0
  396. package/dist/rules/token/no-reversed-links.js +52 -0
  397. package/dist/rules/token/no-reversed-links.js.map +1 -0
  398. package/dist/rules/token/no-space-in-code.d.ts +3 -0
  399. package/dist/rules/token/no-space-in-code.d.ts.map +1 -0
  400. package/dist/rules/token/no-space-in-code.js +75 -0
  401. package/dist/rules/token/no-space-in-code.js.map +1 -0
  402. package/dist/rules/token/no-space-in-emphasis.d.ts +3 -0
  403. package/dist/rules/token/no-space-in-emphasis.d.ts.map +1 -0
  404. package/dist/rules/token/no-space-in-emphasis.js +79 -0
  405. package/dist/rules/token/no-space-in-emphasis.js.map +1 -0
  406. package/dist/rules/token/no-space-in-links.d.ts +3 -0
  407. package/dist/rules/token/no-space-in-links.d.ts.map +1 -0
  408. package/dist/rules/token/no-space-in-links.js +48 -0
  409. package/dist/rules/token/no-space-in-links.js.map +1 -0
  410. package/dist/rules/token/no-trailing-punctuation.d.ts +3 -0
  411. package/dist/rules/token/no-trailing-punctuation.d.ts.map +1 -0
  412. package/dist/rules/token/no-trailing-punctuation.js +38 -0
  413. package/dist/rules/token/no-trailing-punctuation.js.map +1 -0
  414. package/dist/rules/token/no-trailing-spaces.d.ts +3 -0
  415. package/dist/rules/token/no-trailing-spaces.d.ts.map +1 -0
  416. package/dist/rules/token/no-trailing-spaces.js +89 -0
  417. package/dist/rules/token/no-trailing-spaces.js.map +1 -0
  418. package/dist/rules/token/ol-prefix.d.ts +3 -0
  419. package/dist/rules/token/ol-prefix.d.ts.map +1 -0
  420. package/dist/rules/token/ol-prefix.js +70 -0
  421. package/dist/rules/token/ol-prefix.js.map +1 -0
  422. package/dist/rules/token/proper-names.d.ts +3 -0
  423. package/dist/rules/token/proper-names.d.ts.map +1 -0
  424. package/dist/rules/token/proper-names.js +93 -0
  425. package/dist/rules/token/proper-names.js.map +1 -0
  426. package/dist/rules/token/reference-links-images.d.ts +3 -0
  427. package/dist/rules/token/reference-links-images.d.ts.map +1 -0
  428. package/dist/rules/token/reference-links-images.js +36 -0
  429. package/dist/rules/token/reference-links-images.js.map +1 -0
  430. package/dist/rules/token/required-headings.d.ts +3 -0
  431. package/dist/rules/token/required-headings.d.ts.map +1 -0
  432. package/dist/rules/token/required-headings.js +83 -0
  433. package/dist/rules/token/required-headings.js.map +1 -0
  434. package/dist/rules/token/single-h1.d.ts +3 -0
  435. package/dist/rules/token/single-h1.d.ts.map +1 -0
  436. package/dist/rules/token/single-h1.js +56 -0
  437. package/dist/rules/token/single-h1.js.map +1 -0
  438. package/dist/rules/token/single-trailing-newline.d.ts +3 -0
  439. package/dist/rules/token/single-trailing-newline.d.ts.map +1 -0
  440. package/dist/rules/token/single-trailing-newline.js +25 -0
  441. package/dist/rules/token/single-trailing-newline.js.map +1 -0
  442. package/dist/rules/token/strong-style.d.ts +3 -0
  443. package/dist/rules/token/strong-style.d.ts.map +1 -0
  444. package/dist/rules/token/strong-style.js +51 -0
  445. package/dist/rules/token/strong-style.js.map +1 -0
  446. package/dist/rules/token/table-column-count.d.ts +3 -0
  447. package/dist/rules/token/table-column-count.d.ts.map +1 -0
  448. package/dist/rules/token/table-column-count.js +44 -0
  449. package/dist/rules/token/table-column-count.js.map +1 -0
  450. package/dist/rules/token/table-column-style.d.ts +3 -0
  451. package/dist/rules/token/table-column-style.d.ts.map +1 -0
  452. package/dist/rules/token/table-column-style.js +179 -0
  453. package/dist/rules/token/table-column-style.js.map +1 -0
  454. package/dist/rules/token/table-pipe-style.d.ts +3 -0
  455. package/dist/rules/token/table-pipe-style.d.ts.map +1 -0
  456. package/dist/rules/token/table-pipe-style.js +54 -0
  457. package/dist/rules/token/table-pipe-style.js.map +1 -0
  458. package/dist/rules/token/ul-indent.d.ts +3 -0
  459. package/dist/rules/token/ul-indent.d.ts.map +1 -0
  460. package/dist/rules/token/ul-indent.js +72 -0
  461. package/dist/rules/token/ul-indent.js.map +1 -0
  462. package/dist/rules/token/ul-style.d.ts +3 -0
  463. package/dist/rules/token/ul-style.d.ts.map +1 -0
  464. package/dist/rules/token/ul-style.js +80 -0
  465. package/dist/rules/token/ul-style.js.map +1 -0
  466. package/dist/rules/types.d.ts +81 -0
  467. package/dist/rules/types.d.ts.map +1 -0
  468. package/dist/rules/types.js +2 -0
  469. package/dist/rules/types.js.map +1 -0
  470. package/dist/rules/utils.d.ts +29 -0
  471. package/dist/rules/utils.d.ts.map +1 -0
  472. package/dist/{assertions → rules}/utils.js +27 -0
  473. package/dist/rules/utils.js.map +1 -0
  474. package/dist/scopes/extractor.d.ts +7 -0
  475. package/dist/scopes/extractor.d.ts.map +1 -0
  476. package/dist/scopes/extractor.js +475 -0
  477. package/dist/scopes/extractor.js.map +1 -0
  478. package/dist/scopes/selector.d.ts +51 -0
  479. package/dist/scopes/selector.d.ts.map +1 -0
  480. package/dist/scopes/selector.js +121 -0
  481. package/dist/scopes/selector.js.map +1 -0
  482. package/dist/scopes/sentences.d.ts +7 -0
  483. package/dist/scopes/sentences.d.ts.map +1 -0
  484. package/dist/scopes/sentences.js +115 -0
  485. package/dist/scopes/sentences.js.map +1 -0
  486. package/dist/scopes/types.d.ts +44 -0
  487. package/dist/scopes/types.d.ts.map +1 -0
  488. package/dist/scopes/types.js +2 -0
  489. package/dist/scopes/types.js.map +1 -0
  490. package/dist/scopes/vocabulary.d.ts +16 -0
  491. package/dist/scopes/vocabulary.d.ts.map +1 -0
  492. package/dist/scopes/vocabulary.js +70 -0
  493. package/dist/scopes/vocabulary.js.map +1 -0
  494. package/dist/types/assertions.d.ts +95 -32
  495. package/dist/types/assertions.d.ts.map +1 -1
  496. package/dist/types/problems.d.ts +4 -5
  497. package/dist/types/problems.d.ts.map +1 -1
  498. package/dist/types/rules.d.ts +4 -6
  499. package/dist/types/rules.d.ts.map +1 -1
  500. package/examples/appendices/google.appendix.yaml +91 -0
  501. package/examples/appendices/inclusive-language.appendix.yaml +61 -0
  502. package/examples/appendices/microsoft.appendix.yaml +99 -0
  503. package/examples/appendices/plain-language.appendix.yaml +88 -0
  504. package/examples/google.yaml +1525 -0
  505. package/examples/inclusive-language.yaml +304 -0
  506. package/examples/microsoft.yaml +1542 -0
  507. package/examples/plain-language.yaml +325 -0
  508. package/package.json +49 -16
  509. package/presets/google/PROVENANCE.md +1022 -0
  510. package/presets/google/sources.json +192 -0
  511. package/presets/inclusive-language/PROVENANCE.md +174 -0
  512. package/presets/inclusive-language/sources.json +107 -0
  513. package/presets/microsoft/PROVENANCE.md +1555 -0
  514. package/presets/microsoft/sources.json +494 -0
  515. package/presets/plain-language/PROVENANCE.md +364 -0
  516. package/presets/plain-language/sources.json +108 -0
  517. package/dist/assertions/bullet-style.d.ts +0 -3
  518. package/dist/assertions/bullet-style.d.ts.map +0 -1
  519. package/dist/assertions/bullet-style.js +0 -60
  520. package/dist/assertions/bullet-style.js.map +0 -1
  521. package/dist/assertions/index.d.ts +0 -21
  522. package/dist/assertions/index.d.ts.map +0 -1
  523. package/dist/assertions/index.js +0 -30
  524. package/dist/assertions/index.js.map +0 -1
  525. package/dist/assertions/max-image-size.d.ts +0 -3
  526. package/dist/assertions/max-image-size.d.ts.map +0 -1
  527. package/dist/assertions/max-image-size.js +0 -73
  528. package/dist/assertions/max-image-size.js.map +0 -1
  529. package/dist/assertions/max-line-length.d.ts +0 -3
  530. package/dist/assertions/max-line-length.d.ts.map +0 -1
  531. package/dist/assertions/max-line-length.js +0 -68
  532. package/dist/assertions/max-line-length.js.map +0 -1
  533. package/dist/assertions/no-broken-fragment-links.d.ts +0 -3
  534. package/dist/assertions/no-broken-fragment-links.d.ts.map +0 -1
  535. package/dist/assertions/no-broken-fragment-links.js +0 -79
  536. package/dist/assertions/no-broken-fragment-links.js.map +0 -1
  537. package/dist/assertions/no-duplicate-headings.d.ts +0 -3
  538. package/dist/assertions/no-duplicate-headings.d.ts.map +0 -1
  539. package/dist/assertions/no-duplicate-headings.js +0 -66
  540. package/dist/assertions/no-duplicate-headings.js.map +0 -1
  541. package/dist/assertions/no-hard-tabs.d.ts +0 -3
  542. package/dist/assertions/no-hard-tabs.d.ts.map +0 -1
  543. package/dist/assertions/no-hard-tabs.js +0 -63
  544. package/dist/assertions/no-hard-tabs.js.map +0 -1
  545. package/dist/assertions/no-trailing-spaces.d.ts +0 -3
  546. package/dist/assertions/no-trailing-spaces.d.ts.map +0 -1
  547. package/dist/assertions/no-trailing-spaces.js +0 -72
  548. package/dist/assertions/no-trailing-spaces.js.map +0 -1
  549. package/dist/assertions/pattern.d.ts +0 -3
  550. package/dist/assertions/pattern.d.ts.map +0 -1
  551. package/dist/assertions/pattern.js +0 -39
  552. package/dist/assertions/pattern.js.map +0 -1
  553. package/dist/assertions/semantic-line-breaks.d.ts +0 -3
  554. package/dist/assertions/semantic-line-breaks.d.ts.map +0 -1
  555. package/dist/assertions/semantic-line-breaks.js +0 -152
  556. package/dist/assertions/semantic-line-breaks.js.map +0 -1
  557. package/dist/assertions/swap.d.ts +0 -3
  558. package/dist/assertions/swap.d.ts.map +0 -1
  559. package/dist/assertions/swap.js +0 -39
  560. package/dist/assertions/swap.js.map +0 -1
  561. package/dist/assertions/utils.d.ts +0 -8
  562. package/dist/assertions/utils.d.ts.map +0 -1
  563. package/dist/assertions/utils.js.map +0 -1
  564. package/dist/core/scope-parser.d.ts +0 -26
  565. package/dist/core/scope-parser.d.ts.map +0 -1
  566. package/dist/core/scope-parser.js +0 -110
  567. package/dist/core/scope-parser.js.map +0 -1
  568. package/dist/files.d.ts +0 -2
  569. package/dist/files.d.ts.map +0 -1
  570. package/dist/files.js +0 -39
  571. package/dist/files.js.map +0 -1
  572. package/dist/load-config.d.ts +0 -25
  573. package/dist/load-config.d.ts.map +0 -1
  574. package/dist/load-config.js +0 -104
  575. package/dist/load-config.js.map +0 -1
  576. package/dist/load.d.ts +0 -25
  577. package/dist/load.d.ts.map +0 -1
  578. package/dist/load.js +0 -112
  579. package/dist/load.js.map +0 -1
  580. package/dist/scope.d.ts +0 -26
  581. package/dist/scope.d.ts.map +0 -1
  582. package/dist/scope.js +0 -110
  583. package/dist/scope.js.map +0 -1
  584. package/dist/types.d.ts +0 -109
  585. package/dist/types.d.ts.map +0 -1
  586. package/dist/types.js.map +0 -1
  587. package/dist/validate.d.ts +0 -31
  588. package/dist/validate.d.ts.map +0 -1
  589. package/dist/validate.js +0 -154
  590. package/dist/validate.js.map +0 -1
  591. /package/dist/{types.js → parser/types.js} +0 -0
@@ -0,0 +1,729 @@
1
+ // Pure, micromark-free parser for the interior of a single Markdoc
2
+ // `{% ... %}` span: kind, name, attributes, and 0-based offsets into the span
3
+ // text it was given. The tokenizer only locates span BOUNDARIES; all interior
4
+ // structure comes from this single-pass scanner, which depends on no
5
+ // document-level state and so is testable on plain strings.
6
+ //
7
+ // Invariant: never throws. A linter's primary input is broken documents, so
8
+ // anything unparseable comes back as `kind: 'malformed'` with a
9
+ // human-readable `reason`; callers always get a `ParsedMarkdocSpan`.
10
+ //
11
+ // Ground truth is the Markdoc RENDERER, not `tag.pegjs`'s raw grammar. The
12
+ // renderer collapses every line ending to a single `\n` before the grammar's
13
+ // whitespace rule runs, so a `\r\n` pair (or a lone `\r`) counts as ONE
14
+ // whitespace unit here -- see `scanMarkdocSpaceUnit`.
15
+ //
16
+ // Deliberate divergences from upstream Markdoc follow. Unless noted, each is
17
+ // a leniency: we accept what upstream rejects as a parse error.
18
+ //
19
+ // - Barewords: an unquoted identifier in a value slot (`{% t name=star %}`,
20
+ // `{% if maybe %}`) gets its own `'bareword'` kind and offsets so a rule can
21
+ // point at it, instead of collapsing the whole span to `malformed`.
22
+ // - Annotation bodies are never parsed -- `kind: 'annotation'`, no
23
+ // `attributes`, no `shortcuts` -- an extension point for the LSP phase. So
24
+ // sigil-first bodies upstream rejects still classify as annotations here:
25
+ // `{% .a.b %}`, `{% . %}`, `{% .a junk %}`, `{% #a=1 %}`. (Shortcuts inside
26
+ // a NAMED tag are a different matter: those are fully parsed into
27
+ // `shortcuts` -- see `MarkdocShortcut`.)
28
+ // - The attribute-first annotation form (`{% width="30%" %}`) does scan its
29
+ // body, so rather than adding a divergence it inherits the ones below:
30
+ // `{% a=1b=2 %}`, `{% a=1\fb=2 %}`, `{% a=fn(1,) %}`, `{% a="a\qb" %}` all
31
+ // classify as annotations. Genuinely broken bodies still land `malformed`
32
+ // (`{% a=b %}`, `{% a=1 b %}`, `{% width = "30%" %}`, `{% a="x" /%}`).
33
+ // - Glued attributes: `{% t a=1b=2 %}` parses as two attributes. The
34
+ // separator guard in `scanAttributes` gates only its shortcut branch, not
35
+ // the plain attribute-name scan.
36
+ // - `\f`/`\v` pass as an item separator, for attributes and shortcuts alike
37
+ // since both ride the same `scanAttributes` loop: `{% t a=1\fb=2 %}`,
38
+ // `{% t .a\f.b %}`. The one exception is the primary-to-first-attribute gap,
39
+ // which uses the strict `isMarkdocSpace`.
40
+ // - Lenient strings: `scanString` validates nothing between the quotes beyond
41
+ // finding the closing `"`, so it accepts any `\<char>` escape
42
+ // (`{% a="a\qb" %}`) and a raw, unescaped newline inside the quotes. Shared
43
+ // by attribute values, array/object interiors, and a variable's bracket tail
44
+ // (`{% $foo["a\qb"] %}`).
45
+ // - Array, object, and function interiors are only balanced and sliced, never
46
+ // validated. Upstream's `Function` production is the strict one, so
47
+ // `{% fn(1,) %}`, `{% fn(1 ) %}`, and the multiline `{% fn(\n 1,\n 2\n) %}`
48
+ // parse here but error upstream; the equivalent array forms
49
+ // (`{% t x=[1,] %}`) are upstream-valid anyway.
50
+ // - `{%- -%}` trim markers are stripped and ignored; `tag.pegjs` has no such
51
+ // syntax.
52
+ // - A self-close marker on a top-level value parses as a plain
53
+ // `variable`/`function`: `{% $foo/%}`, `{% @foo.bar/%}`, `{% $foo["a"]/%}`,
54
+ // `{% equals(1,1)/%}`. The shared pre-dispatch scan strips the marker before
55
+ // the first character is examined.
56
+ // - Close tags may carry a parsed attribute list, though `TagClose` upstream
57
+ // is just `'/' tag:TagName`.
58
+ // - Duplicate attributes are never flagged. `{% t a=1 a=2 %}`, `{% t #a #b %}`,
59
+ // `{% t #a id="b" %}`, and `{% t .a class="b" %}` are all upstream
60
+ // `duplicate-attribute` warnings; detecting them needs the schema-level
61
+ // `class`/`id` view with shortcuts folded in, which only a schema-aware rule
62
+ // has. Note two shortcuts naming the same class (`{% t .a .a %}`) is clean
63
+ // upstream too -- they merge into one `class` entry.
64
+ // - Keyword literals are the one SHAPE-only divergence: `null`/`true`/`false`
65
+ // match boundary-free upstream but as maximal identifiers here, so
66
+ // accept/reject agrees while the parse differs on keyword-prefixed names.
67
+ // Upstream reads `{% t nullable=true %}` as attribute `{able: true}` and
68
+ // `{% t truey=1 %}` as primary `true` plus `{y: 1}`; this keeps the whole
69
+ // identifier (`nullable`/`truey`). In value position it becomes a bareword.
70
+ function isSpace(ch) {
71
+ return ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r' || ch === '\f' || ch === '\v';
72
+ }
73
+ // Markdoc's own grammar whitespace class: exactly space/tab/newline, narrower
74
+ // than `isSpace` above. Used for the primary-to-first-attribute gap, where the
75
+ // grammar allows at most one whitespace character.
76
+ function isMarkdocSpace(ch) {
77
+ return ch === ' ' || ch === '\t' || ch === '\n';
78
+ }
79
+ /**
80
+ * Consumes ONE Markdoc whitespace unit starting at `pos`. Markdoc's grammar
81
+ * runs after the renderer has collapsed every line ending to a single '\n', so
82
+ * a '\r\n' pair or a lone '\r' each count as one unit here despite spanning two
83
+ * raw characters. Recheck deliberately does not normalize span text (see
84
+ * `core/line-endings.ts`), hence the collapse by hand.
85
+ *
86
+ * Returns `pos` unchanged if no unit starts there -- notably for '\f'/'\v',
87
+ * which are outside Markdoc's whitespace class and must not be consumed here.
88
+ */
89
+ function scanMarkdocSpaceUnit(text, pos, end) {
90
+ if (pos >= end)
91
+ return pos;
92
+ const ch = text[pos];
93
+ if (isMarkdocSpace(ch))
94
+ return pos + 1;
95
+ if (ch === '\r')
96
+ return pos + 1 < end && text[pos + 1] === '\n' ? pos + 2 : pos + 1;
97
+ return pos;
98
+ }
99
+ function isDigit(ch) {
100
+ return ch !== undefined && ch >= '0' && ch <= '9';
101
+ }
102
+ function isAlpha(ch) {
103
+ return ch !== undefined && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'));
104
+ }
105
+ // Markdoc's `Identifier` is a single uniform `[a-zA-Z0-9_-]+` class with no
106
+ // distinct "start" rule, so digit-leading (`1x`) and dash-leading (`-x`) names
107
+ // are legal for tags and attributes alike. Hence this accepts exactly the same
108
+ // characters as `isIdentChar`.
109
+ function isIdentStart(ch) {
110
+ return isAlpha(ch) || isDigit(ch) || ch === '_' || ch === '-';
111
+ }
112
+ function isIdentChar(ch) {
113
+ return isAlpha(ch) || isDigit(ch) || ch === '_' || ch === '-';
114
+ }
115
+ /** Scans `[A-Za-z0-9_-]+` starting at `pos`. Returns `pos` unchanged if no identifier starts there. */
116
+ function scanIdentifier(text, pos, end) {
117
+ let p = pos;
118
+ if (p < end && isIdentStart(text[p])) {
119
+ p++;
120
+ while (p < end && isIdentChar(text[p]))
121
+ p++;
122
+ }
123
+ return p;
124
+ }
125
+ /**
126
+ * Scans a bracketed run (`[...]`, `{...}`, or `(...)`) starting at `pos`,
127
+ * skipping quoted string contents so brackets inside strings
128
+ * (`{title: "a]b"}`) don't miscount. Only the outer pair's own characters need
129
+ * counting: a mismatched nested pair can't affect when the outer bracket
130
+ * closes, and nested same-type pairs still balance correctly.
131
+ *
132
+ * Returns the position just past the matching close bracket, or -1 if the text
133
+ * ends before the brackets balance.
134
+ */
135
+ function scanBalanced(text, pos, end, open, close) {
136
+ let depth = 0;
137
+ let p = pos;
138
+ while (p < end) {
139
+ const ch = text[p];
140
+ if (ch === '"' || ch === "'") {
141
+ const quote = ch;
142
+ p++;
143
+ while (p < end && text[p] !== quote) {
144
+ p += text[p] === '\\' && p + 1 < end ? 2 : 1;
145
+ }
146
+ if (p >= end)
147
+ return -1; // unterminated string inside the bracketed value
148
+ p++; // consume the closing quote
149
+ continue;
150
+ }
151
+ if (ch === open) {
152
+ depth++;
153
+ p++;
154
+ continue;
155
+ }
156
+ if (ch === close) {
157
+ depth--;
158
+ p++;
159
+ if (depth === 0)
160
+ return p;
161
+ continue;
162
+ }
163
+ p++;
164
+ }
165
+ return -1; // never balanced
166
+ }
167
+ /** Scans a double-quoted string starting at `text[pos] === '"'`, decoding `\"` and `\\` escapes. */
168
+ function scanString(text, pos, end) {
169
+ let p = pos + 1;
170
+ let decoded = '';
171
+ while (p < end) {
172
+ const ch = text[p];
173
+ if (ch === '"') {
174
+ return { kind: 'string', value: decoded, endPos: p + 1 };
175
+ }
176
+ if (ch === '\\' && p + 1 < end) {
177
+ const next = text[p + 1];
178
+ decoded += next === '"' || next === '\\' ? next : `\\${next}`;
179
+ p += 2;
180
+ continue;
181
+ }
182
+ decoded += ch;
183
+ p++;
184
+ }
185
+ return null; // unterminated string
186
+ }
187
+ /**
188
+ * Scans Markdoc's `ValueNumber`, `'-'? [0-9]+ ('.'[0-9]+)?`. Notably NOT a
189
+ * JSON/JavaScript number: the grammar has no exponent alternative, so the `e`
190
+ * in `2e3` is not part of the number and `{% img scale=2e3 %}` is an upstream
191
+ * parse error. Matching that greed exactly is what makes leftover text land in
192
+ * the next slot the way it does upstream.
193
+ *
194
+ * Caller has already confirmed a digit (or `-` + digit) at `pos`.
195
+ */
196
+ function scanNumber(text, pos, end) {
197
+ let p = pos;
198
+ if (text[p] === '-')
199
+ p++;
200
+ while (p < end && isDigit(text[p]))
201
+ p++;
202
+ if (text[p] === '.' && isDigit(text[p + 1])) {
203
+ p++;
204
+ while (p < end && isDigit(text[p]))
205
+ p++;
206
+ }
207
+ return { kind: 'number', value: Number(text.slice(pos, p)), endPos: p };
208
+ }
209
+ /**
210
+ * Scans zero or more Markdoc `VariableTail` segments starting at `pos`:
211
+ * `'.' Identifier` or `'[' (ValueNumber | ValueString) ']'`. Shared by a
212
+ * `$`/`@` variable's value position and its top-level position, because
213
+ * upstream reuses the one production for both.
214
+ *
215
+ * Neither alternative allows any whitespace -- not around the `.`, not inside
216
+ * the brackets -- and a bracket's contents must be exactly a number or a
217
+ * string, never a bare identifier or an expression. So `$foo[bar]`,
218
+ * `$foo[ 0 ]`, and `$foo[]` are upstream errors while `$foo[0]`, `$foo["bar"]`,
219
+ * and chains like `$foo.bar["x"].baz` are fine.
220
+ *
221
+ * A segment that fails to match consumes nothing, leaving `pos` at the start of
222
+ * the offending segment so the caller's trailing-content check rejects it. That
223
+ * is how a trailing dot (`$foo.`) or doubled dot (`$foo..bar`) is caught.
224
+ *
225
+ * Returns `pos` unchanged if no tail segment starts there.
226
+ */
227
+ function scanVariableTail(text, pos, end) {
228
+ let p = pos;
229
+ for (;;) {
230
+ if (text[p] === '.') {
231
+ const segEnd = scanIdentifier(text, p + 1, end);
232
+ if (segEnd === p + 1)
233
+ break;
234
+ p = segEnd;
235
+ continue;
236
+ }
237
+ if (text[p] === '[') {
238
+ const bracketValueStart = p + 1;
239
+ let valueEnd;
240
+ if (text[bracketValueStart] === '"') {
241
+ const str = scanString(text, bracketValueStart, end);
242
+ if (!str)
243
+ break; // unterminated string inside the brackets
244
+ valueEnd = str.endPos;
245
+ }
246
+ else if (isDigit(text[bracketValueStart]) ||
247
+ (text[bracketValueStart] === '-' && isDigit(text[bracketValueStart + 1]))) {
248
+ valueEnd = scanNumber(text, bracketValueStart, end).endPos;
249
+ }
250
+ else {
251
+ break; // bracket contents are neither a ValueNumber nor a ValueString
252
+ }
253
+ if (text[valueEnd] !== ']')
254
+ break;
255
+ p = valueEnd + 1;
256
+ continue;
257
+ }
258
+ break;
259
+ }
260
+ return p;
261
+ }
262
+ /**
263
+ * Scans one value at `pos`, recognizing exactly Markdoc's `Value` production
264
+ * and nothing more: quoted string, number, `true`/`false`/`null`, `$`/`@`
265
+ * variable[.path], `identifier(...)` function call, balanced `[...]` array, or
266
+ * balanced `{...}` object. A bare unquoted identifier (the `star` in
267
+ * `name=star`) is deliberately NOT recognized here, since `Value` has no such
268
+ * alternative upstream -- see `scanBareword`.
269
+ *
270
+ * Returns `null` if no `Value` starts at `pos`.
271
+ */
272
+ function scanValue(text, pos, end) {
273
+ const ch = text[pos];
274
+ if (ch === '"')
275
+ return scanString(text, pos, end);
276
+ // `$` and `@` are two prefixes of the same upstream `Variable` production, so
277
+ // a value in attribute or primary position accepts either (`{% t x=@foo /%}`).
278
+ if (ch === '$' || ch === '@') {
279
+ const identEnd = scanIdentifier(text, pos + 1, end);
280
+ if (identEnd === pos + 1)
281
+ return null; // '$'/'@' not followed by an identifier
282
+ const p = scanVariableTail(text, identEnd, end);
283
+ return { kind: 'variable', value: text.slice(pos, p), endPos: p };
284
+ }
285
+ if (isDigit(ch) || (ch === '-' && isDigit(text[pos + 1]))) {
286
+ return scanNumber(text, pos, end);
287
+ }
288
+ if (ch === '[') {
289
+ const closeEnd = scanBalanced(text, pos, end, '[', ']');
290
+ return closeEnd === -1
291
+ ? null
292
+ : { kind: 'array', value: text.slice(pos, closeEnd), endPos: closeEnd };
293
+ }
294
+ if (ch === '{') {
295
+ const closeEnd = scanBalanced(text, pos, end, '{', '}');
296
+ return closeEnd === -1
297
+ ? null
298
+ : { kind: 'object', value: text.slice(pos, closeEnd), endPos: closeEnd };
299
+ }
300
+ if (isIdentStart(ch)) {
301
+ const identEnd = scanIdentifier(text, pos, end);
302
+ const word = text.slice(pos, identEnd);
303
+ if (word === 'true')
304
+ return { kind: 'boolean', value: true, endPos: identEnd };
305
+ if (word === 'false')
306
+ return { kind: 'boolean', value: false, endPos: identEnd };
307
+ if (word === 'null')
308
+ return { kind: 'null', value: null, endPos: identEnd };
309
+ if (text[identEnd] === '(') {
310
+ const closeEnd = scanBalanced(text, identEnd, end, '(', ')');
311
+ return closeEnd === -1
312
+ ? null
313
+ : { kind: 'function', value: text.slice(pos, closeEnd), endPos: closeEnd };
314
+ }
315
+ }
316
+ return null;
317
+ }
318
+ /**
319
+ * A bare unquoted identifier in a value slot (`name=star`, `{% if maybe %}`).
320
+ * Markdoc rejects these outright; recognizing the shape anyway -- instead of
321
+ * collapsing the whole span to `malformed` -- is what lets a rule point at the
322
+ * offending value and say "quote it".
323
+ *
324
+ * The identifier scanned is the maximal `[A-Za-z0-9_-]+` run, so a
325
+ * keyword-prefixed word like `nullx` is one bareword here where upstream
326
+ * prefix-matches `null` and then fails on the leftover. Either way the span is
327
+ * one Markdoc rejects, and the bareword reading is the actionable one.
328
+ */
329
+ function scanBareword(text, pos, end) {
330
+ const identEnd = scanIdentifier(text, pos, end);
331
+ if (identEnd === pos)
332
+ return null;
333
+ return { kind: 'bareword', value: text.slice(pos, identEnd), endPos: identEnd };
334
+ }
335
+ /**
336
+ * Detects Markdoc's positional value slot right after the tag name
337
+ * (`{% if $flag %}`, `{% image "a.png" /%}`). Upstream attempts a plain `Value`
338
+ * there BEFORE the attribute list and with no lookahead for a following `=`:
339
+ * whatever `Value` matches, it takes, and the attribute list starts at the
340
+ * leftover text. That greed shows wherever a `Value` is a strict prefix of an
341
+ * identifier:
342
+ *
343
+ * `{% icon 1x="star" %}` -> primary number `1`, then attribute `x="star"`
344
+ * `{% t 123="x" %}` -> primary number `123`, then `="x"` is a parse error
345
+ * `{% t null=1 %}` -> primary `null`, then `=1` is a parse error
346
+ *
347
+ * so a digit run must be consumed as a number here, not as the head of a `1x=`
348
+ * attribute name. An identifier in a non-first slot has no `Value` attempt in
349
+ * front of it, which is why `1bar="b"` is a legal attribute in
350
+ * `{% x foo="a" 1bar="b" %}`.
351
+ *
352
+ * When `Value` matches nothing the slot holds a bare identifier, which Markdoc
353
+ * rejects either way, so we pick the reading a rule can act on: an identifier
354
+ * followed by `=` is reported as the first ATTRIBUTE's name -- including across
355
+ * whitespace, so `{% t a = 1 %}` is diagnosed as the `=`-spacing error it is
356
+ * rather than a bareword primary trailed by junk. Any other identifier becomes
357
+ * a bareword primary.
358
+ *
359
+ * Returns `null` if nothing recognizable starts at `pos`.
360
+ */
361
+ function scanPrimaryValue(text, pos, end) {
362
+ const value = scanValue(text, pos, end);
363
+ if (value)
364
+ return value;
365
+ const bareword = scanBareword(text, pos, end);
366
+ if (!bareword)
367
+ return null;
368
+ let p = bareword.endPos;
369
+ while (p < end && isSpace(text[p]))
370
+ p++;
371
+ if (p < end && text[p] === '=')
372
+ return null; // "name=..." starts attributes
373
+ return bareword;
374
+ }
375
+ const NO_SPACES_AROUND_EQUALS = "Markdoc allows no spaces around an attribute's '='";
376
+ /**
377
+ * Scans `identifier=value` pairs and (when `allowShortcuts`) `.class`/`#id`
378
+ * shortcuts, whitespace-separated, from `startPos` to `end`. Stops successfully
379
+ * at `end`, or reports the first syntax error it finds.
380
+ *
381
+ * Markdoc puts no whitespace rule on either side of an attribute's `=`, so
382
+ * `a = 1`, `a= 1`, and `a =1` are all parse errors and `a=1` is the only
383
+ * accepted spelling. Whitespace separates one attribute from the next, never a
384
+ * name from its own value.
385
+ *
386
+ * `leadingIsSpace` governs only the FIRST whitespace skip (the gap before the
387
+ * first item this call sees); later gaps always use the lax `isSpace`. The
388
+ * primary-value caller passes the strict `isMarkdocSpace` because it has
389
+ * already consumed the one grammar-legal whitespace unit into `startPos`, so
390
+ * this skip should find nothing and must not swallow a stray '\f'/'\v'.
391
+ *
392
+ * `allowShortcuts` is `false` for close tags and `true` for named
393
+ * tag-open/self-closing spans, where upstream puts shortcuts and named
394
+ * attributes in the same item slot. `sawItem` tracks whether this call has
395
+ * produced an item yet: the first item may follow zero separating whitespace
396
+ * (the caller has already arranged that before `startPos`), but a SHORTCUT
397
+ * after the first item requires at least one whitespace unit. When that
398
+ * separator is missing, the loop deliberately skips the shortcut branch and
399
+ * falls through to the attribute-name scan, which fails on the sigil and so
400
+ * reproduces upstream's rejection of `{% t .a.b %}`, `{% t .a#b %}`, and
401
+ * `{% t b=1.a %}` for free. The guard covers only shortcuts -- glued named
402
+ * attributes still both parse (see the header ledger).
403
+ */
404
+ function scanAttributes(text, startPos, end, leadingIsSpace = isSpace, allowShortcuts = false) {
405
+ const attributes = [];
406
+ const shortcuts = [];
407
+ let pos = startPos;
408
+ let isSpaceHere = leadingIsSpace;
409
+ let sawItem = false;
410
+ for (;;) {
411
+ const beforeSkip = pos;
412
+ while (pos < end && isSpaceHere(text[pos]))
413
+ pos++;
414
+ const skippedSeparator = pos > beforeSkip;
415
+ isSpaceHere = isSpace;
416
+ if (pos >= end)
417
+ return { pos, error: null, attributes, shortcuts };
418
+ const ch = text[pos];
419
+ if (allowShortcuts && (ch === '.' || ch === '#') && (!sawItem || skippedSeparator)) {
420
+ const sigilStart = pos;
421
+ const shortcutNameStart = pos + 1;
422
+ const shortcutNameEnd = scanIdentifier(text, shortcutNameStart, end);
423
+ if (shortcutNameEnd === shortcutNameStart) {
424
+ return {
425
+ pos,
426
+ error: `expected an identifier after "${ch}"`,
427
+ attributes,
428
+ shortcuts,
429
+ };
430
+ }
431
+ shortcuts.push({
432
+ kind: ch === '.' ? 'class' : 'id',
433
+ name: text.slice(shortcutNameStart, shortcutNameEnd),
434
+ start: sigilStart,
435
+ end: shortcutNameEnd,
436
+ });
437
+ pos = shortcutNameEnd;
438
+ sawItem = true;
439
+ continue;
440
+ }
441
+ const nameStart = pos;
442
+ const nameEnd = scanIdentifier(text, pos, end);
443
+ if (nameEnd === nameStart) {
444
+ return {
445
+ pos,
446
+ error: `expected an attribute name`,
447
+ attributes,
448
+ shortcuts,
449
+ };
450
+ }
451
+ const name = text.slice(nameStart, nameEnd);
452
+ pos = nameEnd;
453
+ if (pos >= end || text[pos] !== '=') {
454
+ return {
455
+ pos,
456
+ error: pos < end && isSpace(text[pos])
457
+ ? `expected '=' immediately after attribute name "${name}" (${NO_SPACES_AROUND_EQUALS})`
458
+ : `expected '=' after attribute name "${name}"`,
459
+ attributes,
460
+ shortcuts,
461
+ };
462
+ }
463
+ pos++; // consume '='
464
+ if (pos >= end) {
465
+ return {
466
+ pos,
467
+ error: `expected a value after '=' for attribute "${name}"`,
468
+ attributes,
469
+ shortcuts,
470
+ };
471
+ }
472
+ if (isSpace(text[pos])) {
473
+ return {
474
+ pos,
475
+ error: `expected a value immediately after '=' for attribute "${name}" (${NO_SPACES_AROUND_EQUALS})`,
476
+ attributes,
477
+ shortcuts,
478
+ };
479
+ }
480
+ const valueStart = pos;
481
+ const scanned = scanValue(text, pos, end) ?? scanBareword(text, pos, end);
482
+ if (!scanned) {
483
+ return {
484
+ pos,
485
+ error: `unrecognized attribute value`,
486
+ attributes,
487
+ shortcuts,
488
+ };
489
+ }
490
+ attributes.push({
491
+ name,
492
+ valueKind: scanned.kind,
493
+ value: scanned.value,
494
+ nameStart,
495
+ nameEnd,
496
+ valueStart,
497
+ valueEnd: scanned.endPos,
498
+ });
499
+ pos = scanned.endPos;
500
+ sawItem = true;
501
+ }
502
+ }
503
+ function malformed(reason, reasonOffset) {
504
+ return {
505
+ kind: 'malformed',
506
+ name: null,
507
+ attributes: [],
508
+ nameStart: 0,
509
+ nameEnd: 0,
510
+ reason,
511
+ ...(reasonOffset === undefined ? {} : { reasonOffset }),
512
+ };
513
+ }
514
+ /**
515
+ * The one shape every annotation classifies to, whichever entry point
516
+ * recognized it: no tag name and a deliberately unparsed body.
517
+ */
518
+ function annotation() {
519
+ return { kind: 'annotation', name: null, attributes: [], nameStart: 0, nameEnd: 0 };
520
+ }
521
+ /**
522
+ * Parses one Markdoc `{% ... %}` span's text into its kind, name,
523
+ * attributes, and offsets. `spanText` is expected to be exactly the span
524
+ * (e.g. as recognized by the tokenizer) -- offsets in the result are
525
+ * 0-based indexes into this same string. Never throws; unparseable input
526
+ * comes back as `{ kind: 'malformed', reason }`.
527
+ */
528
+ export function parseMarkdocSpan(spanText) {
529
+ if (!spanText.startsWith('{%')) {
530
+ return malformed("span text does not start with the opening delimiter '{%'");
531
+ }
532
+ if (!spanText.endsWith('%}')) {
533
+ return malformed("span text has no closing '%}' delimiter");
534
+ }
535
+ let start = 2;
536
+ let end = spanText.length - 2;
537
+ // `{%-`/`-%}` whitespace-trim markers. The `end - 1 >= start` guard
538
+ // stops a single `-` sitting exactly between the delimiters (e.g. the
539
+ // degenerate `{%-%}`) from being consumed by both checks.
540
+ if (spanText[start] === '-')
541
+ start++;
542
+ if (end - 1 >= start && spanText[end - 1] === '-')
543
+ end--;
544
+ if (start > end) {
545
+ return malformed('span delimiters leave no room for a body');
546
+ }
547
+ // A trailing `/` (before the close delimiter, whitespace-trim markers
548
+ // already stripped) marks a self-closing tag, e.g. `{% partial ... /%}`.
549
+ let bodyEnd = end;
550
+ let selfClosing = false;
551
+ while (bodyEnd > start && isSpace(spanText[bodyEnd - 1]))
552
+ bodyEnd--;
553
+ if (bodyEnd > start && spanText[bodyEnd - 1] === '/') {
554
+ selfClosing = true;
555
+ bodyEnd--;
556
+ while (bodyEnd > start && isSpace(spanText[bodyEnd - 1]))
557
+ bodyEnd--;
558
+ }
559
+ let pos = start;
560
+ while (pos < bodyEnd && isSpace(spanText[pos]))
561
+ pos++;
562
+ if (pos >= bodyEnd) {
563
+ return malformed('span body is empty or contains only whitespace');
564
+ }
565
+ const first = spanText[pos];
566
+ // Close tag: `{% /name %}`.
567
+ if (first === '/') {
568
+ const nameStart = pos + 1;
569
+ const nameEnd = scanIdentifier(spanText, nameStart, bodyEnd);
570
+ if (nameEnd === nameStart) {
571
+ return malformed('close tag is missing a tag name after "/"', nameStart);
572
+ }
573
+ const scanned = scanAttributes(spanText, nameEnd, bodyEnd);
574
+ if (scanned.error)
575
+ return malformed(scanned.error, scanned.pos);
576
+ if (scanned.pos !== bodyEnd) {
577
+ return malformed('unexpected trailing content in close tag', scanned.pos);
578
+ }
579
+ return {
580
+ kind: 'tag-close',
581
+ name: spanText.slice(nameStart, nameEnd),
582
+ attributes: scanned.attributes,
583
+ nameStart,
584
+ nameEnd,
585
+ };
586
+ }
587
+ // Annotation, sigil-first form: `{% #id .class ... %}`. The name is null (an
588
+ // annotation has no tag name) and the body is left unparsed. The
589
+ // attribute-first form of the same production sits further down, after the
590
+ // variable/function branches, because upstream tries top-level values first.
591
+ if (first === '#' || first === '.') {
592
+ return annotation();
593
+ }
594
+ // Variable interpolation: `{% $name %}` / `{% @name %}`, plus any dotted or
595
+ // bracket-indexed tail (`{% $frontmatter.title %}`, `{% @foo["bar"].baz %}`).
596
+ // `$` and `@` are two prefixes of the same upstream production, not two
597
+ // shapes, so both classify as `'variable'` with identical handling. The tail
598
+ // is not broken out into structured internals -- that is an LSP-phase concern.
599
+ if (first === '$' || first === '@') {
600
+ const identStart = pos + 1;
601
+ const identEnd = scanIdentifier(spanText, identStart, bodyEnd);
602
+ if (identEnd === identStart) {
603
+ return malformed(`variable interpolation is missing a name after "${first}"`, identStart);
604
+ }
605
+ let p = scanVariableTail(spanText, identEnd, bodyEnd);
606
+ while (p < bodyEnd && isSpace(spanText[p]))
607
+ p++;
608
+ if (p !== bodyEnd) {
609
+ return malformed('unexpected trailing content after variable interpolation', p);
610
+ }
611
+ return { kind: 'variable', name: null, attributes: [], nameStart: 0, nameEnd: 0 };
612
+ }
613
+ // Bare top-level function call: `{% equals(1,1) %}`, the other half of the
614
+ // same upstream production as the variable branch above. The `(` must be
615
+ // glued to the identifier: `{% fn (1) %}` is an upstream error, and here it
616
+ // falls through to the tag-name branch below, which fails on the stray `(`
617
+ // naturally. Once the glued shape IS seen, upstream commits to a function
618
+ // with no fallback to a tag-name reading (`{% fn(1) junk %}` and
619
+ // `{% fn(1)/%}` are both errors there), so this must short-circuit ahead of
620
+ // that branch. Interiors stay opaque, matching function values elsewhere.
621
+ if (isIdentStart(first)) {
622
+ const funcNameEnd = scanIdentifier(spanText, pos, bodyEnd);
623
+ if (spanText[funcNameEnd] === '(') {
624
+ const closeEnd = scanBalanced(spanText, funcNameEnd, bodyEnd, '(', ')');
625
+ if (closeEnd === -1) {
626
+ return malformed('unterminated function call parentheses', funcNameEnd);
627
+ }
628
+ let p = closeEnd;
629
+ while (p < bodyEnd && isSpace(spanText[p]))
630
+ p++;
631
+ if (p !== bodyEnd) {
632
+ return malformed('unexpected trailing content after function interpolation', p);
633
+ }
634
+ return { kind: 'function', name: null, attributes: [], nameStart: 0, nameEnd: 0 };
635
+ }
636
+ }
637
+ // Annotation, attribute-first form: `{% width="30%" %}`,
638
+ // `{% colspan=2 align="center" %}`, `{% class="x" #id .cls %}`. Upstream
639
+ // tries its annotation production -- a bare attribute list with no leading
640
+ // tag name -- BEFORE tag-open, so a body that is nothing but attributes and
641
+ // shortcuts is a valid annotation there, never a tag whose name happens to be
642
+ // followed by `=`. This branch's position between the variable/function
643
+ // branches and the tag-name branch reproduces that ordering.
644
+ //
645
+ // Because an annotation body is unparsed by design, nothing downstream would
646
+ // report a problem inside one. Three conditions therefore narrow the branch
647
+ // to genuinely upstream-valid bodies so it can never launder a real error
648
+ // into a silent `annotation`:
649
+ // - The whole body must be consumed with no scan error. A trailing bareword
650
+ // (`{% a=1 b %}`), a stray value (`{% a=1 "str" %}`), or a spaced `=`
651
+ // (`{% width = "30%" %}`) falls through to the tag-name reading and lands
652
+ // `malformed`, as upstream also rejects all three.
653
+ // - No bareword values. An attribute's value must be a real `Value` upstream,
654
+ // so `{% a=b %}` is an error there. The bareword leniency exists to give a
655
+ // better diagnostic inside an otherwise well-formed named tag; here it
656
+ // would only hide an error, so the probe rejects it.
657
+ // - Not self-closing. Only tag-open has a self-close alternative upstream, so
658
+ // `{% a="x" /%}` must stay `malformed`. The sigil-first branch above has no
659
+ // such gate -- `{% .foo /%}` still classifies as an annotation -- which is
660
+ // the pre-existing leniency of its unparsed body, left as-is.
661
+ //
662
+ // `isIdentStart(first)` gates the branch because the sigils are already
663
+ // claimed above, which also makes the first item scanned here necessarily an
664
+ // attribute.
665
+ if (!selfClosing && isIdentStart(first)) {
666
+ const probe = scanAttributes(spanText, pos, bodyEnd, isSpace, true);
667
+ if (!probe.error &&
668
+ probe.pos === bodyEnd &&
669
+ !probe.attributes.some((attribute) => attribute.valueKind === 'bareword')) {
670
+ return annotation();
671
+ }
672
+ }
673
+ // Otherwise it must be an open/self-closing tag: `{% name attr=val ... %}`,
674
+ // optionally with a positional primary value right after the name:
675
+ // `{% name primaryValue attr=val ... %}` (e.g. `{% if $flag %}`).
676
+ if (isIdentStart(first)) {
677
+ const nameStart = pos;
678
+ const nameEnd = scanIdentifier(spanText, nameStart, bodyEnd);
679
+ let afterPos = nameEnd;
680
+ while (afterPos < bodyEnd && isSpace(spanText[afterPos]))
681
+ afterPos++;
682
+ let primary;
683
+ let attrStart = nameEnd;
684
+ const primaryScan = scanPrimaryValue(spanText, afterPos, bodyEnd);
685
+ if (primaryScan) {
686
+ primary = {
687
+ valueKind: primaryScan.kind,
688
+ value: primaryScan.value,
689
+ valueStart: afterPos,
690
+ valueEnd: primaryScan.endPos,
691
+ };
692
+ // Exactly one optional whitespace character may separate the primary from
693
+ // the attribute list, where every other gap in the grammar allows any
694
+ // number. So `{% t 1 x="y" %}` parses but `{% t 1 x="y" %}` is an
695
+ // upstream error, as is a primary followed by a newline plus indentation.
696
+ // (`bodyEnd` is already right-trimmed, so leftover whitespace here is
697
+ // always followed by more content, never by the delimiter.)
698
+ attrStart = primaryScan.endPos;
699
+ const afterOneUnit = scanMarkdocSpaceUnit(spanText, attrStart, bodyEnd);
700
+ if (afterOneUnit > attrStart) {
701
+ attrStart = afterOneUnit;
702
+ if (scanMarkdocSpaceUnit(spanText, attrStart, bodyEnd) > attrStart) {
703
+ return malformed("only one whitespace character may separate a tag's primary value from its first attribute", attrStart);
704
+ }
705
+ }
706
+ }
707
+ // The strict `isMarkdocSpace` (not the lax default) stops a stray '\f'/'\v'
708
+ // right after the primary from being skipped as if it were legal
709
+ // whitespace: `attrStart` has already consumed the one grammar-legal unit,
710
+ // so such a character should fail the attribute-name scan instead.
711
+ const scanned = scanAttributes(spanText, attrStart, bodyEnd, primary ? isMarkdocSpace : isSpace, true);
712
+ if (scanned.error)
713
+ return malformed(scanned.error, scanned.pos);
714
+ if (scanned.pos !== bodyEnd) {
715
+ return malformed('unexpected trailing content in tag', scanned.pos);
716
+ }
717
+ return {
718
+ kind: selfClosing ? 'tag-self-closing' : 'tag-open',
719
+ name: spanText.slice(nameStart, nameEnd),
720
+ attributes: scanned.attributes,
721
+ nameStart,
722
+ nameEnd,
723
+ ...(primary ? { primary } : {}),
724
+ ...(scanned.shortcuts.length ? { shortcuts: scanned.shortcuts } : {}),
725
+ };
726
+ }
727
+ return malformed(`unexpected character "${first}"`, pos);
728
+ }
729
+ //# sourceMappingURL=span.js.map