mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
fix(54465): Broken emit with private field in class decorator (#54679)
This commit is contained in:
+15
-8
@@ -248,6 +248,7 @@ import {
|
||||
getCombinedModifierFlags,
|
||||
getCombinedNodeFlags,
|
||||
getContainingClass,
|
||||
getContainingClassExcludingClassDecorators,
|
||||
getContainingClassStaticBlock,
|
||||
getContainingFunction,
|
||||
getContainingFunctionOrClassStaticBlock,
|
||||
@@ -31407,7 +31408,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
|
||||
// Lookup the private identifier lexically.
|
||||
function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined {
|
||||
for (let containingClass = getContainingClass(location); !!containingClass; containingClass = getContainingClass(containingClass)) {
|
||||
for (let containingClass = getContainingClassExcludingClassDecorators(location); !!containingClass; containingClass = getContainingClass(containingClass)) {
|
||||
const { symbol } = containingClass;
|
||||
const name = getSymbolNameForPrivateIdentifier(symbol, propName);
|
||||
const prop = (symbol.members && symbol.members.get(name)) || (symbol.exports && symbol.exports.get(name));
|
||||
@@ -31547,23 +31548,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (assignmentKind && lexicallyScopedSymbol && lexicallyScopedSymbol.valueDeclaration && isMethodDeclaration(lexicallyScopedSymbol.valueDeclaration)) {
|
||||
grammarErrorOnNode(right, Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable, idText(right));
|
||||
}
|
||||
|
||||
if (isAnyLike) {
|
||||
if (lexicallyScopedSymbol) {
|
||||
return isErrorType(apparentType) ? errorType : apparentType;
|
||||
}
|
||||
if (!getContainingClass(right)) {
|
||||
if (getContainingClassExcludingClassDecorators(right) === undefined) {
|
||||
grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
|
||||
return anyType;
|
||||
}
|
||||
}
|
||||
prop = lexicallyScopedSymbol ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol) : undefined;
|
||||
// Check for private-identifier-specific shadowing and lexical-scoping errors.
|
||||
if (!prop && checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) {
|
||||
return errorType;
|
||||
|
||||
prop = lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol);
|
||||
if (prop === undefined) {
|
||||
// Check for private-identifier-specific shadowing and lexical-scoping errors.
|
||||
if (checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) {
|
||||
return errorType;
|
||||
}
|
||||
const containingClass = getContainingClassExcludingClassDecorators(right);
|
||||
if (containingClass && isPlainJsFile(getSourceFileOfNode(containingClass), compilerOptions.checkJs)) {
|
||||
grammarErrorOnNode(right, Diagnostics.Private_field_0_must_be_declared_in_an_enclosing_class, idText(right));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const isSetonlyAccessor = prop && prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
|
||||
const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
|
||||
if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) {
|
||||
error(node, Diagnostics.Private_accessor_was_defined_without_a_getter);
|
||||
}
|
||||
|
||||
@@ -315,6 +315,10 @@
|
||||
"category": "Error",
|
||||
"code": 1110
|
||||
},
|
||||
"Private field '{0}' must be declared in an enclosing class.": {
|
||||
"category": "Error",
|
||||
"code": 1111
|
||||
},
|
||||
"A 'default' clause cannot appear more than once in a 'switch' statement.": {
|
||||
"category": "Error",
|
||||
"code": 1113
|
||||
|
||||
@@ -1430,6 +1430,7 @@ export const plainJSErrors: Set<number> = new Set([
|
||||
Diagnostics.Class_constructor_may_not_be_an_accessor.code,
|
||||
Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules.code,
|
||||
Diagnostics.await_using_statements_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules.code,
|
||||
Diagnostics.Private_field_0_must_be_declared_in_an_enclosing_class.code,
|
||||
// Type errors
|
||||
Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value.code,
|
||||
]);
|
||||
|
||||
@@ -2838,6 +2838,12 @@ export function getContainingFunctionOrClassStaticBlock(node: Node): SignatureDe
|
||||
return findAncestor(node.parent, isFunctionLikeOrClassStaticBlockDeclaration);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getContainingClassExcludingClassDecorators(node: Node): ClassLikeDeclaration | undefined {
|
||||
const decorator = findAncestor(node.parent, n => isClassLike(n) ? "quit" : isDecorator(n));
|
||||
return decorator && isClassLike(decorator.parent) ? getContainingClass(decorator.parent) : getContainingClass(decorator ?? node);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type ThisContainer =
|
||||
| FunctionDeclaration
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
esDecorators-privateFieldAccess.ts(3,13): error TS18016: Private identifiers are not allowed outside class bodies.
|
||||
esDecorators-privateFieldAccess.ts(11,18): error TS18013: Property '#foo' is not accessible outside class 'B' because it has a private identifier.
|
||||
|
||||
|
||||
==== esDecorators-privateFieldAccess.ts (2 errors) ====
|
||||
declare let dec: any;
|
||||
|
||||
@dec(x => x.#foo) // error
|
||||
~~~~
|
||||
!!! error TS18016: Private identifiers are not allowed outside class bodies.
|
||||
class A {
|
||||
#foo = 3;
|
||||
|
||||
@dec(this, (x: A) => x.#foo) // ok
|
||||
m() {}
|
||||
}
|
||||
|
||||
@dec((x: B) => x.#foo) // error
|
||||
~~~~
|
||||
!!! error TS18013: Property '#foo' is not accessible outside class 'B' because it has a private identifier.
|
||||
class B {
|
||||
#foo = 3;
|
||||
}
|
||||
|
||||
class C {
|
||||
#foo = 2;
|
||||
m() {
|
||||
@dec(() => this.#foo) // ok
|
||||
class D {}
|
||||
return D;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
//// [tests/cases/conformance/esDecorators/esDecorators-privateFieldAccess.ts] ////
|
||||
|
||||
//// [esDecorators-privateFieldAccess.ts]
|
||||
declare let dec: any;
|
||||
|
||||
@dec(x => x.#foo) // error
|
||||
class A {
|
||||
#foo = 3;
|
||||
|
||||
@dec(this, (x: A) => x.#foo) // ok
|
||||
m() {}
|
||||
}
|
||||
|
||||
@dec((x: B) => x.#foo) // error
|
||||
class B {
|
||||
#foo = 3;
|
||||
}
|
||||
|
||||
class C {
|
||||
#foo = 2;
|
||||
m() {
|
||||
@dec(() => this.#foo) // ok
|
||||
class D {}
|
||||
return D;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// [esDecorators-privateFieldAccess.js]
|
||||
@dec(x => x.#foo) // error
|
||||
class A {
|
||||
#foo = 3;
|
||||
@dec(this, (x) => x.#foo) // ok
|
||||
m() { }
|
||||
}
|
||||
@dec((x) => x.#foo) // error
|
||||
class B {
|
||||
#foo = 3;
|
||||
}
|
||||
class C {
|
||||
#foo = 2;
|
||||
m() {
|
||||
@dec(() => this.#foo) // ok
|
||||
class D {
|
||||
}
|
||||
return D;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//// [tests/cases/conformance/esDecorators/esDecorators-privateFieldAccess.ts] ////
|
||||
|
||||
=== esDecorators-privateFieldAccess.ts ===
|
||||
declare let dec: any;
|
||||
>dec : Symbol(dec, Decl(esDecorators-privateFieldAccess.ts, 0, 11))
|
||||
|
||||
@dec(x => x.#foo) // error
|
||||
>dec : Symbol(dec, Decl(esDecorators-privateFieldAccess.ts, 0, 11))
|
||||
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 2, 5))
|
||||
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 2, 5))
|
||||
|
||||
class A {
|
||||
>A : Symbol(A, Decl(esDecorators-privateFieldAccess.ts, 0, 21))
|
||||
|
||||
#foo = 3;
|
||||
>#foo : Symbol(A.#foo, Decl(esDecorators-privateFieldAccess.ts, 3, 9))
|
||||
|
||||
@dec(this, (x: A) => x.#foo) // ok
|
||||
>dec : Symbol(dec, Decl(esDecorators-privateFieldAccess.ts, 0, 11))
|
||||
>this : Symbol(globalThis)
|
||||
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 6, 16))
|
||||
>A : Symbol(A, Decl(esDecorators-privateFieldAccess.ts, 0, 21))
|
||||
>x.#foo : Symbol(A.#foo, Decl(esDecorators-privateFieldAccess.ts, 3, 9))
|
||||
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 6, 16))
|
||||
|
||||
m() {}
|
||||
>m : Symbol(A.m, Decl(esDecorators-privateFieldAccess.ts, 4, 13))
|
||||
}
|
||||
|
||||
@dec((x: B) => x.#foo) // error
|
||||
>dec : Symbol(dec, Decl(esDecorators-privateFieldAccess.ts, 0, 11))
|
||||
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 10, 6))
|
||||
>B : Symbol(B, Decl(esDecorators-privateFieldAccess.ts, 8, 1))
|
||||
>x : Symbol(x, Decl(esDecorators-privateFieldAccess.ts, 10, 6))
|
||||
|
||||
class B {
|
||||
>B : Symbol(B, Decl(esDecorators-privateFieldAccess.ts, 8, 1))
|
||||
|
||||
#foo = 3;
|
||||
>#foo : Symbol(B.#foo, Decl(esDecorators-privateFieldAccess.ts, 11, 9))
|
||||
}
|
||||
|
||||
class C {
|
||||
>C : Symbol(C, Decl(esDecorators-privateFieldAccess.ts, 13, 1))
|
||||
|
||||
#foo = 2;
|
||||
>#foo : Symbol(C.#foo, Decl(esDecorators-privateFieldAccess.ts, 15, 9))
|
||||
|
||||
m() {
|
||||
>m : Symbol(C.m, Decl(esDecorators-privateFieldAccess.ts, 16, 13))
|
||||
|
||||
@dec(() => this.#foo) // ok
|
||||
>dec : Symbol(dec, Decl(esDecorators-privateFieldAccess.ts, 0, 11))
|
||||
>this.#foo : Symbol(C.#foo, Decl(esDecorators-privateFieldAccess.ts, 15, 9))
|
||||
>this : Symbol(C, Decl(esDecorators-privateFieldAccess.ts, 13, 1))
|
||||
|
||||
class D {}
|
||||
>D : Symbol(D, Decl(esDecorators-privateFieldAccess.ts, 17, 9))
|
||||
|
||||
return D;
|
||||
>D : Symbol(D, Decl(esDecorators-privateFieldAccess.ts, 17, 9))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
//// [tests/cases/conformance/esDecorators/esDecorators-privateFieldAccess.ts] ////
|
||||
|
||||
=== esDecorators-privateFieldAccess.ts ===
|
||||
declare let dec: any;
|
||||
>dec : any
|
||||
|
||||
@dec(x => x.#foo) // error
|
||||
>dec(x => x.#foo) : any
|
||||
>dec : any
|
||||
>x => x.#foo : (x: any) => any
|
||||
>x : any
|
||||
>x.#foo : any
|
||||
>x : any
|
||||
|
||||
class A {
|
||||
>A : A
|
||||
|
||||
#foo = 3;
|
||||
>#foo : number
|
||||
>3 : 3
|
||||
|
||||
@dec(this, (x: A) => x.#foo) // ok
|
||||
>dec(this, (x: A) => x.#foo) : any
|
||||
>dec : any
|
||||
>this : typeof globalThis
|
||||
>(x: A) => x.#foo : (x: A) => number
|
||||
>x : A
|
||||
>x.#foo : number
|
||||
>x : A
|
||||
|
||||
m() {}
|
||||
>m : () => void
|
||||
}
|
||||
|
||||
@dec((x: B) => x.#foo) // error
|
||||
>dec((x: B) => x.#foo) : any
|
||||
>dec : any
|
||||
>(x: B) => x.#foo : (x: B) => any
|
||||
>x : B
|
||||
>x.#foo : any
|
||||
>x : B
|
||||
|
||||
class B {
|
||||
>B : B
|
||||
|
||||
#foo = 3;
|
||||
>#foo : number
|
||||
>3 : 3
|
||||
}
|
||||
|
||||
class C {
|
||||
>C : C
|
||||
|
||||
#foo = 2;
|
||||
>#foo : number
|
||||
>2 : 2
|
||||
|
||||
m() {
|
||||
>m : () => typeof D
|
||||
|
||||
@dec(() => this.#foo) // ok
|
||||
>dec(() => this.#foo) : any
|
||||
>dec : any
|
||||
>() => this.#foo : () => number
|
||||
>this.#foo : number
|
||||
>this : this
|
||||
|
||||
class D {}
|
||||
>D : D
|
||||
|
||||
return D;
|
||||
>D : typeof D
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
plainJSGrammarErrors4.js(5,14): error TS1111: Private field '#b' must be declared in an enclosing class.
|
||||
|
||||
|
||||
==== plainJSGrammarErrors4.js (1 errors) ====
|
||||
class A {
|
||||
#a;
|
||||
m() {
|
||||
this.#a; // ok
|
||||
this.#b; // error
|
||||
~~
|
||||
!!! error TS1111: Private field '#b' must be declared in an enclosing class.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//// [tests/cases/conformance/salsa/plainJSGrammarErrors4.ts] ////
|
||||
|
||||
//// [plainJSGrammarErrors4.js]
|
||||
class A {
|
||||
#a;
|
||||
m() {
|
||||
this.#a; // ok
|
||||
this.#b; // error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// [plainJSGrammarErrors4.js]
|
||||
class A {
|
||||
#a;
|
||||
m() {
|
||||
this.#a; // ok
|
||||
this.#b; // error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//// [tests/cases/conformance/salsa/plainJSGrammarErrors4.ts] ////
|
||||
|
||||
=== plainJSGrammarErrors4.js ===
|
||||
class A {
|
||||
>A : Symbol(A, Decl(plainJSGrammarErrors4.js, 0, 0))
|
||||
|
||||
#a;
|
||||
>#a : Symbol(A.#a, Decl(plainJSGrammarErrors4.js, 0, 9))
|
||||
|
||||
m() {
|
||||
>m : Symbol(A.m, Decl(plainJSGrammarErrors4.js, 1, 7))
|
||||
|
||||
this.#a; // ok
|
||||
>this.#a : Symbol(A.#a, Decl(plainJSGrammarErrors4.js, 0, 9))
|
||||
>this : Symbol(A, Decl(plainJSGrammarErrors4.js, 0, 0))
|
||||
|
||||
this.#b; // error
|
||||
>this : Symbol(A, Decl(plainJSGrammarErrors4.js, 0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//// [tests/cases/conformance/salsa/plainJSGrammarErrors4.ts] ////
|
||||
|
||||
=== plainJSGrammarErrors4.js ===
|
||||
class A {
|
||||
>A : A
|
||||
|
||||
#a;
|
||||
>#a : any
|
||||
|
||||
m() {
|
||||
>m : () => void
|
||||
|
||||
this.#a; // ok
|
||||
>this.#a : any
|
||||
>this : this
|
||||
|
||||
this.#b; // error
|
||||
>this.#b : any
|
||||
>this : this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// @target: esnext
|
||||
// @noEmitHelpers: true
|
||||
|
||||
declare let dec: any;
|
||||
|
||||
@dec(x => x.#foo) // error
|
||||
class A {
|
||||
#foo = 3;
|
||||
|
||||
@dec(this, (x: A) => x.#foo) // ok
|
||||
m() {}
|
||||
}
|
||||
|
||||
@dec((x: B) => x.#foo) // error
|
||||
class B {
|
||||
#foo = 3;
|
||||
}
|
||||
|
||||
class C {
|
||||
#foo = 2;
|
||||
m() {
|
||||
@dec(() => this.#foo) // ok
|
||||
class D {}
|
||||
return D;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// @outdir: out/
|
||||
// @target: esnext
|
||||
// @module: esnext
|
||||
// @allowJs: true
|
||||
// @filename: plainJSGrammarErrors4.js
|
||||
class A {
|
||||
#a;
|
||||
m() {
|
||||
this.#a; // ok
|
||||
this.#b; // error
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user