1'use strict'; 2 3const Mixin = require('../../utils/mixin'); 4const Tokenizer = require('../../tokenizer'); 5const PositionTrackingPreprocessorMixin = require('../position-tracking/preprocessor-mixin'); 6 7class LocationInfoTokenizerMixin extends Mixin { 8 constructor(tokenizer) { 9 super(tokenizer); 10 11 this.tokenizer = tokenizer; 12 this.posTracker = Mixin.install(tokenizer.preprocessor, PositionTrackingPreprocessorMixin); 13 this.currentAttrLocation = null; 14 this.ctLoc = null; 15 } 16 17 _getCurrentLocation() { 18 return { 19 startLine: this.posTracker.line, 20 startCol: this.posTracker.col, 21 startOffset: this.posTracker.offset, 22 endLine: -1, 23 endCol: -1, 24 endOffset: -1 25 }; 26 } 27 28 _attachCurrentAttrLocationInfo() { 29 this.currentAttrLocation.endLine = this.posTracker.line; 30 this.currentAttrLocation.endCol = this.posTracker.col; 31 this.currentAttrLocation.endOffset = this.posTracker.offset; 32 33 const currentToken = this.tokenizer.currentToken; 34 const currentAttr = this.tokenizer.currentAttr; 35 36 if (!currentToken.location.attrs) { 37 currentToken.location.attrs = Object.create(null); 38 } 39 40 currentToken.location.attrs[currentAttr.name] = this.currentAttrLocation; 41 } 42 43 _getOverriddenMethods(mxn, orig) { 44 const methods = { 45 _createStartTagToken() { 46 orig._createStartTagToken.call(this); 47 this.currentToken.location = mxn.ctLoc; 48 }, 49 50 _createEndTagToken() { 51 orig._createEndTagToken.call(this); 52 this.currentToken.location = mxn.ctLoc; 53 }, 54 55 _createCommentToken() { 56 orig._createCommentToken.call(this); 57 this.currentToken.location = mxn.ctLoc; 58 }, 59 60 _createDoctypeToken(initialName) { 61 orig._createDoctypeToken.call(this, initialName); 62 this.currentToken.location = mxn.ctLoc; 63 }, 64 65 _createCharacterToken(type, ch) { 66 orig._createCharacterToken.call(this, type, ch); 67 this.currentCharacterToken.location = mxn.ctLoc; 68 }, 69 70 _createEOFToken() { 71 orig._createEOFToken.call(this); 72 this.currentToken.location = mxn._getCurrentLocation(); 73 }, 74 75 _createAttr(attrNameFirstCh) { 76 orig._createAttr.call(this, attrNameFirstCh); 77 mxn.currentAttrLocation = mxn._getCurrentLocation(); 78 }, 79 80 _leaveAttrName(toState) { 81 orig._leaveAttrName.call(this, toState); 82 mxn._attachCurrentAttrLocationInfo(); 83 }, 84 85 _leaveAttrValue(toState) { 86 orig._leaveAttrValue.call(this, toState); 87 mxn._attachCurrentAttrLocationInfo(); 88 }, 89 90 _emitCurrentToken() { 91 const ctLoc = this.currentToken.location; 92 93 //NOTE: if we have pending character token make it's end location equal to the 94 //current token's start location. 95 if (this.currentCharacterToken) { 96 this.currentCharacterToken.location.endLine = ctLoc.startLine; 97 this.currentCharacterToken.location.endCol = ctLoc.startCol; 98 this.currentCharacterToken.location.endOffset = ctLoc.startOffset; 99 } 100 101 if (this.currentToken.type === Tokenizer.EOF_TOKEN) { 102 ctLoc.endLine = ctLoc.startLine; 103 ctLoc.endCol = ctLoc.startCol; 104 ctLoc.endOffset = ctLoc.startOffset; 105 } else { 106 ctLoc.endLine = mxn.posTracker.line; 107 ctLoc.endCol = mxn.posTracker.col + 1; 108 ctLoc.endOffset = mxn.posTracker.offset + 1; 109 } 110 111 orig._emitCurrentToken.call(this); 112 }, 113 114 _emitCurrentCharacterToken() { 115 const ctLoc = this.currentCharacterToken && this.currentCharacterToken.location; 116 117 //NOTE: if we have character token and it's location wasn't set in the _emitCurrentToken(), 118 //then set it's location at the current preprocessor position. 119 //We don't need to increment preprocessor position, since character token 120 //emission is always forced by the start of the next character token here. 121 //So, we already have advanced position. 122 if (ctLoc && ctLoc.endOffset === -1) { 123 ctLoc.endLine = mxn.posTracker.line; 124 ctLoc.endCol = mxn.posTracker.col; 125 ctLoc.endOffset = mxn.posTracker.offset; 126 } 127 128 orig._emitCurrentCharacterToken.call(this); 129 } 130 }; 131 132 //NOTE: patch initial states for each mode to obtain token start position 133 Object.keys(Tokenizer.MODE).forEach(modeName => { 134 const state = Tokenizer.MODE[modeName]; 135 136 methods[state] = function(cp) { 137 mxn.ctLoc = mxn._getCurrentLocation(); 138 orig[state].call(this, cp); 139 }; 140 }); 141 142 return methods; 143 } 144} 145 146module.exports = LocationInfoTokenizerMixin; 147