Skip to content

Commit 7e506b3

Browse files
committed
[clangd] Allow diagnostics to be suppressed with configuration
This has been specifically requested: clangd/vscode-clangd#114 and various issues can be addressed with this as a workaround, e.g.: clangd/clangd#662 Differential Revision: https://reviews.llvm.org/D95349
1 parent 0005438 commit 7e506b3

File tree

9 files changed

+149
-7
lines changed

9 files changed

+149
-7
lines changed

clang-tools-extra/clangd/Config.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "llvm/ADT/FunctionExtras.h"
2929
#include "llvm/ADT/Optional.h"
3030
#include "llvm/ADT/StringMap.h"
31+
#include "llvm/ADT/StringSet.h"
3132
#include <string>
3233
#include <vector>
3334

@@ -77,6 +78,12 @@ struct Config {
7778
llvm::Optional<ExternalIndexSpec> External;
7879
} Index;
7980

81+
/// Controls warnings and errors when parsing code.
82+
struct {
83+
bool SuppressAll = false;
84+
llvm::StringSet<> Suppress;
85+
} Diagnostics;
86+
8087
/// Style of the codebase.
8188
struct {
8289
// Namespaces that should always be fully qualified, meaning no "using"

clang-tools-extra/clangd/ConfigCompile.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "Config.h"
2828
#include "ConfigFragment.h"
2929
#include "ConfigProvider.h"
30+
#include "Diagnostics.h"
3031
#include "Features.inc"
3132
#include "TidyProvider.h"
3233
#include "support/Logger.h"
@@ -187,6 +188,7 @@ struct FragmentCompiler {
187188
compile(std::move(F.If));
188189
compile(std::move(F.CompileFlags));
189190
compile(std::move(F.Index));
191+
compile(std::move(F.Diagnostics));
190192
compile(std::move(F.ClangTidy));
191193
}
192194

@@ -328,6 +330,27 @@ struct FragmentCompiler {
328330
});
329331
}
330332

333+
void compile(Fragment::DiagnosticsBlock &&F) {
334+
std::vector<llvm::StringRef> Normalized;
335+
for (const auto &Suppressed : F.Suppress) {
336+
if (*Suppressed == "*") {
337+
Out.Apply.push_back([&](const Params &, Config &C) {
338+
C.Diagnostics.SuppressAll = true;
339+
C.Diagnostics.Suppress.clear();
340+
});
341+
return;
342+
}
343+
Normalized.push_back(normalizeSuppressedCode(*Suppressed));
344+
}
345+
if (!Normalized.empty())
346+
Out.Apply.push_back([Normalized](const Params &, Config &C) {
347+
if (C.Diagnostics.SuppressAll)
348+
return;
349+
for (llvm::StringRef N : Normalized)
350+
C.Diagnostics.Suppress.insert(N);
351+
});
352+
}
353+
331354
void compile(Fragment::StyleBlock &&F) {
332355
if (!F.FullyQualifiedNamespaces.empty()) {
333356
std::vector<std::string> FullyQualifiedNamespaces;

clang-tools-extra/clangd/ConfigFragment.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,24 @@ struct Fragment {
181181
};
182182
IndexBlock Index;
183183

184+
/// Controls behavior of diagnostics (errors and warnings).
185+
struct DiagnosticsBlock {
186+
/// Diagnostic codes that should be suppressed.
187+
///
188+
/// Valid values are:
189+
/// - *, to disable all diagnostics
190+
/// - diagnostic codes exposed by clangd (e.g unknown_type, -Wunused-result)
191+
/// - clang internal diagnostic codes (e.g. err_unknown_type)
192+
/// - warning categories (e.g. unused-result)
193+
/// - clang-tidy check names (e.g. bugprone-narrowing-conversions)
194+
///
195+
/// This is a simple filter. Diagnostics can be controlled in other ways
196+
/// (e.g. by disabling a clang-tidy check, or the -Wunused compile flag).
197+
/// This often has other advantages, such as skipping some analysis.
198+
std::vector<Located<std::string>> Suppress;
199+
};
200+
DiagnosticsBlock Diagnostics;
201+
184202
// Describes the style of the codebase, beyond formatting.
185203
struct StyleBlock {
186204
// Namespaces that should always be fully qualified, meaning no "using"
@@ -195,6 +213,7 @@ struct Fragment {
195213
///
196214
/// The settings are merged with any settings found in .clang-tidy
197215
/// configiration files with these ones taking precedence.
216+
// FIXME: move this to Diagnostics.Tidy.
198217
struct ClangTidyBlock {
199218
std::vector<Located<std::string>> Add;
200219
/// List of checks to disable.

clang-tools-extra/clangd/Diagnostics.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,5 +801,23 @@ void StoreDiags::flushLastDiag() {
801801
Output.push_back(std::move(*LastDiag));
802802
}
803803

804+
bool isBuiltinDiagnosticSuppressed(unsigned ID,
805+
const llvm::StringSet<> &Suppress) {
806+
if (const char *CodePtr = getDiagnosticCode(ID)) {
807+
if (Suppress.contains(normalizeSuppressedCode(CodePtr)))
808+
return true;
809+
}
810+
StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(ID);
811+
if (!Warning.empty() && Suppress.contains(Warning))
812+
return true;
813+
return false;
814+
}
815+
816+
llvm::StringRef normalizeSuppressedCode(llvm::StringRef Code) {
817+
Code.consume_front("err_");
818+
Code.consume_front("-W");
819+
return Code;
820+
}
821+
804822
} // namespace clangd
805823
} // namespace clang

clang-tools-extra/clangd/Diagnostics.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,15 @@ class StoreDiags : public DiagnosticConsumer {
159159
bool LastPrimaryDiagnosticWasSuppressed = false;
160160
};
161161

162+
/// Determine whether a (non-clang-tidy) diagnostic is suppressed by config.
163+
bool isBuiltinDiagnosticSuppressed(unsigned ID,
164+
const llvm::StringSet<> &Suppressed);
165+
/// Take a user-specified diagnostic code, and convert it to a normalized form
166+
/// stored in the config and consumed by isBuiltinDiagnosticsSuppressed.
167+
///
168+
/// (This strips err_ and -W prefix so we can match with or without them.)
169+
llvm::StringRef normalizeSuppressedCode(llvm::StringRef);
170+
162171
} // namespace clangd
163172
} // namespace clang
164173

clang-tools-extra/clangd/ParsedAST.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "../clang-tidy/ClangTidyModuleRegistry.h"
1313
#include "AST.h"
1414
#include "Compiler.h"
15+
#include "Config.h"
1516
#include "Diagnostics.h"
1617
#include "Headers.h"
1718
#include "IncludeFixer.h"
@@ -315,12 +316,18 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
315316
Check->registerMatchers(&CTFinder);
316317
}
317318

