11import { Tokenizer , TokenizerMode } from '../tokenizer/index.js' ;
22import { OpenElementStack } from './open-element-stack.js' ;
33import { FormattingElementList , ElementEntry , EntryType } from './formatting-element-list.js' ;
4- import { LocationInfoParserMixin } from '../extensions/location-info/parser-mixin.js' ;
5- import { Mixin } from '../utils/mixin.js' ;
64import * as defaultTreeAdapter from '../tree-adapters/default.js' ;
75import * as doctype from '../common/doctype.js' ;
86import * as foreignContent from '../common/foreign-content.js' ;
@@ -26,6 +24,7 @@ import {
2624 TagToken ,
2725 DoctypeToken ,
2826 LocationWithAttributes ,
27+ ElementLocation ,
2928} from '../common/token.js' ;
3029
3130//Misc constants
@@ -141,6 +140,7 @@ export class Parser<T extends TreeAdapterTypeMap> {
141140 treeAdapter : TreeAdapter < T > ;
142141 pendingScript : null | T [ 'element' ] ;
143142 private onParseError : ParserErrorHandler | null ;
143+ private currentToken : Token | null = null ;
144144
145145 constructor ( options ?: ParserOptions < T > ) {
146146 this . options = {
@@ -159,10 +159,6 @@ export class Parser<T extends TreeAdapterTypeMap> {
159159 if ( this . options . onParseError ) {
160160 this . options . sourceCodeLocationInfo = true ;
161161 }
162-
163- if ( this . options . sourceCodeLocationInfo ) {
164- Mixin . install ( this , LocationInfoParserMixin as any ) ;
165- }
166162 }
167163
168164 // API
@@ -250,8 +246,14 @@ export class Parser<T extends TreeAdapterTypeMap> {
250246
251247 this . headElement = null ;
252248 this . formElement = null ;
249+ this . currentToken = null ;
253250
254251 this . openElements = new OpenElementStack ( this . document , this . treeAdapter ) ;
252+
253+ if ( this . options . sourceCodeLocationInfo ) {
254+ this . openElements . onItemPop = ( element ) => this . _setEndLocation ( element , this . currentToken ! ) ;
255+ }
256+
255257 this . activeFormattingElements = new FormattingElementList ( this . treeAdapter ) ;
256258
257259 this . tmplInsertionModeStack = [ ] ;
@@ -293,10 +295,15 @@ export class Parser<T extends TreeAdapterTypeMap> {
293295 break ;
294296 }
295297
298+ this . currentToken = token ;
299+
296300 if ( this . skipNextNewLine ) {
297301 this . skipNextNewLine = false ;
298302
299- if ( token . type === TokenType . WHITESPACE_CHARACTER && token . chars [ 0 ] === '\n' ) {
303+ if (
304+ token . type === TokenType . WHITESPACE_CHARACTER &&
305+ token . chars . charCodeAt ( 0 ) === unicode . CODE_POINTS . LINE_FEED
306+ ) {
300307 if ( token . chars . length === 1 ) {
301308 continue ;
302309 }
@@ -311,6 +318,14 @@ export class Parser<T extends TreeAdapterTypeMap> {
311318 break ;
312319 }
313320 }
321+
322+ if ( this . options . sourceCodeLocationInfo ) {
323+ // NOTE: generate location info for elements
324+ // that remains on open element stack
325+ for ( let i = this . openElements . stackTop ; i >= 0 ; i -- ) {
326+ this . _setEndLocation ( this . openElements . items [ i ] , this . currentToken ! ) ;
327+ }
328+ }
314329 }
315330
316331 runParsingLoopForCurrentChunk (
@@ -335,7 +350,7 @@ export class Parser<T extends TreeAdapterTypeMap> {
335350 }
336351
337352 //Text parsing
338- _setupTokenizerCDATAMode ( ) {
353+ private _setupTokenizerCDATAMode ( ) {
339354 const current = this . _getAdjustedCurrentElement ( ) ;
340355
341356 this . tokenizer . allowCDATA =
@@ -523,6 +538,32 @@ export class Parser<T extends TreeAdapterTypeMap> {
523538 }
524539 }
525540
541+ private _setEndLocation ( element : T [ 'element' ] , closingToken : Token ) {
542+ const loc = this . treeAdapter . getNodeSourceCodeLocation ( element ) ;
543+
544+ if ( loc && closingToken . location ) {
545+ const ctLoc = closingToken . location ;
546+ const tn = this . treeAdapter . getTagName ( element ) ;
547+
548+ // NOTE: For cases like <p> <p> </p> - First 'p' closes without a closing
549+ // tag and for cases like <td> <p> </td> - 'p' closes without a closing tag.
550+ const isClosingEndTag = closingToken . type === TokenType . END_TAG && tn === closingToken . tagName ;
551+ const endLoc : Partial < ElementLocation > = { } ;
552+ if ( isClosingEndTag ) {
553+ endLoc . endTag = { ...ctLoc } ;
554+ endLoc . endLine = ctLoc . endLine ;
555+ endLoc . endCol = ctLoc . endCol ;
556+ endLoc . endOffset = ctLoc . endOffset ;
557+ } else {
558+ endLoc . endLine = ctLoc . startLine ;
559+ endLoc . endCol = ctLoc . startCol ;
560+ endLoc . endOffset = ctLoc . startOffset ;
561+ }
562+
563+ this . treeAdapter . updateNodeSourceCodeLocation ( element , endLoc ) ;
564+ }
565+ }
566+
526567 //Token processing
527568 _shouldProcessTokenInForeignContent ( token : Token ) {
528569 const current = this . _getAdjustedCurrentElement ( ) ;
@@ -613,6 +654,23 @@ export class Parser<T extends TreeAdapterTypeMap> {
613654 } else if ( this . insertionMode === InsertionMode . AFTER_AFTER_FRAMESET ) {
614655 modeAfterAfterFrameset ( this , token ) ;
615656 }
657+
658+ //NOTE: <body> and <html> are never popped from the stack, so we need to updated
659+ //their end location explicitly.
660+ if (
661+ this . options . sourceCodeLocationInfo &&
662+ token . type === TokenType . END_TAG &&
663+ ( token . tagName === $ . HTML || ( token . tagName === $ . BODY && this . openElements . hasInScope ( $ . BODY ) ) )
664+ ) {
665+ for ( let i = this . openElements . stackTop ; i >= 0 ; i -- ) {
666+ const element = this . openElements . items [ i ] ;
667+
668+ if ( this . treeAdapter . getTagName ( element ) === token . tagName ) {
669+ this . _setEndLocation ( element , token ) ;
670+ break ;
671+ }
672+ }
673+ }
616674 }
617675
618676 _processTokenInForeignContent ( token : Token ) {
0 commit comments