Repository: TypeScript
Issue: Suggestion: Provide better APIs to simplify converting CommentRanges into SynthesizedComments
Labels: Suggestion,In Discussion,API

**Scenario**

[Tsickle from the Angular project](https://github.com/angular/tsickle) 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.
```ts
/**
  * The test function
  */
function test(a: number): string { ...}
```
becomes
```js
/**
  * 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.
```ts
/**
 * For example,
 * @madeUpTag
 */
const c = 'c';
```
becomes
```ts
/**
 * 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:
```ts
interface Point {
  x: number;
  y: number;
}
```
becomes
```ts
/**
 * @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.CommentRange`s into `ts.SynthesizedComment`s 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](https://github.com/Microsoft/TypeScript/blob/master/src/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 `SynthesizedComment`s 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 `Block`s 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 `SynthesizedComment`s. 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`:
```ts
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