@@ -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