diff --git a/implementations/Crafter.Build-Lint.cpp b/implementations/Crafter.Build-Lint.cpp index 0defc5a..53f77f8 100644 --- a/implementations/Crafter.Build-Lint.cpp +++ b/implementations/Crafter.Build-Lint.cpp @@ -50,44 +50,44 @@ namespace { LibHandle handle = nullptr; std::string error; // non-empty exactly when handle is null - decltype(&clang_createIndex) CreateIndex = nullptr; - decltype(&clang_disposeIndex) DisposeIndex = nullptr; - decltype(&clang_parseTranslationUnit) ParseTranslationUnit = nullptr; - decltype(&clang_disposeTranslationUnit) DisposeTranslationUnit = nullptr; - decltype(&clang_getFile) GetFile = nullptr; - decltype(&clang_getLocationForOffset) GetLocationForOffset = nullptr; - decltype(&clang_getRange) GetRange = nullptr; - decltype(&clang_getRangeStart) GetRangeStart = nullptr; - decltype(&clang_getRangeEnd) GetRangeEnd = nullptr; - decltype(&clang_getFileLocation) GetFileLocation = nullptr; - decltype(&clang_tokenize) Tokenize = nullptr; - decltype(&clang_disposeTokens) DisposeTokens = nullptr; - decltype(&clang_getTokenKind) GetTokenKind = nullptr; - decltype(&clang_getTokenExtent) GetTokenExtent = nullptr; + decltype(&clang_createIndex) createIndex = nullptr; + decltype(&clang_disposeIndex) disposeIndex = nullptr; + decltype(&clang_parseTranslationUnit) parseTranslationUnit = nullptr; + decltype(&clang_disposeTranslationUnit) disposeTranslationUnit = nullptr; + decltype(&clang_getFile) getFile = nullptr; + decltype(&clang_getLocationForOffset) getLocationForOffset = nullptr; + decltype(&clang_getRange) getRange = nullptr; + decltype(&clang_getRangeStart) getRangeStart = nullptr; + decltype(&clang_getRangeEnd) getRangeEnd = nullptr; + decltype(&clang_getFileLocation) getFileLocation = nullptr; + decltype(&clang_tokenize) tokenize = nullptr; + decltype(&clang_disposeTokens) disposeTokens = nullptr; + decltype(&clang_getTokenKind) getTokenKind = nullptr; + decltype(&clang_getTokenExtent) getTokenExtent = nullptr; // AST layer. - decltype(&clang_getTranslationUnitCursor) GetTranslationUnitCursor = nullptr; - decltype(&clang_visitChildren) VisitChildren = nullptr; - decltype(&clang_getCursorKind) GetCursorKind = nullptr; - decltype(&clang_getCursorSpelling) GetCursorSpelling = nullptr; - decltype(&clang_getCursorType) GetCursorType = nullptr; - decltype(&clang_getTypeSpelling) GetTypeSpelling = nullptr; - decltype(&clang_getCursorLocation) GetCursorLocation = nullptr; - decltype(&clang_getCursorExtent) GetCursorExtent = nullptr; - decltype(&clang_getCursorReferenced) GetCursorReferenced = nullptr; - decltype(&clang_Cursor_getStorageClass) GetStorageClass = nullptr; - decltype(&clang_EnumDecl_isScoped) EnumDeclIsScoped = nullptr; - decltype(&clang_isCursorDefinition) IsCursorDefinition = nullptr; - decltype(&clang_Location_isFromMainFile) LocationIsFromMainFile = nullptr; - decltype(&clang_getNumDiagnostics) GetNumDiagnostics = nullptr; - decltype(&clang_getDiagnostic) GetDiagnostic = nullptr; - decltype(&clang_getDiagnosticSeverity) GetDiagnosticSeverity = nullptr; - decltype(&clang_getDiagnosticSpelling) GetDiagnosticSpelling = nullptr; - decltype(&clang_disposeDiagnostic) DisposeDiagnostic = nullptr; - decltype(&clang_getCString) GetCString = nullptr; - decltype(&clang_disposeString) DisposeString = nullptr; - decltype(&clang_getFileName) GetFileName = nullptr; - decltype(&clang_Cursor_isNull) CursorIsNull = nullptr; - decltype(&clang_getResultType) GetResultType = nullptr; + decltype(&clang_getTranslationUnitCursor) getTranslationUnitCursor = nullptr; + decltype(&clang_visitChildren) visitChildren = nullptr; + decltype(&clang_getCursorKind) getCursorKind = nullptr; + decltype(&clang_getCursorSpelling) getCursorSpelling = nullptr; + decltype(&clang_getCursorType) getCursorType = nullptr; + decltype(&clang_getTypeSpelling) getTypeSpelling = nullptr; + decltype(&clang_getCursorLocation) getCursorLocation = nullptr; + decltype(&clang_getCursorExtent) getCursorExtent = nullptr; + decltype(&clang_getCursorReferenced) getCursorReferenced = nullptr; + decltype(&clang_Cursor_getStorageClass) getStorageClass = nullptr; + decltype(&clang_EnumDecl_isScoped) enumDeclIsScoped = nullptr; + decltype(&clang_isCursorDefinition) isCursorDefinition = nullptr; + decltype(&clang_Location_isFromMainFile) locationIsFromMainFile = nullptr; + decltype(&clang_getNumDiagnostics) getNumDiagnostics = nullptr; + decltype(&clang_getDiagnostic) getDiagnostic = nullptr; + decltype(&clang_getDiagnosticSeverity) getDiagnosticSeverity = nullptr; + decltype(&clang_getDiagnosticSpelling) getDiagnosticSpelling = nullptr; + decltype(&clang_disposeDiagnostic) disposeDiagnostic = nullptr; + decltype(&clang_getCString) getCString = nullptr; + decltype(&clang_disposeString) disposeString = nullptr; + decltype(&clang_getFileName) getFileName = nullptr; + decltype(&clang_Cursor_isNull) cursorIsNull = nullptr; + decltype(&clang_getResultType) getResultType = nullptr; }; LibClang LoadLibClang() { @@ -129,43 +129,43 @@ namespace { slot = reinterpret_cast>(LibrarySymbol(lib.handle, name)); if (!slot) missing.push_back(name); }; - bind(lib.CreateIndex, "clang_createIndex"); - bind(lib.DisposeIndex, "clang_disposeIndex"); - bind(lib.ParseTranslationUnit, "clang_parseTranslationUnit"); - bind(lib.DisposeTranslationUnit, "clang_disposeTranslationUnit"); - bind(lib.GetFile, "clang_getFile"); - bind(lib.GetLocationForOffset, "clang_getLocationForOffset"); - bind(lib.GetRange, "clang_getRange"); - bind(lib.GetRangeStart, "clang_getRangeStart"); - bind(lib.GetRangeEnd, "clang_getRangeEnd"); - bind(lib.GetFileLocation, "clang_getFileLocation"); - bind(lib.Tokenize, "clang_tokenize"); - bind(lib.DisposeTokens, "clang_disposeTokens"); - bind(lib.GetTokenKind, "clang_getTokenKind"); - bind(lib.GetTokenExtent, "clang_getTokenExtent"); - bind(lib.GetTranslationUnitCursor, "clang_getTranslationUnitCursor"); - bind(lib.VisitChildren, "clang_visitChildren"); - bind(lib.GetCursorKind, "clang_getCursorKind"); - bind(lib.GetCursorSpelling, "clang_getCursorSpelling"); - bind(lib.GetCursorType, "clang_getCursorType"); - bind(lib.GetTypeSpelling, "clang_getTypeSpelling"); - bind(lib.GetCursorLocation, "clang_getCursorLocation"); - bind(lib.GetCursorExtent, "clang_getCursorExtent"); - bind(lib.GetCursorReferenced, "clang_getCursorReferenced"); - bind(lib.GetStorageClass, "clang_Cursor_getStorageClass"); - bind(lib.EnumDeclIsScoped, "clang_EnumDecl_isScoped"); - bind(lib.IsCursorDefinition, "clang_isCursorDefinition"); - bind(lib.LocationIsFromMainFile, "clang_Location_isFromMainFile"); - bind(lib.GetNumDiagnostics, "clang_getNumDiagnostics"); - bind(lib.GetDiagnostic, "clang_getDiagnostic"); - bind(lib.GetDiagnosticSeverity, "clang_getDiagnosticSeverity"); - bind(lib.GetDiagnosticSpelling, "clang_getDiagnosticSpelling"); - bind(lib.DisposeDiagnostic, "clang_disposeDiagnostic"); - bind(lib.GetCString, "clang_getCString"); - bind(lib.DisposeString, "clang_disposeString"); - bind(lib.GetFileName, "clang_getFileName"); - bind(lib.CursorIsNull, "clang_Cursor_isNull"); - bind(lib.GetResultType, "clang_getResultType"); + bind(lib.createIndex, "clang_createIndex"); + bind(lib.disposeIndex, "clang_disposeIndex"); + bind(lib.parseTranslationUnit, "clang_parseTranslationUnit"); + bind(lib.disposeTranslationUnit, "clang_disposeTranslationUnit"); + bind(lib.getFile, "clang_getFile"); + bind(lib.getLocationForOffset, "clang_getLocationForOffset"); + bind(lib.getRange, "clang_getRange"); + bind(lib.getRangeStart, "clang_getRangeStart"); + bind(lib.getRangeEnd, "clang_getRangeEnd"); + bind(lib.getFileLocation, "clang_getFileLocation"); + bind(lib.tokenize, "clang_tokenize"); + bind(lib.disposeTokens, "clang_disposeTokens"); + bind(lib.getTokenKind, "clang_getTokenKind"); + bind(lib.getTokenExtent, "clang_getTokenExtent"); + bind(lib.getTranslationUnitCursor, "clang_getTranslationUnitCursor"); + bind(lib.visitChildren, "clang_visitChildren"); + bind(lib.getCursorKind, "clang_getCursorKind"); + bind(lib.getCursorSpelling, "clang_getCursorSpelling"); + bind(lib.getCursorType, "clang_getCursorType"); + bind(lib.getTypeSpelling, "clang_getTypeSpelling"); + bind(lib.getCursorLocation, "clang_getCursorLocation"); + bind(lib.getCursorExtent, "clang_getCursorExtent"); + bind(lib.getCursorReferenced, "clang_getCursorReferenced"); + bind(lib.getStorageClass, "clang_Cursor_getStorageClass"); + bind(lib.enumDeclIsScoped, "clang_EnumDecl_isScoped"); + bind(lib.isCursorDefinition, "clang_isCursorDefinition"); + bind(lib.locationIsFromMainFile, "clang_Location_isFromMainFile"); + bind(lib.getNumDiagnostics, "clang_getNumDiagnostics"); + bind(lib.getDiagnostic, "clang_getDiagnostic"); + bind(lib.getDiagnosticSeverity, "clang_getDiagnosticSeverity"); + bind(lib.getDiagnosticSpelling, "clang_getDiagnosticSpelling"); + bind(lib.disposeDiagnostic, "clang_disposeDiagnostic"); + bind(lib.getCString, "clang_getCString"); + bind(lib.disposeString, "clang_disposeString"); + bind(lib.getFileName, "clang_getFileName"); + bind(lib.cursorIsNull, "clang_Cursor_isNull"); + bind(lib.getResultType, "clang_getResultType"); if (!missing.empty()) { lib.handle = nullptr; lib.error = std::format("loaded {} but it is missing {}", candidates.front(), join(missing)); @@ -229,36 +229,36 @@ namespace { unsaved.Contents = content.data(); unsaved.Length = static_cast(content.size()); - CXIndex index = lc.CreateIndex(0, 0); + CXIndex index = lc.createIndex(0, 0); if (!index) return {}; - CXTranslationUnit tu = lc.ParseTranslationUnit(index, path.c_str(), args.data(), static_cast(args.size()), &unsaved, 1, CXTranslationUnit_SingleFileParse | CXTranslationUnit_SkipFunctionBodies | CXTranslationUnit_KeepGoing); + CXTranslationUnit tu = lc.parseTranslationUnit(index, path.c_str(), args.data(), static_cast(args.size()), &unsaved, 1, CXTranslationUnit_SingleFileParse | CXTranslationUnit_SkipFunctionBodies | CXTranslationUnit_KeepGoing); if (!tu) { - lc.DisposeIndex(index); + lc.disposeIndex(index); return {}; } std::vector tokens; - if (CXFile cxFile = lc.GetFile(tu, path.c_str())) { - CXSourceRange whole = lc.GetRange(lc.GetLocationForOffset(tu, cxFile, 0), lc.GetLocationForOffset(tu, cxFile, static_cast(content.size()))); + if (CXFile cxFile = lc.getFile(tu, path.c_str())) { + CXSourceRange whole = lc.getRange(lc.getLocationForOffset(tu, cxFile, 0), lc.getLocationForOffset(tu, cxFile, static_cast(content.size()))); CXToken* raw = nullptr; std::uint32_t count = 0; - lc.Tokenize(tu, whole, &raw, &count); + lc.tokenize(tu, whole, &raw, &count); tokens.reserve(count); for (std::uint32_t i = 0; i < count; ++i) { - CXSourceRange extent = lc.GetTokenExtent(tu, raw[i]); + CXSourceRange extent = lc.getTokenExtent(tu, raw[i]); std::uint32_t line = 0; std::uint32_t column = 0; std::uint32_t begin = 0; std::uint32_t end = 0; - lc.GetFileLocation(lc.GetRangeStart(extent), nullptr, &line, &column, &begin); - lc.GetFileLocation(lc.GetRangeEnd(extent), nullptr, nullptr, nullptr, &end); + lc.getFileLocation(lc.getRangeStart(extent), nullptr, &line, &column, &begin); + lc.getFileLocation(lc.getRangeEnd(extent), nullptr, nullptr, nullptr, &end); if (end < begin || begin > content.size()) continue; - tokens.push_back({MapTokenKind(lc.GetTokenKind(raw[i])), begin, std::min(end - begin, content.size() - begin), line, column}); + tokens.push_back({MapTokenKind(lc.getTokenKind(raw[i])), begin, std::min(end - begin, content.size() - begin), line, column}); } - if (raw) lc.DisposeTokens(tu, raw, count); + if (raw) lc.disposeTokens(tu, raw, count); } - lc.DisposeTranslationUnit(tu); - lc.DisposeIndex(index); + lc.disposeTranslationUnit(tu); + lc.disposeIndex(index); return tokens; } @@ -269,9 +269,9 @@ namespace { // works this out for itself now: the initialiser resolves to a // declaration in clang-c/, outside the project, so the declaration is // flagged as foreign API and exempt. No suppression comment needed. - const char* raw = lc.GetCString(s); + const char* raw = lc.getCString(s); std::string out = raw ? raw : ""; - lc.DisposeString(s); + lc.disposeString(s); return out; } @@ -386,17 +386,17 @@ namespace { // from where the declaration actually lives. bool ResolvesOutsideProject(const LibClang& lc, CXCursor cursor, const fs::path& projectRoot) { if (projectRoot.empty()) return false; - CXCursor target = lc.GetCursorReferenced(cursor); - if (lc.CursorIsNull(target)) return false; - CXSourceLocation location = lc.GetCursorLocation(target); - if (lc.LocationIsFromMainFile(location)) return false; + CXCursor target = lc.getCursorReferenced(cursor); + if (lc.cursorIsNull(target)) return false; + CXSourceLocation location = lc.getCursorLocation(target); + if (lc.locationIsFromMainFile(location)) return false; CXFile file = nullptr; std::uint32_t line = 0; std::uint32_t column = 0; std::uint32_t offset = 0; - lc.GetFileLocation(location, &file, &line, &column, &offset); + lc.getFileLocation(location, &file, &line, &column, &offset); if (!file) return false; - std::string path = TakeString(lc, lc.GetFileName(file)); + std::string path = TakeString(lc, lc.getFileName(file)); if (path.empty()) return false; return !PathInsideRoot(fs::path(path), projectRoot); } @@ -404,13 +404,13 @@ namespace { CXChildVisitResult VisitDecl(CXCursor cursor, CXCursor, CXClientData data) { DeclWalk& walk = *static_cast(data); const LibClang& lc = *walk.lc; - CXSourceLocation location = lc.GetCursorLocation(cursor); + CXSourceLocation location = lc.getCursorLocation(cursor); // Every declaration the imported modules bring in arrives here too — // an unfiltered visit of one interface unit walks ~495,000 cursors from // std alone. Prune before doing any work. - if (!lc.LocationIsFromMainFile(location)) return CXChildVisit_Continue; + if (!lc.locationIsFromMainFile(location)) return CXChildVisit_Continue; - CXCursorKind kind = lc.GetCursorKind(cursor); + CXCursorKind kind = lc.getCursorKind(cursor); LintDeclKind mapped = MapCursorKind(kind); if (mapped == LintDeclKind::Other) { // A foreign reference inside a VARIABLE, FIELD or PARAMETER is an @@ -430,51 +430,51 @@ namespace { // enclosing-declaration stack stays accurate: the callback is never // told when a subtree ends. if (kind == CXCursor_LinkageSpec) { - CXSourceRange extent = lc.GetCursorExtent(cursor); + CXSourceRange extent = lc.getCursorExtent(cursor); std::uint32_t specBegin = 0; std::uint32_t specEnd = 0; - lc.GetFileLocation(lc.GetRangeStart(extent), nullptr, nullptr, nullptr, &specBegin); - lc.GetFileLocation(lc.GetRangeEnd(extent), nullptr, nullptr, nullptr, &specEnd); + lc.getFileLocation(lc.getRangeStart(extent), nullptr, nullptr, nullptr, &specBegin); + lc.getFileLocation(lc.getRangeEnd(extent), nullptr, nullptr, nullptr, &specEnd); std::string_view text; if (specBegin < walk.content->size()) { text = std::string_view(walk.content->data() + specBegin, std::min(specEnd - specBegin, walk.content->size() - specBegin)); } bool isC = IsExternCLinkage(text); if (isC) ++walk.externCDepth; - lc.VisitChildren(cursor, &VisitDecl, &walk); + lc.visitChildren(cursor, &VisitDecl, &walk); if (isC) --walk.externCDepth; return CXChildVisit_Continue; } - lc.VisitChildren(cursor, &VisitDecl, &walk); + lc.visitChildren(cursor, &VisitDecl, &walk); return CXChildVisit_Continue; } LintDecl decl; decl.kind = mapped; - decl.name = TakeString(lc, lc.GetCursorSpelling(cursor)); + decl.name = TakeString(lc, lc.getCursorSpelling(cursor)); // For anything callable, `type` is the RESULT type rather than the // whole function type: the parameters arrive as their own Parameter // declarations, so spelling them here too would make every rule // reading `type` report each one twice. bool callable = mapped == LintDeclKind::Function || mapped == LintDeclKind::Method; - decl.type = TakeString(lc, lc.GetTypeSpelling(callable ? lc.GetResultType(lc.GetCursorType(cursor)) : lc.GetCursorType(cursor))); + decl.type = TakeString(lc, lc.getTypeSpelling(callable ? lc.getResultType(lc.getCursorType(cursor)) : lc.getCursorType(cursor))); CXFile nameFile = nullptr; std::uint32_t nameLine = 0; std::uint32_t nameColumn = 0; std::uint32_t nameOffset = 0; - lc.GetFileLocation(location, &nameFile, &nameLine, &nameColumn, &nameOffset); + lc.getFileLocation(location, &nameFile, &nameLine, &nameColumn, &nameOffset); decl.line = nameLine; decl.column = nameColumn; - CXSourceRange extent = lc.GetCursorExtent(cursor); + CXSourceRange extent = lc.getCursorExtent(cursor); std::uint32_t begin = 0; std::uint32_t end = 0; - lc.GetFileLocation(lc.GetRangeStart(extent), nullptr, nullptr, nullptr, &begin); - lc.GetFileLocation(lc.GetRangeEnd(extent), nullptr, nullptr, nullptr, &end); + lc.getFileLocation(lc.getRangeStart(extent), nullptr, nullptr, nullptr, &begin); + lc.getFileLocation(lc.getRangeEnd(extent), nullptr, nullptr, nullptr, &end); decl.begin = begin; decl.end = std::min(end, walk.content->size()); - decl.isDefinition = lc.IsCursorDefinition(cursor) != 0; - decl.isStatic = lc.GetStorageClass(cursor) == CX_SC_Static; - decl.isScopedEnum = mapped == LintDeclKind::Enum && lc.EnumDeclIsScoped(cursor) != 0; + decl.isDefinition = lc.isCursorDefinition(cursor) != 0; + decl.isStatic = lc.getStorageClass(cursor) == CX_SC_Static; + decl.isScopedEnum = mapped == LintDeclKind::Enum && lc.enumDeclIsScoped(cursor) != 0; // Inside an extern "C" block, where a C API's spelling is not ours to // modernise. NOT clang_getCursorLanguage: its default answer for a // plain function, variable or parameter is CXLanguage_C even in a C++ @@ -492,7 +492,7 @@ namespace { walk.out->push_back(std::move(decl)); walk.stack.push_back(walk.out->size() - 1); - lc.VisitChildren(cursor, &VisitDecl, &walk); + lc.visitChildren(cursor, &VisitDecl, &walk); walk.stack.pop_back(); return CXChildVisit_Continue; } @@ -504,7 +504,7 @@ namespace { walk.content = &content; walk.projectRoot = &projectRoot; walk.out = &decls; - lc.VisitChildren(lc.GetTranslationUnitCursor(tu), &VisitDecl, &walk); + lc.visitChildren(lc.getTranslationUnitCursor(tu), &VisitDecl, &walk); // A declaration on an interop boundary makes its parameters and fields // interop too — the exemption has to cover the whole signature, not // just the node that happened to name the foreign entity. @@ -579,34 +579,34 @@ namespace { unsaved.Contents = buffer.data(); unsaved.Length = static_cast(buffer.size()); - CXIndex index = lc.CreateIndex(0, 0); + CXIndex index = lc.createIndex(0, 0); if (!index) { result.error = "clang_createIndex failed"; return result; } - CXTranslationUnit tu = lc.ParseTranslationUnit(index, path.c_str(), argv.data(), static_cast(argv.size()), &unsaved, 1, CXTranslationUnit_None); + CXTranslationUnit tu = lc.parseTranslationUnit(index, path.c_str(), argv.data(), static_cast(argv.size()), &unsaved, 1, CXTranslationUnit_None); if (!tu) { - lc.DisposeIndex(index); + lc.disposeIndex(index); result.error = "clang could not create a translation unit"; return result; } std::string fatal; - std::uint32_t diagnostics = lc.GetNumDiagnostics(tu); + std::uint32_t diagnostics = lc.getNumDiagnostics(tu); for (std::uint32_t i = 0; i < diagnostics && fatal.empty(); ++i) { - CXDiagnostic diagnostic = lc.GetDiagnostic(tu, i); - if (lc.GetDiagnosticSeverity(diagnostic) == CXDiagnostic_Fatal) { - fatal = TakeString(lc, lc.GetDiagnosticSpelling(diagnostic)); + CXDiagnostic diagnostic = lc.getDiagnostic(tu, i); + if (lc.getDiagnosticSeverity(diagnostic) == CXDiagnostic_Fatal) { + fatal = TakeString(lc, lc.getDiagnosticSpelling(diagnostic)); } - lc.DisposeDiagnostic(diagnostic); + lc.disposeDiagnostic(diagnostic); } if (fatal.empty()) { result.decls = WalkDecls(lc, tu, buffer, projectRoot); } else { result.error = std::move(fatal); } - lc.DisposeTranslationUnit(tu); - lc.DisposeIndex(index); + lc.disposeTranslationUnit(tu); + lc.disposeIndex(index); return result; } diff --git a/implementations/Crafter.Build-Shader.cpp b/implementations/Crafter.Build-Shader.cpp index 5bc01b7..4791ee6 100644 --- a/implementations/Crafter.Build-Shader.cpp +++ b/implementations/Crafter.Build-Shader.cpp @@ -71,12 +71,12 @@ namespace Crafter { std::string src = contents.str(); std::string pathStr = path.string(); - const char* file_name_list[1] = { pathStr.c_str() }; + const char* fileNameList[1] = { pathStr.c_str() }; const char* shaderSource = src.data(); const std::int32_t shaderSourceLen = static_cast(src.size()); glslang::TShader shader(glslangType); - shader.setStringsWithLengthsAndNames(&shaderSource, &shaderSourceLen, file_name_list, 1); + shader.setStringsWithLengthsAndNames(&shaderSource, &shaderSourceLen, fileNameList, 1); shader.setEntryPoint(entrypoint.c_str()); shader.setSourceEntryPoint(entrypoint.c_str()); shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_4); diff --git a/lint-rules.h b/lint-rules.h index b9c1dd6..b18c9fc 100644 --- a/lint-rules.h +++ b/lint-rules.h @@ -82,15 +82,6 @@ inline bool IsCharPointer(std::string_view type) { return base == "char"; } -// The identifier ending right before position `pos` (exclusive), or empty. -inline std::string_view WordBefore(std::string_view s, std::size_t pos) { - std::size_t end = pos; - while (end > 0 && (s[end - 1] == ' ' || s[end - 1] == '\t')) --end; - std::size_t begin = end; - while (begin > 0 && (IsWordChar(s[begin - 1]) || s[begin - 1] == '~')) --begin; - return s.substr(begin, end - begin); -} - inline void AddProjectLintRules(Crafter::Configuration& cfg) { using Crafter::LintContext; @@ -118,132 +109,77 @@ inline void AddProjectLintRules(Crafter::Configuration& cfg) { } }); - // Naming: functions/types PascalCase, variables camelCase, statics and - // namespace-scope globals PascalCase. A line-based scope tracker decides - // whether a declaration sits at namespace scope (global) or inside a - // function/type (local/member). Heuristic by nature — report-only. - cfg.AddLintRule("naming", [](LintContext& ctx) { + // Naming: types and functions PascalCase, variables camelCase, with + // statics, namespace-scope globals and constexpr constants PascalCase. + // + // Reads the AST. The previous version needed four std::regex, a hand-rolled + // {/} scope stack, a cumulative paren-depth counter to avoid mistaking a + // wrapped parameter list for a declaration, a keyword denylist, and a + // "function-shaped line" guess whose own comment conceded it was heuristic. + // All of it existed to answer two questions clang answers directly: what + // kind of declaration is this, and what encloses it. + cfg.AddAstLintRule("naming", [](LintContext& ctx) { if (!IsCppFile(ctx)) return; - std::vector lines = Lines(ctx.CommentStripped()); - - enum class Scope { Namespace, Type, Function, Other }; - std::vector stack; - auto currentScope = [&]() { return stack.empty() ? Scope::Namespace : stack.back(); }; - - static const std::regex typeDecl(R"(\b(?:class|struct|union)\s+(?:CRAFTER_API\s+)?([A-Za-z_]\w*))"); - static const std::regex enumDecl(R"(\benum\s+(?:class\s+|struct\s+)?([A-Za-z_]\w*))"); - static const std::regex usingDecl(R"(^\s*using\s+([A-Za-z_]\w*)\s*=)"); - static const std::regex varDecl( - R"(^\s*((?:static|constexpr|const|inline|mutable|thread_local|export|CRAFTER_API)\s+)*)" - R"((?:std::)?[A-Za-z_][\w:]*(?:<[^;={]*>)?(?:\s*[&*])*\s+([A-Za-z_]\w*)\s*(=|;|\{))"); - static const std::unordered_set keywords = { - "if", "for", "while", "switch", "catch", "return", "else", "do", "case", "goto", - "new", "delete", "throw", "using", "namespace", "template", "typedef", "friend", - "public", "private", "protected", "class", "struct", "enum", "union", "import", - "module", "export", "break", "continue", "co_return", "co_await", "co_yield", - "sizeof", "alignof", "decltype", "static_assert", "operator", "try", "requires", - }; - - // Cumulative paren depth at the start of each line: declaration and - // function checks only run at depth 0, so wrapped parameter lists and - // continuation lines (`) {` closers) never look like declarations. - std::int64_t parenDepth = 0; - for (std::size_t i = 0; i < lines.size(); ++i) { - std::string_view trimmed = Trim(lines[i]); - std::string lineStr(lines[i]); - std::smatch m; - bool atDepth0 = parenDepth == 0; - parenDepth = std::max(0, parenDepth + ParenDelta(lines[i])); - - if (!trimmed.starts_with('#') && atDepth0) { - // Type / alias names must be PascalCase. - if (std::regex_search(lineStr, m, typeDecl) || std::regex_search(lineStr, m, enumDecl)) { - std::string name = m[1].str(); - if (!keywords.contains(name) && !IsPascalCase(name)) { - ctx.Report(i + 1, std::format("type '{}' should be PascalCase", name)); + using Kind = Crafter::LintDeclKind; + std::span decls = ctx.Decls(); + for (const Crafter::LintDecl& decl : decls) { + if (decl.name.empty()) continue; // anonymous namespace, unnamed struct + // A declaration with C language linkage is named by the C library + // it mirrors — `extern "C" int setenv(...)` is not ours to rename. + if (decl.isExternC) continue; + // Namespace scope is now a lookup rather than a brace count, and it + // is exact for a local declared inside a function body. + bool atNamespaceScope = decl.parent == Crafter::LintNoParent + || decls[decl.parent].kind == Kind::Namespace; + switch (decl.kind) { + case Kind::Class: + case Kind::Struct: + case Kind::Union: + case Kind::Enum: + if (!IsPascalCase(decl.name)) { + ctx.Report(decl.line, std::format("type '{}' should be PascalCase", decl.name)); } - } - if (std::regex_search(lineStr, m, usingDecl) && !IsPascalCase(m[1].str())) { - ctx.Report(i + 1, std::format("type alias '{}' should be PascalCase", m[1].str())); - } - - // Function definitions: identifier before the first '(' on a - // line that ends where a body opens or closes — a multi-line - // def's trailing '{' or a one-liner's trailing '}'. Statement - // calls with inline lambda arguments look similar - // (`bool x = std::any_of(..., [](T v) { ... });`) but end in - // ';' and/or carry '=' before the name — both excluded. - // Lambdas ("](") and control keywords are skipped; ctors/ - // dtors pass the Pascal check by construction; `main` and - // operators exempt. - std::size_t bodyBrace = trimmed.find('{'); - bool functionShaped = trimmed.ends_with('{') || trimmed.ends_with('}'); - if (functionShaped && currentScope() != Scope::Function && !trimmed.starts_with("return")) { - std::size_t paren = trimmed.find('('); - if (paren != std::string_view::npos && paren > 0 && paren < bodyBrace) { - std::string_view name = WordBefore(trimmed, paren); - char before = trimmed[paren - 1]; - bool looksLikeDef = !name.empty() && IsWordChar(before) - && !keywords.contains(name) && name != "main" - && !name.starts_with('~'); - // Require a type token before the name (or a - // qualified Class::Name), with no '=' in front — - // plain calls and initializations don't match. - if (looksLikeDef) { - std::size_t nameStart = trimmed.rfind(name, paren); - std::string_view prefix = Trim(trimmed.substr(0, nameStart)); - looksLikeDef = !prefix.empty() && !prefix.contains('=') - && (IsWordChar(prefix.back()) || prefix.back() == '>' - || prefix.back() == '*' || prefix.back() == '&' - || prefix.ends_with("::")); + break; + case Kind::TypeAlias: + if (!IsPascalCase(decl.name)) { + ctx.Report(decl.line, std::format("type alias '{}' should be PascalCase", decl.name)); + } + break; + case Kind::Function: + case Kind::Method: + // main is spelled by the language; operators by their + // symbol. Constructors and destructors take their type's + // name and are separate kinds, so they never arrive here. + if (decl.name == "main" || decl.name.starts_with("operator")) break; + if (!IsPascalCase(decl.name)) { + ctx.Report(decl.line, std::format("function '{}' should be PascalCase", decl.name)); + } + break; + case Kind::Variable: + // constexpr variables are compile-time constants and take + // constant naming, like statics and globals. + if (decl.isStatic || decl.isConstexpr || atNamespaceScope) { + if (!IsPascalCase(decl.name)) { + ctx.Report(decl.line, std::format("{} '{}' should be PascalCase", + decl.isStatic ? "static variable" + : decl.isConstexpr ? "constexpr constant" + : "global variable", decl.name)); } - if (looksLikeDef && !IsPascalCase(name)) { - ctx.Report(i + 1, std::format("function '{}' should be PascalCase", name)); + } else if (!IsCamelCase(decl.name)) { + ctx.Report(decl.line, std::format("variable '{}' should be camelCase", decl.name)); + } + break; + case Kind::Field: + if (decl.isStatic || decl.isConstexpr) { + if (!IsPascalCase(decl.name)) { + ctx.Report(decl.line, std::format("static member '{}' should be PascalCase", decl.name)); } + } else if (!IsCamelCase(decl.name)) { + ctx.Report(decl.line, std::format("member '{}' should be camelCase", decl.name)); } - } - - // Variable declarations: camelCase locally, PascalCase for - // statics and namespace-scope globals. - if (std::regex_search(lineStr, m, varDecl)) { - std::string name = m[2].str(); - std::string_view typeToken = Trim(std::string_view(lineStr).substr(0, static_cast(m.position(2)))); - std::string_view firstWord = typeToken.substr(0, typeToken.find_first_of(" \t<")); - if (!keywords.contains(firstWord) && !keywords.contains(name)) { - bool isStatic = lineStr.contains("static "); - // constexpr variables are compile-time constants — - // constant naming (PascalCase) like statics/globals. - bool isConstexpr = lineStr.contains("constexpr "); - bool global = currentScope() == Scope::Namespace; - if ((isStatic || global || isConstexpr) && !IsPascalCase(name)) { - ctx.Report(i + 1, std::format("{} '{}' should be PascalCase", - isStatic ? "static variable" - : isConstexpr ? "constexpr constant" - : "global variable", name)); - } else if (!isStatic && !global && !isConstexpr && !IsCamelCase(name)) { - ctx.Report(i + 1, std::format("variable '{}' should be camelCase", name)); - } - } - } - } - - // Scope tracking: classify each '{' opened on this line; pop on '}'. - for (std::size_t c = 0; c < lines[i].size(); ++c) { - if (lines[i][c] == '{') { - Scope kind = Scope::Other; - std::string_view upTo = Trim(lines[i].substr(0, c)); - if (trimmed.starts_with("namespace") || upTo.contains("namespace ")) { - kind = Scope::Namespace; - } else if (std::regex_search(lineStr, typeDecl) || std::regex_search(lineStr, enumDecl)) { - kind = Scope::Type; - } else if (upTo.ends_with(')') || upTo.ends_with("const") || upTo.ends_with("noexcept") - || upTo.ends_with("->") || trimmed.starts_with("extern")) { - kind = Scope::Function; // function/lambda/control body — all non-global - } - stack.push_back(kind); - } else if (lines[i][c] == '}') { - if (!stack.empty()) stack.pop_back(); - } + break; + default: + break; } } }); diff --git a/tests/HouseRules/main.cpp b/tests/HouseRules/main.cpp index 34c6398..e34adbb 100644 --- a/tests/HouseRules/main.cpp +++ b/tests/HouseRules/main.cpp @@ -327,6 +327,42 @@ int main() { Check(HasFinding(r.summary, "'raw'"), "no-char-pointer: a deduced char* local is still ours"); } + // Cases the line-based heuristic structurally could not see. It only looked + // at declarations starting at cumulative paren depth 0 and inferred scope + // from a running {/} count, so a wrapped signature, a specifier split over + // two lines, or a local shadowing a member all escaped it. + { + RuleRun r = RunRule("#include \n" + "namespace Outer {\n" + " int wrapped_function(\n" + " int first,\n" + " int second) { return first + second; }\n" + " static\n" + " int splitStatic = 1;\n" + " struct Holder {\n" + " int Member = 0;\n" + " void Method() {\n" + " int Local = 1;\n" + " (void)Local;\n" + " }\n" + " };\n" + "}\n" + "extern \"C\" int c_api_entry(const char* name);\n", + "naming", LintMode::Report); + // A signature wrapped over lines is still a function declaration. + Check(HasFinding(r.summary, "'wrapped_function' should be PascalCase"), "naming: wrapped signature is seen"); + // `static` on its own line still applies to the declaration below it. + Check(HasFinding(r.summary, "'splitStatic' should be PascalCase"), "naming: split specifier is seen"); + // Members are camelCase; the enclosing kind decides, not a brace count. + Check(HasFinding(r.summary, "'Member' should be camelCase"), "naming: member is checked as a member"); + // A local inside a method is a local, not a member and not a global. + Check(HasFinding(r.summary, "'Local' should be camelCase"), "naming: local inside a method is a local"); + // Named by libc, not by us. + Check(!HasFinding(r.summary, "c_api_entry"), "naming: extern \"C\" declaration is exempt"); + Check(!HasFinding(r.summary, "'Method'"), "naming: PascalCase method passes"); + Check(!HasFinding(r.summary, "'Holder'"), "naming: PascalCase type passes"); + } + if (Failures > 0) { std::println(std::cerr, "{} assertions failed", Failures); return 1;