From b1d49c39e4ebf3d90ee60eec2d724890a25a8e41 Mon Sep 17 00:00:00 2001 From: Spotandjake Date: Mon, 1 Jan 2024 21:17:54 -0500 Subject: [PATCH 1/4] feat(graindoc)!: Allow `@since` and `@returns` once per export --- compiler/graindoc/docblock.re | 60 +++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/compiler/graindoc/docblock.re b/compiler/graindoc/docblock.re index ccbcd02938..fd48aee6a4 100644 --- a/compiler/graindoc/docblock.re +++ b/compiler/graindoc/docblock.re @@ -79,6 +79,7 @@ exception exception MissingParamType({name: string}); exception MissingReturnType; +exception AttributeAppearsMultipleTimes({attr: string}); exception InvalidAttribute({ name: string, @@ -106,6 +107,10 @@ let () = | MissingReturnType => let msg = "Unable to find a return type. Please file an issue!"; Some(msg); + | AttributeAppearsMultipleTimes({attr}) => + let msg = + Printf.sprintf("Attribute @%s is only allowed to appear once.", attr); + Some(msg); | InvalidAttribute({name, attr}) => let msg = Printf.sprintf("Invalid attribute @%s on %s", attr, name); Some(msg); @@ -272,16 +277,18 @@ let for_value_description = examples, ) | Since({attr_version}) => - // TODO(#787): Should we fail if more than one `@since` attribute? - ( - deprecations, - Some({since_version: attr_version}), - history, - params, - returns, - throws, - examples, - ) + switch (since) { + | Some(_) => raise(AttributeAppearsMultipleTimes({attr: "since"})) + | None => ( + deprecations, + Some({since_version: attr_version}), + history, + params, + returns, + throws, + examples, + ) + } | History({attr_version: history_version, attr_desc: history_msg}) => ( deprecations, since, @@ -309,20 +316,25 @@ let for_value_description = examples, ); | Returns({attr_desc: returns_msg}) => - let returns_type = - switch (return_type) { - | Some(typ) => Printtyp.string_of_type_sch(typ) - | None => raise(MissingReturnType) - }; - ( - deprecations, - since, - history, - params, - Some({returns_msg, returns_type}), - throws, - examples, - ); + switch (returns) { + | Some(_) => + raise(AttributeAppearsMultipleTimes({attr: "returns"})) + | None => + let returns_type = + switch (return_type) { + | Some(typ) => Printtyp.string_of_type_sch(typ) + | None => raise(MissingReturnType) + }; + ( + deprecations, + since, + history, + params, + Some({returns_msg, returns_type}), + throws, + examples, + ); + } | Throws({attr_type: throw_type, attr_desc: throw_msg}) => ( deprecations, since, From b771913c6d9d26ab73e0bc724b7ce101ced083ce Mon Sep 17 00:00:00 2001 From: Spotandjake Date: Tue, 2 Jan 2024 20:19:41 -0500 Subject: [PATCH 2/4] chore: Only allow one `since` on types and functions --- compiler/graindoc/docblock.re | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/compiler/graindoc/docblock.re b/compiler/graindoc/docblock.re index fd48aee6a4..f46b7c6213 100644 --- a/compiler/graindoc/docblock.re +++ b/compiler/graindoc/docblock.re @@ -401,13 +401,15 @@ let for_type_declaration = examples, ) | Since({attr_version}) => - // TODO(#787): Should we fail if more than one `@since` attribute? - ( - deprecations, - Some({since_version: attr_version}), - history, - examples, - ) + switch (since) { + | Some(_) => raise(AttributeAppearsMultipleTimes({attr: "since"})) + | None => ( + deprecations, + Some({since_version: attr_version}), + history, + examples, + ) + } | History({attr_version: history_version, attr_desc: history_msg}) => ( deprecations, since, @@ -528,13 +530,15 @@ and for_signature_items = examples, ) | Since({attr_version}) => - // TODO(#787): Should we fail if more than one `@since` attribute? - ( - deprecations, - Some({since_version: attr_version}), - history, - examples, - ) + switch (since) { + | Some(_) => raise(AttributeAppearsMultipleTimes({attr: "since"})) + | None => ( + deprecations, + Some({since_version: attr_version}), + history, + examples, + ) + } | History({attr_version: history_version, attr_desc: history_msg}) => ( deprecations, since, From 878044b80ff4120e158a712484b25a4722d0360b Mon Sep 17 00:00:00 2001 From: Spotandjake Date: Wed, 24 Jan 2024 14:25:43 -0500 Subject: [PATCH 3/4] chore: Add tests --- compiler/test/graindoc/singleReturn.input.gr | 7 +++++++ compiler/test/graindoc/singleSince.input.gr | 7 +++++++ compiler/test/runner.re | 11 +++++++++++ compiler/test/suites/graindoc.re | 13 +++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 compiler/test/graindoc/singleReturn.input.gr create mode 100644 compiler/test/graindoc/singleSince.input.gr diff --git a/compiler/test/graindoc/singleReturn.input.gr b/compiler/test/graindoc/singleReturn.input.gr new file mode 100644 index 0000000000..177bcef778 --- /dev/null +++ b/compiler/test/graindoc/singleReturn.input.gr @@ -0,0 +1,7 @@ +module SingleReturn + +/** + * @returns first return + * @returns second return + */ +provide let test = () => 1 diff --git a/compiler/test/graindoc/singleSince.input.gr b/compiler/test/graindoc/singleSince.input.gr new file mode 100644 index 0000000000..e72d563388 --- /dev/null +++ b/compiler/test/graindoc/singleSince.input.gr @@ -0,0 +1,7 @@ +module SingleSince + +/** + * @since v1.0.0 + * @since v1.0.0 + */ +provide let test = () => print("t") diff --git a/compiler/test/runner.re b/compiler/test/runner.re index d7060f5499..2479f8cd01 100644 --- a/compiler/test/runner.re +++ b/compiler/test/runner.re @@ -487,3 +487,14 @@ let makeGrainDocRunner = (test, name, filename, arguments) => { }, ); }; + +let makeGrainDocErrorRunner = (test, name, filename, expected, arguments) => { + test( + name, + ({expect}) => { + let infile = gaindoc_in_file(filename); + let (result, _) = doc(infile, arguments); + expect.string(result).toMatch(expected); + }, + ); +}; \ No newline at end of file diff --git a/compiler/test/suites/graindoc.re b/compiler/test/suites/graindoc.re index 5b34c96fed..6c2e28e852 100644 --- a/compiler/test/suites/graindoc.re +++ b/compiler/test/suites/graindoc.re @@ -9,6 +9,7 @@ describe("graindoc", ({test, testSkip}) => { Sys.backend_type == Other("js_of_ocaml") ? testSkip : test; let assertGrianDocOutput = makeGrainDocRunner(test_or_skip); + let assertGrainDocError = makeGrainDocErrorRunner(test_or_skip); (); assertGrianDocOutput("noDoc", "noDoc", [||]); assertGrianDocOutput( @@ -18,4 +19,16 @@ describe("graindoc", ({test, testSkip}) => { ); assertGrianDocOutput("since", "since", [|"--current-version=v0.2.0"|]); assertGrianDocOutput("example", "example", [|"--current-version=v0.2.0"|]); + assertGrainDocError( + "singleSince", + "singleSince", + "Attribute @since is only allowed to appear once.", + [|"--current-version=v0.2.0"|], + ); + assertGrainDocError( + "singleReturn", + "singleReturn", + "Attribute @returns is only allowed to appear once.", + [|"--current-version=v0.2.0"|], + ); }); From db22073db67f5ee7a28cdf423c54dfc3335bb19a Mon Sep 17 00:00:00 2001 From: Spotandjake Date: Wed, 24 Jan 2024 15:38:14 -0500 Subject: [PATCH 4/4] chore: format compiler --- compiler/test/runner.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test/runner.re b/compiler/test/runner.re index 2479f8cd01..bec81fe4f8 100644 --- a/compiler/test/runner.re +++ b/compiler/test/runner.re @@ -497,4 +497,4 @@ let makeGrainDocErrorRunner = (test, name, filename, expected, arguments) => { expect.string(result).toMatch(expected); }, ); -}; \ No newline at end of file +};