aboutsummaryrefslogtreecommitdiff
path: root/subprojects/lix-clang-tidy
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/lix-clang-tidy')
-rw-r--r--subprojects/lix-clang-tidy/.clang-format1
-rw-r--r--subprojects/lix-clang-tidy/.editorconfig4
-rw-r--r--subprojects/lix-clang-tidy/FixIncludes.cc90
-rw-r--r--subprojects/lix-clang-tidy/FixIncludes.hh21
-rw-r--r--subprojects/lix-clang-tidy/HasPrefixSuffix.cc80
-rw-r--r--subprojects/lix-clang-tidy/HasPrefixSuffix.hh25
-rw-r--r--subprojects/lix-clang-tidy/LixClangTidyChecks.cc19
-rw-r--r--subprojects/lix-clang-tidy/README.md56
-rw-r--r--subprojects/lix-clang-tidy/default.nix44
-rw-r--r--subprojects/lix-clang-tidy/meson.build18
-rw-r--r--subprojects/lix-clang-tidy/meson.options3
11 files changed, 361 insertions, 0 deletions
diff --git a/subprojects/lix-clang-tidy/.clang-format b/subprojects/lix-clang-tidy/.clang-format
new file mode 100644
index 000000000..cd8995543
--- /dev/null
+++ b/subprojects/lix-clang-tidy/.clang-format
@@ -0,0 +1 @@
+BasedOnStyle: llvm
diff --git a/subprojects/lix-clang-tidy/.editorconfig b/subprojects/lix-clang-tidy/.editorconfig
new file mode 100644
index 000000000..19ee09eec
--- /dev/null
+++ b/subprojects/lix-clang-tidy/.editorconfig
@@ -0,0 +1,4 @@
+# LLVM style code is 2-space indented
+[*.{cc,hh}]
+indent_style = space
+indent_size = 2
diff --git a/subprojects/lix-clang-tidy/FixIncludes.cc b/subprojects/lix-clang-tidy/FixIncludes.cc
new file mode 100644
index 000000000..602d3d355
--- /dev/null
+++ b/subprojects/lix-clang-tidy/FixIncludes.cc
@@ -0,0 +1,90 @@
+#include "FixIncludes.hh"
+#include <clang-tidy/ClangTidyCheck.h>
+#include <clang/Basic/Diagnostic.h>
+#include <clang/Basic/SourceManager.h>
+#include <clang/Lex/PPCallbacks.h>
+#include <clang/Lex/Preprocessor.h>
+#include <llvm/ADT/StringRef.h>
+#include <llvm/Support/Debug.h>
+#include <memory>
+#include <set>
+#include <string>
+
+namespace nix::clang_tidy {
+
+using namespace clang;
+using namespace clang::tidy;
+
+class FixIncludesCallbacks : public PPCallbacks {
+public:
+ ClangTidyCheck &Check;
+ Preprocessor &PP;
+ FixIncludesCallbacks(ClangTidyCheck &Check, Preprocessor &PP)
+ : Check(Check), PP(PP) {}
+
+private:
+ bool Ignore = false;
+ virtual void LexedFileChanged(FileID FID, LexedFileChangeReason Reason,
+ SrcMgr::CharacteristicKind FileType,
+ FileID PrevFID, SourceLocation Loc) override;
+
+ virtual void InclusionDirective(SourceLocation HashLoc,
+ const Token &IncludeTok, StringRef FileName,
+ bool IsAngled, CharSourceRange FilenameRange,
+ OptionalFileEntryRef File,
+ StringRef SearchPath, StringRef RelativePath,
+ const Module *Imported,
+ SrcMgr::CharacteristicKind FileType) override;
+};
+
+void FixIncludesCallbacks::LexedFileChanged(FileID, LexedFileChangeReason,
+ SrcMgr::CharacteristicKind FileType,
+ FileID, SourceLocation) {
+ Ignore = FileType != SrcMgr::C_User;
+}
+
+void FixIncludesCallbacks::InclusionDirective(
+ SourceLocation, const Token &, StringRef FileName, bool IsAngled,
+ CharSourceRange FilenameRange, OptionalFileEntryRef File, StringRef,
+ StringRef, const Module *, SrcMgr::CharacteristicKind) {
+ if (Ignore)
+ return;
+
+ // FIXME: this is kinda evil, but this is a one-time fixup
+ const std::vector<std::string> SourceDirs = {"src/", "include/lix/"};
+
+ const auto Bracketize = [IsAngled](StringRef s) {
+ return IsAngled ? ("<" + s + ">").str() : ("\"" + s + "\"").str();
+ };
+
+ for (const auto &SourceDir : SourceDirs) {
+ const bool IsAlreadyFixed = FileName.starts_with("lix/lib");
+ if (File && File->getNameAsRequested().contains(SourceDir) &&
+ !IsAlreadyFixed) {
+ StringRef Name = File->getNameAsRequested();
+ auto Idx = Name.find(SourceDir);
+ assert(Idx != std::string::npos);
+ std::string Suffix = Name.drop_front(Idx + SourceDir.length()).str();
+
+ if (!Suffix.starts_with("lib")) {
+ llvm::dbgs() << "ignored: " << Suffix << "\n";
+ return;
+ }
+
+ Suffix = "lix/" + Suffix;
+
+ auto Diag = Check.diag(FilenameRange.getBegin(),
+ "include needs to specify the source subdir");
+
+ Diag << FilenameRange
+ << FixItHint::CreateReplacement(FilenameRange, Bracketize(Suffix));
+ }
+ }
+}
+
+void FixIncludesCheck::registerPPCallbacks(const SourceManager &,
+ Preprocessor *PP, Preprocessor *) {
+ PP->addPPCallbacks(std::make_unique<FixIncludesCallbacks>(*this, *PP));
+}
+
+}; // namespace nix::clang_tidy
diff --git a/subprojects/lix-clang-tidy/FixIncludes.hh b/subprojects/lix-clang-tidy/FixIncludes.hh
new file mode 100644
index 000000000..ea890cd39
--- /dev/null
+++ b/subprojects/lix-clang-tidy/FixIncludes.hh
@@ -0,0 +1,21 @@
+#pragma once
+///@file
+
+#include <clang-tidy/ClangTidyCheck.h>
+#include <clang/ASTMatchers/ASTMatchFinder.h>
+#include <llvm/ADT/StringRef.h>
+
+namespace nix::clang_tidy {
+
+using namespace clang;
+using namespace clang::tidy;
+
+class FixIncludesCheck : public ClangTidyCheck {
+ public:
+ FixIncludesCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override;
+};
+
+};
diff --git a/subprojects/lix-clang-tidy/HasPrefixSuffix.cc b/subprojects/lix-clang-tidy/HasPrefixSuffix.cc
new file mode 100644
index 000000000..e29b67e7c
--- /dev/null
+++ b/subprojects/lix-clang-tidy/HasPrefixSuffix.cc
@@ -0,0 +1,80 @@
+#include "HasPrefixSuffix.hh"
+#include <clang/AST/ASTTypeTraits.h>
+#include <clang/AST/Expr.h>
+#include <clang/AST/PrettyPrinter.h>
+#include <clang/AST/Type.h>
+#include <clang/ASTMatchers/ASTMatchers.h>
+#include <clang/Basic/Diagnostic.h>
+#include <clang/Frontend/FrontendAction.h>
+#include <clang/Frontend/FrontendPluginRegistry.h>
+#include <clang/Tooling/Transformer/SourceCode.h>
+#include <clang/Tooling/Transformer/SourceCodeBuilders.h>
+#include <iostream>
+
+namespace nix::clang_tidy {
+using namespace clang::ast_matchers;
+using namespace clang;
+
+void HasPrefixSuffixCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
+ Finder->addMatcher(
+ traverse(clang::TK_AsIs,
+ callExpr(callee(functionDecl(anyOf(hasName("hasPrefix"),
+ hasName("hasSuffix")))
+ .bind("callee-decl")),
+ optionally(hasArgument(
+ 0, cxxConstructExpr(
+ hasDeclaration(functionDecl(hasParameter(
+ 0, parmVarDecl(hasType(
+ asString("const char *")))))))
+ .bind("implicit-cast"))))
+ .bind("call")),
+ this);
+}
+
+void HasPrefixSuffixCheck::check(
+ const ast_matchers::MatchFinder::MatchResult &Result) {
+
+ const auto *CalleeDecl = Result.Nodes.getNodeAs<FunctionDecl>("callee-decl");
+ auto FuncName = std::string(CalleeDecl->getName());
+ std::string NewName;
+ if (FuncName == "hasPrefix") {
+ NewName = "starts_with";
+ } else if (FuncName == "hasSuffix") {
+ NewName = "ends_with";
+ } else {
+ llvm_unreachable("nix-has-prefix: invalid callee");
+ }
+
+ const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("call");
+ const auto *ImplicitConvertArg =
+ Result.Nodes.getNodeAs<CXXConstructExpr>("implicit-cast");
+
+ const auto *Lhs = MatchedDecl->getArg(0);
+ const auto *Rhs = MatchedDecl->getArg(1);
+ auto Diag = diag(MatchedDecl->getExprLoc(), FuncName + " is deprecated");
+
+ std::string Text = "";
+
+ // Form possible cast to string_view, or nothing.
+ if (ImplicitConvertArg) {
+ Text = "std::string_view(";
+ Text.append(tooling::getText(*Lhs, *Result.Context));
+ Text.append(").");
+ } else {
+ Text.append(*tooling::buildAccess(*Lhs, *Result.Context));
+ }
+
+ // Call .starts_with.
+ Text.append(NewName);
+ Text.push_back('(');
+ Text.append(tooling::getText(*Rhs, *Result.Context));
+ Text.push_back(')');
+
+ Diag << FixItHint::CreateReplacement(MatchedDecl->getSourceRange(), Text);
+
+ // for (const auto *arg : MatchedDecl->arguments()) {
+ // arg->dumpColor();
+ // arg->getType().dump();
+ // }
+}
+}; // namespace nix::clang_tidy
diff --git a/subprojects/lix-clang-tidy/HasPrefixSuffix.hh b/subprojects/lix-clang-tidy/HasPrefixSuffix.hh
new file mode 100644
index 000000000..8693b6767
--- /dev/null
+++ b/subprojects/lix-clang-tidy/HasPrefixSuffix.hh
@@ -0,0 +1,25 @@
+#pragma once
+///@file
+/// This is an example of a clang-tidy automated refactoring against the Nix
+/// codebase. The refactoring has been completed in
+/// https://gerrit.lix.systems/c/lix/+/565 so this code is around as
+/// an example.
+
+#include <clang-tidy/ClangTidyCheck.h>
+#include <clang/ASTMatchers/ASTMatchFinder.h>
+#include <llvm/ADT/StringRef.h>
+
+namespace nix::clang_tidy {
+
+using namespace clang;
+using namespace clang::tidy;
+using namespace llvm;
+
+class HasPrefixSuffixCheck : public ClangTidyCheck {
+public:
+ HasPrefixSuffixCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+}; // namespace nix::clang_tidy
diff --git a/subprojects/lix-clang-tidy/LixClangTidyChecks.cc b/subprojects/lix-clang-tidy/LixClangTidyChecks.cc
new file mode 100644
index 000000000..b3503dd3a
--- /dev/null
+++ b/subprojects/lix-clang-tidy/LixClangTidyChecks.cc
@@ -0,0 +1,19 @@
+#include <clang-tidy/ClangTidyModule.h>
+#include <clang-tidy/ClangTidyModuleRegistry.h>
+#include "FixIncludes.hh"
+#include "HasPrefixSuffix.hh"
+
+namespace nix::clang_tidy {
+using namespace clang;
+using namespace clang::tidy;
+
+class NixClangTidyChecks : public ClangTidyModule {
+ public:
+ void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
+ CheckFactories.registerCheck<HasPrefixSuffixCheck>("lix-hasprefixsuffix");
+ CheckFactories.registerCheck<FixIncludesCheck>("lix-fixincludes");
+ }
+};
+
+static ClangTidyModuleRegistry::Add<NixClangTidyChecks> X("lix-module", "Adds lix specific checks");
+};
diff --git a/subprojects/lix-clang-tidy/README.md b/subprojects/lix-clang-tidy/README.md
new file mode 100644
index 000000000..c2d1cb258
--- /dev/null
+++ b/subprojects/lix-clang-tidy/README.md
@@ -0,0 +1,56 @@
+# Clang tidy lints for Lix
+
+This is a skeleton of a clang-tidy lints library for Lix.
+
+Currently there is one check (which is already obsolete as it has served its
+goal and is there as an example), `HasPrefixSuffixCheck`.
+
+## Running fixes/checks
+
+One file:
+
+```
+ninja -C build && clang-tidy --checks='-*,lix-*' --load=build/liblix-clang-tidy.so -p ../compile_commands.json -header-filter '\.\./src/.*\.h' --fix ../src/libcmd/installables.cc
+```
+
+Several files, in parallel:
+
+```
+ninja -C build && run-clang-tidy -checks='-*,lix-*' -load=build/liblix-clang-tidy.so -p .. -header-filter '\.\./src/.*\.h' -fix ../src | tee -a clang-tidy-result
+```
+
+## Resources
+
+* https://firefox-source-docs.mozilla.org/code-quality/static-analysis/writing-new/clang-query.html
+* https://clang.llvm.org/docs/LibASTMatchersReference.html
+* https://devblogs.microsoft.com/cppblog/exploring-clang-tooling-part-3-rewriting-code-with-clang-tidy/
+
+## Developing new checks
+
+Put something like so in `myquery.txt`:
+
+```
+set traversal IgnoreUnlessSpelledInSource
+# ^ Ignore implicit AST nodes. May need to use AsIs depending on how you are
+# working.
+set bind-root true
+# ^ true unless you use any .bind("foo") commands
+set print-matcher true
+enable output dump
+match callExpr(callee(functionDecl(hasName("hasPrefix"))), optionally(hasArgument( 0, cxxConstructExpr(hasDeclaration(functionDecl(hasParameter(0, parmVarDecl(hasType(asString("const char *"))).bind("meow2"))))))))
+```
+
+Then run, e.g. `clang-query --preload hasprefix.query -p compile_commands.json src/libcmd/installables.cc`.
+
+With this you can iterate a query before writing it in C++ and suffering from
+C++.
+
+### Tips and tricks for the C++
+
+There is a function `dump()` on many things that will dump to stderr. Also
+`llvm::errs()` lets you print to stderr.
+
+When I wrote `HasPrefixSuffixCheck`, I was not really able to figure out how
+the structured replacement system was supposed to work. In principle you can
+describe the replacement with a nice DSL. Look up the Stencil system in Clang
+for details.
diff --git a/subprojects/lix-clang-tidy/default.nix b/subprojects/lix-clang-tidy/default.nix
new file mode 100644
index 000000000..1bfc2d9a4
--- /dev/null
+++ b/subprojects/lix-clang-tidy/default.nix
@@ -0,0 +1,44 @@
+{
+ lib,
+ stdenv,
+ cmake,
+ meson,
+ ninja,
+ llvmPackages,
+}:
+let
+ inherit (lib) fileset;
+in
+stdenv.mkDerivation {
+ pname = "lix-clang-tidy-checks";
+ # Setting the version to the Lix version is just going to cause pointless
+ # rebuilds due to versionSuffix and similar, and I cannot conceive of a usage
+ # where we actually care about its version since this is internal-only.
+ version = "0.1";
+
+ src = fileset.toSource {
+ root = ./.;
+ fileset = fileset.unions [
+ ./meson.build
+ ./meson.options
+ (fileset.fileFilter (
+ { hasExt, ... }:
+ builtins.any hasExt [
+ "cc"
+ "hh"
+ ]
+ ) ./.)
+ ];
+ };
+
+ nativeBuildInputs = [
+ meson
+ cmake
+ ninja
+ ];
+
+ buildInputs = [
+ llvmPackages.llvm
+ llvmPackages.clang-unwrapped.dev
+ ];
+}
diff --git a/subprojects/lix-clang-tidy/meson.build b/subprojects/lix-clang-tidy/meson.build
new file mode 100644
index 000000000..ef0226420
--- /dev/null
+++ b/subprojects/lix-clang-tidy/meson.build
@@ -0,0 +1,18 @@
+project('lix-clang-tidy', ['cpp', 'c'],
+ version : '0.1',
+ default_options : ['warning_level=3', 'cpp_std=c++20']
+)
+
+llvm = dependency('Clang', version: '>= 17', modules: ['libclang'])
+sources = files(
+ 'HasPrefixSuffix.cc',
+ 'LixClangTidyChecks.cc',
+ 'FixIncludes.cc',
+)
+
+lix_clang_tidy = shared_module('lix-clang-tidy', sources,
+ dependencies: llvm,
+ # overrides build_by_default, see https://github.com/mesonbuild/meson/issues/13498
+ install : get_option('build-by-default'),
+ build_by_default : get_option('build-by-default')
+)
diff --git a/subprojects/lix-clang-tidy/meson.options b/subprojects/lix-clang-tidy/meson.options
new file mode 100644
index 000000000..57f0f713b
--- /dev/null
+++ b/subprojects/lix-clang-tidy/meson.options
@@ -0,0 +1,3 @@
+option('build-by-default', type : 'boolean', value : true,
+ description : 'set to false to not actually build targets by default. This is a hack to deal with meson lacking a build_by_default default option and building subprojects by default'
+)