diff --git a/compiler/graindoc/docblock.re b/compiler/graindoc/docblock.re index d9ee48f4af..add4cac2fa 100644 --- a/compiler/graindoc/docblock.re +++ b/compiler/graindoc/docblock.re @@ -3,12 +3,72 @@ open Grain_typed; open Grain_utils; open Grain_diagnostics; -type t = { - module_name: string, - name: string, - type_sig: string, - description: option(string), - attributes: Comments.Attribute.attributes, +type param = { + param_name: string, + param_type: string, + param_msg: string, +}; + +type since = {since_version: string}; + +type history = { + history_version: string, + history_msg: string, +}; + +type returns = { + returns_type: string, + returns_msg: string, +}; + +type deprecation = {deprecation_msg: string}; + +type throw = { + throw_type: string, + throw_msg: string, +}; + +type example = {example_txt: string}; + +type t = + | Type({ + module_namespace: option(string), + name: string, + type_sig: string, + description: option(string), + deprecations: list(deprecation), + since: option(since), + history: list(history), + examples: list(example), + }) + | Value({ + module_namespace: option(string), + name: string, + type_sig: string, + description: option(string), + deprecations: list(deprecation), + since: option(since), + history: list(history), + params: list(param), + returns: option(returns), + throws: list(throw), + examples: list(example), + }) + | Module({ + module_namespace: option(string), + name: string, + description: option(string), + deprecations: list(deprecation), + since: option(since), + history: list(history), + examples: list(example), + provided, + }) + +and provided = { + provided_types: list(t), + provided_values: list(t), + provided_modules: list(t), }; exception @@ -17,6 +77,14 @@ exception attr: string, }); +exception MissingParamType({name: string}); +exception MissingReturnType; +exception + InvalidAttribute({ + name: string, + attr: string, + }); + let () = Printexc.register_printer(exn => { switch (exn) { @@ -28,6 +96,19 @@ let () = attr, ); Some(msg); + | MissingParamType({name}) => + let msg = + Printf.sprintf( + "Unable to find a type for %s. Did you specify too many @param attributes?", + name, + ); + Some(msg); + | MissingReturnType => + let msg = "Unable to find a return type. Please file an issue!"; + Some(msg); + | InvalidAttribute({name, attr}) => + let msg = Printf.sprintf("Invalid attribute @%s on %s", attr, name); + Some(msg); | _ => None } }); @@ -81,7 +162,8 @@ let enumerate_provides = program => { }, vbinds, ) - | TTopModule(_) + | TTopModule({tmod_id, tmod_loc}) => + id_tbl := Ident.add(tmod_id, tmod_loc, id_tbl^) | TTopInclude(_) | TTopException(_) | TTopExpr(_) => () @@ -99,25 +181,36 @@ let location_for_ident = snd(Ident.find_name(Ident.name(ident), provides)); }; -let title_for_api = (~module_name, ident: Ident.t) => { - Format.asprintf("%s.**%a**", module_name, Printtyp.ident, ident); +let title_for_api = (~module_namespace, name) => { + switch (module_namespace) { + | Some(module_namespace) => + Format.sprintf("%s.**%s**", module_namespace, name) + | None => name + }; }; -let output_for_since = (~current_version, attr_version) => { +let title_for_namepace = (~module_namespace, name) => { + switch (module_namespace) { + | Some(module_namespace) => Format.sprintf("%s.%s", module_namespace, name) + | None => name + }; +}; + +let output_for_since = (~current_version, {since_version}) => { let current_version = switch (current_version) { | Some(version) => version | None => raise(MissingFlag({flag: "--current-version", attr: "@since"})) }; let (<) = Version.String.less_than; - if (current_version < attr_version) { + if (current_version < since_version) { Format.sprintf("Added in %s", Html.code("next")); } else { - Format.sprintf("Added in %s", Html.code(attr_version)); + Format.sprintf("Added in %s", Html.code(since_version)); }; }; -let output_for_history = (~current_version, attr_version, attr_desc) => { +let output_for_history = (~current_version, {history_version, history_msg}) => { let current_version = switch (current_version) { | Some(version) => version @@ -125,13 +218,61 @@ let output_for_history = (~current_version, attr_version, attr_desc) => { raise(MissingFlag({flag: "--current-version", attr: "@history"})) }; let (<) = Version.String.less_than; - if (current_version < attr_version) { - [Html.code("next"), attr_desc]; + if (current_version < history_version) { + [Html.code("next"), history_msg]; } else { - [Html.code(attr_version), attr_desc]; + [Html.code(history_version), history_msg]; }; }; +let output_for_params = params => { + Markdown.table( + ~headers=["param", "type", "description"], + List.map( + ({param_name, param_type, param_msg}) => { + [Markdown.code(param_name), Markdown.code(param_type), param_msg] + }, + params, + ), + ); +}; + +let output_for_returns = ({returns_type, returns_msg}) => { + Markdown.table( + ~headers=["type", "description"], + // Returns is only 1 item but we want to put it in a table, so we wrap in an outer list + [[Markdown.code(returns_type), returns_msg]], + ); +}; + +let output_for_throws = throws => { + // Used for joining multiple `@throws` annotations with the exact same type + module StringMap = Map.Make(String); + + List.fold_left( + (map, {throw_type, throw_msg}) => { + StringMap.update( + throw_type, + descs => { + switch (descs) { + | None => Some([throw_msg]) + | Some(descs) => Some([throw_msg, ...descs]) + } + }, + map, + ) + }, + StringMap.empty, + throws, + ) + |> StringMap.bindings + |> List.map(((exception_type, exception_descriptions)) => { + Markdown.paragraph(Markdown.code(exception_type)) + ++ Markdown.bullet_list(List.rev(exception_descriptions)) + }) + |> String.concat(""); +}; + let types_for_function = (~ident, vd: Types.value_description) => { switch (Ctype.repr(vd.val_type).desc) { | TTyArrow(args, returns, _) => (Some(args), Some(returns)) @@ -147,12 +288,12 @@ let for_value_description = ( ~comments, ~provides, - ~module_name, + ~module_namespace, ~ident: Ident.t, vd: Types.value_description, ) => { let loc = location_for_ident(~provides, ident); - let name = title_for_api(~module_name, ident); + let name = Format.asprintf("%a", Printtyp.ident, ident); let type_sig = Printtyp.string_of_value_description(~ident, vd); let comment = Comments.Doc.ending_on(~lnum=loc.loc_start.pos_lnum - 1, comments); @@ -163,220 +304,576 @@ let for_value_description = | None => (None, []) }; - let (args, returns) = types_for_function(~ident, vd); - // This replaces the default `None` for `attr_type` on `Param` and `Returns` attributes - let apply_types: (int, Comment_attributes.t) => Comment_attributes.t = - (idx, attr) => { - switch (attr) { - | Param(attr) => - let attr_type = - lookup_type_expr(~idx, args) - |> Option.map(Printtyp.string_of_type_sch); - Param({...attr, attr_type}); - | Returns(attr) => - let attr_type = Option.map(Printtyp.string_of_type_sch, returns); - Returns({...attr, attr_type}); - | _ => attr - }; - }; - let attributes = List.mapi(apply_types, attributes); + let (arg_types, return_type) = types_for_function(~ident, vd); - {module_name, name, type_sig, description, attributes}; + let (deprecations, since, history, params, returns, throws, examples) = + List.fold_left( + ( + (deprecations, since, history, params, returns, throws, examples), + attr: Comment_attributes.t, + ) => { + switch (attr) { + | Deprecated({attr_desc}) => ( + [{deprecation_msg: attr_desc}, ...deprecations], + since, + history, + params, + returns, + throws, + 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, + ) + | History({attr_version: history_version, attr_desc: history_msg}) => ( + deprecations, + since, + [{history_version, history_msg}, ...history], + params, + returns, + throws, + examples, + ) + | Param({attr_name: param_name, attr_desc: param_msg}) => + // TODO: Use label lookups when labeled parameters are introduced + let param_type = + switch (lookup_type_expr(~idx=List.length(params), arg_types)) { + | Some(typ) => Printtyp.string_of_type_sch(typ) + | None => raise(MissingParamType({name: param_name})) + }; + + ( + deprecations, + since, + history, + [{param_name, param_type, param_msg}, ...params], + returns, + throws, + 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, + ); + | Throws({attr_type: throw_type, attr_desc: throw_msg}) => ( + deprecations, + since, + history, + params, + returns, + [{throw_type, throw_msg}, ...throws], + examples, + ) + | Example({attr_desc}) => ( + deprecations, + since, + history, + params, + returns, + throws, + [{example_txt: attr_desc}, ...examples], + ) + } + }, + // deprecations, since, history, params, returns, throws, examples + ([], None, [], [], None, [], []), + attributes, + ); + + Value({ + module_namespace, + name, + type_sig, + description, + deprecations: List.rev(deprecations), + since, + history: List.rev(history), + params: List.rev(params), + returns, + throws: List.rev(throws), + examples: List.rev(examples), + }); }; let for_type_declaration = ( ~comments, ~provides, - ~module_name, + ~module_namespace, ~ident: Ident.t, td: Types.type_declaration, ) => { let loc = location_for_ident(~provides, ident); - let name = title_for_api(~module_name, ident); + let name = Format.asprintf("%a", Printtyp.ident, ident); let type_sig = Printtyp.string_of_type_declaration(~ident, td); let comment = Comments.Doc.ending_on(~lnum=loc.loc_start.pos_lnum - 1, comments); let (description, attributes) = switch (comment) { - | Some((_, description, _)) => (description, []) + | Some((_, description, attributes)) => (description, attributes) | None => (None, []) }; - {module_name, name, type_sig, description, attributes}; + let (deprecations, since, history, examples) = + List.fold_left( + ((deprecations, since, history, examples), attr: Comment_attributes.t) => { + switch (attr) { + | Deprecated({attr_desc}) => ( + [{deprecation_msg: attr_desc}, ...deprecations], + since, + history, + examples, + ) + | Since({attr_version}) => + // TODO(#787): Should we fail if more than one `@since` attribute? + ( + deprecations, + Some({since_version: attr_version}), + history, + examples, + ) + | History({attr_version: history_version, attr_desc: history_msg}) => ( + deprecations, + since, + [{history_version, history_msg}, ...history], + examples, + ) + | Param({attr_name: param_name, attr_desc: param_msg}) => + raise(InvalidAttribute({name, attr: "param"})) + | Returns({attr_desc: returns_msg}) => + raise(InvalidAttribute({name, attr: "returns"})) + | Throws({attr_type: throw_type, attr_desc: throw_msg}) => + raise(InvalidAttribute({name, attr: "throws"})) + | Example({attr_desc}) => ( + deprecations, + since, + history, + [{example_txt: attr_desc}, ...examples], + ) + } + }, + // deprecations, since, history, examples + ([], None, [], []), + attributes, + ); + + Type({ + module_namespace, + name, + type_sig, + description, + deprecations: List.rev(deprecations), + since, + history: List.rev(history), + examples: List.rev(examples), + }); }; -// Used for joining multiple `@throws` annotations with the exact same type -module StringMap = Map.Make(String); +let rec traverse_signature_items = + (~comments, ~provides, ~module_namespace, signature_items) => { + let {provided_types, provided_values, provided_modules} = + List.fold_left( + ( + {provided_types, provided_values, provided_modules}, + sig_item: Types.signature_item, + ) => { + switch (sig_item) { + | TSigType(ident, td, _) => + let docblock = + for_type_declaration( + ~comments, + ~provides, + ~module_namespace, + ~ident, + td, + ); + { + provided_types: [docblock, ...provided_types], + provided_values, + provided_modules, + }; + | TSigValue(ident, vd) => + let docblock = + for_value_description( + ~comments, + ~provides, + ~module_namespace, + ~ident, + vd, + ); + { + provided_types, + provided_values: [docblock, ...provided_values], + provided_modules, + }; + | TSigModule(ident, {md_type: TModSignature(signature_items)}, _) => + let loc = location_for_ident(~provides, ident); + let name = Format.asprintf("%a", Printtyp.ident, ident); + let docblock = + for_signature_items( + ~comments, + ~provides, + ~module_namespace, + ~name, + ~loc, + signature_items, + ); + { + provided_types, + provided_values, + provided_modules: [docblock, ...provided_modules], + }; + | TSigTypeExt(_) + | TSigModType(_) + | TSigModule(_) => {provided_types, provided_values, provided_modules} + } + }, + {provided_types: [], provided_values: [], provided_modules: []}, + signature_items, + ); + + { + provided_types: List.rev(provided_types), + provided_values: List.rev(provided_values), + provided_modules: List.rev(provided_modules), + }; +} +and for_signature_items = + ( + ~comments, + ~provides, + ~module_namespace, + ~name, + ~loc: Grain_parsing.Location.t, + signature_items, + ) => { + let comment = + Comments.Doc.ending_on(~lnum=loc.loc_start.pos_lnum - 1, comments); + + let (description, attributes) = + switch (comment) { + | Some((_, description, attributes)) => (description, attributes) + | None => (None, []) + }; + + let (deprecations, since, history, examples) = + List.fold_left( + ((deprecations, since, history, examples), attr: Comment_attributes.t) => { + switch (attr) { + | Deprecated({attr_desc}) => ( + [{deprecation_msg: attr_desc}, ...deprecations], + since, + history, + examples, + ) + | Since({attr_version}) => + // TODO(#787): Should we fail if more than one `@since` attribute? + ( + deprecations, + Some({since_version: attr_version}), + history, + examples, + ) + | History({attr_version: history_version, attr_desc: history_msg}) => ( + deprecations, + since, + [{history_version, history_msg}, ...history], + examples, + ) + | Param({attr_name: param_name, attr_desc: param_msg}) => + raise(InvalidAttribute({name, attr: "param"})) + | Returns({attr_desc: returns_msg}) => + raise(InvalidAttribute({name, attr: "returns"})) + | Throws({attr_type: throw_type, attr_desc: throw_msg}) => + raise(InvalidAttribute({name, attr: "throws"})) + | Example({attr_desc}) => ( + deprecations, + since, + history, + [{example_txt: attr_desc}, ...examples], + ) + } + }, + // deprecations, since, history, examples + ([], None, [], []), + attributes, + ); + + let provided = + switch (signature_items) { + | [] => {provided_types: [], provided_values: [], provided_modules: []} + | _ => + let namespace = title_for_namepace(~module_namespace, name); + + traverse_signature_items( + ~comments, + ~provides, + ~module_namespace=Some(namespace), + signature_items, + ); + }; -let to_markdown = (~current_version, docblock) => { + Module({ + module_namespace, + name, + description, + deprecations: List.rev(deprecations), + since, + history: List.rev(history), + examples: List.rev(examples), + provided, + }); +}; + +let rec to_markdown = (~current_version, ~heading_level, docblock) => { let buf = Buffer.create(0); - Buffer.add_string(buf, Markdown.heading(~level=3, docblock.name)); - let deprecations = - docblock.attributes - |> List.filter(Comments.Attribute.is_deprecated) - |> List.map((attr: Comment_attributes.t) => { - switch (attr) { - | Deprecated({attr_desc}) => attr_desc - | _ => - failwith( - "Unreachable: Non-`deprecated` attribute can't exist here.", - ) - } - }); - if (List.length(deprecations) > 0) { + + let next_heading_level = heading_level + 1; + + switch (docblock) { + | Type({name, module_namespace}) + | Value({name, module_namespace}) => + Buffer.add_string( + buf, + Markdown.heading( + ~level=next_heading_level, + title_for_api(~module_namespace, name), + ), + ) + | Module({name, module_namespace: Some(_) as module_namespace}) => + Buffer.add_string( + buf, + Markdown.heading( + ~level=heading_level, + title_for_namepace(~module_namespace, name), + ), + ) + | Module(_) => () // No title for top-level modules + }; + + switch (docblock) { + | Type({deprecations: []}) + | Value({deprecations: []}) + | Module({deprecations: []}) => () + | Type({deprecations}) + | Value({deprecations}) + | Module({deprecations}) => List.iter( - msg => + ({deprecation_msg}) => Buffer.add_string( buf, - Markdown.blockquote(Markdown.bold("Deprecated:") ++ " " ++ msg), + Markdown.blockquote( + Markdown.bold("Deprecated:") ++ " " ++ deprecation_msg, + ), ), deprecations, - ); + ) + }; + + switch (docblock) { + // Type and Value descriptions are printed after signature, etc + | Type(_) + | Value(_) + | Module({description: None}) => () + | Module({description: Some(desc)}) => + Buffer.add_string(buf, Markdown.paragraph(desc)) }; - // TODO(#787): Should we fail if more than one `@since` attribute? - let since_attr = - docblock.attributes - |> List.find_opt(Comments.Attribute.is_since) - |> Option.map((attr: Comment_attributes.t) => { - switch (attr) { - | Since({attr_version}) => - output_for_since(~current_version, attr_version) - | _ => - failwith("Unreachable: Non-`since` attribute can't exist here.") - } - }); - let history_attrs = - docblock.attributes - |> List.filter(Comments.Attribute.is_history) - |> List.map((attr: Comment_attributes.t) => { - switch (attr) { - | History({attr_version, attr_desc}) => - output_for_history(~current_version, attr_version, attr_desc) - | _ => - failwith("Unreachable: Non-`since` attribute can't exist here.") - } - }); - if (Option.is_some(since_attr) || List.length(history_attrs) > 0) { - let summary = Option.value(~default="History", since_attr); - let disabled = List.length(history_attrs) == 0 ? true : false; + + switch (docblock) { + | Type({since: None, history: []}) + | Value({since: None, history: []}) + | Module({since: None, history: []}) => () + | Type({since, history}) + | Value({since, history}) + | Module({since, history}) => + let summary = + Option.fold( + ~none="History", + ~some=output_for_since(~current_version), + since, + ); + let disabled = + switch (history) { + | [] => true + | _ => false + }; let details = - if (List.length(history_attrs) == 0) { - "No other changes yet."; - } else { - Html.table(~headers=["version", "changes"], history_attrs); + switch (history) { + | [] => "No other changes yet." + | _ => + Html.table( + ~headers=["version", "changes"], + List.map(output_for_history(~current_version), history), + ) }; Buffer.add_string(buf, Html.details(~disabled, ~summary, details)); }; - Buffer.add_string(buf, Markdown.code_block(docblock.type_sig)); - switch (docblock.description) { + + switch (docblock) { + | Module(_) => () + | Type({type_sig}) + | Value({type_sig}) => + Buffer.add_string(buf, Markdown.code_block(type_sig)) + }; + + switch (docblock) { + | Type({description: None}) + | Value({description: None}) + // Module description comes first + | Module(_) => () // Guard isn't be needed because we turn an empty string into None during extraction - | Some(description) => - Buffer.add_string(buf, Markdown.paragraph(description)) - | None => () + | Type({description: Some(desc)}) + | Value({description: Some(desc)}) => + Buffer.add_string(buf, Markdown.paragraph(desc)) }; - let params = - docblock.attributes - |> List.filter(Comments.Attribute.is_param) - |> List.map((attr: Comment_attributes.t) => { - switch (attr) { - | Param({attr_name, attr_type, attr_desc}) => [ - Markdown.code(attr_name), - Option.fold(~none="", ~some=Markdown.code, attr_type), - attr_desc, - ] - | _ => - failwith("Unreachable: Non-`param` attribute can't exist here.") - } - }); - if (List.length(params) > 0) { + + switch (docblock) { + | Type(_) + | Value({params: []}) + | Module(_) => () + | Value({params}) => Buffer.add_string(buf, Markdown.paragraph("Parameters:")); + Buffer.add_string(buf, output_for_params(params)); + }; + + switch (docblock) { + | Type(_) + | Value({returns: None}) + | Module(_) => () + | Value({returns: Some(returns)}) => + Buffer.add_string(buf, Markdown.paragraph("Returns:")); + Buffer.add_string(buf, output_for_returns(returns)); + }; + + switch (docblock) { + | Type(_) + | Value({throws: []}) + | Module(_) => () + | Value({throws}) => + Buffer.add_string(buf, Markdown.paragraph("Throws:")); + + Buffer.add_string(buf, output_for_throws(throws)); + }; + + switch (docblock) { + | Type({examples: []}) + | Value({examples: []}) + | Module({examples: []}) => () + | Type({examples}) + | Value({examples}) => + Buffer.add_string(buf, Markdown.paragraph("Examples:")); + List.iter( + ({example_txt}) => + Buffer.add_string(buf, Markdown.code_block(example_txt)), + examples, + ); + // No "Examples:" paragraph for module examples + | Module({examples}) => + List.iter( + ({example_txt}) => + Buffer.add_string(buf, Markdown.code_block(example_txt)), + examples, + ) + }; + + switch (docblock) { + | Type(_) + | Value(_) + | Module({provided: {provided_types: []}}) => () + | Module({module_namespace, name, provided: {provided_types}}) => + let namespace = title_for_namepace(~module_namespace, name); Buffer.add_string( buf, - Markdown.table(~headers=["param", "type", "description"], params), + Markdown.heading(~level=next_heading_level, "Types"), ); - }; - let returns = - docblock.attributes - |> List.filter(Comments.Attribute.is_returns) - |> List.map((attr: Comment_attributes.t) => { - switch (attr) { - | Returns({attr_type, attr_desc}) => [ - Option.fold(~none="", ~some=Markdown.code, attr_type), - attr_desc, - ] - | _ => - failwith("Unreachable: Non-`returns` attribute can't exist here.") - } - }); - if (List.length(returns) > 0) { - Buffer.add_string(buf, Markdown.paragraph("Returns:")); Buffer.add_string( buf, - Markdown.table(~headers=["type", "description"], returns), + Markdown.paragraph( + "Type declarations included in the " ++ namespace ++ " module.", + ), ); - }; - let throws = - docblock.attributes - |> List.filter(Comments.Attribute.is_throws) - |> List.fold_left( - (map, attr: Comment_attributes.t) => { - switch (attr) { - | Throws({attr_type: Some(attr_type), attr_desc}) => - StringMap.update( - attr_type, - descs => { - switch (descs) { - | None => Some([attr_desc]) - | Some(descs) => Some([attr_desc, ...descs]) - } - }, - map, - ) - | Throws({attr_type: None, attr_desc}) => - failwith( - "Unreachable: `throws` attribute requires an exception type.", - ) - | _ => - failwith("Unreachable: Non-`throws` attribute can't exist here.") - } - }, - StringMap.empty, - ) - |> StringMap.bindings; - if (List.length(throws) > 0) { - Buffer.add_string(buf, Markdown.paragraph("Throws:")); List.iter( - ((exception_type, exception_descriptions)) => { - Buffer.add_string( + item => + Buffer.add_buffer( buf, - Markdown.paragraph(Markdown.code(exception_type)), - ); - Buffer.add_string( - buf, - Markdown.bullet_list(List.rev(exception_descriptions)), - ); - }, - throws, + to_markdown( + ~current_version, + ~heading_level=next_heading_level, + item, + ), + ), + provided_types, ); }; - let examples = - docblock.attributes - |> List.filter(Comments.Attribute.is_example) - |> List.map((attr: Comment_attributes.t) => { - switch (attr) { - | Example({attr_desc}) => attr_desc - | _ => - failwith("Unreachable: Non-`example` attribute can't exist here.") - } - }); - if (List.length(examples) > 0) { - Buffer.add_string(buf, Markdown.paragraph("Examples:")); + + switch (docblock) { + | Type(_) + | Value(_) + | Module({provided: {provided_values: []}}) => () + | Module({module_namespace, name, provided: {provided_values}}) => + let namespace = title_for_namepace(~module_namespace, name); + Buffer.add_string( + buf, + Markdown.heading(~level=next_heading_level, "Values"), + ); + Buffer.add_string( + buf, + Markdown.paragraph( + "Functions and constants included in the " ++ namespace ++ " module.", + ), + ); List.iter( - example => Buffer.add_string(buf, Markdown.code_block(example)), - examples, + item => + Buffer.add_buffer( + buf, + to_markdown( + ~current_version, + ~heading_level=next_heading_level, + item, + ), + ), + provided_values, ); }; + + switch (docblock) { + | Type(_) + | Value(_) + | Module({provided: {provided_modules: []}}) => () + | Module({module_namespace, name, provided: {provided_modules}}) => + List.iter( + item => + Buffer.add_buffer( + buf, + to_markdown( + ~current_version, + ~heading_level=next_heading_level, + item, + ), + ), + provided_modules, + ) + }; + buf; }; diff --git a/compiler/graindoc/graindoc.re b/compiler/graindoc/graindoc.re index b3ac80e3af..589fe95cb6 100644 --- a/compiler/graindoc/graindoc.re +++ b/compiler/graindoc/graindoc.re @@ -88,195 +88,23 @@ let generate_docs = let buf = Buffer.create(0); let module_name = program.module_name.txt; - let module_comment = - Comments.Doc.ending_on( - ~lnum=program.module_name.loc.loc_start.pos_lnum - 1, - comments, - ); Buffer.add_string(buf, Markdown.frontmatter([("title", module_name)])); - switch (module_comment) { - | Some((_, desc, attrs)) => - let deprecations = - attrs - |> List.filter(Comments.Attribute.is_deprecated) - |> List.map((attr: Comment_attributes.t) => { - switch (attr) { - | Deprecated({attr_desc}) => attr_desc - | _ => - failwith( - "Unreachable: Non-`deprecated` attribute can't exist here.", - ) - } - }); - - if (List.length(deprecations) > 0) { - List.iter( - msg => - Buffer.add_string( - buf, - Markdown.blockquote(Markdown.bold("Deprecated:") ++ " " ++ msg), - ), - deprecations, - ); - }; - - switch (desc) { - | Some(desc) => Buffer.add_string(buf, Markdown.paragraph(desc)) - | None => () - }; - - // TODO(#787): Should we fail if more than one `@since` attribute? - let since_attr = - attrs - |> List.find_opt(Comments.Attribute.is_since) - |> Option.map((attr: Comment_attributes.t) => { - switch (attr) { - | Since({attr_version}) => - Docblock.output_for_since(~current_version, attr_version) - | _ => - failwith("Unreachable: Non-`since` attribute can't exist here.") - } - }); - let history_attrs = - attrs - |> List.filter(Comments.Attribute.is_history) - |> List.map((attr: Comment_attributes.t) => { - switch (attr) { - | History({attr_version, attr_desc}) => - Docblock.output_for_history( - ~current_version, - attr_version, - attr_desc, - ) - | _ => - failwith("Unreachable: Non-`since` attribute can't exist here.") - } - }); - if (Option.is_some(since_attr) || List.length(history_attrs) > 0) { - let summary = Option.value(~default="History", since_attr); - let disabled = List.length(history_attrs) == 0 ? true : false; - let details = - if (List.length(history_attrs) == 0) { - "No other changes yet."; - } else { - Html.table(~headers=["version", "changes"], history_attrs); - }; - Buffer.add_string(buf, Html.details(~disabled, ~summary, details)); - }; - - let example_attrs = attrs |> List.filter(Comments.Attribute.is_example); - if (List.length(example_attrs) > 0) { - List.iter( - (attr: Comment_attributes.t) => { - switch (attr) { - | Example({attr_desc}) => - Buffer.add_string(buf, Markdown.code_block(attr_desc)) - | _ => - failwith("Unreachable: Non-`example` attribute can't exist here.") - } - }, - example_attrs, - ); - }; - | None => () - }; - - let sig_types = - List.filter( - (sig_item: Types.signature_item) => { - switch (sig_item) { - | TSigTypeExt(_) - | TSigModule(_) - | TSigModType(_) - | TSigValue(_) => false - | TSigType(_) => true - } - }, - signature_items, - ); - - switch (sig_types) { - | [] => () - | _ => - Buffer.add_string(buf, Markdown.heading(~level=2, "Types")); - Buffer.add_string( - buf, - Markdown.paragraph( - "Type declarations included in the " ++ module_name ++ " module.", - ), - ); - - List.iter( - (sig_type: Types.signature_item) => { - switch (sig_type) { - | TSigType(ident, td, _rec) => - let docblock = - Docblock.for_type_declaration( - ~comments, - ~provides, - ~module_name, - ~ident, - td, - ); - Buffer.add_buffer( - buf, - Docblock.to_markdown(~current_version, docblock), - ); - | _ => failwith("Unreachable: TSigType-only list") - } - }, - sig_types, - ); - }; - let sig_values = - List.filter( - (sig_item: Types.signature_item) => { - switch (sig_item) { - | TSigType(_) - | TSigTypeExt(_) - | TSigModule(_) - | TSigModType(_) => false - | TSigValue(_) => true - } - }, + let docblock = + Docblock.for_signature_items( + ~comments, + ~provides, + ~module_namespace=None, + ~name=module_name, + ~loc=program.module_name.loc, signature_items, ); - switch (sig_values) { - | [] => () - | _ => - Buffer.add_string(buf, Markdown.heading(~level=2, "Values")); - Buffer.add_string( - buf, - Markdown.paragraph( - "Functions and constants included in the " ++ module_name ++ " module.", - ), - ); - - List.iter( - (sig_value: Types.signature_item) => { - switch (sig_value) { - | TSigValue(ident, vd) => - let docblock = - Docblock.for_value_description( - ~comments, - ~provides, - ~module_name, - ~ident, - vd, - ); - Buffer.add_buffer( - buf, - Docblock.to_markdown(~current_version, docblock), - ); - | _ => failwith("Unreachable: TSigValue-only list") - } - }, - sig_values, - ); - }; + Buffer.add_buffer( + buf, + Docblock.to_markdown(~current_version, ~heading_level=1, docblock), + ); let contents = Buffer.to_bytes(buf); switch (output) { diff --git a/compiler/src/diagnostics/comment_attributes.re b/compiler/src/diagnostics/comment_attributes.re index f00327f1fd..599258b58d 100644 --- a/compiler/src/diagnostics/comment_attributes.re +++ b/compiler/src/diagnostics/comment_attributes.re @@ -1,32 +1,22 @@ exception InvalidAttribute(string); exception MalformedAttribute(string, string); -type attr_name = string; -type attr_desc = string; -// The `attr_type` always starts as `None` and is applied later by something like Graindoc -type attr_type = option(string); -type attr_version = string; - type t = | Param({ - attr_name, - attr_type, - attr_desc, - }) - | Returns({ - attr_desc, - attr_type, + attr_name: string, + attr_desc: string, }) - | Example({attr_desc}) - | Deprecated({attr_desc}) - | Since({attr_version}) + | Returns({attr_desc: string}) + | Example({attr_desc: string}) + | Deprecated({attr_desc: string}) + | Since({attr_version: string}) | History({ - attr_version, - attr_desc, + attr_version: string, + attr_desc: string, }) | Throws({ - attr_type, - attr_desc, + attr_type: string, + attr_desc: string, }); type parsed_graindoc = (option(string), list(t)); diff --git a/compiler/src/diagnostics/graindoc_parser.mly b/compiler/src/diagnostics/graindoc_parser.mly index 599c8c2fa9..a7394d39af 100644 --- a/compiler/src/diagnostics/graindoc_parser.mly +++ b/compiler/src/diagnostics/graindoc_parser.mly @@ -27,13 +27,13 @@ attribute_text: | ioption(eols) multiline_text ioption(eols) %prec EOL { $2 } attribute: - | PARAM IDENT COLON attribute_text { Param({ attr_name=$2; attr_type=None; attr_desc=$4 }) } - | RETURNS attribute_text { Returns({ attr_desc=$2; attr_type=None }) } + | PARAM IDENT COLON attribute_text { Param({ attr_name=$2; attr_desc=$4 }) } + | RETURNS attribute_text { Returns({attr_desc=$2}) } | EXAMPLE attribute_text { Example({attr_desc=$2}) } | DEPRECATED attribute_text { Deprecated({attr_desc=$2}) } | SINCE SEMVER { Since({attr_version=$2}) } | HISTORY SEMVER COLON attribute_text { History({ attr_version=$2; attr_desc=$4; }) } - | THROWS CONSTRUCTOR COLON attribute_text { Throws({ attr_type=Some $2; attr_desc=$4; }); } + | THROWS CONSTRUCTOR COLON attribute_text { Throws({ attr_type=$2; attr_desc=$4; }); } attributes_help: | attribute { [$1] } diff --git a/stdlib/regex.md b/stdlib/regex.md index 0de13c7400..f103f468ee 100644 --- a/stdlib/regex.md +++ b/stdlib/regex.md @@ -25,6 +25,11 @@ type RegularExpression ### Regex.**MatchResult** +
+Added in 0.4.3 +No other changes yet. +
+ ```grain record MatchResult { group: Number -> Option,