From 0199ea628b4d2c67a216f5fb8b7407169146fcce Mon Sep 17 00:00:00 2001 From: Iwan Date: Thu, 1 Nov 2018 13:49:13 +0100 Subject: [PATCH 1/2] Add support for single line comments of the form // --- .../expected_output/basicStructures.re | 2 +- .../expected_output/emptyFileComment.re | 1 + .../expected_output/lineComments.re | 748 ++++++++++++++++++ .../unit_tests/input/basicStructures.re | 2 +- .../unit_tests/input/emptyFileComment.re | 1 + formatTest/unit_tests/input/lineComments.re | 735 +++++++++++++++++ src/reason-parser/reason_comment.ml | 3 + src/reason-parser/reason_lexer.mll | 22 +- src/reason-parser/reason_syntax_util.ml | 6 + src/reason-parser/reason_syntax_util.mli | 2 + src/reason-parser/reason_toolchain.ml | 14 +- 11 files changed, 1530 insertions(+), 6 deletions(-) create mode 100644 formatTest/unit_tests/expected_output/emptyFileComment.re create mode 100644 formatTest/unit_tests/expected_output/lineComments.re create mode 100644 formatTest/unit_tests/input/emptyFileComment.re create mode 100644 formatTest/unit_tests/input/lineComments.re diff --git a/formatTest/unit_tests/expected_output/basicStructures.re b/formatTest/unit_tests/expected_output/basicStructures.re index 4ddebb26b..1a96d50f5 100644 --- a/formatTest/unit_tests/expected_output/basicStructures.re +++ b/formatTest/unit_tests/expected_output/basicStructures.re @@ -146,7 +146,7 @@ let x = arr^[0] = 1; *============================================================================ */; -let (/\/) = (+); /* // is not a comment */ +let (/++) = (+); /* // indicates the start of a comment, not an infix op */ let something = if (self.ext.logSuccess) { diff --git a/formatTest/unit_tests/expected_output/emptyFileComment.re b/formatTest/unit_tests/expected_output/emptyFileComment.re new file mode 100644 index 000000000..eb2b9c00d --- /dev/null +++ b/formatTest/unit_tests/expected_output/emptyFileComment.re @@ -0,0 +1 @@ +// file with just a single line comment diff --git a/formatTest/unit_tests/expected_output/lineComments.re b/formatTest/unit_tests/expected_output/lineComments.re new file mode 100644 index 000000000..e909250cc --- /dev/null +++ b/formatTest/unit_tests/expected_output/lineComments.re @@ -0,0 +1,748 @@ +3; // - +3; //- + +3; //- + +3 /*-*/; +// **** comment +/*** comment */ +/** docstring */ +// comment +/** docstring */ +/*** comment */ +/**** comment */ +/***** comment */ +/** */ +/*** */ +/**** */ +/**/ +/***/ +/****/ +/** (** comment *) */ +/** (*** comment *) */ +// (** comment *) +// (*** comment *) +// *(*** comment *) +// comment * +// comment ** +// comment *** +// comment **** +/** + * Multiline + */ +/** Multiline + * + */ +/** + ** + */ +module JustString = { + include Map.Make(Int32); // Comment eol include +}; + +let testingEndOfLineComments = [ + "Item 1" /* Comment For First Item */, + "Item 2" /* Comment For Second Item */, + "Item 3" /* Comment For Third Item */, + "Item 4" /* Comment For Fourth Item - but before trailing comma */, + // Comment after last item in list. +] /* Comment after rbracket */; + +// But if you place them after the comma at eol, they're preserved as such +let testingEndOfLineComments = [ + "Item 1", // Comment For First Item + "Item 2", // Comment For Second Item + "Item 3", // Comment For Third Item + "Item 4" /* Comment For Fourth Item - but before trailing comma */, + // Comment after last item in list. +] /* Comment after rbracket */; + +// The space between ; and comment shoudn't matter +let testPlacementOfTrailingComment = [ + "Item 0" // + // Comment after last item in list. +]; // Comment after semi + +// The space between ; and comment shoudn't matter +let testPlacementOfTrailingComment = [ + "Item 0" // + // Comment after last item in list. +]; // Comment after semi + +// Try again but without other things in the list +let testPlacementOfTrailingComment = [ + "Item 0" // +]; // Comment after semi + +// The space between ; and comment shoudn't matter +let testPlacementOfTrailingComment = [ + "Item 0" // + // Comment after last item in list. +]; // Comment after semi + +let testingEndOfLineComments = []; // Comment after entire let binding + +// The following is not yet idempotent +// let myFunction +// withFirstArg // First arg +// andSecondArg => { // Second Arg +// withFirstArg + andSecondArg /* before semi */ ; +// }; + +let myFunction = // First arg + ( + withFirstArg, + // Second Arg + andSecondArg, + ) => + withFirstArg + andSecondArg; // After Semi + +type point = { + x: string, // x field + y: string // y field +}; + +type pointWithManyKindsOfComments = { + // Line before x + x: string, // x field + // Line before y + y: string // y field + // Final row of record +}; + +type typeParamPointWithComments('a) = { + // Line before x + x: 'a, // x field + // Line before y + y: 'a // y field + // Final row of record +}; + +// Now, interleaving comments in type params +// Type name +type typeParamPointWithComments2 + // The a type param + ( + 'a, + // The b type apram + 'b, + ) = { + // Line before x + x: 'a, // x field + // Line before y + y: 'a // y field + // Final row of record +}; + +/* The way the last row comment is formatted is suboptimal becuase + * record type definitions do not include enough location information */ +type anotherpoint = { + x: string, // x field + y: string // y field + // comment as last row of record +}; + +type t = (int, int); // End of line on t +type t2 = (int, int); // End of line on (int, int) + +type t3 = (int, int); // End of line on (int, int) + +type variant = + | X(int, int) // End of line on X + | Y(int, int); // End of line on Y // Comment on entire type def for variant + +// Before let +let res = + // Before switch + switch (X(2, 3)) { + // Above X line + | X(_) => "result of X" // End of arrow and X line + // Above Y line + | Y(_) => "result of Y" // End of arrow and Y line + }; // After final semi in switch + +let res = + switch (X(2, 3)) { + | X(0, 0) => + // After X arrow + "result of X" // End of X body line + | X(1, 0) /* Before X's arrow */ => "result of X" // End of X body line + | X(_) => + // After X _ arrow + "result of X" // End of X body line + // Above Y line + | Y(_) => + // Comment above Y body + "result of Y" + }; + +type variant2 = + // Comment above X + | X(int, int) // End of line on X + // Comment above Y + | Y(int, int); + +type variant3 = + // Comment above X + | X(int, int) // End of line on X + // Comment above Y + | Y(int, int); // End of line on Y + +type x = { + // not attached *above* x + fieldOne: int, + fieldA: int, +} // Attached end of line after x +and y = { + // not attached *above* y + fieldTwo: int, +}; // Attached end of line after y + +type x2 = { + // not attached *above* x2 + fieldOne: int, + fieldA: int, +} // Attached end of line after x2 +and y2 = { + // not attached *above* y2 + fieldTwo: int, +}; + +let result = + switch (None) { + | Some({fieldOne: 20, fieldA: a}) => + // Where does this comment go? + let tmp = 0; + 2 + tmp; + | Some({fieldOne: n, fieldA: a}) => + // How about this one + let tmp = n; + n + tmp; + | None => 20 + }; + +let res = + // Before switch + switch (X(2, 3)) { + // Above X line + | X(_) => "result of X" // End of arrow and X line + // Above Y line + | Y(_) => "result of Y" // End of arrow and Y line + }; + +/* + * Now these end of line comments *should* be retained. + */ +let result = + switch (None) { + | Some({ + fieldOne: 20, // end of line + fieldA: a // end of line + }) => + let tmp = 0; + 2 + tmp; + | Some({ + fieldOne: n, // end of line + fieldA: a // end of line + }) => + let tmp = n; + n + tmp; + | None => 20 + }; + +/* + * These end of line comments *should* be retained. + * To get the simple expression eol comment to be retained, we just need to + * implement label breaking eol behavior much like we did with sequences. + * Otherwise, right now they are not idempotent. + */ +let res = + switch ( + // Retain this + X(2, 3) + ) { + // Above X line + | X( + _, // retain this + _ // retain this + ) => "result of X" + + // Above Y line + | Y(_) => "result of Y" // End of arrow and Y line + }; + +type optionalTuple = + | OptTup( + option( + ( + int, // First int + int // Second int + ), + ), + ); + +type optionTuple = + option( + ( + int, // First int + int // Second int + ), + ); + +type intPair = ( + int, // First int + int // Second int +); + +type intPair2 = ( + // First int + int, + // Second int + int, +); + +let result = /**/ (2 + 3); + +// This is not yet idempotent +// { +// /**/ +// (+) 2 3 +// }; + +let a = (); +for (i in 0 to 10) { + // bla + a; +}; + +if (true) { + // hello + () +}; + +type color = + | Red(int) // After red end of line + | Black(int) // After black end of line + | Green(int); // After green end of line // On next line after color type def + +let blahCurriedX = x => + fun + | Red(10) + | Black(20) + | Green(10) => 1 // After or pattern green + | Red(x) => 0 // After red + | Black(x) => 0 // After black + | Green(x) => 0; // After second green // On next line after blahCurriedX def + +let name_equal = (x, y) => x == y; + +let equal = (i1, i2) => + i1.contents === i2.contents && true; // most unlikely first + +let equal = (i1, i2) => + compare(compare(0, 0), compare(1, 1)); // END OF LINE HERE + +let tuple_equal = ((i1, i2)) => i1 == i2; + +let tuple_equal = ((csu, mgd)) => + // Some really long comments, see https://github.com/facebook/reason/issues/811 + tuple_equal((csu, mgd)); + +/** Comments inside empty function bodies + * See https://github.com/facebook/reason/issues/860 + */ +let fun_def_comment_inline = () => {/* */}; + +let fun_def_comment_long = () => { + /* longer comment inside empty function body */ +}; + +let trueThing = true; + +for (i in 0 to 1) { + // comment + print_newline(); +}; + +while (trueThing) { + // comment + print_newline(); +}; + +if (trueThing) { + // comment + print_newline(); +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); + // Comment before print + print_newline(); + // Comment after final print +} else { + // Comment before print + print_newline(); + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); + // Comment after final print +} else { + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before while test +while (trueThing) { + // Comment before print + print_newline(); + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before while test +while (trueThing) { + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before for test +for (i in 0 to 100) { + // Comment before print + print_newline(); + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before for test +for (i in 0 to 100) { + // Comment before print + print_newline(); + // Comment after final print +}; + +if (trueThing) { + // Comment before print + print_newline(); // eol print + // Comment before print + print_newline(); // eol print + // Comment after print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); // eol print + // Comment after print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); // eol print + // Comment before print + print_newline(); // eol print + // Comment after print +} else { + // Comment before print + print_newline(); // eol print + // Comment before print + print_newline(); // eol print + // Comment after print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); // eol print + // Comment before print +} else { + // Comment before print + print_newline(); // eol print + // Comment before print + print_newline(); // eol print + // Comment after print +}; + +// Comment before while test +while (trueThing) { + // Comment before print + print_newline(); // eol + // Comment before print + print_newline(); // eol + // Comment after final print +}; + +// Comment before while test +while (trueThing) { + // Comment before print + print_newline(); // eol + // Comment after final print +}; + +// Comment before for test +for (i in 0 to 100) { + // Comment before print + print_newline(); // eol + // Comment before print + print_newline(); // eol + // Comment after final print +}; + +// Comment before for test +for (i in 0 to 100) { + // Comment before print + print_newline(); // eol + // Comment after final print +}; + +let f = (a, b, c, d) => a + b + c + d; + +while (trueThing) { + f( + // a + 1, + // b + 2, + // c + 3, + // d + 4, + // does work + ); +}; +while (trueThing) { + f( + // a + 1, + // b + 2, + // c + 3, + // d + 4 // does work + ); +}; + +ignore( + ( + _really, + _long, + _printWidth, + _exceeded, + _here, + ) => { + // First comment + let x = 0; + x + x; + // Closing comment +}); + +ignore((_xxx, _yyy) => { + // First comment + let x = 0; + x + x; + // Closing comment +}); + +type tester('a, 'b) = + | TwoArgsConstructor('a, 'b) + | OneTupleArgConstructor(('a, 'b)); +let callFunctionTwoArgs = (a, b) => (); +let callFunctionOneTuple = tuple => (); + +let y = + TwoArgsConstructor( + 1, //eol1 + 2 // eol2 + ); + +let y = + callFunctionTwoArgs( + 1, //eol1 + 2 // eol2 + ); + +let y = + OneTupleArgConstructor(( + 1, //eol1 + 2 // eol2 + )); + +let y = + callFunctionOneTuple(( + 1, //eol1 + 2 // eol2 + )); + +type polyRecord('a, 'b) = { + fieldOne: 'a, + fieldTwo: 'b, +}; + +let r = { + fieldOne: 1, //eol1 + fieldTwo: 2 // eol2 +}; + +let r = { + fieldOne: 1, //eol1 + fieldTwo: 2 // eol2 with trailing comma +}; + +let y = + TwoArgsConstructor( + "1", //eol1 + "2" // eol2 + ); + +let y = + callFunctionTwoArgs( + "1", //eol1 + "2" // eol2 + ); + +let y = + OneTupleArgConstructor(( + "1", //eol1 + "2" // eol2 + )); + +let y = + callFunctionOneTuple(( + "1", //eol1 + "2" // eol2 + )); + +let r = { + fieldOne: "1", //eol1 + fieldTwo: "2" // eol2 +}; + +let r = { + fieldOne: "1", //eol1 + fieldTwo: "2" // eol2 with trailing comma +}; + +let identifier = "hello"; + +let y = + TwoArgsConstructor( + identifier, //eol1 + identifier // eol2 + ); + +let y = + callFunctionTwoArgs( + identifier, //eol1 + identifier // eol2 + ); + +let y = + OneTupleArgConstructor(( + identifier, //eol1 + identifier // eol2 + )); + +let y = + callFunctionOneTuple(( + identifier, //eol1 + identifier // eol2 + )); + +let r = { + fieldOne: identifier, //eol1 + fieldTwo: identifier // eol2 +}; + +let r = { + fieldOne: identifier, //eol1 + fieldTwo: identifier // eol2 with trailing comma +}; + +let y = + TwoArgsConstructor( + identifier: string, //eol1 + identifier: string // eol2 + ); + +let y = + callFunctionTwoArgs( + identifier: string, //eol1 + identifier: string // eol2 + ); + +let y = + OneTupleArgConstructor(( + identifier: string, //eol1 + identifier: string // eol2 + )); + +let y = + callFunctionOneTuple(( + identifier: string, //eol1 + identifier: string // eol2 + )); + +let r = { + fieldOne: (identifier: string), //eol1 + fieldTwo: (identifier: string) // eol2 +}; + +let r = { + fieldOne: (identifier: string), //eol1 + fieldTwo: (identifier: string) // eol2 with trailing comma +}; + +// whitespace interleaving + +// comment1 +// comment2 + +// whitespace above & below + +let r = { + fieldOne: (identifier: string), //eol1 + // c1 + + // c2 + + // c3 + // c4 + + // c5 + fieldTwo: (identifier: string) // eol2 with trailing comma +}; +// trailing + +// trailing whitespace above +// attach + +// last comment diff --git a/formatTest/unit_tests/input/basicStructures.re b/formatTest/unit_tests/input/basicStructures.re index 0b5a261ff..a8ae9eed3 100644 --- a/formatTest/unit_tests/input/basicStructures.re +++ b/formatTest/unit_tests/input/basicStructures.re @@ -134,7 +134,7 @@ let x = arr^[0] = 1; *============================================================================ */; -let (//) = (+); /* // is not a comment */ +let (/++) = (+); /* // indicates the start of a comment, not an infix op */ let something = if (self.ext.logSuccess) { print_string("Did tap"); diff --git a/formatTest/unit_tests/input/emptyFileComment.re b/formatTest/unit_tests/input/emptyFileComment.re new file mode 100644 index 000000000..eb2b9c00d --- /dev/null +++ b/formatTest/unit_tests/input/emptyFileComment.re @@ -0,0 +1 @@ +// file with just a single line comment diff --git a/formatTest/unit_tests/input/lineComments.re b/formatTest/unit_tests/input/lineComments.re new file mode 100644 index 000000000..ce619d579 --- /dev/null +++ b/formatTest/unit_tests/input/lineComments.re @@ -0,0 +1,735 @@ +3; // - +3 //- +; +3//- +; +3/*-*/; +// **** comment +/*** comment */ +/** docstring */ +// comment +/** docstring */ +/*** comment */ +/**** comment */ +/***** comment */ +/** */ +/*** */ +/**** */ +/**/ +/***/ +/****/ +/** (** comment *) */ +/** (*** comment *) */ +// (** comment *) +// (*** comment *) +// *(*** comment *) +// comment * +// comment ** +// comment *** +// comment **** +/** + * Multiline + */ +/** Multiline + * + */ +/** + ** + */ + +module JustString = { + include Map.Make(Int32); // Comment eol include +}; + +let testingEndOfLineComments = + [ + "Item 1" /* Comment For First Item */, + "Item 2" /* Comment For Second Item */, + "Item 3" /* Comment For Third Item */, + "Item 4" /* Comment For Fourth Item - but before trailing comma */, + // Comment after last item in list. + ] /* Comment after rbracket */; + +// But if you place them after the comma at eol, they're preserved as such +let testingEndOfLineComments = + [ + "Item 1", // Comment For First Item + "Item 2", // Comment For Second Item + "Item 3", // Comment For Third Item + "Item 4" /* Comment For Fourth Item - but before trailing comma */, + // Comment after last item in list. + ] /* Comment after rbracket */ ; + + +// The space between ; and comment shoudn't matter +let testPlacementOfTrailingComment = [ + "Item 0" // + // Comment after last item in list. +]; // Comment after semi + +// The space between ; and comment shoudn't matter +let testPlacementOfTrailingComment = [ + "Item 0" // + // Comment after last item in list. +];// Comment after semi + +// Try again but without other things in the list +let testPlacementOfTrailingComment = [ + "Item 0" // +]; // Comment after semi + +// The space between ; and comment shoudn't matter +let testPlacementOfTrailingComment = [ + "Item 0" // + // Comment after last item in list. +];// Comment after semi + +let testingEndOfLineComments = [];// Comment after entire let binding + + +// The following is not yet idempotent +// let myFunction +// withFirstArg // First arg +// andSecondArg => { // Second Arg +// withFirstArg + andSecondArg /* before semi */ ; +// }; + +let myFunction + (// First arg + withFirstArg, + // Second Arg + andSecondArg) { + withFirstArg + andSecondArg +}; // After Semi + +type point = { + x: string, // x field + y: string, // y field +}; + +type pointWithManyKindsOfComments = { + // Line before x + x: string, // x field + // Line before y + y: string, // y field + // Final row of record +}; + +type typeParamPointWithComments('a) = { + // Line before x + x: 'a, // x field + // Line before y + y: 'a // y field + // Final row of record +}; + +// Now, interleaving comments in type params +type + // Type name + typeParamPointWithComments2( + // The a type param + 'a, + // The b type apram + 'b) = { + // Line before x + x: 'a, // x field + // Line before y + y: 'a // y field + // Final row of record +}; + +/* The way the last row comment is formatted is suboptimal becuase + * record type definitions do not include enough location information */ +type anotherpoint = { + x: string, // x field + y: string, // y field + // comment as last row of record +}; + +type t = (int, int); // End of line on t +type t2 = + (int, int) // End of line on (int, int) + ; + +type t3 = + (int, int); // End of line on (int, int) + + +type variant = + | X (int, int) // End of line on X + | Y (int, int) // End of line on Y +; // Comment on entire type def for variant + +// Before let +let res = + // Before switch + switch (X (2, 3)) { + // Above X line + | X(_) => "result of X" // End of arrow and X line + // Above Y line + | Y(_) => "result of Y" // End of arrow and Y line + }; // After final semi in switch + +let res = + switch (X (2, 3)) { + | X (0, 0) => // After X arrow + "result of X" // End of X body line + | X (1, 0) /* Before X's arrow */ => + "result of X" // End of X body line + | X (_) => // After X _ arrow + "result of X" // End of X body line + // Above Y line + | Y (_) => + // Comment above Y body + "result of Y" + }; + +type variant2 = + // Comment above X + | X (int, int) // End of line on X + // Comment above Y + | Y (int, int); + +type variant3 = + // Comment above X + | X (int, int) // End of line on X + // Comment above Y + | Y (int, int) // End of line on Y +; + + +type x = { // not attached *above* x + fieldOne : int, + fieldA : int +} // Attached end of line after x +and y = { // not attached *above* y + fieldTwo : int +} // Attached end of line after y +; + +type x2 = { // not attached *above* x2 + fieldOne : int, + fieldA : int +} // Attached end of line after x2 +and y2 = { // not attached *above* y2 + fieldTwo : int +}; + + +let result = + switch (None) { + | Some({fieldOne: 20, fieldA:a})=> // Where does this comment go? + let tmp = 0; + 2 + tmp + | Some {fieldOne: n, fieldA:a} => + // How about this one + let tmp = n; + n + tmp + | None => 20 + }; + +let res = + // Before switch + switch (X (2, 3)) { + // Above X line + | X(_) => "result of X" // End of arrow and X line + // Above Y line + | Y(_) => "result of Y" // End of arrow and Y line + }; + +/* + * Now these end of line comments *should* be retained. + */ +let result = switch (None) { + | Some { + fieldOne: 20, // end of line + fieldA:a // end of line + } => + let tmp = 0; + 2 + tmp + | Some { + fieldOne: n, // end of line + fieldA:a // end of line + } => + let tmp = n; + n + tmp + | None => 20 + }; + +/* + * These end of line comments *should* be retained. + * To get the simple expression eol comment to be retained, we just need to + * implement label breaking eol behavior much like we did with sequences. + * Otherwise, right now they are not idempotent. + */ +let res = + switch ( // Retain this + X (2, 3) + ) + { + // Above X line + | X ( + _, // retain this + _ // retain this + ) => "result of X" + + // Above Y line + | Y(_) => "result of Y" // End of arrow and Y line + }; + + +type optionalTuple = + | OptTup ( + option (( + int, // First int + int // Second int + )) + ); + +type optionTuple = + option (( + int, // First int + int // Second int + )); + +type intPair = ( + int, // First int + int // Second int +); + +type intPair2 = ( + // First int + int, + // Second int + int +); + +let result = { + /**/ + (+)(2,3) +}; + +// This is not yet idempotent +// { +// /**/ +// (+) 2 3 +// }; + +let a = (); +for (i in 0 to 10) { + // bla + a +}; + +if (true) { + // hello + () +}; + +type color = + | Red(int) // After red end of line + | Black(int) // After black end of line + | Green(int) // After green end of line +; // On next line after color type def + +let blahCurriedX(x) = + fun + | Red(10) + | Black(20) + | Green(10) => 1 // After or pattern green + | Red(x) => 0 // After red + | Black(x) => 0 // After black + | Green(x) => 0 // After second green +; // On next line after blahCurriedX def + +let name_equal(x,y) { x == y }; + +let equal(i1,i2) = + i1.contents === i2.contents && true; // most unlikely first + +let equal(i1,i2) = + compare(compare(0,0),compare(1,1)); // END OF LINE HERE + +let tuple_equal((i1, i2)) = i1 == i2; + +let tuple_equal((csu, mgd)) = + // Some really long comments, see https://github.com/facebook/reason/issues/811 + tuple_equal((csu, mgd)); + +/** Comments inside empty function bodies + * See https://github.com/facebook/reason/issues/860 + */ +let fun_def_comment_inline = () => { /* */ }; + +let fun_def_comment_long = () => { /* longer comment inside empty function body */}; + +let trueThing = true; + +for (i in 0 to 1) { + // comment + print_newline(); +}; + +while (trueThing) { + // comment + print_newline(); +}; + +if (trueThing) { + // comment + print_newline() +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); + // Comment before print + print_newline(); + // Comment after final print +} else { + // Comment before print + print_newline(); + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); + // Comment after final print +} else { + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before while test +while (trueThing) { + // Comment before print + print_newline(); + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before while test +while (trueThing) { + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before for test +for (i in 0 to 100) { + // Comment before print + print_newline(); + // Comment before print + print_newline(); + // Comment after final print +}; + +// Comment before for test +for (i in 0 to 100) { + // Comment before print + print_newline(); + // Comment after final print +}; + + + +if (trueThing) { + // Comment before print + print_newline(); // eol print + // Comment before print + print_newline(); // eol print + // Comment after print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); // eol print + // Comment after print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); // eol print + // Comment before print + print_newline(); // eol print + // Comment after print +} else { + // Comment before print + print_newline(); // eol print + // Comment before print + print_newline(); // eol print + // Comment after print +}; + +// Comment before if test +if (trueThing) { + // Comment before print + print_newline(); // eol print + // Comment before print +} else { + // Comment before print + print_newline(); // eol print + // Comment before print + print_newline(); // eol print + // Comment after print +}; + +// Comment before while test +while (trueThing) { + // Comment before print + print_newline(); // eol + // Comment before print + print_newline(); // eol + // Comment after final print +}; + +// Comment before while test +while (trueThing) { + // Comment before print + print_newline(); // eol + // Comment after final print +}; + +// Comment before for test +for (i in 0 to 100) { + // Comment before print + print_newline(); // eol + // Comment before print + print_newline(); // eol + // Comment after final print +}; + +// Comment before for test +for (i in 0 to 100) { + // Comment before print + print_newline(); // eol + // Comment after final print +}; + + +let f = (a, b, c, d) => a + b + c + d; + +while(trueThing) { + f( + // a + 1, + // b + 2, + // c + 3, + // d + 4 + // does work + ); +}; +while(trueThing) { + f( + // a + 1, + // b + 2, + // c + 3, + // d + 4 // does work + ); +}; + +ignore((_really, _long, _printWidth, _exceeded, _here) => { + // First comment + let x = 0; + x + x; + // Closing comment +}); + +ignore((_xxx, _yyy) => { + // First comment + let x = 0; + x + x; + // Closing comment +}); + +type tester('a, 'b) = | TwoArgsConstructor('a, 'b) | OneTupleArgConstructor(('a, 'b)); +let callFunctionTwoArgs = (a, b) => (); +let callFunctionOneTuple = (tuple) => (); + +let y = TwoArgsConstructor( + 1, //eol1 + 2 // eol2 +); + +let y = callFunctionTwoArgs( + 1, //eol1 + 2 // eol2 +); + +let y = OneTupleArgConstructor(( + 1, //eol1 + 2 // eol2 +)); + +let y = callFunctionOneTuple(( + 1, //eol1 + 2 // eol2 +)); + + +type polyRecord('a, 'b) = {fieldOne: 'a, fieldTwo: 'b}; + +let r = { + fieldOne: 1, //eol1 + fieldTwo: 2 // eol2 +}; + +let r = { + fieldOne: 1, //eol1 + fieldTwo: 2, // eol2 with trailing comma +}; + + +let y = TwoArgsConstructor( + "1", //eol1 + "2" // eol2 +); + +let y = callFunctionTwoArgs( + "1", //eol1 + "2" // eol2 +); + +let y = OneTupleArgConstructor(( + "1", //eol1 + "2" // eol2 +)); + +let y = callFunctionOneTuple(( + "1", //eol1 + "2" // eol2 +)); + + +let r = { + fieldOne: "1", //eol1 + fieldTwo: "2" // eol2 +}; + +let r = { + fieldOne: "1", //eol1 + fieldTwo: "2", // eol2 with trailing comma +}; + +let identifier = "hello"; + +let y = TwoArgsConstructor( + identifier, //eol1 + identifier // eol2 +); + +let y = callFunctionTwoArgs( + identifier , //eol1 + identifier // eol2 +); + +let y = OneTupleArgConstructor(( + identifier , //eol1 + identifier // eol2 +)); + +let y = callFunctionOneTuple(( + identifier , //eol1 + identifier // eol2 +)); + + +let r = { + fieldOne: identifier, //eol1 + fieldTwo: identifier // eol2 +}; + +let r = { + fieldOne: identifier, //eol1 + fieldTwo: identifier, // eol2 with trailing comma +}; + + +let y = TwoArgsConstructor( + identifier : string, //eol1 + identifier : string// eol2 +); + +let y = callFunctionTwoArgs( + identifier : string , //eol1 + identifier : string // eol2 +); + +let y = OneTupleArgConstructor(( + identifier : string , //eol1 + identifier : string // eol2 +)); + +let y = callFunctionOneTuple(( + identifier : string , //eol1 + identifier : string // eol2 +)); + + +let r = { + fieldOne: (identifier : string), //eol1 + fieldTwo: (identifier : string) // eol2 +}; + +let r = { + fieldOne: (identifier : string), //eol1 + fieldTwo: (identifier : string), // eol2 with trailing comma +}; + +// whitespace interleaving + +// comment1 +// comment2 + +// whitespace above & below + +let r = { + fieldOne: (identifier : string), //eol1 + // c1 + + // c2 + + // c3 + // c4 + + // c5 + fieldTwo: (identifier : string), // eol2 with trailing comma +}; +// trailing + +// trailing whitespace above +// attach + +// last comment diff --git a/src/reason-parser/reason_comment.ml b/src/reason-parser/reason_comment.ml index b29ca1748..f8646b138 100644 --- a/src/reason-parser/reason_comment.ml +++ b/src/reason-parser/reason_comment.ml @@ -37,6 +37,9 @@ let dump_list ppf list = let wrap t = match t.text with | "" | "*" -> "/***/" + | txt when Reason_syntax_util.isLineComment txt -> + (* single line comments of the form `// comment` have a `\n` at the end *) + "//" ^ (String.sub txt 0 (String.length txt - 1)) | txt when txt.[0] = '*' && txt.[1] <> '*' -> "/**" ^ txt ^ "*/" | txt -> "/*" ^ txt ^ "*/" diff --git a/src/reason-parser/reason_lexer.mll b/src/reason-parser/reason_lexer.mll index ebf81c21c..0aa974120 100644 --- a/src/reason-parser/reason_lexer.mll +++ b/src/reason-parser/reason_lexer.mll @@ -207,7 +207,7 @@ let set_lexeme_length buf n = ( ) (* This cut comment characters of the current buffer. - * Operators (including "/*" and "*/") are lexed with the same rule, and this + * Operators (including "/*" and "//") are lexed with the same rule, and this * function cuts the lexeme at the beginning of an operator. *) let lexeme_without_comment buf = ( let lexeme = Lexing.lexeme buf in @@ -215,7 +215,7 @@ let lexeme_without_comment buf = ( let found = ref (-1) in while !i < len && !found = -1 do begin match lexeme.[!i], lexeme.[!i+1] with - | ('/', '*') | ('*', '/') -> + | ('/', '*') | ('/', '/') | ('*', '/') -> found := !i; | _ -> () end; @@ -664,6 +664,24 @@ rule token = parse } and enter_comment = parse + | "//" ([^'\010']* newline as line) + { update_loc lexbuf None 1 false 0; + let physical_loc = Location.curr lexbuf in + let location = { physical_loc with + loc_end = { physical_loc.loc_end with + (* Don't track trailing `\n` in the location + * 1| // comment + * 2| let x = 1; + * By omitting the `\n` at the end of line 1, the location of the + * comment spans line 1. Otherwise the comment on line 1 would end + * on the second line. The printer looks at the closing pos_lnum + * location to interleave whitespace correct. It needs to align + * with what we visually see (i.e. it ends on line 1) *) + pos_lnum = physical_loc.loc_end.pos_lnum - 1; + pos_cnum = physical_loc.loc_end.pos_cnum + 1; + }} in + COMMENT (line, location) + } | "/*" ("*" "*"+)? { set_lexeme_length lexbuf 2; let start_loc = Location.curr lexbuf in diff --git a/src/reason-parser/reason_syntax_util.ml b/src/reason-parser/reason_syntax_util.ml index 32cc67c90..e4ade2162 100644 --- a/src/reason-parser/reason_syntax_util.ml +++ b/src/reason-parser/reason_syntax_util.ml @@ -303,6 +303,12 @@ let processLineEndingsAndStarts str = |> String.concat "\n" |> String.trim +let isLineComment str = + (* true iff the first \n is the last character *) + match String.index str '\n' with + | exception Not_found -> false + | n -> n = String.length str - 1 + (** Generate a suitable extension node for Merlin's consumption, for the purposes of reporting a syntax error - only used in recovery mode. diff --git a/src/reason-parser/reason_syntax_util.mli b/src/reason-parser/reason_syntax_util.mli index 8b318833b..231299da0 100644 --- a/src/reason-parser/reason_syntax_util.mli +++ b/src/reason-parser/reason_syntax_util.mli @@ -32,6 +32,8 @@ val split_by : ?keep_empty:bool -> (char -> bool) -> string -> string list val processLineEndingsAndStarts : string -> string +val isLineComment : string -> bool + val syntax_error_extension_node : Ast_404.Location.t -> string -> string Ast_404.Location.loc * Ast_404.Parsetree.payload diff --git a/src/reason-parser/reason_toolchain.ml b/src/reason-parser/reason_toolchain.ml index 1cc5b5eda..6929b49ed 100644 --- a/src/reason-parser/reason_toolchain.ml +++ b/src/reason-parser/reason_toolchain.ml @@ -210,9 +210,19 @@ module Create_parse_entrypoint (Toolchain_impl: Toolchain_spec) :Toolchain = str | [] -> [] | hd :: tl -> ( let classifiedTail = classifyAndNormalizeComments tl in - let (_, physical_loc) = hd in + let (txt, physical_loc) = hd in (* When searching for "^" regexp, returns location of newline + 1 *) - let (stop_char, eol_start, virtual_start_pos) = left_expand_comment false contents physical_loc.loc_start.pos_cnum in + let (stop_char, eol_start, virtual_start_pos) = + left_expand_comment false contents physical_loc.loc_start.pos_cnum + in + if Reason_syntax_util.isLineComment txt then + let comment = Comment.make + ~location:physical_loc + (if eol_start then SingleLine else EndOfLine) + txt + in + comment :: classifiedTail + else let one_char_before_stop_char = if virtual_start_pos <= 1 then ' ' From ffeb8ad50641b915df43e6027a74cbfaac7077d6 Mon Sep 17 00:00:00 2001 From: Iwan Date: Sun, 11 Nov 2018 11:19:30 +0100 Subject: [PATCH 2/2] Fix edge case where // inside a sequence wrapped in braces didn't break. ```reason let x = { // }; ``` If the { }-sequence didn't break we would get parse error: ```reason let x = { // }; ``` --- .../unit_tests/expected_output/lineComments.re | 4 ++++ formatTest/unit_tests/input/lineComments.re | 4 ++++ src/reason-parser/reason_comment.ml | 4 ++++ src/reason-parser/reason_pprint_ast.ml | 17 ++++++++++++++++- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/formatTest/unit_tests/expected_output/lineComments.re b/formatTest/unit_tests/expected_output/lineComments.re index e909250cc..f1fc0d8d5 100644 --- a/formatTest/unit_tests/expected_output/lineComments.re +++ b/formatTest/unit_tests/expected_output/lineComments.re @@ -353,6 +353,10 @@ let tuple_equal = ((csu, mgd)) => */ let fun_def_comment_inline = () => {/* */}; +let fun_def_comment_newline = () => { + // +}; + let fun_def_comment_long = () => { /* longer comment inside empty function body */ }; diff --git a/formatTest/unit_tests/input/lineComments.re b/formatTest/unit_tests/input/lineComments.re index ce619d579..a04275cce 100644 --- a/formatTest/unit_tests/input/lineComments.re +++ b/formatTest/unit_tests/input/lineComments.re @@ -361,6 +361,10 @@ let tuple_equal((csu, mgd)) = */ let fun_def_comment_inline = () => { /* */ }; +let fun_def_comment_newline = () => { + // +}; + let fun_def_comment_long = () => { /* longer comment inside empty function body */}; let trueThing = true; diff --git a/src/reason-parser/reason_comment.ml b/src/reason-parser/reason_comment.ml index f8646b138..6b9ffadd8 100644 --- a/src/reason-parser/reason_comment.ml +++ b/src/reason-parser/reason_comment.ml @@ -48,3 +48,7 @@ let is_doc t = let make ~location category text = { text; category; location } + +let isLineComment {category; text} = match category with + | SingleLine -> Reason_syntax_util.isLineComment text + | EndOfLine | Regular -> false diff --git a/src/reason-parser/reason_pprint_ast.ml b/src/reason-parser/reason_pprint_ast.ml index cd7843839..32ef06514 100644 --- a/src/reason-parser/reason_pprint_ast.ml +++ b/src/reason-parser/reason_pprint_ast.ml @@ -1487,7 +1487,22 @@ let rec insertSingleLineComment layout comment = | Easy _ -> prependSingleLineComment comment layout | Sequence (listConfig, subLayouts) when subLayouts == [] -> - (* If there are no subLayouts (empty body), create a Sequence of just the comment *) + (* If there are no subLayouts (empty body), create a Sequence of just the + * comment. We need to be careful when the empty body contains a //-style + * comment. Example: + * let make = () => { + * // + * }; + * It is clear that the sequence needs to always break here, otherwise + * we get a parse error: let make = () => { // }; + * The closing brace and semicolon `};` would become part of the comment… + *) + let listConfig = + if Reason_comment.isLineComment comment then + {listConfig with break = Always_rec} + else + listConfig + in Sequence (listConfig, [formatComment comment]) | Sequence (listConfig, subLayouts) -> let (beforeComment, afterComment) =