feat(28491): add QF to declare missing properties (#44576)

This commit is contained in:
Oleksandr T
2021-06-17 11:12:29 -07:00
committed by GitHub
parent 9549928ce9
commit eee34d539c
17 changed files with 488 additions and 26 deletions
+1
View File
@@ -393,6 +393,7 @@ namespace ts {
getDiagnostics,
getGlobalDiagnostics,
getRecursionIdentity,
getUnmatchedProperties,
getTypeOfSymbolAtLocation: (symbol, locationIn) => {
const location = getParseTreeNode(locationIn);
return location ? getTypeOfSymbolAtLocation(symbol, location) : errorType;
+8
View File
@@ -7048,6 +7048,14 @@
"category": "Message",
"code": 95164
},
"Add missing properties": {
"category": "Message",
"code": 95165
},
"Add all missing properties": {
"category": "Message",
"code": 95166
},
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
+1
View File
@@ -4275,6 +4275,7 @@ namespace ts {
/* @internal */ getInstantiationCount(): number;
/* @internal */ getRelationCacheSizes(): { assignable: number, identity: number, subtype: number, strictSubtype: number };
/* @internal */ getRecursionIdentity(type: Type): object | undefined;
/* @internal */ getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator<Symbol>;
/* @internal */ isArrayType(type: Type): boolean;
/* @internal */ isTupleType(type: Type): boolean;
+122 -5
View File
@@ -1,7 +1,9 @@
/* @internal */
namespace ts.codefix {
const fixMissingMember = "fixMissingMember";
const fixMissingProperties = "fixMissingProperties";
const fixMissingFunctionDeclaration = "fixMissingFunctionDeclaration";
const errorCodes = [
Diagnostics.Property_0_does_not_exist_on_type_1.code,
Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code,
@@ -19,6 +21,10 @@ namespace ts.codefix {
if (!info) {
return undefined;
}
if (info.kind === InfoKind.ObjectLiteral) {
const changes = textChanges.ChangeTracker.with(context, t => addObjectLiteralProperties(t, context, info));
return [createCodeFixAction(fixMissingProperties, changes, Diagnostics.Add_missing_properties, fixMissingProperties, Diagnostics.Add_all_missing_properties)];
}
if (info.kind === InfoKind.Function) {
const changes = textChanges.ChangeTracker.with(context, t => addFunctionDeclaration(t, context, info));
return [createCodeFixAction(fixMissingFunctionDeclaration, changes, [Diagnostics.Add_missing_function_declaration_0, info.token.text], fixMissingFunctionDeclaration, Diagnostics.Add_all_missing_function_declarations)];
@@ -29,7 +35,7 @@ namespace ts.codefix {
}
return concatenate(getActionsForMissingMethodDeclaration(context, info), getActionsForMissingMemberDeclaration(context, info));
},
fixIds: [fixMissingMember, fixMissingFunctionDeclaration],
fixIds: [fixMissingMember, fixMissingFunctionDeclaration, fixMissingProperties],
getAllCodeActions: context => {
const { program, fixId } = context;
const checker = program.getTypeChecker();
@@ -48,11 +54,15 @@ namespace ts.codefix {
addFunctionDeclaration(changes, context, info);
}
}
else if (fixId === fixMissingProperties) {
if (info.kind === InfoKind.ObjectLiteral) {
addObjectLiteralProperties(changes, context, info);
}
}
else {
if (info.kind === InfoKind.Enum) {
addEnumMemberDeclaration(changes, checker, info);
}
if (info.kind === InfoKind.ClassOrInterface) {
const { parentDeclaration, token } = info;
const infos = getOrUpdate(typeDeclToMembers, parentDeclaration, () => []);
@@ -92,8 +102,8 @@ namespace ts.codefix {
},
});
const enum InfoKind { Enum, ClassOrInterface, Function }
type Info = EnumInfo | ClassOrInterfaceInfo | FunctionInfo;
const enum InfoKind { Enum, ClassOrInterface, Function, ObjectLiteral }
type Info = EnumInfo | ClassOrInterfaceInfo | FunctionInfo | ObjectLiteralInfo;
interface EnumInfo {
readonly kind: InfoKind.Enum;
@@ -120,6 +130,13 @@ namespace ts.codefix {
readonly parentDeclaration: SourceFile | ModuleDeclaration;
}
interface ObjectLiteralInfo {
readonly kind: InfoKind.ObjectLiteral;
readonly token: Identifier;
readonly properties: Symbol[];
readonly parentDeclaration: ObjectLiteralExpression;
}
function getInfo(sourceFile: SourceFile, tokenPos: number, checker: TypeChecker, program: Program): Info | undefined {
// The identifier of the missing property. eg:
// this.missing = 1;
@@ -130,6 +147,13 @@ namespace ts.codefix {
}
const { parent } = token;
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 (isIdentifier(token) && isCallExpression(parent)) {
return { kind: InfoKind.Function, token, call: parent, sourceFile, modifierFlags: ModifierFlags.None, parentDeclaration: sourceFile };
}
@@ -248,7 +272,7 @@ namespace ts.codefix {
}
function initializePropertyToUndefined(obj: Expression, propertyName: string) {
return factory.createExpressionStatement(factory.createAssignment(factory.createPropertyAccessExpression(obj, propertyName), factory.createIdentifier("undefined")));
return factory.createExpressionStatement(factory.createAssignment(factory.createPropertyAccessExpression(obj, propertyName), createUndefined()));
}
function createActionsForAddMissingMemberInTypeScriptFile(context: CodeFixContext, { parentDeclaration, declSourceFile, modifierFlags, token }: ClassOrInterfaceInfo): CodeFixAction[] | undefined {
@@ -405,4 +429,97 @@ namespace ts.codefix {
const functionDeclaration = createSignatureDeclarationFromCallExpression(SyntaxKind.FunctionDeclaration, context, importAdder, info.call, idText(info.token), info.modifierFlags, info.parentDeclaration) as FunctionDeclaration;
changes.insertNodeAtEndOfScope(info.sourceFile, info.parentDeclaration, functionDeclaration);
}
function addObjectLiteralProperties(changes: textChanges.ChangeTracker, context: CodeFixContextBase, info: ObjectLiteralInfo) {
const importAdder = createImportAdder(context.sourceFile, context.program, context.preferences, context.host);
const quotePreference = getQuotePreference(context.sourceFile, context.preferences);
const checker = context.program.getTypeChecker();
const props = map(info.properties, prop => {
const initializer = prop.valueDeclaration ? tryGetInitializerValueFromType(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));
}
function tryGetInitializerValueFromType(context: CodeFixContextBase, checker: TypeChecker, importAdder: ImportAdder, quotePreference: QuotePreference, type: Type): Expression {
if (type.flags & TypeFlags.AnyOrUnknown) {
return createUndefined();
}
if (type.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) {
return factory.createStringLiteral("", /* isSingleQuote */ quotePreference === QuotePreference.Single);
}
if (type.flags & TypeFlags.Number) {
return factory.createNumericLiteral(0);
}
if (type.flags & TypeFlags.BigInt) {
return factory.createBigIntLiteral("0n");
}
if (type.flags & TypeFlags.Boolean) {
return factory.createFalse();
}
if (type.flags & TypeFlags.EnumLike) {
const enumMember = type.symbol.exports ? firstOrUndefined(arrayFrom(type.symbol.exports.values())) : type.symbol;
const name = checker.symbolToExpression(type.symbol.parent ? type.symbol.parent : type.symbol, SymbolFlags.Value, /*enclosingDeclaration*/ undefined, /*flags*/ undefined);
return enumMember === undefined || name === undefined ? factory.createNumericLiteral(0) : factory.createPropertyAccessExpression(name, checker.symbolToString(enumMember));
}
if (type.flags & TypeFlags.NumberLiteral) {
return factory.createNumericLiteral((type as NumberLiteralType).value);
}
if (type.flags & TypeFlags.BigIntLiteral) {
return factory.createBigIntLiteral((type as BigIntLiteralType).value);
}
if (type.flags & TypeFlags.StringLiteral) {
return factory.createStringLiteral((type as StringLiteralType).value, /* isSingleQuote */ quotePreference === QuotePreference.Single);
}
if (type.flags & TypeFlags.BooleanLiteral) {
return (type === checker.getFalseType() || type === checker.getFalseType(/*fresh*/ true)) ? factory.createFalse() : factory.createTrue();
}
if (type.flags & TypeFlags.Null) {
return factory.createNull();
}
if (type.flags & TypeFlags.Union) {
const expression = firstDefined((type as UnionType).types, t => tryGetInitializerValueFromType(context, checker, importAdder, quotePreference, t));
return expression ?? createUndefined();
}
if (checker.isArrayLikeType(type)) {
return factory.createArrayLiteralExpression();
}
if (isObjectLiteralType(type)) {
const props = map(checker.getPropertiesOfType(type), prop => {
const initializer = prop.valueDeclaration ? tryGetInitializerValueFromType(context, checker, importAdder, quotePreference, checker.getTypeAtLocation(prop.valueDeclaration)) : createUndefined();
return factory.createPropertyAssignment(prop.name, initializer);
});
return factory.createObjectLiteralExpression(props, /*multiLine*/ true);
}
if (getObjectFlags(type) & ObjectFlags.Anonymous) {
const decl = find(type.symbol.declarations || emptyArray, or(isFunctionTypeNode, isMethodSignature, isMethodDeclaration));
if (decl === undefined) return createUndefined();
const signature = checker.getSignaturesOfType(type, SignatureKind.Call);
if (signature === undefined) return createUndefined();
const func = createSignatureDeclarationFromSignature(SyntaxKind.FunctionExpression, context, quotePreference, signature[0],
createStubbedBody(Diagnostics.Function_not_implemented.message, quotePreference), /*name*/ undefined, /*modifiers*/ undefined, /*optional*/ undefined, /*enclosingDeclaration*/ undefined, importAdder) as FunctionExpression | undefined;
return func ?? createUndefined();
}
if (getObjectFlags(type) & ObjectFlags.Class) {
const classDeclaration = getClassLikeDeclarationOfSymbol(type.symbol);
if (classDeclaration === undefined || hasAbstractModifier(classDeclaration)) return createUndefined();
const constructorDeclaration = getFirstConstructorWithBody(classDeclaration);
if (constructorDeclaration && length(constructorDeclaration.parameters)) return createUndefined();
return factory.createNewExpression(factory.createIdentifier(type.symbol.name), /*typeArguments*/ undefined, /*argumentsArray*/ undefined);
}
return createUndefined();
}
function createUndefined() {
return factory.createIdentifier("undefined");
}
function isObjectLiteralType(type: Type) {
return (type.flags & TypeFlags.Object) &&
((getObjectFlags(type) & ObjectFlags.ObjectLiteral) || (type.symbol && tryCast(singleOrUndefined(type.symbol.declarations), isTypeLiteralNode)));
}
}
+22 -21
View File
@@ -145,27 +145,28 @@ namespace ts.codefix {
}
function outputMethod(quotePreference: QuotePreference, signature: Signature, modifiers: NodeArray<Modifier> | undefined, name: PropertyName, body?: Block): void {
const method = signatureToMethodDeclaration(context, quotePreference, signature, enclosingDeclaration, modifiers, name, optional, body, importAdder);
const method = createSignatureDeclarationFromSignature(SyntaxKind.MethodDeclaration, context, quotePreference, signature, body, name, modifiers, optional, enclosingDeclaration, importAdder);
if (method) addClassElement(method);
}
}
function signatureToMethodDeclaration(
export function createSignatureDeclarationFromSignature(
kind: SyntaxKind.MethodDeclaration | SyntaxKind.FunctionExpression | SyntaxKind.ArrowFunction,
context: TypeConstructionContext,
quotePreference: QuotePreference,
signature: Signature,
enclosingDeclaration: ClassLikeDeclaration,
modifiers: NodeArray<Modifier> | undefined,
name: PropertyName,
optional: boolean,
body: Block | undefined,
importAdder: ImportAdder | undefined,
): MethodDeclaration | undefined {
name: PropertyName | undefined,
modifiers: NodeArray<Modifier> | undefined,
optional: boolean | undefined,
enclosingDeclaration: Node | undefined,
importAdder: ImportAdder | undefined
) {
const program = context.program;
const checker = program.getTypeChecker();
const scriptTarget = getEmitScriptTarget(program.getCompilerOptions());
const flags = NodeBuilderFlags.NoTruncation | NodeBuilderFlags.NoUndefinedOptionalParameterType | NodeBuilderFlags.SuppressAnyReturnType | (quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : 0);
const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, flags, getNoopSymbolTrackerWithResolver(context)) as MethodDeclaration;
const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, kind, enclosingDeclaration, flags, getNoopSymbolTrackerWithResolver(context)) as ArrowFunction | FunctionExpression | MethodDeclaration;
if (!signatureDeclaration) {
return undefined;
}
@@ -233,18 +234,18 @@ namespace ts.codefix {
}
}
return factory.updateMethodDeclaration(
signatureDeclaration,
/*decorators*/ undefined,
modifiers,
signatureDeclaration.asteriskToken,
name,
optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
typeParameters,
parameters,
type,
body
);
const questionToken = optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined;
const asteriskToken = signatureDeclaration.asteriskToken;
if (isFunctionExpression(signatureDeclaration)) {
return factory.updateFunctionExpression(signatureDeclaration, modifiers, signatureDeclaration.asteriskToken, tryCast(name, isIdentifier), typeParameters, parameters, type, body ?? signatureDeclaration.body);
}
if (isArrowFunction(signatureDeclaration)) {
return factory.updateArrowFunction(signatureDeclaration, modifiers, typeParameters, parameters, type, signatureDeclaration.equalsGreaterThanToken, body ?? signatureDeclaration.body);
}
if (isMethodDeclaration(signatureDeclaration)) {
return factory.updateMethodDeclaration(signatureDeclaration, /* decorators */ undefined, modifiers, asteriskToken, name ?? factory.createIdentifier(""), questionToken, typeParameters, parameters, type, body);
}
return undefined;
}
export function createSignatureDeclarationFromCallExpression(
@@ -0,0 +1,39 @@
/// <reference path='fourslash.ts' />
////interface Foo {
//// a: number;
//// b: string;
//// c: 1;
//// d: "d";
//// e: "e1" | "e2";
//// f(x: number, y: number): void;
//// g: (x: number, y: number) => void;
//// h: number[];
//// i: bigint;
//// j: undefined | "special-string";
//// k: `--${string}`;
////}
////[|const foo: Foo = {}|];
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newRangeContent:
`const foo: Foo = {
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.");
},
h: [],
i: 0n,
j: "special-string",
k: ""
}`
});
@@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />
////type T = { x: number; };
////interface I {
//// a: T
////}
////[|const foo: I = {};|]
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newRangeContent:
`const foo: I = {
a: {
x: 0
}
};`
});
@@ -0,0 +1,22 @@
/// <reference path='fourslash.ts' />
////interface Foo {
//// a: `--${string}`;
//// b: string;
//// c: "a" | "b"
////}
////[|const foo: Foo = {}|];
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
preferences: {
quotePreference: "single"
},
newRangeContent:
`const foo: Foo = {
a: '',
b: '',
c: 'a'
}`
});
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />
////interface Foo {
//// a: number;
//// b: string;
//// c: any;
////}
////[|class C {
//// public c: Foo = {};
////}|]
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newRangeContent:
`class C {
public c: Foo = {
a: 0,
b: "",
c: undefined
};
}`
});
@@ -0,0 +1,19 @@
/// <reference path='fourslash.ts' />
////interface Foo {
//// a: number;
//// b: string;
////}
////[|function fn(foo: Foo = {}) {
////}|]
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newRangeContent:
`function fn(foo: Foo = {
a: 0,
b: ""
}) {
}`
});
@@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />
////interface Foo {
//// a: number;
//// b: string;
////}
////[|const foo: Foo = { a: 10 }|];
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newRangeContent:
`const foo: Foo = {
a: 10,
b: ""
}`
});
@@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />
////type T = {
//// a: null;
////}
////
////[|const foo: T = {}|];
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newRangeContent:
`const foo: T = {
a: null
}`
});
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />
////interface I {
//// x: number;
//// y: number;
////}
////class C {
//// public p: number;
//// m(x: number, y: I) {}
////}
////[|const foo: C = {}|];
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newRangeContent:
`const foo: C = {
p: 0,
m: function(x: number, y: I): void {
throw new Error("Function not implemented.");
}
}`
});
@@ -0,0 +1,27 @@
/// <reference path='fourslash.ts' />
////enum E1 {
//// A, B
////}
////enum E2 {
//// A
////}
////enum E3 {
////}
////interface I {
//// x: E1;
//// y: E2;
//// z: E3;
////}
////[|const foo: I = {};|]
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newRangeContent:
`const foo: I = {
x: E1.A,
y: E2.A,
z: 0
};`
});
@@ -0,0 +1,29 @@
/// <reference path='fourslash.ts' />
////class A {
//// constructor() {}
////}
////
////abstract class B {}
////
////class C {
//// constructor(a: string, b: number, c: A) {}
////}
////
////interface I {
//// a: A;
//// b: B;
//// c: C;
////}
////[|const foo: I = {};|]
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newRangeContent:
`const foo: I = {
a: new A,
b: undefined,
c: undefined
};`
});
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />
////interface I {
//// a: {
//// x: number;
//// y: { z: string; };
//// }
////}
////[|const foo: I = {};|]
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newRangeContent:
`const foo: I = {
a: {
x: 0,
y: {
z: ""
}
}
};`
});
@@ -0,0 +1,78 @@
/// <reference path='fourslash.ts' />
////interface I1 {
//// a: number;
//// b: string;
//// c: 1;
//// d: "d";
//// e: "e1" | "e2";
//// f(x: number, y: number): void;
//// g: (x: number, y: number) => void;
////}
////interface I2 {
//// a: unknown;
//// b: any;
////}
////const a: I1 = {};
////const b: I2 = {};
////class C {
//// public c: I1 = {};
////}
////function fn(foo: I2 = {}) {
////}
verify.codeFixAll({
fixId: "fixMissingProperties",
fixAllDescription: ts.Diagnostics.Add_all_missing_properties.message,
newFileContent:
`interface I1 {
a: number;
b: string;
c: 1;
d: "d";
e: "e1" | "e2";
f(x: number, y: number): void;
g: (x: number, y: number) => void;
}
interface I2 {
a: unknown;
b: any;
}
const a: I1 = {
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.");
}
};
const b: I2 = {
a: undefined,
b: undefined
};
class C {
public c: I1 = {
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.");
}
};
}
function fn(foo: I2 = {
a: undefined,
b: undefined
}) {
}`
});