import * as ts from 'typescript/lib/tsserverlibrary' ; import Logger from './logger' ; import TemplateLanguageService from './template-language-service' ; import TemplateContext from './template-context' ; import TemplateSourceHelper from './template-source-helper' ; type LanguageServiceMethodWrapper < K extends keyof ts . LanguageService > = ( delegate : ts . LanguageService [ K ] , info ? : ts . server . PluginCreateInfo ) => ts . LanguageService [ K ] ; export default class TemplateLanguageServiceProxy { private readonly _wrappers : Array < { name : keyof ts . LanguageService ; wrapper : LanguageServiceMethodWrapper < any > ; } > = [ ] ; constructor ( private readonly typescript , private readonly sourceHelper , private readonly templateStringService , _logger ) { this . tryAdaptGetCompletionsAtPosition ( ) ; this . tryAdaptGetCompletionEntryDetails ( ) ; this . tryAdaptGetQuickInfoAtPosition ( ) ; this . tryAdaptGetSemanticDiagnostics ( ) ; this . tryAdaptGetSyntaxDiagnostics ( ) ; this . tryAdaptGetFormattingEditsForRange ( ) ; this . tryAdaptGetCodeFixesAtPosition ( ) ; this . tryAdaptGetSupportedCodeFixes ( ) ; this . tryAdaptGetDefinitionAtPosition ( ) ; this . tryAdaptGetSignatureHelpItemsAtPosition ( ) ; this . tryAdaptGetOutliningSpans ( ) ; this . tryAdaptGetReferencesAtPosition ( ) ; this . tryAdaptGetJsxClosingTagAtPosition ( ) ; } public decorate ( languageService ) { const intercept = Object . create ( null ) ; for ( const { name , wrapper } of this . _wrappers ) { ( intercept [ name ] as any ) = wrapper ( languageService [ name ] ! . bind ( languageService ) ) ; } return new Proxy ( languageService , { get : ( target , property ) => { return ( intercept as any ) [ property ] || target [ property ] ; } , } ) ; } private tryAdaptGetSyntaxDiagnostics ( ) { if ( ! this . templateStringService . getSyntacticDiagnostics ) { return ; } const call = this . templateStringService . getSyntacticDiagnostics . bind ( this . templateStringService ) ; this . wrap ( 'getSyntacticDiagnostics' , delegate => ( fileName ) => { return this . adaptDiagnosticsCall ( delegate , call , fileName ) as ts . DiagnosticWithLocation [ ] ; } ) ; } private tryAdaptGetSemanticDiagnostics ( ) { if ( ! this . templateStringService . getSemanticDiagnostics ) { return ; } const call = this . templateStringService . getSemanticDiagnostics . bind ( this . templateStringService ) ; this . wrap ( 'getSemanticDiagnostics' , delegate => ( fileName ) => { return this . adaptDiagnosticsCall ( delegate , call , fileName ) ; } ) ; } private tryAdaptGetQuickInfoAtPosition ( ) { if ( ! this . templateStringService . getQuickInfoAtPosition ) { return ; } this . wrap ( 'getQuickInfoAtPosition' , delegate => ( fileName , position ) : ts . QuickInfo | undefined => { const context = this . sourceHelper . getTemplate ( fileName , position ) ; if ( ! context ) { return delegate ( fileName , position ) ; } const quickInfo = this . templateStringService . getQuickInfoAtPosition ! ( context , this . sourceHelper . getRelativePosition ( context , position ) ) ; if ( quickInfo ) { return Object . assign ( { } , quickInfo , { textSpan : { start : quickInfo . textSpan . start + context . node . getStart ( ) + 1 , length : quickInfo . textSpan . length , } , } ) ; } return delegate ( fileName , position ) ; } ) ; } private tryAdaptGetCompletionsAtPosition ( ) { if ( ! this . templateStringService . getCompletionsAtPosition ) { return ; } this . wrap ( 'getCompletionsAtPosition' , delegate => ( fileName , position , ... rest ) => { const context = this . sourceHelper . getTemplate ( fileName , position ) ; if ( ! context ) { return ( delegate as any ) ( fileName , position , ... rest ) ; } return this . translateCompletionInfo ( context , this . templateStringService . getCompletionsAtPosition ! ( context , this . sourceHelper . getRelativePosition ( context , position ) ) ) ; } ) ; } private tryAdaptGetCompletionEntryDetails ( ) { if ( ! this . templateStringService . getCompletionEntryDetails ) { return ; } this . wrap ( 'getCompletionEntryDetails' , delegate => ( fileName , position , name , ... rest ) => { const context = this . sourceHelper . getTemplate ( fileName , position ) ; if ( ! context ) { return ( delegate as any ) ( fileName , position , name , ... rest ) ; } return this . templateStringService . getCompletionEntryDetails ! ( context , this . sourceHelper . getRelativePosition ( context , position ) , name ) ; } ) ; } private tryAdaptGetFormattingEditsForRange ( ) { if ( ! this . templateStringService . getFormattingEditsForRange ) { return ; } this . wrap ( 'getFormattingEditsForRange' , delegate => ( fileName , start , end , options ) => { const templateEdits = [ ] ; for ( const template of this . sourceHelper . getAllTemplates ( fileName ) ) { const nodeStart = template . node . getStart ( ) + 1 ; const nodeEnd = template . node . getEnd ( ) - 1 ; if ( end < nodeStart || start > nodeEnd ) { continue ; } const templateStart = Math . max ( 0 , start - nodeStart ) ; const templateEnd = Math . min ( nodeEnd - nodeStart , end - nodeStart ) ; for ( const change of this . templateStringService . getFormattingEditsForRange ! ( template , templateStart , templateEnd , options ) ) { templateEdits . push ( this . translateTextChange ( template , change ) ) ; } } return [ ... delegate ( fileName , start , end , options ) , ... templateEdits , ] ; } ) ; } private tryAdaptGetCodeFixesAtPosition ( ) { if ( ! this . templateStringService . getCodeFixesAtPosition ) { return ; } this . wrap ( 'getCodeFixesAtPosition' , delegate => ( fileName , start , end , errorCodes , options , preferences ) => { const templateActions = [ ] ; for ( const template of this . sourceHelper . getAllTemplates ( fileName ) ) { const nodeStart = template . node . getStart ( ) + 1 ; const nodeEnd = template . node . getEnd ( ) - 1 ; if ( end < nodeStart || start > nodeEnd ) { continue ; } const templateStart = Math . max ( 0 , start - nodeStart ) ; const templateEnd = Math . min ( nodeEnd - nodeStart , end - nodeStart ) ; for ( const codeAction of this . templateStringService . getCodeFixesAtPosition ! ( template , templateStart , templateEnd , errorCodes , options ) ) { templateActions . push ( this . translateCodeAction ( template , codeAction ) ) ; } } return [ ... delegate ( fileName , start , end , errorCodes , options , preferences ) , ... templateActions , ] ; } ) ; } private tryAdaptGetSupportedCodeFixes ( ) { if ( ! this . templateStringService . getSupportedCodeFixes ) { return ; } const delegate = this . typescript . getSupportedCodeFixes . bind ( this . typescript ) ; this . typescript . getSupportedCodeFixes = ( ) => { return [ ... delegate ( ) , ... this . templateStringService . getSupportedCodeFixes ! ( ) . map ( x => '' + x ) , ] ; } ; } private tryAdaptGetDefinitionAtPosition ( ) { if ( ! this . templateStringService . getDefinitionAtPosition ) { return ; } this . wrap ( 'getDefinitionAtPosition' , delegate => ( fileName , position , ... rest ) => { const context = this . sourceHelper . getTemplate ( fileName , position ) ; if ( context ) { const definition = this . templateStringService . getDefinitionAtPosition ! ( context , this . sourceHelper . getRelativePosition ( context , position ) ) ; return definition ? definition . map ( def => this . translateDefinitionInfo ( context , def ) ) : undefined ; } return ( delegate as any ) ( fileName , position , ... rest ) ; } ) ; } private tryAdaptGetSignatureHelpItemsAtPosition ( ) { if ( ! this . templateStringService . getSignatureHelpItemsAtPosition ) { return ; } this . wrap ( 'getSignatureHelpItems' , delegate => ( fileName , position , ... rest ) => { const context = this . sourceHelper . getTemplate ( fileName , position ) ; if ( ! context ) { return ( delegate as any ) ( fileName , position , ... rest ) ; } const signatureHelp = this . templateStringService . getSignatureHelpItemsAtPosition ! ( context , this . sourceHelper . getRelativePosition ( context , position ) ) ; return signatureHelp ? this . translateSignatureHelpItems ( context , signatureHelp ) : undefined ; } ) ; } private tryAdaptGetOutliningSpans ( ) { if ( ! this . templateStringService . getOutliningSpans ) { return ; } this . wrap ( 'getOutliningSpans' , delegate => ( fileName ) => { const templateSpans = [ ] ; for ( const template of this . sourceHelper . getAllTemplates ( fileName ) ) { for ( const span of this . templateStringService . getOutliningSpans ! ( template ) ) { templateSpans . push ( this . translateOutliningSpan ( template , span ) ) ; } } return [ ... delegate ( fileName ) , ... templateSpans , ] ; } ) ; } private tryAdaptGetReferencesAtPosition ( ) { if ( ! this . templateStringService . getReferencesAtPosition ) { return ; } this . wrap ( 'findReferences' , delegate => ( fileName , position , ... rest ) : ts . ReferencedSymbol [ ] | undefined => { const context = this . sourceHelper . getTemplate ( fileName , position ) ; if ( context ) { const references = this . templateStringService . getReferencesAtPosition ! ( context , this . sourceHelper . getRelativePosition ( context , position ) ) ; if ( references ) { return [ { definition : { containerKind : this . typescript . ScriptElementKind . string , containerName : '' , displayParts : [ ] , fileName , kind : this . typescript . ScriptElementKind . string , name : '' , textSpan : { start : position , length : 0 } , } , references : references . map ( ref => this . translateReferenceEntry ( context , ref ) ) , } ] ; } return undefined ; } return ( delegate as any ) ( fileName , position , ... rest ) ; } ) ; } private tryAdaptGetJsxClosingTagAtPosition ( ) { if ( ! this . templateStringService . getJsxClosingTagAtPosition ) { return ; } this . wrap ( 'getJsxClosingTagAtPosition' , delegate => ( fileName , position , ... rest ) : ts . JsxClosingTagInfo | undefined => { const context = this . sourceHelper . getTemplate ( fileName , position ) ; if ( context ) { const closing = this . templateStringService . getJsxClosingTagAtPosition ! ( context , this . sourceHelper . getRelativePosition ( context , position ) ) ; if ( closing ) { return closing ; } } return ( delegate as any ) ( fileName , position , ... rest ) ; } ) ; } private wrap < K extends keyof ts . LanguageService > ( name , wrapper ) { this . _wrappers . push ( { name , wrapper } ) ; return this ; } private adaptDiagnosticsCall ( delegate , implementation , fileName ) { const baseDiagnostics = delegate ( fileName ) ; const templateDiagnostics = [ ] ; for ( const context of this . sourceHelper . getAllTemplates ( fileName ) ) { for ( const diagnostic of implementation ( context ) ) { templateDiagnostics . push ( { ... diagnostic , start : context . node . getStart ( ) + 1 + ( diagnostic . start || 0 ) , } ) ; } } return [ ... baseDiagnostics , ... templateDiagnostics ] ; } private translateCompletionInfo ( context , info ) { return { ... info , entries : info . entries . map ( entry => this . translateCompletionEntry ( context , entry ) ) , } ; } private translateCompletionEntry ( context , entry ) { return { ... entry , replacementSpan : entry . replacementSpan ? this . translateTextSpan ( context , entry . replacementSpan ) : undefined , } ; } private translateTextChange ( context , textChange ) { return { ... textChange , span : this . translateTextSpan ( context , textChange . span ) , } ; } private translateFileTextChange ( context , changes ) { return { fileName : changes . fileName , textChanges : changes . textChanges . map ( textChange => this . translateTextChange ( context , textChange ) ) , } ; } private translateCodeAction ( context , action ) { return { ... action , fixName : ( action as ts . CodeFixAction ) . fixName || '' , changes : action . changes . map ( change => this . translateFileTextChange ( context , change ) ) , } ; } private translateSignatureHelpItems ( context , signatureHelp ) { return { ... signatureHelp , applicableSpan : this . translateTextSpan ( context , signatureHelp . applicableSpan ) , } ; } private translateOutliningSpan ( context , span ) { return { ... span , textSpan : this . translateTextSpan ( context , span . textSpan ) , hintSpan : this . translateTextSpan ( context , span . hintSpan ) , } ; } private translateTextSpan ( context , span ) { return { start : context . node . getStart ( ) + 1 + span . start , length : span . length , } ; } private translateDefinitionInfo ( context , definition ) { return { ... definition , fileName : context . fileName , textSpan : this . translateTextSpan ( context , definition . textSpan ) , } ; } private translateReferenceEntry ( context , entry ) { return { ... entry , fileName : context . fileName , textSpan : this . translateTextSpan ( context , entry . textSpan ) , } ; } }