@@ -1143,6 +1143,9 @@ module ts {
1143
1143
InMultiLineCommentTrivia ,
1144
1144
InSingleQuoteStringLiteral ,
1145
1145
InDoubleQuoteStringLiteral ,
1146
+ InTemplateHeadOrNoSubstitutionTemplate ,
1147
+ InTemplateMiddleOrTail ,
1148
+ InTemplateSubstitutionPosition ,
1146
1149
}
1147
1150
1148
1151
export enum TokenClass {
@@ -1168,7 +1171,26 @@ module ts {
1168
1171
}
1169
1172
1170
1173
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 ;
1172
1194
}
1173
1195
1174
1196
/**
@@ -5617,6 +5639,28 @@ module ts {
5617
5639
noRegexTable [ SyntaxKind . TrueKeyword ] = true ;
5618
5640
noRegexTable [ SyntaxKind . FalseKeyword ] = true ;
5619
5641
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
+
5620
5664
function isAccessibilityModifier ( kind : SyntaxKind ) {
5621
5665
switch ( kind ) {
5622
5666
case SyntaxKind . PublicKeyword :
@@ -5650,13 +5694,19 @@ module ts {
5650
5694
// if there are more cases we want the classifier to be better at.
5651
5695
return true ;
5652
5696
}
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 {
5656
5701
var offset = 0 ;
5657
5702
var token = SyntaxKind . Unknown ;
5658
5703
var lastNonTriviaToken = SyntaxKind . Unknown ;
5659
5704
5705
+ // Empty out the template stack for reuse.
5706
+ while ( templateStack . length > 0 ) {
5707
+ templateStack . pop ( ) ;
5708
+ }
5709
+
5660
5710
// If we're in a string literal, then prepend: "\
5661
5711
// (and a newline). That way when we lex we'll think we're still in a string literal.
5662
5712
//
@@ -5675,6 +5725,17 @@ module ts {
5675
5725
text = "/*\n" + text ;
5676
5726
offset = 3 ;
5677
5727
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 ;
5678
5739
}
5679
5740
5680
5741
scanner . setText ( text ) ;
@@ -5739,12 +5800,45 @@ module ts {
5739
5800
token === SyntaxKind . StringKeyword ||
5740
5801
token === SyntaxKind . NumberKeyword ||
5741
5802
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
+ }
5748
5842
}
5749
5843
5750
5844
lastNonTriviaToken = token ;
@@ -5789,6 +5883,22 @@ module ts {
5789
5883
result . finalLexState = EndOfLineState . InMultiLineCommentTrivia ;
5790
5884
}
5791
5885
}
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
+ }
5792
5902
}
5793
5903
}
5794
5904
@@ -5892,6 +6002,9 @@ module ts {
5892
6002
return TokenClass . Whitespace ;
5893
6003
case SyntaxKind . Identifier :
5894
6004
default :
6005
+ if ( isTemplateLiteralKind ( token ) ) {
6006
+ return TokenClass . StringLiteral ;
6007
+ }
5895
6008
return TokenClass . Identifier ;
5896
6009
}
5897
6010
}
0 commit comments