From 8694c5d6bd6edefda2e07f32e42eadf3f172f14f Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 11 Dec 2017 07:40:01 -0800 Subject: [PATCH] Don't add completion for method if the `this` parameter doesn't match --- src/compiler/checker.ts | 25 +++++++++++++++---- src/compiler/types.ts | 2 ++ src/services/completions.ts | 6 ++--- .../completionsMethodWithThisParameter.ts | 15 +++++++++++ 4 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 tests/cases/fourslash/completionsMethodWithThisParameter.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4cc0bb7953184..006c0d960f572 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -185,7 +185,11 @@ namespace ts { }, isValidPropertyAccess: (node, propertyName) => { node = getParseTreeNode(node, isPropertyAccessOrQualifiedName); - return node ? isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)) : false; + return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)); + }, + isValidPropertyAccessForCompletions: (node, type, property) => { + node = getParseTreeNode(node, isPropertyAccessExpression); + return !!node && isValidPropertyAccessForCompletions(node, type, property); }, getSignatureFromDeclaration: declaration => { declaration = getParseTreeNode(declaration, isFunctionLike); @@ -16006,13 +16010,24 @@ namespace ts { } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: __String): boolean { - const left = node.kind === SyntaxKind.PropertyAccessExpression - ? (node).expression - : (node).left; - + const left = node.kind === SyntaxKind.PropertyAccessExpression ? node.expression : node.left; return isValidPropertyAccessWithType(node, left, propertyName, getWidenedType(checkExpression(left))); } + function isValidPropertyAccessForCompletions(node: PropertyAccessExpression, type: Type, property: Symbol): boolean { + return isValidPropertyAccessWithType(node, node.expression, property.escapedName, type) + && (!(property.flags & ts.SymbolFlags.Method) || isValidMethodAccess(property, type)); + } + function isValidMethodAccess(method: Symbol, type: Type) { + const propType = getTypeOfFuncClassEnumModule(method); + const signatures = getSignaturesOfType(propType, SignatureKind.Call); + Debug.assert(signatures.length !== 0); + return signatures.some(sig => { + const thisType = getThisTypeOfSignature(sig); + return !thisType || isTypeAssignableTo(type, thisType); + }); + } + function isValidPropertyAccessWithType( node: PropertyAccessExpression | QualifiedName, left: LeftHandSideExpression | QualifiedName, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index db53db2aecbe2..aec03264fafec 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2753,6 +2753,8 @@ namespace ts { getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined; isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; + /** Exclude accesses to private properties or methods with a `this` parameter that `type` doesn't satisfy. */ + /* @internal */ isValidPropertyAccessForCompletions(node: PropertyAccessExpression, type: Type, property: Symbol): boolean; /** Follow all aliases to get the original symbol. */ getAliasedSymbol(symbol: Symbol): Symbol; /** Follow a *single* alias to get the immediately aliased symbol. */ diff --git a/src/services/completions.ts b/src/services/completions.ts index d893f4852e4f8..bb6a7df6b6842 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -918,9 +918,8 @@ namespace ts.Completions { symbols.push(...getPropertiesForCompletion(type, typeChecker, /*isForAccess*/ true)); } else { - // Filter private properties for (const symbol of type.getApparentProperties()) { - if (typeChecker.isValidPropertyAccess((node.parent), symbol.name)) { + if (typeChecker.isValidPropertyAccessForCompletions((node.parent), type, symbol)) { symbols.push(symbol); } } @@ -2119,8 +2118,7 @@ namespace ts.Completions { /** * Gets all properties on a type, but if that type is a union of several types, - * tries to only include those types which declare properties, not methods. - * This ensures that we don't try providing completions for all the methods on e.g. Array. + * excludes array-like types or callable/constructable types. */ function getPropertiesForCompletion(type: Type, checker: TypeChecker, isForAccess: boolean): Symbol[] { if (!(type.flags & TypeFlags.Union)) { diff --git a/tests/cases/fourslash/completionsMethodWithThisParameter.ts b/tests/cases/fourslash/completionsMethodWithThisParameter.ts new file mode 100644 index 0000000000000..b455ba4f5b80f --- /dev/null +++ b/tests/cases/fourslash/completionsMethodWithThisParameter.ts @@ -0,0 +1,15 @@ +/// + +////class A { +//// value: T; // Make the type parameter actually matter +//// ms(this: A) {} +//// mo(this: A<{}>) {} +////} +//// +////const s = new A(); +////const n = new A(); +////s./*s*/; +////n./*n*/; + +verify.completionsAt("s", ["value", "ms", "mo"]); +verify.completionsAt("n", ["value", "mo"]); \ No newline at end of file