2
2
namespace ts . codefix {
3
3
const fixMissingMember = "fixMissingMember" ;
4
4
const fixMissingProperties = "fixMissingProperties" ;
5
+ const fixMissingAttributes = "fixMissingAttributes" ;
5
6
const fixMissingFunctionDeclaration = "fixMissingFunctionDeclaration" ;
6
7
7
8
const errorCodes = [
@@ -25,6 +26,10 @@ namespace ts.codefix {
25
26
const changes = textChanges . ChangeTracker . with ( context , t => addObjectLiteralProperties ( t , context , info ) ) ;
26
27
return [ createCodeFixAction ( fixMissingProperties , changes , Diagnostics . Add_missing_properties , fixMissingProperties , Diagnostics . Add_all_missing_properties ) ] ;
27
28
}
29
+ if ( info . kind === InfoKind . JsxAttributes ) {
30
+ const changes = textChanges . ChangeTracker . with ( context , t => addJsxAttributes ( t , context , info ) ) ;
31
+ return [ createCodeFixAction ( fixMissingAttributes , changes , Diagnostics . Add_missing_attributes , fixMissingAttributes , Diagnostics . Add_all_missing_attributes ) ] ;
32
+ }
28
33
if ( info . kind === InfoKind . Function ) {
29
34
const changes = textChanges . ChangeTracker . with ( context , t => addFunctionDeclaration ( t , context , info ) ) ;
30
35
return [ createCodeFixAction ( fixMissingFunctionDeclaration , changes , [ Diagnostics . Add_missing_function_declaration_0 , info . token . text ] , fixMissingFunctionDeclaration , Diagnostics . Add_all_missing_function_declarations ) ] ;
@@ -35,7 +40,7 @@ namespace ts.codefix {
35
40
}
36
41
return concatenate ( getActionsForMissingMethodDeclaration ( context , info ) , getActionsForMissingMemberDeclaration ( context , info ) ) ;
37
42
} ,
38
- fixIds : [ fixMissingMember , fixMissingFunctionDeclaration , fixMissingProperties ] ,
43
+ fixIds : [ fixMissingMember , fixMissingFunctionDeclaration , fixMissingProperties , fixMissingAttributes ] ,
39
44
getAllCodeActions : context => {
40
45
const { program, fixId } = context ;
41
46
const checker = program . getTypeChecker ( ) ;
@@ -49,15 +54,14 @@ namespace ts.codefix {
49
54
return ;
50
55
}
51
56
52
- if ( fixId === fixMissingFunctionDeclaration ) {
53
- if ( info . kind === InfoKind . Function ) {
54
- addFunctionDeclaration ( changes , context , info ) ;
55
- }
57
+ if ( fixId === fixMissingFunctionDeclaration && info . kind === InfoKind . Function ) {
58
+ addFunctionDeclaration ( changes , context , info ) ;
56
59
}
57
- else if ( fixId === fixMissingProperties ) {
58
- if ( info . kind === InfoKind . ObjectLiteral ) {
59
- addObjectLiteralProperties ( changes , context , info ) ;
60
- }
60
+ else if ( fixId === fixMissingProperties && info . kind === InfoKind . ObjectLiteral ) {
61
+ addObjectLiteralProperties ( changes , context , info ) ;
62
+ }
63
+ else if ( fixId === fixMissingAttributes && info . kind === InfoKind . JsxAttributes ) {
64
+ addJsxAttributes ( changes , context , info ) ;
61
65
}
62
66
else {
63
67
if ( info . kind === InfoKind . Enum ) {
@@ -102,8 +106,8 @@ namespace ts.codefix {
102
106
} ,
103
107
} ) ;
104
108
105
- const enum InfoKind { Enum , ClassOrInterface , Function , ObjectLiteral }
106
- type Info = EnumInfo | ClassOrInterfaceInfo | FunctionInfo | ObjectLiteralInfo ;
109
+ const enum InfoKind { Enum , ClassOrInterface , Function , ObjectLiteral , JsxAttributes }
110
+ type Info = EnumInfo | ClassOrInterfaceInfo | FunctionInfo | ObjectLiteralInfo | JsxAttributesInfo ;
107
111
108
112
interface EnumInfo {
109
113
readonly kind : InfoKind . Enum ;
@@ -137,6 +141,13 @@ namespace ts.codefix {
137
141
readonly parentDeclaration : ObjectLiteralExpression ;
138
142
}
139
143
144
+ interface JsxAttributesInfo {
145
+ readonly kind : InfoKind . JsxAttributes ;
146
+ readonly token : Identifier ;
147
+ readonly attributes : Symbol [ ] ;
148
+ readonly parentDeclaration : JsxOpeningLikeElement ;
149
+ }
150
+
140
151
function getInfo ( sourceFile : SourceFile , tokenPos : number , checker : TypeChecker , program : Program ) : Info | undefined {
141
152
// The identifier of the missing property. eg:
142
153
// this.missing = 1;
@@ -154,6 +165,13 @@ namespace ts.codefix {
154
165
}
155
166
}
156
167
168
+ if ( isIdentifier ( token ) && isJsxOpeningLikeElement ( token . parent ) ) {
169
+ const attributes = getUnmatchedAttributes ( checker , token . parent ) ;
170
+ if ( length ( attributes ) ) {
171
+ return { kind : InfoKind . JsxAttributes , token, attributes, parentDeclaration : token . parent } ;
172
+ }
173
+ }
174
+
157
175
if ( isIdentifier ( token ) && isCallExpression ( parent ) ) {
158
176
return { kind : InfoKind . Function , token, call : parent , sourceFile, modifierFlags : ModifierFlags . None , parentDeclaration : sourceFile } ;
159
177
}
@@ -434,18 +452,33 @@ namespace ts.codefix {
434
452
changes . insertNodeAtEndOfScope ( info . sourceFile , info . parentDeclaration , functionDeclaration ) ;
435
453
}
436
454
455
+ function addJsxAttributes ( changes : textChanges . ChangeTracker , context : CodeFixContextBase , info : JsxAttributesInfo ) {
456
+ const importAdder = createImportAdder ( context . sourceFile , context . program , context . preferences , context . host ) ;
457
+ const quotePreference = getQuotePreference ( context . sourceFile , context . preferences ) ;
458
+ const checker = context . program . getTypeChecker ( ) ;
459
+ const jsxAttributesNode = info . parentDeclaration . attributes ;
460
+ const hasSpreadAttribute = some ( jsxAttributesNode . properties , isJsxSpreadAttribute ) ;
461
+ const attrs = map ( info . attributes , attr => {
462
+ const value = attr . valueDeclaration ? tryGetValueFromType ( context , checker , importAdder , quotePreference , checker . getTypeAtLocation ( attr . valueDeclaration ) ) : createUndefined ( ) ;
463
+ return factory . createJsxAttribute ( factory . createIdentifier ( attr . name ) , factory . createJsxExpression ( /*dotDotDotToken*/ undefined , value ) ) ;
464
+ } ) ;
465
+ const jsxAttributes = factory . createJsxAttributes ( hasSpreadAttribute ? [ ...attrs , ...jsxAttributesNode . properties ] : [ ...jsxAttributesNode . properties , ...attrs ] ) ;
466
+ const options = { prefix : jsxAttributesNode . pos === jsxAttributesNode . end ? " " : undefined } ;
467
+ changes . replaceNode ( context . sourceFile , jsxAttributesNode , jsxAttributes , options ) ;
468
+ }
469
+
437
470
function addObjectLiteralProperties ( changes : textChanges . ChangeTracker , context : CodeFixContextBase , info : ObjectLiteralInfo ) {
438
471
const importAdder = createImportAdder ( context . sourceFile , context . program , context . preferences , context . host ) ;
439
472
const quotePreference = getQuotePreference ( context . sourceFile , context . preferences ) ;
440
473
const checker = context . program . getTypeChecker ( ) ;
441
474
const props = map ( info . properties , prop => {
442
- const initializer = prop . valueDeclaration ? tryGetInitializerValueFromType ( context , checker , importAdder , quotePreference , checker . getTypeAtLocation ( prop . valueDeclaration ) ) : createUndefined ( ) ;
475
+ const initializer = prop . valueDeclaration ? tryGetValueFromType ( context , checker , importAdder , quotePreference , checker . getTypeAtLocation ( prop . valueDeclaration ) ) : createUndefined ( ) ;
443
476
return factory . createPropertyAssignment ( prop . name , initializer ) ;
444
477
} ) ;
445
478
changes . replaceNode ( context . sourceFile , info . parentDeclaration , factory . createObjectLiteralExpression ( [ ...info . parentDeclaration . properties , ...props ] , /*multiLine*/ true ) ) ;
446
479
}
447
480
448
- function tryGetInitializerValueFromType ( context : CodeFixContextBase , checker : TypeChecker , importAdder : ImportAdder , quotePreference : QuotePreference , type : Type ) : Expression {
481
+ function tryGetValueFromType ( context : CodeFixContextBase , checker : TypeChecker , importAdder : ImportAdder , quotePreference : QuotePreference , type : Type ) : Expression {
449
482
if ( type . flags & TypeFlags . AnyOrUnknown ) {
450
483
return createUndefined ( ) ;
451
484
}
@@ -482,15 +515,15 @@ namespace ts.codefix {
482
515
return factory . createNull ( ) ;
483
516
}
484
517
if ( type . flags & TypeFlags . Union ) {
485
- const expression = firstDefined ( ( type as UnionType ) . types , t => tryGetInitializerValueFromType ( context , checker , importAdder , quotePreference , t ) ) ;
518
+ const expression = firstDefined ( ( type as UnionType ) . types , t => tryGetValueFromType ( context , checker , importAdder , quotePreference , t ) ) ;
486
519
return expression ?? createUndefined ( ) ;
487
520
}
488
521
if ( checker . isArrayLikeType ( type ) ) {
489
522
return factory . createArrayLiteralExpression ( ) ;
490
523
}
491
524
if ( isObjectLiteralType ( type ) ) {
492
525
const props = map ( checker . getPropertiesOfType ( type ) , prop => {
493
- const initializer = prop . valueDeclaration ? tryGetInitializerValueFromType ( context , checker , importAdder , quotePreference , checker . getTypeAtLocation ( prop . valueDeclaration ) ) : createUndefined ( ) ;
526
+ const initializer = prop . valueDeclaration ? tryGetValueFromType ( context , checker , importAdder , quotePreference , checker . getTypeAtLocation ( prop . valueDeclaration ) ) : createUndefined ( ) ;
494
527
return factory . createPropertyAssignment ( prop . name , initializer ) ;
495
528
} ) ;
496
529
return factory . createObjectLiteralExpression ( props , /*multiLine*/ true ) ;
@@ -526,4 +559,27 @@ namespace ts.codefix {
526
559
return ( type . flags & TypeFlags . Object ) &&
527
560
( ( getObjectFlags ( type ) & ObjectFlags . ObjectLiteral ) || ( type . symbol && tryCast ( singleOrUndefined ( type . symbol . declarations ) , isTypeLiteralNode ) ) ) ;
528
561
}
562
+
563
+ function getUnmatchedAttributes ( checker : TypeChecker , source : JsxOpeningLikeElement ) {
564
+ const attrsType = checker . getContextualType ( source . attributes ) ;
565
+ if ( attrsType === undefined ) return emptyArray ;
566
+
567
+ const targetProps = attrsType . getProperties ( ) ;
568
+ if ( ! length ( targetProps ) ) return emptyArray ;
569
+
570
+ const seenNames = new Set < __String > ( ) ;
571
+ for ( const sourceProp of source . attributes . properties ) {
572
+ if ( isJsxAttribute ( sourceProp ) ) {
573
+ seenNames . add ( sourceProp . name . escapedText ) ;
574
+ }
575
+ if ( isJsxSpreadAttribute ( sourceProp ) ) {
576
+ const type = checker . getTypeAtLocation ( sourceProp . expression ) ;
577
+ for ( const prop of type . getProperties ( ) ) {
578
+ seenNames . add ( prop . escapedName ) ;
579
+ }
580
+ }
581
+ }
582
+ return filter ( targetProps , targetProp =>
583
+ ! ( ( targetProp . flags & SymbolFlags . Optional || getCheckFlags ( targetProp ) & CheckFlags . Partial ) || seenNames . has ( targetProp . escapedName ) ) ) ;
584
+ }
529
585
}
0 commit comments