Skip to content

Commit 3a8e898

Browse files
committed
Add support for multiple comment styles
Resolves #1433.
1 parent 683de98 commit 3a8e898

File tree

15 files changed

+899
-53
lines changed

15 files changed

+899
-53
lines changed

src/lib/converter/comments/discovery.ts

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as ts from "typescript";
22
import { ReflectionKind } from "../../models";
3-
import type { Logger } from "../../utils";
3+
import { assertNever, Logger } from "../../utils";
4+
import { CommentStyle } from "../../utils/options/declaration";
45
import { nicePath } from "../../utils/paths";
56

67
// Note: This does NOT include JSDoc syntax kinds. This is important!
@@ -82,13 +83,14 @@ const wantedKinds: Record<ReflectionKind, ts.SyntaxKind[]> = {
8283
export function discoverComment(
8384
symbol: ts.Symbol,
8485
kind: ReflectionKind,
85-
logger: Logger
86-
): [ts.SourceFile, ts.CommentRange] | undefined {
86+
logger: Logger,
87+
commentStyle: CommentStyle
88+
): [ts.SourceFile, ts.CommentRange[]] | undefined {
8789
// For a module comment, we want the first one defined in the file,
8890
// not the last one, since that will apply to the import or declaration.
8991
const reverse = symbol.declarations?.some(ts.isSourceFile);
9092

91-
const discovered: [ts.SourceFile, ts.CommentRange][] = [];
93+
const discovered: [ts.SourceFile, ts.CommentRange[]][] = [];
9294

9395
for (const decl of symbol.declarations || []) {
9496
const text = decl.getSourceFile().text;
@@ -111,21 +113,20 @@ export function discoverComment(
111113
continue;
112114
}
113115

114-
const comments = ts.getLeadingCommentRanges(text, node.pos);
116+
const comments = collectCommentRanges(
117+
ts.getLeadingCommentRanges(text, node.pos)
118+
);
115119

116120
if (reverse) {
117121
comments?.reverse();
118122
}
119123

120-
const lastDocComment = comments?.find(
121-
(c) =>
122-
text[c.pos] === "/" &&
123-
text[c.pos + 1] === "*" &&
124-
text[c.pos + 2] === "*"
124+
const selectedDocComment = comments.find((ranges) =>
125+
permittedRange(text, ranges, commentStyle)
125126
);
126127

127-
if (lastDocComment) {
128-
discovered.push([decl.getSourceFile(), lastDocComment]);
128+
if (selectedDocComment) {
129+
discovered.push([decl.getSourceFile(), selectedDocComment]);
129130
}
130131
}
131132
}
@@ -139,7 +140,7 @@ export function discoverComment(
139140
logger.warn(
140141
`${symbol.name} has multiple declarations with a comment. An arbitrary comment will be used.`
141142
);
142-
const locations = discovered.map(([sf, { pos }]) => {
143+
const locations = discovered.map(([sf, [{ pos }]]) => {
143144
const path = nicePath(sf.fileName);
144145
const line = ts.getLineAndCharacterOfPosition(sf, pos).line + 1;
145146
return `${path}:${line}`;
@@ -155,23 +156,23 @@ export function discoverComment(
155156
}
156157

157158
export function discoverSignatureComment(
158-
declaration: ts.SignatureDeclaration | ts.JSDocSignature
159-
): [ts.SourceFile, ts.CommentRange] | undefined {
159+
declaration: ts.SignatureDeclaration | ts.JSDocSignature,
160+
commentStyle: CommentStyle
161+
): [ts.SourceFile, ts.CommentRange[]] | undefined {
160162
const node = declarationToCommentNode(declaration);
161163
if (!node) {
162164
return;
163165
}
164166

165167
const text = node.getSourceFile().text;
166-
const comments = ts.getLeadingCommentRanges(text, node.pos);
167168

168-
const comment = comments?.find(
169-
(c) =>
170-
text[c.pos] === "/" &&
171-
text[c.pos + 1] === "*" &&
172-
text[c.pos + 2] === "*"
169+
const comments = collectCommentRanges(
170+
ts.getLeadingCommentRanges(text, node.pos)
173171
);
174172

173+
const comment = comments.find((ranges) =>
174+
permittedRange(text, ranges, commentStyle)
175+
);
175176
if (comment) {
176177
return [node.getSourceFile(), comment];
177178
}
@@ -181,7 +182,7 @@ export function discoverSignatureComment(
181182
* Check whether the given module declaration is the topmost.
182183
*
183184
* This function returns TRUE if there is no trailing module defined, in
184-
* the following example this would be the case only for module <code>C</code>.
185+
* the following example this would be the case only for module `C`.
185186
*
186187
* ```
187188
* module A.B.C { }
@@ -198,7 +199,7 @@ function isTopmostModuleDeclaration(node: ts.ModuleDeclaration): boolean {
198199
* Return the root module declaration of the given module declaration.
199200
*
200201
* In the following example this function would always return module
201-
* <code>A</code> no matter which of the modules was passed in.
202+
* `A` no matter which of the modules was passed in.
202203
*
203204
* ```
204205
* module A.B.C { }
@@ -251,3 +252,62 @@ function declarationToCommentNode(node: ts.Declaration): ts.Node | undefined {
251252

252253
return node;
253254
}
255+
256+
/**
257+
* Separate comment ranges into arrays so that multiple line comments are kept together
258+
* and each block comment is left on its own.
259+
*/
260+
function collectCommentRanges(
261+
ranges: ts.CommentRange[] | undefined
262+
): ts.CommentRange[][] {
263+
const result: ts.CommentRange[][] = [];
264+
265+
let collect: ts.CommentRange[] = [];
266+
for (const range of ranges || []) {
267+
collect.push(range);
268+
269+
switch (range.kind) {
270+
case ts.SyntaxKind.MultiLineCommentTrivia:
271+
if (collect.length) {
272+
result.push(collect);
273+
collect = [];
274+
}
275+
result.push([range]);
276+
break;
277+
case ts.SyntaxKind.SingleLineCommentTrivia:
278+
collect.push(range);
279+
break;
280+
/* istanbul ignore next */
281+
default:
282+
assertNever(range.kind);
283+
}
284+
}
285+
286+
if (collect.length) {
287+
result.push(collect);
288+
}
289+
290+
return result;
291+
}
292+
293+
function permittedRange(
294+
text: string,
295+
ranges: ts.CommentRange[],
296+
commentStyle: CommentStyle
297+
): boolean {
298+
switch (commentStyle) {
299+
case CommentStyle.All:
300+
return true;
301+
case CommentStyle.Block:
302+
return ranges[0].kind === ts.SyntaxKind.MultiLineCommentTrivia;
303+
case CommentStyle.Line:
304+
return ranges[0].kind === ts.SyntaxKind.SingleLineCommentTrivia;
305+
case CommentStyle.JSDoc:
306+
return (
307+
ranges[0].kind === ts.SyntaxKind.MultiLineCommentTrivia &&
308+
text[ranges[0].pos] === "/" &&
309+
text[ranges[0].pos + 1] === "*" &&
310+
text[ranges[0].pos + 2] === "*"
311+
);
312+
}
313+
}

src/lib/converter/comments/index.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as ts from "typescript";
22
import { Comment, ReflectionKind } from "../../models";
33
import { assertNever, Logger } from "../../utils";
4+
import type { CommentStyle } from "../../utils/options/declaration";
45
import { nicePath } from "../../utils/paths";
56
import { lexBlockComment } from "./blockLexer";
67
import { discoverComment, discoverSignatureComment } from "./discovery";
8+
import { lexLineComments } from "./lineLexer";
79
import { parseComment } from "./parser";
810

911
export interface CommentParserConfig {
@@ -15,40 +17,45 @@ export interface CommentParserConfig {
1517
const commentCache = new WeakMap<ts.SourceFile, Map<number, Comment>>();
1618

1719
function getCommentWithCache(
18-
discovered: [ts.SourceFile, ts.CommentRange] | undefined,
20+
discovered: [ts.SourceFile, ts.CommentRange[]] | undefined,
1921
config: CommentParserConfig,
2022
logger: Logger
2123
) {
2224
if (!discovered) return;
2325

24-
const [file, range] = discovered;
26+
const [file, ranges] = discovered;
2527
const cache = commentCache.get(file) || new Map<number, Comment>();
26-
if (cache?.has(range.pos)) {
27-
return cache.get(range.pos)!.clone();
28+
if (cache?.has(ranges[0].pos)) {
29+
return cache.get(ranges[0].pos)!.clone();
2830
}
2931

30-
const line = ts.getLineAndCharacterOfPosition(file, range.pos).line + 1;
32+
const line = ts.getLineAndCharacterOfPosition(file, ranges[0].pos).line + 1;
3133
const warning = (warning: string) =>
3234
logger.warn(
3335
`${warning} in comment at ${nicePath(file.fileName)}:${line}.`
3436
);
3537

3638
let comment: Comment;
37-
switch (range.kind) {
39+
switch (ranges[0].kind) {
3840
case ts.SyntaxKind.MultiLineCommentTrivia:
3941
comment = parseComment(
40-
lexBlockComment(file.text, range.pos, range.end),
42+
lexBlockComment(file.text, ranges[0].pos, ranges[0].end),
4143
config,
4244
warning
4345
);
4446
break;
4547
case ts.SyntaxKind.SingleLineCommentTrivia:
46-
throw "GERRIT FIX ME"; // GERRIT
48+
comment = parseComment(
49+
lexLineComments(file.text, ranges),
50+
config,
51+
warning
52+
);
53+
break;
4754
default:
48-
assertNever(range.kind);
55+
assertNever(ranges[0].kind);
4956
}
5057

51-
cache.set(range.pos, comment);
58+
cache.set(ranges[0].pos, comment);
5259
commentCache.set(file, cache);
5360

5461
return comment.clone();
@@ -58,10 +65,11 @@ export function getComment(
5865
symbol: ts.Symbol,
5966
kind: ReflectionKind,
6067
config: CommentParserConfig,
61-
logger: Logger
68+
logger: Logger,
69+
commentStyle: CommentStyle
6270
): Comment | undefined {
6371
const comment = getCommentWithCache(
64-
discoverComment(symbol, kind, logger),
72+
discoverComment(symbol, kind, logger, commentStyle),
6573
config,
6674
logger
6775
);
@@ -93,10 +101,11 @@ export function getComment(
93101
export function getSignatureComment(
94102
declaration: ts.SignatureDeclaration | ts.JSDocSignature,
95103
config: CommentParserConfig,
96-
logger: Logger
104+
logger: Logger,
105+
commentStyle: CommentStyle
97106
): Comment | undefined {
98107
return getCommentWithCache(
99-
discoverSignatureComment(declaration),
108+
discoverSignatureComment(declaration, commentStyle),
100109
config,
101110
logger
102111
);
@@ -124,11 +133,13 @@ export function getJsDocComment(
124133
const comment = getCommentWithCache(
125134
[
126135
file,
127-
{
128-
kind: ts.SyntaxKind.MultiLineCommentTrivia,
129-
pos: parent.pos,
130-
end: parent.end,
131-
},
136+
[
137+
{
138+
kind: ts.SyntaxKind.MultiLineCommentTrivia,
139+
pos: parent.pos,
140+
end: parent.end,
141+
},
142+
],
132143
],
133144
config,
134145
logger

0 commit comments

Comments
 (0)