tsdoc
tsdoc copied to clipboard
RFC: @throws tag for documenting exceptions
In https://github.com/microsoft/tsdoc/issues/8#issuecomment-510733311 @bookmoons said:
I vote for
@throws, or some other way to document exceptions. Really useful when coding against a procedure to know which kind of errors I should be handling.
I've opened this issue so we can have a more focused discussion.
(BTW this came up earlier in https://github.com/microsoft/tsdoc/issues/63#issuecomment-422601919. In that discussion, I was proposing that @throws should not be interpreted as an exhaustive list of all possible errors. Instead, it should merely be a way for a documentation author to talk about certain exceptions if they are very interesting to the API contract. If someone disagrees with that position, we could discuss it further.)
Whenever TSDoc refers to a type, it needs to be a rigorous syntax that is able to express any possible API type.
JSDoc uses something called name paths for this. Since JavaScript types are simple, their syntax is relatively simple.
The TSDoc equivalent is called declaration references. It's fairly different because TypeScript's type system is much more challenging to model. We need to distinguish stuff like function overloads, merged declarations, etc. We also support some weird stuff like ECMAScript symbols and quoted identifiers.
In TSDoc, your proposed format @throws MyError - my description has an ambiguity problem: The - character can be part of a declaration reference. For example, if core-library is an NPM package that exports a class MyError, then we want to write:
/**
* @throws core-library#MyError - my description
*/
export function doSomething(): void;
But is the type core, or is it core-library#MyError? A human can tell from context, but a parser cannot be sure. The grammar is ambiguous and would need some special way to distinguish them.
We could solve it by using { and } like JSDoc does:
/**
* @throws {core-library#MyError} a description of when it happens
*/
export function doSomething(): void;
But what if the NPM package is called @microsoft/core-library? Now we want to write:
/**
* @throws {@microsoft/core-library#MyError} a description of when it happens
*/
export function doSomething(): void;
But {@microsoft/core-library#MyError} looks a lot like an inline tag {@microsoft} whose content is /core-library#MyError. We would need some rule to disambiguate them.
If I remember right, JSDoc doesn't have this problem because they use a module: prefix to indicate a module:
/**
* @throws {module:@microsoft/core-library.MyError} a description of when it happens
*/
export function doSomething(): void;
TSDoc chose not to use : as a prefix operator because : pervasively appears in TypeScript code as a postfix operator. It would be fairly confusing to reinterpret it.
There's lots of creative ways to resolve ambiguities. The pros/cons tend to be very deep discussions. This case with @throws is interesting, as it's the first TSDoc block tag that ever needed to embed a declaration reference.
My main input would be to push our discussion towards a /general/ mechanism that can be used by custom tags as well. A major goal of TSDoc is to be extensible: If users invent custom tags and add them to their docs, this should not prevent other tools from being able to correctly parse the content. (For example, XML is perfectly extensible. Whereas Markdown is not extensible at all -- it's effectively impossible to write content that is portable between implementations.)
So I think we could handle this by introducing a general way to embed declaration references in arbitrary content. And then we would say that for @throws, the error type should simply appear at the start of the block. Then someone can invent a custom block tag like @isAFactoryFor or @canContain that refers to other types in the same way.
@rbuckton who recently put a lot of thought into declaration reference grammar.
Heh, I suppose a super simple option is like this:
/**
* @throws {@link @microsoft/core-library#MyError} a description of when it happens
*/
export function doSomething(): void;
Are you proposing an informal convention that would be proprietary to your tool? In other words, TSDoc should treat @throws as a generic rich text block, but specific implementations can choose to parse stuff out of there if they want to?
I think it makes sense to leave it as a generic text block. That is how it is currently handled in typedoc. Making it special has been requested in TypeStrong/typedoc#1045 but even that wouldn't require any special parsing (just a rendering change).
I created PR 175 to add support for this tag.
I think this issue is closed with the merge of #175?
The RFCs will all get closed when we post the first draft of the spec document.