From af689cc5d527789cbc1d18a7b8aae6fa46051ea7 Mon Sep 17 00:00:00 2001 From: Ashley Claymore Date: Fri, 24 Sep 2021 17:05:27 +0100 Subject: [PATCH] ES private field check (#44648) * es private fields in in (#52) add support for the 'private-fields-in-in' TC39 proposal Signed-off-by: Ashley Claymore * [fixup] include inToken when walking forEachChild(node, cb) * [squash] re-accept lib definition baseline changes * [squash] reduce if/else to ternary Signed-off-by: Ashley Claymore * [squash] drop 'originalName' and rename parameter instead Signed-off-by: Ashley Claymore * [squash] extend spelling suggestion to all privateIdentifiers Signed-off-by: Ashley Claymore * [squash] revert the added lexical spelling suggestions logic Signed-off-by: Ashley Claymore * [squash] update baseline Signed-off-by: Ashley Claymore * [squash] inline variable as per PR suggestion Signed-off-by: Ashley Claymore * [squash] test targets both esnext and es2020 as per PR comment Signed-off-by: Ashley Claymore * switch to using a binary expression Signed-off-by: Ashley Claymore * [squash] PrivateIdentifier now extends PrimaryExpression Signed-off-by: Ashley Claymore * [squash] accept public api baseline changes Signed-off-by: Ashley Claymore * [squash] classPrivateFieldInHelper now has documentation Signed-off-by: Ashley Claymore * [squash] type-check now follows existing in-expression path Signed-off-by: Ashley Claymore * [squash] parser now follows existing binaryExpression path Signed-off-by: Ashley Claymore * [squash] correct typo in comment Signed-off-by: Ashley Claymore * [squash] no longer use esNext flag Signed-off-by: Ashley Claymore * [squash] swap 'reciever, state' helper params Signed-off-by: Ashley Claymore * [squash] remove change to parenthesizerRules Signed-off-by: Ashley Claymore * [squash] apply suggested changes to checker Signed-off-by: Ashley Claymore * [squash] remove need for assertion in fixSpelling Signed-off-by: Ashley Claymore * [squash] improve comment hint in test Signed-off-by: Ashley Claymore * [squash] fix comment typos Signed-off-by: Ashley Claymore * [squash] add flow-test for Foo | FooSub | Bar Signed-off-by: Ashley Claymore * [squash] add checkExternalEmitHelpers call and new test case Signed-off-by: Ashley Claymore * [squash] simplify and correct parser Signed-off-by: Ashley Claymore * [squash] move most of the added checker logic to expression level Signed-off-by: Ashley Claymore * [squash] always error when privateId could not be resolved Signed-off-by: Ashley Claymore * [squash] reword comment Signed-off-by: Ashley Claymore * [squash] fix codeFixSpelling test Signed-off-by: Ashley Claymore * [squash] do less work Signed-off-by: Ashley Claymore * store symbol by priateId not binaryExpression Signed-off-by: Ashley Claymore * moved parsePrivateIdentifier into parsePrimaryExpression Signed-off-by: Ashley Claymore * [squash] checkInExpressionn bails out early on silentNeverType Signed-off-by: Ashley Claymore * [squash] more detailed error messages Signed-off-by: Ashley Claymore * [squash] resolves conflict in diagnosticMessages.json Signed-off-by: Ashley Claymore * [squash] update baseline for importHelpersES6 Signed-off-by: Ashley Claymore * [squash] remove redundent if and comment from parser Signed-off-by: Ashley Claymore * [squash] split up grammar/check/symbolLookup Signed-off-by: Ashley Claymore * [squash] reword message for existing left side of in-expression error Signed-off-by: Ashley Claymore --- src/compiler/checker.ts | 89 ++++- src/compiler/diagnosticMessages.json | 6 +- src/compiler/emitter.ts | 2 + src/compiler/factory/emitHelpers.ts | 30 ++ src/compiler/factory/parenthesizerRules.ts | 2 +- src/compiler/parser.ts | 2 + src/compiler/transformers/classFields.ts | 34 +- src/compiler/types.ts | 6 +- src/compiler/utilities.ts | 3 + src/compiler/utilitiesPublic.ts | 1 + .../fixForgottenThisPropertyAccess.ts | 4 +- src/services/codefixes/fixSpelling.ts | 4 + src/services/services.ts | 6 + .../reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- tests/baselines/reference/importHelpersES6.js | 5 +- .../reference/importHelpersES6.symbols | 19 +- .../reference/importHelpersES6.types | 12 + ...elpersNoHelpersForPrivateFields.errors.txt | 23 ++ .../importHelpersNoHelpersForPrivateFields.js | 32 ++ ...rtHelpersNoHelpersForPrivateFields.symbols | 26 ++ ...portHelpersNoHelpersForPrivateFields.types | 29 ++ .../inOperatorWithInvalidOperands.errors.txt | 32 +- .../privateNameHashCharName.errors.txt | 5 +- .../reference/privateNameHashCharName.types | 1 - .../privateNameInInExpression.errors.txt | 146 +++++++++ .../reference/privateNameInInExpression.js | 222 +++++++++++++ .../privateNameInInExpression.symbols | 269 +++++++++++++++ .../reference/privateNameInInExpression.types | 308 ++++++++++++++++++ ...ressionTransform(target=es2020).errors.txt | 61 ++++ ...eInInExpressionTransform(target=es2020).js | 97 ++++++ ...ExpressionTransform(target=es2020).symbols | 112 +++++++ ...InExpressionTransform(target=es2020).types | 141 ++++++++ ...ressionTransform(target=esnext).errors.txt | 64 ++++ ...eInInExpressionTransform(target=esnext).js | 87 +++++ ...ExpressionTransform(target=esnext).symbols | 112 +++++++ ...InExpressionTransform(target=esnext).types | 141 ++++++++ ...privateNameInInExpressionUnused.errors.txt | 16 + .../privateNameInInExpressionUnused.js | 22 ++ .../privateNameInInExpressionUnused.symbols | 23 ++ .../privateNameInInExpressionUnused.types | 22 ++ tests/cases/compiler/importHelpersES6.ts | 2 + .../importHelpersNoHelpersForPrivateFields.ts | 16 + .../privateNames/privateNameInInExpression.ts | 119 +++++++ .../privateNameInInExpressionTransform.ts | 47 +++ .../privateNameInInExpressionUnused.ts | 13 + .../codeFixSpellingPrivateNameInIn.ts | 19 ++ .../findAllRefsPrivateNameProperties.ts | 7 +- 48 files changed, 2398 insertions(+), 45 deletions(-) create mode 100644 tests/baselines/reference/importHelpersNoHelpersForPrivateFields.errors.txt create mode 100644 tests/baselines/reference/importHelpersNoHelpersForPrivateFields.js create mode 100644 tests/baselines/reference/importHelpersNoHelpersForPrivateFields.symbols create mode 100644 tests/baselines/reference/importHelpersNoHelpersForPrivateFields.types create mode 100644 tests/baselines/reference/privateNameInInExpression.errors.txt create mode 100644 tests/baselines/reference/privateNameInInExpression.js create mode 100644 tests/baselines/reference/privateNameInInExpression.symbols create mode 100644 tests/baselines/reference/privateNameInInExpression.types create mode 100644 tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).errors.txt create mode 100644 tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).js create mode 100644 tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).symbols create mode 100644 tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).types create mode 100644 tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).errors.txt create mode 100644 tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).js create mode 100644 tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).symbols create mode 100644 tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).types create mode 100644 tests/baselines/reference/privateNameInInExpressionUnused.errors.txt create mode 100644 tests/baselines/reference/privateNameInInExpressionUnused.js create mode 100644 tests/baselines/reference/privateNameInInExpressionUnused.symbols create mode 100644 tests/baselines/reference/privateNameInInExpressionUnused.types create mode 100644 tests/cases/compiler/importHelpersNoHelpersForPrivateFields.ts create mode 100644 tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts create mode 100644 tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts create mode 100644 tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionUnused.ts create mode 100644 tests/cases/fourslash/codeFixSpellingPrivateNameInIn.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 42c960621e..dab50b7c9f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23917,6 +23917,9 @@ namespace ts { case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); case SyntaxKind.InKeyword: + if (isPrivateIdentifier(expr.left)) { + return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue); + } const target = getReferenceCandidate(expr.right); const leftType = getTypeOfNode(expr.left); if (leftType.flags & TypeFlags.StringLiteral) { @@ -23947,6 +23950,24 @@ namespace ts { return type; } + function narrowTypeByPrivateIdentifierInInExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + const target = getReferenceCandidate(expr.right); + if (!isMatchingReference(reference, target)) { + return type; + } + + Debug.assertNode(expr.left, isPrivateIdentifier); + const symbol = getSymbolForPrivateIdentifierExpression(expr.left); + if (symbol === undefined) { + return type; + } + const classSymbol = symbol.parent!; + const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration")) + ? getTypeOfSymbol(classSymbol) as InterfaceType + : getDeclaredTypeOfSymbol(classSymbol); + return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); + } + function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { // We are in a branch of obj?.foo === value (or any one of the other equality operators). We narrow obj as follows: // When operator is === and type of value excludes undefined, null and undefined is removed from type of obj in true branch. @@ -27790,6 +27811,40 @@ namespace ts { } } + function checkGrammarPrivateIdentifierExpression(privId: PrivateIdentifier): boolean { + if (!getContainingClass(privId)) { + return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + } + if (!isExpressionNode(privId)) { + return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression); + } + if (!getSymbolForPrivateIdentifierExpression(privId)) { + return grammarErrorOnNode(privId, Diagnostics.Cannot_find_name_0, idText(privId)); + } + return false; + } + + function checkPrivateIdentifierExpression(privId: PrivateIdentifier): Type { + checkGrammarPrivateIdentifierExpression(privId); + const symbol = getSymbolForPrivateIdentifierExpression(privId); + if (symbol) { + markPropertyAsReferenced(symbol, /* nodeForCheckWriteOnly: */ undefined, /* isThisAccess: */ false); + } + return anyType; + } + + function getSymbolForPrivateIdentifierExpression(privId: PrivateIdentifier): Symbol | undefined { + if (!isExpressionNode(privId)) { + return undefined; + } + + const links = getNodeLinks(privId); + if (links.resolvedSymbol === undefined) { + links.resolvedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privId.escapedText, privId); + } + return links.resolvedSymbol; + } + function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined { return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName); } @@ -32110,11 +32165,29 @@ namespace ts { if (leftType === silentNeverType || rightType === silentNeverType) { return silentNeverType; } - leftType = checkNonNullType(leftType, left); + if (isPrivateIdentifier(left)) { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(left, ExternalEmitHelpers.ClassPrivateFieldIn); + } + // Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type + // which provides us with the opportunity to emit more detailed errors + if (!getNodeLinks(left).resolvedSymbol && getContainingClass(left)) { + const isUncheckedJS = isUncheckedJSSuggestion(left, rightType.symbol, /*excludeClasses*/ true); + reportNonexistentProperty(left, rightType, isUncheckedJS); + } + } + else { + leftType = checkNonNullType(leftType, left); + // TypeScript 1.0 spec (April 2014): 4.15.5 + // Require the left operand to be of type Any, the String primitive type, or the Number primitive type. + if (!(allTypesAssignableToKind(leftType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) || + isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) { + error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_a_private_identifier_or_of_type_any_string_number_or_symbol); + } + } rightType = checkNonNullType(rightType, right); // TypeScript 1.0 spec (April 2014): 4.15.5 - // The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type, - // and the right operand to be + // The in operator requires the right operand to be // // 1. assignable to the non-primitive type, // 2. an unconstrained type parameter, @@ -32132,10 +32205,6 @@ namespace ts { // unless *all* instantiations would result in an error. // // The result is always of the Boolean primitive type. - if (!(allTypesAssignableToKind(leftType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) || - isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) { - error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol); - } const rightTypeConstraint = getConstraintOfType(rightType); if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || rightTypeConstraint && ( @@ -33517,6 +33586,8 @@ namespace ts { switch (kind) { case SyntaxKind.Identifier: return checkIdentifier(node as Identifier, checkMode); + case SyntaxKind.PrivateIdentifier: + return checkPrivateIdentifierExpression(node as PrivateIdentifier); case SyntaxKind.ThisKeyword: return checkThisExpression(node); case SyntaxKind.SuperKeyword: @@ -40296,6 +40367,9 @@ namespace ts { } return result; } + else if (isPrivateIdentifier(name)) { + return getSymbolForPrivateIdentifierExpression(name); + } else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) { const links = getNodeLinks(name); if (links.resolvedSymbol) { @@ -41712,6 +41786,7 @@ namespace ts { case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet"; case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet"; + case ExternalEmitHelpers.ClassPrivateFieldIn: return "__classPrivateFieldIn"; case ExternalEmitHelpers.CreateBinding: return "__createBinding"; default: return Debug.fail("Unrecognized helper"); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index c2778dd5da..ea7a259a6c 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1392,6 +1392,10 @@ "category": "Message", "code": 1450 }, + "Private identifiers are only allowed in class bodies and may only be used as part of a class member declaration, property access, or on the left-hand-side of an 'in' expression": { + "category": "Error", + "code": 1451 + }, "The types of '{0}' are incompatible between these types.": { "category": "Error", @@ -1654,7 +1658,7 @@ "category": "Error", "code": 2359 }, - "The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'.": { + "The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'.": { "category": "Error", "code": 2360 }, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 23cdda71a2..d9f80b4629 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1684,6 +1684,8 @@ namespace ts { // Identifiers case SyntaxKind.Identifier: return emitIdentifier(node as Identifier); + case SyntaxKind.PrivateIdentifier: + return emitPrivateIdentifier(node as PrivateIdentifier); // Expressions case SyntaxKind.ArrayLiteralExpression: diff --git a/src/compiler/factory/emitHelpers.ts b/src/compiler/factory/emitHelpers.ts index 85b00a5ef3..f99abb3e03 100644 --- a/src/compiler/factory/emitHelpers.ts +++ b/src/compiler/factory/emitHelpers.ts @@ -34,6 +34,7 @@ namespace ts { // Class Fields Helpers createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; + createClassPrivateFieldInHelper(state: Identifier, receiver: Expression): Expression; } export function createEmitHelperFactory(context: TransformationContext): EmitHelperFactory { @@ -75,6 +76,7 @@ namespace ts { // Class Fields Helpers createClassPrivateFieldGetHelper, createClassPrivateFieldSetHelper, + createClassPrivateFieldInHelper }; /** @@ -395,6 +397,10 @@ namespace ts { return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldSet"), /*typeArguments*/ undefined, args); } + function createClassPrivateFieldInHelper(state: Identifier, receiver: Expression) { + context.requestEmitHelper(classPrivateFieldInHelper); + return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldIn"), /* typeArguments*/ undefined, [state, receiver]); + } } /* @internal */ @@ -961,6 +967,29 @@ namespace ts { };` }; + /** + * Parameters: + * @param state — One of the following: + * - A WeakMap when the member is a private instance field. + * - A WeakSet when the member is a private instance method or accessor. + * - A function value that should be the undecorated class constructor when the member is a private static field, method, or accessor. + * @param receiver — The object being checked if it has the private member. + * + * Usage: + * This helper is used to transform `#field in expression` to + * `__classPrivateFieldIn(, expression)` + */ + export const classPrivateFieldInHelper: UnscopedEmitHelper = { + name: "typescript:classPrivateFieldIn", + importName: "__classPrivateFieldIn", + scoped: false, + text: ` + var __classPrivateFieldIn = (this && this.__classPrivateFieldIn) || function(state, receiver) { + if (receiver === null || (typeof receiver !== "object" && typeof receiver !== "function")) throw new TypeError("Cannot use 'in' operator on non-object"); + return typeof state === "function" ? receiver === state : state.has(receiver); + };` + }; + let allUnscopedEmitHelpers: ReadonlyESMap | undefined; export function getAllUnscopedEmitHelpers() { @@ -986,6 +1015,7 @@ namespace ts { exportStarHelper, classPrivateFieldGetHelper, classPrivateFieldSetHelper, + classPrivateFieldInHelper, createBindingHelper, setModuleDefaultHelper ], helper => helper.name)); diff --git a/src/compiler/factory/parenthesizerRules.ts b/src/compiler/factory/parenthesizerRules.ts index 782a0110ab..1e2cb93658 100644 --- a/src/compiler/factory/parenthesizerRules.ts +++ b/src/compiler/factory/parenthesizerRules.ts @@ -452,4 +452,4 @@ namespace ts { parenthesizeConstituentTypesOfUnionOrIntersectionType: nodes => cast(nodes, isNodeArray), parenthesizeTypeArguments: nodes => nodes && cast(nodes, isNodeArray), }; -} \ No newline at end of file +} diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 208e9db11b..67359ff05d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5617,6 +5617,8 @@ namespace ts { break; case SyntaxKind.TemplateHead: return parseTemplateExpression(/* isTaggedTemplate */ false); + case SyntaxKind.PrivateIdentifier: + return parsePrivateIdentifier(); } return parseIdentifier(Diagnostics.Expression_expected); diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts index 935c549dbc..ca13b6cef2 100644 --- a/src/compiler/transformers/classFields.ts +++ b/src/compiler/transformers/classFields.ts @@ -266,15 +266,44 @@ namespace ts { /** * If we visit a private name, this means it is an undeclared private name. - * Replace it with an empty identifier to indicate a problem with the code. + * Replace it with an empty identifier to indicate a problem with the code, + * unless we are in a statement position - otherwise this will not trigger + * a SyntaxError. */ function visitPrivateIdentifier(node: PrivateIdentifier) { if (!shouldTransformPrivateElementsOrClassStaticBlocks) { return node; } + if (isStatement(node.parent)) { + return node; + } return setOriginalNode(factory.createIdentifier(""), node); } + /** + * Visits `#id in expr` + */ + function visitPrivateIdentifierInInExpression(node: BinaryExpression) { + if (!shouldTransformPrivateElementsOrClassStaticBlocks) { + return node; + } + const privId = node.left; + Debug.assertNode(privId, isPrivateIdentifier); + Debug.assert(node.operatorToken.kind === SyntaxKind.InKeyword); + const info = accessPrivateIdentifier(privId); + if (info) { + const receiver = visitNode(node.right, visitor, isExpression); + + return setOriginalNode( + context.getEmitHelperFactory().createClassPrivateFieldInHelper(info.brandCheckIdentifier, receiver), + node + ); + } + + // Private name has not been declared. Subsequent transformers will handle this error + return visitEachChild(node, visitor, context); + } + /** * Visits the members of a class that has fields. * @@ -827,6 +856,9 @@ namespace ts { } } } + if (node.operatorToken.kind === SyntaxKind.InKeyword && isPrivateIdentifier(node.left)) { + return visitPrivateIdentifierInInExpression(node); + } return visitEachChild(node, visitor, context); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index feef8900df..6e80dafff3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1214,7 +1214,8 @@ namespace ts { readonly expression: Expression; } - export interface PrivateIdentifier extends Node { + // Typed as a PrimaryExpression due to its presence in BinaryExpressions (#field in expr) + export interface PrivateIdentifier extends PrimaryExpression { readonly kind: SyntaxKind.PrivateIdentifier; // escaping not strictly necessary // avoids gotchas in transforms and utils @@ -6854,7 +6855,8 @@ namespace ts { MakeTemplateObject = 1 << 18, // __makeTemplateObject (used for constructing template string array objects) ClassPrivateFieldGet = 1 << 19, // __classPrivateFieldGet (used by the class private field transformation) ClassPrivateFieldSet = 1 << 20, // __classPrivateFieldSet (used by the class private field transformation) - CreateBinding = 1 << 21, // __createBinding (use by the module transform for (re)exports and namespace imports) + ClassPrivateFieldIn = 1 << 21, // __classPrivateFieldIn (used by the class private field transformation) + CreateBinding = 1 << 22, // __createBinding (use by the module transform for (re)exports and namespace imports) FirstEmitHelper = Extends, LastEmitHelper = CreateBinding, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8ac91f86f9..86363d8296 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1949,6 +1949,8 @@ namespace ts { node = node.parent; } return node.parent.kind === SyntaxKind.TypeQuery || isJSDocLinkLike(node.parent) || isJSDocNameReference(node.parent) || isJSDocMemberName(node.parent) || isJSXTagName(node); + case SyntaxKind.PrivateIdentifier: + return isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.InKeyword; case SyntaxKind.Identifier: if (node.parent.kind === SyntaxKind.TypeQuery || isJSDocLinkLike(node.parent) || isJSDocNameReference(node.parent) || isJSDocMemberName(node.parent) || isJSXTagName(node)) { return true; @@ -3706,6 +3708,7 @@ namespace ts { case SyntaxKind.ThisKeyword: case SyntaxKind.SuperKeyword: case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: case SyntaxKind.NullKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index a5c0f9296d..8bed8e5d0a 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -1516,6 +1516,7 @@ namespace ts { case SyntaxKind.ClassExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: // technically this is only an Expression if it's in a `#field in expr` BinaryExpression case SyntaxKind.RegularExpressionLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: diff --git a/src/services/codefixes/fixForgottenThisPropertyAccess.ts b/src/services/codefixes/fixForgottenThisPropertyAccess.ts index 067ce4b0b9..8e275724f9 100644 --- a/src/services/codefixes/fixForgottenThisPropertyAccess.ts +++ b/src/services/codefixes/fixForgottenThisPropertyAccess.ts @@ -4,7 +4,7 @@ namespace ts.codefix { const didYouMeanStaticMemberCode = Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0.code; const errorCodes = [ Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code, - Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies.code, + Diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression.code, didYouMeanStaticMemberCode, ]; registerCodeFix({ @@ -32,7 +32,7 @@ namespace ts.codefix { function getInfo(sourceFile: SourceFile, pos: number, diagCode: number): Info | undefined { const node = getTokenAtPosition(sourceFile, pos); - if (isIdentifier(node)) { + if (isIdentifier(node) || isPrivateIdentifier(node)) { return { node, className: diagCode === didYouMeanStaticMemberCode ? getContainingClass(node)!.name!.text : undefined }; } } diff --git a/src/services/codefixes/fixSpelling.ts b/src/services/codefixes/fixSpelling.ts index 99ccc51e83..f4f8b31aca 100644 --- a/src/services/codefixes/fixSpelling.ts +++ b/src/services/codefixes/fixSpelling.ts @@ -57,6 +57,10 @@ namespace ts.codefix { } suggestedSymbol = checker.getSuggestedSymbolForNonexistentProperty(node, containingType); } + else if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.InKeyword && parent.left === node && isPrivateIdentifier(node)) { + const receiverType = checker.getTypeAtLocation(parent.right); + suggestedSymbol = checker.getSuggestedSymbolForNonexistentProperty(node, receiverType); + } else if (isQualifiedName(parent) && parent.right === node) { const symbol = checker.getSymbolAtLocation(parent.left); if (symbol && symbol.flags & SymbolFlags.Module) { diff --git a/src/services/services.ts b/src/services/services.ts index 3817eae5f9..4ad7e9cf3f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -401,6 +401,12 @@ namespace ts { public kind!: SyntaxKind.PrivateIdentifier; public escapedText!: __String; public symbol!: Symbol; + _primaryExpressionBrand: any; + _memberExpressionBrand: any; + _leftHandSideExpressionBrand: any; + _updateExpressionBrand: any; + _unaryExpressionBrand: any; + _expressionBrand: any; constructor(_kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) { super(pos, end); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 8693361894..e84a5df5ad 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -670,7 +670,7 @@ declare namespace ts { readonly parent: Declaration; readonly expression: Expression; } - export interface PrivateIdentifier extends Node { + export interface PrivateIdentifier extends PrimaryExpression { readonly kind: SyntaxKind.PrivateIdentifier; readonly escapedText: __String; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 275ddc2153..8e71803a76 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -670,7 +670,7 @@ declare namespace ts { readonly parent: Declaration; readonly expression: Expression; } - export interface PrivateIdentifier extends Node { + export interface PrivateIdentifier extends PrimaryExpression { readonly kind: SyntaxKind.PrivateIdentifier; readonly escapedText: __String; } diff --git a/tests/baselines/reference/importHelpersES6.js b/tests/baselines/reference/importHelpersES6.js index 9a7a6bc30c..ec2ca96a54 100644 --- a/tests/baselines/reference/importHelpersES6.js +++ b/tests/baselines/reference/importHelpersES6.js @@ -5,6 +5,7 @@ declare var dec: any; @dec export class A { #x: number = 1; async f() { this.#x = await this.#x; } + g(u) { return #x in u; } } const o = { a: 1 }; @@ -18,11 +19,12 @@ export declare function __metadata(metadataKey: any, metadataValue: any): Functi export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; export declare function __classPrivateFieldGet(a: any, b: any, c: any, d: any): any; export declare function __classPrivateFieldSet(a: any, b: any, c: any, d: any, e: any): any; +export declare function __classPrivateFieldIn(a: any, b: any): boolean; //// [a.js] var _A_x; -import { __awaiter, __classPrivateFieldGet, __classPrivateFieldSet, __decorate } from "tslib"; +import { __awaiter, __classPrivateFieldGet, __classPrivateFieldIn, __classPrivateFieldSet, __decorate } from "tslib"; let A = class A { constructor() { _A_x.set(this, 1); @@ -30,6 +32,7 @@ let A = class A { f() { return __awaiter(this, void 0, void 0, function* () { __classPrivateFieldSet(this, _A_x, yield __classPrivateFieldGet(this, _A_x, "f"), "f"); }); } + g(u) { return __classPrivateFieldIn(_A_x, u); } }; _A_x = new WeakMap(); A = __decorate([ diff --git a/tests/baselines/reference/importHelpersES6.symbols b/tests/baselines/reference/importHelpersES6.symbols index 38c044cbe7..1015f19b74 100644 --- a/tests/baselines/reference/importHelpersES6.symbols +++ b/tests/baselines/reference/importHelpersES6.symbols @@ -15,15 +15,21 @@ declare var dec: any; >this : Symbol(A, Decl(a.ts, 0, 21)) >this.#x : Symbol(A.#x, Decl(a.ts, 1, 21)) >this : Symbol(A, Decl(a.ts, 0, 21)) + + g(u) { return #x in u; } +>g : Symbol(A.g, Decl(a.ts, 3, 42)) +>u : Symbol(u, Decl(a.ts, 4, 6)) +>#x : Symbol(A.#x, Decl(a.ts, 1, 21)) +>u : Symbol(u, Decl(a.ts, 4, 6)) } const o = { a: 1 }; ->o : Symbol(o, Decl(a.ts, 6, 5)) ->a : Symbol(a, Decl(a.ts, 6, 11)) +>o : Symbol(o, Decl(a.ts, 7, 5)) +>a : Symbol(a, Decl(a.ts, 7, 11)) const y = { ...o }; ->y : Symbol(y, Decl(a.ts, 7, 5)) ->o : Symbol(o, Decl(a.ts, 6, 5)) +>y : Symbol(y, Decl(a.ts, 8, 5)) +>o : Symbol(o, Decl(a.ts, 7, 5)) === tests/cases/compiler/tslib.d.ts === export declare function __extends(d: Function, b: Function): void; @@ -78,3 +84,8 @@ export declare function __classPrivateFieldSet(a: any, b: any, c: any, d: any, e >d : Symbol(d, Decl(tslib.d.ts, --, --)) >e : Symbol(e, Decl(tslib.d.ts, --, --)) +export declare function __classPrivateFieldIn(a: any, b: any): boolean; +>__classPrivateFieldIn : Symbol(__classPrivateFieldIn, Decl(tslib.d.ts, --, --)) +>a : Symbol(a, Decl(tslib.d.ts, --, --)) +>b : Symbol(b, Decl(tslib.d.ts, --, --)) + diff --git a/tests/baselines/reference/importHelpersES6.types b/tests/baselines/reference/importHelpersES6.types index bbb6f97969..70f8e09c9d 100644 --- a/tests/baselines/reference/importHelpersES6.types +++ b/tests/baselines/reference/importHelpersES6.types @@ -18,6 +18,13 @@ declare var dec: any; >await this.#x : number >this.#x : number >this : this + + g(u) { return #x in u; } +>g : (u: any) => boolean +>u : any +>#x in u : boolean +>#x : any +>u : any } const o = { a: 1 }; @@ -76,3 +83,8 @@ export declare function __classPrivateFieldSet(a: any, b: any, c: any, d: any, e >d : any >e : any +export declare function __classPrivateFieldIn(a: any, b: any): boolean; +>__classPrivateFieldIn : (a: any, b: any) => boolean +>a : any +>b : any + diff --git a/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.errors.txt b/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.errors.txt new file mode 100644 index 0000000000..3e93768e89 --- /dev/null +++ b/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.errors.txt @@ -0,0 +1,23 @@ +tests/cases/compiler/main.ts(4,9): error TS2343: This syntax requires an imported helper named '__classPrivateFieldSet' which does not exist in 'tslib'. Consider upgrading your version of 'tslib'. +tests/cases/compiler/main.ts(4,23): error TS2343: This syntax requires an imported helper named '__classPrivateFieldGet' which does not exist in 'tslib'. Consider upgrading your version of 'tslib'. +tests/cases/compiler/main.ts(5,9): error TS2343: This syntax requires an imported helper named '__classPrivateFieldIn' which does not exist in 'tslib'. Consider upgrading your version of 'tslib'. + + +==== tests/cases/compiler/main.ts (3 errors) ==== + export class Foo { + #field = true; + f() { + this.#field = this.#field; + ~~~~~~~~~~~ +!!! error TS2343: This syntax requires an imported helper named '__classPrivateFieldSet' which does not exist in 'tslib'. Consider upgrading your version of 'tslib'. + ~~~~~~~~~~~ +!!! error TS2343: This syntax requires an imported helper named '__classPrivateFieldGet' which does not exist in 'tslib'. Consider upgrading your version of 'tslib'. + #field in this; + ~~~~~~ +!!! error TS2343: This syntax requires an imported helper named '__classPrivateFieldIn' which does not exist in 'tslib'. Consider upgrading your version of 'tslib'. + } + } + +==== tests/cases/compiler/tslib.d.ts (0 errors) ==== + export {} + \ No newline at end of file diff --git a/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.js b/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.js new file mode 100644 index 0000000000..d84cca9411 --- /dev/null +++ b/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.js @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/importHelpersNoHelpersForPrivateFields.ts] //// + +//// [main.ts] +export class Foo { + #field = true; + f() { + this.#field = this.#field; + #field in this; + } +} + +//// [tslib.d.ts] +export {} + + +//// [main.js] +"use strict"; +var _Foo_field; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Foo = void 0; +const tslib_1 = require("tslib"); +class Foo { + constructor() { + _Foo_field.set(this, true); + } + f() { + (0, tslib_1.__classPrivateFieldSet)(this, _Foo_field, (0, tslib_1.__classPrivateFieldGet)(this, _Foo_field, "f"), "f"); + (0, tslib_1.__classPrivateFieldIn)(_Foo_field, this); + } +} +exports.Foo = Foo; +_Foo_field = new WeakMap(); diff --git a/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.symbols b/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.symbols new file mode 100644 index 0000000000..2fbafd8892 --- /dev/null +++ b/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/main.ts === +export class Foo { +>Foo : Symbol(Foo, Decl(main.ts, 0, 0)) + + #field = true; +>#field : Symbol(Foo.#field, Decl(main.ts, 0, 18)) + + f() { +>f : Symbol(Foo.f, Decl(main.ts, 1, 18)) + + this.#field = this.#field; +>this.#field : Symbol(Foo.#field, Decl(main.ts, 0, 18)) +>this : Symbol(Foo, Decl(main.ts, 0, 0)) +>this.#field : Symbol(Foo.#field, Decl(main.ts, 0, 18)) +>this : Symbol(Foo, Decl(main.ts, 0, 0)) + + #field in this; +>#field : Symbol(Foo.#field, Decl(main.ts, 0, 18)) +>this : Symbol(Foo, Decl(main.ts, 0, 0)) + } +} + +=== tests/cases/compiler/tslib.d.ts === +export {} +No type information for this code. +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.types b/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.types new file mode 100644 index 0000000000..fcb8f6214d --- /dev/null +++ b/tests/baselines/reference/importHelpersNoHelpersForPrivateFields.types @@ -0,0 +1,29 @@ +=== tests/cases/compiler/main.ts === +export class Foo { +>Foo : Foo + + #field = true; +>#field : boolean +>true : true + + f() { +>f : () => void + + this.#field = this.#field; +>this.#field = this.#field : boolean +>this.#field : boolean +>this : this +>this.#field : boolean +>this : this + + #field in this; +>#field in this : boolean +>#field : any +>this : this + } +} + +=== tests/cases/compiler/tslib.d.ts === +export {} +No type information for this code. +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt b/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt index f624cb1ff0..7984645b6d 100644 --- a/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt +++ b/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt @@ -1,12 +1,12 @@ -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(15,11): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(16,11): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(17,11): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(15,11): error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(16,11): error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(17,11): error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(19,11): error TS2531: Object is possibly 'null'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(20,11): error TS2532: Object is possibly 'undefined'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(22,11): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(23,11): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(24,12): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(25,12): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(22,11): error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(23,11): error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(24,12): error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(25,12): error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(35,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(36,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(37,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. @@ -17,7 +17,7 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(42,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(43,16): error TS2531: Object is possibly 'null'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(44,17): error TS2532: Object is possibly 'undefined'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(47,11): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(47,11): error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(47,17): error TS2361: The right-hand side of an 'in' expression must not be a primitive. @@ -38,13 +38,13 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv var ra1 = a1 in x; ~~ -!!! error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +!!! error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. var ra2 = a2 in x; ~~ -!!! error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +!!! error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. var ra3 = a3 in x; ~~ -!!! error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +!!! error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. var ra4 = a4 in x; var ra5 = null in x; ~~~~ @@ -55,16 +55,16 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv var ra7 = E.a in x; var ra8 = false in x; ~~~~~ -!!! error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +!!! error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. var ra9 = {} in x; ~~ -!!! error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +!!! error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. var ra10 = a5 in x; ~~ -!!! error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +!!! error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. var ra11 = a6 in x; ~~ -!!! error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +!!! error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. // invalid right operands // the right operand is required to be of type Any, an object type, or a type parameter type @@ -108,6 +108,6 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv // both operands are invalid var rc1 = {} in ''; ~~ -!!! error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. +!!! error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. ~~ !!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. \ No newline at end of file diff --git a/tests/baselines/reference/privateNameHashCharName.errors.txt b/tests/baselines/reference/privateNameHashCharName.errors.txt index 211a62a47f..8fdc42b434 100644 --- a/tests/baselines/reference/privateNameHashCharName.errors.txt +++ b/tests/baselines/reference/privateNameHashCharName.errors.txt @@ -1,15 +1,12 @@ tests/cases/conformance/classes/members/privateNames/privateNameHashCharName.ts(1,1): error TS1127: Invalid character. -tests/cases/conformance/classes/members/privateNames/privateNameHashCharName.ts(1,1): error TS2304: Cannot find name '#'. tests/cases/conformance/classes/members/privateNames/privateNameHashCharName.ts(4,5): error TS1127: Invalid character. tests/cases/conformance/classes/members/privateNames/privateNameHashCharName.ts(7,14): error TS1127: Invalid character. -==== tests/cases/conformance/classes/members/privateNames/privateNameHashCharName.ts (4 errors) ==== +==== tests/cases/conformance/classes/members/privateNames/privateNameHashCharName.ts (3 errors) ==== # ~ !!! error TS1127: Invalid character. - ~ -!!! error TS2304: Cannot find name '#'. class C { # diff --git a/tests/baselines/reference/privateNameHashCharName.types b/tests/baselines/reference/privateNameHashCharName.types index f321b7bcda..520e51ee43 100644 --- a/tests/baselines/reference/privateNameHashCharName.types +++ b/tests/baselines/reference/privateNameHashCharName.types @@ -1,6 +1,5 @@ === tests/cases/conformance/classes/members/privateNames/privateNameHashCharName.ts === # -># : any class C { >C : C diff --git a/tests/baselines/reference/privateNameInInExpression.errors.txt b/tests/baselines/reference/privateNameInInExpression.errors.txt new file mode 100644 index 0000000000..d4ba5f81e9 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpression.errors.txt @@ -0,0 +1,146 @@ +tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(21,29): error TS2571: Object is of type 'unknown'. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(23,19): error TS2304: Cannot find name '#fiel'. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(23,19): error TS2339: Property '#fiel' does not exist on type 'any'. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(25,20): error TS1451: Private identifiers are only allowed in class bodies and may only be used as part of a class member declaration, property access, or on the left-hand-side of an 'in' expression +tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(27,14): error TS1451: Private identifiers are only allowed in class bodies and may only be used as part of a class member declaration, property access, or on the left-hand-side of an 'in' expression +tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(27,14): error TS2406: The left-hand side of a 'for...in' statement must be a variable or a property access. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(29,23): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter, but here has type 'boolean'. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(43,27): error TS2531: Object is possibly 'null'. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(114,12): error TS18016: Private identifiers are not allowed outside class bodies. + + +==== tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts (9 errors) ==== + class Foo { + #field = 1; + static #staticField = 2; + #method() {} + static #staticMethod() {} + + goodRhs(v: any) { + const a = #field in v; + + const b = #field in v.p1.p2; + + const c = #field in (v as {}); + + const d = #field in (v as Foo); + + const e = #field in (v as never); + + for (let f in #field in v as any) { /**/ } // unlikely but valid + } + badRhs(v: any) { + const a = #field in (v as unknown); // Bad - RHS of in must be object type or any + ~~~~~~~~~~~~~~ +!!! error TS2571: Object is of type 'unknown'. + + const b = #fiel in v; // Bad - typo in privateID + ~~~~~ +!!! error TS2304: Cannot find name '#fiel'. + ~~~~~ +!!! error TS2339: Property '#fiel' does not exist on type 'any'. + + const c = (#field) in v; // Bad - privateID is not an expression on its own + ~~~~~~ +!!! error TS1451: Private identifiers are only allowed in class bodies and may only be used as part of a class member declaration, property access, or on the left-hand-side of an 'in' expression + + for (#field in v) { /**/ } // Bad - 'in' not allowed + ~~~~~~ +!!! error TS1451: Private identifiers are only allowed in class bodies and may only be used as part of a class member declaration, property access, or on the left-hand-side of an 'in' expression + ~~~~~~ +!!! error TS2406: The left-hand side of a 'for...in' statement must be a variable or a property access. + + for (let d in #field in v) { /**/ } // Bad - rhs of in should be a object/any + ~~~~~~~~~~~ +!!! error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter, but here has type 'boolean'. + } + whitespace(v: any) { + const a = v && /*0*/#field/*1*/ + /*2*/in/*3*/ + /*4*/v/*5*/ + } + flow(u: unknown, n: never, fb: Foo | Bar, fs: FooSub, b: Bar, fsb: FooSub | Bar, fsfb: Foo | FooSub | Bar) { + + if (typeof u === 'object') { + if (#field in n) { + n; // good n is never + } + + if (#field in u) { + ~ +!!! error TS2531: Object is possibly 'null'. + u; // good u is Foo + } else { + u; // good u is object | null + } + + if (u !== null) { + if (#field in u) { + u; // good u is Foo + } else { + u; // good u is object + } + + if (#method in u) { + u; // good u is Foo + } + + if (#staticField in u) { + u; // good u is typeof Foo + } + + if (#staticMethod in u) { + u; // good u is typeof Foo + } + } + } + + if (#field in fb) { + fb; // good fb is Foo + } else { + fb; // good fb is Bar + } + + if (#field in fs) { + fs; // good fs is FooSub + } else { + fs; // good fs is never + } + + if (#field in b) { + b; // good b is 'Bar & Foo' + } else { + b; // good b is Bar + } + + if (#field in fsb) { + fsb; // good fsb is FooSub + } else { + fsb; // good fsb is Bar + } + + if (#field in fsfb) { + fsfb; // good fsfb is 'Foo | FooSub' + } else { + fsfb; // good fsfb is Bar + } + + class Nested { + m(v: any) { + if (#field in v) { + v; // good v is Foo + } + } + } + } + } + + class FooSub extends Foo { subTypeOfFoo = true } + class Bar { notFoo = true } + + function badSyntax(v: Foo) { + return #field in v; // Bad - outside of class + ~~~~~~ +!!! error TS18016: Private identifiers are not allowed outside class bodies. + } + \ No newline at end of file diff --git a/tests/baselines/reference/privateNameInInExpression.js b/tests/baselines/reference/privateNameInInExpression.js new file mode 100644 index 0000000000..a57d640c8d --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpression.js @@ -0,0 +1,222 @@ +//// [privateNameInInExpression.ts] +class Foo { + #field = 1; + static #staticField = 2; + #method() {} + static #staticMethod() {} + + goodRhs(v: any) { + const a = #field in v; + + const b = #field in v.p1.p2; + + const c = #field in (v as {}); + + const d = #field in (v as Foo); + + const e = #field in (v as never); + + for (let f in #field in v as any) { /**/ } // unlikely but valid + } + badRhs(v: any) { + const a = #field in (v as unknown); // Bad - RHS of in must be object type or any + + const b = #fiel in v; // Bad - typo in privateID + + const c = (#field) in v; // Bad - privateID is not an expression on its own + + for (#field in v) { /**/ } // Bad - 'in' not allowed + + for (let d in #field in v) { /**/ } // Bad - rhs of in should be a object/any + } + whitespace(v: any) { + const a = v && /*0*/#field/*1*/ + /*2*/in/*3*/ + /*4*/v/*5*/ + } + flow(u: unknown, n: never, fb: Foo | Bar, fs: FooSub, b: Bar, fsb: FooSub | Bar, fsfb: Foo | FooSub | Bar) { + + if (typeof u === 'object') { + if (#field in n) { + n; // good n is never + } + + if (#field in u) { + u; // good u is Foo + } else { + u; // good u is object | null + } + + if (u !== null) { + if (#field in u) { + u; // good u is Foo + } else { + u; // good u is object + } + + if (#method in u) { + u; // good u is Foo + } + + if (#staticField in u) { + u; // good u is typeof Foo + } + + if (#staticMethod in u) { + u; // good u is typeof Foo + } + } + } + + if (#field in fb) { + fb; // good fb is Foo + } else { + fb; // good fb is Bar + } + + if (#field in fs) { + fs; // good fs is FooSub + } else { + fs; // good fs is never + } + + if (#field in b) { + b; // good b is 'Bar & Foo' + } else { + b; // good b is Bar + } + + if (#field in fsb) { + fsb; // good fsb is FooSub + } else { + fsb; // good fsb is Bar + } + + if (#field in fsfb) { + fsfb; // good fsfb is 'Foo | FooSub' + } else { + fsfb; // good fsfb is Bar + } + + class Nested { + m(v: any) { + if (#field in v) { + v; // good v is Foo + } + } + } + } +} + +class FooSub extends Foo { subTypeOfFoo = true } +class Bar { notFoo = true } + +function badSyntax(v: Foo) { + return #field in v; // Bad - outside of class +} + + +//// [privateNameInInExpression.js] +"use strict"; +class Foo { + #field = 1; + static #staticField = 2; + #method() { } + static #staticMethod() { } + goodRhs(v) { + const a = #field in v; + const b = #field in v.p1.p2; + const c = #field in v; + const d = #field in v; + const e = #field in v; + for (let f in #field in v) { /**/ } // unlikely but valid + } + badRhs(v) { + const a = #field in v; // Bad - RHS of in must be object type or any + const b = #fiel in v; // Bad - typo in privateID + const c = (#field) in v; // Bad - privateID is not an expression on its own + for (#field in v) { /**/ } // Bad - 'in' not allowed + for (let d in #field in v) { /**/ } // Bad - rhs of in should be a object/any + } + whitespace(v) { + const a = v && /*0*/ #field /*1*/ + /*2*/ in /*3*/ + /*4*/ v; /*5*/ + } + flow(u, n, fb, fs, b, fsb, fsfb) { + if (typeof u === 'object') { + if (#field in n) { + n; // good n is never + } + if (#field in u) { + u; // good u is Foo + } + else { + u; // good u is object | null + } + if (u !== null) { + if (#field in u) { + u; // good u is Foo + } + else { + u; // good u is object + } + if (#method in u) { + u; // good u is Foo + } + if (#staticField in u) { + u; // good u is typeof Foo + } + if (#staticMethod in u) { + u; // good u is typeof Foo + } + } + } + if (#field in fb) { + fb; // good fb is Foo + } + else { + fb; // good fb is Bar + } + if (#field in fs) { + fs; // good fs is FooSub + } + else { + fs; // good fs is never + } + if (#field in b) { + b; // good b is 'Bar & Foo' + } + else { + b; // good b is Bar + } + if (#field in fsb) { + fsb; // good fsb is FooSub + } + else { + fsb; // good fsb is Bar + } + if (#field in fsfb) { + fsfb; // good fsfb is 'Foo | FooSub' + } + else { + fsfb; // good fsfb is Bar + } + class Nested { + m(v) { + if (#field in v) { + v; // good v is Foo + } + } + } + } +} +class FooSub extends Foo { + subTypeOfFoo = true; +} +class Bar { + notFoo = true; +} +function badSyntax(v) { + return #field in v; // Bad - outside of class +} diff --git a/tests/baselines/reference/privateNameInInExpression.symbols b/tests/baselines/reference/privateNameInInExpression.symbols new file mode 100644 index 0000000000..2827e42d11 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpression.symbols @@ -0,0 +1,269 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts === +class Foo { +>Foo : Symbol(Foo, Decl(privateNameInInExpression.ts, 0, 0)) + + #field = 1; +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) + + static #staticField = 2; +>#staticField : Symbol(Foo.#staticField, Decl(privateNameInInExpression.ts, 1, 15)) + + #method() {} +>#method : Symbol(Foo.#method, Decl(privateNameInInExpression.ts, 2, 28)) + + static #staticMethod() {} +>#staticMethod : Symbol(Foo.#staticMethod, Decl(privateNameInInExpression.ts, 3, 16)) + + goodRhs(v: any) { +>goodRhs : Symbol(Foo.goodRhs, Decl(privateNameInInExpression.ts, 4, 29)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12)) + + const a = #field in v; +>a : Symbol(a, Decl(privateNameInInExpression.ts, 7, 13)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12)) + + const b = #field in v.p1.p2; +>b : Symbol(b, Decl(privateNameInInExpression.ts, 9, 13)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12)) + + const c = #field in (v as {}); +>c : Symbol(c, Decl(privateNameInInExpression.ts, 11, 13)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12)) + + const d = #field in (v as Foo); +>d : Symbol(d, Decl(privateNameInInExpression.ts, 13, 13)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12)) +>Foo : Symbol(Foo, Decl(privateNameInInExpression.ts, 0, 0)) + + const e = #field in (v as never); +>e : Symbol(e, Decl(privateNameInInExpression.ts, 15, 13)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12)) + + for (let f in #field in v as any) { /**/ } // unlikely but valid +>f : Symbol(f, Decl(privateNameInInExpression.ts, 17, 16)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 6, 12)) + } + badRhs(v: any) { +>badRhs : Symbol(Foo.badRhs, Decl(privateNameInInExpression.ts, 18, 5)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11)) + + const a = #field in (v as unknown); // Bad - RHS of in must be object type or any +>a : Symbol(a, Decl(privateNameInInExpression.ts, 20, 13)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11)) + + const b = #fiel in v; // Bad - typo in privateID +>b : Symbol(b, Decl(privateNameInInExpression.ts, 22, 13)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11)) + + const c = (#field) in v; // Bad - privateID is not an expression on its own +>c : Symbol(c, Decl(privateNameInInExpression.ts, 24, 13)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11)) + + for (#field in v) { /**/ } // Bad - 'in' not allowed +>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11)) + + for (let d in #field in v) { /**/ } // Bad - rhs of in should be a object/any +>d : Symbol(d, Decl(privateNameInInExpression.ts, 28, 16)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 19, 11)) + } + whitespace(v: any) { +>whitespace : Symbol(Foo.whitespace, Decl(privateNameInInExpression.ts, 29, 5)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 30, 15)) + + const a = v && /*0*/#field/*1*/ +>a : Symbol(a, Decl(privateNameInInExpression.ts, 31, 13)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 30, 15)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) + + /*2*/in/*3*/ + /*4*/v/*5*/ +>v : Symbol(v, Decl(privateNameInInExpression.ts, 30, 15)) + } + flow(u: unknown, n: never, fb: Foo | Bar, fs: FooSub, b: Bar, fsb: FooSub | Bar, fsfb: Foo | FooSub | Bar) { +>flow : Symbol(Foo.flow, Decl(privateNameInInExpression.ts, 34, 5)) +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) +>n : Symbol(n, Decl(privateNameInInExpression.ts, 35, 20)) +>fb : Symbol(fb, Decl(privateNameInInExpression.ts, 35, 30)) +>Foo : Symbol(Foo, Decl(privateNameInInExpression.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(privateNameInInExpression.ts, 109, 48)) +>fs : Symbol(fs, Decl(privateNameInInExpression.ts, 35, 45)) +>FooSub : Symbol(FooSub, Decl(privateNameInInExpression.ts, 107, 1)) +>b : Symbol(b, Decl(privateNameInInExpression.ts, 35, 57)) +>Bar : Symbol(Bar, Decl(privateNameInInExpression.ts, 109, 48)) +>fsb : Symbol(fsb, Decl(privateNameInInExpression.ts, 35, 65)) +>FooSub : Symbol(FooSub, Decl(privateNameInInExpression.ts, 107, 1)) +>Bar : Symbol(Bar, Decl(privateNameInInExpression.ts, 109, 48)) +>fsfb : Symbol(fsfb, Decl(privateNameInInExpression.ts, 35, 84)) +>Foo : Symbol(Foo, Decl(privateNameInInExpression.ts, 0, 0)) +>FooSub : Symbol(FooSub, Decl(privateNameInInExpression.ts, 107, 1)) +>Bar : Symbol(Bar, Decl(privateNameInInExpression.ts, 109, 48)) + + if (typeof u === 'object') { +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + + if (#field in n) { +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>n : Symbol(n, Decl(privateNameInInExpression.ts, 35, 20)) + + n; // good n is never +>n : Symbol(n, Decl(privateNameInInExpression.ts, 35, 20)) + } + + if (#field in u) { +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + + u; // good u is Foo +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + + } else { + u; // good u is object | null +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + } + + if (u !== null) { +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + + if (#field in u) { +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + + u; // good u is Foo +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + + } else { + u; // good u is object +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + } + + if (#method in u) { +>#method : Symbol(Foo.#method, Decl(privateNameInInExpression.ts, 2, 28)) +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + + u; // good u is Foo +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + } + + if (#staticField in u) { +>#staticField : Symbol(Foo.#staticField, Decl(privateNameInInExpression.ts, 1, 15)) +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + + u; // good u is typeof Foo +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + } + + if (#staticMethod in u) { +>#staticMethod : Symbol(Foo.#staticMethod, Decl(privateNameInInExpression.ts, 3, 16)) +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + + u; // good u is typeof Foo +>u : Symbol(u, Decl(privateNameInInExpression.ts, 35, 9)) + } + } + } + + if (#field in fb) { +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>fb : Symbol(fb, Decl(privateNameInInExpression.ts, 35, 30)) + + fb; // good fb is Foo +>fb : Symbol(fb, Decl(privateNameInInExpression.ts, 35, 30)) + + } else { + fb; // good fb is Bar +>fb : Symbol(fb, Decl(privateNameInInExpression.ts, 35, 30)) + } + + if (#field in fs) { +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>fs : Symbol(fs, Decl(privateNameInInExpression.ts, 35, 45)) + + fs; // good fs is FooSub +>fs : Symbol(fs, Decl(privateNameInInExpression.ts, 35, 45)) + + } else { + fs; // good fs is never +>fs : Symbol(fs, Decl(privateNameInInExpression.ts, 35, 45)) + } + + if (#field in b) { +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>b : Symbol(b, Decl(privateNameInInExpression.ts, 35, 57)) + + b; // good b is 'Bar & Foo' +>b : Symbol(b, Decl(privateNameInInExpression.ts, 35, 57)) + + } else { + b; // good b is Bar +>b : Symbol(b, Decl(privateNameInInExpression.ts, 35, 57)) + } + + if (#field in fsb) { +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>fsb : Symbol(fsb, Decl(privateNameInInExpression.ts, 35, 65)) + + fsb; // good fsb is FooSub +>fsb : Symbol(fsb, Decl(privateNameInInExpression.ts, 35, 65)) + + } else { + fsb; // good fsb is Bar +>fsb : Symbol(fsb, Decl(privateNameInInExpression.ts, 35, 65)) + } + + if (#field in fsfb) { +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>fsfb : Symbol(fsfb, Decl(privateNameInInExpression.ts, 35, 84)) + + fsfb; // good fsfb is 'Foo | FooSub' +>fsfb : Symbol(fsfb, Decl(privateNameInInExpression.ts, 35, 84)) + + } else { + fsfb; // good fsfb is Bar +>fsfb : Symbol(fsfb, Decl(privateNameInInExpression.ts, 35, 84)) + } + + class Nested { +>Nested : Symbol(Nested, Decl(privateNameInInExpression.ts, 97, 9)) + + m(v: any) { +>m : Symbol(Nested.m, Decl(privateNameInInExpression.ts, 99, 22)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 100, 14)) + + if (#field in v) { +>#field : Symbol(Foo.#field, Decl(privateNameInInExpression.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 100, 14)) + + v; // good v is Foo +>v : Symbol(v, Decl(privateNameInInExpression.ts, 100, 14)) + } + } + } + } +} + +class FooSub extends Foo { subTypeOfFoo = true } +>FooSub : Symbol(FooSub, Decl(privateNameInInExpression.ts, 107, 1)) +>Foo : Symbol(Foo, Decl(privateNameInInExpression.ts, 0, 0)) +>subTypeOfFoo : Symbol(FooSub.subTypeOfFoo, Decl(privateNameInInExpression.ts, 109, 26)) + +class Bar { notFoo = true } +>Bar : Symbol(Bar, Decl(privateNameInInExpression.ts, 109, 48)) +>notFoo : Symbol(Bar.notFoo, Decl(privateNameInInExpression.ts, 110, 11)) + +function badSyntax(v: Foo) { +>badSyntax : Symbol(badSyntax, Decl(privateNameInInExpression.ts, 110, 27)) +>v : Symbol(v, Decl(privateNameInInExpression.ts, 112, 19)) +>Foo : Symbol(Foo, Decl(privateNameInInExpression.ts, 0, 0)) + + return #field in v; // Bad - outside of class +>v : Symbol(v, Decl(privateNameInInExpression.ts, 112, 19)) +} + diff --git a/tests/baselines/reference/privateNameInInExpression.types b/tests/baselines/reference/privateNameInInExpression.types new file mode 100644 index 0000000000..af1ce8a754 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpression.types @@ -0,0 +1,308 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts === +class Foo { +>Foo : Foo + + #field = 1; +>#field : number +>1 : 1 + + static #staticField = 2; +>#staticField : number +>2 : 2 + + #method() {} +>#method : () => void + + static #staticMethod() {} +>#staticMethod : () => void + + goodRhs(v: any) { +>goodRhs : (v: any) => void +>v : any + + const a = #field in v; +>a : boolean +>#field in v : boolean +>#field : any +>v : any + + const b = #field in v.p1.p2; +>b : boolean +>#field in v.p1.p2 : boolean +>#field : any +>v.p1.p2 : any +>v.p1 : any +>v : any +>p1 : any +>p2 : any + + const c = #field in (v as {}); +>c : boolean +>#field in (v as {}) : boolean +>#field : any +>(v as {}) : {} +>v as {} : {} +>v : any + + const d = #field in (v as Foo); +>d : boolean +>#field in (v as Foo) : boolean +>#field : any +>(v as Foo) : Foo +>v as Foo : Foo +>v : any + + const e = #field in (v as never); +>e : boolean +>#field in (v as never) : boolean +>#field : any +>(v as never) : never +>v as never : never +>v : any + + for (let f in #field in v as any) { /**/ } // unlikely but valid +>f : string +>#field in v as any : any +>#field in v : boolean +>#field : any +>v : any + } + badRhs(v: any) { +>badRhs : (v: any) => void +>v : any + + const a = #field in (v as unknown); // Bad - RHS of in must be object type or any +>a : boolean +>#field in (v as unknown) : boolean +>#field : any +>(v as unknown) : unknown +>v as unknown : unknown +>v : any + + const b = #fiel in v; // Bad - typo in privateID +>b : boolean +>#fiel in v : boolean +>#fiel : any +>v : any + + const c = (#field) in v; // Bad - privateID is not an expression on its own +>c : boolean +>(#field) in v : boolean +>(#field) : any +>v : any + + for (#field in v) { /**/ } // Bad - 'in' not allowed +>v : any + + for (let d in #field in v) { /**/ } // Bad - rhs of in should be a object/any +>d : string +>#field in v : boolean +>#field : any +>v : any + } + whitespace(v: any) { +>whitespace : (v: any) => void +>v : any + + const a = v && /*0*/#field/*1*/ +>a : any +>v && /*0*/#field/*1*/ /*2*/in/*3*/ /*4*/v : any +>v : any +>#field/*1*/ /*2*/in/*3*/ /*4*/v : boolean +>#field : any + + /*2*/in/*3*/ + /*4*/v/*5*/ +>v : any + } + flow(u: unknown, n: never, fb: Foo | Bar, fs: FooSub, b: Bar, fsb: FooSub | Bar, fsfb: Foo | FooSub | Bar) { +>flow : (u: unknown, n: never, fb: Foo | Bar, fs: FooSub, b: Bar, fsb: FooSub | Bar, fsfb: Foo | FooSub | Bar) => void +>u : unknown +>n : never +>fb : Foo | Bar +>fs : FooSub +>b : Bar +>fsb : Bar | FooSub +>fsfb : Foo | Bar | FooSub + + if (typeof u === 'object') { +>typeof u === 'object' : boolean +>typeof u : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>u : unknown +>'object' : "object" + + if (#field in n) { +>#field in n : boolean +>#field : any +>n : never + + n; // good n is never +>n : never + } + + if (#field in u) { +>#field in u : boolean +>#field : any +>u : object | null + + u; // good u is Foo +>u : Foo + + } else { + u; // good u is object | null +>u : object | null + } + + if (u !== null) { +>u !== null : boolean +>u : object | null +>null : null + + if (#field in u) { +>#field in u : boolean +>#field : any +>u : object + + u; // good u is Foo +>u : Foo + + } else { + u; // good u is object +>u : object + } + + if (#method in u) { +>#method in u : boolean +>#method : any +>u : object + + u; // good u is Foo +>u : Foo + } + + if (#staticField in u) { +>#staticField in u : boolean +>#staticField : any +>u : object + + u; // good u is typeof Foo +>u : typeof Foo + } + + if (#staticMethod in u) { +>#staticMethod in u : boolean +>#staticMethod : any +>u : object + + u; // good u is typeof Foo +>u : typeof Foo + } + } + } + + if (#field in fb) { +>#field in fb : boolean +>#field : any +>fb : Foo | Bar + + fb; // good fb is Foo +>fb : Foo + + } else { + fb; // good fb is Bar +>fb : Bar + } + + if (#field in fs) { +>#field in fs : boolean +>#field : any +>fs : FooSub + + fs; // good fs is FooSub +>fs : FooSub + + } else { + fs; // good fs is never +>fs : never + } + + if (#field in b) { +>#field in b : boolean +>#field : any +>b : Bar + + b; // good b is 'Bar & Foo' +>b : Bar & Foo + + } else { + b; // good b is Bar +>b : Bar + } + + if (#field in fsb) { +>#field in fsb : boolean +>#field : any +>fsb : Bar | FooSub + + fsb; // good fsb is FooSub +>fsb : FooSub + + } else { + fsb; // good fsb is Bar +>fsb : Bar + } + + if (#field in fsfb) { +>#field in fsfb : boolean +>#field : any +>fsfb : Foo | Bar | FooSub + + fsfb; // good fsfb is 'Foo | FooSub' +>fsfb : Foo | FooSub + + } else { + fsfb; // good fsfb is Bar +>fsfb : Bar + } + + class Nested { +>Nested : Nested + + m(v: any) { +>m : (v: any) => void +>v : any + + if (#field in v) { +>#field in v : boolean +>#field : any +>v : any + + v; // good v is Foo +>v : Foo + } + } + } + } +} + +class FooSub extends Foo { subTypeOfFoo = true } +>FooSub : FooSub +>Foo : Foo +>subTypeOfFoo : boolean +>true : true + +class Bar { notFoo = true } +>Bar : Bar +>notFoo : boolean +>true : true + +function badSyntax(v: Foo) { +>badSyntax : (v: Foo) => boolean +>v : Foo + + return #field in v; // Bad - outside of class +>#field in v : boolean +>#field : any +>v : Foo +} + diff --git a/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).errors.txt b/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).errors.txt new file mode 100644 index 0000000000..1d00c96fac --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).errors.txt @@ -0,0 +1,61 @@ +tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts(20,24): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts(24,14): error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts(29,21): error TS1005: ';' expected. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts(30,21): error TS1005: ';' expected. + + +==== tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts (4 errors) ==== + class Foo { + #field = 1; + #method() {} + static #staticField= 2; + static #staticMethod() {} + + check(v: any) { + #field in v; // expect Foo's 'field' WeakMap + #method in v; // expect Foo's 'instances' WeakSet + #staticField in v; // expect Foo's constructor + #staticMethod in v; // expect Foo's constructor + } + precedence(v: any) { + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + + v == #field in v || v; // Good precedence: (v == (#field in v)) || v + + v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) + ~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + + v << #field in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v + + v == #field in v in v; // Good precedence: v == ((#field in v) in v) + ~~~~~~~~~~~ +!!! error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. + + #field in v && #field in v; // Good precedence: (#field in v) && (#field in v) + } + invalidLHS(v: any) { + 'prop' in v = 10; + ~ +!!! error TS1005: ';' expected. + #field in v = 10; + ~ +!!! error TS1005: ';' expected. + } + } + + class Bar { + #field = 1; + check(v: any) { + #field in v; // expect Bar's 'field' WeakMap + } + } + + function syntaxError(v: Foo) { + return #field in v; // expect `return in v` so runtime will have a syntax error + } + + export { } + \ No newline at end of file diff --git a/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).js b/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).js new file mode 100644 index 0000000000..f8ad510a09 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).js @@ -0,0 +1,97 @@ +//// [privateNameInInExpressionTransform.ts] +class Foo { + #field = 1; + #method() {} + static #staticField= 2; + static #staticMethod() {} + + check(v: any) { + #field in v; // expect Foo's 'field' WeakMap + #method in v; // expect Foo's 'instances' WeakSet + #staticField in v; // expect Foo's constructor + #staticMethod in v; // expect Foo's constructor + } + precedence(v: any) { + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + + v == #field in v || v; // Good precedence: (v == (#field in v)) || v + + v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) + + v << #field in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v + + v == #field in v in v; // Good precedence: v == ((#field in v) in v) + + #field in v && #field in v; // Good precedence: (#field in v) && (#field in v) + } + invalidLHS(v: any) { + 'prop' in v = 10; + #field in v = 10; + } +} + +class Bar { + #field = 1; + check(v: any) { + #field in v; // expect Bar's 'field' WeakMap + } +} + +function syntaxError(v: Foo) { + return #field in v; // expect `return in v` so runtime will have a syntax error +} + +export { } + + +//// [privateNameInInExpressionTransform.js] +var __classPrivateFieldIn = (this && this.__classPrivateFieldIn) || function(state, receiver) { + if (receiver === null || (typeof receiver !== "object" && typeof receiver !== "function")) throw new TypeError("Cannot use 'in' operator on non-object"); + return typeof state === "function" ? receiver === state : state.has(receiver); +}; +var _Foo_instances, _a, _Foo_field, _Foo_method, _Foo_staticField, _Foo_staticMethod, _Bar_field; +class Foo { + constructor() { + _Foo_instances.add(this); + _Foo_field.set(this, 1); + } + check(v) { + __classPrivateFieldIn(_Foo_field, v); // expect Foo's 'field' WeakMap + __classPrivateFieldIn(_Foo_instances, v); // expect Foo's 'instances' WeakSet + __classPrivateFieldIn(_a, v); // expect Foo's constructor + __classPrivateFieldIn(_a, v); // expect Foo's constructor + } + precedence(v) { + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + v == __classPrivateFieldIn(_Foo_field, v) || v; // Good precedence: (v == (#field in v)) || v + v << in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) + v << in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v + v == __classPrivateFieldIn(_Foo_field, v) in v; // Good precedence: v == ((#field in v) in v) + __classPrivateFieldIn(_Foo_field, v) && __classPrivateFieldIn(_Foo_field, v); // Good precedence: (#field in v) && (#field in v) + } + invalidLHS(v) { + 'prop' in v; + 10; + __classPrivateFieldIn(_Foo_field, v); + 10; + } +} +_a = Foo, _Foo_field = new WeakMap(), _Foo_instances = new WeakSet(), _Foo_method = function _Foo_method() { }, _Foo_staticMethod = function _Foo_staticMethod() { }; +_Foo_staticField = { value: 2 }; +class Bar { + constructor() { + _Bar_field.set(this, 1); + } + check(v) { + __classPrivateFieldIn(_Bar_field, v); // expect Bar's 'field' WeakMap + } +} +_Bar_field = new WeakMap(); +function syntaxError(v) { + return in v; // expect `return in v` so runtime will have a syntax error +} +export {}; diff --git a/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).symbols b/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).symbols new file mode 100644 index 0000000000..572beaf65e --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).symbols @@ -0,0 +1,112 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts === +class Foo { +>Foo : Symbol(Foo, Decl(privateNameInInExpressionTransform.ts, 0, 0)) + + #field = 1; +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) + + #method() {} +>#method : Symbol(Foo.#method, Decl(privateNameInInExpressionTransform.ts, 1, 15)) + + static #staticField= 2; +>#staticField : Symbol(Foo.#staticField, Decl(privateNameInInExpressionTransform.ts, 2, 16)) + + static #staticMethod() {} +>#staticMethod : Symbol(Foo.#staticMethod, Decl(privateNameInInExpressionTransform.ts, 3, 27)) + + check(v: any) { +>check : Symbol(Foo.check, Decl(privateNameInInExpressionTransform.ts, 4, 29)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 6, 10)) + + #field in v; // expect Foo's 'field' WeakMap +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 6, 10)) + + #method in v; // expect Foo's 'instances' WeakSet +>#method : Symbol(Foo.#method, Decl(privateNameInInExpressionTransform.ts, 1, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 6, 10)) + + #staticField in v; // expect Foo's constructor +>#staticField : Symbol(Foo.#staticField, Decl(privateNameInInExpressionTransform.ts, 2, 16)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 6, 10)) + + #staticMethod in v; // expect Foo's constructor +>#staticMethod : Symbol(Foo.#staticMethod, Decl(privateNameInInExpressionTransform.ts, 3, 27)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 6, 10)) + } + precedence(v: any) { +>precedence : Symbol(Foo.precedence, Decl(privateNameInInExpressionTransform.ts, 11, 5)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + + v == #field in v || v; // Good precedence: (v == (#field in v)) || v +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + + v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + + v << #field in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + + v == #field in v in v; // Good precedence: v == ((#field in v) in v) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + + #field in v && #field in v; // Good precedence: (#field in v) && (#field in v) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + } + invalidLHS(v: any) { +>invalidLHS : Symbol(Foo.invalidLHS, Decl(privateNameInInExpressionTransform.ts, 26, 5)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 27, 15)) + + 'prop' in v = 10; +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 27, 15)) + + #field in v = 10; +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 27, 15)) + } +} + +class Bar { +>Bar : Symbol(Bar, Decl(privateNameInInExpressionTransform.ts, 31, 1)) + + #field = 1; +>#field : Symbol(Bar.#field, Decl(privateNameInInExpressionTransform.ts, 33, 11)) + + check(v: any) { +>check : Symbol(Bar.check, Decl(privateNameInInExpressionTransform.ts, 34, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 35, 10)) + + #field in v; // expect Bar's 'field' WeakMap +>#field : Symbol(Bar.#field, Decl(privateNameInInExpressionTransform.ts, 33, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 35, 10)) + } +} + +function syntaxError(v: Foo) { +>syntaxError : Symbol(syntaxError, Decl(privateNameInInExpressionTransform.ts, 38, 1)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 40, 21)) +>Foo : Symbol(Foo, Decl(privateNameInInExpressionTransform.ts, 0, 0)) + + return #field in v; // expect `return in v` so runtime will have a syntax error +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 40, 21)) +} + +export { } + diff --git a/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).types b/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).types new file mode 100644 index 0000000000..135b6495e4 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionTransform(target=es2020).types @@ -0,0 +1,141 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts === +class Foo { +>Foo : Foo + + #field = 1; +>#field : number +>1 : 1 + + #method() {} +>#method : () => void + + static #staticField= 2; +>#staticField : number +>2 : 2 + + static #staticMethod() {} +>#staticMethod : () => void + + check(v: any) { +>check : (v: any) => void +>v : any + + #field in v; // expect Foo's 'field' WeakMap +>#field in v : boolean +>#field : any +>v : any + + #method in v; // expect Foo's 'instances' WeakSet +>#method in v : boolean +>#method : any +>v : any + + #staticField in v; // expect Foo's constructor +>#staticField in v : boolean +>#staticField : any +>v : any + + #staticMethod in v; // expect Foo's constructor +>#staticMethod in v : boolean +>#staticMethod : any +>v : any + } + precedence(v: any) { +>precedence : (v: any) => void +>v : any + + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + + v == #field in v || v; // Good precedence: (v == (#field in v)) || v +>v == #field in v || v : any +>v == #field in v : boolean +>v : any +>#field in v : boolean +>#field : any +>v : any +>v : any + + v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) +>v << #field in v << v : boolean +>v << #field : number +>v : any +>v << v : number +>v : any +>v : any + + v << #field in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v +>v << #field in v == v : boolean +>v << #field in v : boolean +>v << #field : number +>v : any +>v : any +>v : any + + v == #field in v in v; // Good precedence: v == ((#field in v) in v) +>v == #field in v in v : boolean +>v : any +>#field in v in v : boolean +>#field in v : boolean +>#field : any +>v : any +>v : any + + #field in v && #field in v; // Good precedence: (#field in v) && (#field in v) +>#field in v && #field in v : boolean +>#field in v : boolean +>#field : any +>v : any +>#field in v : boolean +>#field : any +>v : Foo + } + invalidLHS(v: any) { +>invalidLHS : (v: any) => void +>v : any + + 'prop' in v = 10; +>'prop' in v : boolean +>'prop' : "prop" +>v : any +>10 : 10 + + #field in v = 10; +>#field in v : boolean +>#field : any +>v : any +>10 : 10 + } +} + +class Bar { +>Bar : Bar + + #field = 1; +>#field : number +>1 : 1 + + check(v: any) { +>check : (v: any) => void +>v : any + + #field in v; // expect Bar's 'field' WeakMap +>#field in v : boolean +>#field : any +>v : any + } +} + +function syntaxError(v: Foo) { +>syntaxError : (v: Foo) => boolean +>v : Foo + + return #field in v; // expect `return in v` so runtime will have a syntax error +>#field in v : boolean +>#field : any +>v : Foo +} + +export { } + diff --git a/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).errors.txt b/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).errors.txt new file mode 100644 index 0000000000..b48e2ac788 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).errors.txt @@ -0,0 +1,64 @@ +tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts(4,26): error TS2805: Static fields with private names can't have initializers when the '--useDefineForClassFields' flag is not specified with a '--target' of 'esnext'. Consider adding the '--useDefineForClassFields' flag. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts(20,24): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts(24,14): error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts(29,21): error TS1005: ';' expected. +tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts(30,21): error TS1005: ';' expected. + + +==== tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts (5 errors) ==== + class Foo { + #field = 1; + #method() {} + static #staticField= 2; + ~ +!!! error TS2805: Static fields with private names can't have initializers when the '--useDefineForClassFields' flag is not specified with a '--target' of 'esnext'. Consider adding the '--useDefineForClassFields' flag. + static #staticMethod() {} + + check(v: any) { + #field in v; // expect Foo's 'field' WeakMap + #method in v; // expect Foo's 'instances' WeakSet + #staticField in v; // expect Foo's constructor + #staticMethod in v; // expect Foo's constructor + } + precedence(v: any) { + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + + v == #field in v || v; // Good precedence: (v == (#field in v)) || v + + v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) + ~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + + v << #field in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v + + v == #field in v in v; // Good precedence: v == ((#field in v) in v) + ~~~~~~~~~~~ +!!! error TS2360: The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'. + + #field in v && #field in v; // Good precedence: (#field in v) && (#field in v) + } + invalidLHS(v: any) { + 'prop' in v = 10; + ~ +!!! error TS1005: ';' expected. + #field in v = 10; + ~ +!!! error TS1005: ';' expected. + } + } + + class Bar { + #field = 1; + check(v: any) { + #field in v; // expect Bar's 'field' WeakMap + } + } + + function syntaxError(v: Foo) { + return #field in v; // expect `return in v` so runtime will have a syntax error + } + + export { } + \ No newline at end of file diff --git a/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).js b/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).js new file mode 100644 index 0000000000..50a91a840e --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).js @@ -0,0 +1,87 @@ +//// [privateNameInInExpressionTransform.ts] +class Foo { + #field = 1; + #method() {} + static #staticField= 2; + static #staticMethod() {} + + check(v: any) { + #field in v; // expect Foo's 'field' WeakMap + #method in v; // expect Foo's 'instances' WeakSet + #staticField in v; // expect Foo's constructor + #staticMethod in v; // expect Foo's constructor + } + precedence(v: any) { + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + + v == #field in v || v; // Good precedence: (v == (#field in v)) || v + + v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) + + v << #field in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v + + v == #field in v in v; // Good precedence: v == ((#field in v) in v) + + #field in v && #field in v; // Good precedence: (#field in v) && (#field in v) + } + invalidLHS(v: any) { + 'prop' in v = 10; + #field in v = 10; + } +} + +class Bar { + #field = 1; + check(v: any) { + #field in v; // expect Bar's 'field' WeakMap + } +} + +function syntaxError(v: Foo) { + return #field in v; // expect `return in v` so runtime will have a syntax error +} + +export { } + + +//// [privateNameInInExpressionTransform.js] +class Foo { + #field = 1; + #method() { } + static #staticField = 2; + static #staticMethod() { } + check(v) { + #field in v; // expect Foo's 'field' WeakMap + #method in v; // expect Foo's 'instances' WeakSet + #staticField in v; // expect Foo's constructor + #staticMethod in v; // expect Foo's constructor + } + precedence(v) { + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + v == #field in v || v; // Good precedence: (v == (#field in v)) || v + v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) + v << #field in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v + v == #field in v in v; // Good precedence: v == ((#field in v) in v) + #field in v && #field in v; // Good precedence: (#field in v) && (#field in v) + } + invalidLHS(v) { + 'prop' in v; + 10; + #field in v; + 10; + } +} +class Bar { + #field = 1; + check(v) { + #field in v; // expect Bar's 'field' WeakMap + } +} +function syntaxError(v) { + return #field in v; // expect `return in v` so runtime will have a syntax error +} +export {}; diff --git a/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).symbols b/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).symbols new file mode 100644 index 0000000000..572beaf65e --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).symbols @@ -0,0 +1,112 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts === +class Foo { +>Foo : Symbol(Foo, Decl(privateNameInInExpressionTransform.ts, 0, 0)) + + #field = 1; +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) + + #method() {} +>#method : Symbol(Foo.#method, Decl(privateNameInInExpressionTransform.ts, 1, 15)) + + static #staticField= 2; +>#staticField : Symbol(Foo.#staticField, Decl(privateNameInInExpressionTransform.ts, 2, 16)) + + static #staticMethod() {} +>#staticMethod : Symbol(Foo.#staticMethod, Decl(privateNameInInExpressionTransform.ts, 3, 27)) + + check(v: any) { +>check : Symbol(Foo.check, Decl(privateNameInInExpressionTransform.ts, 4, 29)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 6, 10)) + + #field in v; // expect Foo's 'field' WeakMap +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 6, 10)) + + #method in v; // expect Foo's 'instances' WeakSet +>#method : Symbol(Foo.#method, Decl(privateNameInInExpressionTransform.ts, 1, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 6, 10)) + + #staticField in v; // expect Foo's constructor +>#staticField : Symbol(Foo.#staticField, Decl(privateNameInInExpressionTransform.ts, 2, 16)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 6, 10)) + + #staticMethod in v; // expect Foo's constructor +>#staticMethod : Symbol(Foo.#staticMethod, Decl(privateNameInInExpressionTransform.ts, 3, 27)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 6, 10)) + } + precedence(v: any) { +>precedence : Symbol(Foo.precedence, Decl(privateNameInInExpressionTransform.ts, 11, 5)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + + v == #field in v || v; // Good precedence: (v == (#field in v)) || v +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + + v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + + v << #field in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + + v == #field in v in v; // Good precedence: v == ((#field in v) in v) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + + #field in v && #field in v; // Good precedence: (#field in v) && (#field in v) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 12, 15)) + } + invalidLHS(v: any) { +>invalidLHS : Symbol(Foo.invalidLHS, Decl(privateNameInInExpressionTransform.ts, 26, 5)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 27, 15)) + + 'prop' in v = 10; +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 27, 15)) + + #field in v = 10; +>#field : Symbol(Foo.#field, Decl(privateNameInInExpressionTransform.ts, 0, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 27, 15)) + } +} + +class Bar { +>Bar : Symbol(Bar, Decl(privateNameInInExpressionTransform.ts, 31, 1)) + + #field = 1; +>#field : Symbol(Bar.#field, Decl(privateNameInInExpressionTransform.ts, 33, 11)) + + check(v: any) { +>check : Symbol(Bar.check, Decl(privateNameInInExpressionTransform.ts, 34, 15)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 35, 10)) + + #field in v; // expect Bar's 'field' WeakMap +>#field : Symbol(Bar.#field, Decl(privateNameInInExpressionTransform.ts, 33, 11)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 35, 10)) + } +} + +function syntaxError(v: Foo) { +>syntaxError : Symbol(syntaxError, Decl(privateNameInInExpressionTransform.ts, 38, 1)) +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 40, 21)) +>Foo : Symbol(Foo, Decl(privateNameInInExpressionTransform.ts, 0, 0)) + + return #field in v; // expect `return in v` so runtime will have a syntax error +>v : Symbol(v, Decl(privateNameInInExpressionTransform.ts, 40, 21)) +} + +export { } + diff --git a/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).types b/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).types new file mode 100644 index 0000000000..135b6495e4 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionTransform(target=esnext).types @@ -0,0 +1,141 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts === +class Foo { +>Foo : Foo + + #field = 1; +>#field : number +>1 : 1 + + #method() {} +>#method : () => void + + static #staticField= 2; +>#staticField : number +>2 : 2 + + static #staticMethod() {} +>#staticMethod : () => void + + check(v: any) { +>check : (v: any) => void +>v : any + + #field in v; // expect Foo's 'field' WeakMap +>#field in v : boolean +>#field : any +>v : any + + #method in v; // expect Foo's 'instances' WeakSet +>#method in v : boolean +>#method : any +>v : any + + #staticField in v; // expect Foo's constructor +>#staticField in v : boolean +>#staticField : any +>v : any + + #staticMethod in v; // expect Foo's constructor +>#staticMethod in v : boolean +>#staticMethod : any +>v : any + } + precedence(v: any) { +>precedence : (v: any) => void +>v : any + + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + + v == #field in v || v; // Good precedence: (v == (#field in v)) || v +>v == #field in v || v : any +>v == #field in v : boolean +>v : any +>#field in v : boolean +>#field : any +>v : any +>v : any + + v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) +>v << #field in v << v : boolean +>v << #field : number +>v : any +>v << v : number +>v : any +>v : any + + v << #field in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v +>v << #field in v == v : boolean +>v << #field in v : boolean +>v << #field : number +>v : any +>v : any +>v : any + + v == #field in v in v; // Good precedence: v == ((#field in v) in v) +>v == #field in v in v : boolean +>v : any +>#field in v in v : boolean +>#field in v : boolean +>#field : any +>v : any +>v : any + + #field in v && #field in v; // Good precedence: (#field in v) && (#field in v) +>#field in v && #field in v : boolean +>#field in v : boolean +>#field : any +>v : any +>#field in v : boolean +>#field : any +>v : Foo + } + invalidLHS(v: any) { +>invalidLHS : (v: any) => void +>v : any + + 'prop' in v = 10; +>'prop' in v : boolean +>'prop' : "prop" +>v : any +>10 : 10 + + #field in v = 10; +>#field in v : boolean +>#field : any +>v : any +>10 : 10 + } +} + +class Bar { +>Bar : Bar + + #field = 1; +>#field : number +>1 : 1 + + check(v: any) { +>check : (v: any) => void +>v : any + + #field in v; // expect Bar's 'field' WeakMap +>#field in v : boolean +>#field : any +>v : any + } +} + +function syntaxError(v: Foo) { +>syntaxError : (v: Foo) => boolean +>v : Foo + + return #field in v; // expect `return in v` so runtime will have a syntax error +>#field in v : boolean +>#field : any +>v : Foo +} + +export { } + diff --git a/tests/baselines/reference/privateNameInInExpressionUnused.errors.txt b/tests/baselines/reference/privateNameInInExpressionUnused.errors.txt new file mode 100644 index 0000000000..94d56876d2 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionUnused.errors.txt @@ -0,0 +1,16 @@ +tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionUnused.ts(2,5): error TS6133: '#unused' is declared but its value is never read. + + +==== tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionUnused.ts (1 errors) ==== + class Foo { + #unused: undefined; // expect unused error + ~~~~~~~ +!!! error TS6133: '#unused' is declared but its value is never read. + #brand: undefined; // expect no error + + isFoo(v: any): v is Foo { + // This should count as using/reading '#brand' + return #brand in v; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/privateNameInInExpressionUnused.js b/tests/baselines/reference/privateNameInInExpressionUnused.js new file mode 100644 index 0000000000..651f2932d1 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionUnused.js @@ -0,0 +1,22 @@ +//// [privateNameInInExpressionUnused.ts] +class Foo { + #unused: undefined; // expect unused error + #brand: undefined; // expect no error + + isFoo(v: any): v is Foo { + // This should count as using/reading '#brand' + return #brand in v; + } +} + + +//// [privateNameInInExpressionUnused.js] +"use strict"; +class Foo { + #unused; // expect unused error + #brand; // expect no error + isFoo(v) { + // This should count as using/reading '#brand' + return #brand in v; + } +} diff --git a/tests/baselines/reference/privateNameInInExpressionUnused.symbols b/tests/baselines/reference/privateNameInInExpressionUnused.symbols new file mode 100644 index 0000000000..027c62bc16 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionUnused.symbols @@ -0,0 +1,23 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionUnused.ts === +class Foo { +>Foo : Symbol(Foo, Decl(privateNameInInExpressionUnused.ts, 0, 0)) + + #unused: undefined; // expect unused error +>#unused : Symbol(Foo.#unused, Decl(privateNameInInExpressionUnused.ts, 0, 11)) + + #brand: undefined; // expect no error +>#brand : Symbol(Foo.#brand, Decl(privateNameInInExpressionUnused.ts, 1, 23)) + + isFoo(v: any): v is Foo { +>isFoo : Symbol(Foo.isFoo, Decl(privateNameInInExpressionUnused.ts, 2, 22)) +>v : Symbol(v, Decl(privateNameInInExpressionUnused.ts, 4, 10)) +>v : Symbol(v, Decl(privateNameInInExpressionUnused.ts, 4, 10)) +>Foo : Symbol(Foo, Decl(privateNameInInExpressionUnused.ts, 0, 0)) + + // This should count as using/reading '#brand' + return #brand in v; +>#brand : Symbol(Foo.#brand, Decl(privateNameInInExpressionUnused.ts, 1, 23)) +>v : Symbol(v, Decl(privateNameInInExpressionUnused.ts, 4, 10)) + } +} + diff --git a/tests/baselines/reference/privateNameInInExpressionUnused.types b/tests/baselines/reference/privateNameInInExpressionUnused.types new file mode 100644 index 0000000000..95d8c12e05 --- /dev/null +++ b/tests/baselines/reference/privateNameInInExpressionUnused.types @@ -0,0 +1,22 @@ +=== tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionUnused.ts === +class Foo { +>Foo : Foo + + #unused: undefined; // expect unused error +>#unused : undefined + + #brand: undefined; // expect no error +>#brand : undefined + + isFoo(v: any): v is Foo { +>isFoo : (v: any) => v is Foo +>v : any + + // This should count as using/reading '#brand' + return #brand in v; +>#brand in v : boolean +>#brand : any +>v : any + } +} + diff --git a/tests/cases/compiler/importHelpersES6.ts b/tests/cases/compiler/importHelpersES6.ts index 394c01e147..3655aa7742 100644 --- a/tests/cases/compiler/importHelpersES6.ts +++ b/tests/cases/compiler/importHelpersES6.ts @@ -6,6 +6,7 @@ declare var dec: any; @dec export class A { #x: number = 1; async f() { this.#x = await this.#x; } + g(u) { return #x in u; } } const o = { a: 1 }; @@ -19,3 +20,4 @@ export declare function __metadata(metadataKey: any, metadataValue: any): Functi export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; export declare function __classPrivateFieldGet(a: any, b: any, c: any, d: any): any; export declare function __classPrivateFieldSet(a: any, b: any, c: any, d: any, e: any): any; +export declare function __classPrivateFieldIn(a: any, b: any): boolean; diff --git a/tests/cases/compiler/importHelpersNoHelpersForPrivateFields.ts b/tests/cases/compiler/importHelpersNoHelpersForPrivateFields.ts new file mode 100644 index 0000000000..e9687dfcd6 --- /dev/null +++ b/tests/cases/compiler/importHelpersNoHelpersForPrivateFields.ts @@ -0,0 +1,16 @@ +// @importHelpers: true +// @target: es2020 +// @module: commonjs +// @lib: esnext +// @moduleResolution: classic +// @filename: main.ts +export class Foo { + #field = true; + f() { + this.#field = this.#field; + #field in this; + } +} + +// @filename: tslib.d.ts +export {} diff --git a/tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts b/tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts new file mode 100644 index 0000000000..e274378150 --- /dev/null +++ b/tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts @@ -0,0 +1,119 @@ +// @strict: true +// @target: esnext +// @useDefineForClassFields: true + +class Foo { + #field = 1; + static #staticField = 2; + #method() {} + static #staticMethod() {} + + goodRhs(v: any) { + const a = #field in v; + + const b = #field in v.p1.p2; + + const c = #field in (v as {}); + + const d = #field in (v as Foo); + + const e = #field in (v as never); + + for (let f in #field in v as any) { /**/ } // unlikely but valid + } + badRhs(v: any) { + const a = #field in (v as unknown); // Bad - RHS of in must be object type or any + + const b = #fiel in v; // Bad - typo in privateID + + const c = (#field) in v; // Bad - privateID is not an expression on its own + + for (#field in v) { /**/ } // Bad - 'in' not allowed + + for (let d in #field in v) { /**/ } // Bad - rhs of in should be a object/any + } + whitespace(v: any) { + const a = v && /*0*/#field/*1*/ + /*2*/in/*3*/ + /*4*/v/*5*/ + } + flow(u: unknown, n: never, fb: Foo | Bar, fs: FooSub, b: Bar, fsb: FooSub | Bar, fsfb: Foo | FooSub | Bar) { + + if (typeof u === 'object') { + if (#field in n) { + n; // good n is never + } + + if (#field in u) { + u; // good u is Foo + } else { + u; // good u is object | null + } + + if (u !== null) { + if (#field in u) { + u; // good u is Foo + } else { + u; // good u is object + } + + if (#method in u) { + u; // good u is Foo + } + + if (#staticField in u) { + u; // good u is typeof Foo + } + + if (#staticMethod in u) { + u; // good u is typeof Foo + } + } + } + + if (#field in fb) { + fb; // good fb is Foo + } else { + fb; // good fb is Bar + } + + if (#field in fs) { + fs; // good fs is FooSub + } else { + fs; // good fs is never + } + + if (#field in b) { + b; // good b is 'Bar & Foo' + } else { + b; // good b is Bar + } + + if (#field in fsb) { + fsb; // good fsb is FooSub + } else { + fsb; // good fsb is Bar + } + + if (#field in fsfb) { + fsfb; // good fsfb is 'Foo | FooSub' + } else { + fsfb; // good fsfb is Bar + } + + class Nested { + m(v: any) { + if (#field in v) { + v; // good v is Foo + } + } + } + } +} + +class FooSub extends Foo { subTypeOfFoo = true } +class Bar { notFoo = true } + +function badSyntax(v: Foo) { + return #field in v; // Bad - outside of class +} diff --git a/tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts b/tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts new file mode 100644 index 0000000000..f7be176373 --- /dev/null +++ b/tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts @@ -0,0 +1,47 @@ +// @target: esnext, es2020 + +class Foo { + #field = 1; + #method() {} + static #staticField= 2; + static #staticMethod() {} + + check(v: any) { + #field in v; // expect Foo's 'field' WeakMap + #method in v; // expect Foo's 'instances' WeakSet + #staticField in v; // expect Foo's constructor + #staticMethod in v; // expect Foo's constructor + } + precedence(v: any) { + // '==' and '||' have lower precedence than 'in' + // 'in' naturally has same precedence as 'in' + // '<<' has higher precedence than 'in' + + v == #field in v || v; // Good precedence: (v == (#field in v)) || v + + v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) + + v << #field in v == v; // Good precedence (SyntaxError): ((v << #field) in v) == v + + v == #field in v in v; // Good precedence: v == ((#field in v) in v) + + #field in v && #field in v; // Good precedence: (#field in v) && (#field in v) + } + invalidLHS(v: any) { + 'prop' in v = 10; + #field in v = 10; + } +} + +class Bar { + #field = 1; + check(v: any) { + #field in v; // expect Bar's 'field' WeakMap + } +} + +function syntaxError(v: Foo) { + return #field in v; // expect `return in v` so runtime will have a syntax error +} + +export { } diff --git a/tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionUnused.ts b/tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionUnused.ts new file mode 100644 index 0000000000..4b214a2b9e --- /dev/null +++ b/tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionUnused.ts @@ -0,0 +1,13 @@ +// @strict: true +// @noUnusedLocals: true +// @target: esnext + +class Foo { + #unused: undefined; // expect unused error + #brand: undefined; // expect no error + + isFoo(v: any): v is Foo { + // This should count as using/reading '#brand' + return #brand in v; + } +} diff --git a/tests/cases/fourslash/codeFixSpellingPrivateNameInIn.ts b/tests/cases/fourslash/codeFixSpellingPrivateNameInIn.ts new file mode 100644 index 0000000000..daba84dde2 --- /dev/null +++ b/tests/cases/fourslash/codeFixSpellingPrivateNameInIn.ts @@ -0,0 +1,19 @@ +/// + +////class A { +//// #foo: number; +//// static isA(v: A) { +//// [|return #fo in v;|] +//// } +////} + +verify.codeFixAvailable([ + { description: "Change spelling to '#foo'" }, + { description: "Remove unused declaration for: '#foo'" }, +]); + +verify.codeFix({ + index: 0, + description: "Change spelling to '#foo'", + newRangeContent: "return #foo in v;" +}); diff --git a/tests/cases/fourslash/findAllRefsPrivateNameProperties.ts b/tests/cases/fourslash/findAllRefsPrivateNameProperties.ts index 8470772e9b..06c886b7e4 100644 --- a/tests/cases/fourslash/findAllRefsPrivateNameProperties.ts +++ b/tests/cases/fourslash/findAllRefsPrivateNameProperties.ts @@ -4,6 +4,7 @@ //// [|[|{|"isDefinition": true, "isWriteAccess": true, "contextRangeIndex": 0 |}#foo|] = 10;|] //// constructor() { //// this.[|{|"isWriteAccess": true|}#foo|] = 20; +//// [|#foo|] in this; //// } ////} ////class D extends C { @@ -13,12 +14,12 @@ //// } ////} ////class E { -//// [|[|{|"isDefinition": true, "contextRangeIndex": 3 |}#foo|]: number;|] +//// [|[|{|"isDefinition": true, "contextRangeIndex": 4 |}#foo|]: number;|] //// constructor() { //// this.[|{|"isWriteAccess": true|}#foo|] = 20; //// } ////} -const [rC0Def, rC0, rC1, rE0Def, rE0, rE1] = test.ranges(); -verify.singleReferenceGroup("(property) C.#foo: number", [rC0, rC1]); +const [rC0Def, rC0, rC1, rC2, rE0Def, rE0, rE1] = test.ranges(); +verify.singleReferenceGroup("(property) C.#foo: number", [rC0, rC1, rC2]); verify.singleReferenceGroup("(property) E.#foo: number", [rE0, rE1]);