Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upFeature Request: `Documented` Utility Type #41165
Comments
|
Documentation can be bound to both types and properties: /**
* Type-bound docs.
*/
interface A {
/**
* Property-bound docs.
*/
prop: string;
}How would they be differentiated? |
|
We could create a differently-documented interface AWithNewDocs extends Documented<A, "New type-bound docs."> {
prop: Documented<A["prop"], "New property-bound docs.">;
}Or create the type from scratch. type AWithDocs = Documented<{
prop: Documented<string, "Property-bound docs.">
}, "Type-bound docs."> |
|
It just occurred to me, this would be extremely useful for developers of type-level DSL parsers (which I'm guessing is the next wave of TS tooling thanks to ^4.1's string literal parsing). I've been experimenting with parsing GraphQL source string types into GraphQL AST types (and soon hopefully into TS types). For now, it's tough not to provoke the recursion limiter. A source string with more than a few statements gives TS error 2589. Soon, however, I'd hope it will be possible to parse and extract type information from large samples of many embedded languages. So how could we make use of GraphQL documentation ("block strings")? Ideally we could extract that documentation from the GraphQL source string type, and reuse it for js/tsdocs. Let's look at this hypothetical GraphQL-in-TS experience: import {Parse, CorrespondingTsType} from "type-level-graphql";
type SchemaAst = Parse<`
"""
Some `User` type description.
"""
type User {
"""
Some `User.name` field description.
"""
name: String!
"""
Some `User.favoriteLanguage` description.
"""
favoriteLanguage: String!
}
type Query {
users: [User!]!
}
schema {
query: Query
}
`>;
type Schema = CorrespondingTsType<SchemaAst>;
declare const schema: Schema;
schema.query.users.?[0]; // documentation is "Some `User` type description."
schema.query.users.?[0].name; // documentation is "Some `User.name` field description."
schema.query.users.?[0].favoriteLanguage; // documentation is "Some `User.favoriteLanguage` description."Here, we would want for the documentation in the GraphQL source string to flow through into A |
|
I might be mistaken, but comments are not actual values in JS/TS or any other programming language and therefore they cannot have a type in anyway, so the syntax you propose is impossible to implement. |
|
@micnic I'm not sure why having or not having a JS value complement would impose whether a syntax is possible. For instance, type params which act as type variables for reusability in others. type WithVars<
UserProvided,
SynthesizedA = SomeComputationA<UserProvided>, // <--
SynthesizedB = SomeComputationB<UserProvided>, // <--
SynthesizedC = SomeComputationC<SynthesizedA, SynthesizedB>
> = SynthesizedC;I'm curious to hear your thoughts on the following arguments for a Js/tsdoc comments are already treated (somewhat) as members of the language. They abide by a specific control flow so that users can re-alias and map between documented types with their documentation preserved. Another similarity with the language: documentation is not critical to the code's runtime execution and can be compiled away (just like static types). Documentation is a pillar of good software. Yet it's often difficult to give this process the time of day. This is––in part––because documentation cannot be easily modularized, composed, and constrained (not to mention, it involves configuring often-custom workflow tools, which spawn new processes). These challenges can be addressed well within the language. Existing documentation validation tools' flexibility pales in comparison to that of the type system. A |
|
A few days ago I submitted #41023, which has a similar intent. If this were to be implemented I would suggest a As a slight aside, I don't really care for the
|
|
@tjjfvi it does seem that we're requesting the same thing! Apologies for not finding your issue. Your proposed solution is certainly more readable!: Yours type AWithDocs = {
prop: string & Docs<"Property-bound docs.">;
} & Docs<"Type-bound docs.">;Vs. type AWithDocs = Documented<{
prop: Documented<string, "Property-bound docs.">
}, "Type-bound docs.">If we intersect with If we were to document some value... type A = string & Docs<"Documentation for A.">;And then utilize that type... const a: A = "hello"; // TS error 2322: Type 'string' is not assignable to type 'A'.Would the error––in the case of the There's also the matter of js/tsdoc -> types. /**
* Some description.
*/
type A = number;Does the signature of What I proposed is also flawed, as At this point, I have no strong preference for the experience. Just a belief that manipulating documentation within the type system could open some cool & useful doors. Hopefully we get some more feedback and ideas. |
|
In my mind, the
No; in my mind, both // In lib.d.ts, with magic special handling
type Docs<T> = unknown;
/**
* Some description.
*/
type A = number; // Hover `A`
type B = number & Docs<"Some description.">; // Hover `B`; with my proposal it should look identical to `A`.Originally, I thought that the the
IMO, the special handling makes sense for documentation; it isn't really type information, as While I think people may be resistant to bringing documentation information into the type system, I think it is essential with the complex types typescript is increasingly allowing us to create, as many of these complex type modification lose documentation, like I demonstrated in #41023. |
|
Yes!: it would be an intrinsic utility type, so I suppose there's no need to get too fixated on the "magic" as bad. One more question about your proposal: /**
* Some documentation for A.
*/
type A = {aField: string};
type AAndB = {bField: string} & A;
declare const aAndB: AAndB;In this case, While introducing an intrinsic utility makes sense... I'm not so sure that its usage should enforce a convention for applying the documentation data of If this is the desired experience, I'd hope that––in the above example–– /**
* Some documentation for A.
*/
type A = {aField: string};
/**
* Some documentation for B.
*/
type B = {bField: string};
type AAndB = A & B;
declare const aAndB: AAndB;This leads me to believe that you're proposing some new documentation control flow behavior in addition to the intrinsic utility type. Is this the case? |
|
No; rather I was suggesting a special casing wrt intersection types and the I agree that this inconsistency seems non-ideal, but I think it is reasonable:
This is the "magic" I was talking about in my earlier comments; similar to how |
|
See also rbuckton's recently submitted #41220 |
|
So, after digging around for a while in typescript's source, I have started to figure out how the documentation stuff gets handled. AFAICT, when it is retrieving the documentation, it:
// ...
function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] {
if (!declarations) return emptyArray;
let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations);
if (doc.length === 0 || declarations.some(hasJSDocInheritDocTag)) {
forEachUnique(declarations, declaration => {
const inheritedDocs = findInheritedJSDocComments(declaration, declaration.symbol.name, checker!); // TODO: GH#18217
// TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs
if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc);
});
}
return doc;
}
// ...I attempted to start inspecting what the |
Search Terms
Docs, documentation, re-key, utility, type, extract, literal, update, tsdoc, comment, source, text
Suggestion
I'd love to see documentation as a first-class citizen of the type system. My suggestion is a new utility type
Documented, which allows users to document and unwrap documentation at the type-level.The type could look as follows:
Documented<V, D extends string> = V.This:
... would be equivalent to this:
While it would be preferable for consistency to specify the documentation attached to the key (as this is how mapped types transfer documentation), index signature and other contextual restrictions apply. Aka., this is no good:
Additionally, accessing the symbol of the literal of a specific key can be arduous (
Extract<keyof ..., ...>). Whereas accessing the symbol of the field is simple (X["a"]).Use Cases
This utility type would enable users to extract documentation as string literal types, which they could then further manipulate and use elsewhere. They could build up higher-order generic types to assist with and type-check the documentation process.
Examples
In either of the cases above,
awill be documented as "USE WITH CAUTION: Some documentation fora".The real power of this utility is not in direct usage as demoed above, but rather in higher-order utilities. For instance, you could imagine a utility library that lets users specify flags that correspond to expected documentation.
Checklist
My suggestion meets these guidelines: