-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Proposal: The internal modifier for classes #5228
Comments
Would the following also error: declaration.d.ts class C {
internal x(): void {};
} source.ts /// <reference path="declaration.d.ts" />
class D extends C {
x(): void {}; // error?
} How does this differ from the more common language semantic The non-goal is not to address this in other declarations, but couldn't this start to becoming confusing for someone trying to utilise a package or library? For example, I would assume the following you would want to work this way: declaration.d.ts interface InterfaceC {
}
class C implements InterfaceC {
internal x(): void {};
} source.ts /// <reference path="declaration.d.ts" />
class D implements InterfaceC {
}
const c: C = new D(); // error, but why? I implemented the interface properly. When targeting ES6, do you see any changes, considerations in the emit? Do you have a more practical use case of where this pattern would resolve significant problems? I can understand how for a consumer it could clean up an API, but at the same time, it could be really confusing where you are "hiding" things that will always exist at runtime from compile time. It almost seems like a six of one, half a dozen of another. |
An alternative to modifiers is: package.d.ts
An |
+1 to something like this |
+1 for this. |
Internal modifier for classes and their properties/methods within a component/program would allow greater scope for Typescript identifier shortening. |
I think I'd want to extend It would work pretty much like the Additionally, though this may need some exploring, it could be used to keep classes within a module. So for example: //file1.ts
module A.B
{
internal class C { }
var c: C = new C(); //Works
}
//file2.ts
module A
{
var d: A.B.C = new A.B.C(); //Error because C is only available inside A.B module scope.
}
//file3.ts
module A.B
{
var e: A.B.C = new A.B.C(); //Works, back in scope
} |
I'm polishing classes' members' openness of RxJS like ReactiveX/rxjs#1244, then I wanted this feature to control openness in a complexed class dependencies. |
+1 Having to write @internal all over the docs is currently my biggest pain point with TS. Which is another way of saying that things are pretty good - good job guys :) |
+1 |
+1, this would be awesome to have. |
This would be really nice for Angular components where you need properties/methods to be public to be accessed by the template, but want them to be private to other users that are consuming your components. The current |
+1 This would really help with testing; right now you have to make methods public that you really just want to be visible for testing but not part of the public API. |
Agreed. This would make sense for services, where you can have internal methods that can only be accessed from other services if necessary, but not the API consumer. |
+1 internal modifier would be a big Christmas gift. |
We really need this feature too. I am a developer of a HTML5 engine called Egret. Our engine contains plenty of internal APIs which are not allowed to accessed by user, otherwise it may cause internal problems. Currently, we have to make them as |
excuse me, i know what a module or a namespace is, but what is a package? |
related #321 |
I have another thought here. From what I understand, an [edit] //Class will not be visible
/** @internal */
export class MyHelperClass {
}
export class MyPublicClass {
//Method will not be visible
/** @internal */
helperMethod() {}
}
// Binding will not be visible
/** @internal */
export const MyValue = 5; tsc --stripInternal |
@andykais If the project is distributed as d.ts then yes, this hides the properties but the properties are still visible in IntelliSense when working with .ts. If you use a common prefix such as "_" then it is sorted before your public members. I propose that if an |
@jgranick By protected do you mean that it will not show up in intellisense? My preference for
Another way I think of it is that it's a normal public property/method/etc in JavaScript. There's no runtime component to this. It's just that it's completely hidden by compiler/intellisense/tooling for any code outside of the project where it is defined. |
@EisenbergEffect I would be happy with your proposal if the concept of "inside" or "outside" the project is clear to the compiler. I was concerned this was a foreign concept? If there is not a way to distinguish cleanly between "inside" or "outside" the project then my proposal might be a second best:
|
I think it's been mentioned elsewhere in this thread, but I would add to @EisenbergEffect's description:
This is both a way to provide a level of runtime enforcement for internal types/functions/variables, and provides a runtime benefit (smaller names) with no downside. It's also part of the reason that I think |
Just my +1 for internal. I was surprised to find out that Typescript does not support this :( Another use case to consider, which is specifically mine ;), is having classes share a common abstract parent class. You may want to distribute/document their children, but neither the parent itself nor its methods/properties... |
That's simlar to "package protected" as called in other languages. What we need in JS and TS is "module protected" and "module private". Seems that #35554 would suit your needs more, and it is better than the |
+1 for internal. On our team, we use the jsDoc /** @internal */ comment quite a lot to designate internal functions, but there are some files that we exclude from the compilation used to generate the index.d.ts file, and in those files, we don't always consistently use the @internal comment (though we probably should.) If someone in the future then does something such that those files are included in the index.d.ts compilation, those functions would be exposed. An internal keyword with the characteristics described would be especially useful to better communicate intent, and to enforce it as best as Typescript can. |
The only time I need to access private methods from the outside would be within a unit test. As we separate the tests from the code how would the "internal" keyword cope with this ? |
@Xample Real private methods should not be tested directly, since they are implementation details. So basically you would use |
Please, see this comment. |
Since this seems to go nowhere anytime soon and I am currently in need for this, as I am maintaining a TypeScript Port of a C++ Project (Box2D), which uses C++ friend classes, I need an alternative way to strip away access from the outside. So I've written a small post-processing tool to help me do this by using recast to transform the (generated) .d.ts files after they've been built. This works by adding a jsdoc tag This is better than stripInternals, since the identifiers still remain in the d.ts files, so they will complain on extended classes, etc. Of course, you'll have the extra work of using jsdoc, but an I still need to add these jsdoc tags to my Box2D port, so I haven't tried this on a large scale project yet, but the sample snippets work just fine. Give it a try if you like: https://github.com/Lusito/idtsc Feel free to add feedback in the project's issue tracker. Check out the readme for a sample input/output Looking forward to a language feature, so I can discontinue this project. |
I like this concept of an I have a separate concern that I feel may have been conflated a bit in the overall discussion above and would like to disambiguate it here. While TDD'ing code, I will place my *.test.ts unit test files in the same directory as their systems under test, which seems to be a fairly common convention. I'd like to be able to mark some methods of my class with an access specifier other than The idea is that modules in the same directory can access the class member as if it was public, but it is private to modules in every other directory. I like the terminology JSDoc uses for this access specifier, Finally, this concept differs from #321 where the proposal is for a |
still greatly required, we have a multi project solution with TypeScript (using tsconfig project references) and currently we leak all the "should be internal" methods into the other projects, we cannot mark them as private as they are required to be used internally by the project that owns them. |
+1 for this. |
+1 for this as well. |
I think this would be very helpful +1 |
While working on a set of cooperating classes, I was looking for the equivalent of C++ |
The
I like this possibility too. |
I'd like to add that a huge shortcoming of using
The only way to protect from those issues today is to use a tool like api-extractor for checking that the API is consistent. Ideally, the new
On a related note, a pain point would be having to rewrite all those internal type aliases that we declare just to make our code more readable, but that we don't want to export from our files. Maybe TypeScript should automatically expand those internal type aliases into their definition. The interaction between |
One idea that would be even more explicit and not require any special file/directory handling, would be if I could mark a specific method/property as being allowed by specific classes, e.g.: class Bar {
run(foo: Foo) {
foo.run() // good
foo.run2() // type error
}
}
class Baz {
run(foo: Foo) {
foo.run() // type error
foo.run2() // type error
}
}
class Qux {
run(foo: Foo) {
foo.run() // good
foo.run2() // good
}
}
class Foo {
// Foo and Qux could be in the same file or imported from anywhere
internal(Bar, Qux) run() {
programming.solveOneOffError(n => n+1);
}
// different methods/properties of the same class could be internal to different things; they'd all be independent
internal(Qux) run2() {
this.jog();
}
} |
I like @sdegutis' idea, which would essentially be Some seem to think this proposal should be exclusive of the As originally proposed, Now, if your project is 100% yours, or is only worked on by you and other people who understand a unified vision, maybe it makes no difference to you. If that's you, you're very lucky, and I envy you. In reality, a big organization is going to have a lot of developers who have varying levels of understanding of good architecture, design patterns, etc. Some sleep with the Gang of Four book under their pillows. Others heard of design patterns once three years ago, and... that's it. Therefore, I allege that it's useful to have a mechanism for sharing access to certain things within a subset of a package, but not necessarily to the whole entire package. For example, suppose you have three layers:
Service has some methods useful both to itself and to Adapter, but which would be inappropriate to be called by Controller. Lacking Of course, sooner or later, someone comes along who doesn't understand this design, isn't inclined to learn, and is used to adding conditionals with additional code in the most convenient-looking places. Their behavior is dictated by an energy function which only cares about how many points they can burn down in this sprint. More points = more better! Whatever happens in three months or a year is irrelevant to this energy function. They tack on some code to Controller which calls methods on Service which were intended for use only by Adapter. This makes perfect sense to them. You know it's going to be harder to maintain this way, but they don't, and whoever reads their PRs won't care either. You can't read every PR organization-wide, and you may not be empowered to decline them anyway. Now a Controller that should be quite thin, and shouldn't be tightly coupled with implementation details at lower levels of abstraction, is hard-wired directly into that stuff. Possible solutions: Service "eats" Adapter. Adapter's properties and methods are all moved into Service. What would otherwise have been a concretion of Adapter is now a concretion of Service. This would mash two design patterns together in a way that isn't so great. The resulting class would be much less compliant with the Single Responsibility Principle. What might be just over the edge of "uncomfortably large" today may sprawl into a monstrous God Object by the time a few years have gone by. The methods/variables you wanted to "friend" could be refactored out to some other class. Service and Adapter would have to extend, implement, or compose with that class. You wouldn't need |
The
internal
modifierOften there is a need to share information on types within a program or package that should not be
accessed from outside of the program or package. While the
public
accessibility modifier allowstypes to share information, is insufficient for this case as consumers of the package have access
to the information. While the
private
accessibility modifier prevents consumers of the packagefrom accessing information from the type, it is insufficient for this case as types within the
package also cannot access the information. To satisfy this case, we propose the addition of the
internal
modifier to class members.Goals
This proposal aims to describe the static semantics of the
internal
modifier as it applies to members of a class (methods, accessors, and properties).Non-goals
This proposal does not cover any other use of the
internal
modifier on other declarations.Static Semantics
Visibility
Within a non-declaration file, a class member marked
internal
is treated as if it hadpublic
visibility for any property access:
source.ts:
When consuming a class from a declaration file, a class member marked
internal
is treated asif it had
private
visibility for any property access:declaration.d.ts
source.ts
Assignability
When checking assignability of types within a non-declaration file, a class member marked
internal
is treated as if it hadpublic
visibility:source.ts:
If one of the types is imported or referenced from a declaration file, but the other is defined
inside of a non-declaration file, a class member marked
internal
is treated as if it hadprivate
visibility:declaration.d.ts
source.ts
It is important to allow assignability between super- and subclasses from a declaration file
with overridden members marked
internal
. When both types are imported or referenced from adeclaration file, a class member marked
internal
is treated as if it hadprotected
visibility:declaration.d.ts
source.ts
However, this does not carry over to subclasses that are defined in a non-declaration file:
declaration.d.ts
source.ts
The text was updated successfully, but these errors were encountered: