@@ -298,7 +298,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
298298 userPreferences
299299 )
300300 . concat (
301- this . createElementEventHandlerQuickFix (
301+ await this . getSvelteQuickFixes (
302302 lang ,
303303 document ,
304304 cannotFoundNameDiagnostic ,
@@ -513,37 +513,25 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
513513 ) ;
514514 }
515515
516- /**
517- * Workaround for TypesScript doesn't provide a quick fix if the signature is typed as union type, like `(() => void) | null`
518- * We can remove this once TypesScript doesn't have this limitation.
519- */
520- private createElementEventHandlerQuickFix (
516+ private async getSvelteQuickFixes (
521517 lang : ts . LanguageService ,
522518 document : Document ,
523519 diagnostics : Diagnostic [ ] ,
524520 tsDoc : DocumentSnapshot ,
525521 formatCodeBasis : FormatCodeBasis ,
526522 userPreferences : ts . UserPreferences
527- ) : ts . CodeFixAction [ ] {
523+ ) : Promise < ts . CodeFixAction [ ] > {
528524 const program = lang . getProgram ( ) ;
529525 const sourceFile = program ?. getSourceFile ( tsDoc . filePath ) ;
530526 if ( ! program || ! sourceFile ) {
531527 return [ ] ;
532528 }
533529
534530 const typeChecker = program . getTypeChecker ( ) ;
535- const result : ts . CodeFixAction [ ] = [ ] ;
531+ const results : ts . CodeFixAction [ ] = [ ] ;
536532 const quote = getQuotePreference ( sourceFile , userPreferences ) ;
537533
538534 for ( const diagnostic of diagnostics ) {
539- const htmlNode = document . html . findNodeAt ( document . offsetAt ( diagnostic . range . start ) ) ;
540- if (
541- ! htmlNode . attributes ||
542- ! Object . keys ( htmlNode . attributes ) . some ( ( attr ) => attr . startsWith ( 'on:' ) )
543- ) {
544- continue ;
545- }
546-
547535 const start = tsDoc . offsetAt ( tsDoc . getGeneratedPosition ( diagnostic . range . start ) ) ;
548536 const end = tsDoc . offsetAt ( tsDoc . getGeneratedPosition ( diagnostic . range . end ) ) ;
549537
@@ -553,66 +541,165 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
553541 ts . isIdentifier
554542 ) ;
555543
556- const type = identifier && typeChecker . getContextualType ( identifier ) ;
557-
558- // if it's not union typescript should be able to do it. no need to enhance
559- if ( ! type || ! type . isUnion ( ) ) {
544+ if ( ! identifier ) {
560545 continue ;
561546 }
562547
563- const nonNullable = type . getNonNullableType ( ) ;
548+ const isQuickFixTargetTargetStore =
549+ identifier ?. escapedText . toString ( ) . startsWith ( '$' ) && diagnostic . code === 2304 ;
550+ const isQuickFixTargetEventHandler = this . isQuickFixForEventHandler (
551+ document ,
552+ diagnostic
553+ ) ;
564554
565- if (
566- ! (
567- nonNullable . flags & ts . TypeFlags . Object &&
568- ( nonNullable as ts . ObjectType ) . objectFlags & ts . ObjectFlags . Anonymous
569- )
570- ) {
571- continue ;
555+ if ( isQuickFixTargetTargetStore ) {
556+ results . push (
557+ ...( await this . getSvelteStoreQuickFixes (
558+ identifier ,
559+ lang ,
560+ document ,
561+ tsDoc ,
562+ userPreferences
563+ ) )
564+ ) ;
572565 }
573566
574- const signature = typeChecker . getSignaturesOfType (
575- nonNullable ,
576- ts . SignatureKind . Call
577- ) [ 0 ] ;
567+ if ( isQuickFixTargetEventHandler ) {
568+ results . push (
569+ ...this . getEventHandlerQuickFixes (
570+ identifier ,
571+ tsDoc ,
572+ typeChecker ,
573+ quote ,
574+ formatCodeBasis
575+ )
576+ ) ;
577+ }
578+ }
578579
579- const parameters = signature . parameters . map ( ( p ) => {
580- const declaration = p . valueDeclaration ?? p . declarations ?. [ 0 ] ;
581- const typeString = declaration
582- ? typeChecker . typeToString (
583- typeChecker . getTypeOfSymbolAtLocation ( p , declaration )
584- )
585- : '' ;
580+ return results ;
581+ }
586582
587- return { name : p . name , typeString } ;
588- } ) ;
589- const returnType = typeChecker . typeToString ( signature . getReturnType ( ) ) ;
590- const useJsDoc =
591- tsDoc . scriptKind === ts . ScriptKind . JS || tsDoc . scriptKind === ts . ScriptKind . JSX ;
592- const parametersText = (
593- useJsDoc
594- ? parameters . map ( ( p ) => p . name )
595- : parameters . map ( ( p ) => p . name + ( p . typeString ? ': ' + p . typeString : '' ) )
596- ) . join ( ', ' ) ;
597-
598- const jsDoc = useJsDoc
599- ? [ '/**' , ...parameters . map ( ( p ) => ` * @param {${ p . typeString } } ${ p . name } ` ) , ' */' ]
600- : [ ] ;
601-
602- const newText = [
603- ...jsDoc ,
604- `function ${ identifier . text } (${ parametersText } )${
605- useJsDoc ? '' : ': ' + returnType
606- } {`,
607- formatCodeBasis . indent +
608- `throw new Error(${ quote } Function not implemented.${ quote } )` +
609- formatCodeBasis . semi ,
610- '}'
611- ]
612- . map ( ( line ) => formatCodeBasis . baseIndent + line + formatCodeBasis . newLine )
613- . join ( '' ) ;
614-
615- result . push ( {
583+ private async getSvelteStoreQuickFixes (
584+ identifier : ts . Identifier ,
585+ lang : ts . LanguageService ,
586+ document : Document ,
587+ tsDoc : DocumentSnapshot ,
588+ userPreferences : ts . UserPreferences
589+ ) : Promise < ts . CodeFixAction [ ] > {
590+ const storeIdentifier = identifier . escapedText . toString ( ) . substring ( 1 ) ;
591+ const formatCodeSettings = await this . configManager . getFormatCodeSettingsForFile (
592+ document ,
593+ tsDoc . scriptKind
594+ ) ;
595+ const completion = lang . getCompletionsAtPosition (
596+ tsDoc . filePath ,
597+ 0 ,
598+ userPreferences ,
599+ formatCodeSettings
600+ ) ;
601+
602+ if ( ! completion ) {
603+ return [ ] ;
604+ }
605+
606+ const toFix = ( c : ts . CompletionEntry ) =>
607+ lang
608+ . getCompletionEntryDetails (
609+ tsDoc . filePath ,
610+ 0 ,
611+ c . name ,
612+ formatCodeSettings ,
613+ c . source ,
614+ userPreferences ,
615+ c . data
616+ )
617+ ?. codeActions ?. map ( ( a ) => ( {
618+ ...a ,
619+ changes : a . changes . map ( ( change ) => {
620+ return {
621+ ...change ,
622+ textChanges : change . textChanges . map ( ( textChange ) => {
623+ // For some reason, TS sometimes adds the `type` modifier. Remove it.
624+ return {
625+ ...textChange ,
626+ newText : textChange . newText . replace ( ' type ' , ' ' )
627+ } ;
628+ } )
629+ } ;
630+ } ) ,
631+ fixName : 'import'
632+ } ) ) ?? [ ] ;
633+
634+ return flatten ( completion . entries . filter ( ( c ) => c . name === storeIdentifier ) . map ( toFix ) ) ;
635+ }
636+
637+ /**
638+ * Workaround for TypeScript doesn't provide a quick fix if the signature is typed as union type, like `(() => void) | null`
639+ * We can remove this once TypeScript doesn't have this limitation.
640+ */
641+ private getEventHandlerQuickFixes (
642+ identifier : ts . Identifier ,
643+ tsDoc : DocumentSnapshot ,
644+ typeChecker : ts . TypeChecker ,
645+ quote : string ,
646+ formatCodeBasis : FormatCodeBasis
647+ ) : ts . CodeFixAction [ ] {
648+ const type = identifier && typeChecker . getContextualType ( identifier ) ;
649+
650+ // if it's not union typescript should be able to do it. no need to enhance
651+ if ( ! type || ! type . isUnion ( ) ) {
652+ return [ ] ;
653+ }
654+
655+ const nonNullable = type . getNonNullableType ( ) ;
656+
657+ if (
658+ ! (
659+ nonNullable . flags & ts . TypeFlags . Object &&
660+ ( nonNullable as ts . ObjectType ) . objectFlags & ts . ObjectFlags . Anonymous
661+ )
662+ ) {
663+ return [ ] ;
664+ }
665+
666+ const signature = typeChecker . getSignaturesOfType ( nonNullable , ts . SignatureKind . Call ) [ 0 ] ;
667+
668+ const parameters = signature . parameters . map ( ( p ) => {
669+ const declaration = p . valueDeclaration ?? p . declarations ?. [ 0 ] ;
670+ const typeString = declaration
671+ ? typeChecker . typeToString ( typeChecker . getTypeOfSymbolAtLocation ( p , declaration ) )
672+ : '' ;
673+
674+ return { name : p . name , typeString } ;
675+ } ) ;
676+
677+ const returnType = typeChecker . typeToString ( signature . getReturnType ( ) ) ;
678+ const useJsDoc =
679+ tsDoc . scriptKind === ts . ScriptKind . JS || tsDoc . scriptKind === ts . ScriptKind . JSX ;
680+ const parametersText = (
681+ useJsDoc
682+ ? parameters . map ( ( p ) => p . name )
683+ : parameters . map ( ( p ) => p . name + ( p . typeString ? ': ' + p . typeString : '' ) )
684+ ) . join ( ', ' ) ;
685+
686+ const jsDoc = useJsDoc
687+ ? [ '/**' , ...parameters . map ( ( p ) => ` * @param {${ p . typeString } } ${ p . name } ` ) , ' */' ]
688+ : [ ] ;
689+
690+ const newText = [
691+ ...jsDoc ,
692+ `function ${ identifier . text } (${ parametersText } )${ useJsDoc ? '' : ': ' + returnType } {` ,
693+ formatCodeBasis . indent +
694+ `throw new Error(${ quote } Function not implemented.${ quote } )` +
695+ formatCodeBasis . semi ,
696+ '}'
697+ ]
698+ . map ( ( line ) => formatCodeBasis . baseIndent + line + formatCodeBasis . newLine )
699+ . join ( '' ) ;
700+
701+ return [
702+ {
616703 description : `Add missing function declaration '${ identifier . text } '` ,
617704 fixName : 'fixMissingFunctionDeclaration' ,
618705 changes : [
@@ -626,10 +713,20 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
626713 ]
627714 }
628715 ]
629- } ) ;
716+ }
717+ ] ;
718+ }
719+
720+ private isQuickFixForEventHandler ( document : Document , diagnostic : Diagnostic ) {
721+ const htmlNode = document . html . findNodeAt ( document . offsetAt ( diagnostic . range . start ) ) ;
722+ if (
723+ ! htmlNode . attributes ||
724+ ! Object . keys ( htmlNode . attributes ) . some ( ( attr ) => attr . startsWith ( 'on:' ) )
725+ ) {
726+ return false ;
630727 }
631728
632- return result ;
729+ return true ;
633730 }
634731
635732 private async getApplicableRefactors (
0 commit comments