Skip to content

Commit

Permalink
Add a framework for a subspace clang-tidy module
Browse files Browse the repository at this point in the history
  • Loading branch information
danakj committed Mar 11, 2023
1 parent 220dd42 commit 8f40b08
Show file tree
Hide file tree
Showing 9 changed files with 376 additions and 0 deletions.
13 changes: 13 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ include(OptionIf)

option_if_not_defined(SUBSPACE_BUILD_CIR "Build CIR (requires LLVM)" ON)
option_if_not_defined(SUBSPACE_BUILD_SUBDOC "Build subdoc (requires LLVM)" ON)
option_if_not_defined(SUBSPACE_BUILD_TIDY "Build clang-tidy plugin (requires LLVM)" NOT WIN32)

message(STATUS "Build CIR: ${SUBSPACE_BUILD_CIR}")
message(STATUS "Build subdoc: ${SUBSPACE_BUILD_SUBDOC}")
message(STATUS "Build clang-tidy plugin: ${SUBSPACE_BUILD_TIDY}")

function(subspace_default_compile_options TARGET)
if(MSVC)
Expand Down Expand Up @@ -81,3 +83,14 @@ endif()
if (${SUBSPACE_BUILD_SUBDOC})
add_subdirectory(subdoc)
endif()

if(${SUBSPACE_BUILD_TIDY})
add_subdirectory(tidy)

#set(CMAKE_CXX_CLANG_TIDY
# clang-tidy;
# -header-filter=.;
# -checks=-*,subspace-*;
# -warnings-as-errors=*;
#)
endif()
81 changes: 81 additions & 0 deletions tidy/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

add_library(subspace_clang_tidy_module MODULE "")
add_library(subspace::clang_tidy ALIAS subspace_clang_tidy_module)
target_sources(subspace_clang_tidy_module PUBLIC
"llvm.h"
"module.cc"
"smoke_check.cc"
"smoke_check.h"
)

target_link_libraries(subspace_clang_tidy_module
subspace::lib

clangTidy

clangAnalysis
clangAnalysisFlowSensitive
clangAnalysisFlowSensitiveModels
clangAPINotes
clangARCMigrate
clangAST
clangASTMatchers
clangBasic
clangCodeGen
clangCrossTU
clangDependencyScanning
clangDirectoryWatcher
clangDriver
clangDynamicASTMatchers
clangEdit
clangExtractAPI
clangFormat
clangFrontend
clangFrontendTool
clangHandleCXX
clangHandleLLVM
clangIndex
clangIndexSerialization
clangInterpreter
clangLex
clangParse
clangRewrite
clangRewriteFrontend
clangSema
clangSerialization
clangStaticAnalyzerCheckers
clangStaticAnalyzerCore
clangStaticAnalyzerFrontend
clangSupport
clangTooling
clangToolingASTDiff
clangToolingCore
clangToolingInclusions
clangToolingInclusionsStdlib
clangToolingRefactoring
clangToolingSyntax
clangTransformer
)

find_package(Clang REQUIRED)
llvm_config(subdoc_lib)
target_include_directories(subspace_clang_tidy_module PUBLIC ${LLVM_INCLUDE_DIRS})
target_link_directories(subspace_clang_tidy_module PUBLIC ${LLVM_LIBRARY_DIRS})

# Subspace clang-tidy module.
subspace_default_compile_options(subspace_clang_tidy_module)

add_subdirectory(tests)
47 changes: 47 additions & 0 deletions tidy/llvm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

// All LLVM and Clang includes go here, because they are full of compiler
// warnings that we have to disable.

#pragma warning(push)
#pragma warning(disable : 4100)
#pragma warning(disable : 4127)
#pragma warning(disable : 4146)
#pragma warning(disable : 4244)
#pragma warning(disable : 4245)
#pragma warning(disable : 4267)
#pragma warning(disable : 4291)
#pragma warning(disable : 4324)
#pragma warning(disable : 4389)
#pragma warning(disable : 4456)
#pragma warning(disable : 4458)
#pragma warning(disable : 4459)
#pragma warning(disable : 4616)
#pragma warning(disable : 4624)
#pragma warning(disable : 4702)

#include "clang-tidy/ClangTidyCheck.h"
#include "clang-tidy/ClangTidyModule.h"
#include "clang-tidy/ClangTidyModuleRegistry.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclGroup.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"

#pragma warning(pop)
30 changes: 30 additions & 0 deletions tidy/module.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "tidy/llvm.h"
#include "tidy/smoke_check.h"

namespace clang::tidy::subspace {

class SubspaceClangTidyModule : public ClangTidyModule {
void addCheckFactories(ClangTidyCheckFactories& factories) override {
factories.registerCheck<SmokeCheck>("subspace-smoke");
}
};

static ClangTidyModuleRegistry::Add<SubspaceClangTidyModule> X(
"subspace-clang-tidy-module",
"Adds lint checks to be used with the Subspace library.");

} // namespace clang::tidy::subspace
41 changes: 41 additions & 0 deletions tidy/smoke_check.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "tidy/smoke_check.h"

#include "subspace/prelude.h"
#include "tidy/llvm.h"

