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