Porytiles
Loading...
Searching...
No Matches
diagnostic_engine.cpp
Go to the documentation of this file.
2
4
5namespace {
6
7std::optional<std::string> construct_flag(const porytiles::DiagLevel in_flight_level,
8 const porytiles::DiagTempl &templ) {
9 if (in_flight_level == porytiles::DiagLevel::kWarning) {
10 return std::optional{fmt::format("-W{}", templ.name())};
11 }
12 if (templ.level() == porytiles::DiagLevel::kWarning && in_flight_level == porytiles::DiagLevel::kError) {
13 return std::optional{fmt::format("-Werror={}", templ.name())};
14 }
15 return std::nullopt;
16}
17
18} // namespace
19
20namespace porytiles {
21
23 for (const auto &diag : AllDiagNames()) {
24 // Only apply enablement to diagnostics that are default-warnings
25 if (const auto &templ = DiagFor(diag); templ.level() == DiagLevel::kWarning) {
27 }
28 }
29}
30
32 all_warnings_disabled_ = true;
33}
34
36 for (const auto &diag : AllDiagNames()) {
37 // Only apply enablement to diagnostics that are default-warnings
38 if (const auto &templ = DiagFor(diag); templ.level() == DiagLevel::kWarning) {
39 if (enabled_at_level_.contains(diag)) {
40 auto &set = enabled_at_level_.at(diag);
41 set.insert(DiagLevel::kError);
42 }
43 }
44 }
45}
46
47void DiagEngine::EnableAtLevel(std::string_view diag, DiagLevel override) {
48 // Only allow warns to be overridden for the warning-as-error case
49 if (const auto &templ = DiagFor(diag); templ.level() != DiagLevel::kWarning) {
50 Panic("cannot change diagnostic enablement level for non-warning diagnostics");
51 }
52
53 // Only allow warnings to be upgraded to err or downgraded to warn
54 if (override != DiagLevel::kWarning && override != DiagLevel::kError) {
55 Panic(fmt::format("cannot override diagnostic '{}' level to {}", diag, LevelToStr(override)));
56 }
57
58 if (enabled_at_level_.contains(diag.data())) {
59 auto &set = enabled_at_level_.at(diag.data());
60 set.insert(override);
61 } else {
62 enabled_at_level_.insert({std::string{diag}, std::set{override}});
63 }
64}
65
66void DiagEngine::DisableAtLevel(std::string_view diag, DiagLevel override) {
67 // Only allow warns to be overridden for the warning-as-error case
68 if (const auto &templ = DiagFor(diag); templ.level() != DiagLevel::kWarning) {
69 Panic("cannot change diagnostic enablement level for non-warning diagnostics");
70 }
71
72 // Only allow warnings to be upgraded to err or downgraded to warn
73 if (override != DiagLevel::kWarning && override != DiagLevel::kError) {
74 Panic(fmt::format("cannot override diagnostic '{}' level to {}", diag, LevelToStr(override)));
75 }
76
77 if (enabled_at_level_.contains(diag.data())) {
78 auto &set = enabled_at_level_.at(diag.data());
79 set.erase(override);
80 if (set.empty()) {
81 enabled_at_level_.erase(diag.data());
82 }
83 }
84}
85
86DiagLevel DiagEngine::EnabledAt(std::string_view diag) const {
87 if (!enabled_at_level_.contains(diag.data())) {
89 }
90 AssertOrPanic(!enabled_at_level_.at(diag.data()).empty(),
91 fmt::format("enabled_at_level_[{}] - set was empty!", diag.data()));
92 // Return the highest level present
93 return *enabled_at_level_.at(diag.data()).rbegin();
94}
95
96std::uint64_t DiagEngine::InFlightCountForLevel(DiagLevel level) const {
97 return std::ranges::count(in_flight_diags_, level, &InFlightDiag::level);
98}
99
100std::uint64_t DiagEngine::InFlightCountFor(std::string_view diag) const {
101 const auto diag_str = std::string{diag};
102 if (!diag_counts_.contains(diag_str)) {
103 return 0;
104 }
105 return diag_counts_.at(diag_str);
106}
107
109 return *consumer_;
110}
111
112// ReSharper disable once CppParameterMayBeConst
113DiagLevel DiagEngine::ComputeLevel(std::string_view diag) const {
114 const auto &templ = DiagFor(diag);
115
116 // Only warnings can "change" levels, so short circuit on anything else
117 if (templ.level() != DiagLevel::kWarning) {
118 return templ.level();
119 }
120
121 // Return level override if present
122 if (auto diag_str = std::string{diag}; enabled_at_level_.contains(diag_str)) {
123 AssertOrPanic(!enabled_at_level_.at(diag_str).empty(),
124 fmt::format("enabled_at_level_[{}] - set was empty!", diag_str));
125 // Return the highest level present
126 return *enabled_at_level_.at(diag_str).rbegin();
127 }
128
129 // Default to the default level from the template
130 return templ.level();
131}
132
133[[nodiscard]] bool DiagEngine::IsEnabled(std::string_view diag) const {
134
135 // If this diagnostic is a note, remark, error, or fatal by default, it is
136 // always enabled.
137 //
138 // TODO : should we have note always enabled? Or should we have the generic
139 // error and fatal diagnostics contain a blank note partner? The downside
140 // to that approach is we're locked in to having only a single note partner
141 if (const auto &templ = DiagFor(diag); templ.level() == DiagLevel::kNote || templ.level() == DiagLevel::kRemark ||
142 templ.level() == DiagLevel::kError || templ.level() == DiagLevel::kFatal) {
143 return true;
144 }
145
146 // The highest precedence is global warning disable. If this is specified,
147 // all warnings (including warnings which have been upgraded to errors)
148 // will be disabled. Any other override setting will be ignored.
149 if (all_warnings_disabled_) {
150 return false;
151 }
152
153 // The next highest precedence is an explicitly enabled or disabled
154 // diagnostic. If the diagnostic is not present in this map, that means
155 // it was not set by the user, so it shouldn't be enabled.
156 if (const auto diag_str = std::string{diag}; enabled_at_level_.contains(diag_str)) {
157 return true;
158 }
159
160 // Lowest precedence, if nothing else passed, then diagnostic is disabled
161 return false;
162}
163
164std::string DiagEngine::ConstructMsgStr(const DiagLevel in_flight_level, const DiagTempl &templ,
165 const std::vector<std::string> &msg) const {
166 std::stringstream ss{};
167
168 auto level_prefix = fmt::format("{}: ", LevelToStr(in_flight_level));
169 const auto style = fmt::emphasis::bold | fg(ColorForLevel(in_flight_level));
170
171 // If consumer is a tty, style the prefix with the appropriate color.
172 if (consumer_->IsATty()) {
173 level_prefix = fmt::format("{}", styled(level_prefix, style));
174 }
175
176 // Dump the level prefix followed by the first line of the message.
177 ss << level_prefix << msg.at(0);
178
179 // Warnings and warnings-as-errors show "[-Wname-of-warning]" at the end
180 // of the first line, so the user can easily identify the source of the
181 // diagnostic. Handle that formatting here, styling if the consumer is
182 // a tty.
183 if (const auto flag = construct_flag(in_flight_level, templ); flag.has_value()) {
184 ss << " [";
185 const auto styled_flag = fmt::format("{}", styled(flag.value(), style));
186 consumer_->IsATty() ? ss << styled_flag : ss << flag.value();
187 ss << "]";
188 }
189
190 // Terminate the first line.
191 ss << std::endl;
192
193 // Dump the rest of the message lines (if present).
194 for (const auto msg_view = msg | std::views::drop(1); const auto &line : msg_view) {
195 ss << line << std::endl;
196 }
197
198 return ss.str();
199}
200
201} // namespace porytiles
A customizable consumer for diagnostic messages.
std::uint64_t InFlightCountForLevel(DiagLevel level) const
void DisableAtLevel(std::string_view diag, DiagLevel override)
void EnableAtLevel(std::string_view diag, DiagLevel override)
std::uint64_t InFlightCountFor(std::string_view diag) const
DiagLevel EnabledAt(std::string_view diag) const
const DiagConsumer & consumer() const
Defines a reusable template for standardized diagnostic reporting.
DiagLevel level() const
Gets the default diagnostic level of the template.
std::string_view name() const
DiagLevel level() const noexcept
void Panic(const StringViewSourceLoc &s) noexcept
Definition panic.hpp:31
void AssertOrPanic(const bool condition, const StringViewSourceLoc &s)
Definition panic.hpp:36
DiagTempl DiagFor(std::string_view name)
Retrieves the DiagTempl corresponding to a given diagnostic name.
std::vector< const char * > AllDiagNames()
Gets an iterable view of all DiagTempl names in the internal table.
std::string LevelToStr(DiagLevel level)
fmt::terminal_color ColorForLevel(DiagLevel level)