-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reuse checker's property accessibility check for completions #45388
Changes from all commits
89c4e8c
85e1f26
05c993b
6136c7c
8dd1cf6
2546be8
4ffa810
053c00b
7c3d29c
996bce5
7ca48dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -713,6 +713,7 @@ namespace ts { | |
|
||
getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, | ||
isDeclarationVisible, | ||
isPropertyAccessible, | ||
}; | ||
|
||
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { | ||
|
@@ -27291,11 +27292,30 @@ namespace ts { | |
node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement, | ||
isSuper: boolean, writing: boolean, type: Type, prop: Symbol, reportError = true): boolean { | ||
|
||
const flags = getDeclarationModifierFlagsFromSymbol(prop, writing); | ||
const errorNode = node.kind === SyntaxKind.QualifiedName ? node.right : | ||
const errorNode = !reportError ? undefined : | ||
node.kind === SyntaxKind.QualifiedName ? node.right : | ||
node.kind === SyntaxKind.ImportType ? node : | ||
node.kind === SyntaxKind.BindingElement && node.propertyName ? node.propertyName : node.name; | ||
|
||
return checkPropertyAccessibilityAtLocation(node, isSuper, writing, type, prop, errorNode); | ||
} | ||
|
||
/** | ||
* Check whether the requested property can be accessed at the requested location. | ||
* Returns true if node is a valid property access, and false otherwise. | ||
* @param location The location node where we want to check if the property is accessible. | ||
* @param isSuper True if the access is from `super.`. | ||
* @param writing True if this is a write property access, false if it is a read property access. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: I've seen |
||
* @param containingType The type of the object whose property is being accessed. (Not the type of the property.) | ||
* @param prop The symbol for the property being accessed. | ||
* @param errorNode The node where we should report an invalid property access error, or undefined if we should not report errors. | ||
*/ | ||
function checkPropertyAccessibilityAtLocation(location: Node, | ||
isSuper: boolean, writing: boolean, | ||
gabritto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
containingType: Type, prop: Symbol, errorNode?: Node): boolean { | ||
gabritto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const flags = getDeclarationModifierFlagsFromSymbol(prop, writing); | ||
|
||
if (isSuper) { | ||
// TS 1.0 spec (April 2014): 4.8.2 | ||
// - In a constructor, instance member function, instance member accessor, or | ||
|
@@ -27306,7 +27326,7 @@ namespace ts { | |
// a super property access is permitted and must specify a public static member function of the base class. | ||
if (languageVersion < ScriptTarget.ES2015) { | ||
if (symbolHasNonMethodDeclaration(prop)) { | ||
if (reportError) { | ||
if (errorNode) { | ||
error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword); | ||
} | ||
return false; | ||
|
@@ -27317,20 +27337,26 @@ namespace ts { | |
// This error could mask a private property access error. But, a member | ||
// cannot simultaneously be private and abstract, so this will trigger an | ||
// additional error elsewhere. | ||
if (reportError) { | ||
error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); | ||
if (errorNode) { | ||
error(errorNode, | ||
Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, | ||
symbolToString(prop), | ||
typeToString(getDeclaringClass(prop)!)); | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
// Referencing abstract properties within their own constructors is not allowed | ||
if ((flags & ModifierFlags.Abstract) && symbolHasNonMethodDeclaration(prop) && | ||
(isThisProperty(node) || isThisInitializedObjectBindingExpression(node) || isObjectBindingPattern(node.parent) && isThisInitializedDeclaration(node.parent.parent))) { | ||
(isThisProperty(location) || isThisInitializedObjectBindingExpression(location) || isObjectBindingPattern(location.parent) && isThisInitializedDeclaration(location.parent.parent))) { | ||
const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!); | ||
if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(node)) { | ||
if (reportError) { | ||
error(errorNode, Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, symbolToString(prop), getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!)); // TODO: GH#18217 | ||
if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(location)) { | ||
if (errorNode) { | ||
error(errorNode, | ||
Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, | ||
symbolToString(prop), | ||
getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!)); | ||
} | ||
return false; | ||
} | ||
|
@@ -27346,9 +27372,12 @@ namespace ts { | |
// Private property is accessible if the property is within the declaring class | ||
if (flags & ModifierFlags.Private) { | ||
const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!)!; | ||
if (!isNodeWithinClass(node, declaringClassDeclaration)) { | ||
if (reportError) { | ||
error(errorNode, Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); | ||
if (!isNodeWithinClass(location, declaringClassDeclaration)) { | ||
if (errorNode) { | ||
error(errorNode, | ||
Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, | ||
symbolToString(prop), | ||
typeToString(getDeclaringClass(prop)!)); | ||
} | ||
return false; | ||
} | ||
|
@@ -27364,7 +27393,7 @@ namespace ts { | |
|
||
// Find the first enclosing class that has the declaring classes of the protected constituents | ||
// of the property as base classes | ||
let enclosingClass = forEachEnclosingClass(node, enclosingDeclaration => { | ||
let enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => { | ||
const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!) as InterfaceType; | ||
return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing) ? enclosingClass : undefined; | ||
}); | ||
|
@@ -27373,9 +27402,12 @@ namespace ts { | |
// allow PropertyAccessibility if context is in function with this parameter | ||
// static member access is disallow | ||
let thisParameter: ParameterDeclaration | undefined; | ||
if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(node)) || !thisParameter.type) { | ||
if (reportError) { | ||
error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, symbolToString(prop), typeToString(getDeclaringClass(prop) || type)); | ||
if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(location)) || !thisParameter.type) { | ||
if (errorNode) { | ||
error(errorNode, | ||
Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, | ||
symbolToString(prop), | ||
typeToString(getDeclaringClass(prop) || containingType)); | ||
} | ||
return false; | ||
} | ||
|
@@ -27387,13 +27419,15 @@ namespace ts { | |
if (flags & ModifierFlags.Static) { | ||
return true; | ||
} | ||
if (type.flags & TypeFlags.TypeParameter) { | ||
if (containingType.flags & TypeFlags.TypeParameter) { | ||
// get the original type -- represented as the type constraint of the 'this' type | ||
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 | ||
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 | ||
} | ||
if (!type || !hasBaseType(type, enclosingClass)) { | ||
if (reportError) { | ||
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)); | ||
if (!containingType || !hasBaseType(containingType, enclosingClass)) { | ||
if (errorNode) { | ||
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(containingType)); | ||
} | ||
return false; | ||
} | ||
|
@@ -28117,8 +28151,22 @@ namespace ts { | |
} | ||
} | ||
|
||
/** | ||
* Checks if an existing property access is valid for completions purposes. | ||
* @param node a property access-like node where we want to check if we can access a property. | ||
* This node does not need to be an access of the property we are checking. | ||
* e.g. in completions, this node will often be an incomplete property access node, as in `foo.`. | ||
* Besides providing a location (i.e. scope) used to check property accessibility, we use this node for | ||
* computing whether this is a `super` property access. | ||
* @param type the type whose property we are checking. | ||
* @param property the accessed property's symbol. | ||
*/ | ||
function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean { | ||
return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type); | ||
return isPropertyAccessible(node, | ||
node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, | ||
/* isWrite */ false, | ||
type, | ||
property); | ||
// Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context. | ||
} | ||
|
||
|
@@ -28128,19 +28176,45 @@ namespace ts { | |
propertyName: __String, | ||
type: Type): boolean { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copied this from the existing |
||
// Short-circuiting for improved performance. | ||
if (type === errorType || isTypeAny(type)) { | ||
return true; | ||
} | ||
|
||
const prop = getPropertyOfType(type, propertyName); | ||
if (prop) { | ||
if (prop.valueDeclaration && isPrivateIdentifierClassElementDeclaration(prop.valueDeclaration)) { | ||
const declClass = getContainingClass(prop.valueDeclaration); | ||
return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass); | ||
} | ||
return checkPropertyAccessibility(node, isSuper, /*writing*/ false, type, prop, /* reportError */ false); | ||
return !!prop && isPropertyAccessible(node, isSuper, /* isWrite */ false, type, prop); | ||
} | ||
|
||
/** | ||
* Checks if a property can be accessed in a location. | ||
* The location is given by the `node` parameter. | ||
* The node does not need to be a property access. | ||
* @param node location where to check property accessibility | ||
* @param isSuper whether to consider this a `super` property access, e.g. `super.foo`. | ||
* @param isWrite whether this is a write access, e.g. `++foo.x`. | ||
* @param containingType type where the property comes from. | ||
* @param property property symbol. | ||
*/ | ||
function isPropertyAccessible( | ||
node: Node, | ||
isSuper: boolean, | ||
isWrite: boolean, | ||
containingType: Type, | ||
property: Symbol): boolean { | ||
|
||
// Short-circuiting for improved performance. | ||
if (containingType === errorType || isTypeAny(containingType)) { | ||
return true; | ||
} | ||
|
||
// A #private property access in an optional chain is an error dealt with by the parser. | ||
// The checker does not check for it, so we need to do our own check here. | ||
if (property.valueDeclaration && isPrivateIdentifierClassElementDeclaration(property.valueDeclaration)) { | ||
const declClass = getContainingClass(property.valueDeclaration); | ||
return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass); | ||
} | ||
// In js files properties of unions are allowed in completion | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deleted this special casing of JS files because it's already dealt with in completions by |
||
return isInJSFile(node) && (type.flags & TypeFlags.Union) !== 0 && (type as UnionType).types.some(elementType => isValidPropertyAccessWithType(node, isSuper, propertyName, elementType)); | ||
|
||
return checkPropertyAccessibilityAtLocation(node, isSuper, isWrite, containingType, property); | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4216,7 +4216,7 @@ namespace ts { | |
|
||
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined; | ||
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: string): boolean; | ||
/** Exclude accesses to private properties or methods with a `this` parameter that `type` doesn't satisfy. */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updating comment since the implementation of this function no longer verifies |
||
/** Exclude accesses to private properties. */ | ||
/* @internal */ isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean; | ||
/** Follow all aliases to get the original symbol. */ | ||
getAliasedSymbol(symbol: Symbol): Symbol; | ||
|
@@ -4355,6 +4355,7 @@ namespace ts { | |
|
||
/* @internal */ getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): readonly TypeParameter[] | undefined; | ||
/* @internal */ isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean; | ||
/* @internal */ isPropertyAccessible(node: Node, isSuper: boolean, isWrite: boolean, containingType: Type, property: Symbol): boolean; | ||
} | ||
|
||
/* @internal */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/// <reference path="fourslash.ts" /> | ||
|
||
////class Client { | ||
//// private close() { } | ||
//// public open() { } | ||
////} | ||
////type Wrap<T> = T & | ||
////{ | ||
//// [K in Extract<keyof T, string> as `${K}Wrapped`]: T[K]; | ||
////}; | ||
////class Service { | ||
//// method() { | ||
//// let service = undefined as unknown as Wrap<Client>; | ||
//// const { /*a*/ } = service; | ||
//// } | ||
////} | ||
|
||
verify.completions({ marker: "a", exact: ["open", "openWrapped"] }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.