From d5a7dc1053430179212921645ecaae193041dbe6 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 29 Mar 2018 12:54:17 -0700 Subject: [PATCH] fixAddMissingMember: Remove special-casing for 'this' (#22987) * fixAddMissingMember: Remove special-casing for 'this' * Update type of 'call' --- src/services/codefixes/fixAddMissingMember.ts | 47 +++++-------------- src/services/completions.ts | 11 ++--- src/services/utilities.ts | 4 ++ 3 files changed, 21 insertions(+), 41 deletions(-) diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index d8df415a4de..9a3b4854351 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -46,7 +46,7 @@ namespace ts.codefix { }, }); - interface Info { token: Identifier; classDeclaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; inJs: boolean; call: CallExpression; } + interface Info { token: Identifier; classDeclaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; inJs: boolean; call: CallExpression | undefined; } function getInfo(tokenSourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Info | undefined { // The identifier of the missing property. eg: // this.missing = 1; @@ -56,45 +56,22 @@ namespace ts.codefix { return undefined; } - const classAndMakeStatic = getClassAndMakeStatic(token, checker); - if (!classAndMakeStatic) { - return undefined; - } - const { classDeclaration, makeStatic } = classAndMakeStatic; + const { parent } = token; + if (!isPropertyAccessExpression(parent)) return undefined; + + const leftExpressionType = skipConstraint(checker.getTypeAtLocation(parent.expression)); + const { symbol } = leftExpressionType; + const classDeclaration = symbol && symbol.declarations && find(symbol.declarations, isClassLike); + if (!classDeclaration) return undefined; + + const makeStatic = (leftExpressionType as TypeReference).target !== checker.getDeclaredTypeOfSymbol(symbol); const classDeclarationSourceFile = classDeclaration.getSourceFile(); - const inJs = isInJavaScriptFile(classDeclarationSourceFile); - const call = tryCast(token.parent.parent, isCallExpression); + const inJs = isSourceFileJavaScript(classDeclarationSourceFile); + const call = tryCast(parent.parent, isCallExpression); return { token, classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call }; } - function getClassAndMakeStatic(token: Node, checker: TypeChecker): { readonly classDeclaration: ClassLikeDeclaration, readonly makeStatic: boolean } | undefined { - const { parent } = token; - if (!isPropertyAccessExpression(parent)) { - return undefined; - } - - if (parent.expression.kind === SyntaxKind.ThisKeyword) { - const containingClassMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false); - if (!isClassElement(containingClassMemberDeclaration)) { - return undefined; - } - const classDeclaration = containingClassMemberDeclaration.parent; - // Property accesses on `this` in a static method are accesses of a static member. - return isClassLike(classDeclaration) ? { classDeclaration, makeStatic: hasModifier(containingClassMemberDeclaration, ModifierFlags.Static) } : undefined; - } - else { - const leftExpressionType = checker.getTypeAtLocation(parent.expression); - const { symbol } = leftExpressionType; - if (!(symbol && leftExpressionType.flags & TypeFlags.Object && symbol.flags & SymbolFlags.Class)) { - return undefined; - } - const classDeclaration = cast(first(symbol.declarations), isClassLike); - // The expression is a class symbol but the type is not the instance-side. - return { classDeclaration, makeStatic: leftExpressionType !== checker.getDeclaredTypeOfSymbol(symbol) }; - } - } - function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined { const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic)); return changes.length === 0 ? undefined diff --git a/src/services/completions.ts b/src/services/completions.ts index 4284de589bc..a83ca166956 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -462,13 +462,12 @@ namespace ts.Completions { return type && { kind: StringLiteralCompletionKind.Properties, symbols: type.getApparentProperties(), hasIndexSignature: hasIndexSignature(type) }; } - function getStringLiteralTypes(type: Type, typeChecker: TypeChecker, uniques = createMap()): ReadonlyArray { - if (type && type.flags & TypeFlags.TypeParameter) { - type = type.getConstraint(); - } - return type && type.flags & TypeFlags.Union + function getStringLiteralTypes(type: Type | undefined, typeChecker: TypeChecker, uniques = createMap()): ReadonlyArray | undefined { + if (!type) return emptyArray; + type = skipConstraint(type); + return type.flags & TypeFlags.Union ? flatMap((type).types, t => getStringLiteralTypes(t, typeChecker, uniques)) - : type && type.flags & TypeFlags.StringLiteral && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, (type as StringLiteralType).value) + : type.flags & TypeFlags.StringLiteral && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, (type as StringLiteralType).value) ? [type as StringLiteralType] : emptyArray; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index bd32b1a9a4f..0ee06139ea1 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1216,6 +1216,10 @@ namespace ts { } return result; } + + export function skipConstraint(type: Type): Type { + return type.flags & TypeFlags.TypeParameter ? type.getConstraint() : type; + } } // Display-part writer helpers