2026-07-23 01:24:42 +02:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
|
|
|
|
|
|
module;
|
|
|
|
|
#include "Crafter.Build-Api.h"
|
|
|
|
|
export module Crafter.Build:Lint;
|
|
|
|
|
import std;
|
|
|
|
|
import :Clang;
|
|
|
|
|
|
|
|
|
|
export namespace Crafter {
|
|
|
|
|
enum class LintMode {
|
|
|
|
|
// `lint`: diagnose only; transform output becomes would-reformat
|
|
|
|
|
// findings. Never writes. The default.
|
|
|
|
|
Report,
|
|
|
|
|
// `format --check`: dry run; record the files that would change.
|
|
|
|
|
Check,
|
|
|
|
|
// `format`: rewrite changed files in place.
|
|
|
|
|
Apply,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct RunLintOptions {
|
|
|
|
|
// Rule-name globs ('*', '?'); empty = every registered rule.
|
|
|
|
|
std::vector<std::string> globs;
|
|
|
|
|
// Enumerate matching rule names without running them.
|
|
|
|
|
bool listOnly = false;
|
|
|
|
|
LintMode mode = LintMode::Report;
|
|
|
|
|
// Absolute path of the loaded project.cpp. It is linted too, and its
|
|
|
|
|
// parent directory is the project root that decides which dependency
|
|
|
|
|
// Configurations contribute rules/files (GitProject / cache-dir deps
|
|
|
|
|
// are foreign code and skipped). Empty => fall back to cfg.path.
|
|
|
|
|
std::filesystem::path projectFile;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct LintSummary {
|
|
|
|
|
std::vector<LintFinding> findings; // sorted by (file, line); filled in every mode
|
|
|
|
|
// Apply: files rewritten on disk. Report/Check: files a transform
|
|
|
|
|
// would change. The `format` verb's exit code keys off this in Check
|
|
|
|
|
// mode; formatting files in Apply mode is success.
|
|
|
|
|
std::vector<std::filesystem::path> changedFiles;
|
|
|
|
|
std::size_t filesLinted = 0;
|
|
|
|
|
std::size_t rulesRun = 0; // rules remaining after glob filter
|
feat(lint): libclang-backed token layer
Adds LintContext::Tokens() and friends, backed by clang_tokenize, as the
substrate the rules will move onto. Nothing consumes it yet.
libclang is dlopen'd rather than linked: -lclang would break the mingw and
MSVC cross-builds at link time and would put a libclang.so.NN runtime
dependency into the otherwise self-contained release tarballs. The clang-c
header is used for its declarations only, and the function-pointer table is
typed with decltype so the signatures cannot drift from the real API.
Three properties this buys that the hand-rolled scanners could not have:
- a raw string literal or block comment is ONE token, so the documented
"raw string literals are not recognized" limitation goes away;
- `//` inside a literal is not a comment, so LineHasComment() replaces the
Line(n).contains("//") probes that false-positive on it;
- tokens cover preprocessor branches that are inactive for the host, since
clang_tokenize lexes rather than evaluates #if. Token rules therefore
keep seeing every platform's code, which an AST could not offer.
The parse backing the tokenizer is expected to fail on module units — no
PCMs, no build flags — and that is fine, because lexing has no semantic
prerequisites. Verified in the new tests.
LintSummary::Clean() now counts `errors`. It previously ignored them, so an
infrastructure failure that produced no findings reported clean and exited
0; a missing libclang would have been exactly that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 02:54:38 +02:00
|
|
|
// Rule exceptions, write failures, and infrastructure failures such as
|
|
|
|
|
// libclang not loading. Counted in Clean() so a run that could not do
|
|
|
|
|
// its job never looks like a run that found nothing.
|
|
|
|
|
std::size_t errors = 0;
|
2026-07-23 01:24:42 +02:00
|
|
|
bool noRulesDefined = false; // project registered no rules at all
|
|
|
|
|
// Host-side only (like TestSummary::AllPassed), safe as in-class inline.
|
feat(lint): libclang-backed token layer
Adds LintContext::Tokens() and friends, backed by clang_tokenize, as the
substrate the rules will move onto. Nothing consumes it yet.
libclang is dlopen'd rather than linked: -lclang would break the mingw and
MSVC cross-builds at link time and would put a libclang.so.NN runtime
dependency into the otherwise self-contained release tarballs. The clang-c
header is used for its declarations only, and the function-pointer table is
typed with decltype so the signatures cannot drift from the real API.
Three properties this buys that the hand-rolled scanners could not have:
- a raw string literal or block comment is ONE token, so the documented
"raw string literals are not recognized" limitation goes away;
- `//` inside a literal is not a comment, so LineHasComment() replaces the
Line(n).contains("//") probes that false-positive on it;
- tokens cover preprocessor branches that are inactive for the host, since
clang_tokenize lexes rather than evaluates #if. Token rules therefore
keep seeing every platform's code, which an AST could not offer.
The parse backing the tokenizer is expected to fail on module units — no
PCMs, no build flags — and that is fine, because lexing has no semantic
prerequisites. Verified in the new tests.
LintSummary::Clean() now counts `errors`. It previously ignored them, so an
infrastructure failure that produced no findings reported clean and exited
0; a missing libclang would have been exactly that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 02:54:38 +02:00
|
|
|
bool Clean() const { return findings.empty() && !noRulesDefined && errors == 0; }
|
2026-07-23 01:24:42 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Run the project's lint rules over its own sources: module interfaces
|
|
|
|
|
// (+ partitions), implementations, cFiles, cuda, shaders, declared tests'
|
|
|
|
|
// sources, and project.cpp itself — for the root Configuration plus every
|
|
|
|
|
// transitive dependency whose path lies inside the project root. Rules
|
|
|
|
|
// run in registration order per file, each seeing the previous rule's
|
|
|
|
|
// transform output; a rule that throws is reverted and surfaced as a
|
|
|
|
|
// finding + error. Only Apply mode writes to disk, and only files whose
|
|
|
|
|
// final content differs from the original. Prints per-mode output:
|
|
|
|
|
// findings compiler-style (Report), would-change paths (Check), or
|
|
|
|
|
// formatted paths (Apply), plus a summary line.
|
|
|
|
|
CRAFTER_API LintSummary RunLint(Configuration& projectCfg, const RunLintOptions& opts);
|
|
|
|
|
}
|