5252import com .semmle .js .ast .ForStatement ;
5353import com .semmle .js .ast .FunctionDeclaration ;
5454import com .semmle .js .ast .FunctionExpression ;
55+ import com .semmle .js .ast .GeneratedCodeExpr ;
5556import com .semmle .js .ast .IFunction ;
5657import com .semmle .js .ast .INode ;
5758import com .semmle .js .ast .IPattern ;
@@ -537,7 +538,7 @@ private Token readToken_question() { // '?'
537538 }
538539 return this .finishOp (TokenType .questionquestion , 2 );
539540 }
540-
541+
541542 }
542543 return this .finishOp (TokenType .question , 1 );
543544 }
@@ -617,6 +618,15 @@ private Token readToken_lt_gt(int code) { // '<>'
617618 this .skipSpace ();
618619 return this .nextToken ();
619620 }
621+ if (next == '%' && code == '<' && this .options .allowGeneratedCodeExprs ()) {
622+ // `<%`, the beginning of an EJS-style template tag
623+ size = 2 ;
624+ int nextNext = charAt (this .pos + 2 );
625+ if (nextNext == '=' || nextNext == '-' ) {
626+ ++size ;
627+ }
628+ return this .finishOp (TokenType .generatedCodeDelimiterEJS , size );
629+ }
620630 if (next == 61 ) size = 2 ;
621631 return this .finishOp (TokenType .relational , size );
622632 }
@@ -1688,6 +1698,9 @@ protected Expression parseExprAtom(DestructuringErrors refDestructuringErrors) {
16881698 return this .parseNew ();
16891699 } else if (this .type == TokenType .backQuote ) {
16901700 return this .parseTemplate (false );
1701+ } else if (this .type == TokenType .generatedCodeDelimiterEJS ) {
1702+ String openingDelimiter = (String ) this .value ;
1703+ return this .parseGeneratedCodeExpr (this .startLoc , openingDelimiter , "%>" );
16911704 } else {
16921705 this .unexpected ();
16931706 return null ;
@@ -1929,10 +1942,16 @@ MethodDefinition.Kind getMethodKind() {
19291942 // Parse an object literal or binding pattern.
19301943 protected Expression parseObj (boolean isPattern , DestructuringErrors refDestructuringErrors ) {
19311944 Position startLoc = this .startLoc ;
1945+ if (!isPattern && options .allowGeneratedCodeExprs () && charAt (pos ) == '{' ) {
1946+ // Parse mustache-style placeholder expression: {{ ... }} or {{{ ... }}}
1947+ return charAt (pos + 1 ) == '{'
1948+ ? parseGeneratedCodeExpr (startLoc , "{{{" , "}}}" )
1949+ : parseGeneratedCodeExpr (startLoc , "{{" , "}}" );
1950+ }
19321951 boolean first = true ;
19331952 Map <String , PropInfo > propHash = new LinkedHashMap <>();
19341953 List <Property > properties = new ArrayList <Property >();
1935- this .next ();
1954+ this .next (); // skip '{'
19361955 while (!this .eat (TokenType .braceR )) {
19371956 if (!first ) {
19381957 this .expect (TokenType .comma );
@@ -1949,6 +1968,42 @@ protected Expression parseObj(boolean isPattern, DestructuringErrors refDestruct
19491968 return this .finishNode (node );
19501969 }
19511970
1971+ /** Emit a token ranging from the current position until <code>endOfToken</code>. */
1972+ private Token generateTokenEndingAt (int endOfToken , TokenType tokenType ) {
1973+ this .lastTokEnd = this .end ;
1974+ this .lastTokStart = this .start ;
1975+ this .lastTokEndLoc = this .endLoc ;
1976+ this .lastTokStartLoc = this .startLoc ;
1977+ this .start = this .pos ;
1978+ this .startLoc = this .curPosition ();
1979+ this .pos = endOfToken ;
1980+ return finishToken (tokenType );
1981+ }
1982+
1983+ /** Parse a generated expression. The current token refers to the opening delimiter. */
1984+ protected Expression parseGeneratedCodeExpr (Position startLoc , String openingDelimiter , String closingDelimiter ) {
1985+ // Emit a token for what's left of the opening delimiter, if there are any remaining characters
1986+ int startOfBody = startLoc .getOffset () + openingDelimiter .length ();
1987+ if (this .pos != startOfBody ) {
1988+ this .generateTokenEndingAt (startOfBody , TokenType .generatedCodeDelimiter );
1989+ }
1990+
1991+ // Emit a token for the generated code body
1992+ int endOfBody = this .input .indexOf (closingDelimiter , startOfBody );
1993+ if (endOfBody == -1 ) {
1994+ this .unexpected (startLoc );
1995+ }
1996+ Token bodyToken = this .generateTokenEndingAt (endOfBody , TokenType .generatedCodeExpr );
1997+
1998+ // Emit a token for the closing delimiter
1999+ this .generateTokenEndingAt (endOfBody + closingDelimiter .length (), TokenType .generatedCodeDelimiter );
2000+
2001+ this .next (); // produce lookahead token
2002+
2003+ return finishNode (new GeneratedCodeExpr (new SourceLocation (startLoc ), openingDelimiter , closingDelimiter ,
2004+ bodyToken .getValue ()));
2005+ }
2006+
19522007 protected Property parseProperty (
19532008 boolean isPattern ,
19542009 DestructuringErrors refDestructuringErrors ,
0 commit comments