A way to allow certain other modules to access properties or methods of a class.
Sometimes, we want other modules (other code) to have access to certain properties or methods, but otherwise the end user not to have access to the properties/methods. This is labeled as "package protected" or similar terms in other languages.
This is how it could work, with some sort of new non-runtime syntax:
import SomeClass from './SomeClass'
export class Foo {
visible in SomeClass
private foo: number = 123
}
or
import SomeClass from './SomeClass'
export class Foo {
private foo visible in SomeClass: number = 123
}
or maybe even as a special decorator, globally-declared, virtual decorator
import SomeClass from './SomeClass'
export class Foo {
@visibleIn(SomeClass)
private foo: number = 123
}
SomeClass would then be able to access the private property/method:
import {Foo} from './Foo'
export class SomeClass {
doSomethingWithFoo(o: Foo) {
console.log(o.foo) // OK, no type error.
}
}
The code inside of SomeClass could be allowed to access the private properties. This is great at design time when the author controls the source for both sides, but still wants to hide private parts from the end user on the outside of the APIs.
This allows for patterns like object managers being able to control certain aspects of the objects they manage (for example a renderer in a game engine that manages how objects in a scene render, and the renderable properties that the engine manager accesses, which are private to the end user) are composed from reactions to changes in the objects' public APIs by end users).
At the moment, the only way to allow objects to access properties of other objects is to make them public, but this does not allow for us to hide (at least in the type system) certain implementation details of a cross-class or cross-module whole system from the end user.
My suggestion meets these guidelines:
__ to denote that the properties were not to be used by the end user. The engine accessed the properties for internal implementation of the system.Seems like a duplicate of https://github.com/microsoft/TypeScript/issues/5228 (C#-style internal), although your actual proposal looks semantically closer to C++鈥榮 friend.
Ah, thanks for linking me to that. Yeah, this is totally closer to C++ friend than C# internal or Java "package protected".
I think this is more of an alternative than a duplicate, because internal (like C#) is not so useful in a language without any concept of a unit of compilation built from more than one file. Basically what I described at https://github.com/microsoft/TypeScript/issues/5228#issuecomment-562822307.
Since we are listing similar features in other languages, there are also these two:
InternalsVisibleTo for sharing internal visibility between assemblies in .NET.
@package annotation in Closure Compiler JSDoc, which retracts visibility to a directory. One of the few things I miss from the pre-TypeScript days.
One problem with a declaration like @visibleIn(SomeClass) - what is SomeClass? This question is 100% clear in a language like C++ where all types are nominal, but TypeScript is structurally typed. So are we saying that all types shaped like SomeClass are friends, or all types named SomeClass are? Neither scenario is ideal IMO, and SomeClass only becomes a truly nominal type (the only ideal scenario) if it has private members.
One problem with a declaration like @visibleIn(SomeClass) - what is SomeClass?
In this case, SomeClass is in particular the exact class exported from the module where it is imported from. It isn't regarded as any sort of structural type, but merely tells the compiler that any code in SomeClass can access private members of instances of the current class. I guess it isn't about structural typing at all in this sense.
In some sense, it refers to SomeClass nominally, but usage of SomeClass isn't being type checked here. It would tell the engine "allow source from SomeClass class to access this here private or protected property on instances of this class".
Maybe it could be useful to think of friend visibility at _module_ level instead of classes. Modules are a native encapsulation concept in JavaScript, so it would probably feel more natural to reason on them, especially for the many folks that avoid classes in their programming style.
Yeah, totally. Maybe we can have both.
Because what if I have more than one class in a module, but I want the member visible only in a specific class? Or maybe even visible only in a specific method of a specific class.
Maybe we can come up with syntax for all those cases.
What about similar to above, but referring to a module:
export class Foo {
visible in import('./SomeClass')
private foo: number = 123
}
or
type Friend = typeof import('./SomeClass')
export class Foo {
visible in Friend
private foo: number = 123
}
or something?
Any other syntax ideas?
IMO it makes sense to have visible keyword as a replacement to export (in some cases of course) like so:
Foo.ts
namespace NS {
visible class Foo {}
}
PublicAPI.ts
namespace NS {
export class PublicAPI {
public constructor () {
const tmp = new NS.Foo();
}
}
}
index.ts
const API = new NS.PublicAPI();
new NS.Foo(); // Cannot find class Foo within NS namespace
Reference tags omitted intentionally.
So if something is visible then we can access it within same namespace(for ex.), if export then we can access it outside.
Of course, it's a bit off the topic, I understand.
Still I think it'd allow to have same kind of functionality and having relatively small changes to the syntax.
Most helpful comment
Maybe it could be useful to think of friend visibility at _module_ level instead of classes. Modules are a native encapsulation concept in JavaScript, so it would probably feel more natural to reason on them, especially for the many folks that avoid classes in their programming style.