question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature Request: `Documented` Utility Type

See original GitHub issue

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:

type X = {
  a: Documented<string, "Some documentation for `a`">;
};

… would be equivalent to this:

type X = {
  /**
   * Some documentation for `a`
   */
  a: string;
};

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:

type X = {
  [Documented<"a", "Some documentation for `a`">]: string;
}

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"]).

ALSO: apologies if I’m butchering the terminology.

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

type X = {
  /**
   * Some documentation for `a`
   */
  a: string;
};

type XaDocs = X["a"] extends Documented<any, infer D> ? D : undefined;
declare const xaDocs: XaDocs; // "Some documentation for `a`"

// update (prefix) the string literal type of the documentation
type XaDocsUpdated = XaDocs extends string ? `USE WITH CAUTION: ${XaDocs}` : undefined;

// utilize the `Documented` utility to attach the new documentation
type XUpdated1 = {
  a: Documented<string, XaDocsUpdated>;
};

// perhaps within a mapped type
type XUpdated2 = {
  [K in keyof X]: K extends "a" ? Documented<X[K], XaDocsUpdated> : X[K];
};

In either of the cases above, a will be documented as “USE WITH CAUTION: Some documentation for a”.

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.

import {EnsureDocumentationExists} from "some-doc-utility-lib";
import {HasDescriptionAndExampleDocs, HasDescriptionDocs} from "./my-own-potentially-poorly-documented-types";

export type A = EnsureDocumentationExists<HasDescriptionAndExampleDocs, {description: true; example: true}>;
export type B = EnsureDocumentationExists<HasDescriptionDocs, {description: true; example: true}>; // type-error
export type C = EnsureDocumentationExists<HasDescriptionDocs, {description: true}>;

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, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:28
  • Comments:18 (9 by maintainers)

github_iconTop GitHub Comments

3reactions
tjjfvicommented, Oct 25, 2020

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:

  • Starts at getQuickInfoAtPosition in src/services/services.ts
  • Goes through a very roundabout process
  • Reaches one of four places: (Note that these functions could have different implementations, as they are checked based on e.g. Symbol and not SymbolObject, but I didn’t see any instance of this in the source)
    • return []
    • SignatureObject.getDocumentationComment
      • Essentially becomes getDocumentationComment([this.declaration], this.checker)
    • SymbolObject.getDocumentationComment
      • This has some special handling in the case of a tuple label, which I am going to ignore for right now, as it is a bit of an edge case
      • Returns getDocumentationComment(this.declarations, checker)
    • SymbolObject.getContextualDocumentationComment
      • Essentially boils down to the above, but with some slight special handling in the case of get/set stuff (I think).
  • And then all paths seem to reach one glorious function: getDocumentationComment
// ...
    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 getDocumentationComment function was being passed, but have not yet been able to get it working.

2reactions
tjjfvicommented, Oct 23, 2020

No; rather I was suggesting a special casing wrt intersection types and the Docs<T>/DocsOf<T>, so that A & Docs<"blah"> would transfer the documentation, while B & AWithDocs would not.

I agree that this inconsistency seems non-ideal, but I think it is reasonable:

  • For the clarity of the syntax; I think A & Docs<"test"> reads cleaner than Documented<A, "test">
  • Otherwise, it would be changing existing behavior wrt intersection and documentation
  • As you pointed out, merging two docs in an intersection would be problematic.

This is the “magic” I was talking about in my earlier comments; similar to how ThisType is handled specially.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Feature Request Template: How to Manage Suggestions at ...
Streamline and organize user feedback with this free feature request template. Available in Google Docs and Sheets (no email required).
Read more >
Feature Request Template: How to Collect User ... - Sleekplan
A feature request template is a document that provides guidance for your users on how to provide feature requests, what information you really ......
Read more >
Documentation - Utility Types - TypeScript
Constructs an object type whose property keys are Keys and whose property values are Type . This utility can be used to map...
Read more >
3 ways to manage software feature request - Amoeboids
Select the type of project, and give it a name –This will create a Board, which will be the place to view and...
Read more >
How To Manage Feature Requests [Template included]
Let's start by looking at what types of feature requests you might ... And the best way to structure it is to use...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found