Skip to content

Commit 8400f87

Browse files
gideongoodwindcodeIO
authored andcommitted
New: Add IParseOptions#alternateCommentMode (#968)
This PR adds a new option to IParseOptions, alternateCommentMode. When enabled, this activates an alternate comment parsing mode that preserves double-slash comments.
1 parent d6e3b9e commit 8400f87

File tree

5 files changed

+207
-23
lines changed

5 files changed

+207
-23
lines changed

index.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,12 @@ export interface IParseOptions {
10151015

10161016
/** Keeps field casing instead of converting to camel case */
10171017
keepCase?: boolean;
1018+
1019+
/**
1020+
* Turns on an alternate comment parsing mode that preserves double-slash
1021+
* and slash-star comments as documentation.
1022+
*/
1023+
alternateCommentMode?: boolean;
10181024
}
10191025

10201026
/**

src/parse.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function parse(source, root, options) {
6161
if (!options)
6262
options = parse.defaults;
6363

64-
var tn = tokenize(source),
64+
var tn = tokenize(source, options.alternateCommentMode || false),
6565
next = tn.next,
6666
push = tn.push,
6767
peek = tn.peek,

src/tokenize.js

+92-22
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var delimRe = /[\s{}=;:[\],'"()<>]/g,
66
stringSingleRe = /(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g;
77

88
var setCommentRe = /^ *[*/]+ */,
9+
setCommentAltRe = /^\s*\*?\/*/,
910
setCommentSplitRe = /\n/g,
1011
whitespaceRe = /\s/,
1112
unescapeRe = /\\(.?)/g;
@@ -92,9 +93,10 @@ tokenize.unescape = unescape;
9293
/**
9394
* Tokenizes the given .proto source and returns an object with useful utility functions.
9495
* @param {string} source Source contents
96+
* @param {boolean} alternateCommentMode Whether we should activate alternate comment parsing mode.
9597
* @returns {ITokenizerHandle} Tokenizer handle
9698
*/
97-
function tokenize(source) {
99+
function tokenize(source, alternateCommentMode) {
98100
/* eslint-disable callback-return */
99101
source = source.toString();
100102

@@ -159,10 +161,17 @@ function tokenize(source) {
159161
commentType = source.charAt(start++);
160162
commentLine = line;
161163
commentLineEmpty = false;
162-
var offset = start - 3, // "///" or "/**"
164+
var lookback;
165+
if (alternateCommentMode) {
166+
lookback = 2; // alternate comment parsing: "//" or "/*"
167+
} else {
168+
lookback = 3; // "///" or "/**"
169+
}
170+
var commentOffset = start - lookback,
163171
c;
164172
do {
165-
if (--offset < 0 || (c = source.charAt(offset)) === "\n") {
173+
if (--commentOffset < 0 ||
174+
(c = source.charAt(commentOffset)) === "\n") {
166175
commentLineEmpty = true;
167176
break;
168177
}
@@ -171,12 +180,34 @@ function tokenize(source) {
171180
.substring(start, end)
172181
.split(setCommentSplitRe);
173182
for (var i = 0; i < lines.length; ++i)
174-
lines[i] = lines[i].replace(setCommentRe, "").trim();
183+
lines[i] = lines[i]
184+
.replace(alternateCommentMode ? setCommentAltRe : setCommentRe, "")
185+
.trim();
175186
commentText = lines
176187
.join("\n")
177188
.trim();
178189
}
179190

191+
function isDoubleSlashCommentLine(startOffset) {
192+
var endOffset = findEndOfLine(startOffset);
193+
194+
// see if remaining line matches comment pattern
195+
var lineText = source.substring(startOffset, endOffset);
196+
// look for 1 or 2 slashes since startOffset would already point past
197+
// the first slash that started the comment.
198+
var isComment = /^\s*\/{1,2}/.test(lineText);
199+
return isComment;
200+
}
201+
202+
function findEndOfLine(cursor) {
203+
// find end of cursor's line
204+
var endOffset = cursor;
205+
while (endOffset < length && charAt(endOffset) !== "\n") {
206+
endOffset++;
207+
}
208+
return endOffset;
209+
}
210+
180211
/**
181212
* Obtains the next token.
182213
* @returns {string|null} Next token or `null` on eof
@@ -202,35 +233,71 @@ function tokenize(source) {
202233
if (++offset === length)
203234
return null;
204235
}
236+
205237
if (charAt(offset) === "/") {
206-
if (++offset === length)
238+
if (++offset === length) {
207239
throw illegal("comment");
240+
}
208241
if (charAt(offset) === "/") { // Line
209-
isDoc = charAt(start = offset + 1) === "/";
210-
while (charAt(++offset) !== "\n")
211-
if (offset === length)
212-
return null;
213-
++offset;
214-
if (isDoc) /// Comment
215-
setComment(start, offset - 1);
216-
++line;
217-
repeat = true;
242+
if (!alternateCommentMode) {
243+
// check for triple-slash comment
244+
isDoc = charAt(start = offset + 1) === "/";
245+
246+
while (charAt(++offset) !== "\n") {
247+
if (offset === length) {
248+
return null;
249+
}
250+
}
251+
++offset;
252+
if (isDoc) {
253+
setComment(start, offset - 1);
254+
}
255+
++line;
256+
repeat = true;
257+
} else {
258+
// check for double-slash comments, consolidating consecutive lines
259+
start = offset;
260+
isDoc = false;
261+
if (isDoubleSlashCommentLine(offset)) {
262+
isDoc = true;
263+
do {
264+
offset = findEndOfLine(offset);
265+
if (offset === length) {
266+
break;
267+
}
268+
offset++;
269+
} while (isDoubleSlashCommentLine(offset));
270+
} else {
271+
offset = Math.min(length, findEndOfLine(offset) + 1);
272+
}
273+
if (isDoc) {
274+
setComment(start, offset);
275+
}
276+
line++;
277+
repeat = true;
278+
}
218279
} else if ((curr = charAt(offset)) === "*") { /* Block */
219-
isDoc = charAt(start = offset + 1) === "*";
280+
// check for /** (regular comment mode) or /* (alternate comment mode)
281+
start = offset + 1;
282+
isDoc = alternateCommentMode || charAt(start) === "*";
220283
do {
221-
if (curr === "\n")
284+
if (curr === "\n") {
222285
++line;
223-
if (++offset === length)
286+
}
287+
if (++offset === length) {
224288
throw illegal("comment");
289+
}
225290
prev = curr;
226291
curr = charAt(offset);
227292
} while (prev !== "*" || curr !== "/");
228293
++offset;
229-
if (isDoc) /** Comment */
294+
if (isDoc) {
230295
setComment(start, offset - 2);
296+
}
231297
repeat = true;
232-
} else
298+
} else {
233299
return "/";
300+
}
234301
}
235302
} while (repeat);
236303

@@ -302,14 +369,17 @@ function tokenize(source) {
302369
function cmnt(trailingLine) {
303370
var ret = null;
304371
if (trailingLine === undefined) {
305-
if (commentLine === line - 1 && (commentType === "*" || commentLineEmpty))
372+
if (commentLine === line - 1 && (alternateCommentMode || commentType === "*" || commentLineEmpty)) {
306373
ret = commentText;
374+
}
307375
} else {
308376
/* istanbul ignore else */
309-
if (commentLine < trailingLine)
377+
if (commentLine < trailingLine) {
310378
peek();
311-
if (commentLine === trailingLine && !commentLineEmpty && commentType === "/")
379+
}
380+
if (commentLine === trailingLine && !commentLineEmpty && (alternateCommentMode || commentType === "/")) {
312381
ret = commentText;
382+
}
313383
}
314384
return ret;
315385
}
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* File with alternate comment syntax.
3+
* This file uses double slash and regular star-slash comment styles for doc
4+
* strings.
5+
*/
6+
7+
syntax = "proto3";
8+
9+
// Message with
10+
// a
11+
// multi-line comment.
12+
message Test1 {
13+
14+
/**
15+
* Field with a doc-block comment.
16+
*/
17+
string field1 = 1;
18+
19+
// Field with a single-line comment starting with two slashes.
20+
uint32 field2 = 2;
21+
22+
/// Field with a single-line comment starting with three slashes.
23+
bool field3 = 3;
24+
25+
/* Field with a single-line slash-star comment. */
26+
bool field4 = 4;
27+
28+
bool field5 = 5; // Field with a trailing single-line two-slash comment.
29+
30+
bool field6 = 6; /// Field with a trailing single-line three-slash comment.
31+
32+
bool field7 = 7; /* Field with a trailing single-line slash-star comment. */
33+
34+
bool field8 = 8;
35+
36+
// Field with a
37+
// multi-line comment.
38+
bool field9 = 9;
39+
40+
/**
41+
* Field with a
42+
* multi-line doc-block comment.
43+
*/
44+
string field10 = 10;
45+
}
46+
47+
/* Message
48+
with
49+
a multiline plain slash-star
50+
comment.
51+
*/
52+
message Test2 {
53+
}
54+
55+
/*
56+
* Message
57+
* with
58+
* a
59+
* comment and stars.
60+
*/
61+
enum Test3 {
62+
63+
/** Value with a comment. */
64+
ONE = 1;
65+
66+
// Value with a single-line comment.
67+
TWO = 2;
68+
69+
/// Value with a triple-slash comment.
70+
THREE = 3; // ignored
71+
72+
FOUR = 4; /// Other value with a comment.
73+
}
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
var tape = require("tape");
2+
3+
var protobuf = require("..");
4+
5+
tape.test("proto comments in alternate-parse mode", function(test) {
6+
test.plan(17);
7+
var options = {alternateCommentMode: true};
8+
var root = new protobuf.Root();
9+
root.load("tests/data/comments-alternate-parse.proto", options, function(err, root) {
10+
if (err)
11+
throw test.fail(err.message);
12+
13+
test.equal(root.lookup("Test1").comment, "Message with\na\nmulti-line comment.", "should parse double-slash multiline comment");
14+
test.equal(root.lookup("Test2").comment, "Message\nwith\na multiline plain slash-star\ncomment.", "should parse slash-star multiline comment");
15+
test.equal(root.lookup("Test3").comment, "Message\nwith\na\ncomment and stars.", "should parse doc-block multiline comment");
16+
17+
test.equal(root.lookup("Test1.field1").comment, "Field with a doc-block comment.", "should parse doc-block field comment");
18+
test.equal(root.lookup("Test1.field2").comment, "Field with a single-line comment starting with two slashes.", "should parse double-slash field comment");
19+
test.equal(root.lookup("Test1.field3").comment, "Field with a single-line comment starting with three slashes.", "should parse triple-slash field comment");
20+
test.equal(root.lookup("Test1.field4").comment, "Field with a single-line slash-star comment.", "should parse single-line slash-star field comment");
21+
test.equal(root.lookup("Test1.field5").comment, "Field with a trailing single-line two-slash comment.", "should parse trailing double-slash comment");
22+
test.equal(root.lookup("Test1.field6").comment, "Field with a trailing single-line three-slash comment.", "should parse trailing triple-slash comment");
23+
test.equal(root.lookup("Test1.field7").comment, "Field with a trailing single-line slash-star comment.", "should parse trailing slash-star comment");
24+
test.equal(root.lookup("Test1.field8").comment, null, "should parse no comment");
25+
test.equal(root.lookup("Test1.field9").comment, "Field with a\nmulti-line comment.", "should parse multiline double-slash field comment");
26+
test.equal(root.lookup("Test1.field10").comment, "Field with a\nmulti-line doc-block comment.", "should parse multiline doc-block field comment");
27+
28+
test.equal(root.lookup("Test3").comments.ONE, "Value with a comment.", "should parse blocks for enum values");
29+
test.equal(root.lookup("Test3").comments.TWO, "Value with a single-line comment.", "should parse double-slash comments for enum values");
30+
test.equal(root.lookup("Test3").comments.THREE, "Value with a triple-slash comment.", "should parse lines for enum values and prefer on top over trailing");
31+
test.equal(root.lookup("Test3").comments.FOUR, "Other value with a comment.", "should not confuse previous trailing comments with comments for the next field");
32+
33+
test.end();
34+
});
35+
});

0 commit comments

Comments
 (0)