Skip to content

Commit 712ee69

Browse files
recihalexander-fenster
authored and
Taylor McIntyre
committed
feat: better comment parse (protobufjs#1419)
* fix trailing comment in last line recognized as leading comment for current token. * add preferTrailingComment to IParseOptions Co-authored-by: Alexander Fenster <[email protected]>
1 parent d263bea commit 712ee69

7 files changed

+100
-12
lines changed

index.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,9 @@ export interface IParseOptions {
10461046

10471047
/** Recognize double-slash comments in addition to doc-block comments. */
10481048
alternateCommentMode?: boolean;
1049+
1050+
/** Use trailing comment when both leading comment and trailing comment exist. */
1051+
preferTrailingComment?: boolean;
10491052
}
10501053

10511054
/** Options modifying the behavior of JSON serialization. */

src/parse.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ var base10Re = /^[1-9][0-9]*$/,
4242
* @interface IParseOptions
4343
* @property {boolean} [keepCase=false] Keeps field casing instead of converting to camel case
4444
* @property {boolean} [alternateCommentMode=false] Recognize double-slash comments in addition to doc-block comments.
45+
* @property {boolean} [preferTrailingComment=false] Use trailing comment when both leading comment and trailing comment exist.
4546
*/
4647

4748
/**
@@ -68,6 +69,7 @@ function parse(source, root, options) {
6869
if (!options)
6970
options = parse.defaults;
7071

72+
var preferTrailingComment = options.preferTrailingComment || false;
7173
var tn = tokenize(source, options.alternateCommentMode || false),
7274
next = tn.next,
7375
push = tn.push,
@@ -291,8 +293,8 @@ function parse(source, root, options) {
291293
if (fnElse)
292294
fnElse();
293295
skip(";");
294-
if (obj && typeof obj.comment !== "string")
295-
obj.comment = cmnt(trailingLine); // try line-type comment if no block
296+
if (obj && (typeof obj.comment !== "string" || preferTrailingComment))
297+
obj.comment = cmnt(trailingLine) || obj.comment; // try line-type comment
296298
}
297299
}
298300

src/tokenize.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ function tokenize(source, alternateCommentMode) {
106106
commentType = null,
107107
commentText = null,
108108
commentLine = 0,
109-
commentLineEmpty = false;
109+
commentLineEmpty = false,
110+
commentIsLeading = false;
110111

111112
var stack = [];
112113

@@ -154,13 +155,15 @@ function tokenize(source, alternateCommentMode) {
154155
* Sets the current comment text.
155156
* @param {number} start Start offset
156157
* @param {number} end End offset
158+
* @param {boolean} isLeading set if a leading comment
157159
* @returns {undefined}
158160
* @inner
159161
*/
160-
function setComment(start, end) {
162+
function setComment(start, end, isLeading) {
161163
commentType = source.charAt(start++);
162164
commentLine = line;
163165
commentLineEmpty = false;
166+
commentIsLeading = isLeading;
164167
var lookback;
165168
if (alternateCommentMode) {
166169
lookback = 2; // alternate comment parsing: "//" or "/*"
@@ -222,14 +225,17 @@ function tokenize(source, alternateCommentMode) {
222225
prev,
223226
curr,
224227
start,
225-
isDoc;
228+
isDoc,
229+
isLeadingComment = offset === 0;
226230
do {
227231
if (offset === length)
228232
return null;
229233
repeat = false;
230234
while (whitespaceRe.test(curr = charAt(offset))) {
231-
if (curr === "\n")
235+
if (curr === "\n") {
236+
isLeadingComment = true;
232237
++line;
238+
}
233239
if (++offset === length)
234240
return null;
235241
}
@@ -250,7 +256,7 @@ function tokenize(source, alternateCommentMode) {
250256
}
251257
++offset;
252258
if (isDoc) {
253-
setComment(start, offset - 1);
259+
setComment(start, offset - 1, isLeadingComment);
254260
}
255261
++line;
256262
repeat = true;
@@ -271,7 +277,7 @@ function tokenize(source, alternateCommentMode) {
271277
offset = Math.min(length, findEndOfLine(offset) + 1);
272278
}
273279
if (isDoc) {
274-
setComment(start, offset);
280+
setComment(start, offset, isLeadingComment);
275281
}
276282
line++;
277283
repeat = true;
@@ -292,7 +298,7 @@ function tokenize(source, alternateCommentMode) {
292298
} while (prev !== "*" || curr !== "/");
293299
++offset;
294300
if (isDoc) {
295-
setComment(start, offset - 2);
301+
setComment(start, offset - 2, isLeadingComment);
296302
}
297303
repeat = true;
298304
} else {
@@ -370,15 +376,15 @@ function tokenize(source, alternateCommentMode) {
370376
var ret = null;
371377
if (trailingLine === undefined) {
372378
if (commentLine === line - 1 && (alternateCommentMode || commentType === "*" || commentLineEmpty)) {
373-
ret = commentText;
379+
ret = commentIsLeading ? commentText : null;
374380
}
375381
} else {
376382
/* istanbul ignore else */
377383
if (commentLine < trailingLine) {
378384
peek();
379385
}
380386
if (commentLine === trailingLine && !commentLineEmpty && (alternateCommentMode || commentType === "/")) {
381-
ret = commentText;
387+
ret = commentIsLeading ? null : commentText;
382388
}
383389
}
384390
return ret;

tests/api_tokenize.js

+7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ tape.test("tokenize", function(test) {
3535
tn = tokenize("/// line comment\na\n");
3636
tn.next();
3737
test.equal(tn.cmnt(), "line comment", "should keep leading comments around while on the next line");
38+
tn = tokenize("/// leading comment A\na /// trailing comment A\nb /// trailing comment B\n");
39+
tn.next();
40+
test.equal(tn.cmnt(), "leading comment A", "should parse leading comment");
41+
test.equal(tn.cmnt(tn.line), "trailing comment A", "should parse trailing comment");
42+
tn.next();
43+
test.equal(tn.cmnt(), null, "trailing comment should not be recognized as leading comment for next line");
44+
test.equal(tn.cmnt(tn.line), "trailing comment B", "should parse trailing comment");
3845

3946
test.ok(expectError("something; /"), "should throw for unterminated line comments");
4047
test.ok(expectError("something; /* comment"), "should throw for unterminated block comments");

tests/data/comments-alternate-parse.proto

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ message Test1 {
4242
* multi-line doc-block comment.
4343
*/
4444
string field10 = 10;
45+
46+
// Field with both block comment
47+
string field11 = 11; // and trailing comment.
48+
string field12 = 12; // Trailing comment in last line should not be recognized as leading comment for this field.
4549
}
4650

4751
/* Message

tests/docs_comments.js

+25
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,28 @@ tape.test("proto comments", function(test) {
2424
test.end();
2525
});
2626
});
27+
28+
tape.test("proto comments with trailing comment preferred", function(test) {
29+
test.plan(10);
30+
var options = {preferTrailingComment: true};
31+
var root = new protobuf.Root();
32+
root.load("tests/data/comments.proto", options, function(err, root) {
33+
if (err)
34+
throw test.fail(err.message);
35+
36+
test.equal(root.lookup("Test1").comment, "Message\nwith\na\ncomment.", "should parse /**-blocks");
37+
test.equal(root.lookup("Test2").comment, null, "should not parse //-blocks");
38+
test.equal(root.lookup("Test3").comment, null, "should not parse /*-blocks");
39+
40+
test.equal(root.lookup("Test1.field1").comment, "Field with a comment.", "should parse blocks for message fields");
41+
test.equal(root.lookup("Test1.field2").comment, null, "should not parse lines for message fields");
42+
test.equal(root.lookup("Test1.field3").comment, "Field with a comment and a <a href=\"http://example.com/foo/\">link</a>", "should parse triple-slash lines for message fields");
43+
44+
test.equal(root.lookup("Test3").comments.ONE, "Value with a comment.", "should parse blocks for enum values");
45+
test.equal(root.lookup("Test3").comments.TWO, null, "should not parse lines for enum values");
46+
test.equal(root.lookup("Test3").comments.THREE, "Value with a comment.", "should prefer trailing comment when preferTrailingComment option enabled");
47+
test.equal(root.lookup("Test3").comments.FOUR, "Other value with a comment.", "should not confuse previous trailing comments with comments for the next field");
48+
49+
test.end();
50+
});
51+
});

tests/docs_comments_alternate_parse.js

+42-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ var tape = require("tape");
33
var protobuf = require("..");
44

55
tape.test("proto comments in alternate-parse mode", function(test) {
6-
test.plan(21);
6+
test.plan(23);
77
var options = {alternateCommentMode: true};
88
var root = new protobuf.Root();
99
root.load("tests/data/comments-alternate-parse.proto", options, function(err, root) {
@@ -24,6 +24,8 @@ tape.test("proto comments in alternate-parse mode", function(test) {
2424
test.equal(root.lookup("Test1.field8").comment, null, "should parse no comment");
2525
test.equal(root.lookup("Test1.field9").comment, "Field with a\nmulti-line comment.", "should parse multiline double-slash field comment");
2626
test.equal(root.lookup("Test1.field10").comment, "Field with a\nmulti-line doc-block comment.", "should parse multiline doc-block field comment");
27+
test.equal(root.lookup("Test1.field11").comment, "Field with both block comment", "should parse both trailing comment and trailing comment");
28+
test.equal(root.lookup("Test1.field12").comment, "Trailing comment in last line should not be recognized as leading comment for this field.", "trailing comment in last line should not be recognized as leading comment for this field");
2729

2830
test.equal(root.lookup("Test3").comments.ONE, "Value with a comment.", "should parse blocks for enum values");
2931
test.equal(root.lookup("Test3").comments.TWO, "Value with a single-line comment.", "should parse double-slash comments for enum values");
@@ -38,3 +40,42 @@ tape.test("proto comments in alternate-parse mode", function(test) {
3840
test.end();
3941
});
4042
});
43+
44+
tape.test("proto comments in alternate-parse mode with trailing comment preferred", function(test) {
45+
test.plan(23);
46+
var options = {alternateCommentMode: true, preferTrailingComment: true};
47+
var root = new protobuf.Root();
48+
root.load("tests/data/comments-alternate-parse.proto", options, function(err, root) {
49+
if (err)
50+
throw test.fail(err.message);
51+
52+
test.equal(root.lookup("Test1").comment, "Message with\na\nmulti-line comment.", "should parse double-slash multiline comment");
53+
test.equal(root.lookup("Test2").comment, "Message\nwith\na multiline plain slash-star\ncomment.", "should parse slash-star multiline comment");
54+
test.equal(root.lookup("Test3").comment, "Message\nwith\na\ncomment and stars.", "should parse doc-block multiline comment");
55+
56+
test.equal(root.lookup("Test1.field1").comment, "Field with a doc-block comment.", "should parse doc-block field comment");
57+
test.equal(root.lookup("Test1.field2").comment, "Field with a single-line comment starting with two slashes.", "should parse double-slash field comment");
58+
test.equal(root.lookup("Test1.field3").comment, "Field with a single-line comment starting with three slashes.", "should parse triple-slash field comment");
59+
test.equal(root.lookup("Test1.field4").comment, "Field with a single-line slash-star comment.", "should parse single-line slash-star field comment");
60+
test.equal(root.lookup("Test1.field5").comment, "Field with a trailing single-line two-slash comment.", "should parse trailing double-slash comment");
61+
test.equal(root.lookup("Test1.field6").comment, "Field with a trailing single-line three-slash comment.", "should parse trailing triple-slash comment");
62+
test.equal(root.lookup("Test1.field7").comment, "Field with a trailing single-line slash-star comment.", "should parse trailing slash-star comment");
63+
test.equal(root.lookup("Test1.field8").comment, null, "should parse no comment");
64+
test.equal(root.lookup("Test1.field9").comment, "Field with a\nmulti-line comment.", "should parse multiline double-slash field comment");
65+
test.equal(root.lookup("Test1.field10").comment, "Field with a\nmulti-line doc-block comment.", "should parse multiline doc-block field comment");
66+
test.equal(root.lookup("Test1.field11").comment, "and trailing comment.", "should parse both trailing comment and trailing comment");
67+
test.equal(root.lookup("Test1.field12").comment, "Trailing comment in last line should not be recognized as leading comment for this field.", "trailing comment in last line should not be recognized as leading comment for this field");
68+
69+
test.equal(root.lookup("Test3").comments.ONE, "Value with a comment.", "should parse blocks for enum values");
70+
test.equal(root.lookup("Test3").comments.TWO, "Value with a single-line comment.", "should parse double-slash comments for enum values");
71+
test.equal(root.lookup("Test3").comments.THREE, "ignored", "should prefer trailing comment when preferTrailingComment option enabled");
72+
test.equal(root.lookup("Test3").comments.FOUR, "Other value with a comment.", "should not confuse previous trailing comments with comments for the next field");
73+
74+
test.equal(root.lookup("ServiceTest.SingleLineMethod").comment, 'My method does things');
75+
test.equal(root.lookup("ServiceTest.TwoLineMethodWithComment").comment, 'TwoLineMethodWithComment documentation');
76+
test.equal(root.lookup("ServiceTest.ThreeLine012345678901234567890123456712345671234567123456783927483923473892837489238749832432874983274983274983274").comment, 'Very very long method');
77+
test.equal(root.lookup("ServiceTest.TwoLineMethodNoComment").comment, null);
78+
79+
test.end();
80+
});
81+
});

0 commit comments

Comments
 (0)