Crafter.Build/tests/Lint/fixture/ExportNamespace.cppm.in

21 lines
813 B
Text
Raw Normal View History

feat(lint): AST layer over libclang cursors Adds LintContext::Decls() — clang's view of the declarations written in the file — plus AddAstLintRule to register a rule that reads it. No rule uses it yet; the three that will are migrated separately. The declarations come back as a flat vector with parent indices rather than an opaque cursor handle: no lifetimes cross the project-DLL boundary, no callback hops back into project.so per node, and "is this at namespace scope or inside a function?" becomes an index lookup instead of a hand-rolled brace stack. Three things had to be solved for this to work at all on this codebase. libclang cannot see through `export`. A C++20 export declaration has no CXCursorKind, so `export namespace Crafter { … }` arrives as a childless CXCursor_UnexposedDecl and clang_visitChildren does not descend. Five of the eleven interfaces here are written that way — 677 lines, including Configuration and LintContext, yielding zero usable cursors. A plain `namespace` IS descended into, so the fix is to blank the keyword before parsing, byte-length preserving so every line and column still lands on the original file. `export module` is left alone or the unit stops being a module interface. Verified end-to-end against a fixture whose asserted line numbers match the unblanked file. PCMs are flag-locked, so each file has to parse with the flags that built it. CollectConfigSources now records which Configuration owns each source instead of flattening to a set, because three regimes coexist: the library, each test (carrying its own target, defines and -march), and project.cpp, which Build never touches and which therefore gets no command at all. libclang resolves its builtin headers relative to its own install path, which need not match the clang++ that wrote the PCMs. When it doesn't, every parse dies on "'stddef.h' file not found", so -resource-dir is passed explicitly from `clang++ -print-resource-dir`. Two flags on each declaration replace what would otherwise become more substring denylists: isExternC, and isForeignApi for a declaration that binds to an entity declared outside the project root — resolved through clang_getCursorReferenced and the same inside-the-root test the dependency walk already uses. Parameters and fields inherit it, so an exemption covers a whole signature rather than the one node that named the foreign entity. Failure is never silent. A fatal diagnostic leaves a fragment that is indistinguishable from a file declaring nothing, so it is reported as an error instead: the rule is skipped, a finding explains why, and summary.errors makes the run fail. --no-ast opts out deliberately and exits normally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 23:30:41 +02:00
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Fixture for the AST layer's export-blanking pass, copied to a scratch dir as
// Demo.cppm by tests/Lint. The .cppm.in suffix keeps the build's module-import
// scanner from treating it as a real interface of this project — the same
// convention tests/IncrementalInterfaceChange uses.
//
// libclang maps a C++20 export declaration to a childless
// CXCursor_UnexposedDecl and does not descend into it, so every declaration
// below is invisible unless the `export` keywords are blanked first. Line
// numbers are asserted, so do not reflow this file.
export module Demo;
export namespace Demo {
enum class Mode { On, Off };
struct Widget {
int count;
};
export int Exported = 1;
}