Skip to content

Commit 925569c

Browse files
committed
feat(build): humanize compiler warning output
1 parent 2f277fd commit 925569c

3 files changed

Lines changed: 495 additions & 107 deletions

File tree

include/vix/cli/build/BuildStyle.hpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,37 @@ namespace vix::cli::build
6767
bool has_code_frame() const;
6868
};
6969

70+
enum class BuildWarningKind
71+
{
72+
Unknown,
73+
UnusedFunction,
74+
UnusedVariable,
75+
UnusedParameter,
76+
ShadowedVariable,
77+
DeprecatedDeclaration,
78+
SignCompare,
79+
Conversion,
80+
Reorder,
81+
MissingFieldInitializer
82+
};
83+
84+
struct BuildWarning
85+
{
86+
fs::path file;
87+
std::size_t line{0};
88+
std::size_t column{0};
89+
90+
BuildWarningKind kind{BuildWarningKind::Unknown};
91+
92+
std::string symbol;
93+
std::string message;
94+
std::string hint;
95+
std::string flag;
96+
std::string raw;
97+
98+
bool has_location() const;
99+
};
100+
70101
struct BuildProgress
71102
{
72103
std::string target;
@@ -163,6 +194,25 @@ namespace vix::cli::build
163194
const std::string &message,
164195
long long milliseconds);
165196

197+
std::optional<BuildWarning> parse_build_warning(
198+
const std::string &text);
199+
200+
void print_build_warnings_summary(
201+
std::ostream &out,
202+
const std::vector<BuildWarning> &warnings,
203+
std::size_t total);
204+
205+
std::optional<BuildWarning> parse_build_warning(
206+
const std::string &text);
207+
208+
BuildWarning humanize_build_warning(
209+
BuildWarning warning);
210+
211+
void print_build_warnings_summary(
212+
std::ostream &out,
213+
const std::vector<BuildWarning> &warnings,
214+
std::size_t total);
215+
166216
} // namespace vix::cli::build
167217

168218
#endif

src/build/BuildStyle.cpp

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,98 @@ namespace vix::cli::build
129129
<< value
130130
<< "\n";
131131
}
132+
133+
static std::string trim_copy(std::string value)
134+
{
135+
while (!value.empty() &&
136+
std::isspace(static_cast<unsigned char>(value.front())) != 0)
137+
{
138+
value.erase(value.begin());
139+
}
140+
141+
while (!value.empty() &&
142+
std::isspace(static_cast<unsigned char>(value.back())) != 0)
143+
{
144+
value.pop_back();
145+
}
146+
147+
return value;
148+
}
149+
150+
static std::string extract_warning_flag(const std::string &message)
151+
{
152+
static const std::regex flagRe(R"(\[(-W[^\]]+)\])");
153+
154+
std::smatch match;
155+
156+
if (!std::regex_search(message, match, flagRe))
157+
return {};
158+
159+
return match[1].str();
160+
}
161+
162+
static std::string strip_warning_flag(std::string message)
163+
{
164+
static const std::regex flagRe(R"(\s*\[-W[^\]]+\]\s*$)");
165+
return trim_copy(std::regex_replace(message, flagRe, ""));
166+
}
167+
168+
static std::string shorten_cpp_signature(std::string value)
169+
{
170+
const std::size_t paren = value.find('(');
171+
172+
if (paren != std::string::npos)
173+
value = value.substr(0, paren);
174+
175+
const std::string anonymous = "{anonymous}::";
176+
const std::size_t anonymousPos = value.find(anonymous);
177+
178+
if (anonymousPos != std::string::npos)
179+
value = value.substr(anonymousPos + anonymous.size());
180+
181+
const std::size_t ns = value.rfind("::");
182+
183+
if (ns != std::string::npos)
184+
value = value.substr(ns + 2);
185+
186+
if (!value.empty() && value.front() == '\'' && value.back() == '\'')
187+
value = value.substr(1, value.size() - 2);
188+
189+
if (!value.empty() && value.front() == '' && value.back() == '')
190+
value = value.substr(3, value.size() > 6 ? value.size() - 6 : value.size());
191+
192+
return trim_copy(value);
193+
}
194+
195+
static std::string extract_quoted_symbol(const std::string &message)
196+
{
197+
{
198+
const std::size_t start = message.find("");
199+
const std::size_t end = message.find("", start + 1);
200+
201+
if (start != std::string::npos &&
202+
end != std::string::npos &&
203+
end > start)
204+
{
205+
return message.substr(start + std::string("").size(),
206+
end - (start + std::string("").size()));
207+
}
208+
}
209+
210+
{
211+
const std::size_t start = message.find('\'');
212+
const std::size_t end = message.find('\'', start + 1);
213+
214+
if (start != std::string::npos &&
215+
end != std::string::npos &&
216+
end > start)
217+
{
218+
return message.substr(start + 1, end - start - 1);
219+
}
220+
}
221+
222+
return {};
223+
}
132224
} // namespace
133225

