Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A way to hide a property from completions #47613

Open
5 tasks done
Andarist opened this issue Jan 26, 2022 · 13 comments · May be fixed by #60895
Open
5 tasks done

A way to hide a property from completions #47613

Andarist opened this issue Jan 26, 2022 · 13 comments · May be fixed by #60895
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@Andarist
Copy link
Contributor

Suggestion

🔍 Search Terms

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

hide, private, internal, completions

The closest issue found was this but while its title suggests that it may be related - its content seems to be entirely different.

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

It would be cool if I could annotate a member property as being "hidden" and effectively hide it from the completions list.

📃 Motivating Example

I've found out that extracting type params using a conditional type doesn't always behave the same way as getting that type from a property member.

When it comes to inference we might get different results from signatures resembling those ones:

type StateMachine<TContext> = {
  context: TContext;
  TContext: TContext;
}
// in a more complicated situation this might not infer the return type correctly
declare function useMachine<T extends StateMachine<any>>(machine: T): T extends StateMachine<infer C> ? State<C> : never
// but this might work just OK
declare function useMachine<T extends StateMachine<any>>(machine: T): State<T["TContext"]>

Obviously, my situation at hand is far more complicated. All type params are actually used, it's possible to extract the generic using a conditional type but when we call useMachine with the "concrete" machine then it fails to retrieve the embedded type using the first technique.

I've accidentally figured out that the latter improved the situation for us. I've seen this technique to be called in the past "phantom types". Obviously, though, we don't our users to access such a phantom property - it's merely used to help us out with the inference at the consuming call site. So it would be quite cool if we could just annotate those properties somehow so they could be removed from the completions list.

I'm pretty sure that this is such a generic feature that it could be useful to people in more contexts. For instance, in react-navigation we might find this beauty:
https://github.com/react-navigation/react-navigation/blob/90874397e653a6db642822bff18014a3e5980fed/packages/core/src/types.tsx#L156-L168

I've tested out all of the JSdoc tags~ that I could think of but none of them work like that:
TS playground

It seem to me though that @ptivate/@access private should actually work as I've found some comments~ in the TypeScript codebase that private properties should be excluded. I see how JSDoc annotation is different from a "real" private property but it would be great if this could "just work".

I'm not sure how feasible implementing this is but maybe I could take a crack at this - it seems that the required changes would be limited to isValidPropertyAccessForCompletions

💻 Use Cases

It got kinda covered in the previous section.

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Jan 26, 2022
@RyanCavanaugh
Copy link
Member

@deprecated seems pretty close?

@Andarist
Copy link
Contributor Author

Thank you for the suggestion - it's indeed "pretty close" and I didn't think about using this (didn't actually realize that this would put them on the bottom of the suggestions list).

I think there is still potential in the original request though as "pretty close" is not the same. For the time being, I will use the suggested workaround (PR) but I would love it if I could just hide those properties in the future altogether.

@Emiliano-Bucci
Copy link

Is there any news on this?

@cyan-2048
Copy link

need this

@RyanCavanaugh
Copy link
Member

need this for what?

@mtornwall
Copy link

mtornwall commented Mar 13, 2024

I came across a bootstrapping problem that would greatly benefit from having this functionality:

// Represents a type in my database schema. Needs to be fully functional before any initialization
// has taken place. Notably, the decorated properties are *not* accessible before initialization,
// so I need to provide a duplicate set of manually implemented property getters/setters.
// These need to be publicly accessible but should be hidden from the completions list.
@DbType()
class Type {
   // Name of the type - not available during initialization, but required for initialization.
  @DbProperty({ type: String })
  accessor name: String;

  // Bootstrapping property to use in place of `name` during initialization.
  // Needs to be public (because it is accessed outside of the class) but should be
  // hidden from the completions list.
  /** @internal @hidden ... @whatever? */
  get __name(): String {
    return this.manuallyRead("name");
  }
  set __name(value: String) {
    this.manuallyWrite("name", value);
  }
}

Regarding the use of @deprecated, I suppose it works in a pinch, but it doesn't accurately describe the status of the __name property above.

@fabian-hiller
Copy link

This would be great for Valibot. We have an internal ._run property that I want to hide (or move down) and a ._types property that just stores type information but is never actually assigned at runtime.

https://github.com/fabian-hiller/valibot/blob/v0.36.0/library/src/types/schema.ts#L50-L59

@snarbies
Copy link

For the use case in the original motivating example, I've been using symbols for this. I don't know if there are any downsides to this approach.

declare const phantom: unique symbol;
type Phantom = typeof phantom;

type Demo<T> = {
    [phantom]: T;
    value: T;
}
declare const demoValue: Demo<'test'>;
type DemoPhantom = (typeof demoValue)[Phantom];
  // ^?

(Playground)

@cyan-2048
Copy link

need this for what?

let's say I have a property that I know exists but should not be used somewhere else, it's like private but without the classes.

@snarbies
Copy link

let's say I have a property that I know exists but should not be used somewhere else, it's like private but without the classes.

You can expose the object as a type that does not include the property, or mark the property as deprecated, or use a convention like underscore prefix, or use a symbol key. Perhaps a more specific example?

@cyan-2048
Copy link

let's say I have a property that I know exists but should not be used somewhere else, it's like private but without the classes.

You can expose the object as a type that does not include the property, or mark the property as deprecated, or use a convention like underscore prefix, or use a symbol key. Perhaps a more specific example?

I have a function that recursively calls itself when it fails,
because it recursively calls itself, I don't want it to run some functions again and use the previous result,

downloadFile(opts: {
// this should be private
hash: number
}) {
const hash = opts.hash || createHash();

// fails
return downloadFile({ hash })

}

I should not be able to use hash when calling this function elsewhere, I guess symbols could work, deprecated is a bit ehh, idk why private shouldn't work for object types

@snarbies
Copy link

snarbies commented Aug 1, 2024

The example still seems incomplete. You have a function that accepts a parameter that has a property that you intend to use but you want that property to be undocumented or hidden. Do you want the hash property to be visible only to that function? How is the caller then able to know he is providing a valid argument?

Assuming that hash is meant to be a "private" property only visible to certain "friend" functions, typing the object with a public interface that omits the property and then using type assertions inside the friend function seems like the appropriate and idiomatic approach.

I'd be all for private in object literals, but I would expect private member access to be limited to the literal's lexical scope. That wouldn't satisfy this use case.

@snarbies
Copy link

snarbies commented Aug 1, 2024

I should not be able to use hash when calling this function elsewhere

Sorry, missed this bit. So your example makes sense, but the rest of my response applies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants