diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts
index e4bd2e43623fb..b709bd75340d0 100644
--- a/src/services/codefixes/fixAddMissingMember.ts
+++ b/src/services/codefixes/fixAddMissingMember.ts
@@ -11,6 +11,7 @@ namespace ts.codefix {
Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2.code,
Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2.code,
Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more.code,
+ Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code,
Diagnostics.Cannot_find_name_0.code
];
@@ -18,7 +19,7 @@ namespace ts.codefix {
errorCodes,
getCodeActions(context) {
const typeChecker = context.program.getTypeChecker();
- const info = getInfo(context.sourceFile, context.span.start, typeChecker, context.program);
+ const info = getInfo(context.sourceFile, context.span.start, context.errorCode, typeChecker, context.program);
if (!info) {
return undefined;
}
@@ -49,7 +50,7 @@ namespace ts.codefix {
return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
eachDiagnostic(context, errorCodes, diag => {
- const info = getInfo(diag.file, diag.start, checker, context.program);
+ const info = getInfo(diag.file, diag.start, diag.code, checker, context.program);
if (!info || !addToSeen(seen, getNodeId(info.parentDeclaration) + "#" + info.token.text)) {
return;
}
@@ -139,6 +140,7 @@ namespace ts.codefix {
readonly token: Identifier;
readonly properties: Symbol[];
readonly parentDeclaration: ObjectLiteralExpression;
+ readonly indentation?: number;
}
interface JsxAttributesInfo {
@@ -148,43 +150,53 @@ namespace ts.codefix {
readonly parentDeclaration: JsxOpeningLikeElement;
}
- function getInfo(sourceFile: SourceFile, tokenPos: number, checker: TypeChecker, program: Program): Info | undefined {
+ function getInfo(sourceFile: SourceFile, tokenPos: number, errorCode: number, checker: TypeChecker, program: Program): Info | undefined {
// The identifier of the missing property. eg:
// this.missing = 1;
// ^^^^^^^
const token = getTokenAtPosition(sourceFile, tokenPos);
- if (!isIdentifier(token) && !isPrivateIdentifier(token)) {
- return undefined;
+ const parent = token.parent;
+
+ if (errorCode === Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code) {
+ if (!(token.kind === SyntaxKind.OpenBraceToken && isObjectLiteralExpression(parent) && isCallExpression(parent.parent))) return undefined;
+
+ const argIndex = findIndex(parent.parent.arguments, arg => arg === parent);
+ if (argIndex < 0) return undefined;
+
+ const signature = singleOrUndefined(checker.getSignaturesOfType(checker.getTypeAtLocation(parent.parent.expression), SignatureKind.Call));
+ if (!(signature && signature.declaration && signature.parameters[argIndex])) return undefined;
+
+ const param = signature.parameters[argIndex].valueDeclaration;
+ if (!(param && isParameter(param) && isIdentifier(param.name))) return undefined;
+
+ const properties = arrayFrom(checker.getUnmatchedProperties(checker.getTypeAtLocation(parent), checker.getTypeAtLocation(param), /* requireOptionalProperties */ false, /* matchDiscriminantProperties */ false));
+ if (!length(properties)) return undefined;
+ return { kind: InfoKind.ObjectLiteral, token: param.name, properties, indentation: 0, parentDeclaration: parent };
}
- const { parent } = token;
+ if (!isMemberName(token)) return undefined;
+
if (isIdentifier(token) && hasInitializer(parent) && parent.initializer && isObjectLiteralExpression(parent.initializer)) {
const properties = arrayFrom(checker.getUnmatchedProperties(checker.getTypeAtLocation(parent.initializer), checker.getTypeAtLocation(token), /* requireOptionalProperties */ false, /* matchDiscriminantProperties */ false));
- if (length(properties)) {
- return { kind: InfoKind.ObjectLiteral, token, properties, parentDeclaration: parent.initializer };
- }
+ if (!length(properties)) return undefined;
+ return { kind: InfoKind.ObjectLiteral, token, properties, indentation: undefined, parentDeclaration: parent.initializer };
}
if (isIdentifier(token) && isJsxOpeningLikeElement(token.parent)) {
const attributes = getUnmatchedAttributes(checker, token.parent);
- if (length(attributes)) {
- return { kind: InfoKind.JsxAttributes, token, attributes, parentDeclaration: token.parent };
- }
+ if (!length(attributes)) return undefined;
+ return { kind: InfoKind.JsxAttributes, token, attributes, parentDeclaration: token.parent };
}
if (isIdentifier(token) && isCallExpression(parent)) {
return { kind: InfoKind.Function, token, call: parent, sourceFile, modifierFlags: ModifierFlags.None, parentDeclaration: sourceFile };
}
- if (!isPropertyAccessExpression(parent)) {
- return undefined;
- }
+ if (!isPropertyAccessExpression(parent)) return undefined;
const leftExpressionType = skipConstraint(checker.getTypeAtLocation(parent.expression));
- const { symbol } = leftExpressionType;
- if (!symbol || !symbol.declarations) {
- return undefined;
- }
+ const symbol = leftExpressionType.symbol;
+ if (!symbol || !symbol.declarations) return undefined;
if (isIdentifier(token) && isCallExpression(parent.parent)) {
const moduleDeclaration = find(symbol.declarations, isModuleDeclaration);
@@ -194,9 +206,7 @@ namespace ts.codefix {
}
const moduleSourceFile = find(symbol.declarations, isSourceFile);
- if (sourceFile.commonJsModuleIndicator) {
- return;
- }
+ if (sourceFile.commonJsModuleIndicator) return undefined;
if (moduleSourceFile && !isSourceFileFromLibrary(program, moduleSourceFile)) {
return { kind: InfoKind.Function, token, call: parent.parent, sourceFile: moduleSourceFile, modifierFlags: ModifierFlags.Export, parentDeclaration: moduleSourceFile };
@@ -205,17 +215,13 @@ namespace ts.codefix {
const classDeclaration = find(symbol.declarations, isClassLike);
// Don't suggest adding private identifiers to anything other than a class.
- if (!classDeclaration && isPrivateIdentifier(token)) {
- return undefined;
- }
+ if (!classDeclaration && isPrivateIdentifier(token)) return undefined;
// Prefer to change the class instead of the interface if they are merged
const classOrInterface = classDeclaration || find(symbol.declarations, isInterfaceDeclaration);
if (classOrInterface && !isSourceFileFromLibrary(program, classOrInterface.getSourceFile())) {
const makeStatic = ((leftExpressionType as TypeReference).target || leftExpressionType) !== checker.getDeclaredTypeOfSymbol(symbol);
- if (makeStatic && (isPrivateIdentifier(token) || isInterfaceDeclaration(classOrInterface))) {
- return undefined;
- }
+ if (makeStatic && (isPrivateIdentifier(token) || isInterfaceDeclaration(classOrInterface))) return undefined;
const declSourceFile = classOrInterface.getSourceFile();
const modifierFlags = (makeStatic ? ModifierFlags.Static : 0) | (startsWithUnderscore(token.text) ? ModifierFlags.Private : 0);
@@ -475,7 +481,12 @@ namespace ts.codefix {
const initializer = prop.valueDeclaration ? tryGetValueFromType(context, checker, importAdder, quotePreference, checker.getTypeAtLocation(prop.valueDeclaration)) : createUndefined();
return factory.createPropertyAssignment(prop.name, initializer);
});
- changes.replaceNode(context.sourceFile, info.parentDeclaration, factory.createObjectLiteralExpression([...info.parentDeclaration.properties, ...props], /*multiLine*/ true));
+ const options = {
+ leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude,
+ trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude,
+ indentation: info.indentation
+ };
+ changes.replaceNode(context.sourceFile, info.parentDeclaration, factory.createObjectLiteralExpression([...info.parentDeclaration.properties, ...props], /*multiLine*/ true), options);
}
function tryGetValueFromType(context: CodeFixContextBase, checker: TypeChecker, importAdder: ImportAdder, quotePreference: QuotePreference, type: Type): Expression {
diff --git a/tests/cases/fourslash/codeFixAddMissingProperties12.ts b/tests/cases/fourslash/codeFixAddMissingProperties12.ts
new file mode 100644
index 0000000000000..418e8b542bf9c
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAddMissingProperties12.ts
@@ -0,0 +1,18 @@
+///
+
+////interface Foo {
+//// a: number;
+//// b: number;
+////}
+////function f(foo: Foo) {}
+////[|f({})|];
+
+verify.codeFix({
+ index: 0,
+ description: ts.Diagnostics.Add_missing_properties.message,
+ newRangeContent:
+`f({
+ a: 0,
+ b: 0
+})`
+});
diff --git a/tests/cases/fourslash/codeFixAddMissingProperties13.ts b/tests/cases/fourslash/codeFixAddMissingProperties13.ts
new file mode 100644
index 0000000000000..19ca1b4eae0c3
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAddMissingProperties13.ts
@@ -0,0 +1,22 @@
+///
+
+////interface Foo {
+//// a: number;
+//// b: number;
+//// c: () => void;
+////}
+////function f(foo: Foo) {}
+////[|f({ a: 10 })|];
+
+verify.codeFix({
+ index: 0,
+ description: ts.Diagnostics.Add_missing_properties.message,
+ newRangeContent:
+`f({
+ a: 10,
+ b: 0,
+ c: function(): void {
+ throw new Error("Function not implemented.");
+ }
+})`
+});
diff --git a/tests/cases/fourslash/codeFixAddMissingProperties14.ts b/tests/cases/fourslash/codeFixAddMissingProperties14.ts
new file mode 100644
index 0000000000000..7e0ef4fa81fe7
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAddMissingProperties14.ts
@@ -0,0 +1,18 @@
+///
+
+////interface Foo {
+//// a: number;
+//// b: number;
+////}
+////function f(a: number, b: number, c: Foo) {}
+////[|f(1, 2, {})|];
+
+verify.codeFix({
+ index: 0,
+ description: ts.Diagnostics.Add_missing_properties.message,
+ newRangeContent:
+`f(1, 2, {
+ a: 0,
+ b: 0
+})`
+});
diff --git a/tests/cases/fourslash/codeFixAddMissingProperties_all.ts b/tests/cases/fourslash/codeFixAddMissingProperties_all.ts
index f0e842e005530..78c7e6d165674 100644
--- a/tests/cases/fourslash/codeFixAddMissingProperties_all.ts
+++ b/tests/cases/fourslash/codeFixAddMissingProperties_all.ts
@@ -18,8 +18,9 @@
////class C {
//// public c: I1 = {};
////}
-////function fn(foo: I2 = {}) {
-////}
+////function fn1(foo: I2 = {}) {}
+////function fn2(a: I1) {}
+////fn2({});
verify.codeFixAll({
fixId: "fixMissingProperties",
@@ -70,9 +71,22 @@ class C {
}
};
}
-function fn(foo: I2 = {
+function fn1(foo: I2 = {
a: undefined,
b: undefined
-}) {
-}`
+}) {}
+function fn2(a: I1) {}
+fn2({
+ a: 0,
+ b: "",
+ c: 1,
+ d: "d",
+ e: "e1",
+ f: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ g: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ }
+});`
});