diff --git a/README.md b/README.md index d543462..f91656b 100644 --- a/README.md +++ b/README.md @@ -83,3 +83,17 @@ To validate pre-existing YAML: To output updated YAML: ```./hyde -use-system-clang -hyde-yaml-dir=/path/to/output -hyde-update ../test_files/classes.cpp``` + +# Hyde 1 to Hyde 2 Format Conversion + +As of the Hyde 2 work, all subfields in the YAML output (except the Jekyll-required `layout` and `title` fields) must go under a top-level `hyde` subfield. This allows for other tools to include additional (possibly same-named) fields under their own top-level subfields in the YAML. + +Here is an example of updating from Hyde 1 to Hyde 2 formatted docs by scanning a directory for markdown-formatted files and passing them to `hyde` with the new `-hyde-fixup-subfield` mode: + + find . -name '*.md' | xargs -I % -L 1 /path/to/hyde -hyde-fixup-subfield % -- + +# Sass Updates + +Sometimes it may be necessary to clean up or "lint" the sass files. You can do so with: + + bundle exec sass-convert -i /path/to/file.scss diff --git a/docs/libraries/index.md b/docs/libraries/index.md deleted file mode 100644 index 095db08..0000000 --- a/docs/libraries/index.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: library -title: Hyde Sample Docs -owner: fosterbrereton -brief: Sample Hyde Documentation -tags: - - library -library-type: library -icon: book -short_title: Sample Docs -tab: Docs ---- diff --git a/emitters/yaml_base_emitter.cpp b/emitters/yaml_base_emitter.cpp index 2687e27..ef9cd0f 100644 --- a/emitters/yaml_base_emitter.cpp +++ b/emitters/yaml_base_emitter.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -30,6 +30,11 @@ namespace { /**************************************************************************************************/ +static const hyde::json no_json_k; +static const hyde::json inline_json_k(hyde::tag_value_inlined_k); + +/**************************************************************************************************/ + YAML::Node update_cleanup(const YAML::Node& node) { YAML::Node result; @@ -156,6 +161,7 @@ YAML::Node json_to_yaml_ordered(hyde::json j) { move_key("owner"); move_key("brief"); move_key("tags"); + move_key("inline"); move_key("library-type"); move_key("defined_in_file"); move_key("declaration"); @@ -202,16 +208,29 @@ hyde::json fixup_hyde_subfield(hyde::json&& j) { j.erase("title"); } - result["hyde"] = std::move(j); + // If a fixup has already been done, or there are already fields in the `hyde` subobject, + // move them before going on to the next step. + if (j.count("hyde")) { + result["hyde"] = std::move(j.at("hyde")); + j.erase("hyde"); + } - std::string result_str = result.dump(4); + // For any keys that are left over in `j`, move them under the `hyde` subobject. Note that + // this will overwrite any conflicting keys that may already exist there. + for (auto& entry : j.items()) { + result["hyde"][entry.key()] = std::move(entry.value()); + } return result; } /**************************************************************************************************/ -static const std::string front_matter_delimiter_k("---\n"); +static const std::string front_matter_begin_k("---\n"); + +// Using a string wrapped in newlines because some YAML data may contain "---" in them +// (e.g., inline comments that have sucked up a horizontal delimiter.) +static const std::string front_matter_end_k("\n---\n"); /**************************************************************************************************/ @@ -227,29 +246,82 @@ file_checker yaml_base_emitter::checker_s; // REVISIT (fbrereto) : Global. Bad p /**************************************************************************************************/ -json yaml_base_emitter::base_emitter_node(std::string layout, std::string title, std::string tag) { +json yaml_base_emitter::base_emitter_node(std::string layout, + std::string title, + std::string tag, + bool implicit) { json node; node["layout"] = std::move(layout); node["title"] = std::move(title); - node["hyde"]["owner"] = tag_value_missing_k; + const auto& default_tag_value = implicit ? tag_value_optional_k : tag_value_missing_k; + + node["hyde"]["owner"] = default_tag_value; node["hyde"]["tags"].emplace_back(std::move(tag)); - node["hyde"]["brief"] = tag_value_missing_k; + node["hyde"]["brief"] = default_tag_value; return node; } /**************************************************************************************************/ -void yaml_base_emitter::insert_typedefs(const json& j, json& node) { +void yaml_base_emitter::insert_doxygen(const json& j, json& node) { + if (!j.count("comments")) return; + + const auto& comments = j.at("comments"); + auto& output = node["inline"]; + + if (comments.count("command")) { + for (const auto& item : comments.at("command").items()) { + const auto& command = item.value(); + std::string key = command["name"].get(); + if (key.starts_with("hyde-")) { + key = key.substr(5); + } + output[key] = command["text"].get(); + } + } + + if (comments.count("paragraph")) { + json::array_t paragraphs; + for (const auto& paragraph : comments.at("paragraph").items()) { + paragraphs.push_back(paragraph.value().at("text")); + } + output["description"] = std::move(paragraphs); + } + + if (comments.count("param")) { + json::object_t arguments; + for (const auto& item : comments.at("param").items()) { + const auto& param = item.value(); + const auto& name = param["name"].get(); + json::object_t argument; + if (param["direction_explicit"].get()) { + argument["direction"] = param["direction"]; + } + argument["description"] = param["text"]; + arguments[name] = std::move(argument); + } + output["arguments"] = std::move(arguments); + } +} + +/**************************************************************************************************/ + +void yaml_base_emitter::insert_typedefs(const json& j, json& node, const json& inherited) { if (j.count("typedefs")) { for (const auto& type_def : j["typedefs"]) { const std::string& key = type_def["name"]; auto& type_node = node["hyde"]["typedefs"][key]; + insert_inherited(inherited, type_node); + insert_annotations(type_def, type_node); + insert_doxygen(type_def, type_node); + type_node["definition"] = static_cast(type_def["type"]); - type_node["description"] = tag_value_missing_k; - maybe_annotate(type_def, type_node); + type_node["description"] = has_inline_field(type_node, "description") ? + tag_value_inlined_k : + tag_value_missing_k; } } @@ -257,29 +329,49 @@ void yaml_base_emitter::insert_typedefs(const json& j, json& node) { for (const auto& type_def : j["typealiases"]) { const std::string& key = type_def["name"]; auto& type_node = node["hyde"]["typedefs"][key]; + insert_inherited(inherited, type_node); + insert_annotations(type_def, type_node); + insert_doxygen(type_def, type_node); + type_node["definition"] = static_cast(type_def["type"]); - type_node["description"] = tag_value_missing_k; - maybe_annotate(type_def, type_node); + type_node["description"] = has_inline_field(type_node, "description") ? + tag_value_inlined_k : + tag_value_missing_k; } } } /**************************************************************************************************/ +void yaml_base_emitter::check_inline_comments(const json& expected, json& out_merged) { + // inline comments *always* come from the sources. Therefore, they are always overwritten in the + // merge. + if (expected.count("inline")) { + out_merged["inline"] = expected.at("inline"); + } +} + +/**************************************************************************************************/ + bool yaml_base_emitter::check_typedefs(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node) { - return check_map(filepath, have_node, expected_node, nodepath, merged_node, "typedefs", + return check_map( + filepath, have_node, expected_node, nodepath, merged_node, "typedefs", [this](const std::string& filepath, const json& have, const json& expected, const std::string& nodepath, json& out_merged) { bool failure{false}; failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "name"); failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "definition"); - failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "description"); - failure |= check_scalar_array(filepath, have, expected, nodepath, out_merged, "annotation"); + failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, + "description"); + failure |= + check_scalar_array(filepath, have, expected, nodepath, out_merged, "annotation"); + + check_inline_comments(expected, out_merged); return failure; }); @@ -294,15 +386,15 @@ void yaml_base_emitter::check_notify(const std::string& filepath, const std::string& update_message) { std::string escaped_nodepath = hyde::ReplaceAll(nodepath, "\n", "\\n"); std::string escaped_key = hyde::ReplaceAll(key, "\n", "\\n"); - + switch (_mode) { case yaml_mode::validate: { - std::cerr << filepath << "@" << escaped_nodepath << "['" << escaped_key << "']: " << validate_message - << "\n"; + std::cerr << filepath << "@" << escaped_nodepath << "['" << escaped_key + << "']: " << validate_message << "\n"; } break; case yaml_mode::update: { - std::cout << filepath << "@" << escaped_nodepath << "['" << escaped_key << "']: " << update_message - << "\n"; + std::cout << filepath << "@" << escaped_nodepath << "['" << escaped_key + << "']: " << update_message << "\n"; } break; } } @@ -352,12 +444,12 @@ bool yaml_base_emitter::check_scalar(const std::string& filepath, result = expected; return true; } - + const json& have = have_node[key]; if (have != expected) { result = expected; - + // Since yaml <-> json type conversions aren't perfect (at least with the libraries we are // currently using), we will only report a failure if the yaml-serialized value is different auto have_yaml = json_to_yaml(have).as(); @@ -375,6 +467,58 @@ bool yaml_base_emitter::check_scalar(const std::string& filepath, } /**************************************************************************************************/ +// The intent of this routine is to arrive at the ideal output given the various bits of state the +// engine has available to it. The two major players are `have` (the data that has been parsed out +// of documentation that already exists) and `expected` (the data derived by the engine from +// compiling the source file). In addition to user-entered values in the `have` data, there are a +// handful of predefined values for the key that also play a part (missing, inlined, deprecated, +// etc.) Both `have` and `expected` come in as json blobs that might contain an entry under `key`. +// The goal is to affect `merged_node` (which is also a json blob) under that same `key`. I +// say "affect" because it doesn't always mean setting a value to the output - sometimes it's +// the _removal_ of said key. Hopefully this all is made more clear by the path outlined below. +// +// It's worth adding a comment about "associated inline" values. To start, this is data extracted +// from the source file's Doxygen-formatted inline comments (hence the term). Therefore, this data +// is _always_ derived, and _never_ modified by `have`. Thus, if inline values exist, they are +// always copied from `expected` into the merged output. In order for an inline value to be +// considered "associated" with a given entry `expected[key]`, the value must exist under `expected +// ["inline"][key]`. The associated value type is not always a string (for example, inline comments +// can be an array of strings _or_ a single string. Those value type differences need to be +// accounted for in the Jekyll theme that will be displaying that data - we don't care about them +// here.) +// +// A word on the "special" values. These are placeholder string values that, depending on their +// individual semantics, will affect how the final documentation is displayed: +// - __MISSING__: This is the value most developers will be familiar with. In order for the +// documentation to pass validation, all __MISSING__ fields must be manually filled in. +// - __OPTIONAL__: This field's value is not required for validation and will be shown if +// manually filled in. Among other cases, this value is used when a declaration is implicit +// (e.g., compiler-implemented, v. defined in the source code.) +// - __DEPRECATED__: This field will be found in `expected`, and will cause the equivalent field +// in `have` to be removed. +// - __INLINED__: The value _would_ be __MISSING__ except that there is an associated inline +// value for the field, and thus the minimum requirement for documentation has been met, +// and validation will pass for this field. Users are allowed to replace this value should +// they want to add further documentation. +// +// These `check_` routines are used for both the validation and update phases. Their logic is the +// same, but how they behave will differ (e.g., inserting a value [updating] v. reporting a value +// missing [validating].) +// +// The logical gist of this routine is as follows: +// - If `expected[key]` doesn't exist, make sure `have[key]` doesn't either. Done. +// - If `expected[key]` isn't a string, we're in the wrong routine. Done. In other words, all +// hyde scalars are strings. +// - If `expected[key]` has an associated inline value, set `default_value` to __INLINED__ +// - Otherwise, set `default_value` to __MISSING__ +// - If `have[key]` does not exist, the result is `default_value`. Done. +// - If `have[key]` is not a scalar, the result is `default_value`. Done. +// - If both `expected[key]` and `have[key]` are __MISSING__, the result is +// `default_value`. Done. +// - If `expected[key]` is __DEPRECATED__, result is no output. Done. +// - If `expected[key]` and `have[key]` are both __OPTIONAL__, result is __OPTIONAL__. Done. +// - If `have[key]` is a special tag value, result is `default_value`. Done. +// - Otherwise, result is `have[key]`. Done. bool yaml_base_emitter::check_editable_scalar(const std::string& filepath, const json& have_node, @@ -392,12 +536,18 @@ bool yaml_base_emitter::check_editable_scalar(const std::string& filepath, } const json& expected = expected_node[key]; + const bool has_associated_inline_value = has_inline_field(expected_node, key.c_str()); if (!expected.is_string()) { throw std::runtime_error("expected type mismatch?"); } const std::string& expected_scalar(expected); + const bool expected_set_to_missing = expected_scalar == tag_value_missing_k; + const bool use_inline_value = expected_set_to_missing && has_associated_inline_value; + const json& default_value = use_inline_value ? inline_json_k : expected; + const std::string& default_value_scalar(default_value); + json& result = merged_node[key]; if (!have_node.count(key)) { @@ -406,28 +556,27 @@ bool yaml_base_emitter::check_editable_scalar(const std::string& filepath, return false; } else { notify("value missing", "value inserted"); - result = expected; + result = default_value; return true; } } - + const json& have = have_node[key]; - + if (!have.is_string()) { - notify("value not scalar; expected `" + expected_scalar + "`", - "value not scalar; updated to `" + expected_scalar + "`"); - result = expected; + notify("value not scalar; expected `" + default_value_scalar + "`", + "value not scalar; updated to `" + default_value_scalar + "`"); + result = default_value; return true; } const std::string& have_scalar(have); - if (expected_scalar == tag_value_missing_k && have_scalar == tag_value_missing_k) { - result = have; + if (default_value_scalar == tag_value_missing_k && have_scalar == tag_value_missing_k) { + result = default_value; if (_mode == yaml_mode::validate) { notify("value not documented", ""); } - return true; } @@ -436,15 +585,19 @@ bool yaml_base_emitter::check_editable_scalar(const std::string& filepath, return true; } - if (expected_scalar == tag_value_optional_k && have_scalar == tag_value_optional_k) { + // If the scalars are identical, they're both equal tags at this point. Done. + if (default_value_scalar == have_scalar) { result = have; return false; } + // Among other cases, this check will handle when tags go from __MISSING__ to __INLINED__ and + // vice versa. if (hyde::is_tag(have_scalar)) { - notify("value is unexpected tag `" + have_scalar + "`", - "value updated from `" + have_scalar + "` to `" + expected_scalar + "`"); - result = expected; // Replace unexpected tag + notify("found unexpected tag `" + have_scalar + "`", + "tag updated from `" + have_scalar + "` to `" + default_value_scalar + "`"); + // Replace unexpected tag? + result = default_value; return true; } @@ -585,13 +738,13 @@ bool yaml_base_emitter::check_scalar_array(const std::string& filepath, } json& result = merged_node[key]; - + if (!have_node.count(key)) { notify("value missing", "value inserted"); result = expected; return true; } - + if (!have_node[key].is_array()) { notify("value not an array", "non-array value replaced"); result = expected; @@ -656,8 +809,10 @@ bool yaml_base_emitter::check_scalar_array(const std::string& filepath, std::size_t have_index = have_found_iter->second; if (have_index != index) { std::string have_index_str(std::to_string(have_index)); - notify("bad item location for item `" + expected_str + "`; have: " + have_index_str + ", expected: " + index_str, - "moved item `" + expected_str + "` at index " + have_index_str + " to index " + index_str); + notify("bad item location for item `" + expected_str + + "`; have: " + have_index_str + ", expected: " + index_str, + "moved item `" + expected_str + "` at index " + have_index_str + + " to index " + index_str); failure = true; } result_array.push_back(expected_str); @@ -710,7 +865,7 @@ bool yaml_base_emitter::check_object_array(const std::string& filepath, } json& result = merged_node[key]; - + if (!have_node.count(key)) { notify("value missing", "value inserted"); result = expected; @@ -794,8 +949,10 @@ bool yaml_base_emitter::check_object_array(const std::string& filepath, std::size_t have_index = have_found_iter->second; if (have_index != index) { std::string have_index_str(std::to_string(have_index)); - notify("bad item location for key `" + expected_key + "`; have: " + have_index_str + ", expected: " + index_str, - "moved item with key `" + expected_key + "` at index " + have_index_str + " to index " + index_str); + notify("bad item location for key `" + expected_key + "`; have: " + have_index_str + + ", expected: " + index_str, + "moved item with key `" + expected_key + "` at index " + have_index_str + + " to index " + index_str); failure = true; } std::string nodepath = "['" + key + "'][" + index_str + "]"; @@ -810,7 +967,8 @@ bool yaml_base_emitter::check_object_array(const std::string& filepath, for (const auto& have_iter : have_map) { const std::string& have_key = have_iter.first; std::string have_index_str(std::to_string(have_iter.second)); - auto message = "extraneous item with key `" + have_key + "` at index `" + have_index_str + "`"; + auto message = + "extraneous item with key `" + have_key + "` at index `" + have_index_str + "`"; notify(message, "removed " + message); failure = true; } @@ -829,6 +987,8 @@ bool yaml_base_emitter::check_map(const std::string& filepath, json& merged_node, const std::string& key, const check_proc& proc) { + const bool at_root = key == ""; + const auto notify = [&](const std::string& validate_message, const std::string& update_message) { check_notify(filepath, nodepath, key, validate_message, update_message); @@ -867,23 +1027,27 @@ bool yaml_base_emitter::check_map(const std::string& filepath, keys.erase(std::unique(keys.begin(), keys.end()), keys.end()); bool failure{false}; - + json result_map; for (const auto& subkey : keys) { std::string curnodepath = nodepath + "['" + subkey + "']"; if (!expected.count(subkey)) { - notify("extraneous map key: `" + subkey + "`", "map key removed: `" + subkey + "`"); - failure = true; + // Issue #75: only remove non-root keys to allow non-hyde YAML into the file. + if (!at_root) { + notify("extraneous map key: `" + subkey + "`", "map key removed: `" + subkey + "`"); + failure = true; + } } else if (!have.count(subkey)) { notify("map key missing: `" + subkey + "`", "map key inserted: `" + subkey + "`"); result_map[subkey] = expected[subkey]; failure = true; } else { - failure |= proc(filepath, have[subkey], expected[subkey], curnodepath, result_map[subkey]); + failure |= + proc(filepath, have[subkey], expected[subkey], curnodepath, result_map[subkey]); } } - + result = std::move(result_map); return failure; @@ -891,11 +1055,17 @@ bool yaml_base_emitter::check_map(const std::string& filepath, /**************************************************************************************************/ +bool yaml_base_emitter::has_inline_field(const json& j, const char* field) { + return j.count("inline") && j.at("inline").count(field); +} + +/**************************************************************************************************/ + std::pair yaml_base_emitter::merge(const std::string& filepath, const json& have, const json& expected) { bool failure{false}; - + // Create a temporary object with the json to merge as a value so we can use `check_map` // to make sure removed keys are handled static const auto root_key = ""; @@ -905,10 +1075,10 @@ std::pair yaml_base_emitter::merge(const std::string& filepath, expected_root[root_key] = expected; json merged_root; failure |= check_map(filepath, have_root, expected_root, "", merged_root, root_key, - [](const std::string& filepath, const json& have, const json& expected, - const std::string& nodepath, json& out_merged) { return false; }); + [](const std::string& filepath, const json& have, const json& expected, + const std::string& nodepath, json& out_merged) { return false; }); json& merged = merged_root[root_key]; - + // we can probably get rid of `have` in the check // routines; I don't think we can keep it from being an // out-arg, though, because we need to preserve the @@ -923,16 +1093,17 @@ std::pair yaml_base_emitter::merge(const std::string& filepath, } { - static const hyde::json no_json_k; - const hyde::json& expected_hyde = expected.at("hyde"); - const hyde::json& have_hyde = have.count("hyde") ? have.at("hyde") : no_json_k; - hyde::json& merged_hyde = merged["hyde"]; + const hyde::json& expected_hyde = expected.at("hyde"); + const hyde::json& have_hyde = have.count("hyde") ? have.at("hyde") : no_json_k; + hyde::json& merged_hyde = merged["hyde"]; - failure |= check_editable_scalar(filepath, have_hyde, expected_hyde, "", merged_hyde, "owner"); - failure |= check_editable_scalar(filepath, have_hyde, expected_hyde, "", merged_hyde, "brief"); - failure |= check_scalar_array(filepath, have_hyde, expected_hyde, "", merged_hyde, "tags"); + failure |= + check_editable_scalar(filepath, have_hyde, expected_hyde, "", merged_hyde, "owner"); + failure |= + check_editable_scalar(filepath, have_hyde, expected_hyde, "", merged_hyde, "brief"); + failure |= check_scalar_array(filepath, have_hyde, expected_hyde, "", merged_hyde, "tags"); - failure |= do_merge(filepath, have_hyde, expected_hyde, merged_hyde); + failure |= do_merge(filepath, have_hyde, expected_hyde, merged_hyde); } return std::make_pair(failure, std::move(merged)); @@ -950,7 +1121,7 @@ inline std::uint64_t fnv_1a(const std::string& s) { } return result; - } +} /**************************************************************************************************/ @@ -1004,9 +1175,9 @@ bool yaml_base_emitter::create_directory_stub(std::filesystem::path p) { {"title", p.filename().string()}, }; - output << front_matter_delimiter_k; - output << json_to_yaml_ordered(stub_json_k) << '\n'; - output << front_matter_delimiter_k; + output << front_matter_begin_k; + output << json_to_yaml_ordered(stub_json_k); + output << front_matter_end_k; return false; } @@ -1065,6 +1236,64 @@ auto load_yaml(const std::filesystem::path& path) try { /**************************************************************************************************/ +documentation parse_documentation(const std::filesystem::path& path, bool fixup_subfield) { + // we have to load the file ourselves and find the place where the + // front-matter ends and any other relevant documentation begins. We + // need to do this for the boilerpolate step to keep it from blasting + // out any extra documentation that's already been added. + std::ifstream have_file(path); + std::stringstream have_contents_stream; + have_contents_stream << have_file.rdbuf(); + std::string have_contents = have_contents_stream.str(); + documentation result; + + if (have_contents.find_first_of(front_matter_begin_k) != 0) { + std::cerr << "./" << path.string() << ": does not begin with YAML front-matter.\n"; + result._error = true; + return result; + } + + const auto contents_end = have_contents.find(front_matter_end_k); + + if (contents_end == std::string::npos) { + std::cerr << "./" << path.string() << ": could not find end of YAML front-matter.\n"; + result._error = true; + return result; + } + + const auto front_matter_end = contents_end + front_matter_end_k.size(); + std::string yaml_src = have_contents.substr(0, front_matter_end); + have_contents.erase(0, front_matter_end); + + result._remainder = std::move(have_contents); + result._json = yaml_to_json(load_yaml(path)); + + if (fixup_subfield) { + result._json = fixup_hyde_subfield(std::move(result._json)); + } + + return result; +} + +/**************************************************************************************************/ + +bool write_documentation(const documentation& docs, const std::filesystem::path& path) { + std::ofstream output(path); + if (!output) { + std::cerr << "./" << path.string() << ": could not open file for output\n"; + return true; + } + + output << front_matter_begin_k; + output << json_to_yaml_ordered(docs._json); + output << front_matter_end_k; + output << docs._remainder; + + return false; +} + +/**************************************************************************************************/ + bool yaml_base_emitter::reconcile(json expected, std::filesystem::path root_path, std::filesystem::path path, @@ -1094,27 +1323,15 @@ bool yaml_base_emitter::reconcile(json expected, failure |= create_path_directories(path); if (checker_s.exists(path)) { - // we have to load the file ourselves and find the place where the - // front-matter ends and any other relevant documentation begins. We - // need to do this for the boilerpolate step to keep it from blasting - // out any extra documentation that's already been added. - std::ifstream have_file(path); - std::stringstream have_contents_stream; - have_contents_stream << have_file.rdbuf(); - std::string have_contents = have_contents_stream.str(); - auto front_matter_pos = have_contents.find_first_of(front_matter_delimiter_k); - auto front_matter_end = have_contents.find( - front_matter_delimiter_k, front_matter_pos + front_matter_delimiter_k.size()); - std::string yaml_src = have_contents.substr( - front_matter_pos, front_matter_end + front_matter_delimiter_k.size()); - have_contents.erase(front_matter_pos, front_matter_end + front_matter_delimiter_k.size()); - std::string remainder = std::move(have_contents); - json have = yaml_to_json(load_yaml(path)); - - if (_mode == yaml_mode::update && _options._fixup_hyde_subfield) { - have = fixup_hyde_subfield(std::move(have)); + const auto have_docs = parse_documentation(path, true); + + if (have_docs._error) { + return true; } + const auto& have = have_docs._json; + const auto& remainder = have_docs._remainder; + json merged; std::tie(failure, merged) = merge(relative_path, have, expected); @@ -1126,16 +1343,7 @@ bool yaml_base_emitter::reconcile(json expected, // do nothing } break; case hyde::yaml_mode::update: { - std::ofstream output(path); - if (!output) { - std::cerr << "./" << path.string() << ": could not open file for output\n"; - failure = true; - } else { - output << front_matter_delimiter_k; - output << json_to_yaml_ordered(merged) << '\n'; - output << front_matter_delimiter_k; - output << remainder; - } + failure = write_documentation({std::move(merged), std::move(remainder)}, path); } break; } } else { // file does not exist @@ -1148,14 +1356,17 @@ bool yaml_base_emitter::reconcile(json expected, } break; case hyde::yaml_mode::update: { // Add update. No remainder yet, as above. + // REVISIT: Refactor all this into a call to write_documentation, + // though I'm not sure what the call to `update_cleanup` is all about, + // and which was missing from the prior case when the file existed. std::ofstream output(path); if (!output) { std::cerr << "./" << path.string() << ": could not open file for output\n"; failure = true; } else { - output << "---\n"; - output << update_cleanup(json_to_yaml_ordered(expected)) << '\n'; - output << "---\n"; + output << front_matter_begin_k; + output << update_cleanup(json_to_yaml_ordered(expected)); + output << front_matter_end_k; } } break; } @@ -1164,7 +1375,6 @@ bool yaml_base_emitter::reconcile(json expected, return failure; } - /**************************************************************************************************/ std::string yaml_base_emitter::defined_in_file(const std::string& src_path, @@ -1175,39 +1385,55 @@ std::string yaml_base_emitter::defined_in_file(const std::string& src_path, /**************************************************************************************************/ std::filesystem::path yaml_base_emitter::subcomponent(const std::filesystem::path& src_path, - const std::filesystem::path& src_root) { + const std::filesystem::path& src_root) { return std::filesystem::relative(src_path, src_root); } /**************************************************************************************************/ -void yaml_base_emitter::maybe_annotate(const json& j, json& node) { +void yaml_base_emitter::insert_inherited(const json& inherited, json& node) { + if (inherited.count("owner") && !is_tag(inherited.at("owner").get())) { + node["owner"] = inherited.at("owner"); + } + + if (has_inline_field(inherited, "owner")) { + node["inline"]["owner"] = inherited.at("inline").at("owner"); + } +} + +/**************************************************************************************************/ + +void yaml_base_emitter::insert_annotations(const json& j, json& node) { std::string annotation; if (j.count("access")) { const std::string& access = j["access"]; if (access != "public") { - node["hyde"]["annotation"].push_back(access); + node["annotation"].push_back(access); } } - if (j.count("default") && j["default"]) - node["hyde"]["annotation"].push_back("default"); - else if (j.count("delete") && j["delete"]) - node["hyde"]["annotation"].push_back("delete"); + if (has_json_flag(j, "implicit")) { + node["annotation"].push_back("implicit"); + } + if (has_json_flag(j, "defaulted")) { + node["annotation"].push_back("defaulted"); + } + if (has_json_flag(j, "deleted")) { + node["annotation"].push_back("deleted"); + } - if (j.count("deprecated") && j["deprecated"]) { + if (has_json_flag(j, "deprecated")) { std::string deprecated("deprecated"); + if (j.count("deprecated_message")) { const std::string& message_str = j["deprecated_message"]; if (!message_str.empty()) { - deprecated = deprecated.append(" (") - .append(message_str) - .append(")"); + deprecated = deprecated.append("(\"").append(message_str).append("\")"); } } - node["hyde"]["annotation"].push_back(deprecated); + node["annotation"].push_back(deprecated); } } diff --git a/emitters/yaml_base_emitter.hpp b/emitters/yaml_base_emitter.hpp index c8f226d..c8f8be3 100644 --- a/emitters/yaml_base_emitter.hpp +++ b/emitters/yaml_base_emitter.hpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ #pragma once @@ -47,6 +47,12 @@ struct file_checker { /**************************************************************************************************/ +inline bool has_json_flag(const json& j, const char* k) { + return j.count(k) && j.at(k).get(); +} + +/**************************************************************************************************/ + struct yaml_base_emitter { public: yaml_base_emitter(std::filesystem::path src_root, @@ -54,29 +60,45 @@ struct yaml_base_emitter { yaml_mode mode, emit_options options, bool editable_title = false) - : _src_root(std::move(src_root)), _dst_root(std::move(dst_root)), _mode(mode), _options(std::move(options)), _editable_title{editable_title} {} + : _src_root(std::move(src_root)), _dst_root(std::move(dst_root)), _mode(mode), + _options(std::move(options)), _editable_title{editable_title} {} - virtual bool emit(const json& j, json& out_emitted) = 0; + /// @param matched The json given to us by the matcher engine + /// @param output The resulting output of this call + /// @param inherited Any inherited fields from parent constructs, e.g., passing + /// the owner of a class to its members + /// @return `true` if an error took place during emit; `false` otherwise. + virtual bool emit(const json& matched, json& output, const json& inherited) = 0; protected: - json base_emitter_node(std::string layout, std::string title, std::string tag); + json base_emitter_node(std::string layout, std::string title, std::string tag, bool implicit); - bool reconcile(json node, std::filesystem::path root_path, std::filesystem::path path, json& out_reconciled); + bool reconcile(json node, + std::filesystem::path root_path, + std::filesystem::path path, + json& out_reconciled); - std::string defined_in_file(const std::string& src_path, - const std::filesystem::path& src_root); + std::string defined_in_file(const std::string& src_path, const std::filesystem::path& src_root); std::filesystem::path subcomponent(const std::filesystem::path& src_path, - const std::filesystem::path& src_root); + const std::filesystem::path& src_root); - void maybe_annotate(const json& j, json& node); // make out arg? + // For some reason nlohmann's JSON types aren't happy with my moving them about + // (they always seem to show up null (~) in the final YAML output) + // so converting these to out-arg-based routines will have to wait until I can + // sort that out. + void insert_inherited(const json& inherited, json& node); // make out arg? + void insert_annotations(const json& j, json& node); // make out arg? + void insert_doxygen(const json& j, json& node); // make out arg? std::string format_template_parameters(const json& json, bool with_types); std::string filename_filter(std::string f); std::string filename_truncate(std::string s); - void insert_typedefs(const json& j, json& node); + void insert_typedefs(const json& j, json& node, const json& inherited); + + void check_inline_comments(const json& expected, json& out_merged); bool check_typedefs(const std::string& filepath, const json& have_node, @@ -143,6 +165,8 @@ struct yaml_base_emitter { const std::string& key, const check_proc& proc); + static bool has_inline_field(const json& j, const char* field); + private: template std::filesystem::path dst_path_append(std::filesystem::path p, Arg&& arg, Args&&... args); @@ -206,7 +230,9 @@ std::filesystem::path yaml_base_emitter::dst_path(const json& j, Args&&... args) /**************************************************************************************************/ template -std::filesystem::path yaml_base_emitter::dst_path_append(std::filesystem::path p, Arg&& arg, Args&&... args) { +std::filesystem::path yaml_base_emitter::dst_path_append(std::filesystem::path p, + Arg&& arg, + Args&&... args) { return dst_path_append(dst_path_append(std::move(p), arg), std::forward(args)...); } diff --git a/emitters/yaml_base_emitter_fwd.hpp b/emitters/yaml_base_emitter_fwd.hpp index 674a177..1176c11 100644 --- a/emitters/yaml_base_emitter_fwd.hpp +++ b/emitters/yaml_base_emitter_fwd.hpp @@ -6,15 +6,19 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ #pragma once // stdc++ +#include #include #include +// application +#include "json.hpp" + /**************************************************************************************************/ namespace hyde { @@ -24,16 +28,12 @@ namespace hyde { static constexpr char const* tag_value_missing_k = "__MISSING__"; static constexpr char const* tag_value_optional_k = "__OPTIONAL__"; static constexpr char const* tag_value_deprecated_k = "__DEPRECATED__"; +static constexpr char const* tag_value_inlined_k = "__INLINED__"; static constexpr char const* index_filename_k = "index.md"; /**************************************************************************************************/ -enum class attribute_category { - disabled, - required, - optional, - deprecated -}; +enum class attribute_category { disabled, required, optional, deprecated, inlined }; static constexpr char const* get_tag(attribute_category c) { switch (c) { @@ -43,25 +43,37 @@ static constexpr char const* get_tag(attribute_category c) { return tag_value_optional_k; case attribute_category::deprecated: return tag_value_deprecated_k; + case attribute_category::inlined: + return tag_value_inlined_k; default: throw std::invalid_argument("unexpected attribute category"); } } -static inline bool is_tag(const std::string& s) { - return s.substr(0, 2) == "__"; -} +static inline bool is_tag(const std::string& s) { return s.substr(0, 2) == "__"; } /**************************************************************************************************/ struct emit_options { attribute_category _tested_by{attribute_category::disabled}; bool _ignore_extraneous_files{false}; - bool _fixup_hyde_subfield{false}; }; /**************************************************************************************************/ +struct documentation { + json _json; + std::string _remainder; + bool _error{false}; +}; + +documentation parse_documentation(const std::filesystem::path& path, bool fixup_subfield); + +/// @return `true` on failure to write, `false` otherwise. +bool write_documentation(const documentation& docs, const std::filesystem::path& path); + +/**************************************************************************************************/ + } // namespace hyde /**************************************************************************************************/ diff --git a/emitters/yaml_class_emitter.cpp b/emitters/yaml_class_emitter.cpp index e9239a3..f8fd7ea 100644 --- a/emitters/yaml_class_emitter.cpp +++ b/emitters/yaml_class_emitter.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -43,14 +43,18 @@ bool yaml_class_emitter::do_merge(const std::string& filepath, bool failure{false}; failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "type"); - failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "description"); - failure |= check_scalar_array(filepath, have, expected, nodepath, out_merged, "annotation"); + failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, + "description"); + failure |= + check_scalar_array(filepath, have, expected, nodepath, out_merged, "annotation"); + + check_inline_comments(expected, out_merged); return failure; }); failure |= check_typedefs(filepath, have, expected, "", out_merged); - + failure |= check_object_array( filepath, have, expected, "", out_merged, "methods", "title", [this](const std::string& filepath, const json& have, const json& expected, @@ -59,15 +63,20 @@ bool yaml_class_emitter::do_merge(const std::string& filepath, return function_emitter.do_merge(filepath, have, expected, out_merged); }); + check_inline_comments(expected, out_merged); + return failure; } /**************************************************************************************************/ -bool yaml_class_emitter::emit(const json& j, json& out_emitted) { - json node = base_emitter_node("class", j["name"], "class"); +bool yaml_class_emitter::emit(const json& j, json& out_emitted, const json& inherited) { + json node = base_emitter_node("class", j["name"], "class", has_json_flag(j, "implicit")); node["hyde"]["defined_in_file"] = defined_in_file(j["defined_in_file"], _src_root); - maybe_annotate(j, node); + + insert_inherited(inherited, node["hyde"]); + insert_annotations(j, node["hyde"]); + insert_doxygen(j, node["hyde"]); std::string declaration = format_template_parameters(j, true) + '\n' + static_cast(j["kind"]) + " " + @@ -84,18 +93,23 @@ bool yaml_class_emitter::emit(const json& j, json& out_emitted) { for (const auto& field : j["fields"]) { const std::string& key = field["name"]; auto& field_node = node["hyde"]["fields"][key]; + insert_annotations(field, field_node); + insert_doxygen(field, field_node); + field_node["type"] = static_cast(field["type"]); - field_node["description"] = tag_value_missing_k; - maybe_annotate(field, field_node); + const bool inline_description_exists = + field_node.count("inline") && field_node["inline"].count("description"); + field_node["description"] = + inline_description_exists ? tag_value_inlined_k : tag_value_missing_k; } } - insert_typedefs(j, node); + insert_typedefs(j, node, inherited); - auto dst = dst_path(j, - static_cast(j["name"])); + auto dst = dst_path(j, static_cast(j["name"])); - bool failure = reconcile(std::move(node), _dst_root, std::move(dst) / index_filename_k, out_emitted); + bool failure = + reconcile(std::move(node), _dst_root, std::move(dst) / index_filename_k, out_emitted); const auto& methods = j["methods"]; yaml_function_emitter function_emitter(_src_root, _dst_root, _mode, _options, true); @@ -103,7 +117,7 @@ bool yaml_class_emitter::emit(const json& j, json& out_emitted) { for (auto it = methods.begin(); it != methods.end(); ++it) { function_emitter.set_key(it.key()); auto function_emitted = hyde::json::object(); - failure |= function_emitter.emit(it.value(), function_emitted); + failure |= function_emitter.emit(it.value(), function_emitted, out_emitted.at("hyde")); out_emitted["methods"].push_back(std::move(function_emitted)); } diff --git a/emitters/yaml_class_emitter.hpp b/emitters/yaml_class_emitter.hpp index f9f1825..ffc4552 100644 --- a/emitters/yaml_class_emitter.hpp +++ b/emitters/yaml_class_emitter.hpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ #pragma once @@ -28,7 +28,7 @@ struct yaml_class_emitter : public yaml_base_emitter { emit_options options) : yaml_base_emitter(std::move(src_root), std::move(dst_root), mode, std::move(options)) {} - bool emit(const json& j, json& out_emitted) override; + bool emit(const json& matched, json& output, const json& inherited) override; bool do_merge(const std::string& filepath, const json& have, diff --git a/emitters/yaml_enum_emitter.cpp b/emitters/yaml_enum_emitter.cpp index bce7146..0032cd7 100644 --- a/emitters/yaml_enum_emitter.cpp +++ b/emitters/yaml_enum_emitter.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -36,45 +36,67 @@ bool yaml_enum_emitter::do_merge(const std::string& filepath, [this](const std::string& filepath, const json& have, const json& expected, const std::string& nodepath, json& out_merged) { bool failure{false}; - + failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "name"); - failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "description"); + failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, + "description"); failure |= check_scalar_array(filepath, have, expected, "", out_merged, "values"); - + + check_inline_comments(expected, out_merged); + return failure; }); + check_inline_comments(expected, out_merged); + return failure; } /**************************************************************************************************/ -bool yaml_enum_emitter::emit(const json& j, json& out_emitted) { +bool yaml_enum_emitter::emit(const json& j, json& out_emitted, const json& inherited) { const std::string& name = j["name"]; // Most likely an enum forward declaration. Nothing to document here. if (j["values"].empty()) return true; - json node = base_emitter_node("enumeration", j["name"], "enumeration"); - node["hyde"]["defined_in_file"] = defined_in_file(j["defined_in_file"], _src_root); - maybe_annotate(j, node); - + json base_node = + base_emitter_node("enumeration", j["name"], "enumeration", has_json_flag(j, "implicit")); + json& node = base_node["hyde"]; + + insert_inherited(inherited, node); + insert_annotations(j, node); + insert_doxygen(j, node); + + if (has_inline_field(node, "owner")) { + node["owner"] = tag_value_inlined_k; + } + + if (has_inline_field(node, "brief")) { + node["brief"] = tag_value_inlined_k; + } + + node["defined_in_file"] = defined_in_file(j["defined_in_file"], _src_root); + std::string filename; for (const auto& ns : j["namespaces"]) { const std::string& namespace_str = ns; - node["hyde"]["namespace"].push_back(namespace_str); + node["namespace"].push_back(namespace_str); filename += namespace_str + "::"; } filename = filename_filter(std::move(filename) + name) + ".md"; for (const auto& value : j["values"]) { json cur_value; + insert_doxygen(value, cur_value); + cur_value["name"] = value["name"]; - cur_value["description"] = tag_value_missing_k; - node["hyde"]["values"].push_back(std::move(cur_value)); + cur_value["description"] = + has_inline_field(cur_value, "description") ? tag_value_inlined_k : tag_value_missing_k; + node["values"].push_back(std::move(cur_value)); } - return reconcile(std::move(node), _dst_root, dst_path(j) / filename, out_emitted); + return reconcile(std::move(base_node), _dst_root, dst_path(j) / filename, out_emitted); } /**************************************************************************************************/ diff --git a/emitters/yaml_enum_emitter.hpp b/emitters/yaml_enum_emitter.hpp index ae5784f..db04268 100644 --- a/emitters/yaml_enum_emitter.hpp +++ b/emitters/yaml_enum_emitter.hpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ #pragma once @@ -28,7 +28,7 @@ struct yaml_enum_emitter : public yaml_base_emitter { emit_options options) : yaml_base_emitter(std::move(src_root), std::move(dst_root), mode, std::move(options)) {} - bool emit(const json& j, json& out_emitted) override; + bool emit(const json& matched, json& output, const json& inherited) override; bool do_merge(const std::string& filepath, const json& have, diff --git a/emitters/yaml_function_emitter.cpp b/emitters/yaml_function_emitter.cpp index c68fb77..f195552 100644 --- a/emitters/yaml_function_emitter.cpp +++ b/emitters/yaml_function_emitter.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -37,14 +37,23 @@ bool yaml_function_emitter::do_merge(const std::string& filepath, const std::string& nodepath, json& out_merged) { bool failure{false}; - failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "description"); - failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "signature_with_names"); - failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "return"); + failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, + "description"); + failure |= check_scalar(filepath, have, expected, nodepath, out_merged, + "signature_with_names"); + + if (!has_json_flag(expected, "is_ctor") && !has_json_flag(expected, "is_dtor")) { + failure |= + check_editable_scalar(filepath, have, expected, nodepath, out_merged, "return"); + } + if (_options._tested_by != hyde::attribute_category::disabled) { - failure |= check_editable_scalar_array(filepath, have, expected, nodepath, out_merged, "tested_by"); + failure |= check_editable_scalar_array(filepath, have, expected, nodepath, + out_merged, "tested_by"); } - - failure |= check_scalar_array(filepath, have, expected, nodepath, out_merged, "annotation"); + + failure |= + check_scalar_array(filepath, have, expected, nodepath, out_merged, "annotation"); failure |= check_object_array( filepath, have, expected, nodepath, out_merged, "arguments", "name", @@ -54,33 +63,50 @@ bool yaml_function_emitter::do_merge(const std::string& filepath, failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "name"); failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "type"); - failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "description"); - failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "unnamed"); + failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, + "description"); + failure |= + check_scalar(filepath, have, expected, nodepath, out_merged, "unnamed"); + + check_inline_comments(expected, out_merged); return failure; }); + check_inline_comments(expected, out_merged); + return failure; }); + // REVISIT (fosterbrereton) : Roll-up the owners and briefs/descriptions to see if we can derive + // a set of inline values here from inline values used in the function definition(s). + + check_inline_comments(expected, out_merged); + return failure; } /**************************************************************************************************/ -bool yaml_function_emitter::emit(const json& jsn, json& out_emitted) { +bool yaml_function_emitter::emit(const json& jsn, json& out_emitted, const json& inherited) { std::filesystem::path dst; std::string name; std::string filename; std::string defined_path; - std::size_t i{0}; json overloads = json::object(); + bool first{true}; bool is_ctor{false}; bool is_dtor{false}; + bool all_compiler_managed{true}; std::size_t count(jsn.size()); + std::size_t inline_description_count{0}; + json last_inline_description; + std::size_t inline_brief_count{0}; + json last_inline_brief; + std::vector overload_owners; for (const auto& overload : jsn) { - if (!i) { + if (first) { dst = dst_path(overload); // always the unqualified name, as free functions may be defined // over different namespaces. @@ -88,23 +114,74 @@ bool yaml_function_emitter::emit(const json& jsn, json& out_emitted) { // prefix to keep free-function from colliding with class member (e.g., `swap`) filename = (_as_methods ? "m_" : "f_") + filename_filter(name); defined_path = defined_in_file(overload["defined_in_file"], _src_root); - if (overload.count("is_ctor") && overload["is_ctor"]) is_ctor = true; - if (overload.count("is_dtor") && overload["is_dtor"]) is_dtor = true; + is_ctor = has_json_flag(overload, "is_ctor"); + is_dtor = has_json_flag(overload, "is_dtor"); + first = false; } const std::string& key = static_cast(overload["signature"]); - overloads[key]["signature_with_names"] = overload["signature_with_names"]; - // description is now optional when there is a singular variant. - overloads[key]["description"] = count > 1 ? tag_value_missing_k : tag_value_optional_k; - overloads[key]["return"] = tag_value_optional_k; + auto& destination = overloads[key]; + + // If there are any in-source (a.k.a. Doxygen) comments, insert them into + // the node *first* so we can use them to decide if subsequent Hyde fields + // can be deferred. + insert_doxygen(overload, destination); + + // The intent of these checks is to roll up brief/description details that were + // entered inline to the top-level file that documents this function and all of its + // overrides. Note that a given override does _not_ require a `brief` value, but + // _may_ require a `description` value if there is more than one override (otherwise + // the description is optional, falling back to the top-level `brief` for the functions + // documentation). I foresee *a lot* of conflation between `brief` and `description` + // as developers document their code, so we'll have to track both of these values as if + // they are the same to make it as easy as possible to bubble up information. + if (destination.count("inline") && destination["inline"].count("brief")) { + ++inline_brief_count; + last_inline_brief = destination["inline"]["brief"]; + } + if (destination.count("inline") && destination["inline"].count("description")) { + ++inline_description_count; + last_inline_description = destination["inline"]["description"]; + } + if (destination.count("inline") && destination["inline"].count("owner")) { + const auto& owners = destination["inline"]["owner"]; + if (owners.is_string()) { + overload_owners.push_back(owners.get()); + } else if (owners.is_array()) { + for (const auto& owner : owners) { + overload_owners.push_back(owner.get()); + } + } + } + + destination["signature_with_names"] = overload["signature_with_names"]; + + // description is now optional when there is a singular variant, or when the overload + // is compiler-managed (implicit, =default, or =delete) + const bool has_associated_inline = has_inline_field(destination, "description"); + const bool is_implicit = has_json_flag(overload, "implicit"); + const bool is_defaulted = has_json_flag(overload, "defaulted"); + const bool is_deleted = has_json_flag(overload, "deleted"); + const bool is_compiler_managed = is_implicit || is_defaulted || is_deleted; + const bool is_optional = count <= 1 || is_compiler_managed; + destination["description"] = has_associated_inline ? tag_value_inlined_k : + is_optional ? tag_value_optional_k : + tag_value_missing_k; + + if (!is_ctor && !is_dtor) { + destination["return"] = tag_value_optional_k; + } + if (_options._tested_by != hyde::attribute_category::disabled) { - overloads[key]["tested_by"] = hyde::get_tag(_options._tested_by); + destination["tested_by"] = is_compiler_managed ? tag_value_optional_k : hyde::get_tag(_options._tested_by); } - maybe_annotate(overload, overloads[key]); + insert_annotations(overload, destination); + + all_compiler_managed &= is_compiler_managed; if (!overload["arguments"].empty()) { std::size_t j{0}; - auto& args = overloads[key]["arguments"]; + auto& args = destination["arguments"]; for (const auto& arg : overload["arguments"]) { auto& cur_arg = args[j]; const std::string& name = arg["name"]; @@ -112,24 +189,69 @@ bool yaml_function_emitter::emit(const json& jsn, json& out_emitted) { cur_arg["name"] = unnamed ? "unnamed-" + std::to_string(j) : name; cur_arg["type"] = arg["type"]; cur_arg["description"] = tag_value_optional_k; - if (unnamed) cur_arg["unnamed"] = true; + if (unnamed) { + cur_arg["unnamed"] = true; + } ++j; } } - - ++i; } json node = base_emitter_node(_as_methods ? "method" : "function", name, - _as_methods ? "method" : "function"); - node["hyde"]["defined_in_file"] = defined_path; - + _as_methods ? "method" : "function", + all_compiler_managed); + + insert_inherited(inherited, node["hyde"]); + + // If the function being emitted is either the ctor or dtor of a class, + // the high-level `brief` is optional, as their default implementations + // should require no additional commenatary beyond that which is provided + // on an overload-by-overload basis. + if (is_ctor || is_dtor) { + node["hyde"]["brief"] = tag_value_optional_k; + } + + // Here we roll-up the brief(s) and description(s) from the function overloads. + // The complication here is that `description` may be required for the overloads, + // but `brief` is not. However at the top level, `brief` _is_ required, and + // `description` is not. So if there is one brief _or_ description, use that + // as the inline brief for the top-level docs. (If there is one of each, brief + // wins.) Beyond that, if there are multiple briefs and multiple descriptions, + // we'll put some pat statement into the brief indicating as much. Finally, if + // at least one overload has an inline `brief`, then the top-level `brief` is + // marked inlined. + if (inline_brief_count == 1) { + node["hyde"]["inline"]["brief"] = last_inline_brief; + node["hyde"]["brief"] = tag_value_inlined_k; + } else if (inline_description_count == 1) { + node["hyde"]["inline"]["brief"] = last_inline_description; + node["hyde"]["brief"] = tag_value_inlined_k; + } else if (inline_brief_count > 1 || inline_description_count > 1) { + node["hyde"]["inline"]["brief"] = "_multiple descriptions_"; + node["hyde"]["brief"] = tag_value_inlined_k; + } + + // Round up any overload owners into an inline top-level owner field, + // and then set the hyde owner field to inlined. + if (!overload_owners.empty()) { + std::sort(overload_owners.begin(), overload_owners.end()); + overload_owners.erase(std::unique(overload_owners.begin(), overload_owners.end()), + overload_owners.end()); + json::array_t owners; + std::copy(overload_owners.begin(), overload_owners.end(), std::back_inserter(owners)); + node["hyde"]["inline"]["owner"] = std::move(owners); + node["hyde"]["owner"] = tag_value_inlined_k; + } else if (node["hyde"].count("inline") && node["hyde"]["inline"].count("owner")) { + node["hyde"]["owner"] = tag_value_inlined_k; + } + + // All overloads will have the same namespace if (!_as_methods && jsn.size() > 0) { - // All overloads will have the same namespace for (const auto& ns : jsn.front()["namespaces"]) node["hyde"]["namespace"].push_back(static_cast(ns)); } - + + node["hyde"]["defined_in_file"] = defined_path; node["hyde"]["overloads"] = std::move(overloads); if (is_ctor) node["hyde"]["is_ctor"] = true; if (is_dtor) node["hyde"]["is_dtor"] = true; diff --git a/emitters/yaml_function_emitter.hpp b/emitters/yaml_function_emitter.hpp index 0f77188..7461338 100644 --- a/emitters/yaml_function_emitter.hpp +++ b/emitters/yaml_function_emitter.hpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ #pragma once @@ -32,7 +32,7 @@ struct yaml_function_emitter : public yaml_base_emitter { void set_key(std::string key) { _key = std::move(key); } - bool emit(const json& j, json& out_emitted) override; + bool emit(const json& matched, json& output, const json& inherited) override; bool do_merge(const std::string& filepath, const json& have, diff --git a/emitters/yaml_library_emitter.cpp b/emitters/yaml_library_emitter.cpp index 3066308..1c8e4d9 100644 --- a/emitters/yaml_library_emitter.cpp +++ b/emitters/yaml_library_emitter.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -37,8 +37,9 @@ bool yaml_library_emitter::do_merge(const std::string& filepath, /**************************************************************************************************/ -bool yaml_library_emitter::emit(const json&, json& out_emitted) { - json node = base_emitter_node("library", tag_value_missing_k, "library"); +bool yaml_library_emitter::emit(const json& j, json& out_emitted, const json&) { + json node = + base_emitter_node("library", tag_value_missing_k, "library", has_json_flag(j, "implicit")); node["hyde"]["library-type"] = "library"; node["hyde"]["icon"] = tag_value_missing_k; node["hyde"]["tab"] = tag_value_missing_k; diff --git a/emitters/yaml_library_emitter.hpp b/emitters/yaml_library_emitter.hpp index 3059776..cc7f420 100644 --- a/emitters/yaml_library_emitter.hpp +++ b/emitters/yaml_library_emitter.hpp @@ -6,10 +6,9 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ - #pragma once // application @@ -27,9 +26,10 @@ struct yaml_library_emitter : public yaml_base_emitter { std::filesystem::path dst_root, yaml_mode mode, emit_options options) - : yaml_base_emitter(std::move(src_root), std::move(dst_root), mode, std::move(options), true) {} + : yaml_base_emitter( + std::move(src_root), std::move(dst_root), mode, std::move(options), true) {} - bool emit(const json& j, json& out_emitted) override; + bool emit(const json& matched, json& output, const json& inherited) override; bool do_merge(const std::string& filepath, const json& have, diff --git a/emitters/yaml_sourcefile_emitter.cpp b/emitters/yaml_sourcefile_emitter.cpp index c194c47..d414a7c 100644 --- a/emitters/yaml_sourcefile_emitter.cpp +++ b/emitters/yaml_sourcefile_emitter.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -35,12 +35,14 @@ bool yaml_sourcefile_emitter::do_merge(const std::string& filepath, /**************************************************************************************************/ -bool yaml_sourcefile_emitter::emit(const json& j, json& out_emitted) { - const auto sub_path = subcomponent(static_cast(j["paths"]["src_path"]), _src_root); - json node = base_emitter_node("library", sub_path.string(), "sourcefile"); +bool yaml_sourcefile_emitter::emit(const json& j, json& out_emitted, const json& inherited) { + const auto sub_path = + subcomponent(static_cast(j["paths"]["src_path"]), _src_root); + json node = + base_emitter_node("library", sub_path.string(), "sourcefile", has_json_flag(j, "implicit")); node["hyde"]["library-type"] = "sourcefile"; - insert_typedefs(j, node); + insert_typedefs(j, node, inherited); _sub_dst = dst_path(j, sub_path); diff --git a/emitters/yaml_sourcefile_emitter.hpp b/emitters/yaml_sourcefile_emitter.hpp index ba7594a..8e66b98 100644 --- a/emitters/yaml_sourcefile_emitter.hpp +++ b/emitters/yaml_sourcefile_emitter.hpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ #pragma once @@ -28,7 +28,7 @@ struct yaml_sourcefile_emitter : public yaml_base_emitter { emit_options options) : yaml_base_emitter(std::move(src_root), std::move(dst_root), mode, std::move(options)) {} - bool emit(const json& j, json& out_emitted) override; + bool emit(const json& matched, json& output, const json& inherited) override; bool do_merge(const std::string& filepath, const json& have, diff --git a/include/json_fwd.hpp b/include/json_fwd.hpp index e023c71..1d04651 100644 --- a/include/json_fwd.hpp +++ b/include/json_fwd.hpp @@ -11,6 +11,9 @@ written permission of Adobe. #pragma once +// stdc++ +#include + // nlohmann/json #include "nlohmann/json_fwd.hpp" @@ -22,6 +25,8 @@ namespace hyde { using json = nlohmann::json; +using optional_json = std::optional; + /**************************************************************************************************/ } // namespace hyde diff --git a/matchers/class_matcher.cpp b/matchers/class_matcher.cpp index 63dc1b0..e217423 100644 --- a/matchers/class_matcher.cpp +++ b/matchers/class_matcher.cpp @@ -16,12 +16,14 @@ written permission of Adobe. #include // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/class_matcher.hpp b/matchers/class_matcher.hpp index 64a6cb7..cc510d4 100644 --- a/matchers/class_matcher.hpp +++ b/matchers/class_matcher.hpp @@ -12,10 +12,12 @@ written permission of Adobe. #pragma once // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/enum_matcher.cpp b/matchers/enum_matcher.cpp index b69da06..7117dce 100644 --- a/matchers/enum_matcher.cpp +++ b/matchers/enum_matcher.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -16,11 +16,13 @@ written permission of Adobe. #include // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" @@ -41,8 +43,8 @@ void EnumInfo::run(const MatchFinder::MatchResult& Result) { if (!info_opt) return; auto info = std::move(*info_opt); - //info["scoped"] = enumeration->isScoped(); - //info["fixed"] = enumeration->isFixed(); + // info["scoped"] = enumeration->isScoped(); + // info["fixed"] = enumeration->isFixed(); info["type"] = hyde::to_string(enumeration, enumeration->getIntegerType()); info["values"] = json::array(); @@ -51,6 +53,10 @@ void EnumInfo::run(const MatchFinder::MatchResult& Result) { enumerator["name"] = p->getNameAsString(); + if (auto comments = ProcessComments(p)) { + enumerator["comments"] = std::move(*comments); + } + info["values"].push_back(std::move(enumerator)); } diff --git a/matchers/enum_matcher.hpp b/matchers/enum_matcher.hpp index 14fe3f2..817a410 100644 --- a/matchers/enum_matcher.hpp +++ b/matchers/enum_matcher.hpp @@ -12,10 +12,12 @@ written permission of Adobe. #pragma once // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/function_matcher.cpp b/matchers/function_matcher.cpp index de8ad56..70eca72 100644 --- a/matchers/function_matcher.cpp +++ b/matchers/function_matcher.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -16,12 +16,14 @@ written permission of Adobe. #include // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Lex/Lexer.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/function_matcher.hpp b/matchers/function_matcher.hpp index 5f30ac0..5003a1b 100644 --- a/matchers/function_matcher.hpp +++ b/matchers/function_matcher.hpp @@ -12,10 +12,12 @@ written permission of Adobe. #pragma once // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/namespace_matcher.cpp b/matchers/namespace_matcher.cpp index 82f7447..505cf88 100644 --- a/matchers/namespace_matcher.cpp +++ b/matchers/namespace_matcher.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -16,11 +16,13 @@ written permission of Adobe. #include // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/namespace_matcher.hpp b/matchers/namespace_matcher.hpp index 543ed5c..c4937b6 100644 --- a/matchers/namespace_matcher.hpp +++ b/matchers/namespace_matcher.hpp @@ -12,10 +12,12 @@ written permission of Adobe. #pragma once // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/typealias_matcher.cpp b/matchers/typealias_matcher.cpp index ced7fec..e13753f 100644 --- a/matchers/typealias_matcher.cpp +++ b/matchers/typealias_matcher.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -16,11 +16,13 @@ written permission of Adobe. #include // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/typealias_matcher.hpp b/matchers/typealias_matcher.hpp index 2a81d73..0522611 100644 --- a/matchers/typealias_matcher.hpp +++ b/matchers/typealias_matcher.hpp @@ -12,10 +12,12 @@ written permission of Adobe. #pragma once // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/typedef_matcher.cpp b/matchers/typedef_matcher.cpp index 6954fcd..17f1723 100644 --- a/matchers/typedef_matcher.cpp +++ b/matchers/typedef_matcher.cpp @@ -6,7 +6,7 @@ NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior -written permission of Adobe. +written permission of Adobe. */ // identity @@ -16,11 +16,13 @@ written permission of Adobe. #include // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/typedef_matcher.hpp b/matchers/typedef_matcher.hpp index a75f36c..2f33b4e 100644 --- a/matchers/typedef_matcher.hpp +++ b/matchers/typedef_matcher.hpp @@ -12,10 +12,12 @@ written permission of Adobe. #pragma once // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" diff --git a/matchers/utilities.cpp b/matchers/utilities.cpp index cefde2e..4925e66 100644 --- a/matchers/utilities.cpp +++ b/matchers/utilities.cpp @@ -13,32 +13,48 @@ written permission of Adobe. #include "utilities.hpp" // stdc++ +#include #include #include // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Comment.h" #include "clang/AST/Type.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/ArrayRef.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" using namespace clang; using namespace clang::ast_matchers; +using namespace clang::comments; /**************************************************************************************************/ namespace { /**************************************************************************************************/ +// TODO: Make this `std::string_view trim_front(std::string_view src)` +void trim_front(std::string& text) { + std::size_t trim_count(0); + for (; trim_count < text.size(); ++trim_count) + if (!(std::isspace(text[trim_count]) || text[trim_count] == '\n')) break; + + text.erase(0, trim_count); +} + +/**************************************************************************************************/ +// TODO: Make this `std::string_view trim_back(std::string_view src)` void trim_back(std::string& src) { std::size_t start(src.size()); @@ -48,6 +64,13 @@ void trim_back(std::string& src) { src.erase(start, std::string::npos); } +/**************************************************************************************************/ +// TODO: Make this `std::string_view chomp(std::string_view src)` +void chomp(std::string& src) { + trim_front(src); + trim_back(src); +} + /**************************************************************************************************/ // If `as_token` is true, the end of the range is assumed to be the beginning of a token, and the // range will be extended to the end of that token before it is serialized. Otherwise, the end of @@ -55,6 +78,7 @@ void trim_back(std::string& src) { std::string to_string(const ASTContext* n, SourceRange range, bool as_token) { const auto char_range = as_token ? clang::CharSourceRange::getTokenRange(range) : clang::CharSourceRange::getCharRange(range); + // TODO: Make `result` a std::string_view std::string result = Lexer::getSourceText(char_range, n->getSourceManager(), n->getLangOpts()).str(); trim_back(result); @@ -321,6 +345,276 @@ hyde::json GetParents(const ASTContext* n, const Decl* d) { /**************************************************************************************************/ +inline std::string_view to_string_view(StringRef string) { + return std::string_view(string.data(), string.size()); +} + +/**************************************************************************************************/ + +inline std::string_view to_string_view(ParamCommandComment::PassDirection x) { + // clang-format off + switch (x) { + case ParamCommandComment::PassDirection::In: return "in"; + case ParamCommandComment::PassDirection::InOut: return "inout"; + case ParamCommandComment::PassDirection::Out: return "out"; + } + // clang-format on +} + +/**************************************************************************************************/ + +hyde::optional_json ProcessComment(const ASTContext& n, + const FullComment* full_comment, + const Comment* comment) { + const CommandTraits& commandTraits = n.getCommentCommandTraits(); + hyde::json::object_t result; + + const auto process_comment_args = [](auto& comment_with_args) -> hyde::optional_json { + const unsigned arg_count = comment_with_args.getNumArgs(); + if (arg_count == 0) return std::nullopt; + + hyde::json::array_t result; + + for (unsigned i{0}; i < arg_count; ++i) { + hyde::json::object_t args_entry; + args_entry["text"] = to_string_view(comment_with_args.getArgText(i)); + result.push_back(std::move(args_entry)); + } + + return result; + }; + + const auto process_comment_children = + [&n, &full_comment](auto& comment_with_children) -> hyde::optional_json { + auto first = comment_with_children.child_begin(); + auto last = comment_with_children.child_end(); + + if (first == last) return std::nullopt; + + hyde::json::array_t result; + + while (first != last) { + if (auto entry = ProcessComment(n, full_comment, *first++)) { + result.emplace_back(std::move(*entry)); + } + } + + return result; + }; + + // If the comment only has one child and it's a paragraph comment, + // take the text from that child and move it into the parent, then + // delete all the children (which is just the now-empty paragraph.) + const auto roll_up_single_paragraph_child = + [](hyde::json::object_t json) -> hyde::json::object_t { + if (json.count("children") != 1) return json; + auto& children = json["children"]; + auto& first_child = *children.begin(); + if (first_child["kind"] != "ParagraphComment") return json; + json["text"] = std::move(first_child["text"]); + json.erase("children"); + return json; + }; + + const auto post_process_hyde_command = [](hyde::json::object_t json) -> hyde::json::object_t { + if (json["name"].get() != "hyde") return json; + + const std::string text = json["text"].get(); + const auto first_space = text.find_first_of(" \t\n\r\v\f"); // \v and \f? Really? + + if (first_space == std::string::npos) return json; + + const std::string new_command = "hyde" + text.substr(0, first_space); + const std::string new_text = text.substr(first_space + 1); + + json["name"] = std::move(new_command); + json["text"] = std::move(new_text); + + return json; + }; + + switch (comment->getCommentKind()) { + case Comment::NoCommentKind: + break; + case Comment::BlockCommandCommentKind: { + const BlockCommandComment* block_command_comment = + llvm::dyn_cast_or_null(comment); + assert(block_command_comment); + + result["name"] = to_string_view(block_command_comment->getCommandName(commandTraits)); + + if (auto args = process_comment_args(*block_command_comment)) { + result["args"] = std::move(*args); + } + + if (auto children = process_comment_children(*block_command_comment)) { + result["children"] = std::move(*children); + } + + result = roll_up_single_paragraph_child(std::move(result)); + + // Do further post-processing if the comment is a hyde command. + result = post_process_hyde_command(std::move(result)); + } break; + case Comment::ParamCommandCommentKind: { + const ParamCommandComment* param_command_comment = + llvm::dyn_cast_or_null(comment); + assert(param_command_comment); + + result["direction"] = to_string_view(param_command_comment->getDirection()); + result["direction_explicit"] = param_command_comment->isDirectionExplicit(); + result["index_valid"] = param_command_comment->isParamIndexValid(); + result["vararg_param"] = param_command_comment->isVarArgParam(); + + if (param_command_comment->hasParamName()) { + result["name"] = to_string_view(param_command_comment->getParamName(full_comment)); + } + + if (auto children = process_comment_children(*param_command_comment)) { + result["children"] = std::move(*children); + } + + result = roll_up_single_paragraph_child(std::move(result)); + } break; + case Comment::TParamCommandCommentKind: { + const TParamCommandComment* tparam_command_comment = + llvm::dyn_cast_or_null(comment); + assert(tparam_command_comment); + + if (auto children = process_comment_children(*tparam_command_comment)) { + result["children"] = std::move(*children); + } + + result = roll_up_single_paragraph_child(std::move(result)); + } break; + case Comment::VerbatimBlockCommentKind: { + const VerbatimBlockComment* verbatim_block_comment = + llvm::dyn_cast_or_null(comment); + assert(verbatim_block_comment); + + if (auto children = process_comment_children(*verbatim_block_comment)) { + result["children"] = std::move(*children); + } + } break; + case Comment::VerbatimLineCommentKind: { + const VerbatimLineComment* verbatim_line_comment = + llvm::dyn_cast_or_null(comment); + assert(verbatim_line_comment); + + if (auto children = process_comment_children(*verbatim_line_comment)) { + result["children"] = std::move(*children); + } + } break; + case Comment::ParagraphCommentKind: { + const ParagraphComment* paragraph_comment = + llvm::dyn_cast_or_null(comment); + assert(paragraph_comment); + + // Paragraph comments have only been observed containing `TextComment`s, + // one per line in the paragraph. Some formatting gets sucked into the + // `TextComment`s, so processing of each is needed. + + auto first = paragraph_comment->child_begin(); + auto last = paragraph_comment->child_end(); + std::string paragraph; + + while (first != last) { + if (const TextComment* text_comment = + llvm::dyn_cast_or_null(*first++)) { + std::string line(text_comment->getText()); + chomp(line); + if (line.empty()) continue; + if (!paragraph.empty()) paragraph += ' '; // add one space between lines. + paragraph += std::move(line); + } + } + + if (!paragraph.empty()) { + result["text"] = std::move(paragraph); + } + } break; + case Comment::FullCommentKind: { + const FullComment* full_comment_inner = llvm::dyn_cast_or_null(comment); + assert(full_comment_inner); + + if (auto children = process_comment_children(*full_comment_inner)) { + result["children"] = std::move(*children); + } + } break; + case Comment::HTMLEndTagCommentKind: + break; + case Comment::HTMLStartTagCommentKind: + break; + case Comment::InlineCommandCommentKind: { + const InlineCommandComment* inline_command_comment = + llvm::dyn_cast_or_null(comment); + assert(inline_command_comment); + + result["name"] = to_string_view(inline_command_comment->getCommandName(commandTraits)); + + if (auto args = process_comment_args(*inline_command_comment)) { + result["args"] = std::move(*args); + } + } break; + case Comment::TextCommentKind: { + const TextComment* text_comment = llvm::dyn_cast_or_null(comment); + assert(text_comment); + + if (auto children = process_comment_children(*text_comment)) { + result["children"] = std::move(*children); + } + + result["text"] = to_string_view(text_comment->getText()); + } break; + case Comment::VerbatimBlockLineCommentKind: + break; + } + + if (result.empty()) { + return std::nullopt; + } + + result["kind"] = comment->getCommentKindName(); + + return result; +} + +const char* remap_kind_key(std::string_view key) { + // clang-format off + if (key == "BlockCommandComment") return "command"; + else if (key == "FullComment") return "full"; + else if (key == "HTMLEndTagComment") return "html_end"; + else if (key == "HTMLStartTagComment") return "html_start"; + else if (key == "InlineCommandComment") return "command_inline"; + else if (key == "NoComment") return "none"; + else if (key == "ParagraphComment") return "paragraph"; + else if (key == "ParamCommandComment") return "param"; + else if (key == "TextComment") return "text"; + else if (key == "TParamCommandComment") return "tparam"; + else if (key == "VerbatimBlockComment") return "vblock"; + else if (key == "VerbatimBlockLineComment") return "vblockline"; + else if (key == "VerbatimLineComment") return "vline"; + // clang-format on + + return key.data(); +} + +hyde::optional_json group_comments_by_kind(hyde::json comments) { + if (!comments.is_array()) return std::nullopt; + + hyde::json result; + + for (auto& comment : comments.get()) { + const char* new_key = remap_kind_key(comment["kind"].get()); + comment.erase("kind"); + result[new_key].push_back(std::move(comment)); + } + + return result; +} + +/**************************************************************************************************/ + } // namespace /**************************************************************************************************/ @@ -341,8 +635,8 @@ json GetParentCXXRecords(const ASTContext* n, const Decl* d) { /**************************************************************************************************/ -std::optional DetailCXXRecordDecl(const hyde::processing_options& options, - const clang::CXXRecordDecl* cxx) { +optional_json DetailCXXRecordDecl(const hyde::processing_options& options, + const clang::CXXRecordDecl* cxx) { auto info_opt = StandardDeclInfo(options, cxx); if (!info_opt) return info_opt; auto info = std::move(*info_opt); @@ -398,7 +692,7 @@ json GetTemplateParameters(const ASTContext* n, const clang::TemplateDecl* d) { /**************************************************************************************************/ -std::optional DetailFunctionDecl(const hyde::processing_options& options, const FunctionDecl* f) { +optional_json DetailFunctionDecl(const hyde::processing_options& options, const FunctionDecl* f) { auto info_opt = StandardDeclInfo(options, f); if (!info_opt) return info_opt; auto info = std::move(*info_opt); @@ -412,6 +706,7 @@ std::optional DetailFunctionDecl(const hyde::processing_options& options, // redo the name and qualified name for this entry, now that we have a proper function info["name"] = info["signature"]; info["qualified_name"] = GetSignature(n, f, signature_options::fully_qualified); + info["implicit"] = f->isImplicit(); if (f->isConstexpr()) info["constexpr"] = true; @@ -427,30 +722,30 @@ std::optional DetailFunctionDecl(const hyde::processing_options& options, break; } - auto visibility = f->getVisibility(); - switch (visibility) { - case Visibility::HiddenVisibility: - info["visibility"] = "hidden"; - break; - case Visibility::DefaultVisibility: - info["visibility"] = "default"; - break; - case Visibility::ProtectedVisibility: - info["visibility"] = "protected"; - break; - } - LinkageInfo linkage_info = f->getLinkageAndVisibility(); - info["visibility_explicit"] = linkage_info.isVisibilityExplicit() ? "true" : "false"; + auto visibility = f->getVisibility(); + switch (visibility) { + case Visibility::HiddenVisibility: + info["visibility"] = "hidden"; + break; + case Visibility::DefaultVisibility: + info["visibility"] = "default"; + break; + case Visibility::ProtectedVisibility: + info["visibility"] = "protected"; + break; + } + LinkageInfo linkage_info = f->getLinkageAndVisibility(); + info["visibility_explicit"] = linkage_info.isVisibilityExplicit() ? "true" : "false"; if (const auto* method = llvm::dyn_cast_or_null(f)) { - if (method->isConst()) info["const"] = true; - if (method->isVolatile()) info["volatile"] = true; - if (method->isStatic()) info["static"] = true; - if (method->isDeletedAsWritten()) info["delete"] = true; - if (method->isExplicitlyDefaulted()) info["default"] = true; + info["const"] = method->isConst(); + info["volatile"] = method->isVolatile(); + info["static"] = method->isStatic(); + info["deleted"] = method->isDeletedAsWritten(); + info["defaulted"] = method->isExplicitlyDefaulted(); - bool is_ctor = isa(method); - bool is_dtor = isa(method); + const bool is_ctor = isa(method); + const bool is_dtor = isa(method); if (is_ctor || is_dtor) { if (is_ctor) { @@ -458,15 +753,16 @@ std::optional DetailFunctionDecl(const hyde::processing_options& options, if (auto ctor_decl = llvm::dyn_cast_or_null(method)) { auto specifier = ctor_decl->getExplicitSpecifier(); - if (specifier.isExplicit()) info["explicit"] = true; + info["explicit"] = specifier.isExplicit(); } + } else /*is_dtor*/ { + info["is_dtor"] = true; } - if (is_dtor) info["is_dtor"] = true; } if (auto conversion_decl = llvm::dyn_cast_or_null(method)) { auto specifier = conversion_decl->getExplicitSpecifier(); - if (specifier.isExplicit()) info["explicit"] = true; + info["explicit"] = specifier.isExplicit(); } } @@ -580,8 +876,7 @@ std::string PostProcessTypeParameter(const clang::Decl* decl, std::string type) auto qualType = context.getTemplateTypeParmType( depth, index, template_type->isParameterPack(), template_type); - std::string old_type = - needle + std::to_string(depth) + "-" + std::to_string(index); + std::string old_type = needle + std::to_string(depth) + "-" + std::to_string(index); std::string new_type = hyde::to_string(template_type, qualType); parent_template_types.emplace_back( std::make_pair(std::move(old_type), std::move(new_type))); @@ -647,9 +942,7 @@ std::string ReplaceAll(std::string str, const std::string& substr, const std::st } } -std::string PostProcessSpacing(std::string type) { - return ReplaceAll(type, "> >", ">>"); -} +std::string PostProcessSpacing(std::string type) { return ReplaceAll(type, "> >", ">>"); } /**************************************************************************************************/ @@ -661,6 +954,24 @@ std::string PostProcessType(const clang::Decl* decl, std::string type) { /**************************************************************************************************/ +hyde::optional_json ProcessComments(const Decl* d) { + const ASTContext& n = d->getASTContext(); + const FullComment* full_comment = n.getCommentForDecl(d, nullptr); + + if (!full_comment) return std::nullopt; + + if (auto result = ProcessComment(n, full_comment, full_comment)) { + // The top-level FullComment has only been observed to have + // children and nothing else. Roll up the children as the + // comments, then, and shed the needless wrapper. + return group_comments_by_kind(std::move((*result)["children"])); + } + + return std::nullopt; +} + +/**************************************************************************************************/ + } // namespace hyde /**************************************************************************************************/ diff --git a/matchers/utilities.hpp b/matchers/utilities.hpp index 1ed8f08..8e68cd1 100644 --- a/matchers/utilities.hpp +++ b/matchers/utilities.hpp @@ -11,10 +11,8 @@ written permission of Adobe. #pragma once -// stdc++ -#include - // clang/llvm +// clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" @@ -22,6 +20,7 @@ written permission of Adobe. #include "clang/ASTMatchers/ASTMatchers.h" #include "llvm/ADT/ArrayRef.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings +// clang-format on // application #include "json.hpp" @@ -39,11 +38,11 @@ json GetParentCXXRecords(const clang::ASTContext* n, const clang::Decl* d); json GetTemplateParameters(const clang::ASTContext* n, const clang::TemplateDecl* d); -std::optional DetailFunctionDecl(const hyde::processing_options& options, - const clang::FunctionDecl* f); +optional_json DetailFunctionDecl(const hyde::processing_options& options, + const clang::FunctionDecl* f); -std::optional DetailCXXRecordDecl(const hyde::processing_options& options, - const clang::CXXRecordDecl* cxx); +optional_json DetailCXXRecordDecl(const hyde::processing_options& options, + const clang::CXXRecordDecl* cxx); bool PathCheck(const std::vector& paths, const clang::Decl* d, clang::ASTContext* n); @@ -58,6 +57,9 @@ std::string ReplaceAll(std::string str, const std::string& substr, const std::st // type-parameter-N-M filtering. std::string PostProcessType(const clang::Decl* decl, std::string type); +// Doxygen-style comments. +optional_json ProcessComments(const clang::Decl* d); + /**************************************************************************************************/ inline std::string to_string(clang::AccessSpecifier access) { @@ -86,11 +88,10 @@ inline std::string to_string(const clang::Decl* decl, clang::QualType type) { /**************************************************************************************************/ template -std::optional StandardDeclInfo(const hyde::processing_options& options, - const DeclarationType* d) { +optional_json StandardDeclInfo(const hyde::processing_options& options, const DeclarationType* d) { clang::ASTContext* n = &d->getASTContext(); - if (!PathCheck(options._paths, d, n)) return std::optional(); + if (!PathCheck(options._paths, d, n)) return std::nullopt; json info = json::object(); @@ -99,14 +100,17 @@ std::optional StandardDeclInfo(const hyde::processing_options& options, info["parents"] = GetParentCXXRecords(n, d); info["qualified_name"] = d->getQualifiedNameAsString(); - if (NamespaceBlacklist(options._namespace_blacklist, info)) return std::optional(); + if (NamespaceBlacklist(options._namespace_blacklist, info)) return std::nullopt; auto clang_access = d->getAccess(); - if (!AccessCheck(options._access_filter, clang_access)) return std::optional(); + if (!AccessCheck(options._access_filter, clang_access)) return std::nullopt; + + if (auto comments = ProcessComments(d)) { + info["comments"] = std::move(*comments); + } - if (clang_access != clang::AccessSpecifier::AS_none) - info["access"] = to_string(clang_access); + if (clang_access != clang::AccessSpecifier::AS_none) info["access"] = to_string(clang_access); info["defined_in_file"] = [&] { auto beginLoc = d->getBeginLoc(); diff --git a/sources/main.cpp b/sources/main.cpp index 9a9ebd9..d8c3ecf 100644 --- a/sources/main.cpp +++ b/sources/main.cpp @@ -91,7 +91,7 @@ std::vector make_absolute(std::vector paths) { Command line arguments section. These are intentionally global. See: https://llvm.org/docs/CommandLine.html */ -enum ToolMode { ToolModeJSON, ToolModeYAMLValidate, ToolModeYAMLUpdate }; +enum ToolMode { ToolModeJSON, ToolModeYAMLValidate, ToolModeYAMLUpdate, ToolModeFixupSubfield }; enum ToolDiagnostic { ToolDiagnosticQuiet, ToolDiagnosticVerbose, ToolDiagnosticVeryVerbose }; static llvm::cl::OptionCategory MyToolCategory( "Hyde is a tool to scan library headers to ensure documentation is kept up to\n" @@ -103,7 +103,11 @@ static cl::opt ToolMode( clEnumValN(ToolModeYAMLValidate, "hyde-validate", "Validate existing YAML documentation"), clEnumValN(ToolModeYAMLUpdate, "hyde-update", - "Write updated YAML documentation for missing elements")), + "Write updated YAML documentation for missing elements"), + clEnumValN(ToolModeFixupSubfield, + "hyde-fixup-subfield", + "Fix-up preexisting documentation; move all fields except `layout` and `title` into a `hyde` subfield. Note this mode is unique in that it takes pre-existing documentation as source(s), not a C++ source file.") + ), cl::cat(MyToolCategory)); static cl::opt ToolAccessFilter( cl::desc("Restrict documentation of class elements by their access specifier."), @@ -135,12 +139,6 @@ static cl::opt EmitJson( cl::cat(MyToolCategory), cl::ValueDisallowed); -static cl::opt FixupHydeSubfield( - "fixup-hyde-subfield", - cl::desc("Fix-up preexisting documentation; move all fields except `layout` and `title` into a `hyde` subfield. `hyde-update` mode only."), - cl::cat(MyToolCategory), - cl::ValueDisallowed); - static cl::opt TestedBy( "hyde-tested-by", cl::values( @@ -401,6 +399,22 @@ CommonOptionsParser MakeOptionsParser(int argc, const char** argv) { return std::move(*MaybeOptionsParser); } +/**************************************************************************************************/ +// Hyde may accumulate many "fixups" throughout its lifetime. The first of these so far is to move +// the hyde fields under a `hyde` subfield in the YAML, allowing for other tools' fields to coexist +// under other values in the same have file. +bool fixup_have_file_subfield(const std::filesystem::path& path) { + // Passing `true` is what's actually causing the fixup. + const auto parsed = hyde::parse_documentation(path, true); + const auto failure = parsed._error || hyde::write_documentation(parsed, path); + + if (failure) { + std::cerr << "Failed to fixup " << path << '\n'; + } + + return failure; +} + /**************************************************************************************************/ } // namespace @@ -454,6 +468,18 @@ int main(int argc, const char** argv) try { s.insert(i); } sourcePaths.assign(s.begin(), s.end()); + + if (ToolMode == ToolModeFixupSubfield) { + bool failure{false}; + + for (const auto& path : sourcePaths) { + failure |= fixup_have_file_subfield(path); + } + + // In this mode, once the documentation file has been fixed up, we're done. + return failure ? EXIT_FAILURE : EXIT_SUCCESS; + } + MatchFinder Finder; hyde::processing_options options{sourcePaths, ToolAccessFilter, NamespaceBlacklist, ProcessClassMethods}; @@ -553,25 +579,38 @@ int main(int argc, const char** argv) try { // Specify the hyde preprocessor macro arguments.emplace_back("-DADOBE_TOOL_HYDE=1"); + // Have the driver parse comments. See: + // https://clang.llvm.org/docs/UsersManual.html#comment-parsing-options + // This isn't strictly necessary, as Doxygen comments will be detected + // and parsed regardless. Better to be thorough, though. + arguments.emplace_back("-fparse-all-comments"); + + // Enables some checks built in to the clang driver to ensure comment + // documentation matches whatever it is documenting. We also make it + // an error because the documentation should be accurate when generated. + arguments.emplace_back("-Werror=documentation"); + arguments.emplace_back("-Werror=documentation-deprecated-sync"); + arguments.emplace_back("-Werror=documentation-html"); + arguments.emplace_back("-Werror=documentation-pedantic"); + + // Add hyde-specific commands to the Clang Doxygen parser. For hyde, we'll require the first + // word to be the hyde field (e.g., `@hyde-owner fosterbrereton`.) Because the Doxygen parser + // doesn't consider `-` or `_` as part of the command token, the first word will be + // `-owner` in this case, which gives us something parseable, and it reads + // reasonably in the code as well. + arguments.emplace_back("-fcomment-block-commands=hyde"); + // // Spin up the tool and run it. // ClangTool Tool(OptionsParser.getCompilations(), sourcePaths); - Tool.appendArgumentsAdjuster([](const CommandLineArguments& arguments, StringRef Filename){ - return arguments; - }); - Tool.appendArgumentsAdjuster(OptionsParser.getArgumentsAdjuster()); Tool.appendArgumentsAdjuster( getInsertArgumentAdjuster(arguments, clang::tooling::ArgumentInsertPosition::END)); - Tool.appendArgumentsAdjuster([](const CommandLineArguments& arguments, StringRef Filename){ - return arguments; - }); - if (Tool.run(newFrontendActionFactory(&Finder).get())) throw std::runtime_error("compilation failed."); @@ -609,7 +648,6 @@ int main(int argc, const char** argv) try { hyde::emit_options emit_options; emit_options._tested_by = TestedBy; emit_options._ignore_extraneous_files = IgnoreExtraneousFiles; - emit_options._fixup_hyde_subfield = FixupHydeSubfield; auto out_emitted = hyde::json::object(); output_yaml(std::move(result), std::move(src_root), std::move(dst_root), out_emitted, diff --git a/sources/output_yaml.cpp b/sources/output_yaml.cpp index 39d30c8..1ec318f 100644 --- a/sources/output_yaml.cpp +++ b/sources/output_yaml.cpp @@ -40,20 +40,21 @@ void output_yaml(json j, const emit_options& options) { bool failure{false}; auto& library_emitted = out_emitted; + const json no_inheritance_k; // Process top-level library - yaml_library_emitter(src_root, dst_root, mode, options).emit(j, library_emitted); + yaml_library_emitter(src_root, dst_root, mode, options).emit(j, library_emitted, no_inheritance_k); // Process sourcefile yaml_sourcefile_emitter sourcefile_emitter(src_root, dst_root, mode, options); auto sourcefile_emitted = hyde::json::object(); - failure |= sourcefile_emitter.emit(j, sourcefile_emitted); + failure |= sourcefile_emitter.emit(j, sourcefile_emitted, no_inheritance_k); // Process classes yaml_class_emitter class_emitter(src_root, dst_root, mode, options); for (const auto& c : j["classes"]) { auto class_emitted = hyde::json::object(); - failure |= class_emitter.emit(c, class_emitted); + failure |= class_emitter.emit(c, class_emitted, no_inheritance_k); sourcefile_emitted["classes"].push_back(std::move(class_emitted)); } @@ -61,7 +62,7 @@ void output_yaml(json j, yaml_enum_emitter enum_emitter(src_root, dst_root, mode, options); for (const auto& c : j["enums"]) { auto enum_emitted = hyde::json::object(); - failure |= enum_emitter.emit(c, enum_emitted); + failure |= enum_emitter.emit(c, enum_emitted, no_inheritance_k); sourcefile_emitted["enums"].push_back(std::move(enum_emitted)); } @@ -71,7 +72,7 @@ void output_yaml(json j, for (auto it = functions.begin(); it != functions.end(); ++it) { function_emitter.set_key(it.key()); auto function_emitted = hyde::json::object(); - failure |= function_emitter.emit(it.value(), function_emitted); + failure |= function_emitter.emit(it.value(), function_emitted, no_inheritance_k); sourcefile_emitted["functions"].push_back(std::move(function_emitted)); } diff --git a/test_files/classes.cpp b/test_files/classes.cpp index d6eb7b6..9fcac05 100644 --- a/test_files/classes.cpp +++ b/test_files/classes.cpp @@ -17,82 +17,175 @@ written permission of Adobe. //------------------------------------------------------------------------------------------------------------------------------------------ +/// @brief an example class that demonstrates what Hyde documents. +/// @hyde-owner fosterbrereton class class_example { public: - enum class color { red, green = 42, blue }; + /// Note that nested classes will not inherit their ownership from + /// the class that contains them, so thus need their own `hyde-owner`. + /// @brief an example enumeration within the class. + /// @hyde-owner sean-parent + enum class color { + /// this is an example description for the `red` value. + red, + /// this is an example description for the `green` value. + green = 42, + /// this is an example description for the `blue` value. + blue + }; + /// Note that nested classes will not inherit their ownership from + /// the class that contains them, so thus need their own `hyde-owner`. + /// @brief a class definition contained within `example_class`. + /// @hyde-owner sean-parent struct nested_class_example { + /// member field `_x` within the nested class example. int _x{0}; + + /// member field `_y` within the nested class example. int _y; }; + + /// a nested `typedef` expression. typedef std::string typedef_example; + + /// a nested `using` expression. using using_example = std::string; + + /// default constructor. class_example() = default; + /// an explicit constructor that takes a single `int`. + /// @param x The one integer parameter this routine takes explicit class_example(int x) : _x(x) {} + + /// member function with a trailing return type. + /// @return "`_x`" auto member_function_trailing_return_type() const -> int { return _x; } - void member_function() { _x *= 2; } + /// example member function. + /// @return double the value of `_x`. + auto member_function() { return _x *= 2; } + + /// an overloaded member function that takes one parameter. + /// @brief A series of overloaded functions. + /// @param first the first parameter of the first overload. void overloaded(const std::string& first); + + /// an overloaded member function that takes two parameters. + /// @brief Another brief describing one of the overloaded functions. + /// @param first the first parameter of the second overload. + /// @param second the second parameter of the second overload. void overloaded(const std::string& first, const std::string& second) volatile; + + /// another overloaded member function that takes two parameters. + /// @param first the first parameter of the third overload. + /// @param second the second parameter of the third overload. void overloaded(const std::string& first, std::vector second) const; + + /// an overloaded member function that takes _five_ parameters. + /// @param first the first parameter of the fourth overload. + /// @param second the second parameter of the fourth overload. + /// @param third the third parameter of the fourth overload. + /// @param fourth the fourth parameter of the fourth overload. + /// @param fifth the fifth parameter of the fourth overload. void overloaded( const std::string& first, class_example* second, int third, bool fourth, std::size_t fifth); + + /// an overloaded member function that takes three unnamed parameters. + /// Let it be known that Doxygen doesn't support documenting unnamed parameters at this time. + /// There is a [bug open on the issue](https://github.com/doxygen/doxygen/issues/6926), but as + /// of this writing does not appear to be progressing. void overloaded(const std::string&, class_example*, int); // intentionally unnamed + + /// a deprecated overload that takes zero parameters. [[deprecated]] void overloaded(); + + /// deprecated member function. + /// @param first the first parameter + /// @param second the second parameter [[deprecated]] void deprecated(const std::string& first, class_example* second); - [[deprecated("message")]] void deprecated_with_message(const std::string& s, class_example* f); + /// deprecated member function that contains a compile-time deprecation message. + /// @param s the first parameter + /// @param f the second parameter + [[deprecated("example deprecation message")]] void deprecated_with_message(const std::string& s, class_example* f); + + /// static member variable. static const int _static_member = 0; + /// @brief static member function. + /// @return Zero. + // By which I mean `0`. + // In the sources, this comment is on multiple lines. static int static_method() { return 0; }; + /// templatized member function. template void template_member_function() {} + /// specialization of the above templatized member function. template <> void template_member_function() {} private: + /// some variable that holds an integer. int _x; - [[deprecated("message")]] int _deprecated_member = 0; + + /// a deprecated member variable that contains a message. Apparently this works?! + [[deprecated("example deprecation message")]] int _deprecated_member = 0; + + /// an instance of the nested class example defined earlier. nested_class_example _nested; }; //------------------------------------------------------------------------------------------------------------------------------------------ +/// @brief an example template class with some specializations +/// @hyde-owner fosterbrereton template struct specialization_example { constexpr auto as_tuple() const { return std::forward_as_tuple(); } }; +/// @brief an example `std::int32` specialization +/// @hyde-owner fosterbrereton template <> struct specialization_example { + /// An example typedef using value_type = std::int32_t; - value_type _first{0}; + value_type _first{0}; ///< An example field used in `as_tuple` + /// An example function + /// @return a tuple of the fields of this class constexpr auto as_tuple() const { return std::forward_as_tuple(_first); } }; +/// @brief an example `float` specialization +/// @hyde-owner fosterbrereton template <> struct specialization_example { - using value_type = float; - value_type _first{0}; + using value_type = float; ///< An example typedef + value_type _first{0}; ///< An example field used in `as_tuple` constexpr auto as_tuple() const { return std::forward_as_tuple(_first); } }; //------------------------------------------------------------------------------------------------------------------------------------------ +/// @brief an example template class with a partial specialization. +/// @hyde-owner fosterbrereton template class partial_specialization_example { T1 _first; T2 _second; }; +/// @brief an example `int, T` partial specialization +/// @hyde-owner fosterbrereton template class partial_specialization_example { std::string _first; diff --git a/test_files/comments.cpp b/test_files/comments.cpp new file mode 100644 index 0000000..6a04cf9 --- /dev/null +++ b/test_files/comments.cpp @@ -0,0 +1,68 @@ +/* +Copyright 2018 Adobe +All Rights Reserved. + +NOTICE: Adobe permits you to use, modify, and distribute this file in +accordance with the terms of the Adobe license agreement accompanying +it. If you have received this file from a source other than Adobe, +then your use, modification, or distribution of it requires the prior +written permission of Adobe. +*/ + +// AST dump with +// TERM="" clang --std=c++1z -Xclang -ast-dump -fsyntax-only -fparse-all-comments -fcomment-block-commands=hyde ./comments.cpp + +/// @brief Sample class intended to exhibit docs for compiler-generated routines +/// @hyde-owner fosterbrereton +struct compiler_generated { + compiler_generated() = delete; + compiler_generated(const compiler_generated&) = default; + + /// This definition will force the compiler to auto-generate this class' assignment operator. + void assign(const compiler_generated& rhs) { + *this = rhs; + } +}; + +/// An example struct from which these commands will hang. +/// @brief This is a sample brief. +/// @warning This is a sample warning. +/// @par header This is a sample paragraph. +/// @hyde-owner fosterbrereton +/// @see [Slides](https://llvm.org/devmtg/2012-11/Gribenko_CommentParsing.pdf) from an LLVM dev meeting chat on the comment parsing feature +struct some_struct { + virtual ~some_struct() = delete; + + /// This is a longer description of this function that does things as well as it does. + /// Notice how long this comment is! So impressive. 💥 + /// @brief A function that does a thing, and does it well. + /// @param[in] input an input parameter + /// @param[in,out] input_output a bidirectional parameter + /// @param[out] output an output parameter + /// @pre An example precondition. + /// @post An example postcondition. + /// @return Some additional value. + /// @throw `std::runtime_error` if the function actually _can't_ do the thing. Sorry! + /// @todo This really could use some cleanup. Although, its implementation doesn't exist... + /// @warning This function may be very expensive to run. Do not call it inside a loop. + int some_function(int input, int& input_output, int& output); + + /// A virtual function that intends to be overridden. + virtual void virtual_function(); + + int _x{0}; ///< A trailing comment that documents `_x`. +}; + +/// Notice how many of the comments for this structure are inherited from its superclass. +/// @brief This is a sample brief for `some_other_struct` +struct some_other_struct : public some_struct { + void virtual_function() override; +}; + +/// @brief some template function +/// @tparam T The type of thing being returned +/// @return an instance of type `T` +template +T template_function(); + +//------------------------------------------------------------------------------------------------------------------------------------------ diff --git a/test_files/enums.cpp b/test_files/enums.cpp index 9c8e7eb..2487e20 100644 --- a/test_files/enums.cpp +++ b/test_files/enums.cpp @@ -9,19 +9,32 @@ then your use, modification, or distribution of it requires the prior written permission of Adobe. */ +// AST dump with +// clang --std=c++1z -Xclang -ast-dump -fsyntax-only ./enums.cpp + //------------------------------------------------------------------------------------------------------------------------------------------ +/// @brief An example typed enumeration with three values. +/// @hyde-owner fosterbrereton enum class color_channel { + /// Red commentary red, - green, + /// Green commentary. Note this enum has a pre-set value. + green = 42, + /// Blue commentary blue, }; //------------------------------------------------------------------------------------------------------------------------------------------ +/// @brief An example untyped enumeration with three values. +/// @hyde-owner fosterbrereton enum untyped { + /// Apple commentary apple, + /// Orange commentary orange, + /// Banana commentary banana, }; diff --git a/test_files/functions.cpp b/test_files/functions.cpp index a2b200b..5ac7210 100644 --- a/test_files/functions.cpp +++ b/test_files/functions.cpp @@ -11,23 +11,59 @@ written permission of Adobe. //------------------------------------------------------------------------------------------------------------------------------------------ +/// an example nullary function. +/// @return `0` int nullary_function_example() { return 0; } + +/// an example binary function. +/// @param first the first input +/// @param second the second input +/// @return The sum of the two input parameters. int binary_function_example(int first, int second) { return 0; } -auto overloaded(int first) -> float { return 0; } -auto overloaded(int first, int second) -> double { return 0; } -auto overloaded(int first, int second, int third) -> float { return 0; } +/// an example unary overloaded function +/// @param first the first input parameter +/// @return `first` +auto overloaded(int first) -> float { return first; } + +/// an example binary overloaded function +/// @param first the first input parameter +/// @param second the second input parameter +/// @return the product of `first` and `second` +auto overloaded(int first, int second) -> double { return first * second; } + +/// an example tertiary overloaded function +/// @param first the first input parameter +/// @param second the second input parameter +/// @param third the third input parameter +/// @return the product of `first`, `second`, and `third` +auto overloaded(int first, int second, int third) -> float { return first * second * third; } + + +/// an example static function +/// @return `0` static int static_function_example() { return 0; } + +/// an example static function with `auto` return type +/// @return `0` static auto static_auto_function_example() { return 0; } + +/// an example static function with trailing return type +/// @return `0` static auto static_trailing_type_function_example() -> int { return 0; } + +/// an example template function, deleted by default +/// @return Not applicable, seeing that the default definition has been deleted. template T template_function_example() = delete; +/// an example specialization of the template function example +/// @return Forty-two template <> int template_function_example() { return 42; diff --git a/test_files/namespaces.cpp b/test_files/namespaces.cpp index 9942983..68426dd 100644 --- a/test_files/namespaces.cpp +++ b/test_files/namespaces.cpp @@ -13,14 +13,26 @@ written permission of Adobe. namespace foo { +//------------------------------------------------------------------------------------------------------------------------------------------ + namespace bar::baz { +//------------------------------------------------------------------------------------------------------------------------------------------ + +/// function contained within namespaces `foo::bar::baz`. void function(); +//------------------------------------------------------------------------------------------------------------------------------------------ + } // namespace bar::baz +//------------------------------------------------------------------------------------------------------------------------------------------ + +/// function contained within namespace `foo`. void function(); +//------------------------------------------------------------------------------------------------------------------------------------------ + } // namespace foo //------------------------------------------------------------------------------------------------------------------------------------------ diff --git a/test_files/point.cpp b/test_files/point.cpp index 6ab3db5..f37ea6e 100644 --- a/test_files/point.cpp +++ b/test_files/point.cpp @@ -16,26 +16,48 @@ written permission of Adobe. //------------------------------------------------------------------------------------------------------------------------------------------ +/// @brief An example point class template struct point { + /// The `x` coordinate of the point. T x{0}; + /// The `y` coordinate of the point. T y{0}; - static constexpr auto zero() { return point(); } + /// A static routine that returns the origin point. + /// @return The point `(0, 0)` + static constexpr auto origin() { return point(); } + + /// Default constructor of default definition. constexpr point() noexcept = default; + + /// Value-based constructor that takes `x` and `y` values and sinks them into place. + /// @param _x The `x` coordinate to sink. + /// @param _y The `y` coordinate to sink. constexpr point(T _x, T _y) noexcept : x(std::move(_x)), y(std::move(_y)) {} + /// Equality operator. + /// @return `true` iff the two points' `x` and `y` coordinates are memberwise equal. friend inline constexpr bool operator==(const point& a, const point& b) { return (a.x == b.x) && (a.y == b.y); } + /// Inequality operator. + /// @return `true` iff the two points' `x` or `y` coordinates are memberwise inequal. friend inline constexpr bool operator!=(const point& a, const point& b) { return !(a == b); } + /// Subtraction operator. + /// @param a The point to be subtracted from. + /// @param b The point to subtract. + /// @return A new point whose axis values are subtractions of the two inputs' axis values. friend inline constexpr point operator-(const point& a, const point& b) { return point(a.x - b.x, a.y - b.y); } + /// Subtraction-assignment operator. + /// @param a The point to subtract from this point + /// @return A reference to `this`. constexpr point& operator-=(const point& a) { x -= a.x; y -= a.y; diff --git a/test_files/typedef_and_alias.cpp b/test_files/typedef_and_alias.cpp index d11ff6c..4398f00 100644 --- a/test_files/typedef_and_alias.cpp +++ b/test_files/typedef_and_alias.cpp @@ -10,34 +10,51 @@ written permission of Adobe. */ // AST dump with -// clang --std=c++1z -Xclang -ast-dump -fsyntax-only ./test_files/typedef_and_alias.cpp +// TERM="" clang --std=c++1z -Xclang -ast-dump -fsyntax-only -fparse-all-comments -fcomment-block-commands=hyde ./typedef_and_alias.cpp //------------------------------------------------------------------------------------------------------------------------------------------ +/// Example typedef expression whose underlying type is `int`. typedef int typedef_example; + +/// Example using expression whose underlying type is `int`. using using_example = int; //------------------------------------------------------------------------------------------------------------------------------------------ +/// @brief Example class with two type definitions template struct template_example { + /// Type derived from the first template parameter. typedef T typedef_from_T; + /// Type derived from the second template parameter. using using_from_U = U; }; //------------------------------------------------------------------------------------------------------------------------------------------ +/// Partial specialization of the above `template_example` template template using using_partial_specialization_example = template_example; + +/// Full specialization of the above partial specialization using using_full_specialization_example = using_partial_specialization_example; + +/// Using typedef to define another full specialization of the above partial specialization typedef using_partial_specialization_example typedef_full_specialization_example; //------------------------------------------------------------------------------------------------------------------------------------------ -struct template_example_user { +/// @brief Example struct that leverages type aliases defined above. +struct template_example_instantiator { + /// Example partial specialization template using using_partial_specialization_example = template_example; + + /// Example full specialization using using_full_specialization_example = using_partial_specialization_example; + + /// Example partial specialization using `typedef` typedef using_partial_specialization_example typedef_full_specialization_example; };