318-
if (!CTChecks.empty()) {
319-
ASTDiags.setLevelAdjuster([&CTContext](DiagnosticsEngine::Level DiagLevel,
320-
const clang::Diagnostic &Info) {
319+
ASTDiags.setLevelAdjuster([&, &Cfg(Config::current())](
320+
DiagnosticsEngine::Level DiagLevel,
321+
const clang::Diagnostic &Info) {
322+
if (Cfg.Diagnostics.SuppressAll ||
323+
isBuiltinDiagnosticSuppressed(Info.getID(), Cfg.Diagnostics.Suppress))
324+
return DiagnosticsEngine::Ignored;
325+
if (!CTChecks.empty()) {
321326
std::string CheckName = CTContext->getCheckName(Info.getID());
322327
bool IsClangTidyDiag = !CheckName.empty();
323328
if (IsClangTidyDiag) {
329+
if (Cfg.Diagnostics.Suppress.contains(CheckName))
330+
return DiagnosticsEngine::Ignored;
324331
// Check for suppression comment. Skip the check for diagnostics not
325332
// in the main file, because we don't want that function to query the
326333
// source buffer for preamble files. For the same reason, we ask
@@ -342,9 +349,9 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
342349
return DiagnosticsEngine::Error;
343350
}
344351
}
345-
return DiagLevel;
346-
});
347-
}
352+
}
353+
return DiagLevel;
354+
});
348355
}
349356

350357
// Add IncludeFixer which can recover diagnostics caused by missing includes

clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "ConfigTesting.h"
1212
#include "Features.inc"
1313
#include "TestFS.h"
14+
#include "clang/Basic/DiagnosticSema.h"
1415
#include "llvm/ADT/None.h"
1516
#include "llvm/ADT/Optional.h"
1617
#include "llvm/ADT/StringRef.h"
@@ -30,6 +31,7 @@ using ::testing::ElementsAre;
3031
using ::testing::IsEmpty;
3132
using ::testing::SizeIs;
3233
using ::testing::StartsWith;
34+
using ::testing::UnorderedElementsAre;
3335

3436
class ConfigCompileTests : public ::testing::Test {
3537
protected:
@@ -183,6 +185,39 @@ TEST_F(ConfigCompileTests, PathSpecMatch) {
183185
}
184186
}
185187