namespace clang::tidy::subspace {

SmokeCheck::SmokeCheck(llvm::StringRef name, ClangTidyContext* context)
: ClangTidyCheck(sus::move(name), context) {}

void SmokeCheck::registerMatchers(ast_matchers::MatchFinder* finder) {
using namespace ast_matchers;

finder->addMatcher(functionDecl().bind("x"), this);
}

void SmokeCheck::check(const ast_matchers::MatchFinder::MatchResult& match) {
const auto* MatchedDecl = match.Nodes.getNodeAs<FunctionDecl>("x");
if (!MatchedDecl->getIdentifier() ||
MatchedDecl->getName().startswith("awesome_"))
return;
diag(MatchedDecl->getLocation(), "function %0 is insufficiently awesome")
<< MatchedDecl
<< FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
}

} // namespace clang::tidy::subspace
29 changes: 29 additions & 0 deletions tidy/smoke_check.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include "subspace/macros/compiler.h"
#include "tidy/llvm.h"

namespace clang::tidy::subspace {

class SmokeCheck : public ClangTidyCheck {
public:
SmokeCheck(llvm::StringRef name, ClangTidyContext* context);
void registerMatchers(ast_matchers::MatchFinder* finder) override;
void check(const ast_matchers::MatchFinder::MatchResult& match) override;
};

} // namespace clang::tidy::subspace
27 changes: 27 additions & 0 deletions tidy/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

enable_testing()

function(tidy_test check_name)
add_test(NAME "RunClangTidy.${check_name}" COMMAND ${CMAKE_COMMAND}
"-DCLANG_TIDY_COMMAND=$<TARGET_FILE:clang-tidy>"
"-DCLANG_TIDY_MODULE=$<TARGET_FILE:subspace_clang_tidy_module>"
"-DCHECK_NAME=${check_name}"
"-DRunClangTidy_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/run_clang_tidy.cmake"
)
endfunction()

tidy_test(subspace-smoke)
107 changes: 107 additions & 0 deletions tidy/tests/run_clang_tidy.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set(config_arg)
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.clang-tidy")
set(config_arg "--config-file=${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.clang-tidy")
endif()

if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}-stdout.txt")
file(READ "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}-stdout.txt" expect_stdout)
string(REGEX REPLACE "\n+$" "" expect_stdout "${expect_stdout}")
else()
set(expect_stdout "")
endif()

set(source_file "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}.cc")
configure_file("${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.cc" "${source_file}" COPYONLY)

file(GLOB header_files RELATIVE "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}" "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/*")
file(REMOVE_RECURSE "${RunClangTiy_BINARY_DIR}/${CHECK_NAME}")
foreach(header_file IN LISTS header_files)
if(NOT header_file MATCHES "-fixit\\.h\$")
file(MAKE_DIRECTORY "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}")
configure_file("${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/${header_file}" "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}/${header_file}" COPYONLY)
endif()
endforeach()

set(command
"${CLANG_TIDY_COMMAND}"
"--load=${CLANG_TIDY_MODULE}"
"--checks=-*,${CHECK_NAME}"
"--fix"
"--format-style=file"
"--header-filter=/${CHECK_NAME}/"
${config_arg}
"${source_file}"
--
)
execute_process(
COMMAND ${command}
RESULT_VARIABLE result
OUTPUT_VARIABLE actual_stdout
ERROR_VARIABLE actual_stderr
)
string(REPLACE "${RunClangTidy_BINARY_DIR}/" "" actual_stdout "${actual_stdout}")

set(RunClangTidy_TEST_FAILED)

if(NOT result EQUAL 0)
string(APPEND RunClangTidy_TEST_FAILED "Expected result: 0, actual result: ${result}\n")
endif()

string(REGEX REPLACE "\n+$" "" actual_stdout "${actual_stdout}")
if(NOT actual_stdout STREQUAL expect_stdout)
string(REPLACE "\n" "\n " expect_stdout_formatted " ${expect_stdout}")
string(REPLACE "\n" "\n " actual_stdout_formatted " ${actual_stdout}")
string(APPEND RunClangTidy_TEST_FAILED "Expected stdout:\n${expect_stdout_formatted}\nActual stdout:\n${actual_stdout_formatted}\n")
endif()

function(check_fixit expected fallback_expected actual)
if(EXISTS "${expected}")
set(expect_fixit_file "${expected}")
else()
set(expect_fixit_file "${fallback_expected}")
endif()
file(READ "${expect_fixit_file}" expect_fixit)
file(READ "${actual}" actual_fixit)
if(NOT expect_fixit STREQUAL actual_fixit)
string(REPLACE "\n" "\n " expect_fixit_formatted " ${expect_fixit}")
string(REPLACE "\n" "\n " actual_fixit_formatted " ${actual_fixit}")
string(APPEND RunClangTidy_TEST_FAILED "Expected fixit for ${actual}:\n${expect_fixit_formatted}\nActual fixit:\n${actual_fixit_formatted}\n")
set(RunClangTidy_TEST_FAILED "${RunClangTidy_TEST_FAILED}" PARENT_SCOPE)
endif()
endfunction()

check_fixit(
"${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}-fixit.cc"
"${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.cc"
"${source_file}"
)

foreach(header_file IN LISTS header_files)
if(NOT header_file MATCHES "-fixit\\.h\$")
string(REGEX REPLACE "\\.h\$" "-fixit.h" header_fixit "${header_file}")
check_fixit(
"${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/${header_fixit}"
"${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/${header_file}"
"${RunClangTidy_BINARY_DIR}/${CHECK_NAME}/${header_file}"
)
endif()
endforeach()

if(RunClangTidy_TEST_FAILED)
string(REPLACE ";" " " command_formatted "${command}")
message(FATAL_ERROR "Command:\n ${command_formatted}\n${RunClangTidy_TEST_FAILED}")
endif()
1 change: 1 addition & 0 deletions tidy/tests/subspace-smoke.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
void f() {}

0 comments on commit 8f40b08

Please sign in to comment.