From aa78fe87f997075b82a51c957c3b9ccb106aa278 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 26 Feb 2025 13:28:57 -0800 Subject: [PATCH 1/3] Restrict indexed accesses on nonpublic fields of concrete types the same way we do generic ones --- src/compiler/checker.ts | 26 +++++++++++----- src/compiler/diagnosticMessages.json | 4 +++ src/server/editorServices.ts | 10 +++++-- ...AccessOfConcreteNonpublicFields.errors.txt | 15 ++++++++++ .../indexedAccessOfConcreteNonpublicFields.js | 30 +++++++++++++++++++ ...xedAccessOfConcreteNonpublicFields.symbols | 19 ++++++++++++ ...dexedAccessOfConcreteNonpublicFields.types | 26 ++++++++++++++++ .../keyofAndIndexedAccess.errors.txt | 8 ++++- ...rotectedAndIntersectionProperty.errors.txt | 11 ++++++- .../indexedAccessOfConcreteNonpublicFields.ts | 7 +++++ 10 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.errors.txt create mode 100644 tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.js create mode 100644 tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.symbols create mode 100644 tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.types create mode 100644 tests/cases/compiler/indexedAccessOfConcreteNonpublicFields.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cbdb92933023e..c4afcb8605092 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -42066,6 +42066,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) { if (!(type.flags & TypeFlags.IndexedAccess)) { + if (isIndexedAccessTypeNode(accessNode)) { + const indexType = getTypeFromTypeNode(accessNode.indexType); + const objectType = getTypeFromTypeNode(accessNode.objectType); + const propertyName = getPropertyNameFromIndex(indexType, accessNode); + if (propertyName) { + const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName)); + if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { + error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_via_indexed_access, unescapeLeadingUnderscores(propertyName)); + return errorType; + } + } + } return type; } // Check if the index type is assignable to 'keyof T' for the object type. @@ -42085,14 +42097,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return type; } - if (isGenericObjectType(objectType)) { - const propertyName = getPropertyNameFromIndex(indexType, accessNode); - if (propertyName) { - const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName)); - if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { - error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); - return errorType; - } + const propertyName = getPropertyNameFromIndex(indexType, accessNode); + if (propertyName) { + const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName)); + if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { + error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); + return errorType; } } error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index be2fe3957b20a..bd651ad2478a1 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4436,6 +4436,10 @@ "category": "Error", "code": 4128 }, + "Private or protected member '{0}' cannot be accessed via indexed access.": { + "category": "Error", + "code": 4129 + }, "The current host does not support the '{0}' option.": { "category": "Error", diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 23f8d844c9856..30a47b3b2ca63 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -748,7 +748,13 @@ export type ConfiguredProjectToAnyReloadKind = Map< >; /** @internal */ -export type DefaultConfiguredProjectResult = ReturnType; +export interface DefaultConfiguredProjectResult { + defaultProject: ConfiguredProject | undefined; + tsconfigProject: ConfiguredProject | undefined; + sentConfigDiag: Set; + seenProjects: ConfigureProjectToLoadKind; + seenConfigs: Set | undefined; +}; /** @internal */ export interface FindCreateOrLoadConfiguredProjectResult { @@ -4534,7 +4540,7 @@ export class ProjectService { allowDeferredClosed?: boolean, /** Used with ConfiguredProjectLoadKind.Reload to check if this project was already reloaded */ reloadedProjects?: ConfiguredProjectToAnyReloadKind, - ) { + ): DefaultConfiguredProjectResult { const infoIsOpenScriptInfo = isOpenScriptInfo(info); const optimizedKind = toConfiguredProjectLoadOptimized(kind); const seenProjects: ConfigureProjectToLoadKind = new Map(); diff --git a/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.errors.txt b/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.errors.txt new file mode 100644 index 0000000000000..abdbbc0d7dc35 --- /dev/null +++ b/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.errors.txt @@ -0,0 +1,15 @@ +indexedAccessOfConcreteNonpublicFields.ts(4,22): error TS4129: Private or protected member '_property' cannot be accessed via indexed access. +indexedAccessOfConcreteNonpublicFields.ts(4,47): error TS4129: Private or protected member '_property2' cannot be accessed via indexed access. + + +==== indexedAccessOfConcreteNonpublicFields.ts (2 errors) ==== + export class Foo { + private _property: string = ''; + protected _property2: string = ''; + constructor(arg: Foo['_property'], other: Foo['_property2']) { + ~~~~~~~~~~~~~~~~ +!!! error TS4129: Private or protected member '_property' cannot be accessed via indexed access. + ~~~~~~~~~~~~~~~~~ +!!! error TS4129: Private or protected member '_property2' cannot be accessed via indexed access. + } + } \ No newline at end of file diff --git a/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.js b/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.js new file mode 100644 index 0000000000000..47ba4d935033c --- /dev/null +++ b/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.js @@ -0,0 +1,30 @@ +//// [tests/cases/compiler/indexedAccessOfConcreteNonpublicFields.ts] //// + +//// [indexedAccessOfConcreteNonpublicFields.ts] +export class Foo { + private _property: string = ''; + protected _property2: string = ''; + constructor(arg: Foo['_property'], other: Foo['_property2']) { + } +} + +//// [indexedAccessOfConcreteNonpublicFields.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Foo = void 0; +var Foo = /** @class */ (function () { + function Foo(arg, other) { + this._property = ''; + this._property2 = ''; + } + return Foo; +}()); +exports.Foo = Foo; + + +//// [indexedAccessOfConcreteNonpublicFields.d.ts] +export declare class Foo { + private _property; + protected _property2: string; + constructor(arg: Foo['_property'], other: Foo['_property2']); +} diff --git a/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.symbols b/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.symbols new file mode 100644 index 0000000000000..e2cf3b4f1377e --- /dev/null +++ b/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.symbols @@ -0,0 +1,19 @@ +//// [tests/cases/compiler/indexedAccessOfConcreteNonpublicFields.ts] //// + +=== indexedAccessOfConcreteNonpublicFields.ts === +export class Foo { +>Foo : Symbol(Foo, Decl(indexedAccessOfConcreteNonpublicFields.ts, 0, 0)) + + private _property: string = ''; +>_property : Symbol(Foo._property, Decl(indexedAccessOfConcreteNonpublicFields.ts, 0, 18)) + + protected _property2: string = ''; +>_property2 : Symbol(Foo._property2, Decl(indexedAccessOfConcreteNonpublicFields.ts, 1, 35)) + + constructor(arg: Foo['_property'], other: Foo['_property2']) { +>arg : Symbol(arg, Decl(indexedAccessOfConcreteNonpublicFields.ts, 3, 16)) +>Foo : Symbol(Foo, Decl(indexedAccessOfConcreteNonpublicFields.ts, 0, 0)) +>other : Symbol(other, Decl(indexedAccessOfConcreteNonpublicFields.ts, 3, 38)) +>Foo : Symbol(Foo, Decl(indexedAccessOfConcreteNonpublicFields.ts, 0, 0)) + } +} diff --git a/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.types b/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.types new file mode 100644 index 0000000000000..a46baaf9aec0b --- /dev/null +++ b/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.types @@ -0,0 +1,26 @@ +//// [tests/cases/compiler/indexedAccessOfConcreteNonpublicFields.ts] //// + +=== indexedAccessOfConcreteNonpublicFields.ts === +export class Foo { +>Foo : Foo +> : ^^^ + + private _property: string = ''; +>_property : string +> : ^^^^^^ +>'' : "" +> : ^^ + + protected _property2: string = ''; +>_property2 : string +> : ^^^^^^ +>'' : "" +> : ^^ + + constructor(arg: Foo['_property'], other: Foo['_property2']) { +>arg : string +> : ^^^^^^ +>other : string +> : ^^^^^^ + } +} diff --git a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt index e833106df6fe5..447fe5d54712a 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt @@ -1,3 +1,5 @@ +keyofAndIndexedAccess.ts(173,14): error TS4129: Private or protected member 'y' cannot be accessed via indexed access. +keyofAndIndexedAccess.ts(174,14): error TS4129: Private or protected member 'z' cannot be accessed via indexed access. keyofAndIndexedAccess.ts(205,24): error TS2322: Type 'T[keyof T]' is not assignable to type 'object'. Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'object'. Type 'T[string]' is not assignable to type 'object'. @@ -15,7 +17,7 @@ keyofAndIndexedAccess.ts(318,5): error TS2322: Type 'T[K]' is not assignable to Type 'T[string]' is not assignable to type '{}'. -==== keyofAndIndexedAccess.ts (5 errors) ==== +==== keyofAndIndexedAccess.ts (7 errors) ==== class Shape { name: string; width: number; @@ -189,7 +191,11 @@ keyofAndIndexedAccess.ts(318,5): error TS2322: Type 'T[K]' is not assignable to function f40(c: C) { type X = C["x"]; type Y = C["y"]; + ~~~~~~ +!!! error TS4129: Private or protected member 'y' cannot be accessed via indexed access. type Z = C["z"]; + ~~~~~~ +!!! error TS4129: Private or protected member 'z' cannot be accessed via indexed access. let x: X = c["x"]; let y: Y = c["y"]; let z: Z = c["z"]; diff --git a/tests/baselines/reference/unionPropertyOfProtectedAndIntersectionProperty.errors.txt b/tests/baselines/reference/unionPropertyOfProtectedAndIntersectionProperty.errors.txt index c40dbd3cc453c..b60f8e3efe0e3 100644 --- a/tests/baselines/reference/unionPropertyOfProtectedAndIntersectionProperty.errors.txt +++ b/tests/baselines/reference/unionPropertyOfProtectedAndIntersectionProperty.errors.txt @@ -1,7 +1,10 @@ +unionPropertyOfProtectedAndIntersectionProperty.ts(18,11): error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. +unionPropertyOfProtectedAndIntersectionProperty.ts(19,11): error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. unionPropertyOfProtectedAndIntersectionProperty.ts(19,23): error TS2339: Property 'foo' does not exist on type 'Foo | Bar'. +unionPropertyOfProtectedAndIntersectionProperty.ts(20,11): error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. -==== unionPropertyOfProtectedAndIntersectionProperty.ts (1 errors) ==== +==== unionPropertyOfProtectedAndIntersectionProperty.ts (4 errors) ==== class Foo { protected foo = 0; } @@ -20,10 +23,16 @@ unionPropertyOfProtectedAndIntersectionProperty.ts(19,23): error TS2339: Propert // that shows the direct result of the change: type _3 = (Foo & Bar)['foo']; // Ok + ~~~~~~~~~~~~~~~~~~ +!!! error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. type _4 = (Foo | Bar)['foo']; // Error + ~~~~~~~~~~~~~~~~~~ +!!! error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. ~~~~~ !!! error TS2339: Property 'foo' does not exist on type 'Foo | Bar'. type _5 = (Foo | (Foo & Bar))['foo']; // Prev error, now ok + ~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. // V[P] in `Nothing` is the substitution type `V[P] & Foo`. When // checking if that's assignable to `Foo` in the constraint of `Nothing`, diff --git a/tests/cases/compiler/indexedAccessOfConcreteNonpublicFields.ts b/tests/cases/compiler/indexedAccessOfConcreteNonpublicFields.ts new file mode 100644 index 0000000000000..f06fb2c04f510 --- /dev/null +++ b/tests/cases/compiler/indexedAccessOfConcreteNonpublicFields.ts @@ -0,0 +1,7 @@ +// @declaration: true +export class Foo { + private _property: string = ''; + protected _property2: string = ''; + constructor(arg: Foo['_property'], other: Foo['_property2']) { + } +} \ No newline at end of file From 805a8fe745bfca7aacc06be3f4d9596ba6f19521 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 26 Feb 2025 14:31:28 -0800 Subject: [PATCH 2/3] Format --- src/server/editorServices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 30a47b3b2ca63..03ab916cd4741 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -754,7 +754,7 @@ export interface DefaultConfiguredProjectResult { sentConfigDiag: Set; seenProjects: ConfigureProjectToLoadKind; seenConfigs: Set | undefined; -}; +} /** @internal */ export interface FindCreateOrLoadConfiguredProjectResult { From bac6092ce96c46a3512442a43fbc3114eb564c19 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 28 Feb 2025 11:49:22 -0800 Subject: [PATCH 3/3] Issue error only on privates when declaration emit is set --- src/compiler/checker.ts | 6 +++--- src/compiler/diagnosticMessages.json | 2 +- .../indexedAccessOfConcreteNonpublicFields.errors.txt | 9 +++------ .../reference/keyofAndIndexedAccess.errors.txt | 9 +++------ ...pertyOfProtectedAndIntersectionProperty.errors.txt | 11 +---------- 5 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c4afcb8605092..81466f7b829bb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -42070,10 +42070,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const indexType = getTypeFromTypeNode(accessNode.indexType); const objectType = getTypeFromTypeNode(accessNode.objectType); const propertyName = getPropertyNameFromIndex(indexType, accessNode); - if (propertyName) { + if (propertyName && getEmitDeclarations(compilerOptions)) { const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName)); - if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { - error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_via_indexed_access, unescapeLeadingUnderscores(propertyName)); + if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private) { + error(accessNode, Diagnostics.Private_member_0_cannot_be_accessed_via_indexed_access, unescapeLeadingUnderscores(propertyName)); return errorType; } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index bd651ad2478a1..a8926e5af4cab 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4436,7 +4436,7 @@ "category": "Error", "code": 4128 }, - "Private or protected member '{0}' cannot be accessed via indexed access.": { + "Private member '{0}' cannot be accessed via indexed access.": { "category": "Error", "code": 4129 }, diff --git a/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.errors.txt b/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.errors.txt index abdbbc0d7dc35..ae22871348ea8 100644 --- a/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.errors.txt +++ b/tests/baselines/reference/indexedAccessOfConcreteNonpublicFields.errors.txt @@ -1,15 +1,12 @@ -indexedAccessOfConcreteNonpublicFields.ts(4,22): error TS4129: Private or protected member '_property' cannot be accessed via indexed access. -indexedAccessOfConcreteNonpublicFields.ts(4,47): error TS4129: Private or protected member '_property2' cannot be accessed via indexed access. +indexedAccessOfConcreteNonpublicFields.ts(4,22): error TS4129: Private member '_property' cannot be accessed via indexed access. -==== indexedAccessOfConcreteNonpublicFields.ts (2 errors) ==== +==== indexedAccessOfConcreteNonpublicFields.ts (1 errors) ==== export class Foo { private _property: string = ''; protected _property2: string = ''; constructor(arg: Foo['_property'], other: Foo['_property2']) { ~~~~~~~~~~~~~~~~ -!!! error TS4129: Private or protected member '_property' cannot be accessed via indexed access. - ~~~~~~~~~~~~~~~~~ -!!! error TS4129: Private or protected member '_property2' cannot be accessed via indexed access. +!!! error TS4129: Private member '_property' cannot be accessed via indexed access. } } \ No newline at end of file diff --git a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt index 447fe5d54712a..7c5ac0e5858e2 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt @@ -1,5 +1,4 @@ -keyofAndIndexedAccess.ts(173,14): error TS4129: Private or protected member 'y' cannot be accessed via indexed access. -keyofAndIndexedAccess.ts(174,14): error TS4129: Private or protected member 'z' cannot be accessed via indexed access. +keyofAndIndexedAccess.ts(174,14): error TS4129: Private member 'z' cannot be accessed via indexed access. keyofAndIndexedAccess.ts(205,24): error TS2322: Type 'T[keyof T]' is not assignable to type 'object'. Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'object'. Type 'T[string]' is not assignable to type 'object'. @@ -17,7 +16,7 @@ keyofAndIndexedAccess.ts(318,5): error TS2322: Type 'T[K]' is not assignable to Type 'T[string]' is not assignable to type '{}'. -==== keyofAndIndexedAccess.ts (7 errors) ==== +==== keyofAndIndexedAccess.ts (6 errors) ==== class Shape { name: string; width: number; @@ -191,11 +190,9 @@ keyofAndIndexedAccess.ts(318,5): error TS2322: Type 'T[K]' is not assignable to function f40(c: C) { type X = C["x"]; type Y = C["y"]; - ~~~~~~ -!!! error TS4129: Private or protected member 'y' cannot be accessed via indexed access. type Z = C["z"]; ~~~~~~ -!!! error TS4129: Private or protected member 'z' cannot be accessed via indexed access. +!!! error TS4129: Private member 'z' cannot be accessed via indexed access. let x: X = c["x"]; let y: Y = c["y"]; let z: Z = c["z"]; diff --git a/tests/baselines/reference/unionPropertyOfProtectedAndIntersectionProperty.errors.txt b/tests/baselines/reference/unionPropertyOfProtectedAndIntersectionProperty.errors.txt index b60f8e3efe0e3..c40dbd3cc453c 100644 --- a/tests/baselines/reference/unionPropertyOfProtectedAndIntersectionProperty.errors.txt +++ b/tests/baselines/reference/unionPropertyOfProtectedAndIntersectionProperty.errors.txt @@ -1,10 +1,7 @@ -unionPropertyOfProtectedAndIntersectionProperty.ts(18,11): error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. -unionPropertyOfProtectedAndIntersectionProperty.ts(19,11): error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. unionPropertyOfProtectedAndIntersectionProperty.ts(19,23): error TS2339: Property 'foo' does not exist on type 'Foo | Bar'. -unionPropertyOfProtectedAndIntersectionProperty.ts(20,11): error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. -==== unionPropertyOfProtectedAndIntersectionProperty.ts (4 errors) ==== +==== unionPropertyOfProtectedAndIntersectionProperty.ts (1 errors) ==== class Foo { protected foo = 0; } @@ -23,16 +20,10 @@ unionPropertyOfProtectedAndIntersectionProperty.ts(20,11): error TS4129: Private // that shows the direct result of the change: type _3 = (Foo & Bar)['foo']; // Ok - ~~~~~~~~~~~~~~~~~~ -!!! error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. type _4 = (Foo | Bar)['foo']; // Error - ~~~~~~~~~~~~~~~~~~ -!!! error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. ~~~~~ !!! error TS2339: Property 'foo' does not exist on type 'Foo | Bar'. type _5 = (Foo | (Foo & Bar))['foo']; // Prev error, now ok - ~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS4129: Private or protected member 'foo' cannot be accessed via indexed access. // V[P] in `Nothing` is the substitution type `V[P] & Foo`. When // checking if that's assignable to `Foo` in the constraint of `Nothing`,