diff --git a/include/mrdocs/Support/Report.hpp b/include/mrdocs/Support/Report.hpp index 127e28096df..f17285123c5 100644 --- a/include/mrdocs/Support/Report.hpp +++ b/include/mrdocs/Support/Report.hpp @@ -34,7 +34,7 @@ */ namespace mrdocs::report { -/** Severity levels attached to reported messags. +/** Severity levels attached to reported messages. */ enum class Level { @@ -103,11 +103,10 @@ Level getMinimumLevel() noexcept; /** If true, source location information will be - printed with warnings, errors, and fatal messages. + printed. - @param b true to enable source location - information, false to disable it. The default - value is true. + @param b true to enable the bug report details, + false to disable them. The default value is true. */ MRDOCS_DECL void @@ -133,8 +132,13 @@ print( trailing newline will be added to the message automatically. - @param loc The source location of the report. - If this value is null, no location is printed. + @param loc The source location a bug report + should carry. If this value is null, the message + is printed on its own, without the bug report + details. + + @param e The error the message reports, when + there is one. Only read when `loc` is not null. */ MRDOCS_DECL void @@ -179,38 +183,42 @@ struct Located column widths) from the public reporting API. */ namespace detail { +// `withBugDetails` says whether the message reports a MrDocs defect. Only +// then is the location where MrDocs raised it worth printing, along with +// the rest of what a bug report needs. A message about the user's input +// does not contain such info. template requires (!std::same_as, Error>) void log_impl( Level level, + bool withBugDetails, Located fs, Arg0&& arg0, Args&&... args) { std::string str = std::vformat(fs.value, std::make_format_args(arg0, args...)); - return print(level, str, &fs.where); + return print(level, str, withBugDetails ? &fs.where : nullptr); } template void log_impl( Level level, + bool withBugDetails, Located fs, Error const& e, Args&&... args) { - // When the message is an error, we send split - // the information relevant to the user from - // the information relevant for bug tracking - // so that users can understand the message. + // The reason is what went wrong; where MrDocs raised it belongs to the + // bug report details, so the message carries the reason alone. std::string str = std::vformat(fs.value, std::make_format_args(e.reason(), args...)); return print( level, str, - &fs.where, + withBugDetails ? &fs.where : nullptr, &e); } @@ -218,10 +226,11 @@ inline void log_impl( Level level, + bool withBugDetails, Located fs) { std::string str(fs.value); - return print(level, str, &fs.where); + return print(level, str, withBugDetails ? &fs.where : nullptr); } } @@ -246,6 +255,7 @@ log( { return detail::log_impl( level, + false, fs, std::forward(args)...); } @@ -328,6 +338,29 @@ fatal( return log(Level::fatal, format, std::forward(args)...); } +/** Emit an error that reports a defect in MrDocs itself. + + The message is followed by the version and the source location a bug + report needs. Use it where MrDocs reached a state it does not handle. + A mistake in the user's input is not a bug in MrDocs, so it goes + through the other reporting functions. + + @param format fmt-style format string. + @param args Arguments substituted into the format string. +*/ +template +void +bug( + Located format, + Args&&... args) +{ + return detail::log_impl( + Level::error, + true, + format, + std::forward(args)...); +} + } // mrdocs #endif diff --git a/src/mrdocs/AST/ASTVisitor.cpp b/src/mrdocs/AST/ASTVisitor.cpp index 1e9fc460597..f0b969e24c2 100644 --- a/src/mrdocs/AST/ASTVisitor.cpp +++ b/src/mrdocs/AST/ASTVisitor.cpp @@ -2221,7 +2221,7 @@ addMember( addMember(I.Members.Macros, *U); return; } - report::error("Cannot push {} of type {} into members of namespace {}", + report::bug("Cannot push {} of type {} into members of namespace {}", Member.Name, mrdocs::toString(Member.Kind), I.Name); @@ -2311,7 +2311,7 @@ addMember(RecordTranche& T, Symbol const& Member) addMember(T.Usings, *U); return; } - report::error("Cannot push {} of type {} into tranche", + report::bug("Cannot push {} of type {} into tranche", Member.Name, mrdocs::toString(Member.Kind)); } @@ -2325,7 +2325,7 @@ addMember(EnumSymbol& I, Symbol const& Member) const addMember(I.Constants, *U); return; } - report::error("Cannot push {} of type {} into members of enum {}", + report::bug("Cannot push {} of type {} into members of enum {}", Member.Name, mrdocs::toString(Member.Kind), I.Name); @@ -2340,7 +2340,7 @@ addMember(OverloadsSymbol& I, Symbol const& Member) const addMember(I.Members, Member); return; } - report::error("Cannot push {} of type {} into members of enum {}", + report::bug("Cannot push {} of type {} into members of overload set {}", Member.Name, mrdocs::toString(Member.Kind), I.Name); diff --git a/src/mrdocs/AST/ExtractDocComment.cpp b/src/mrdocs/AST/ExtractDocComment.cpp index bb6c15609da..7d2d8fece79 100644 --- a/src/mrdocs/AST/ExtractDocComment.cpp +++ b/src/mrdocs/AST/ExtractDocComment.cpp @@ -200,7 +200,7 @@ convertDirection(clang::comments::ParamCommandPassDirection d) case D::InOut: return doc::ParamDirection::inout; } - report::error( + report::bug( "error: unsupported ParamCommandPassDirection <{}>", static_cast(d)); MRDOCS_UNREACHABLE(); @@ -219,7 +219,7 @@ convertCopydoc(unsigned id) case T::KCI_copydetails: return doc::Parts::description; default: - report::error("error: unsupported CommandTrait id <{}>", id); + report::bug("error: unsupported CommandTrait id <{}>", id); MRDOCS_UNREACHABLE(); } } @@ -793,7 +793,7 @@ class DocCommentVisitor Expected cellExp = parseTableCell(sub); if (!cellExp) { - warnNear(s, cellExp.error().message()); + warnNear(s, cellExp.error().reason()); return; } row.Cells.push_back(std::move(*cellExp)); @@ -894,7 +894,7 @@ class DocCommentVisitor Expected rowExp = parseTableRow(sub); if (!rowExp) { - warnNear(s, rowExp.error().message()); + warnNear(s, rowExp.error().reason()); return; } // Skip rows whose cells all failed to parse: a row with @@ -1049,7 +1049,7 @@ class DocCommentVisitor Expected tbExp = parseTable(cur); if (!tbExp) { - warnNear(C, tbExp.error().message()); + warnNear(C, tbExp.error().reason()); return; } // A table with no rows (e.g., because every row's content @@ -1185,7 +1185,7 @@ class DocCommentVisitor // written; the rest of the phrase follows as ordinary text. if (isSupportedHTMLTag(C->getTagName())) { - warnOnce(filename, loc.getLine(), compsExp.error().message()); + warnOnce(filename, loc.getLine(), compsExp.error().reason()); } emplaceInline( C->hasTrailingNewline(), @@ -1210,7 +1210,7 @@ class DocCommentVisitor // problem in the user's doc comment, not in Mr.Docs: warn // with the location and keep the text instead of failing // the whole run with an internal-error banner. - warnOnce(filename, loc.getLine(), r.error().message()); + warnOnce(filename, loc.getLine(), r.error().reason()); emplaceInline( C->hasTrailingNewline(), ensureUTF8(std::move(comps.text))); @@ -1276,7 +1276,7 @@ class DocCommentVisitor auto srcAttr = getAttr("src"); if (!srcAttr) { - warnOnce(filename, loc.getLine(), srcAttr.error().message()); + warnOnce(filename, loc.getLine(), srcAttr.error().reason()); return; } std::string alt = getAttr("alt").value_or(std::string()); @@ -1360,7 +1360,7 @@ class DocCommentVisitor warnOnce( files::makePosixStyle(loc.getFilename()), loc.getLine(), - r.error().message()); + r.error().reason()); } else if (comps.tag == "em") { @@ -1761,7 +1761,7 @@ class DocCommentVisitor case T::KCI_copybrief: case T::KCI_copydetails: case T::KCI_copydoc: - report::error( + report::bug( "error: inline command {} should be handled elsewhere", cmd->Name); MRDOCS_UNREACHABLE(); diff --git a/src/mrdocs/Config.cpp b/src/mrdocs/Config.cpp index d615109b48d..a1391f4dc0d 100644 --- a/src/mrdocs/Config.cpp +++ b/src/mrdocs/Config.cpp @@ -1119,9 +1119,7 @@ struct ConfigSchemaVisitor { MRDOCS_TRY( std::string_view const baseDir, getBaseDir(referenceDirKey, dirs, settings)); - if (pos != std::string::npos) { - value = value.substr(pos + 1); - } + value = pos != std::string::npos ? value.substr(pos + 1) : "."; return std::string(baseDir); } }; diff --git a/src/mrdocs/Corpus.cpp b/src/mrdocs/Corpus.cpp index b5c404eb50c..33f85a1df35 100644 --- a/src/mrdocs/Corpus.cpp +++ b/src/mrdocs/Corpus.cpp @@ -522,8 +522,7 @@ Corpus::build( { return Unexpected(err); } - report::warn( - "Warning: mapping failed because ", err); + report::warn("Mapping failed: {}", err); } MRDOCS_TRY(auto results, context.results()); diff --git a/src/mrdocs/Engines/JavaScript.cpp b/src/mrdocs/Engines/JavaScript.cpp index 57ef8e371d2..ca44eddeb3a 100644 --- a/src/mrdocs/Engines/JavaScript.cpp +++ b/src/mrdocs/Engines/JavaScript.cpp @@ -305,7 +305,7 @@ invokeHelper(Value const& fn, dom::Array const& args) auto ret = fn.apply(callArgs); if (!ret) { - return Unexpected(dom::Error(std::string(ret.error().message()))); + return Unexpected(dom::Error(std::string(ret.error().reason()))); } return ret->getDom(); } diff --git a/src/mrdocs/Extensions/ExtensionRegistry.cpp b/src/mrdocs/Extensions/ExtensionRegistry.cpp index e6b7b498ec9..7bb820b3dc7 100644 --- a/src/mrdocs/Extensions/ExtensionRegistry.cpp +++ b/src/mrdocs/Extensions/ExtensionRegistry.cpp @@ -95,7 +95,7 @@ ExtensionRegistry::applyTransforms(Corpus& corpus, Config const& config) const { return Unexpected(formatError( "extension transform '{}': {}", - id, invoked.error().message())); + id, invoked.error().reason())); } } } diff --git a/src/mrdocs/Extensions/JsBinding.cpp b/src/mrdocs/Extensions/JsBinding.cpp index 4c7cb5522cd..3c7a5d33d2b 100644 --- a/src/mrdocs/Extensions/JsBinding.cpp +++ b/src/mrdocs/Extensions/JsBinding.cpp @@ -189,7 +189,7 @@ loadJsExtensions(std::string const& scriptPath, Config const& config) if (!ran.has_value()) { return Unexpected(formatError( - "extension '{}': {}", scriptPath, ran.error().message())); + "extension '{}': {}", scriptPath, ran.error().reason())); } if (loaded.transforms.empty() && loaded.generators.empty()) diff --git a/src/mrdocs/Generators/hbs/Builder.cpp b/src/mrdocs/Generators/hbs/Builder.cpp index a6d89884b2d..0e196a54a99 100644 --- a/src/mrdocs/Generators/hbs/Builder.cpp +++ b/src/mrdocs/Generators/hbs/Builder.cpp @@ -360,7 +360,7 @@ registerUserJsHelpers( { return Unexpected(formatError( "Error loading utility {}: {}", - utilPath, exp.error().message())); + utilPath, exp.error().reason())); } } @@ -417,7 +417,7 @@ registerUserLuaHelpers( { return Unexpected(formatError( "Error loading utility {}: {}", - utilPath, exp.error().message())); + utilPath, exp.error().reason())); } } @@ -680,7 +680,7 @@ operator()(std::ostream& os, T const& I) // Reuse the already-built context to avoid recomputing DOM data. if (auto exp = callTemplate(os, templateFile, ctx); !exp) { - return Unexpected(dom::Error(std::string(exp.error().message()))); + return Unexpected(dom::Error(std::string(exp.error().reason()))); } return {}; })); @@ -715,7 +715,7 @@ renderWrapped( ctx.set("contents", dom::makeInvocable([&](dom::Value const &) -> dom::Expected { if (auto exp = contentsCb(); !exp) { - return Unexpected(dom::Error(std::string(exp.error().message()))); + return Unexpected(dom::Error(std::string(exp.error().reason()))); } return {}; })); diff --git a/src/mrdocs/Generators/script/ScriptGenerator.cpp b/src/mrdocs/Generators/script/ScriptGenerator.cpp index f7c93349cd1..ba63cb1692f 100644 --- a/src/mrdocs/Generators/script/ScriptGenerator.cpp +++ b/src/mrdocs/Generators/script/ScriptGenerator.cpp @@ -129,7 +129,7 @@ buildOutputApi(OutputSink& sink) doAppend); !wrote) { - result = Unexpected(dom::Error(std::string(wrote.error().message()))); + result = Unexpected(dom::Error(std::string(wrote.error().reason()))); } else { @@ -212,7 +212,7 @@ build(Corpus const& corpus, Config const& config) const if (!invoked) { result = Unexpected(formatError( - "generator '{}': {}", id_, invoked.error().message())); + "generator '{}': {}", id_, invoked.error().reason())); } return result; } diff --git a/src/mrdocs/Support/Report.cpp b/src/mrdocs/Support/Report.cpp index 22b340e8fee..8d12a82afb2 100644 --- a/src/mrdocs/Support/Report.cpp +++ b/src/mrdocs/Support/Report.cpp @@ -107,20 +107,6 @@ print( //------------------------------------------------ -Level -getLevel(unsigned level) noexcept -{ - switch(level) - { - case 0: return Level::debug; - case 1: return Level::info; - case 2: return Level::warn; - case 3: return Level::error; - default: - return Level::fatal; - } -} - constexpr llvm::raw_ostream::Colors getLevelColor(Level level) diff --git a/src/mrdocs/Support/ReportImpl.hpp b/src/mrdocs/Support/ReportImpl.hpp index 77404709327..8c802b1839d 100644 --- a/src/mrdocs/Support/ReportImpl.hpp +++ b/src/mrdocs/Support/ReportImpl.hpp @@ -94,16 +94,11 @@ class separator //------------------------------------------------ -/** Return a level from an integer. -*/ -MRDOCS_DECL -Level -getLevel( - unsigned level) noexcept; - /** Formatted reporting to a live stream. - A trailing newline will be added automatically. + A trailing newline will be added automatically. If a location is present, + the message will be considered to be for a MrDocs defect, and will contain + an invite to file a bug report. */ MRDOCS_DECL void @@ -113,20 +108,6 @@ call_impl( source_location const* loc, Error const* e = nullptr); -/** Formatted reporting to a live stream. - - A trailing newline will be added automatically. -*/ -inline void -call( - Level level, - std::function f, - source_location const& loc = - source_location::current()) -{ - call_impl(level, std::move(f), &loc); -} - } // report } // mrdocs diff --git a/tests/golden/TestMain.cpp b/tests/golden/TestMain.cpp index 0ae40eb8c4a..64ed8a3295c 100644 --- a/tests/golden/TestMain.cpp +++ b/tests/golden/TestMain.cpp @@ -136,13 +136,13 @@ accepted and applied like the mrdocs tool. return EXIT_SUCCESS; } -#ifdef _NDEBUG +#ifdef NDEBUG static void reportUnhandledException( std::exception const& ex) { namespace sys = llvm::sys; - report::error("Unhandled exception: {}\n", ex.what()); + report::bug("Unhandled exception: {}\n", ex.what()); sys::PrintStackTrace(llvm::errs()); } #endif @@ -152,7 +152,7 @@ static void reportUnhandledException( int main(int argc, char const** argv) { -#ifndef _NDEBUG +#ifndef NDEBUG return mrdocs::test_main(argc, argv); #else try diff --git a/tests/unit/Config.cpp b/tests/unit/Config.cpp new file mode 100644 index 00000000000..d60f8477d8f --- /dev/null +++ b/tests/unit/Config.cpp @@ -0,0 +1,57 @@ +// +// Licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include +#include +#include +#include +#include +#include + +namespace mrdocs { + +struct ConfigTest +{ + void + testSourceRootDefaultsToConfigDir() + { + ScopedTempDirectory td("mrdocs-config"); + BOOST_TEST(td); + // `addons` must name a directory that exists, and nothing reads + // it here, so an empty one will do. + std::string const addonsDir = files::appendPath(td.path(), "addons"); + BOOST_TEST(files::createDirectory(addonsDir).has_value()); + std::string const configPath = + files::appendPath(td.path(), "mrdocs.yml"); + { + std::ofstream os(configPath, std::ios::binary | std::ios::trunc); + os << "addons: addons\n"; + } + + Config config; + ReferenceDirectories const dirs; + Expected const loaded = + Config::load_file(config, configPath, dirs); + BOOST_TEST(loaded.has_value()); + BOOST_TEST(config.sourceRoot == files::makePosixStyle(td.path())); + } + + void + run() + { + testSourceRootDefaultsToConfigDir(); + } +}; + +TEST_SUITE( + ConfigTest, + "clang.mrdocs.Config"); + +} // mrdocs diff --git a/tools/mrdocs/src/Main.cpp b/tools/mrdocs/src/Main.cpp index 4595c121ab6..67af1688444 100644 --- a/tools/mrdocs/src/Main.cpp +++ b/tools/mrdocs/src/Main.cpp @@ -413,7 +413,7 @@ mrdocs_main(int argc, char const** argv) auto res = getReferenceDirectories(execPath); if (!res) { - report::fatal("Failed to determine reference directories: {}", res.error().message()); + report::fatal("Failed to determine reference directories: {}", res.error()); return EXIT_FAILURE; } auto dirs = *std::move(res); @@ -421,7 +421,7 @@ mrdocs_main(int argc, char const** argv) auto expConfigPath = getConfigPath(dirs, cl); if (!expConfigPath) { - report::fatal("Failed to determine config path: {}", expConfigPath.error().message()); + report::fatal("Failed to determine config path: {}", expConfigPath.error()); return EXIT_FAILURE; } auto configPath = *std::move(expConfigPath); @@ -439,7 +439,7 @@ mrdocs_main(int argc, char const** argv) return EXIT_SUCCESS; } -#ifdef _NDEBUG +#ifdef NDEBUG static void reportUnhandledException( @@ -447,7 +447,7 @@ reportUnhandledException( { namespace sys = llvm::sys; - report::fatal("Unhandled exception: {}\n", ex.what()); + report::bug("Unhandled exception: {}\n", ex.what()); sys::PrintStackTrace(llvm::errs()); } #endif @@ -457,7 +457,7 @@ reportUnhandledException( int main(int argc, char const** argv) { -#ifndef _NDEBUG +#ifndef NDEBUG return mrdocs::mrdocs_main(argc, argv); #else try