Skip to content

Triple slash support (C# style) #160

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

Open
MikeMitterer opened this issue Apr 23, 2019 · 15 comments
Open

Triple slash support (C# style) #160

MikeMitterer opened this issue Apr 23, 2019 · 15 comments

Comments

@MikeMitterer
Copy link

Please support triple slashes
Triple slash documentation is much cleaner in my opinion.
E.g. Dart also supports it: https://www.dartlang.org/guides/language/effective-dart/documentation#do-use--doc-comments-to-document-members-and-types

@octogonz
Copy link
Collaborator

octogonz commented Apr 23, 2019

I agree that /// is a better design, if we were starting from a clean slate. For example, /// comments can easily be embedded inside other /// comments without any escaping, but */ is rather awkard to handle inside a JSDoc comment.

Unfortunately there are a couple problems with such a change:

  • TSDoc aims for "one obvious way" of doing things, versus lots of different equivalent syntaxes for the same thing. This is my personal philosophy as an engineer. But there is also a technical justification, since TSDoc grammar aims to be formalized, unambiguous, and easy to write interoperable parsers for, so every extra grammar rule is expensive to add. (Here I mean "standard" rules, not stuff supported in "lax mode")

  • TSDoc aims to be familiar and align with practices already in use today (which is mostly inspired by JSDoc). I've never seen an NPM package that uses /// for its API docs -- but maybe I'm not looking hard enough?

From a non-standard perspective, it would be reasonable to request for the parser library API to accept the raw lines as its input. In the current implementation we remove the /* framing during parsing, so the error line/col will be correct.

@ghost
Copy link

ghost commented Feb 21, 2020

Coming from Rust, the triple-slash syntax is used everywhere and I've come to appreciate it a lot more than the usual block-style comments. I think that there's a bit of a difference between allowing line and block comments and adding multiple formats for the documentation itself.

I absolutely agree that the actual @tags should have one syntax. It would be ridiculous to allow aliases like @arg and @param for example, and having just one makes sense. But forcing all of the comments to be block comments feels wrong to me.

There's a reason why languages offer both block and line comments and I think that it doesn't really split the ecosystem to allow both. Consistency within a project can easily be established through linting, and I don't think it requires many mental hurdles to jump through to understand.

@octogonz
Copy link
Collaborator

Coming from Rust, the triple-slash syntax is used everywhere and I've come to appreciate it a lot more than the usual block-style comments.

Is there a specific benefit, though, beyond looking more like Rust source code? There are nontrivial costs:

  • Everyone who writes a comment parser needs to implement this alternate syntax, even though it is very rarely used.

  • The standard will need to specify handling of edge cases (what if some of the lines start with // or //// instead of ///?)

  • When people copy code or move source files between projects created by different "comment tribes," they would have to fix up comments. Or else a tool like Prettier would need to add a feature to automatically normalize /// back to the more common /**.

We can do this. They are certainly easier problems than other work we've already done. But what do we get from this investment?

@ghost
Copy link

ghost commented Feb 24, 2020

It's nice because:

  1. Many documentation lines are only one line long.
  2. Most people modify every line of a block comment to start with * anyway.
  3. For single-line comments, you don't need to worry about a */ at the end.
  4. For multi-line comments, you don't waste an extra line for /** and */.

Also, the case you mentioned: what if someone types /* or /***? It's the same deal; you count it as a doc comment if it has three or more slashes and not otherwise. "Very rarely used" would need backing data and I have a feeling that it would be a welcome change.

Is it worth it? I can't say. If I had more time to spend on this it'd make sense to run a community poll. But I figured I'd add my comments on this because the issue was open and there wasn't that much detail here. :)

@octogonz
Copy link
Collaborator

I realize that this proposal would also require separate fixes for each tool that supports TSDoc, since the TSDoc library itself does not find comments within a source file. That job is always handled by the caller (because they do it while parsing API signatures):

For example, our API demo does it like this (using the TypeScript compiler API):

// True if the comment starts with '/**' but not if it is '/**/'
return commentRanges.filter((comment) =>
text.charCodeAt(comment.pos + 1) === 0x2A /* ts.CharacterCodes.asterisk */ &&
text.charCodeAt(comment.pos + 2) === 0x2A /* ts.CharacterCodes.asterisk */ &&
text.charCodeAt(comment.pos + 3) !== 0x2F /* ts.CharacterCodes.slash */);
}

Whereas the TSDoc Playground does it like this (using the Monaco editor control):

const textRange: TextRange = TextRange.fromStringRange(sourceCode.text, comment.range[0], comment.range[1]);
// Smallest comment is "/***/"
if (textRange.length < 5) {
continue;
}
// Make sure it starts with "/**"
if (textRange.buffer[textRange.pos + 2] !== '*') {
continue;
}

The TypeScript compiler itself provides a bunch of APIs with names like getJSDocType(), getJSDocTags(), etc that are used for refactoring and IntelliSense.

All these parsers would need to be updated to recognize /// as JSDoc. And there will be slight grammar differences, for example this is allowed:

/// ```
/// /* code snippet */
/// ```

Wheres in TSDoc it needs to get escaped like:

/**
 * ```
 * /* code snippet *
 *+/
 * ```
 */

I personally agree that /// is a better comment syntax than /**. If I were a benevolent dictator, I would deprecate /** and force everyone to adopt ///. 😋 But the JavaScript community has already mostly converged on /**. It seems unwise to go against that. And replacing one standard with "two standards" brings its own costs.

Is it worth it? I can't say. If I had more time to spend on this it'd make sense to run a community poll.

We could ask automobile owners: "How do you feel about a switch that lets you move your steering wheel to the left side OR the right side? The same car can drive in any country!"

People might vote yes. It has some marginal value, and nobody's obligated to use it.

But we should also poll the car manufacturers to see if they are willing to build it. 🙂

@orta
Copy link

orta commented Aug 7, 2020

We've been thinking a bit about this on the TS side too: microsoft/TypeScript#39930

@ohnv
Copy link

ohnv commented Nov 30, 2020

Strong +1 for '///' style comments.

I love commenting stuff. But I also like the code to be reasonably compact. For me, sure, a single /// comment and a 1-line /** comment */ comment is the same. But once you move the two lines...

/// Triple-slash comments on two lines are
/// still pretty compact.
public someProperty: string;
/**
* These comments though suddenly baloon to 4 lines.
* Eeep! Save me
*/
public someProperty: string;

Pedantic, but why would I be a programmer if I wasn't 😎

@octogonz
Copy link
Collaborator

octogonz commented Dec 2, 2020

The "it's slightly less keystrokes!" argument didn't seem very compelling.

But there is a particularly thorny problem of /** */ that is neatly solved by /// comments: code excerpts.

Consider this example:

/**
 * Strips the comments from an input string:
 *
 * @example
 * ```ts
 * // prints " some text":
 * console.log(stripComments("/* a comment */ some text");
 * ```
 */
function stripComments(code: string): string;

You will find that this is a syntax error because */ cannot be embedded inside another comment. After a very long deliberation, TSDoc RFC 166 settled on a special + notation that unwraps the following line:

/**
 * Strips the comments from an input string:
 * @param code - the input string
 *
 * @example
 * ```ts
 * // prints " some text":
 * console.log(stripComments("/* a comment *
 *+/ some text");
 * ```
 */
function stripComments(code: string): string;

It is a clunky solution, but I've become convinced it's the only complete solution to this problem.

Whereas nesting /// comments is trivially easy:

/// Strips the comments from an input string:
/// @param code - the input string
///
/// @example
/// ```ts
/// // prints " some text":
/// console.log(stripComments("/* a comment */ some text");
/// ```
function stripComments(code: string): string;

However moving from /// to /** seems like a big enough deviation from JSDoc that it might remove the expectation to align with legacy JavaScript comments. So if we went this way, we might also consider bundling it with more radical changes:

  1. Making /// the preferred TSDoc syntax, and treating /** */ as a deprecated notation
  2. Relaxing the goal for TSDoc to align with JSDoc, and eliminating baggage like {@link} in favor of Markdown links
  3. Even replacing Markdown with XML markup like what C# used. XML is more verbose, but it has the significant advantages of (1) having a well-specified grammar with sane escaping mechanisms and (2) providing a baked-in guarantee that custom tags will never break other tool's ability to parse the comment -- the major hangup of Markdown.

As a conversation piece, this is how Sandcastle would represent the above docs:

/// <summary>Strips the comments from an input string:</summary>
/// <param name="code">the input string</param>
///
/// <example>
/// <code lang="ts">
/// // prints " some text":
/// console.log(stripComments("/* a comment */ some text");
/// </code>
/// </example>
function stripComments(code: string): string;

It's verbose. But having personally written a lot of Sandcastle docs back in the day, it does have the upside that you never have to guess how your markup will get rendered on the website. And it's super easy to correctly parse a comment containing unsupported markup.

But... are any TypeScript devleopers not horrified by this prospect? 😁

@buschtoens
Copy link

A further advantage of /// is better Tab indentation support. When using /** */, the horizontal (column) offset introduced by the leading  *  is uneven (3 chars), where as ///  is even (4 chars).

This makes authoring multi-line code blocks, e.g. for @example, extremely annoying.

I haven't yet found any setting / extension that fixes this indentation offset in VS Code.

/**
 * The back ticks of the following code fence start at column 3.
 * This means that the first level of indentation starts at column 5.
 *
 * Hitting the `Tab` key always inserts even indentation and thus indents
 * incorrectly.
 * 
 * ```ts
 * const foo = {
 *   expected: true,         // ✅ Col 5: Manual correct indentation by two spaces.
 *  tabPressedOnce: true,    // ❌ Col 4: 1 x `Tab` indents by one space.
 *    tabPressedTwice: true, // ❌ Col 6: 2 x `Tab` indents by three spaces.
 * };
 * ```
 */

image

@buschtoens
Copy link

It seems that prettier-plugin-jsdoc adds support for auto-formatting code fences. So while Tab is still broken, at least saving a file will fix the code block, if auto-format is enabled.

// .prettierrc
{
  "plugins": ["prettier-plugin-jsdoc"],
  "tsdoc": true
}

@nex3
Copy link

nex3 commented Oct 7, 2021

  • I've never seen an NPM package that uses /// for its API docs -- but maybe I'm not looking hard enough?

This may be a chicken-and-egg issue where the cost of using them is high enough (in terms of losing access to doc generation and IDE support) that even packages that would normally choose to do so can't. I'll say that I would certainly use them for Sass's TypeScript documentation if I could, and I've even looked around to see if there are packages I could use to compile ///-style comments to /**-style as part of our release process.

I understand the perspective that ergonomics alone aren't necessarily a reason to add a totally separate way of writing comments, but I think nested comments push the case for /// over the edge. RFC 166 only does so much to mitigate the issue, and at a cost—it makes the docs very hard to read in the source, and it's entirely non-discoverable. I would never have known about it had I not re-read this issue several times (and I still can't use it because TypeDoc doesn't support it).

@breyed
Copy link

breyed commented Feb 6, 2022

TSDoc aims for "one obvious way" of doing things

Even with a transition to ///-style, TSDoc would still have this feature. It will be the new and improved one obvious way. That's the power of deprecation. The detail that /** still works needn't dilute the guidance for using TSDoc. And unforeseen consequences of an age-old decision needn't hamper the tool forever.

TSDoc aims to be familiar and align with practices already in use today

The industry has moved on. Rust, Dart, Go, Swift, C#, and F# all use slashes. /** harkens back to the 1995 introduction of Java, ultimately influencing JavaScript and JSDoc. Enough developers now work in multiple languages that the modern style has become the familiar style.

@jimrandomh
Copy link

IMO not supporting /// is analogous to only supporting spaces for indentation and not tabs. I have my own opinions about that (mostly coming from habits in other languages) and don't want my tools to fight me about it.

@Keavon
Copy link

Keavon commented Feb 5, 2024

Just for a little perspective on my particular workflow: because the /** ... */ syntax is so ugly (I particularly hate the wasted lines and leading alignment whitespace) and cumbersome to write, I strictly don't use it. Since I write TypeScript, I don't really find a use for JSDoc @blah syntax because my function signatures already have explicit types. I'm also used to Rust and how it elegantly handles all of this.

So I often comment my functions, but I only use // comments which means VS Code doesn't give me documentation on hover for functions. This is annoying, but preferrable to seeing the horribly ugly /** ... */ in my code. For the record, I'm not writing libraries consumed by others where I auto-generate documentation, but having a nicer VS Code experience would be an improvement. I'd be satisfied with even the basic case where VS Code simply displayed /// comments above a function when hovering over a reference to that function, even if it didn't parse full-on JSDoc syntax.

The discussion about why we shouldn't have this seems a lot like the classic "well it wouldn't be perfect, so it's not worth doing at all" fallacy. And with ever-growing market share for modern languages like Rust, C#, Dart, Swift, Go, etc. that all use the elegance of ///, TS should really aim to follow along here. TypeScript actually does a reasonably good job at following the syntax of these modern languages, like with its identifier: Type instead of Type identifier ordering syntax.

@nnmrts
Copy link

nnmrts commented Aug 22, 2024

I came from #166 and similar issues and also agree that supporting /// is definitely the way to go, it's the only way to solve the problem of using JSDoc/TSDoc inside an @example, AFAIK.

But there's a slight issue with deprecating or even not supporting the /** ... */ syntax. The only way to write an as const in JavaScript is like so:

const readonlyTuple = /** @type {const} */ ([1,2,3]);

For longer tuples with line breaks between items you can even do this:

const readonlyTuple = /** @type {const} */ (
	[
		1,
		2,
		3
	]
);

This could be written with /// like this:

const readonlyTuple = /// @type {const}
([1,2,3]);
const readonlyTuple = /// @type {const}
(
	[
		1,
		2,
		3
	]
);

but of course because // comments out a full line, we need to add a line break. Which still "works", I guess, but looks ugly.

So my proposal is to think about this little issue if support for /// ever gets implemented and to support this additional way of writing as const in JS:

const readonlyTuple = ([1,2,3]); /// @type {const}
const readonlyTuple = ([
	1,
	2,
	3
]); /// @type {const}

The above is much closer to the "real" as const anyway.

I also realize this is mostly about JS and JSDoc and not TS and TSDoc, but I think such support for /// would anyway encompass all types of code that is parsed by and hoverable with IntelliSense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants