docs: function createClassifier() {
          var scanner = ts.createScanner(2 /* Latest */, /*skipTrivia*/ false);
          /// We do not have a full parser support to know when we should parse a regex or not
          /// If we consider every slash token to be a regex, we could be missing cases like "1/2/3", where
          /// we have a series of divide operator. this list allows us to be more accurate by ruling out
          /// locations where a regexp cannot exist.
          var noRegexTable = [];
          noRegexTable[69 /* Identifier */] = true;
          noRegexTable[9 /* StringLiteral */] = true;
          noRegexTable[8 /* NumericLiteral */] = true;
          noRegexTable[10 /* RegularExpressionLiteral */] = true;
          noRegexTable[97 /* ThisKeyword */] = true;
          noRegexTable[41 /* PlusPlusToken */] = true;
          noRegexTable[42 /* MinusMinusToken */] = true;
          noRegexTable[18 /* CloseParenToken */] = true;
          noRegexTable[20 /* CloseBracketToken */] = true;
          noRegexTable[16 /* CloseBraceToken */] = true;
          noRegexTable[99 /* TrueKeyword */] = true;
          noRegexTable[84 /* FalseKeyword */] = true;
          // Just a stack of TemplateHeads and OpenCurlyBraces, used to perform rudimentary (inexact)
          // classification on template strings. Because of the context free nature of templates,
          // the only precise way to classify a template portion would be by propagating the stack across
          // lines, just as we do with the end-of-line state. However, this is a burden for implementers,
          // and the behavior is entirely subsumed by the syntactic classifier anyway, so we instead
          // flatten any nesting when the template stack is non-empty and encode it in the end-of-line state.
          // Situations in which this fails are
          //  1) When template strings are nested across different lines:
          //          `hello ${ `world
          //          ` }`
          //
          //     Where on the second line, you will get the closing of a template,
          //     a closing curly, and a new template.
          //
          //  2) When substitution expressions have curly braces and the curly brace falls on the next line:
          //          `hello ${ () => {
          //          return "world" } } `
          //
          //     Where on the second line, you will get the 'return' keyword,
          //     a string literal, and a template end consisting of '} } `'.
          var templateStack = [];
          /** Returns true if 'keyword2' can legally follow 'keyword1' in any language construct. */
          function canFollow(keyword1, keyword2) {
              if (ts.isAccessibilityModifier(keyword1)) {
                  if (keyword2 === 123 /* GetKeyword */ ||
                      keyword2 === 131 /* SetKeyword */ ||
                      keyword2 === 121 /* ConstructorKeyword */ ||
                      keyword2 === 113 /* StaticKeyword */) {
                      // Allow things like "public get", "public constructor" and "public static".
                      // These are all legal.
                      return true;
                  }
                  // Any other keyword following "public" is actually an identifier an not a real
                  // keyword.
                  return false;
              }
              // Assume any other keyword combination is legal.  This can be refined in the future
              // if there are more cases we want the classifier to be better at.
              return true;
          }
          function convertClassifications(classifications, text) {
              var entries = [];
              var dense = classifications.spans;
              var lastEnd = 0;
              for (var i = 0, n = dense.length; i < n; i += 3) {
                  var start = dense[i];
                  var length_5 = dense[i + 1];
                  var type = dense[i + 2];
                  // Make a whitespace entry between the last item and this one.
                  if (lastEnd >= 0) {
                      var whitespaceLength_1 = start - lastEnd;
                      if (whitespaceLength_1 > 0) {
                          entries.push({ length: whitespaceLength_1, classification: TokenClass.Whitespace });
                      }
                  }
                  entries.push({ length: length_5, classification: convertClassification(type) });
                  lastEnd = start + length_5;
              }
              var whitespaceLength = text.length - lastEnd;
              if (whitespaceLength > 0) {
                  entries.push({ length: whitespaceLength, classification: TokenClass.Whitespace });
              }
              return { entries: entries, finalLexState: classifications.endOfLineState };
          }
          function convertClassification(type) {
              switch (type) {
                  case 1 /* comment */: return TokenClass.Comment;
                  case 3 /* keyword */: return TokenClass.Keyword;
                  case 4 /* numericLiteral */: return TokenClass.NumberLiteral;
                  case 5 /* operator */: return TokenClass.Operator;
                  case 6 /* stringLiteral */: return TokenClass.StringLiteral;
                  case 8 /* whiteSpace */: return TokenClass.Whitespace;
                  case 10 /* punctuation */: return TokenClass.Punctuation;
                  case 2 /* identifier */:
                  case 11 /* className */:
                  case 12 /* enumName */:
                  case 13 /* interfaceName */:
                  case 14 /* moduleName */:
                  case 15 /* typeParameterName */:
                  case 16 /* typeAliasName */:
                  case 9 /* text */:
                  case 17 /* parameterName */:
                  default:
                      return TokenClass.Identifier;
              }
          }
          function getClassificationsForLine(text, lexState, syntacticClassifierAbsent) {
              return convertClassifications(getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent), text);
          }
          // If there is a syntactic classifier ('syntacticClassifierAbsent' is false),
          // we will be more conservative in order to avoid conflicting with the syntactic classifier.
          function getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent) {
              var offset = 0;
              var token = 0 /* Unknown */;
              var lastNonTriviaToken = 0 /* Unknown */;
              // Empty out the template stack for reuse.
              while (templateStack.length > 0) {
                  templateStack.pop();
              }
              // If we're in a string literal, then prepend: "\
              // (and a newline).  That way when we lex we'll think we're still in a string literal.
              //
              // If we're in a multiline comment, then prepend: /*
              // (and a newline).  That way when we lex we'll think we're still in a multiline comment.
              switch (lexState) {
                  case 3 /* InDoubleQuoteStringLiteral */:
                      text = "\"\\\n" + text;
                      offset = 3;
                      break;
                  case 2 /* InSingleQuoteStringLiteral */:
                      text = "'\\\n" + text;
                      offset = 3;
                      break;
                  case 1 /* InMultiLineCommentTrivia */:
                      text = "/*\n" + text;
                      offset = 3;
                      break;
                  case 4 /* InTemplateHeadOrNoSubstitutionTemplate */:
                      text = "`\n" + text;
                      offset = 2;
                      break;
                  case 5 /* InTemplateMiddleOrTail */:
                      text = "}\n" + text;
                      offset = 2;
                  // fallthrough
                  case 6 /* InTemplateSubstitutionPosition */:
                      templateStack.push(12 /* TemplateHead */);
                      break;
              }
              scanner.setText(text);
              var result = {
                  endOfLineState: 0 /* None */,
                  spans: []
              };
              // We can run into an unfortunate interaction between the lexical and syntactic classifier
              // when the user is typing something generic.  Consider the case where the user types:
              //
              //      Foo<number
              //
              // From the lexical classifier's perspective, 'number' is a keyword, and so the word will
              // be classified as such.  However, from the syntactic classifier's tree-based perspective
              // this is simply an expression with the identifier 'number' on the RHS of the less than
              // token.  So the classification will go back to being an identifier.  The moment the user
              // types again, number will become a keyword, then an identifier, etc. etc.
              //
              // To try to avoid this problem, we avoid classifying contextual keywords as keywords
              // when the user is potentially typing something generic.  We just can't do a good enough
              // job at the lexical level, and so well leave it up to the syntactic classifier to make
              // the determination.
              //
              // In order to determine if the user is potentially typing something generic, we use a
              // weak heuristic where we track < and > tokens.  It's a weak heuristic, but should
              // work well enough in practice.
              var angleBracketStack = 0;
              do {
                  token = scanner.scan();
                  if (!ts.isTrivia(token)) {
                      if ((token === 39 /* SlashToken */ || token === 61 /* SlashEqualsToken */) && !noRegexTable[lastNonTriviaToken]) {
                          if (scanner.reScanSlashToken() === 10 /* RegularExpressionLiteral */) {
                              token = 10 /* RegularExpressionLiteral */;
                          }
                      }
                      else if (lastNonTriviaToken === 21 /* DotToken */ && isKeyword(token)) {
                          token = 69 /* Identifier */;
                      }
                      else if (isKeyword(lastNonTriviaToken) && isKeyword(token) && !canFollow(lastNonTriviaToken, token)) {
                          // We have two keywords in a row.  Only treat the second as a keyword if
                          // it's a sequence that could legally occur in the language.  Otherwise
                          // treat it as an identifier.  This way, if someone writes "private var"
                          // we recognize that 'var' is actually an identifier here.
                          token = 69 /* Identifier */;
                      }
                      else if (lastNonTriviaToken === 69 /* Identifier */ &&
                          token === 25 /* LessThanToken */) {
                          // Could be the start of something generic.  Keep track of that by bumping
                          // up the current count of generic contexts we may be in.
                          angleBracketStack++;
                      }
                      else if (token === 27 /* GreaterThanToken */ && angleBracketStack > 0) {
                          // If we think we're currently in something generic, then mark that that
                          // generic entity is complete.
                          angleBracketStack--;
                      }
                      else if (token === 117 /* AnyKeyword */ ||
                          token === 132 /* StringKeyword */ ||
                          token === 130 /* NumberKeyword */ ||
                          token === 120 /* BooleanKeyword */ ||
                          token === 133 /* SymbolKeyword */) {
                          if (angleBracketStack > 0 && !syntacticClassifierAbsent) {
                              // If it looks like we're could be in something generic, don't classify this
                              // as a keyword.  We may just get overwritten by the syntactic classifier,
                              // causing a noisy experience for the user.
                              token = 69 /* Identifier */;
                          }
                      }
                      else if (token === 12 /* TemplateHead */) {
                          templateStack.push(token);
                      }
                      else if (token === 15 /* OpenBraceToken */) {
                          // If we don't have anything on the template stack,
                          // then we aren't trying to keep track of a previously scanned template head.
                          if (templateStack.length > 0) {
                              templateStack.push(token);
                          }
                      }
                      else if (token === 16 /* CloseBraceToken */) {
                          // If we don't have anything on the template stack,
                          // then we aren't trying to keep track of a previously scanned template head.
                          if (templateStack.length > 0) {
                              var lastTemplateStackToken = ts.lastOrUndefined(templateStack);
                              if (lastTemplateStackToken === 12 /* TemplateHead */) {
                                  token = scanner.reScanTemplateToken();
                                  // Only pop on a TemplateTail; a TemplateMiddle indicates there is more for us.
                                  if (token === 14 /* TemplateTail */) {
                                      templateStack.pop();
                                  }
                                  else {
                                      ts.Debug.assert(token === 13 /* TemplateMiddle */, "Should have been a template middle. Was " + token);
                                  }
                              }
                              else {
                                  ts.Debug.assert(lastTemplateStackToken === 15 /* OpenBraceToken */, "Should have been an open brace. Was: " + token);
                                  templateStack.pop();
                              }
                          }
                      }
                      lastNonTriviaToken = token;
                  }
                  processToken();
              } while (token !== 1 /* EndOfFileToken */);
              return result;
              function processToken() {
                  var start = scanner.getTokenPos();
                  var end = scanner.getTextPos();
                  addResult(start, end, classFromKind(token));
                  if (end >= text.length) {
                      if (token === 9 /* StringLiteral */) {
                          // Check to see if we finished up on a multiline string literal.
                          var tokenText = scanner.getTokenText();
                          if (scanner.isUnterminated()) {
                              var lastCharIndex = tokenText.length - 1;
                              var numBackslashes = 0;
                              while (tokenText.charCodeAt(lastCharIndex - numBackslashes) === 92 /* backslash */) {
                                  numBackslashes++;
                              }
                              // If we have an odd number of backslashes, then the multiline string is unclosed
                              if (numBackslashes & 1) {
                                  var quoteChar = tokenText.charCodeAt(0);
                                  result.endOfLineState = quoteChar === 34 /* doubleQuote */
                                      ? 3 /* InDoubleQuoteStringLiteral */
                                      : 2 /* InSingleQuoteStringLiteral */;
                              }
                          }
                      }
                      else if (token === 3 /* MultiLineCommentTrivia */) {
                          // Check to see if the multiline comment was unclosed.
                          if (scanner.isUnterminated()) {
                              result.endOfLineState = 1 /* InMultiLineCommentTrivia */;
                          }
                      }
                      else if (ts.isTemplateLiteralKind(token)) {
                          if (scanner.isUnterminated()) {
                              if (token === 14 /* TemplateTail */) {
                                  result.endOfLineState = 5 /* InTemplateMiddleOrTail */;
                              }
                              else if (token === 11 /* NoSubstitutionTemplateLiteral */) {
                                  result.endOfLineState = 4 /* InTemplateHeadOrNoSubstitutionTemplate */;
                              }
                              else {
                                  ts.Debug.fail("Only 'NoSubstitutionTemplateLiteral's and 'TemplateTail's can be unterminated; got SyntaxKind #" + token);
                              }
                          }
                      }
                      else if (templateStack.length > 0 && ts.lastOrUndefined(templateStack) === 12 /* TemplateHead */) {
                          result.endOfLineState = 6 /* InTemplateSubstitutionPosition */;
                      }
                  }
              }
              function addResult(start, end, classification) {
                  if (classification === 8 /* whiteSpace */) {
                      // Don't bother with whitespace classifications.  They're not needed.
                      return;
                  }
                  if (start === 0 && offset > 0) {
                      // We're classifying the first token, and this was a case where we prepended
                      // text.  We should consider the start of this token to be at the start of
                      // the original text.
                      start += offset;
                  }
                  // All our tokens are in relation to the augmented text.  Move them back to be
                  // relative to the original text.
                  start -= offset;
                  end -= offset;
                  var length = end - start;
                  if (length > 0) {
                      result.spans.push(start);
                      result.spans.push(length);
                      result.spans.push(classification);
                  }
              }
          }
          function isBinaryExpressionOperatorToken(token) {
              switch (token) {
                  case 37 /* AsteriskToken */:
                  case 39 /* SlashToken */:
                  case 40 /* PercentToken */:
                  case 35 /* PlusToken */:
                  case 36 /* MinusToken */:
                  case 43 /* LessThanLessThanToken */:
                  case 44 /* GreaterThanGreaterThanToken */:
                  case 45 /* GreaterThanGreaterThanGreaterThanToken */:
                  case 25 /* LessThanToken */:
                  case 27 /* GreaterThanToken */:
                  case 28 /* LessThanEqualsToken */:
                  case 29 /* GreaterThanEqualsToken */:
                  case 91 /* InstanceOfKeyword */:
                  case 90 /* InKeyword */:
                  case 116 /* AsKeyword */:
                  case 30 /* EqualsEqualsToken */:
                  case 31 /* ExclamationEqualsToken */:
                  case 32 /* EqualsEqualsEqualsToken */:
                  case 33 /* ExclamationEqualsEqualsToken */:
                  case 46 /* AmpersandToken */:
                  case 48 /* CaretToken */:
                  case 47 /* BarToken */:
                  case 51 /* AmpersandAmpersandToken */:
                  case 52 /* BarBarToken */:
                  case 67 /* BarEqualsToken */:
                  case 66 /* AmpersandEqualsToken */:
                  case 68 /* CaretEqualsToken */:
                  case 63 /* LessThanLessThanEqualsToken */:
                  case 64 /* GreaterThanGreaterThanEqualsToken */:
                  case 65 /* GreaterThanGreaterThanGreaterThanEqualsToken */:
                  case 57 /* PlusEqualsToken */:
                  case 58 /* MinusEqualsToken */:
                  case 59 /* AsteriskEqualsToken */:
                  case 61 /* SlashEqualsToken */:
                  case 62 /* PercentEqualsToken */:
                  case 56 /* EqualsToken */:
                  case 24 /* CommaToken */:
                      return true;
                  default:
                      return false;
              }
          }
          function isPrefixUnaryExpressionOperatorToken(token) {
              switch (token) {
                  case 35 /* PlusToken */:
                  case 36 /* MinusToken */:
                  case 50 /* TildeToken */:
                  case 49 /* ExclamationToken */:
                  case 41 /* PlusPlusToken */:
                  case 42 /* MinusMinusToken */:
                      return true;
                  default:
                      return false;
              }
          }
          function isKeyword(token) {
              return token >= 70 /* FirstKeyword */ && token <= 138 /* LastKeyword */;
          }
          function classFromKind(token) {
              if (isKeyword(token)) {
                  return 3 /* keyword */;
              }
              else if (isBinaryExpressionOperatorToken(token) || isPrefixUnaryExpressionOperatorToken(token)) {
                  return 5 /* operator */;
              }
              else if (token >= 15 /* FirstPunctuation */ && token <= 68 /* LastPunctuation */) {
                  return 10 /* punctuation */;
              }
              switch (token) {
                  case 8 /* NumericLiteral */:
                      return 4 /* numericLiteral */;
                  case 9 /* StringLiteral */:
                      return 6 /* stringLiteral */;
                  case 10 /* RegularExpressionLiteral */:
                      return 7 /* regularExpressionLiteral */;
                  case 7 /* ConflictMarkerTrivia */:
                  case 3 /* MultiLineCommentTrivia */:
                  case 2 /* SingleLineCommentTrivia */:
                      return 1 /* comment */;
                  case 5 /* WhitespaceTrivia */:
                  case 4 /* NewLineTrivia */:
                      return 8 /* whiteSpace */;
                  case 69 /* Identifier */:
                  default:
                      if (ts.isTemplateLiteralKind(token)) {
                          return 6 /* stringLiteral */;
                      }
                      return 2 /* identifier */;
              }
          }
          return {
              getClassificationsForLine: getClassificationsForLine,
              getEncodedLexicalClassifications: getEncodedLexicalClassifications
          };
      }