@@ -16,50 +16,104 @@ namespace ts.codefix {
16
16
registerCodeFix ( {
17
17
errorCodes,
18
18
getCodeActions ( context ) {
19
- const { sourceFile, span, program } = context ;
20
- const related = getDiagnosticRelatedInfo ( program , sourceFile , span ) ;
21
- if ( ! related ) {
22
- return ;
23
- }
24
- const changes = textChanges . ChangeTracker . with ( context , t => addMissingConstraint ( t , related ) ) ;
19
+ const { sourceFile, span, program, preferences, host } = context ;
20
+ const info = getInfo ( program , sourceFile , span ) ;
21
+ if ( info === undefined ) return ;
22
+
23
+ const changes = textChanges . ChangeTracker . with ( context , t => addMissingConstraint ( t , program , preferences , host , sourceFile , info ) ) ;
25
24
return [ createCodeFixAction ( fixId , changes , Diagnostics . Add_extends_constraint , fixId , Diagnostics . Add_extends_constraint_to_all_type_parameters ) ] ;
26
25
} ,
27
26
fixIds : [ fixId ] ,
28
- getAllCodeActions : context => codeFixAll ( context , errorCodes , ( changes , diag ) => {
29
- const info = getDiagnosticRelatedInfo ( context . program , context . sourceFile , diag ) ;
30
- if ( ! info ) return ;
31
- return addMissingConstraint ( changes , info ) ;
32
- } ) ,
27
+ getAllCodeActions : context => {
28
+ const { program, preferences, host } = context ;
29
+ const seen = new Map < string , true > ( ) ;
30
+
31
+ return createCombinedCodeActions ( textChanges . ChangeTracker . with ( context , changes => {
32
+ eachDiagnostic ( context , errorCodes , diag => {
33
+ const info = getInfo ( program , diag . file , createTextSpan ( diag . start , diag . length ) ) ;
34
+ if ( info ) {
35
+ const id = getNodeId ( info . declaration ) + "#" + info . token . getText ( ) ;
36
+ if ( addToSeen ( seen , id ) ) {
37
+ return addMissingConstraint ( changes , program , preferences , host , diag . file , info ) ;
38
+ }
39
+ }
40
+ return undefined ;
41
+ } ) ;
42
+ } ) ) ;
43
+ }
33
44
} ) ;
34
45
35
- function getDiagnosticRelatedInfo ( program : Program , sourceFile : SourceFile , span : TextSpan ) {
46
+ interface Info {
47
+ constraint : Type | string ;
48
+ declaration : TypeParameterDeclaration ;
49
+ token : Node ;
50
+ }
51
+
52
+ function getInfo ( program : Program , sourceFile : SourceFile , span : TextSpan ) : Info | undefined {
36
53
const diag = find ( program . getSemanticDiagnostics ( sourceFile ) , diag => diag . start === span . start && diag . length === span . length ) ;
37
- if ( ! diag || ! diag . relatedInformation ) return ;
54
+ if ( diag === undefined || diag . relatedInformation === undefined ) return ;
55
+
38
56
const related = find ( diag . relatedInformation , related => related . code === Diagnostics . This_type_parameter_might_need_an_extends_0_constraint . code ) ;
39
- if ( ! related ) return ;
40
- return related ;
41
- }
57
+ if ( related === undefined || related . file === undefined || related . start === undefined || related . length === undefined ) return ;
58
+
59
+ let declaration = findAncestorMatchingSpan ( related . file , createTextSpan ( related . start , related . length ) ) ;
60
+ if ( declaration === undefined ) return ;
42
61
43
- function addMissingConstraint ( changes : textChanges . ChangeTracker , related : DiagnosticRelatedInformation ) : void {
44
- let decl = findAncestorMatchingSpan ( related . file ! , related as TextSpan ) ;
45
- if ( ! decl ) return ;
46
- if ( isIdentifier ( decl ) && isTypeParameterDeclaration ( decl . parent ) ) {
47
- decl = decl . parent ;
62
+ if ( isIdentifier ( declaration ) && isTypeParameterDeclaration ( declaration . parent ) ) {
63
+ declaration = declaration . parent ;
48
64
}
49
- if ( ! isTypeParameterDeclaration ( decl ) || isMappedTypeNode ( decl . parent ) ) return ; // should only issue fix on type parameters written using `extends`
50
- const newConstraint = flattenDiagnosticMessageText ( related . messageText , "\n" , 0 ) . match ( / ` e x t e n d s ( .* ) ` / ) ;
51
- if ( ! newConstraint ) return ;
52
- const newConstraintText = newConstraint [ 1 ] ;
53
65
54
- changes . insertText ( related . file ! , decl . name . end , ` extends ${ newConstraintText } ` ) ;
66
+ if ( isTypeParameterDeclaration ( declaration ) ) {
67
+ // should only issue fix on type parameters written using `extends`
68
+ if ( isMappedTypeNode ( declaration . parent ) ) return ;
69
+
70
+ const token = getTokenAtPosition ( sourceFile , span . start ) ;
71
+ const checker = program . getTypeChecker ( ) ;
72
+ const constraint = tryGetConstraintType ( checker , token ) || tryGetConstraintFromDiagnosticMessage ( related . messageText ) ;
73
+
74
+ return { constraint, declaration, token } ;
75
+ }
76
+ return undefined ;
77
+ }
78
+
79
+ function addMissingConstraint ( changes : textChanges . ChangeTracker , program : Program , preferences : UserPreferences , host : LanguageServiceHost , sourceFile : SourceFile , info : Info ) : void {
80
+ const { declaration, constraint } = info ;
81
+ const checker = program . getTypeChecker ( ) ;
82
+
83
+ if ( isString ( constraint ) ) {
84
+ changes . insertText ( sourceFile , declaration . name . end , ` extends ${ constraint } ` ) ;
85
+ }
86
+ else {
87
+ const scriptTarget = getEmitScriptTarget ( program . getCompilerOptions ( ) ) ;
88
+ const tracker = getNoopSymbolTrackerWithResolver ( { program, host } ) ;
89
+ const importAdder = createImportAdder ( sourceFile , program , preferences , host ) ;
90
+ const typeNode = typeToAutoImportableTypeNode ( checker , importAdder , constraint , /*contextNode*/ undefined , scriptTarget , /*flags*/ undefined , tracker ) ;
91
+ if ( typeNode ) {
92
+ changes . replaceNode ( sourceFile , declaration , factory . updateTypeParameterDeclaration ( declaration , /*modifiers*/ undefined , declaration . name , typeNode , declaration . default ) ) ;
93
+ importAdder . writeFixes ( changes ) ;
94
+ }
95
+ }
55
96
}
56
97
57
98
function findAncestorMatchingSpan ( sourceFile : SourceFile , span : TextSpan ) : Node {
58
- let token = getTokenAtPosition ( sourceFile , span . start ) ;
59
99
const end = textSpanEnd ( span ) ;
100
+ let token = getTokenAtPosition ( sourceFile , span . start ) ;
60
101
while ( token . end < end ) {
61
102
token = token . parent ;
62
103
}
63
104
return token ;
64
105
}
106
+
107
+ function tryGetConstraintFromDiagnosticMessage ( messageText : string | DiagnosticMessageChain ) {
108
+ const [ _ , constraint ] = flattenDiagnosticMessageText ( messageText , "\n" , 0 ) . match ( / ` e x t e n d s ( .* ) ` / ) || [ ] ;
109
+ return constraint ;
110
+ }
111
+
112
+ function tryGetConstraintType ( checker : TypeChecker , node : Node ) {
113
+ if ( isTypeNode ( node . parent ) ) {
114
+ return checker . getTypeArgumentConstraint ( node . parent ) ;
115
+ }
116
+ const contextualType = isExpression ( node ) ? checker . getContextualType ( node ) : undefined ;
117
+ return contextualType || checker . getTypeAtLocation ( node ) ;
118
+ }
65
119
}
0 commit comments