From 40dcf2271d41885f538d974b1c2879bd8fc5728b Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sat, 31 Oct 2026 22:59:37 +0100 Subject: [PATCH] fix(lint): fail the files whose configuration has no compile command When GetCompileCommand threw or the std module failed to build, the lint driver cleared the configuration's command, and an empty command means "not a translation unit" (project.cpp, a header): every rule that needs an AST skipped the files quietly and the run passed them as clean. MingwSysroot made that reachable for every mingw configuration whose view cannot be made. The reason is now kept per configuration and reported as an AST failure for each of its files, which fails the run and prints it. Co-Authored-By: Claude Opus 5.5 --- implementations/Crafter.Build-Lint.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/implementations/Crafter.Build-Lint.cpp b/implementations/Crafter.Build-Lint.cpp index 357fb33..39e4be0 100644 --- a/implementations/Crafter.Build-Lint.cpp +++ b/implementations/Crafter.Build-Lint.cpp @@ -1149,6 +1149,10 @@ format` applies it to disk, and `crafter-build lint` reports where it would. // command per configuration — GetCompileCommand walks the dependency tree. const bool anyRuleNeedsAst = std::ranges::any_of(rules, [](const LintRule* r) { return r->needsAst; }); std::unordered_map commands; + // Why a configuration of the build graph has no command: its files + // cannot be parsed, which fails the run. Cleared to nothing, the command + // read as "not a translation unit" and every semantic rule passed them. + std::unordered_map commandErrors; if (anyRuleNeedsAst && !opts.noAst) { for (Configuration* c : localConfigs) { auto ensure = [&](const Configuration& cfg) { @@ -1166,10 +1170,14 @@ format` applies it to disk, and `crafter-build lint` reports where it would. Progress::Task task(std::format("Building std PCM ({}-{})", cfg.target, cfg.march)); fs::create_directories(assembled.stdPcmDir); std::string error = BuildStdPcm(cfg, assembled.stdPcmDir/"std.pcm"); - if (!error.empty()) command.clear(); + if (!error.empty()) { + command.clear(); + commandErrors.emplace(&cfg, std::format("building the std module for {}: {}", cfg.target, error)); + } } - } catch (const std::exception&) { - command.clear(); // surfaced per file as an AST reason + } catch (const std::exception& e) { + command.clear(); + commandErrors.emplace(&cfg, std::format("assembling the compile command for {}: {}", cfg.target, e.what())); } commands.emplace(&cfg, std::move(command)); }; @@ -1200,6 +1208,11 @@ format` applies it to disk, and `crafter-build lint` reports where it would. // decides what is safe to touch. Skip it and make the run fail. if (rule->needsAst) { if (opts.noAst) continue; + if (auto failed = commandErrors.find(owner); failed != commandErrors.end()) { + astFailures.emplace(file, failed->second); + skippedAstRules.insert(rule->name); + continue; + } // No compile command means this file is not a translation unit // of the build graph — project.cpp, which LoadProject compiles // with its own flags, or a header. A semantic rule does not