namespace ts { /** The classifier is used for syntactic highlighting in editors via the TSServer */ export function createClassifier ( ) { const scanner = createScanner ( ScriptTarget . Latest , false ) ; function getClassificationsForLine ( text , lexState , syntacticClassifierAbsent ) { return convertClassificationsToResult ( getEncodedLexicalClassifications ( text , lexState , syntacticClassifierAbsent ) , text ) ; } function getEncodedLexicalClassifications ( text , lexState , syntacticClassifierAbsent ) { let token = SyntaxKind . Unknown ; let lastNonTriviaToken = SyntaxKind . Unknown ; const templateStack = [ ] ; const { prefix , pushTemplate } = getPrefixFromLexState ( lexState ) ; text = prefix + text ; const offset = prefix . length ; if ( pushTemplate ) { templateStack . push ( SyntaxKind . TemplateHead ) ; } scanner . setText ( text ) ; let endOfLineState = EndOfLineState . None ; const spans = [ ] ; let angleBracketStack = 0 ; do { token = scanner . scan ( ) ; if ( ! isTrivia ( token ) ) { handleToken ( ) ; lastNonTriviaToken = token ; } const end = scanner . getTextPos ( ) ; pushEncodedClassification ( scanner . getTokenPos ( ) , end , offset , classFromKind ( token ) , spans ) ; if ( end >= text . length ) { const end = getNewEndOfLineState ( scanner , token , lastOrUndefined ( templateStack ) ) ; if ( end !== undefined ) { endOfLineState = end ; } } } while ( token !== SyntaxKind . EndOfFileToken ) ; function handleToken ( ) { switch ( token ) { case SyntaxKind . SlashToken : case SyntaxKind . SlashEqualsToken : if ( ! noRegexTable [ lastNonTriviaToken ] && scanner . reScanSlashToken ( ) === SyntaxKind . RegularExpressionLiteral ) { token = SyntaxKind . RegularExpressionLiteral ; } break ; case SyntaxKind . LessThanToken : if ( lastNonTriviaToken === SyntaxKind . Identifier ) { angleBracketStack ++ ; } break ; case SyntaxKind . GreaterThanToken : if ( angleBracketStack > 0 ) { angleBracketStack -- ; } break ; case SyntaxKind . AnyKeyword : case SyntaxKind . StringKeyword : case SyntaxKind . NumberKeyword : case SyntaxKind . BooleanKeyword : case SyntaxKind . SymbolKeyword : if ( angleBracketStack > 0 && ! syntacticClassifierAbsent ) { token = SyntaxKind . Identifier ; } break ; case SyntaxKind . TemplateHead : templateStack . push ( token ) ; break ; case SyntaxKind . OpenBraceToken : if ( templateStack . length > 0 ) { templateStack . push ( token ) ; } break ; case SyntaxKind . CloseBraceToken : if ( templateStack . length > 0 ) { const lastTemplateStackToken = lastOrUndefined ( templateStack ) ; if ( lastTemplateStackToken === SyntaxKind . TemplateHead ) { token = scanner . reScanTemplateToken ( false ) ; if ( token === SyntaxKind . TemplateTail ) { templateStack . pop ( ) ; } else { Debug . assertEqual ( token , SyntaxKind . TemplateMiddle , "Should have been a template middle." ) ; } } else { Debug . assertEqual ( lastTemplateStackToken , SyntaxKind . OpenBraceToken , "Should have been an open brace" ) ; templateStack . pop ( ) ; } } break ; default : if ( ! isKeyword ( token ) ) { break ; } if ( lastNonTriviaToken === SyntaxKind . DotToken ) { token = SyntaxKind . Identifier ; } else if ( isKeyword ( lastNonTriviaToken ) && isKeyword ( token ) && ! canFollow ( lastNonTriviaToken , token ) ) { token = SyntaxKind . Identifier ; } } } return { endOfLineState , spans } ; } return { getClassificationsForLine , getEncodedLexicalClassifications } ; } const noRegexTable = arrayToNumericMap < SyntaxKind , true > ( [ SyntaxKind . Identifier , SyntaxKind . StringLiteral , SyntaxKind . NumericLiteral , SyntaxKind . BigIntLiteral , SyntaxKind . RegularExpressionLiteral , SyntaxKind . ThisKeyword , SyntaxKind . PlusPlusToken , SyntaxKind . MinusMinusToken , SyntaxKind . CloseParenToken , SyntaxKind . CloseBracketToken , SyntaxKind . CloseBraceToken , SyntaxKind . TrueKeyword , SyntaxKind . FalseKeyword , ] , token => token , ( ) => true ) ; function getNewEndOfLineState ( scanner , token , lastOnTemplateStack ) { switch ( token ) { case SyntaxKind . StringLiteral : { if ( ! scanner . isUnterminated ( ) ) return undefined ; const tokenText = scanner . getTokenText ( ) ; const lastCharIndex = tokenText . length - 1 ; let numBackslashes = 0 ; while ( tokenText . charCodeAt ( lastCharIndex - numBackslashes ) === CharacterCodes . backslash ) { numBackslashes ++ ; } if ( ( numBackslashes & 1 ) === 0 ) return undefined ; return tokenText . charCodeAt ( 0 ) === CharacterCodes . doubleQuote ? EndOfLineState . InDoubleQuoteStringLiteral : EndOfLineState . InSingleQuoteStringLiteral ; } case SyntaxKind . MultiLineCommentTrivia : return scanner . isUnterminated ( ) ? EndOfLineState . InMultiLineCommentTrivia : undefined ; default : if ( isTemplateLiteralKind ( token ) ) { if ( ! scanner . isUnterminated ( ) ) { return undefined ; } switch ( token ) { case SyntaxKind . TemplateTail : return EndOfLineState . InTemplateMiddleOrTail ; case SyntaxKind . NoSubstitutionTemplateLiteral : return EndOfLineState . InTemplateHeadOrNoSubstitutionTemplate ; default : return Debug . fail ( "Only 'NoSubstitutionTemplateLiteral's and 'TemplateTail's can be unterminated; got SyntaxKind #" + token ) ; } } return lastOnTemplateStack === SyntaxKind . TemplateHead ? EndOfLineState . InTemplateSubstitutionPosition : undefined ; } } function pushEncodedClassification ( start , end , offset , classification , result ) { if ( classification === ClassificationType . whiteSpace ) { return ; } if ( start === 0 && offset > 0 ) { start += offset ; } const length = end - start ; if ( length > 0 ) { result . push ( start - offset , length , classification ) ; } } function convertClassificationsToResult ( classifications , text ) { const entries = [ ] ; const dense = classifications . spans ; let lastEnd = 0 ; for ( let i = 0 ; i < dense . length ; i += 3 ) { const start = dense [ i ] ; const length = dense [ i + 1 ] ; const type = dense [ i + 2 ] as ClassificationType ; if ( lastEnd >= 0 ) { const whitespaceLength = start - lastEnd ; if ( whitespaceLength > 0 ) { entries . push ( { length : whitespaceLength , classification : TokenClass . Whitespace } ) ; } } entries . push ( { length , classification : convertClassification ( type ) } ) ; lastEnd = start + length ; } const whitespaceLength = text . length - lastEnd ; if ( whitespaceLength > 0 ) { entries . push ( { length : whitespaceLength , classification : TokenClass . Whitespace } ) ; } return { entries , finalLexState : classifications . endOfLineState } ; } function convertClassification ( type ) { switch ( type ) { case ClassificationType . comment : return TokenClass . Comment ; case ClassificationType . keyword : return TokenClass . Keyword ; case ClassificationType . numericLiteral : return TokenClass . NumberLiteral ; case ClassificationType . bigintLiteral : return TokenClass . BigIntLiteral ; case ClassificationType . operator : return TokenClass . Operator ; case ClassificationType . stringLiteral : return TokenClass . StringLiteral ; case ClassificationType . whiteSpace : return TokenClass . Whitespace ; case ClassificationType . punctuation : return TokenClass . Punctuation ; case ClassificationType . identifier : case ClassificationType . className : case ClassificationType . enumName : case ClassificationType . interfaceName : case ClassificationType . moduleName : case ClassificationType . typeParameterName : case ClassificationType . typeAliasName : case ClassificationType . text : case ClassificationType . parameterName : return TokenClass . Identifier ; default : return undefined ! ; } } /** Returns true if 'keyword2' can legally follow 'keyword1' in any language construct. */ function canFollow ( keyword1 , keyword2 ) { if ( ! isAccessibilityModifier ( keyword1 ) ) { return true ; } switch ( keyword2 ) { case SyntaxKind . GetKeyword : case SyntaxKind . SetKeyword : case SyntaxKind . ConstructorKeyword : case SyntaxKind . StaticKeyword : return true ; default : return false ; } } function getPrefixFromLexState ( lexState ) { switch ( lexState ) { case EndOfLineState . InDoubleQuoteStringLiteral : return { prefix : "\"\\\n" } ; case EndOfLineState . InSingleQuoteStringLiteral : return { prefix : "'\\\n" } ; case EndOfLineState . InMultiLineCommentTrivia : return { prefix : "/*\n" } ; case EndOfLineState . InTemplateHeadOrNoSubstitutionTemplate : return { prefix : "`\n" } ; case EndOfLineState . InTemplateMiddleOrTail : return { prefix : "}\n" , pushTemplate : true } ; case EndOfLineState . InTemplateSubstitutionPosition : return { prefix : "" , pushTemplate : true } ; case EndOfLineState . None : return { prefix : "" } ; default : return Debug . assertNever ( lexState ) ; } } function isBinaryExpressionOperatorToken ( token ) { switch ( token ) { case SyntaxKind . AsteriskToken : case SyntaxKind . SlashToken : case SyntaxKind . PercentToken : case SyntaxKind . PlusToken : case SyntaxKind . MinusToken : case SyntaxKind . LessThanLessThanToken : case SyntaxKind . GreaterThanGreaterThanToken : case SyntaxKind . GreaterThanGreaterThanGreaterThanToken : case SyntaxKind . LessThanToken : case SyntaxKind . GreaterThanToken : case SyntaxKind . LessThanEqualsToken : case SyntaxKind . GreaterThanEqualsToken : case SyntaxKind . InstanceOfKeyword : case SyntaxKind . InKeyword : case SyntaxKind . AsKeyword : case SyntaxKind . EqualsEqualsToken : case SyntaxKind . ExclamationEqualsToken : case SyntaxKind . EqualsEqualsEqualsToken : case SyntaxKind . ExclamationEqualsEqualsToken : case SyntaxKind . AmpersandToken : case SyntaxKind . CaretToken : case SyntaxKind . BarToken : case SyntaxKind . AmpersandAmpersandToken : case SyntaxKind . BarBarToken : case SyntaxKind . BarEqualsToken : case SyntaxKind . AmpersandEqualsToken : case SyntaxKind . CaretEqualsToken : case SyntaxKind . LessThanLessThanEqualsToken : case SyntaxKind . GreaterThanGreaterThanEqualsToken : case SyntaxKind . GreaterThanGreaterThanGreaterThanEqualsToken : case SyntaxKind . PlusEqualsToken : case SyntaxKind . MinusEqualsToken : case SyntaxKind . AsteriskEqualsToken : case SyntaxKind . SlashEqualsToken : case SyntaxKind . PercentEqualsToken : case SyntaxKind . EqualsToken : case SyntaxKind . CommaToken : case SyntaxKind . QuestionQuestionToken : case SyntaxKind . BarBarEqualsToken : case SyntaxKind . AmpersandAmpersandEqualsToken : case SyntaxKind . QuestionQuestionEqualsToken : return true ; default : return false ; } } function isPrefixUnaryExpressionOperatorToken ( token ) { switch ( token ) { case SyntaxKind . PlusToken : case SyntaxKind . MinusToken : case SyntaxKind . TildeToken : case SyntaxKind . ExclamationToken : case SyntaxKind . PlusPlusToken : case SyntaxKind . MinusMinusToken : return true ; default : return false ; } } function classFromKind ( token ) { if ( isKeyword ( token ) ) { return ClassificationType . keyword ; } else if ( isBinaryExpressionOperatorToken ( token ) || isPrefixUnaryExpressionOperatorToken ( token ) ) { return ClassificationType . operator ; } else if ( token >= SyntaxKind . FirstPunctuation && token <= SyntaxKind . LastPunctuation ) { return ClassificationType . punctuation ; } switch ( token ) { case SyntaxKind . NumericLiteral : return ClassificationType . numericLiteral ; case SyntaxKind . BigIntLiteral : return ClassificationType . bigintLiteral ; case SyntaxKind . StringLiteral : return ClassificationType . stringLiteral ; case SyntaxKind . RegularExpressionLiteral : return ClassificationType . regularExpressionLiteral ; case SyntaxKind . ConflictMarkerTrivia : case SyntaxKind . MultiLineCommentTrivia : case SyntaxKind . SingleLineCommentTrivia : return ClassificationType . comment ; case SyntaxKind . WhitespaceTrivia : case SyntaxKind . NewLineTrivia : return ClassificationType . whiteSpace ; case SyntaxKind . Identifier : default : if ( isTemplateLiteralKind ( token ) ) { return ClassificationType . stringLiteral ; } return ClassificationType . identifier ; } } export function getSemanticClassifications ( typeChecker , cancellationToken , sourceFile , classifiableNames , span ) { return convertClassificationsToSpans ( getEncodedSemanticClassifications ( typeChecker , cancellationToken , sourceFile , classifiableNames , span ) ) ; } function checkForClassificationCancellation ( cancellationToken , kind ) { switch ( kind ) { case SyntaxKind . ModuleDeclaration : case SyntaxKind . ClassDeclaration : case SyntaxKind . InterfaceDeclaration : case SyntaxKind . FunctionDeclaration : case SyntaxKind . ClassExpression : case SyntaxKind . FunctionExpression : case SyntaxKind . ArrowFunction : cancellationToken . throwIfCancellationRequested ( ) ; } } export function getEncodedSemanticClassifications ( typeChecker , cancellationToken , sourceFile , classifiableNames , span ) { const spans = [ ] ; sourceFile . forEachChild ( function cb ( node ) : void { if ( ! node || ! textSpanIntersectsWith ( span , node . pos , node . getFullWidth ( ) ) ) { return ; } checkForClassificationCancellation ( cancellationToken , node . kind ) ; if ( isIdentifier ( node ) && ! nodeIsMissing ( node ) && classifiableNames . has ( node . escapedText ) ) { const symbol = typeChecker . getSymbolAtLocation ( node ) ; const type = symbol && classifySymbol ( symbol , getMeaningFromLocation ( node ) , typeChecker ) ; if ( type ) { pushClassification ( node . getStart ( sourceFile ) , node . getEnd ( ) , type ) ; } } node . forEachChild ( cb ) ; } ) ; return { spans , endOfLineState : EndOfLineState . None } ; function pushClassification ( start , end , type ) { const length = end - start ; Debug . assert ( length > 0 , `Classification had non-positive length of ${ length }` ) ; spans . push ( start ) ; spans . push ( length ) ; spans . push ( type ) ; } } function classifySymbol ( symbol , meaningAtPosition , checker ) { const flags = symbol . getFlags ( ) ; if ( ( flags & SymbolFlags . Classifiable ) === SymbolFlags . None ) { return undefined ; } else if ( flags & SymbolFlags . Class ) { return ClassificationType . className ; } else if ( flags & SymbolFlags . Enum ) { return ClassificationType . enumName ; } else if ( flags & SymbolFlags . TypeAlias ) { return ClassificationType . typeAliasName ; } else if ( flags & SymbolFlags . Module ) { return meaningAtPosition & SemanticMeaning . Namespace || meaningAtPosition & SemanticMeaning . Value && hasValueSideModule ( symbol ) ? ClassificationType . moduleName : undefined ; } else if ( flags & SymbolFlags . Alias ) { return classifySymbol ( checker . getAliasedSymbol ( symbol ) , meaningAtPosition , checker ) ; } else if ( meaningAtPosition & SemanticMeaning . Type ) { return flags & SymbolFlags . Interface ? ClassificationType . interfaceName : flags & SymbolFlags . TypeParameter ? ClassificationType . typeParameterName : undefined ; } else { return undefined ; } } /** Returns true if there exists a module that introduces entities on the value side. */ function hasValueSideModule ( symbol ) { return some ( symbol . declarations , declaration => isModuleDeclaration ( declaration ) && getModuleInstanceState ( declaration ) === ModuleInstanceState . Instantiated ) ; } function getClassificationTypeName ( type ) { switch ( type ) { case ClassificationType . comment : return ClassificationTypeNames . comment ; case ClassificationType . identifier : return ClassificationTypeNames . identifier ; case ClassificationType . keyword : return ClassificationTypeNames . keyword ; case ClassificationType . numericLiteral : return ClassificationTypeNames . numericLiteral ; case ClassificationType . bigintLiteral : return ClassificationTypeNames . bigintLiteral ; case ClassificationType . operator : return ClassificationTypeNames . operator ; case ClassificationType . stringLiteral : return ClassificationTypeNames . stringLiteral ; case ClassificationType . whiteSpace : return ClassificationTypeNames . whiteSpace ; case ClassificationType . text : return ClassificationTypeNames . text ; case ClassificationType . punctuation : return ClassificationTypeNames . punctuation ; case ClassificationType . className : return ClassificationTypeNames . className ; case ClassificationType . enumName : return ClassificationTypeNames . enumName ; case ClassificationType . interfaceName : return ClassificationTypeNames . interfaceName ; case ClassificationType . moduleName : return ClassificationTypeNames . moduleName ; case ClassificationType . typeParameterName : return ClassificationTypeNames . typeParameterName ; case ClassificationType . typeAliasName : return ClassificationTypeNames . typeAliasName ; case ClassificationType . parameterName : return ClassificationTypeNames . parameterName ; case ClassificationType . docCommentTagName : return ClassificationTypeNames . docCommentTagName ; case ClassificationType . jsxOpenTagName : return ClassificationTypeNames . jsxOpenTagName ; case ClassificationType . jsxCloseTagName : return ClassificationTypeNames . jsxCloseTagName ; case ClassificationType . jsxSelfClosingTagName : return ClassificationTypeNames . jsxSelfClosingTagName ; case ClassificationType . jsxAttribute : return ClassificationTypeNames . jsxAttribute ; case ClassificationType . jsxText : return ClassificationTypeNames . jsxText ; case ClassificationType . jsxAttributeStringLiteralValue : return ClassificationTypeNames . jsxAttributeStringLiteralValue ; default : return undefined ! ; } } function convertClassificationsToSpans ( classifications ) { Debug . assert ( classifications . spans . length % 3 === 0 ) ; const dense = classifications . spans ; const result = [ ] ; for ( let i = 0 ; i < dense . length ; i += 3 ) { result . push ( { textSpan : createTextSpan ( dense [ i ] , dense [ i + 1 ] ) , classificationType : getClassificationTypeName ( dense [ i + 2 ] ) } ) ; } return result ; } export function getSyntacticClassifications ( cancellationToken , sourceFile , span ) { return convertClassificationsToSpans ( getEncodedSyntacticClassifications ( cancellationToken , sourceFile , span ) ) ; } export function getEncodedSyntacticClassifications ( cancellationToken , sourceFile , span ) { const spanStart = span . start ; const spanLength = span . length ; const triviaScanner = createScanner ( ScriptTarget . Latest , false , sourceFile . languageVariant , sourceFile . text ) ; const mergeConflictScanner = createScanner ( ScriptTarget . Latest , false , sourceFile . languageVariant , sourceFile . text ) ; const result = [ ] ; processElement ( sourceFile ) ; return { spans : result , endOfLineState : EndOfLineState . None } ; function pushClassification ( start , length , type ) { result . push ( start ) ; result . push ( length ) ; result . push ( type ) ; } function classifyLeadingTriviaAndGetTokenStart ( token ) { triviaScanner . setTextPos ( token . pos ) ; while ( true ) { const start = triviaScanner . getTextPos ( ) ; if ( ! couldStartTrivia ( sourceFile . text , start ) ) { return start ; } const kind = triviaScanner . scan ( ) ; const end = triviaScanner . getTextPos ( ) ; const width = end - start ; if ( ! isTrivia ( kind ) ) { return start ; } switch ( kind ) { case SyntaxKind . NewLineTrivia : case SyntaxKind . WhitespaceTrivia : continue ; case SyntaxKind . SingleLineCommentTrivia : case SyntaxKind . MultiLineCommentTrivia : classifyComment ( token , kind , start , width ) ; triviaScanner . setTextPos ( end ) ; continue ; case SyntaxKind . ConflictMarkerTrivia : const text = sourceFile . text ; const ch = text . charCodeAt ( start ) ; if ( ch === CharacterCodes . lessThan || ch === CharacterCodes . greaterThan ) { pushClassification ( start , width , ClassificationType . comment ) ; continue ; } Debug . assert ( ch === CharacterCodes . bar || ch === CharacterCodes . equals ) ; classifyDisabledMergeCode ( text , start , end ) ; break ; case SyntaxKind . ShebangTrivia : break ; default : Debug . assertNever ( kind ) ; } } } function classifyComment ( token , kind , start , width ) { if ( kind === SyntaxKind . MultiLineCommentTrivia ) { const docCommentAndDiagnostics = parseIsolatedJSDocComment ( sourceFile . text , start , width ) ; if ( docCommentAndDiagnostics && docCommentAndDiagnostics . jsDoc ) { setParent ( docCommentAndDiagnostics . jsDoc , token as HasJSDoc ) ; classifyJSDocComment ( docCommentAndDiagnostics . jsDoc ) ; return ; } } else if ( kind === SyntaxKind . SingleLineCommentTrivia ) { if ( tryClassifyTripleSlashComment ( start , width ) ) { return ; } } pushCommentRange ( start , width ) ; } function pushCommentRange ( start , width ) { pushClassification ( start , width , ClassificationType . comment ) ; } function classifyJSDocComment ( docComment ) { let pos = docComment . pos ; if ( docComment . tags ) { for ( const tag of docComment . tags ) { if ( tag . pos !== pos ) { pushCommentRange ( pos , tag . pos - pos ) ; } pushClassification ( tag . pos , 1 , ClassificationType . punctuation ) ; pushClassification ( tag . tagName . pos , tag . tagName . end - tag . tagName . pos , ClassificationType . docCommentTagName ) ; pos = tag . tagName . end ; let commentStart = tag . tagName . end ; switch ( tag . kind ) { case SyntaxKind . JSDocParameterTag : const param = tag as JSDocParameterTag ; processJSDocParameterTag ( param ) ; commentStart = param . isNameFirst && param . typeExpression ?. end || param . name . end ; break ; case SyntaxKind . JSDocPropertyTag : const prop = tag as JSDocPropertyTag ; commentStart = prop . isNameFirst && prop . typeExpression ?. end || prop . name . end ; break ; case SyntaxKind . JSDocTemplateTag : processJSDocTemplateTag ( tag as JSDocTemplateTag ) ; pos = tag . end ; commentStart = ( tag as JSDocTemplateTag ) . typeParameters . end ; break ; case SyntaxKind . JSDocTypedefTag : const type = tag as JSDocTypedefTag ; commentStart = type . typeExpression ?. kind === SyntaxKind . JSDocTypeExpression && type . fullName ?. end || type . typeExpression ?. end || commentStart ; break ; case SyntaxKind . JSDocCallbackTag : commentStart = ( tag as JSDocCallbackTag ) . typeExpression . end ; break ; case SyntaxKind . JSDocTypeTag : processElement ( ( tag as JSDocTypeTag ) . typeExpression ) ; pos = tag . end ; commentStart = ( tag as JSDocTypeTag ) . typeExpression . end ; break ; case SyntaxKind . JSDocThisTag : case SyntaxKind . JSDocEnumTag : commentStart = ( tag as JSDocThisTag | JSDocEnumTag ) . typeExpression . end ; break ; case SyntaxKind . JSDocReturnTag : processElement ( ( tag as JSDocReturnTag ) . typeExpression ) ; pos = tag . end ; commentStart = ( tag as JSDocReturnTag ) . typeExpression ?. end || commentStart ; break ; case SyntaxKind . JSDocSeeTag : commentStart = ( tag as JSDocSeeTag ) . name ?. end || commentStart ; break ; case SyntaxKind . JSDocAugmentsTag : case SyntaxKind . JSDocImplementsTag : commentStart = ( tag as JSDocImplementsTag | JSDocAugmentsTag ) . class . end ; break ; } if ( typeof tag . comment === "object" ) { pushCommentRange ( tag . comment . pos , tag . comment . end - tag . comment . pos ) ; } else if ( typeof tag . comment === "string" ) { pushCommentRange ( commentStart , tag . end - commentStart ) ; } } } if ( pos !== docComment . end ) { pushCommentRange ( pos , docComment . end - pos ) ; } return ; function processJSDocParameterTag ( tag ) { if ( tag . isNameFirst ) { pushCommentRange ( pos , tag . name . pos - pos ) ; pushClassification ( tag . name . pos , tag . name . end - tag . name . pos , ClassificationType . parameterName ) ; pos = tag . name . end ; } if ( tag . typeExpression ) { pushCommentRange ( pos , tag . typeExpression . pos - pos ) ; processElement ( tag . typeExpression ) ; pos = tag . typeExpression . end ; } if ( ! tag . isNameFirst ) { pushCommentRange ( pos , tag . name . pos - pos ) ; pushClassification ( tag . name . pos , tag . name . end - tag . name . pos , ClassificationType . parameterName ) ; pos = tag . name . end ; } } } function tryClassifyTripleSlashComment ( start , width ) { const tripleSlashXMLCommentRegEx = /^(\/\/\/\s*)(<)(?:(\S+)((?:[^/]|\/[^>])*)(\/>)?)?/im ; const attributeRegex = /(\s)(\S+)(\s*)(=)(\s*)('[^']+'|"[^"]+")/img ; const text = sourceFile . text . substr ( start , width ) ; const match = tripleSlashXMLCommentRegEx . exec ( text ) ; if ( ! match ) { return false ; } if ( ! match [ 3 ] || ! ( match [ 3 ] in commentPragmas ) ) { return false ; } let pos = start ; pushCommentRange ( pos , match [ 1 ] . length ) ; pos += match [ 1 ] . length ; pushClassification ( pos , match [ 2 ] . length , ClassificationType . punctuation ) ; pos += match [ 2 ] . length ; pushClassification ( pos , match [ 3 ] . length , ClassificationType . jsxSelfClosingTagName ) ; pos += match [ 3 ] . length ; const attrText = match [ 4 ] ; let attrPos = pos ; while ( true ) { const attrMatch = attributeRegex . exec ( attrText ) ; if ( ! attrMatch ) { break ; } const newAttrPos = pos + attrMatch . index + attrMatch [ 1 ] . length ; if ( newAttrPos > attrPos ) { pushCommentRange ( attrPos , newAttrPos - attrPos ) ; attrPos = newAttrPos ; } pushClassification ( attrPos , attrMatch [ 2 ] . length , ClassificationType . jsxAttribute ) ; attrPos += attrMatch [ 2 ] . length ; if ( attrMatch [ 3 ] . length ) { pushCommentRange ( attrPos , attrMatch [ 3 ] . length ) ; attrPos += attrMatch [ 3 ] . length ; } pushClassification ( attrPos , attrMatch [ 4 ] . length , ClassificationType . operator ) ; attrPos += attrMatch [ 4 ] . length ; if ( attrMatch [ 5 ] . length ) { pushCommentRange ( attrPos , attrMatch [ 5 ] . length ) ; attrPos += attrMatch [ 5 ] . length ; } pushClassification ( attrPos , attrMatch [ 6 ] . length , ClassificationType . jsxAttributeStringLiteralValue ) ; attrPos += attrMatch [ 6 ] . length ; } pos += match [ 4 ] . length ; if ( pos > attrPos ) { pushCommentRange ( attrPos , pos - attrPos ) ; } if ( match [ 5 ] ) { pushClassification ( pos , match [ 5 ] . length , ClassificationType . punctuation ) ; pos += match [ 5 ] . length ; } const end = start + width ; if ( pos < end ) { pushCommentRange ( pos , end - pos ) ; } return true ; } function processJSDocTemplateTag ( tag ) { for ( const child of tag . getChildren ( ) ) { processElement ( child ) ; } } function classifyDisabledMergeCode ( text , start , end ) { let i ; for ( i = start ; i < end ; i ++ ) { if ( isLineBreak ( text . charCodeAt ( i ) ) ) { break ; } } pushClassification ( start , i - start , ClassificationType . comment ) ; mergeConflictScanner . setTextPos ( i ) ; while ( mergeConflictScanner . getTextPos ( ) < end ) { classifyDisabledCodeToken ( ) ; } } function classifyDisabledCodeToken ( ) { const start = mergeConflictScanner . getTextPos ( ) ; const tokenKind = mergeConflictScanner . scan ( ) ; const end = mergeConflictScanner . getTextPos ( ) ; const type = classifyTokenType ( tokenKind ) ; if ( type ) { pushClassification ( start , end - start , type ) ; } } /**
         * Returns true if node should be treated as classified and no further processing is required.
         * False will mean that node is not classified and traverse routine should recurse into node contents.
         */ function tryClassifyNode ( node ) { if ( isJSDoc ( node ) ) { return true ; } if ( nodeIsMissing ( node ) ) { return true ; } const classifiedElementName = tryClassifyJsxElementName ( node ) ; if ( ! isToken ( node ) && node . kind !== SyntaxKind . JsxText && classifiedElementName === undefined ) { return false ; } const tokenStart = node . kind === SyntaxKind . JsxText ? node . pos : classifyLeadingTriviaAndGetTokenStart ( node ) ; const tokenWidth = node . end - tokenStart ; Debug . assert ( tokenWidth >= 0 ) ; if ( tokenWidth > 0 ) { const type = classifiedElementName || classifyTokenType ( node . kind , node ) ; if ( type ) { pushClassification ( tokenStart , tokenWidth , type ) ; } } return true ; } function tryClassifyJsxElementName ( token ) { switch ( token . parent && token . parent . kind ) { case SyntaxKind . JsxOpeningElement : if ( ( token . parent as JsxOpeningElement ) . tagName === token ) { return ClassificationType . jsxOpenTagName ; } break ; case SyntaxKind . JsxClosingElement : if ( ( token . parent as JsxClosingElement ) . tagName === token ) { return ClassificationType . jsxCloseTagName ; } break ; case SyntaxKind . JsxSelfClosingElement : if ( ( token . parent as JsxSelfClosingElement ) . tagName === token ) { return ClassificationType . jsxSelfClosingTagName ; } break ; case SyntaxKind . JsxAttribute : if ( ( token . parent as JsxAttribute ) . name === token ) { return ClassificationType . jsxAttribute ; } break ; } return undefined ; } function classifyTokenType ( tokenKind , token ? ) { if ( isKeyword ( tokenKind ) ) { return ClassificationType . keyword ; } if ( tokenKind === SyntaxKind . LessThanToken || tokenKind === SyntaxKind . GreaterThanToken ) { if ( token && getTypeArgumentOrTypeParameterList ( token . parent ) ) { return ClassificationType . punctuation ; } } if ( isPunctuation ( tokenKind ) ) { if ( token ) { const parent = token . parent ; if ( tokenKind === SyntaxKind . EqualsToken ) { if ( parent . kind === SyntaxKind . VariableDeclaration || parent . kind === SyntaxKind . PropertyDeclaration || parent . kind === SyntaxKind . Parameter || parent . kind === SyntaxKind . JsxAttribute ) { return ClassificationType . operator ; } } if ( parent . kind === SyntaxKind . BinaryExpression || parent . kind === SyntaxKind . PrefixUnaryExpression || parent . kind === SyntaxKind . PostfixUnaryExpression || parent . kind === SyntaxKind . ConditionalExpression ) { return ClassificationType . operator ; } } return ClassificationType . punctuation ; } else if ( tokenKind === SyntaxKind . NumericLiteral ) { return ClassificationType . numericLiteral ; } else if ( tokenKind === SyntaxKind . BigIntLiteral ) { return ClassificationType . bigintLiteral ; } else if ( tokenKind === SyntaxKind . StringLiteral ) { return token && token . parent . kind === SyntaxKind . JsxAttribute ? ClassificationType . jsxAttributeStringLiteralValue : ClassificationType . stringLiteral ; } else if ( tokenKind === SyntaxKind . RegularExpressionLiteral ) { return ClassificationType . stringLiteral ; } else if ( isTemplateLiteralKind ( tokenKind ) ) { return ClassificationType . stringLiteral ; } else if ( tokenKind === SyntaxKind . JsxText ) { return ClassificationType . jsxText ; } else if ( tokenKind === SyntaxKind . Identifier ) { if ( token ) { switch ( token . parent . kind ) { case SyntaxKind . ClassDeclaration : if ( ( token . parent as ClassDeclaration ) . name === token ) { return ClassificationType . className ; } return ; case SyntaxKind . TypeParameter : if ( ( token . parent as TypeParameterDeclaration ) . name === token ) { return ClassificationType . typeParameterName ; } return ; case SyntaxKind . InterfaceDeclaration : if ( ( token . parent as InterfaceDeclaration ) . name === token ) { return ClassificationType . interfaceName ; } return ; case SyntaxKind . EnumDeclaration : if ( ( token . parent as EnumDeclaration ) . name === token ) { return ClassificationType . enumName ; } return ; case SyntaxKind . ModuleDeclaration : if ( ( token . parent as ModuleDeclaration ) . name === token ) { return ClassificationType . moduleName ; } return ; case SyntaxKind . Parameter : if ( ( token . parent as ParameterDeclaration ) . name === token ) { return isThisIdentifier ( token ) ? ClassificationType . keyword : ClassificationType . parameterName ; } return ; } if ( isConstTypeReference ( token . parent ) ) { return ClassificationType . keyword ; } } return ClassificationType . identifier ; } } function processElement ( element ) { if ( ! element ) { return ; } if ( decodedTextSpanIntersectsWith ( spanStart , spanLength , element . pos , element . getFullWidth ( ) ) ) { checkForClassificationCancellation ( cancellationToken , element . kind ) ; for ( const child of element . getChildren ( sourceFile ) ) { if ( ! tryClassifyNode ( child ) ) { processElement ( child ) ; } } } } } }