147 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| const Mixin = require('../../utils/mixin');
 | |
| const Tokenizer = require('../../tokenizer');
 | |
| const PositionTrackingPreprocessorMixin = require('../position-tracking/preprocessor-mixin');
 | |
| 
 | |
| class LocationInfoTokenizerMixin extends Mixin {
 | |
|     constructor(tokenizer) {
 | |
|         super(tokenizer);
 | |
| 
 | |
|         this.tokenizer = tokenizer;
 | |
|         this.posTracker = Mixin.install(tokenizer.preprocessor, PositionTrackingPreprocessorMixin);
 | |
|         this.currentAttrLocation = null;
 | |
|         this.ctLoc = null;
 | |
|     }
 | |
| 
 | |
|     _getCurrentLocation() {
 | |
|         return {
 | |
|             startLine: this.posTracker.line,
 | |
|             startCol: this.posTracker.col,
 | |
|             startOffset: this.posTracker.offset,
 | |
|             endLine: -1,
 | |
|             endCol: -1,
 | |
|             endOffset: -1
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     _attachCurrentAttrLocationInfo() {
 | |
|         this.currentAttrLocation.endLine = this.posTracker.line;
 | |
|         this.currentAttrLocation.endCol = this.posTracker.col;
 | |
|         this.currentAttrLocation.endOffset = this.posTracker.offset;
 | |
| 
 | |
|         const currentToken = this.tokenizer.currentToken;
 | |
|         const currentAttr = this.tokenizer.currentAttr;
 | |
| 
 | |
|         if (!currentToken.location.attrs) {
 | |
|             currentToken.location.attrs = Object.create(null);
 | |
|         }
 | |
| 
 | |
|         currentToken.location.attrs[currentAttr.name] = this.currentAttrLocation;
 | |
|     }
 | |
| 
 | |
|     _getOverriddenMethods(mxn, orig) {
 | |
|         const methods = {
 | |
|             _createStartTagToken() {
 | |
|                 orig._createStartTagToken.call(this);
 | |
|                 this.currentToken.location = mxn.ctLoc;
 | |
|             },
 | |
| 
 | |
|             _createEndTagToken() {
 | |
|                 orig._createEndTagToken.call(this);
 | |
|                 this.currentToken.location = mxn.ctLoc;
 | |
|             },
 | |
| 
 | |
|             _createCommentToken() {
 | |
|                 orig._createCommentToken.call(this);
 | |
|                 this.currentToken.location = mxn.ctLoc;
 | |
|             },
 | |
| 
 | |
|             _createDoctypeToken(initialName) {
 | |
|                 orig._createDoctypeToken.call(this, initialName);
 | |
|                 this.currentToken.location = mxn.ctLoc;
 | |
|             },
 | |
| 
 | |
|             _createCharacterToken(type, ch) {
 | |
|                 orig._createCharacterToken.call(this, type, ch);
 | |
|                 this.currentCharacterToken.location = mxn.ctLoc;
 | |
|             },
 | |
| 
 | |
|             _createEOFToken() {
 | |
|                 orig._createEOFToken.call(this);
 | |
|                 this.currentToken.location = mxn._getCurrentLocation();
 | |
|             },
 | |
| 
 | |
|             _createAttr(attrNameFirstCh) {
 | |
|                 orig._createAttr.call(this, attrNameFirstCh);
 | |
|                 mxn.currentAttrLocation = mxn._getCurrentLocation();
 | |
|             },
 | |
| 
 | |
|             _leaveAttrName(toState) {
 | |
|                 orig._leaveAttrName.call(this, toState);
 | |
|                 mxn._attachCurrentAttrLocationInfo();
 | |
|             },
 | |
| 
 | |
|             _leaveAttrValue(toState) {
 | |
|                 orig._leaveAttrValue.call(this, toState);
 | |
|                 mxn._attachCurrentAttrLocationInfo();
 | |
|             },
 | |
| 
 | |
|             _emitCurrentToken() {
 | |
|                 const ctLoc = this.currentToken.location;
 | |
| 
 | |
|                 //NOTE: if we have pending character token make it's end location equal to the
 | |
|                 //current token's start location.
 | |
|                 if (this.currentCharacterToken) {
 | |
|                     this.currentCharacterToken.location.endLine = ctLoc.startLine;
 | |
|                     this.currentCharacterToken.location.endCol = ctLoc.startCol;
 | |
|                     this.currentCharacterToken.location.endOffset = ctLoc.startOffset;
 | |
|                 }
 | |
| 
 | |
|                 if (this.currentToken.type === Tokenizer.EOF_TOKEN) {
 | |
|                     ctLoc.endLine = ctLoc.startLine;
 | |
|                     ctLoc.endCol = ctLoc.startCol;
 | |
|                     ctLoc.endOffset = ctLoc.startOffset;
 | |
|                 } else {
 | |
|                     ctLoc.endLine = mxn.posTracker.line;
 | |
|                     ctLoc.endCol = mxn.posTracker.col + 1;
 | |
|                     ctLoc.endOffset = mxn.posTracker.offset + 1;
 | |
|                 }
 | |
| 
 | |
|                 orig._emitCurrentToken.call(this);
 | |
|             },
 | |
| 
 | |
|             _emitCurrentCharacterToken() {
 | |
|                 const ctLoc = this.currentCharacterToken && this.currentCharacterToken.location;
 | |
| 
 | |
|                 //NOTE: if we have character token and it's location wasn't set in the _emitCurrentToken(),
 | |
|                 //then set it's location at the current preprocessor position.
 | |
|                 //We don't need to increment preprocessor position, since character token
 | |
|                 //emission is always forced by the start of the next character token here.
 | |
|                 //So, we already have advanced position.
 | |
|                 if (ctLoc && ctLoc.endOffset === -1) {
 | |
|                     ctLoc.endLine = mxn.posTracker.line;
 | |
|                     ctLoc.endCol = mxn.posTracker.col;
 | |
|                     ctLoc.endOffset = mxn.posTracker.offset;
 | |
|                 }
 | |
| 
 | |
|                 orig._emitCurrentCharacterToken.call(this);
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         //NOTE: patch initial states for each mode to obtain token start position
 | |
|         Object.keys(Tokenizer.MODE).forEach(modeName => {
 | |
|             const state = Tokenizer.MODE[modeName];
 | |
| 
 | |
|             methods[state] = function(cp) {
 | |
|                 mxn.ctLoc = mxn._getCurrentLocation();
 | |
|                 orig[state].call(this, cp);
 | |
|             };
 | |
|         });
 | |
| 
 | |
|         return methods;
 | |
|     }
 | |
| }
 | |
| 
 | |
| module.exports = LocationInfoTokenizerMixin;
 |