11// @ts -check
22
3- import { addError , addErrorContext , addErrorDetailIf } from "../helpers/helpers.cjs" ;
3+ import { addError , addErrorDetailIf } from "../helpers/helpers.cjs" ;
44import { getHeadingLevel , getHeadingText } from "../helpers/micromark-helpers.cjs" ;
55import { filterByTypesCached } from "./cache.mjs" ;
66
77export const headingsItemsRe = / ^ ( [ * + ? ] | \^ + | [ # % ] { 1 , 6 } \s + \S .* ) $ / u;
88const headingPartsRe = / ^ ( [ # % ] { 1 , 6 } ) \s + ( \S .* ) $ / u;
9+ const loopHeadingRe = / ^ \^ + $ / u;
10+ const validExtraHeadingsRe = / ^ (?: \* | \^ + ) $ / u;
911
1012const headingIsExpected = ( /** @type {string } */ actual , /** @type {string } */ expected , /** @type {boolean } */ matchCase ) => {
1113 if ( expected . startsWith ( "%" ) ) {
@@ -43,26 +45,31 @@ export default {
4345 let loopsRemaining = 1000 ;
4446 let matchAny = false ;
4547 let hasError = false ;
46- let anyHeadings = false ;
47- const getExpected = ( ) => requiredHeadings [ i ++ ] || "[No heading]" ;
48+ const getExpected = ( /** @type {boolean } */ skipLoopHeadings ) => {
49+ let expected = null ;
50+ do {
51+ expected = requiredHeadings [ i ++ ] || "[No heading]" ;
52+ // eslint-disable-next-line no-unmodified-loop-condition
53+ } while ( skipLoopHeadings && loopHeadingRe . test ( expected ) ) ;
54+ return expected ;
55+ } ;
4856 for ( const heading of filterByTypesCached ( [ "atxHeading" , "setextHeading" ] ) ) {
4957 if ( ! hasError ) {
5058 const headingText = getHeadingText ( heading ) ;
5159 const headingLevel = getHeadingLevel ( heading ) ;
52- anyHeadings = true ;
5360 const actual = `${ "" . padEnd ( headingLevel , "#" ) } ${ headingText } ` ;
54- let expected = getExpected ( ) ;
61+ let expected = getExpected ( false ) ;
5562
5663 if ( expected . startsWith ( "^" ) ) {
5764 const save = i ;
58- const nextExpected = getExpected ( ) ;
65+ const nextExpected = getExpected ( true ) ;
5966 if ( headingIsExpected ( actual , nextExpected , matchCase ) ) {
6067 expected = nextExpected ;
6168 } else {
6269 for ( let j = save - 1 ; j -- ; j >= 0 ) {
6370 if ( requiredHeadings [ j ] === expected ) {
6471 i = j + 1 ;
65- expected = getExpected ( ) ;
72+ expected = getExpected ( false ) ;
6673 loopsRemaining -- ;
6774 break ;
6875 }
@@ -78,7 +85,7 @@ export default {
7885 addError ( onError , heading . startLine , "Too many loops" ) ;
7986 hasError = true ;
8087 } else if ( expected === "*" ) {
81- const nextExpected = getExpected ( ) ;
88+ const nextExpected = getExpected ( false ) ;
8289 if ( ! headingIsExpected ( actual , nextExpected , matchCase ) ) {
8390 matchAny = true ;
8491 i -- ;
@@ -102,18 +109,16 @@ export default {
102109 }
103110 }
104111 }
105- const extraHeadings = requiredHeadings . length - i ;
112+ const extraHeadings = requiredHeadings . slice ( i ) ;
106113 if (
107114 ! hasError &&
108- ( ( extraHeadings > 1 ) ||
109- ( ( extraHeadings === 1 ) &&
110- ( ( requiredHeadings [ i ] !== "*" ) && ( requiredHeadings [ i ] !== "^" ) ) ) ) &&
111- ( anyHeadings || ! requiredHeadings . every ( ( heading ) => heading === "*" ) )
115+ ! extraHeadings . every ( ( heading ) => validExtraHeadingsRe . test ( heading ) )
112116 ) {
113- addErrorContext (
117+ addErrorDetailIf (
114118 onError ,
115119 params . lines . length ,
116- requiredHeadings [ i ]
120+ requiredHeadings [ i ] ,
121+ "[End of file]"
117122 ) ;
118123 }
119124 }
0 commit comments