microsoft / microsoft/TypeScript
Suggestion: Provide better APIs to simplify converting CommentRanges into SynthesizedComments
@rbuckton is already working on this.
Since Aug 22, 2017.
- 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.
VariableStatementand the firstVariableDeclaration),ts.getLeadingCommentRanges/ts.getTrailingCommentRangesreturns 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.getLeadingCommentRangesas it skips the characters until the first newline. With the current API, we force the function to read all leading comments by usingpos = 0and 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 andSourceFile). 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 detachedSynthesizedComments. So far we are using ats.NonEmittedStatementwithts.EmitFlags.CustomPrologue(theEmitFlagis to prevent additional newlines before the comment). - synthetic comments already add the wrapping comment characters (
/*/*////) depending on theCommentKind, so when converting aCommentRangeinto aSynthesizedCommentwe 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
- similar to
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
- similar to
ts.getLeading/TrailingDetachedCommentRanges(node: ts.Node): ts.CommentRange[]:- returns the leading / trailing detached comments for a
ts.Blockandts.SourceFile
- returns the leading / trailing detached comments for a
ts.setSyntheticLeading/TrailingDetachedComments(node: ts.Node, comments: ts.SynthesizedComment[]):- would set leading / trailing detached synthesized comments for a
ts.Blockandts.SourceFile
- would set leading / trailing detached synthesized comments for a
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.