99// Requirements
1010//------------------------------------------------------------------------------
1111
12- const entities = require ( "entities" )
12+ const sortedIndexBy = require ( "lodash.sortedindexby" )
13+ const decodeHtmlEntities = require ( "./decode-html-entities" )
14+ const traverseNodes = require ( "./traverse-nodes" )
1315
1416//------------------------------------------------------------------------------
1517// Helpers
@@ -18,6 +20,83 @@ const entities = require("entities")
1820const NON_LT = / [ ^ \r \n \u2028 \u2029 ] / g
1921const SPACE = / \s /
2022
23+ /**
24+ * Get the 1st element of the given array.
25+ * @param {any[] } item The array to get.
26+ * @returns {any } The 1st element.
27+ */
28+ function first ( item ) {
29+ return item [ 0 ]
30+ }
31+
32+ /**
33+ * Fix the range of location of the given node.
34+ * This will expand ranges because those have shrunk by decoding HTML entities.
35+ * @param {ASTNode } node The node to fix range and location.
36+ * @param {TokenGenerator } tokenGenerator The token generator to re-calculate locations.
37+ * @param {(number[])[] } gaps The gap array to re-calculate ranges.
38+ * @param {number } codeStart The start offset of this expression.
39+ * @returns {void }
40+ */
41+ function fixRangeAndLocByGap ( node , tokenGenerator , gaps , codeStart ) {
42+ const range = node . range
43+ const loc = node . loc
44+ const start = range [ 0 ] - codeStart
45+ const end = range [ 1 ] - codeStart
46+
47+ let i = sortedIndexBy ( gaps , [ start ] , first ) - 1
48+ if ( i >= 0 ) {
49+ range [ 0 ] += ( i + 1 < gaps . length && gaps [ i + 1 ] [ 0 ] === start )
50+ ? gaps [ i + 1 ] [ 1 ]
51+ : gaps [ i ] [ 1 ]
52+ loc . start = tokenGenerator . getLocPart ( range [ 0 ] )
53+ }
54+ i = sortedIndexBy ( gaps , [ end ] , first ) - 1
55+ if ( i >= 0 ) {
56+ range [ 1 ] += ( i + 1 < gaps . length && gaps [ i + 1 ] [ 0 ] === end )
57+ ? gaps [ i + 1 ] [ 1 ]
58+ : gaps [ i ] [ 1 ]
59+ loc . end = tokenGenerator . getLocPart ( range [ 1 ] )
60+ }
61+ }
62+
63+ /**
64+ * Do post-process of parsing an expression.
65+ *
66+ * 1. Set `node.parent`.
67+ * 2. Fix `node.range` and `node.loc` for HTML entities.
68+ *
69+ * @param {ASTNode } ast The AST root node to initialize.
70+ * @param {TokenGenerator } tokenGenerator The token generator to calculate locations.
71+ * @param {(number[])[] } gaps The gaps to re-calculate locations.
72+ * @param {number } codeStart The start offset of the expression.
73+ * @returns {void }
74+ */
75+ function postprocess ( ast , tokenGenerator , gaps , codeStart ) {
76+ const gapsExist = gaps . length >= 1
77+
78+ traverseNodes ( ast , {
79+ enterNode ( node , parent ) {
80+ node . parent = parent
81+ if ( gapsExist ) {
82+ fixRangeAndLocByGap ( node , tokenGenerator , gaps , codeStart )
83+ }
84+ } ,
85+ leaveNode ( ) {
86+ // Do nothing.
87+ } ,
88+ } )
89+
90+ if ( gapsExist ) {
91+ for ( const token of ast . tokens ) {
92+ fixRangeAndLocByGap ( token , tokenGenerator , gaps , codeStart )
93+ }
94+ for ( const comment of ast . comments ) {
95+ fixRangeAndLocByGap ( comment , tokenGenerator , gaps , codeStart )
96+ }
97+ }
98+ }
99+
21100/**
22101 * The script parser.
23102 */
@@ -96,9 +175,10 @@ class ScriptParser {
96175 * Parse the script which is on the given range.
97176 * @param {number } start The start offset to parse.
98177 * @param {number } end The end offset to parse.
178+ * @param {TokenGenerator } tokenGenerator The token generator to fix loc.
99179 * @returns {ASTNode } The created AST node.
100180 */
101- parseExpression ( start , end ) {
181+ parseExpression ( start , end , tokenGenerator ) {
102182 const codeStart = this . getInlineScriptStart ( start )
103183 const codeEnd = this . getInlineScriptEnd ( end )
104184 if ( codeStart >= codeEnd ) {
@@ -109,8 +189,10 @@ class ScriptParser {
109189 }
110190
111191 const prefix = this . text . slice ( 0 , codeStart - 1 ) . replace ( NON_LT , " " )
112- const code = entities . decodeHTML ( this . text . slice ( codeStart , codeEnd ) )
113- const ast = this . _parseScript ( `${ prefix } (${ code } )` )
192+ const code = this . text . slice ( codeStart , codeEnd )
193+ const gaps = [ ]
194+ const decodedCode = decodeHtmlEntities ( code , gaps )
195+ const ast = this . _parseScript ( `${ prefix } (${ decodedCode } )` )
114196
115197 if ( ast . body . length === 0 ) {
116198 throw new Error (
@@ -131,6 +213,8 @@ class ScriptParser {
131213 expression . tokens . shift ( )
132214 expression . tokens . pop ( )
133215
216+ postprocess ( expression , tokenGenerator , gaps , codeStart )
217+
134218 return expression
135219 }
136220}
0 commit comments