microsoft / microsoft/TypeScript

Suggestion: Provide better APIs to simplify converting CommentRanges into SynthesizedComments

Open
#17,615 2 comments 0 reactions 1 assignee View on GitHub

@rbuckton is already working on this.

Since Aug 22, 2017.

Domain: API In Discussion Suggestion
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

Scenario

Tsickle from the Angular project is a TypeScript transformer that makes the emitted JavaScript compatible with Google Closure Compiler.

For this, it converts types into comments so that Closure Compiler can understand the types. E.g.

/**
  * The test function
  */
function test(a: number): string { ...}

becomes

/**
  * The test function
  * @param {number} a
  * @return {string}
  */
function test(a: number): string { ...}

Additionally, it escapes existing @... tags in jsdoc tags that are unknown to Closure Compiler. E.g.

/**
 * For example,
 * @madeUpTag
 */
const c = 'c';

becomes

/**
 * For example,
 * \@madeUpTag
 */
const c = 'c';

Finally, it also generates new code that also contains comments. E.g. it always adds a file prelude and converts interfaces into functions with closure type annotations:

interface Point {
  x: number;
  y: number;
}

becomes

/**
 * @fileoverview Generated by tsickle
 */

function Point() { }
function Point_tsickle_Closure_declarations() {
    /** @type {number} */
    Point.prototype.x;
    /** @type {number} */
    Point.prototype.y;
}

It follows, that Tsickle needs to be able to potentially modify every comment in the emitted JavaScript, and be able to synthetically create any kind of comment as well. We recently switched to using transformers, and went with the following approach to modify comments: Suppress the original comment via ts.EmitFlags.NoComment, convert the ts.CommentRanges into ts.SynthesizedComments which can be modified.

Problems with the current API
We made this work, but we had to duplicate quite a but of logic from compiler/comments.ts:

  • for a parent and a child node that start / end at the same offset (e.g. VariableStatement and the first VariableDeclaration), ts.getLeadingCommentRanges / ts.getTrailingCommentRanges returns the same comments for both, as it is only based on the node position. With the current API, we need to keep track of which comments we already processed so that we don't get duplications in the output.
  • getting the leading inline comments before a node does not work when using ts.getLeadingCommentRanges as it skips the characters until the first newline. With the current API, we force the function to read all leading comments by using pos = 0 and just passing in the trivia of the node. This gives us all comments, but we also need to keep track of which comments were already processed so that we don't duplicate the trailing comments from the previous node.
  • there is no API do detect the different kind of comments (pinned, detached, tripple slash), and the API for emitting SynthesizedComments only has regular comments that are always emitted. I.e. using the current API, we need to duplicate a lot of the logic from TypeScript.
  • there is no API for detecting detached leading / trailing comments (e.g. for Blocks and SourceFile). With the current API, we need to duplicate the logic for detecting detached comments, including the counting of newlines between comments. There is also no direct API for emitting detached SynthesizedComments. So far we are using a ts.NonEmittedStatement with ts.EmitFlags.CustomPrologue (the EmitFlag is to prevent additional newlines before the comment).
  • synthetic comments already add the wrapping comment characters (/* / */ / // ) depending on the CommentKind, so when converting a CommentRange into a SynthesizedComment we need to strip them manually.

Proposal Attempt
Add the following APIs:

  • ts.getLeadingCommentRanges(node: ts.Node): ts.CommentRange[]
    • similar to ts.getLeadingCommentRanges(text: string, pos: number)
    • would solve the problems of inline leading comments
    • would only return a comment for either the parent or child when they start at the same position.
    • would never return leading detached comments
  • ts.getTrailingCommentRanges(node: ts.Node): ts.CommentRange[]:
    • similar to ts.getTrailingCommentRanges(text: string, pos: number)
    • would only return a comment for either the parent or child when they end at the same position.
    • would never return trailing detached comments
  • ts.getLeading/TrailingDetachedCommentRanges(node: ts.Node): ts.CommentRange[]:
    • returns the leading / trailing detached comments for a ts.Block and ts.SourceFile
  • ts.setSyntheticLeading/TrailingDetachedComments(node: ts.Node, comments: ts.SynthesizedComment[]):
    • would set leading / trailing detached synthesized comments for a ts.Block and ts.SourceFile

Additionally, change ts.SynthesizedComment / ts.CommentRange:

type CommentKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia | SyntaxKind.TrippleSlashCommentTrivia;

interface ts.CommentRange {
  isPinned?: boolean;
}

interface ts.SynthesizedComment extends ts.CommentRange {
  ...
  containsTrivia?: boolean // whether the `text` already contains the trivia or not.
}

/cc @mprobst @evmar @alexeagle

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.