22namespace ts . codefix {
33 const fixMissingMember = "fixMissingMember" ;
44 const fixMissingProperties = "fixMissingProperties" ;
5+ const fixMissingAttributes = "fixMissingAttributes" ;
56 const fixMissingFunctionDeclaration = "fixMissingFunctionDeclaration" ;
67
78 const errorCodes = [
@@ -25,6 +26,10 @@ namespace ts.codefix {
2526 const changes = textChanges . ChangeTracker . with ( context , t => addObjectLiteralProperties ( t , context , info ) ) ;
2627 return [ createCodeFixAction ( fixMissingProperties , changes , Diagnostics . Add_missing_properties , fixMissingProperties , Diagnostics . Add_all_missing_properties ) ] ;
2728 }
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+ }
2833 if ( info . kind === InfoKind . Function ) {
2934 const changes = textChanges . ChangeTracker . with ( context , t => addFunctionDeclaration ( t , context , info ) ) ;
3035 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 {
3540 }
3641 return concatenate ( getActionsForMissingMethodDeclaration ( context , info ) , getActionsForMissingMemberDeclaration ( context , info ) ) ;
3742 } ,
38- fixIds : [ fixMissingMember , fixMissingFunctionDeclaration , fixMissingProperties ] ,
43+ fixIds : [ fixMissingMember , fixMissingFunctionDeclaration , fixMissingProperties , fixMissingAttributes ] ,
3944 getAllCodeActions : context => {
4045 const { program, fixId } = context ;
4146 const checker = program . getTypeChecker ( ) ;
@@ -49,15 +54,14 @@ namespace ts.codefix {
4954 return ;
5055 }
5156
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 ) ;
5659 }
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 ) ;
6165 }
6266 else {
6367 if ( info . kind === InfoKind . Enum ) {
@@ -102,8 +106,8 @@ namespace ts.codefix {
102106 } ,
103107 } ) ;
104108
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 ;
107111
108112 interface EnumInfo {
109113 readonly kind : InfoKind . Enum ;
@@ -137,6 +141,13 @@ namespace ts.codefix {
137141 readonly parentDeclaration : ObjectLiteralExpression ;
138142 }
139143
144+ interface JsxAttributesInfo {
145+ readonly kind : InfoKind . JsxAttributes ;
146+ readonly token : Identifier ;
147+ readonly attributes : Symbol [ ] ;
148+ readonly parentDeclaration : JsxOpeningLikeElement ;
149+ }
150+
140151 function getInfo ( sourceFile : SourceFile , tokenPos : number , checker : TypeChecker , program : Program ) : Info | undefined {
141152 // The identifier of the missing property. eg:
142153 // this.missing = 1;
@@ -154,6 +165,13 @@ namespace ts.codefix {
154165 }
155166 }
156167
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+
157175 if ( isIdentifier ( token ) && isCallExpression ( parent ) ) {
158176 return { kind : InfoKind . Function , token, call : parent , sourceFile, modifierFlags : ModifierFlags . None , parentDeclaration : sourceFile } ;
159177 }
@@ -434,18 +452,33 @@ namespace ts.codefix {
434452 changes . insertNodeAtEndOfScope ( info . sourceFile , info . parentDeclaration , functionDeclaration ) ;
435453 }
436454
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+
437470 function addObjectLiteralProperties ( changes : textChanges . ChangeTracker , context : CodeFixContextBase , info : ObjectLiteralInfo ) {
438471 const importAdder = createImportAdder ( context . sourceFile , context . program , context . preferences , context . host ) ;
439472 const quotePreference = getQuotePreference ( context . sourceFile , context . preferences ) ;
440473 const checker = context . program . getTypeChecker ( ) ;
441474 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 ( ) ;
443476 return factory . createPropertyAssignment ( prop . name , initializer ) ;
444477 } ) ;
445478 changes . replaceNode ( context . sourceFile , info . parentDeclaration , factory . createObjectLiteralExpression ( [ ...info . parentDeclaration . properties , ...props ] , /*multiLine*/ true ) ) ;
446479 }
447480
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 {
449482 if ( type . flags & TypeFlags . AnyOrUnknown ) {
450483 return createUndefined ( ) ;
451484 }
@@ -482,15 +515,15 @@ namespace ts.codefix {
482515 return factory . createNull ( ) ;
483516 }
484517 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 ) ) ;
486519 return expression ?? createUndefined ( ) ;
487520 }
488521 if ( checker . isArrayLikeType ( type ) ) {
489522 return factory . createArrayLiteralExpression ( ) ;
490523 }
491524 if ( isObjectLiteralType ( type ) ) {
492525 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 ( ) ;
494527 return factory . createPropertyAssignment ( prop . name , initializer ) ;
495528 } ) ;
496529 return factory . createObjectLiteralExpression ( props , /*multiLine*/ true ) ;
@@ -526,4 +559,27 @@ namespace ts.codefix {
526559 return ( type . flags & TypeFlags . Object ) &&
527560 ( ( getObjectFlags ( type ) & ObjectFlags . ObjectLiteral ) || ( type . symbol && tryCast ( singleOrUndefined ( type . symbol . declarations ) , isTypeLiteralNode ) ) ) ;
528561 }
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+ }
529585}
0 commit comments