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,