import * as ts from 'typescript/lib/tsserverlibrary' ; import TemplateSourceHelper from './template-source-helper' ; import ScriptSourceHelper from './script-source-helper' ; import { isTaggedLiteral , isTagged , relative } from './nodes' ; import TemplateContext from './template-context' ; import TemplateSettings from './template-settings' ; import Logger from './logger' ; import { memoize } from './util/memoize' ; class PlaceholderSubstituter { public static replacePlaceholders ( typescript , settings , node ) { const literalContents = node . getText ( ) . slice ( 1 , - 1 ) ; if ( node . kind === typescript . SyntaxKind . NoSubstitutionTemplateLiteral ) { return literalContents ; } return PlaceholderSubstituter . getSubstitutions ( settings , literalContents , PlaceholderSubstituter . getPlaceholderSpans ( node ) ) ; } private static getPlaceholderSpans ( node ) { const spans = [ ] ; const stringStart = node . getStart ( ) + 1 ; let nodeStart = node . head . end - stringStart - 2 ; for ( const child of node . templateSpans . map ( x => x . literal ) ) { const start = child . getStart ( ) - stringStart + 1 ; spans . push ( { start : nodeStart , end : start } ) ; nodeStart = child . getEnd ( ) - stringStart - 2 ; } return spans ; } private static getSubstitutions ( settings , contents , locations ) { if ( settings . getSubstitutions ) { return settings . getSubstitutions ( contents , locations ) ; } const parts = [ ] ; let lastIndex = 0 ; for ( const span of locations ) { parts . push ( contents . slice ( lastIndex , span . start ) ) ; parts . push ( this . getSubstitution ( settings , contents , span . start , span . end ) ) ; lastIndex = span . end ; } parts . push ( contents . slice ( lastIndex ) ) ; return parts . join ( '' ) ; } private static getSubstitution ( settings , templateString , start , end ) { return settings . getSubstitution ? settings . getSubstitution ( templateString , start , end ) : 'x' . repeat ( end - start ) ; } } class StandardTemplateContext implements TemplateContext { constructor ( public readonly typescript , public readonly fileName , public readonly node , private readonly helper , private readonly templateSettings ) { } public toOffset ( position ) { const docOffset = this . helper . getOffset ( this . fileName , position . line + this . stringBodyPosition . line , position . line === 0 ? this . stringBodyPosition . character + position . character : position . character ) ; return docOffset - this . stringBodyOffset ; } public toPosition ( offset ) { const docPosition = this . helper . getLineAndChar ( this . fileName , this . stringBodyOffset + offset ) ; return relative ( this . stringBodyPosition , docPosition ) ; } @ memoize private get stringBodyOffset ( ) : number { return this . node . getStart ( ) + 1 ; } @ memoize private get stringBodyPosition ( ) : ts . LineAndCharacter { return this . helper . getLineAndChar ( this . fileName , this . stringBodyOffset ) ; } @ memoize public get text ( ) : string { return PlaceholderSubstituter . replacePlaceholders ( this . typescript , this . templateSettings , this . node ) ; } @ memoize public get rawText ( ) { return this . node . getText ( ) . slice ( 1 , - 1 ) ; } } export default class StandardTemplateSourceHelper implements TemplateSourceHelper { constructor ( private readonly typescript , private readonly templateStringSettings , private readonly helper , _logger ) { } public getTemplate ( fileName , position ) { const node = this . getValidTemplateNode ( this . templateStringSettings , this . helper . getNode ( fileName , position ) ) ; if ( ! node ) { return undefined ; } if ( position <= node . pos ) { return undefined ; } if ( node . kind === this . typescript . SyntaxKind . TemplateExpression ) { let start = node . head . end ; for ( const child of node . templateSpans . map ( x => x . literal ) ) { const nextStart = child . getStart ( ) ; if ( position >= start && position <= nextStart ) { return undefined ; } start = child . getEnd ( ) ; } } return new StandardTemplateContext ( this . typescript , fileName , node , this . helper , this . templateStringSettings ) ; } public getAllTemplates ( fileName ) { const out = [ ] ; for ( const node of this . helper . getAllNodes ( fileName , n => this . getValidTemplateNode ( this . templateStringSettings , n ) !== undefined ) ) { const validNode = this . getValidTemplateNode ( this . templateStringSettings , node ) ; if ( validNode ) { out . push ( new StandardTemplateContext ( this . typescript , fileName , validNode , this . helper , this . templateStringSettings ) ) ; } } return out ; } public getRelativePosition ( context , offset ) { const baseLC = this . helper . getLineAndChar ( context . fileName , context . node . getStart ( ) + 1 ) ; const cursorLC = this . helper . getLineAndChar ( context . fileName , offset ) ; return relative ( baseLC , cursorLC ) ; } private getValidTemplateNode ( templateStringSettings , node ) { if ( ! node ) { return undefined ; } switch ( node . kind ) { case this . typescript . SyntaxKind . TaggedTemplateExpression : if ( isTagged ( node as ts . TaggedTemplateExpression , templateStringSettings . tags ) ) { return ( node as ts . TaggedTemplateExpression ) . template ; } return undefined ; case this . typescript . SyntaxKind . NoSubstitutionTemplateLiteral : if ( isTaggedLiteral ( this . typescript , node as ts . NoSubstitutionTemplateLiteral , templateStringSettings . tags ) ) { return node as ts . NoSubstitutionTemplateLiteral ; } return undefined ; case this . typescript . SyntaxKind . TemplateHead : if ( templateStringSettings . enableForStringWithSubstitutions && node . parent && node . parent . parent ) { return this . getValidTemplateNode ( templateStringSettings , node . parent . parent ) ; } return undefined ; case this . typescript . SyntaxKind . TemplateMiddle : case this . typescript . SyntaxKind . TemplateTail : if ( templateStringSettings . enableForStringWithSubstitutions && node . parent && node . parent . parent ) { return this . getValidTemplateNode ( templateStringSettings , node . parent . parent . parent ) ; } return undefined ; default : return undefined ; } } }