@@ -1143,6 +1143,9 @@ module ts {
11431143 InMultiLineCommentTrivia ,
11441144 InSingleQuoteStringLiteral ,
11451145 InDoubleQuoteStringLiteral ,
1146+ InTemplateHeadOrNoSubstitutionTemplate ,
1147+ InTemplateMiddleOrTail ,
1148+ InTemplateSubstitutionPosition ,
11461149 }
11471150
11481151 export enum TokenClass {
@@ -1168,7 +1171,26 @@ module ts {
11681171 }
11691172
11701173 export interface Classifier {
1171- getClassificationsForLine ( text : string , lexState : EndOfLineState , classifyKeywordsInGenerics ?: boolean ) : ClassificationResult ;
1174+ /**
1175+ * Gives lexical classifications of tokens on a line without any syntactic context.
1176+ * For instance, a token consisting of the text 'string' can be either an identifier
1177+ * named 'string' or the keyword 'string', however, because this classifier is not aware,
1178+ * it relies on certain heuristics to give acceptable results. For classifications where
1179+ * speed trumps accuracy, this function is preferable; however, for true accuracy, the
1180+ * syntactic classifier is ideal. In fact, in certain editing scenarios, combining the
1181+ * lexical, syntactic, and semantic classifiers may issue the best user experience.
1182+ *
1183+ * @param text The text of a line to classify.
1184+ * @param lexState The state of the lexical classifier at the end of the previous line.
1185+ * @param syntacticClassifierAbsent Whether the client is *not* using a syntactic classifier.
1186+ * If there is no syntactic classifier (syntacticClassifierAbsent=true),
1187+ * certain heuristics may be used in its place; however, if there is a
1188+ * syntactic classifier (syntacticClassifierAbsent=false), certain
1189+ * classifications which may be incorrectly categorized will be given
1190+ * back as Identifiers in order to allow the syntactic classifier to
1191+ * subsume the classification.
1192+ */
1193+ getClassificationsForLine ( text : string , lexState : EndOfLineState , syntacticClassifierAbsent : boolean ) : ClassificationResult ;
11721194 }
11731195
11741196 /**
@@ -5617,6 +5639,28 @@ module ts {
56175639 noRegexTable [ SyntaxKind . TrueKeyword ] = true ;
56185640 noRegexTable [ SyntaxKind . FalseKeyword ] = true ;
56195641
5642+ // Just a stack of TemplateHeads and OpenCurlyBraces, used to perform rudimentary (inexact)
5643+ // classification on template strings. Because of the context free nature of templates,
5644+ // the only precise way to classify a template portion would be by propagating the stack across
5645+ // lines, just as we do with the end-of-line state. However, this is a burden for implementers,
5646+ // and the behavior is entirely subsumed by the syntactic classifier anyway, so we instead
5647+ // flatten any nesting when the template stack is non-empty and encode it in the end-of-line state.
5648+ // Situations in which this fails are
5649+ // 1) When template strings are nested across different lines:
5650+ // `hello ${ `world
5651+ // ` }`
5652+ //
5653+ // Where on the second line, you will get the closing of a template,
5654+ // a closing curly, and a new template.
5655+ //
5656+ // 2) When substitution expressions have curly braces and the curly brace falls on the next line:
5657+ // `hello ${ () => {
5658+ // return "world" } } `
5659+ //
5660+ // Where on the second line, you will get the 'return' keyword,
5661+ // a string literal, and a template end consisting of ' } } `'.
5662+ var templateStack : SyntaxKind [ ] = [ ] ;
5663+
56205664 function isAccessibilityModifier ( kind : SyntaxKind ) {
56215665 switch ( kind ) {
56225666 case SyntaxKind . PublicKeyword :
@@ -5650,13 +5694,19 @@ module ts {
56505694 // if there are more cases we want the classifier to be better at.
56515695 return true ;
56525696 }
5653-
5654- // 'classifyKeywordsInGenerics' should be 'true' when a syntactic classifier is not present.
5655- function getClassificationsForLine ( text : string , lexState : EndOfLineState , classifyKeywordsInGenerics ?: boolean ) : ClassificationResult {
5697+
5698+ // If there is a syntactic classifier ('syntacticClassifierAbsent' is false),
5699+ // we will be more conservative in order to avoid conflicting with the syntactic classifier.
5700+ function getClassificationsForLine ( text : string , lexState : EndOfLineState , syntacticClassifierAbsent ?: boolean ) : ClassificationResult {
56565701 var offset = 0 ;
56575702 var token = SyntaxKind . Unknown ;
56585703 var lastNonTriviaToken = SyntaxKind . Unknown ;
56595704
5705+ // Empty out the template stack for reuse.
5706+ while ( templateStack . length > 0 ) {
5707+ templateStack . pop ( ) ;
5708+ }
5709+
56605710 // If we're in a string literal, then prepend: "\
56615711 // (and a newline). That way when we lex we'll think we're still in a string literal.
56625712 //
@@ -5675,6 +5725,17 @@ module ts {
56755725 text = "/*\n" + text ;
56765726 offset = 3 ;
56775727 break ;
5728+ case EndOfLineState . InTemplateHeadOrNoSubstitutionTemplate :
5729+ text = "`\n" + text ;
5730+ offset = 2 ;
5731+ break ;
5732+ case EndOfLineState . InTemplateMiddleOrTail :
5733+ text = "}\n" + text ;
5734+ offset = 2 ;
5735+ // fallthrough
5736+ case EndOfLineState . InTemplateSubstitutionPosition :
5737+ templateStack . push ( SyntaxKind . TemplateHead ) ;
5738+ break ;
56785739 }
56795740
56805741 scanner . setText ( text ) ;
@@ -5739,12 +5800,45 @@ module ts {
57395800 token === SyntaxKind . StringKeyword ||
57405801 token === SyntaxKind . NumberKeyword ||
57415802 token === SyntaxKind . BooleanKeyword ) {
5742- if ( angleBracketStack > 0 && ! classifyKeywordsInGenerics ) {
5743- // If it looks like we're could be in something generic, don't classify this
5744- // as a keyword. We may just get overwritten by the syntactic classifier,
5745- // causing a noisy experience for the user.
5746- token = SyntaxKind . Identifier ;
5747- }
5803+ if ( angleBracketStack > 0 && ! syntacticClassifierAbsent ) {
5804+ // If it looks like we're could be in something generic, don't classify this
5805+ // as a keyword. We may just get overwritten by the syntactic classifier,
5806+ // causing a noisy experience for the user.
5807+ token = SyntaxKind . Identifier ;
5808+ }
5809+ }
5810+ else if ( token === SyntaxKind . TemplateHead ) {
5811+ templateStack . push ( token ) ;
5812+ }
5813+ else if ( token === SyntaxKind . OpenBraceToken ) {
5814+ // If we don't have anything on the template stack,
5815+ // then we aren't trying to keep track of a previously scanned template head.
5816+ if ( templateStack . length > 0 ) {
5817+ templateStack . push ( token ) ;
5818+ }
5819+ }
5820+ else if ( token === SyntaxKind . CloseBraceToken ) {
5821+ // If we don't have anything on the template stack,
5822+ // then we aren't trying to keep track of a previously scanned template head.
5823+ if ( templateStack . length > 0 ) {
5824+ var lastTemplateStackToken = lastOrUndefined ( templateStack ) ;
5825+
5826+ if ( lastTemplateStackToken === SyntaxKind . TemplateHead ) {
5827+ token = scanner . reScanTemplateToken ( ) ;
5828+
5829+ // Only pop on a TemplateTail; a TemplateMiddle indicates there is more for us.
5830+ if ( token === SyntaxKind . TemplateTail ) {
5831+ templateStack . pop ( ) ;
5832+ }
5833+ else {
5834+ Debug . assert ( token === SyntaxKind . TemplateMiddle , "Should have been a template middle. Was " + token ) ;
5835+ }
5836+ }
5837+ else {
5838+ Debug . assert ( lastTemplateStackToken === SyntaxKind . OpenBraceToken , "Should have been an open brace. Was: " + token ) ;
5839+ templateStack . pop ( ) ;
5840+ }
5841+ }
57485842 }
57495843
57505844 lastNonTriviaToken = token ;
@@ -5789,6 +5883,22 @@ module ts {
57895883 result . finalLexState = EndOfLineState . InMultiLineCommentTrivia ;
57905884 }
57915885 }
5886+ else if ( isTemplateLiteralKind ( token ) ) {
5887+ if ( scanner . isUnterminated ( ) ) {
5888+ if ( token === SyntaxKind . TemplateTail ) {
5889+ result . finalLexState = EndOfLineState . InTemplateMiddleOrTail ;
5890+ }
5891+ else if ( token === SyntaxKind . NoSubstitutionTemplateLiteral ) {
5892+ result . finalLexState = EndOfLineState . InTemplateHeadOrNoSubstitutionTemplate ;
5893+ }
5894+ else {
5895+ Debug . fail ( "Only 'NoSubstitutionTemplateLiteral's and 'TemplateTail's can be unterminated; got SyntaxKind #" + token ) ;
5896+ }
5897+ }
5898+ }
5899+ else if ( templateStack . length > 0 && lastOrUndefined ( templateStack ) === SyntaxKind . TemplateHead ) {
5900+ result . finalLexState = EndOfLineState . InTemplateSubstitutionPosition ;
5901+ }
57925902 }
57935903 }
57945904
@@ -5892,6 +6002,9 @@ module ts {
58926002 return TokenClass . Whitespace ;
58936003 case SyntaxKind . Identifier :
58946004 default :
6005+ if ( isTemplateLiteralKind ( token ) ) {
6006+ return TokenClass . StringLiteral ;
6007+ }
58956008 return TokenClass . Identifier ;
58966009 }
58976010 }
0 commit comments