134226
void print_build_header_full(
@@ -600,4 +692,231 @@ namespace vix::cli::build
600692
<< "s\n";
601693
}
602694

695+
bool BuildWarning::has_location() const
696+
{
697+
return !file.empty();
698+
}
699+
700+
std::optional<BuildWarning> parse_build_warning(
701+
const std::string &text)
702+
{
703+
static const std::regex pattern(
704+
R"(([^:\n\r]+):([0-9]+):([0-9]+):\s+warning:\s+(.+))");
705+
706+
std::smatch match;
707+
708+
if (!std::regex_search(text, match, pattern))
709+
return std::nullopt;
710+
711+
BuildWarning warning;
712+
warning.file = fs::path(match[1].str());
713+
714+
try
715+
{
716+
warning.line = static_cast<std::size_t>(
717+
std::stoull(match[2].str()));
718+
719+
warning.column = static_cast<std::size_t>(
720+
std::stoull(match[3].str()));
721+
}
722+
catch (...)
723+
{
724+
return std::nullopt;
725+
}
726+
727+
warning.message = match[4].str();
728+
warning.raw = text;
729+
730+
return humanize_build_warning(std::move(warning));
731+
}
732+
733+
void print_build_warnings_summary(
734+
std::ostream &out,
735+
const std::vector<BuildWarning> &warnings,
736+
std::size_t total)
737+
{
738+
if (total == 0)
739+
return;
740+
741+
out << " "
742+
<< colorize(style::YELLOW, "warning")
743+
<< " "
744+
<< colorize(style::BOLD, std::to_string(total))
745+
<< " compiler warning"
746+
<< (total > 1 ? "s" : "")
747+
<< "\n";
748+
749+
const std::size_t shown = warnings.size();
750+
751+
for (const BuildWarning &warning : warnings)
752+
{
753+
out << " "
754+
<< colorize(style::YELLOW, "")
755+
<< " ";
756+
757+
if (warning.has_location())
758+
{
759+
out << colorize(style::CYAN, warning.file.filename().string());
760+
761+
if (warning.line > 0)
762+
{
763+
out << style::GRAY
764+
<< ":"
765+
<< warning.line;
766+
767+
if (warning.column > 0)
768+
out << ":" << warning.column;
769+
770+
out << style::RESET;
771+
}
772+
773+
out << "\n";
774+
}
775+
776+
out << " "
777+
<< (warning.message.empty() ? warning.raw : warning.message)
778+
<< "\n";
779+
780+
if (!warning.hint.empty())
781+
{
782+
out << " "
783+
<< colorize(style::GRAY, "hint:")
784+
<< " "
785+
<< warning.hint
786+
<< "\n";
787+
}
788+
}
789+
790+
if (total > shown)
791+
{
792+
out << " "
793+
<< colorize(style::GRAY, "")
794+
<< colorize(
795+
style::GRAY,
796+
std::to_string(total - shown) +
797+
" more warning" +
798+
((total - shown) > 1 ? "s" : "") +
799+
" hidden")
800+
<< "\n";
801+
}
802+
803+
out << " "
804+
<< colorize(style::GRAY, "hint:")
805+
<< " run with "
806+
<< colorize(style::CYAN, "--verbose")
807+
<< " for full compiler output"
808+
<< "\n\n";
809+
}
810+
811+
BuildWarning humanize_build_warning(BuildWarning warning)
812+
{
813+
const std::string message = warning.message;
814+
warning.flag = extract_warning_flag(message);
815+
816+
const std::string cleanMessage = strip_warning_flag(message);
817+
const std::string quoted = extract_quoted_symbol(cleanMessage);
818+
819+
if (cleanMessage.find("defined but not used") != std::string::npos)
820+
{
821+
warning.kind = BuildWarningKind::UnusedFunction;
822+
warning.symbol = shorten_cpp_signature(quoted);
823+
warning.message =
824+
warning.symbol.empty()
825+
? "unused function"
826+
: "unused function: " + warning.symbol;
827+
warning.hint = "remove it, use it, or mark it [[maybe_unused]]";
828+
return warning;
829+
}
830+
831+
if (cleanMessage.find("unused variable") != std::string::npos)
832+
{
833+
warning.kind = BuildWarningKind::UnusedVariable;
834+
warning.symbol = shorten_cpp_signature(quoted);
835+
warning.message =
836+
warning.symbol.empty()
837+
? "unused variable"
838+
: "unused variable: " + warning.symbol;
839+
warning.hint = "remove it or mark it [[maybe_unused]]";
840+
return warning;
841+
}
842+
843+
if (cleanMessage.find("unused parameter") != std::string::npos)
844+
{
845+
warning.kind = BuildWarningKind::UnusedParameter;
846+
warning.symbol = shorten_cpp_signature(quoted);
847+
warning.message =
848+
warning.symbol.empty()
849+
? "unused parameter"
850+
: "unused parameter: " + warning.symbol;
851+
warning.hint = "remove the parameter name or mark it [[maybe_unused]]";
852+
return warning;
853+
}
854+
855+
if (cleanMessage.find("shadows a previous local") != std::string::npos ||
856+
cleanMessage.find("declaration of") != std::string::npos &&
857+
cleanMessage.find("shadows") != std::string::npos)
858+
{
859+
warning.kind = BuildWarningKind::ShadowedVariable;
860+
warning.symbol = shorten_cpp_signature(quoted);
861+
warning.message =
862+
warning.symbol.empty()
863+
? "variable shadows another variable"
864+
: "variable shadows another variable: " + warning.symbol;
865+
warning.hint = "rename the inner variable to avoid confusion";
866+
return warning;
867+
}
868+
869+
if (cleanMessage.find("deprecated") != std::string::npos)
870+
{
871+
warning.kind = BuildWarningKind::DeprecatedDeclaration;
872+
warning.symbol = shorten_cpp_signature(quoted);
873+
warning.message =
874+
warning.symbol.empty()
875+
? "deprecated API used"
876+
: "deprecated API used: " + warning.symbol;
877+
warning.hint = "replace it with the recommended API";
878+
return warning;
879+
}
880+
881+
if (warning.flag == "-Wsign-compare")
882+
{
883+
warning.kind = BuildWarningKind::SignCompare;
884+
warning.message = "comparison mixes signed and unsigned values";
885+
warning.hint = "use matching integer types before comparing";
886+
return warning;
887+
}
888+
889+
if (warning.flag == "-Wconversion")
890+
{
891+
warning.kind = BuildWarningKind::Conversion;
892+
warning.message = "implicit conversion may change the value";
893+
warning.hint = "use an explicit cast or a safer target type";
894+
return warning;
895+
}
896+
897+
if (warning.flag == "-Wreorder")
898+
{
899+
warning.kind = BuildWarningKind::Reorder;
900+
warning.message = "constructor initializes members in a different order";
901+
warning.hint = "match the initializer list order with the class field order";
902+
return warning;
903+
}
904+
905+
if (warning.flag == "-Wmissing-field-initializers")
906+
{
907+
warning.kind = BuildWarningKind::MissingFieldInitializer;
908+
warning.message = "some fields are not initialized";
909+
warning.hint = "initialize all fields explicitly";
910+
return warning;
911+
}
912+
913+
warning.kind = BuildWarningKind::Unknown;
914+
warning.message = cleanMessage;
915+
warning.hint = warning.flag.empty()
916+
? "review this compiler warning"
917+
: "review this compiler warning: " + warning.flag;
918+
919+
return warning;
920+
}
921+
603922
} // namespace vix::cli::build

0 commit comments

Comments
 (0)