188+
TEST_F(ConfigCompileTests, DiagnosticSuppression) {
189+
Frag.Diagnostics.Suppress.emplace_back("bugprone-use-after-move");
190+
Frag.Diagnostics.Suppress.emplace_back("unreachable-code");
191+
Frag.Diagnostics.Suppress.emplace_back("-Wunused-variable");
192+
Frag.Diagnostics.Suppress.emplace_back("typecheck_bool_condition");
193+
Frag.Diagnostics.Suppress.emplace_back("err_unexpected_friend");
194+
Frag.Diagnostics.Suppress.emplace_back("warn_alloca");
195+
EXPECT_TRUE(compileAndApply());
196+
EXPECT_THAT(Conf.Diagnostics.Suppress.keys(),
197+
UnorderedElementsAre("bugprone-use-after-move",
198+
"unreachable-code", "unused-variable",
199+
"typecheck_bool_condition",
200+
"unexpected_friend", "warn_alloca"));
201+
EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::warn_unreachable,
202+
Conf.Diagnostics.Suppress));
203+
// Subcategory not respected/suppressed.
204+
EXPECT_FALSE(isBuiltinDiagnosticSuppressed(diag::warn_unreachable_break,
205+
Conf.Diagnostics.Suppress));
206+
EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::warn_unused_variable,
207+
Conf.Diagnostics.Suppress));
208+
EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::err_typecheck_bool_condition,
209+
Conf.Diagnostics.Suppress));
210+
EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::err_unexpected_friend,
211+
Conf.Diagnostics.Suppress));
212+
EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::warn_alloca,
213+
Conf.Diagnostics.Suppress));
214+
215+
Frag.Diagnostics.Suppress.emplace_back("*");
216+
EXPECT_TRUE(compileAndApply());
217+
EXPECT_TRUE(Conf.Diagnostics.SuppressAll);
218+
EXPECT_THAT(Conf.Diagnostics.Suppress, IsEmpty());
219+
}
220+
186221
TEST_F(ConfigCompileTests, Tidy) {
187222
Frag.ClangTidy.Add.emplace_back("bugprone-use-after-move");
188223
Frag.ClangTidy.Add.emplace_back("llvm-*");

clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ TEST(ParseYAML, Locations) {
9898
YAML.range());
9999
}
100100

101-
TEST(ParseYAML, Diagnostics) {
101+
TEST(ParseYAML, ConfigDiagnostics) {
102102
CapturedDiags Diags;
103103
Annotations YAML(R"yaml(
104104
If:

clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "Annotations.h"
10+
#include "Config.h"
1011
#include "Diagnostics.h"
1112
#include "ParsedAST.h"
1213
#include "Protocol.h"
@@ -16,6 +17,7 @@
1617
#include "TestTU.h"
1718
#include "TidyProvider.h"
1819
#include "index/MemIndex.h"
20+
#include "support/Context.h"
1921
#include "support/Path.h"
2022
#include "clang/Basic/Diagnostic.h"
2123
#include "clang/Basic/DiagnosticSema.h"
@@ -371,6 +373,28 @@ TEST(DiagnosticTest, NoMultipleDiagnosticInFlight) {
371373
DiagSource(Diag::ClangTidy), DiagName("modernize-loop-convert"))));
372374
}
373375

376+
TEST(DiagnosticTest, RespectsDiagnosticConfig) {
377+
Annotations Main(R"cpp(
378+
// error-ok
379+
void x() {
380+
[[unknown]]();
381+
$ret[[return]] 42;
382+
}
383+
)cpp");
384+
auto TU = TestTU::withCode(Main.code());
385+
EXPECT_THAT(
386+
TU.build().getDiagnostics(),
387+
ElementsAre(Diag(Main.range(), "use of undeclared identifier 'unknown'"),
388+
Diag(Main.range("ret"),
389+
"void function 'x' should not return a value")));
390+
Config Cfg;
391+
Cfg.Diagnostics.Suppress.insert("return-type");
392+
WithContextValue WithCfg(Config::Key, std::move(Cfg));
393+
EXPECT_THAT(TU.build().getDiagnostics(),
394+
ElementsAre(Diag(Main.range(),
395+
"use of undeclared identifier 'unknown'")));
396+
}
397+
374398
TEST(DiagnosticTest, ClangTidySuppressionComment) {
375399
Annotations Main(R"cpp(
376400
int main() {

0 commit comments

Comments
 (0)