Skip to content

Commit edad4a1

Browse files
gabrittoBobobUnicorn
authored andcommitted
Reuse checker's property accessibility check for completions (microsoft#45388)
* add test for completions crash * WIP: experiment * remove comments * add call to getParseTreeNode * Revert "add call to getParseTreeNode" This reverts commit 8dd1cf6. * Fix comments * minor fixes * fix formatting * rename type to containingType
1 parent 21a4a77 commit edad4a1

File tree

4 files changed

+127
-39
lines changed

4 files changed

+127
-39
lines changed

src/compiler/checker.ts

Lines changed: 104 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,7 @@ namespace ts {
713713

714714
getLocalTypeParametersOfClassOrInterfaceOrTypeAlias,
715715
isDeclarationVisible,
716+
isPropertyAccessible,
716717
};
717718

718719
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined {
@@ -27284,11 +27285,30 @@ namespace ts {
2728427285
node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement,
2728527286
isSuper: boolean, writing: boolean, type: Type, prop: Symbol, reportError = true): boolean {
2728627287

27287-
const flags = getDeclarationModifierFlagsFromSymbol(prop, writing);
27288-
const errorNode = node.kind === SyntaxKind.QualifiedName ? node.right :
27288+
const errorNode = !reportError ? undefined :
27289+
node.kind === SyntaxKind.QualifiedName ? node.right :
2728927290
node.kind === SyntaxKind.ImportType ? node :
2729027291
node.kind === SyntaxKind.BindingElement && node.propertyName ? node.propertyName : node.name;
2729127292

27293+
return checkPropertyAccessibilityAtLocation(node, isSuper, writing, type, prop, errorNode);
27294+
}
27295+
27296+
/**
27297+
* Check whether the requested property can be accessed at the requested location.
27298+
* Returns true if node is a valid property access, and false otherwise.
27299+
* @param location The location node where we want to check if the property is accessible.
27300+
* @param isSuper True if the access is from `super.`.
27301+
* @param writing True if this is a write property access, false if it is a read property access.
27302+
* @param containingType The type of the object whose property is being accessed. (Not the type of the property.)
27303+
* @param prop The symbol for the property being accessed.
27304+
* @param errorNode The node where we should report an invalid property access error, or undefined if we should not report errors.
27305+
*/
27306+
function checkPropertyAccessibilityAtLocation(location: Node,
27307+
isSuper: boolean, writing: boolean,
27308+
containingType: Type, prop: Symbol, errorNode?: Node): boolean {
27309+
27310+
const flags = getDeclarationModifierFlagsFromSymbol(prop, writing);
27311+
2729227312
if (isSuper) {
2729327313
// TS 1.0 spec (April 2014): 4.8.2
2729427314
// - In a constructor, instance member function, instance member accessor, or
@@ -27299,7 +27319,7 @@ namespace ts {
2729927319
// a super property access is permitted and must specify a public static member function of the base class.
2730027320
if (languageVersion < ScriptTarget.ES2015) {
2730127321
if (symbolHasNonMethodDeclaration(prop)) {
27302-
if (reportError) {
27322+
if (errorNode) {
2730327323
error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword);
2730427324
}
2730527325
return false;
@@ -27310,20 +27330,26 @@ namespace ts {
2731027330
// This error could mask a private property access error. But, a member
2731127331
// cannot simultaneously be private and abstract, so this will trigger an
2731227332
// additional error elsewhere.
27313-
if (reportError) {
27314-
error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(getDeclaringClass(prop)!));
27333+
if (errorNode) {
27334+
error(errorNode,
27335+
Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression,
27336+
symbolToString(prop),
27337+
typeToString(getDeclaringClass(prop)!));
2731527338
}
2731627339
return false;
2731727340
}
2731827341
}
2731927342

2732027343
// Referencing abstract properties within their own constructors is not allowed
2732127344
if ((flags & ModifierFlags.Abstract) && symbolHasNonMethodDeclaration(prop) &&
27322-
(isThisProperty(node) || isThisInitializedObjectBindingExpression(node) || isObjectBindingPattern(node.parent) && isThisInitializedDeclaration(node.parent.parent))) {
27345+
(isThisProperty(location) || isThisInitializedObjectBindingExpression(location) || isObjectBindingPattern(location.parent) && isThisInitializedDeclaration(location.parent.parent))) {
2732327346
const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!);
27324-
if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(node)) {
27325-
if (reportError) {
27326-
error(errorNode, Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, symbolToString(prop), getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!)); // TODO: GH#18217
27347+
if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(location)) {
27348+
if (errorNode) {
27349+
error(errorNode,
27350+
Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor,
27351+
symbolToString(prop),
27352+
getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!));
2732727353
}
2732827354
return false;
2732927355
}
@@ -27339,9 +27365,12 @@ namespace ts {
2733927365
// Private property is accessible if the property is within the declaring class
2734027366
if (flags & ModifierFlags.Private) {
2734127367
const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!)!;
27342-
if (!isNodeWithinClass(node, declaringClassDeclaration)) {
27343-
if (reportError) {
27344-
error(errorNode, Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, symbolToString(prop), typeToString(getDeclaringClass(prop)!));
27368+
if (!isNodeWithinClass(location, declaringClassDeclaration)) {
27369+
if (errorNode) {
27370+
error(errorNode,
27371+
Diagnostics.Property_0_is_private_and_only_accessible_within_class_1,
27372+
symbolToString(prop),
27373+
typeToString(getDeclaringClass(prop)!));
2734527374
}
2734627375
return false;
2734727376
}
@@ -27357,7 +27386,7 @@ namespace ts {
2735727386

2735827387
// Find the first enclosing class that has the declaring classes of the protected constituents
2735927388
// of the property as base classes
27360-
let enclosingClass = forEachEnclosingClass(node, enclosingDeclaration => {
27389+
let enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => {
2736127390
const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!) as InterfaceType;
2736227391
return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing) ? enclosingClass : undefined;
2736327392
});
@@ -27366,9 +27395,12 @@ namespace ts {
2736627395
// allow PropertyAccessibility if context is in function with this parameter
2736727396
// static member access is disallow
2736827397
let thisParameter: ParameterDeclaration | undefined;
27369-
if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(node)) || !thisParameter.type) {
27370-
if (reportError) {
27371-
error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, symbolToString(prop), typeToString(getDeclaringClass(prop) || type));
27398+
if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(location)) || !thisParameter.type) {
27399+
if (errorNode) {
27400+
error(errorNode,
27401+
Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses,
27402+
symbolToString(prop),
27403+
typeToString(getDeclaringClass(prop) || containingType));
2737227404
}
2737327405
return false;
2737427406
}
@@ -27380,13 +27412,15 @@ namespace ts {
2738027412
if (flags & ModifierFlags.Static) {
2738127413
return true;
2738227414
}
27383-
if (type.flags & TypeFlags.TypeParameter) {
27415+
if (containingType.flags & TypeFlags.TypeParameter) {
2738427416
// get the original type -- represented as the type constraint of the 'this' type
27385-
type = (type as TypeParameter).isThisType ? getConstraintOfTypeParameter(type as TypeParameter)! : getBaseConstraintOfType(type as TypeParameter)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined
27417+
containingType = (containingType as TypeParameter).isThisType ? getConstraintOfTypeParameter(containingType as TypeParameter)! : getBaseConstraintOfType(containingType as TypeParameter)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined
2738627418
}
27387-
if (!type || !hasBaseType(type, enclosingClass)) {
27388-
if (reportError) {
27389-
error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1_This_is_an_instance_of_class_2, symbolToString(prop), typeToString(enclosingClass), typeToString(type));
27419+
if (!containingType || !hasBaseType(containingType, enclosingClass)) {
27420+
if (errorNode) {
27421+
error(errorNode,
27422+
Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1_This_is_an_instance_of_class_2,
27423+
symbolToString(prop), typeToString(enclosingClass), typeToString(containingType));
2739027424
}
2739127425
return false;
2739227426
}
@@ -28110,8 +28144,22 @@ namespace ts {
2811028144
}
2811128145
}
2811228146

28147+
/**
28148+
* Checks if an existing property access is valid for completions purposes.
28149+
* @param node a property access-like node where we want to check if we can access a property.
28150+
* This node does not need to be an access of the property we are checking.
28151+
* e.g. in completions, this node will often be an incomplete property access node, as in `foo.`.
28152+
* Besides providing a location (i.e. scope) used to check property accessibility, we use this node for
28153+
* computing whether this is a `super` property access.
28154+
* @param type the type whose property we are checking.
28155+
* @param property the accessed property's symbol.
28156+
*/
2811328157
function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean {
28114-
return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type);
28158+
return isPropertyAccessible(node,
28159+
node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword,
28160+
/* isWrite */ false,
28161+
type,
28162+
property);
2811528163
// Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context.
2811628164
}
2811728165

@@ -28121,19 +28169,45 @@ namespace ts {
2812128169
propertyName: __String,
2812228170
type: Type): boolean {
2812328171

28172+
// Short-circuiting for improved performance.
2812428173
if (type === errorType || isTypeAny(type)) {
2812528174
return true;
2812628175
}
28176+
2812728177
const prop = getPropertyOfType(type, propertyName);
28128-
if (prop) {
28129-
if (prop.valueDeclaration && isPrivateIdentifierClassElementDeclaration(prop.valueDeclaration)) {
28130-
const declClass = getContainingClass(prop.valueDeclaration);
28131-
return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass);
28132-
}
28133-
return checkPropertyAccessibility(node, isSuper, /*writing*/ false, type, prop, /* reportError */ false);
28178+
return !!prop && isPropertyAccessible(node, isSuper, /* isWrite */ false, type, prop);
28179+
}
28180+
28181+
/**
28182+
* Checks if a property can be accessed in a location.
28183+
* The location is given by the `node` parameter.
28184+
* The node does not need to be a property access.
28185+
* @param node location where to check property accessibility
28186+
* @param isSuper whether to consider this a `super` property access, e.g. `super.foo`.
28187+
* @param isWrite whether this is a write access, e.g. `++foo.x`.
28188+
* @param containingType type where the property comes from.
28189+
* @param property property symbol.
28190+
*/
28191+
function isPropertyAccessible(
28192+
node: Node,
28193+
isSuper: boolean,
28194+
isWrite: boolean,
28195+
containingType: Type,
28196+
property: Symbol): boolean {
28197+
28198+
// Short-circuiting for improved performance.
28199+
if (containingType === errorType || isTypeAny(containingType)) {
28200+
return true;
28201+
}
28202+
28203+
// A #private property access in an optional chain is an error dealt with by the parser.
28204+
// The checker does not check for it, so we need to do our own check here.
28205+
if (property.valueDeclaration && isPrivateIdentifierClassElementDeclaration(property.valueDeclaration)) {
28206+
const declClass = getContainingClass(property.valueDeclaration);
28207+
return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass);
2813428208
}
28135-
// In js files properties of unions are allowed in completion
28136-
return isInJSFile(node) && (type.flags & TypeFlags.Union) !== 0 && (type as UnionType).types.some(elementType => isValidPropertyAccessWithType(node, isSuper, propertyName, elementType));
28209+
28210+
return checkPropertyAccessibilityAtLocation(node, isSuper, isWrite, containingType, property);
2813728211
}
2813828212

2813928213
/**

src/compiler/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4216,7 +4216,7 @@ namespace ts {
42164216

42174217
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
42184218
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: string): boolean;
4219-
/** Exclude accesses to private properties or methods with a `this` parameter that `type` doesn't satisfy. */
4219+
/** Exclude accesses to private properties. */
42204220
/* @internal */ isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean;
42214221
/** Follow all aliases to get the original symbol. */
42224222
getAliasedSymbol(symbol: Symbol): Symbol;
@@ -4355,6 +4355,7 @@ namespace ts {
43554355

43564356
/* @internal */ getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): readonly TypeParameter[] | undefined;
43574357
/* @internal */ isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean;
4358+
/* @internal */ isPropertyAccessible(node: Node, isSuper: boolean, isWrite: boolean, containingType: Type, property: Symbol): boolean;
43584359
}
43594360

43604361
/* @internal */

src/services/completions.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2216,14 +2216,9 @@ namespace ts.Completions {
22162216
if (canGetType) {
22172217
const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer);
22182218
if (!typeForObject) return GlobalsSearch.Fail;
2219-
// In a binding pattern, get only known properties (unless in the same scope).
2220-
// Everywhere else we will get all possible properties.
2221-
const containerClass = getContainingClass(objectLikeContainer);
2222-
typeMembers = typeChecker.getPropertiesOfType(typeForObject).filter(symbol =>
2223-
// either public
2224-
!(getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.NonPublicAccessibilityModifier)
2225-
// or we're in it
2226-
|| containerClass && contains(typeForObject.symbol.declarations, containerClass));
2219+
typeMembers = typeChecker.getPropertiesOfType(typeForObject).filter(propertySymbol => {
2220+
return typeChecker.isPropertyAccessible(objectLikeContainer, /*isSuper*/ false, /*writing*/ false, typeForObject, propertySymbol);
2221+
});
22272222
existingMembers = objectLikeContainer.elements;
22282223
}
22292224
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////class Client {
4+
//// private close() { }
5+
//// public open() { }
6+
////}
7+
////type Wrap<T> = T &
8+
////{
9+
//// [K in Extract<keyof T, string> as `${K}Wrapped`]: T[K];
10+
////};
11+
////class Service {
12+
//// method() {
13+
//// let service = undefined as unknown as Wrap<Client>;
14+
//// const { /*a*/ } = service;
15+
//// }
16+
////}
17+
18+
verify.completions({ marker: "a", exact: ["open", "openWrapped"] });

0 commit comments

Comments
 (0)