Merge "clang-tidy check infrastructure" into main
This commit is contained in:
commit
a7161b6c0f
1
clang-tidy/.clang-format
Normal file
1
clang-tidy/.clang-format
Normal file
|
@ -0,0 +1 @@
|
||||||
|
BasedOnStyle: llvm
|
80
clang-tidy/HasPrefixSuffix.cc
Normal file
80
clang-tidy/HasPrefixSuffix.cc
Normal file
|
@ -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
|
25
clang-tidy/HasPrefixSuffix.hh
Normal file
25
clang-tidy/HasPrefixSuffix.hh
Normal file
|
@ -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
|
17
clang-tidy/NixClangTidyChecks.cc
Normal file
17
clang-tidy/NixClangTidyChecks.cc
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#include <clang-tidy/ClangTidyModule.h>
|
||||||
|
#include <clang-tidy/ClangTidyModuleRegistry.h>
|
||||||
|
#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>("nix-hasprefixsuffix");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static ClangTidyModuleRegistry::Add<NixClangTidyChecks> X("nix-module", "Adds nix specific checks");
|
||||||
|
};
|
56
clang-tidy/README.md
Normal file
56
clang-tidy/README.md
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# Clang tidy lints for Nix
|
||||||
|
|
||||||
|
This is a skeleton of a clang-tidy lints library for Nix.
|
||||||
|
|
||||||
|
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='-*,nix-*' --load=build/libnix-clang-tidy.so -p ../compile_commands.json --fix ../src/libcmd/installables.cc
|
||||||
|
```
|
||||||
|
|
||||||
|
Several files, in parallel:
|
||||||
|
|
||||||
|
```
|
||||||
|
ninja -C build && run-clang-tidy -checks='-*,nix-*' -load=build/libnix-clang-tidy.so -p .. -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.
|
8
clang-tidy/meson.build
Normal file
8
clang-tidy/meson.build
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
project('nix-clang-tidy', ['cpp', 'c'],
|
||||||
|
version : '0.1',
|
||||||
|
default_options : ['warning_level=3', 'cpp_std=c++20'])
|
||||||
|
|
||||||
|
llvm = dependency('Clang', version: '>= 14', modules: ['libclang'])
|
||||||
|
sources = ['HasPrefixSuffix.cc', 'NixClangTidyChecks.cc']
|
||||||
|
shared_module('nix-clang-tidy', sources,
|
||||||
|
dependencies: llvm)
|
|
@ -317,11 +317,18 @@
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
nix.overrideAttrs (prev: {
|
nix.overrideAttrs (prev: {
|
||||||
|
# Required for clang-tidy checks
|
||||||
|
buildInputs = prev.buildInputs ++ lib.optionals (stdenv.cc.isClang) [ pkgs.llvmPackages.llvm pkgs.llvmPackages.clang-unwrapped.dev ];
|
||||||
nativeBuildInputs = prev.nativeBuildInputs
|
nativeBuildInputs = prev.nativeBuildInputs
|
||||||
++ lib.optional (stdenv.cc.isClang && !stdenv.buildPlatform.isDarwin) pkgs.buildPackages.bear
|
++ lib.optional (stdenv.cc.isClang && !stdenv.buildPlatform.isDarwin) pkgs.buildPackages.bear
|
||||||
|
# Required for clang-tidy checks
|
||||||
|
++ lib.optionals (stdenv.cc.isClang) [ pkgs.buildPackages.cmake pkgs.buildPackages.ninja pkgs.buildPackages.llvmPackages.llvm.dev ]
|
||||||
++ lib.optional
|
++ lib.optional
|
||||||
(stdenv.cc.isClang && stdenv.hostPlatform == stdenv.buildPlatform)
|
(stdenv.cc.isClang && stdenv.hostPlatform == stdenv.buildPlatform)
|
||||||
pkgs.buildPackages.clang-tools;
|
# for some reason that seems accidental and was changed in
|
||||||
|
# NixOS 24.05-pre, clang-tools is pinned to LLVM 14 when
|
||||||
|
# default LLVM is newer.
|
||||||
|
(pkgs.buildPackages.clang-tools.override { inherit (pkgs.buildPackages) llvmPackages; });
|
||||||
|
|
||||||
src = null;
|
src = null;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue