@@ -11,16 +11,11 @@ namespace ts.codefix {
11
11
errorCodes,
12
12
getCodeActions ( context ) {
13
13
const typeChecker = context . program . getTypeChecker ( ) ;
14
- const info = getInfo ( context . sourceFile , context . span . start , typeChecker ) ;
15
- if ( ! info . length ) {
14
+ const toAdd = getPropertiesToAdd ( context . sourceFile , context . span . start , typeChecker ) ;
15
+ if ( ! toAdd . length ) {
16
16
return undefined ;
17
17
}
18
- // if method, it has to be rewritten to property
19
- // skip any and unions with any
20
- // add to existing unions
21
- // parenthesise conditional types and arrows (the printer should take care of that, but it needs a test)
22
- // test with destructuring, I've no idea what to do there
23
- const changes = textChanges . ChangeTracker . with ( context , t => addUndefinedToOptionalProperty ( t , info ) ) ;
18
+ const changes = textChanges . ChangeTracker . with ( context , t => addUndefinedToOptionalProperty ( t , toAdd ) ) ;
24
19
return [ createCodeFixAction ( addOptionalPropertyUndefined , changes , Diagnostics . Add_undefined_to_optional_property_type , addOptionalPropertyUndefined , Diagnostics . Add_undefined_to_all_optional_properties ) ] ;
25
20
} ,
26
21
fixIds : [ addOptionalPropertyUndefined ] ,
@@ -30,81 +25,96 @@ namespace ts.codefix {
30
25
const seen = new Map < string , true > ( ) ;
31
26
return createCombinedCodeActions ( textChanges . ChangeTracker . with ( context , changes => {
32
27
eachDiagnostic ( context , errorCodes , diag => {
33
- const info = getInfo ( diag . file , diag . start , checker ) ;
34
- if ( ! info . length ) {
28
+ const toAdd = getPropertiesToAdd ( diag . file , diag . start , checker ) ;
29
+ if ( ! toAdd . length ) {
35
30
return ;
36
31
}
37
- for ( const add of info ) {
38
- if ( addToSeen ( seen , add . id + "" ) ) {
39
- addUndefinedToOptionalProperty ( changes , info ) ;
32
+ let untouched = true ;
33
+ for ( const add of toAdd ) {
34
+ if ( ! addToSeen ( seen , add . id + "" ) ) {
35
+ untouched = false ;
40
36
}
41
37
}
38
+ if ( untouched ) { addUndefinedToOptionalProperty ( changes , toAdd ) ; }
42
39
} ) ;
43
40
} ) ) ;
44
41
} ,
45
42
} ) ;
46
43
47
- // The target of the incorrect assignment
48
- // eg
49
- // this.definite = 1; -OR- definite = source
50
- // ^^^^ ^^^^^^^^
51
- // TODO: More examples here
52
- function getTarget ( file : SourceFile , pos : number ) : MemberName | PropertyAccessExpression | undefined {
53
- const start = getTokenAtPosition ( file , pos )
44
+ function getPropertiesToAdd ( file : SourceFile , pos : number , checker : TypeChecker ) : Symbol [ ] {
45
+ const sourceTarget = getSourceTarget ( getErrorNode ( file , pos ) , checker ) ;
46
+ if ( ! sourceTarget ) {
47
+ return [ ] ;
48
+ }
49
+ const { source : sourceNode , target : targetNode } = sourceTarget ;
50
+ const target = checker . getTypeAtLocation ( targetNode ) ;
51
+ if ( target . symbol ?. declarations ?. some ( d => getSourceFileOfNode ( d ) . fileName . match ( / ( n o d e _ m o d u l e s | ^ l i b \. ) / ) ) ) {
52
+ return [ ] ;
53
+ }
54
+ return checker . getExactOptionalUnassignableProperties ( checker . getTypeAtLocation ( sourceNode ) , target ) ;
55
+ }
56
+
57
+ /**
58
+ * Get the part of the incorrect assignment that is useful for type-checking
59
+ * eg
60
+ * this.definite = 1; ---> `this.definite`
61
+ * ^^^^
62
+ * definite = source ----> `definite`
63
+ * ^^^^^^^^
64
+ */
65
+ function getErrorNode ( file : SourceFile , pos : number ) : MemberName | PropertyAccessExpression | undefined {
66
+ const start = getTokenAtPosition ( file , pos ) ;
54
67
return isPropertyAccessExpression ( start . parent ) && start . parent . expression === start ? start . parent
55
68
: isIdentifier ( start ) || isPrivateIdentifier ( start ) ? start
56
69
: undefined ;
57
70
}
58
71
59
- function getSourceTarget ( target : Node | undefined , checker : TypeChecker ) : { source : Node , target : Node } | undefined {
60
- if ( ! target ) return undefined
61
- if ( isBinaryExpression ( target . parent ) && target . parent . operatorToken . kind === SyntaxKind . EqualsToken ) {
62
- return { source : target . parent . right , target : target . parent . left }
72
+ /**
73
+ * Find the source and target of the incorrect assignment.
74
+ * The call is recursive for property assignments.
75
+ */
76
+ function getSourceTarget ( errorNode : Node | undefined , checker : TypeChecker ) : { source : Node , target : Node } | undefined {
77
+ if ( ! errorNode ) {
78
+ return undefined ;
63
79
}
64
- else if ( isVariableDeclaration ( target . parent ) && target . parent . initializer ) {
65
- return { source : target . parent . initializer , target : target . parent . name }
80
+ else if ( isBinaryExpression ( errorNode . parent ) && errorNode . parent . operatorToken . kind === SyntaxKind . EqualsToken ) {
81
+ return { source : errorNode . parent . right , target : errorNode . parent . left } ;
66
82
}
67
- else if ( isCallExpression ( target . parent ) ) {
68
- const n = checker . getSymbolAtLocation ( target . parent . expression )
69
- if ( ! n ?. valueDeclaration ) return undefined
70
- if ( ! isExpression ( target ) ) return undefined ;
71
- const i = target . parent . arguments . indexOf ( target )
72
- const name = ( n . valueDeclaration as any as SignatureDeclaration ) . parameters [ i ] . name
73
- if ( isIdentifier ( name ) ) return { source : target , target : name }
83
+ else if ( isVariableDeclaration ( errorNode . parent ) && errorNode . parent . initializer ) {
84
+ return { source : errorNode . parent . initializer , target : errorNode . parent . name } ;
74
85
}
75
- else if ( isPropertyAssignment ( target . parent ) && isIdentifier ( target . parent . name ) ||
76
- isShorthandPropertyAssignment ( target . parent ) ) {
77
- const parentTarget = getSourceTarget ( target . parent . parent , checker )
78
- if ( ! parentTarget ) return undefined
79
- const prop = checker . getPropertyOfType ( checker . getTypeAtLocation ( parentTarget . target ) , ( target . parent . name as Identifier ) . text )
80
- const declaration = prop ?. declarations ?. [ 0 ]
81
- if ( ! declaration ) return undefined
86
+ else if ( isCallExpression ( errorNode . parent ) ) {
87
+ const n = checker . getSymbolAtLocation ( errorNode . parent . expression ) ;
88
+ if ( ! n ?. valueDeclaration ) return undefined ;
89
+ if ( ! isExpression ( errorNode ) ) return undefined ;
90
+ const i = errorNode . parent . arguments . indexOf ( errorNode ) ;
91
+ const name = ( n . valueDeclaration as any as SignatureDeclaration ) . parameters [ i ] . name ;
92
+ if ( isIdentifier ( name ) ) return { source : errorNode , target : name } ;
93
+ }
94
+ else if ( isPropertyAssignment ( errorNode . parent ) && isIdentifier ( errorNode . parent . name ) ||
95
+ isShorthandPropertyAssignment ( errorNode . parent ) ) {
96
+ const parentTarget = getSourceTarget ( errorNode . parent . parent , checker ) ;
97
+ if ( ! parentTarget ) return undefined ;
98
+ const prop = checker . getPropertyOfType ( checker . getTypeAtLocation ( parentTarget . target ) , ( errorNode . parent . name as Identifier ) . text ) ;
99
+ const declaration = prop ?. declarations ?. [ 0 ] ;
100
+ if ( ! declaration ) return undefined ;
82
101
return {
83
- source : isPropertyAssignment ( target . parent ) ? target . parent . initializer : target . parent . name ,
102
+ source : isPropertyAssignment ( errorNode . parent ) ? errorNode . parent . initializer : errorNode . parent . name ,
84
103
target : declaration
85
- }
104
+ } ;
86
105
}
87
- return undefined
88
- }
89
-
90
- function getInfo ( file : SourceFile , pos : number , checker : TypeChecker ) : Symbol [ ] {
91
- const sourceTarget = getSourceTarget ( getTarget ( file , pos ) , checker )
92
- if ( ! sourceTarget ) return [ ]
93
- const { source : sourceNode , target : targetNode } = sourceTarget
94
- const target = checker . getTypeAtLocation ( targetNode )
95
- if ( target . symbol ?. declarations ?. some ( d => getSourceFileOfNode ( d ) . fileName . match ( / ( n o d e _ m o d u l e s | ^ l i b \. ) / ) ) ) return [ ] ;
96
- return checker . getExactOptionalUnassignableProperties ( checker . getTypeAtLocation ( sourceNode ) , target )
106
+ return undefined ;
97
107
}
98
108
99
109
function addUndefinedToOptionalProperty ( changes : textChanges . ChangeTracker , toAdd : Symbol [ ] ) {
100
110
for ( const add of toAdd ) {
101
- const d = add . valueDeclaration
111
+ const d = add . valueDeclaration ;
102
112
if ( d && ( isPropertySignature ( d ) || isPropertyDeclaration ( d ) ) && d . type ) {
103
113
const t = factory . createUnionTypeNode ( [
104
114
...d . type . kind === SyntaxKind . UnionType ? ( d . type as UnionTypeNode ) . types : [ d . type ] ,
105
115
factory . createTypeReferenceNode ( "undefined" )
106
- ] )
107
- changes . replaceNode ( d . getSourceFile ( ) , d . type , t )
116
+ ] ) ;
117
+ changes . replaceNode ( d . getSourceFile ( ) , d . type , t ) ;
108
118
}
109
119
}
110
120
}
0 commit comments