lint rules lite

This commit is contained in:
Jorijn van der Graaf 2026-10-31 02:21:19 +01:00
commit 42a27016e3
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
4 changed files with 213 additions and 111 deletions

View file

@ -9,8 +9,10 @@ using namespace Crafter;
// Validates the house-style ruleset — the transforms and reports every repo
// that imports Crafter.LintRules relies on. Each case writes a scratch file,
// runs ONE rule via the glob filter, and asserts the findings and/or
// rewritten bytes.
// runs ONE rule via the glob filter (or all of them, for the cases about the
// ruleset as a whole), and asserts the findings and/or rewritten bytes. The
// lite cases pin AddLiteRules to AddRules minus the style rules: a style rule
// leaking into lite, or a core rule falling out of either set, fails them.
namespace {
std::int32_t Failures = 0;
@ -26,7 +28,7 @@ namespace {
std::string text; // file content after the run
};
RuleRun RunRule(std::string_view content, std::string_view rule, LintMode mode) {
RuleRun RunRule(std::string_view content, std::string_view rule, LintMode mode, void (*addRules)(Configuration&) = LintRules::AddRules) {
static std::int32_t Counter = 0;
fs::path dir = fs::temp_directory_path() / "crafter-build-house-rules" / std::format("case-{}", Counter++);
fs::remove_all(dir);
@ -43,7 +45,7 @@ namespace {
std::array<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "f" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
LintRules::AddRules(cfg);
addRules(cfg);
RunLintOptions opts;
opts.mode = mode;
opts.globs = { std::string(rule) };
@ -534,6 +536,67 @@ int main() {
Check(!HasFinding(r.summary, "'already'"), "constexpr: an existing constexpr is not re-reported");
}
// AddLiteRules is AddRules minus the style rules — the rest unchanged and
// in the same order, so reports still run before every transform. Both
// lists are spelled out: the full one so a rule lost while regrouping
// fails here, the lite one so a style rule slipping into a core group
// does.
{
auto namesOf = [](void (*addRules)(Configuration&)) {
Configuration cfg;
addRules(cfg);
std::vector<std::string> names;
for (const LintRule& rule : cfg.lintRules) names.push_back(rule.name);
return names;
};
std::vector<std::string> lite = namesOf(LintRules::AddLiteRules);
std::vector<std::string> full = namesOf(LintRules::AddRules);
std::vector<std::string> expectedLite = {
"spdx-header", "enum-class", "no-iostream-print", "no-char-pointer", "const-local", "constexpr-constant",
"format-concat", "fixed-width-types",
};
std::vector<std::string> expectedFull = {
"spdx-header", "enum-class", "no-iostream-print", "no-char-pointer", "const-local", "constexpr-constant",
"no-tabs", "naming",
"format-concat", "fixed-width-types",
"single-declaration", "brace-style", "if-single-line", "wrap-join", "paren-spacing", "trim-trailing-ws", "final-newline",
};
Check(lite == expectedLite, std::format("lite: exactly the non-style rules, in order (got {})", lite));
Check(full == expectedFull, std::format("full: all 17 rules, reports before transforms (got {})", full));
}
// The same file through both entry points. It breaks the style rules — an
// Allman brace, tabs, snake_case functions, a wrapped call padded inside
// its parens, trailing whitespace, no final newline — and two core ones:
// a plain enum, which lite reports, and an `int`, which lite rewrites.
// Lite must do exactly that and leave every other byte alone. The full run
// is the control: it shows this fixture really does trip the style rules,
// which is what makes lite leaving them alone mean something.
{
std::string_view source = "// SPDX-License-Identifier: MIT\n"
"// SPDX-FileCopyrightText: Copyright (C) 2026 Fixture\n"
"\n"
"#include <cstdint>\n"
"enum Color { Red };\n"
"void call_me(bool alpha, bool beta);\n"
"int snake_case_fn(bool alpha, bool beta)\n"
"{\n"
"\tcall_me( alpha,\n"
"\t beta ); \n"
"\treturn 0;\n"
"}";
std::string expected(source);
expected.replace(expected.find("int snake_case_fn"), 3, "std::int32_t");
RuleRun lite = RunRule(source, "*", LintMode::Apply, LintRules::AddLiteRules);
Check(lite.text == expected, "lite: rewrites the int and leaves every style violation byte for byte");
Check(lite.summary.errors == 0, "lite: every rule ran, the AST ones included");
Check(lite.summary.findings.size() == 1 && lite.summary.findings[0].rule == "enum-class", std::format("lite: the plain enum is the only finding ({} found)", lite.summary.findings.size()));
RuleRun full = RunRule(source, "*", LintMode::Apply);
Check(full.text != lite.text, "full: the style transforms rewrite the same file further");
Check(HasFinding(full.summary, "should be PascalCase"), "full: naming reports the same file");
Check(HasFinding(full.summary, "tab character"), "full: no-tabs reports the same file");
}
if (Failures > 0) {
std::println(std::cerr, "{} assertions failed", Failures);
return 1;