@@ -13,55 +13,103 @@ namespace ts.codefix {
13
13
if ( ! info ) return undefined ;
14
14
15
15
if ( info . kind === InfoKind . enum ) {
16
- const { token, enumDeclaration } = info ;
17
- const changes = textChanges . ChangeTracker . with ( context , t => addEnumMemberDeclaration ( t , context . program . getTypeChecker ( ) , token , enumDeclaration ) ) ;
16
+ const { token, parentDeclaration } = info ;
17
+ const changes = textChanges . ChangeTracker . with ( context , t => addEnumMemberDeclaration ( t , context . program . getTypeChecker ( ) , token , parentDeclaration ) ) ;
18
18
return [ createCodeFixAction ( fixName , changes , [ Diagnostics . Add_missing_enum_member_0 , token . text ] , fixId , Diagnostics . Add_all_missing_members ) ] ;
19
19
}
20
- const { classDeclaration , classDeclarationSourceFile, inJs, makeStatic, token, call } = info ;
21
- const methodCodeAction = call && getActionForMethodDeclaration ( context , classDeclarationSourceFile , classDeclaration , token , call , makeStatic , inJs , context . preferences ) ;
20
+ const { parentDeclaration , classDeclarationSourceFile, inJs, makeStatic, token, call } = info ;
21
+ const methodCodeAction = call && getActionForMethodDeclaration ( context , classDeclarationSourceFile , parentDeclaration , token , call , makeStatic , inJs , context . preferences ) ;
22
22
const addMember = inJs ?
23
- singleElementArray ( getActionsForAddMissingMemberInJavaScriptFile ( context , classDeclarationSourceFile , classDeclaration , token . text , makeStatic ) ) :
24
- getActionsForAddMissingMemberInTypeScriptFile ( context , classDeclarationSourceFile , classDeclaration , token , makeStatic ) ;
23
+ singleElementArray ( getActionsForAddMissingMemberInJavaScriptFile ( context , classDeclarationSourceFile , parentDeclaration , token . text , makeStatic ) ) :
24
+ getActionsForAddMissingMemberInTypeScriptFile ( context , classDeclarationSourceFile , parentDeclaration , token , makeStatic ) ;
25
25
return concatenate ( singleElementArray ( methodCodeAction ) , addMember ) ;
26
26
} ,
27
27
fixIds : [ fixId ] ,
28
28
getAllCodeActions : context => {
29
- const seenNames = createMap < true > ( ) ;
30
- return codeFixAll ( context , errorCodes , ( changes , diag ) => {
31
- const { program, preferences } = context ;
32
- const checker = program . getTypeChecker ( ) ;
33
- const info = getInfo ( diag . file , diag . start , checker ) ;
34
- if ( ! info || ! addToSeen ( seenNames , info . token . text ) ) {
35
- return ;
36
- }
37
-
38
- if ( info . kind === InfoKind . enum ) {
39
- const { token, enumDeclaration } = info ;
40
- addEnumMemberDeclaration ( changes , checker , token , enumDeclaration ) ;
41
- }
42
- else {
43
- const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info ;
44
- // Always prefer to add a method declaration if possible.
45
- if ( call ) {
46
- addMethodDeclaration ( context , changes , classDeclarationSourceFile , classDeclaration , token , call , makeStatic , inJs , preferences ) ;
29
+ const { program, preferences } = context ;
30
+ const checker = program . getTypeChecker ( ) ;
31
+ const seen = createMap < true > ( ) ;
32
+
33
+ const classToMembers = new NodeMap < ClassLikeDeclaration , ClassInfo [ ] > ( ) ;
34
+
35
+ return createCombinedCodeActions ( textChanges . ChangeTracker . with ( context , changes => {
36
+ eachDiagnostic ( context , errorCodes , diag => {
37
+ const info = getInfo ( diag . file , diag . start , checker ) ;
38
+ if ( ! info || ! addToSeen ( seen , getNodeId ( info . parentDeclaration ) + "#" + info . token . text ) ) {
39
+ return ;
40
+ }
41
+
42
+ if ( info . kind === InfoKind . enum ) {
43
+ const { token, parentDeclaration } = info ;
44
+ addEnumMemberDeclaration ( changes , checker , token , parentDeclaration ) ;
47
45
}
48
46
else {
49
- if ( inJs ) {
50
- addMissingMemberInJs ( changes , classDeclarationSourceFile , classDeclaration , token . text , makeStatic ) ;
47
+ const { parentDeclaration, token } = info ;
48
+ const infos = classToMembers . getOrUpdate ( parentDeclaration , ( ) => [ ] ) ;
49
+ if ( ! infos . some ( i => i . token . text === token . text ) ) infos . push ( info ) ;
50
+ }
51
+ } ) ;
52
+
53
+ classToMembers . forEach ( ( infos , classDeclaration ) => {
54
+ const superClasses = getAllSuperClasses ( classDeclaration , checker ) ;
55
+ for ( const info of infos ) {
56
+ // If some superclass added this property, don't add it again.
57
+ if ( superClasses . some ( superClass => {
58
+ const superInfos = classToMembers . get ( superClass ) ;
59
+ return ! ! superInfos && superInfos . some ( ( { token } ) => token . text === info . token . text ) ;
60
+ } ) ) continue ;
61
+
62
+ const { parentDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info ;
63
+
64
+ // Always prefer to add a method declaration if possible.
65
+ if ( call ) {
66
+ addMethodDeclaration ( context , changes , classDeclarationSourceFile , parentDeclaration , token , call , makeStatic , inJs , preferences ) ;
51
67
}
52
68
else {
53
- const typeNode = getTypeNode ( program . getTypeChecker ( ) , classDeclaration , token ) ;
54
- addPropertyDeclaration ( changes , classDeclarationSourceFile , classDeclaration , token . text , typeNode , makeStatic ) ;
69
+ if ( inJs ) {
70
+ addMissingMemberInJs ( changes , classDeclarationSourceFile , parentDeclaration , token . text , makeStatic ) ;
71
+ }
72
+ else {
73
+ const typeNode = getTypeNode ( program . getTypeChecker ( ) , parentDeclaration , token ) ;
74
+ addPropertyDeclaration ( changes , classDeclarationSourceFile , parentDeclaration , token . text , typeNode , makeStatic ) ;
75
+ }
55
76
}
56
77
}
57
- }
58
- } ) ;
78
+ } ) ;
79
+ } ) ) ;
59
80
} ,
60
81
} ) ;
61
82
83
+ function getAllSuperClasses ( cls : ClassLikeDeclaration | undefined , checker : TypeChecker ) : ReadonlyArray < ClassLikeDeclaration > {
84
+ const res : ClassLikeDeclaration [ ] = [ ] ;
85
+ while ( cls ) {
86
+ const superElement = getClassExtendsHeritageElement ( cls ) ;
87
+ const superSymbol = superElement && checker . getSymbolAtLocation ( superElement . expression ) ;
88
+ const superDecl = superSymbol && find ( superSymbol . declarations , isClassLike ) ;
89
+ if ( superDecl ) { res . push ( superDecl ) ; }
90
+ cls = superDecl ;
91
+ }
92
+ return res ;
93
+ }
94
+
95
+ interface InfoBase {
96
+ readonly kind : InfoKind ;
97
+ readonly token : Identifier ;
98
+ readonly parentDeclaration : EnumDeclaration | ClassLikeDeclaration ;
99
+ }
62
100
enum InfoKind { enum , class }
63
- interface EnumInfo { kind : InfoKind . enum ; token : Identifier ; enumDeclaration : EnumDeclaration ; }
64
- interface ClassInfo { kind : InfoKind . class ; token : Identifier ; classDeclaration : ClassLikeDeclaration ; makeStatic : boolean ; classDeclarationSourceFile : SourceFile ; inJs : boolean ; call : CallExpression | undefined ; }
101
+ interface EnumInfo extends InfoBase {
102
+ readonly kind : InfoKind . enum ;
103
+ readonly parentDeclaration : EnumDeclaration ;
104
+ }
105
+ interface ClassInfo extends InfoBase {
106
+ readonly kind : InfoKind . class ;
107
+ readonly parentDeclaration : ClassLikeDeclaration ;
108
+ readonly makeStatic : boolean ;
109
+ readonly classDeclarationSourceFile : SourceFile ;
110
+ readonly inJs : boolean ;
111
+ readonly call : CallExpression | undefined ;
112
+ }
65
113
type Info = EnumInfo | ClassInfo ;
66
114
67
115
function getInfo ( tokenSourceFile : SourceFile , tokenPos : number , checker : TypeChecker ) : Info | undefined {
@@ -86,11 +134,11 @@ namespace ts.codefix {
86
134
const classDeclarationSourceFile = classDeclaration . getSourceFile ( ) ;
87
135
const inJs = isSourceFileJavaScript ( classDeclarationSourceFile ) ;
88
136
const call = tryCast ( parent . parent , isCallExpression ) ;
89
- return { kind : InfoKind . class , token, classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call } ;
137
+ return { kind : InfoKind . class , token, parentDeclaration : classDeclaration , makeStatic, classDeclarationSourceFile, inJs, call } ;
90
138
}
91
139
const enumDeclaration = find ( symbol . declarations , isEnumDeclaration ) ;
92
140
if ( enumDeclaration ) {
93
- return { kind : InfoKind . enum , token, enumDeclaration } ;
141
+ return { kind : InfoKind . enum , token, parentDeclaration : enumDeclaration } ;
94
142
}
95
143
return undefined ;
96
144
}
0 commit comments