Skip to content

Commit b859c39

Browse files
committed
[clang-tidy] Add a Standalone diagnostics mode to clang-tidy
Adds a flag to `ClangTidyContext` that is used to indicate to checks that fixes will only be applied one at a time. This is to indicate to checks that each fix emitted should not depend on any other fixes emitted across the translation unit. I've currently implemented the `IncludeInserter`, `LoopConvertCheck` and `PreferMemberInitializerCheck` to use these support these modes. Reasoning behind this is in use cases like `clangd` it's only possible to apply one fix at a time. For include inserter checks, the include is only added once for the first diagnostic that requires it, this will result in subsequent fixes not having the included needed. A similar issue is seen in the `PreferMemberInitializerCheck` where the `:` will only be added for the first member that needs fixing. Fixes emitted in `StandaloneDiagsMode` will likely result in malformed code if they are applied all together, conversely fixes currently emitted may result in malformed code if they are applied one at a time. For this reason invoking `clang-tidy` from the binary will always with `StandaloneDiagsMode` disabled, However using it as a library its possible to select the mode you wish to use, `clangd` always selects `StandaloneDiagsMode`. This is an example of the current behaviour failing ```lang=c++ struct Foo { int A, B; Foo(int D, int E) { A = D; B = E; // Fix Here } }; ``` Incorrectly transformed to: ```lang=c++ struct Foo { int A, B; Foo(int D, int E), B(E) { A = D; // Fix Here } }; ``` In `StandaloneDiagsMode`, it gets transformed to: ```lang=c++ struct Foo { int A, B; Foo(int D, int E) : B(E) { A = D; // Fix Here } }; ``` Reviewed By: sammccall Differential Revision: https://reviews.llvm.org/D97121
1 parent e75d8b7 commit b859c39

23 files changed

+170
-28
lines changed

clang-tools-extra/clang-tidy/ClangTidyCheck.h

+5
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,11 @@ class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback {
417417
StringRef getCurrentMainFile() const { return Context->getCurrentFile(); }
418418
/// Returns the language options from the context.
419419
const LangOptions &getLangOpts() const { return Context->getLangOpts(); }
420+
/// Returns true when the check is run in a use case when only 1 fix will be
421+
/// applied at a time.
422+
bool areDiagsSelfContained() const {
423+
return Context->areDiagsSelfContained();
424+
}
420425
};
421426

422427
/// Read a named option from the ``Context`` and parse it as a bool.

clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ ClangTidyContext::ClangTidyContext(
162162
bool AllowEnablingAnalyzerAlphaCheckers)
163163
: DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
164164
Profile(false),
165-
AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers) {
165+
AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers),
166+
SelfContainedDiags(false) {
166167
// Before the first translation unit we can get errors related to command-line
167168
// parsing, use empty string for the file name in this case.
168169
setCurrentFile("");

clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h

+6
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ class ClangTidyContext {
187187
return AllowEnablingAnalyzerAlphaCheckers;
188188
}
189189

190+
void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; }
191+
192+
bool areDiagsSelfContained() const { return SelfContainedDiags; }
193+
190194
using DiagLevelAndFormatString = std::pair<DiagnosticIDs::Level, std::string>;
191195
DiagLevelAndFormatString getDiagLevelAndFormatString(unsigned DiagnosticID,
192196
SourceLocation Loc) {
@@ -223,6 +227,8 @@ class ClangTidyContext {
223227

224228
bool AllowEnablingAnalyzerAlphaCheckers;
225229

230+
bool SelfContainedDiags;
231+
226232
NoLintDirectiveHandler NoLintHandler;
227233
};
228234

clang-tools-extra/clang-tidy/abseil/StringFindStartswithCheck.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ StringFindStartswithCheck::StringFindStartswithCheck(StringRef Name,
2727
StringLikeClasses(utils::options::parseStringList(
2828
Options.get("StringLikeClasses", "::std::basic_string"))),
2929
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
30-
utils::IncludeSorter::IS_LLVM)),
30+
utils::IncludeSorter::IS_LLVM),
31+
areDiagsSelfContained()),
3132
AbseilStringsMatchHeader(
3233
Options.get("AbseilStringsMatchHeader", "absl/strings/match.h")) {}
3334

clang-tools-extra/clang-tidy/bugprone/ImplicitWideningOfMultiplicationResultCheck.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ ImplicitWideningOfMultiplicationResultCheck::
4343
Options.get("UseCXXStaticCastsInCppSources", true)),
4444
UseCXXHeadersInCppSources(Options.get("UseCXXHeadersInCppSources", true)),
4545
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
46-
utils::IncludeSorter::IS_LLVM)) {
47-
}
46+
utils::IncludeSorter::IS_LLVM),
47+
areDiagsSelfContained()) {}
4848

4949
void ImplicitWideningOfMultiplicationResultCheck::registerPPCallbacks(
5050
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {

clang-tools-extra/clang-tidy/cppcoreguidelines/InitVariablesCheck.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ InitVariablesCheck::InitVariablesCheck(StringRef Name,
2727
ClangTidyContext *Context)
2828
: ClangTidyCheck(Name, Context),
2929
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
30-
utils::IncludeSorter::IS_LLVM)),
30+
utils::IncludeSorter::IS_LLVM),
31+
areDiagsSelfContained()),
3132
MathHeader(Options.get("MathHeader", "<math.h>")) {}
3233

3334
void InitVariablesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {

clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -306,10 +306,10 @@ void PreferMemberInitializerCheck::check(
306306
NewInit, AddComma ? "), " : ")"});
307307
Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
308308
FirstToCtorInits);
309+
FirstToCtorInits = areDiagsSelfContained();
309310
}
310311
Diag << FixItHint::CreateRemoval(
311312
CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));
312-
FirstToCtorInits = false;
313313
}
314314
}
315315
}

clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ ProBoundsConstantArrayIndexCheck::ProBoundsConstantArrayIndexCheck(
2222
StringRef Name, ClangTidyContext *Context)
2323
: ClangTidyCheck(Name, Context), GslHeader(Options.get("GslHeader", "")),
2424
Inserter(Options.getLocalOrGlobal("IncludeStyle",
25-
utils::IncludeSorter::IS_LLVM)) {}
25+
utils::IncludeSorter::IS_LLVM),
26+
areDiagsSelfContained()) {}
2627

2728
void ProBoundsConstantArrayIndexCheck::storeOptions(
2829
ClangTidyOptions::OptionMap &Opts) {

clang-tools-extra/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ UniqueptrResetReleaseCheck::UniqueptrResetReleaseCheck(
2020
StringRef Name, ClangTidyContext *Context)
2121
: ClangTidyCheck(Name, Context),
2222
Inserter(Options.getLocalOrGlobal("IncludeStyle",
23-
utils::IncludeSorter::IS_LLVM)) {}
23+
utils::IncludeSorter::IS_LLVM),
24+
areDiagsSelfContained()) {}
2425

2526
void UniqueptrResetReleaseCheck::storeOptions(
2627
ClangTidyOptions::OptionMap &Opts) {

clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp

+8-4
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,8 @@ LoopConvertCheck::LoopConvertCheck(StringRef Name, ClangTidyContext *Context)
463463
MinConfidence(Options.get("MinConfidence", Confidence::CL_Reasonable)),
464464
NamingStyle(Options.get("NamingStyle", VariableNamer::NS_CamelCase)),
465465
Inserter(Options.getLocalOrGlobal("IncludeStyle",
466-
utils::IncludeSorter::IS_LLVM)),
466+
utils::IncludeSorter::IS_LLVM),
467+
areDiagsSelfContained()),
467468
UseCxx20IfAvailable(Options.get("UseCxx20ReverseRanges", true)),
468469
ReverseFunction(Options.get("MakeReverseRangeFunction", "")),
469470
ReverseHeader(Options.get("MakeReverseRangeHeader", "")) {
@@ -800,9 +801,12 @@ bool LoopConvertCheck::isConvertible(ASTContext *Context,
800801
const ast_matchers::BoundNodes &Nodes,
801802
const ForStmt *Loop,
802803
LoopFixerKind FixerKind) {
803-
// If we already modified the range of this for loop, don't do any further
804-
// updates on this iteration.
805-
if (TUInfo->getReplacedVars().count(Loop))
804+
// In self contained diagnosics mode we don't want dependancies on other
805+
// loops, otherwise, If we already modified the range of this for loop, don't
806+
// do any further updates on this iteration.
807+
if (areDiagsSelfContained())
808+
TUInfo = std::make_unique<TUTrackingInfo>();
809+
else if (TUInfo->getReplacedVars().count(Loop))
806810
return false;
807811

808812
// Check that we have exactly one index variable and at most one end variable.

clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ MakeSmartPtrCheck::MakeSmartPtrCheck(StringRef Name, ClangTidyContext *Context,
4444
StringRef MakeSmartPtrFunctionName)
4545
: ClangTidyCheck(Name, Context),
4646
Inserter(Options.getLocalOrGlobal("IncludeStyle",
47-
utils::IncludeSorter::IS_LLVM)),
47+
utils::IncludeSorter::IS_LLVM),
48+
areDiagsSelfContained()),
4849
MakeSmartPtrFunctionHeader(
4950
Options.get("MakeSmartPtrFunctionHeader", "<memory>")),
5051
MakeSmartPtrFunctionName(

clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ collectParamDecls(const CXXConstructorDecl *Ctor,
190190
PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context)
191191
: ClangTidyCheck(Name, Context),
192192
Inserter(Options.getLocalOrGlobal("IncludeStyle",
193-
utils::IncludeSorter::IS_LLVM)),
193+
utils::IncludeSorter::IS_LLVM),
194+
areDiagsSelfContained()),
194195
ValuesOnly(Options.get("ValuesOnly", false)) {}
195196

196197
void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {

clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ ReplaceAutoPtrCheck::ReplaceAutoPtrCheck(StringRef Name,
4141
ClangTidyContext *Context)
4242
: ClangTidyCheck(Name, Context),
4343
Inserter(Options.getLocalOrGlobal("IncludeStyle",
44-
utils::IncludeSorter::IS_LLVM)) {}
44+
utils::IncludeSorter::IS_LLVM),
45+
areDiagsSelfContained()) {}
4546

4647
void ReplaceAutoPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
4748
Options.store(Opts, "IncludeStyle", Inserter.getStyle());

clang-tools-extra/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ ReplaceRandomShuffleCheck::ReplaceRandomShuffleCheck(StringRef Name,
2424
ClangTidyContext *Context)
2525
: ClangTidyCheck(Name, Context),
2626
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
27-
utils::IncludeSorter::IS_LLVM)) {
28-
}
27+
utils::IncludeSorter::IS_LLVM),
28+
areDiagsSelfContained()) {}
2929

3030
void ReplaceRandomShuffleCheck::registerMatchers(MatchFinder *Finder) {
3131
const auto Begin = hasArgument(0, expr());

clang-tools-extra/clang-tidy/performance/TypePromotionInMathFnCheck.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ TypePromotionInMathFnCheck::TypePromotionInMathFnCheck(
3232
StringRef Name, ClangTidyContext *Context)
3333
: ClangTidyCheck(Name, Context),
3434
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
35-
utils::IncludeSorter::IS_LLVM)) {
36-
}
35+
utils::IncludeSorter::IS_LLVM),
36+
areDiagsSelfContained()) {}
3737

3838
void TypePromotionInMathFnCheck::registerPPCallbacks(
3939
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {

clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ UnnecessaryValueParamCheck::UnnecessaryValueParamCheck(
6969
StringRef Name, ClangTidyContext *Context)
7070
: ClangTidyCheck(Name, Context),
7171
Inserter(Options.getLocalOrGlobal("IncludeStyle",
72-
utils::IncludeSorter::IS_LLVM)),
72+
utils::IncludeSorter::IS_LLVM),
73+
areDiagsSelfContained()),
7374
AllowedTypes(
7475
utils::options::parseStringList(Options.get("AllowedTypes", ""))) {}
7576

clang-tools-extra/clang-tidy/utils/IncludeInserter.cpp

+6-3
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ class IncludeInserterCallback : public PPCallbacks {
3636
IncludeInserter *Inserter;
3737
};
3838

39-
IncludeInserter::IncludeInserter(IncludeSorter::IncludeStyle Style)
40-
: Style(Style) {}
39+
IncludeInserter::IncludeInserter(IncludeSorter::IncludeStyle Style,
40+
bool SelfContainedDiags)
41+
: Style(Style), SelfContainedDiags(SelfContainedDiags) {}
4142

4243
void IncludeInserter::registerPreprocessor(Preprocessor *PP) {
4344
assert(PP && "PP shouldn't be null");
@@ -73,7 +74,9 @@ IncludeInserter::createIncludeInsertion(FileID FileID, llvm::StringRef Header) {
7374
return llvm::None;
7475
// We assume the same Header will never be included both angled and not
7576
// angled.
76-
if (!InsertedHeaders[FileID].insert(Header).second)
77+
// In self contained diags mode we don't track what headers we have already
78+
// inserted.
79+
if (!SelfContainedDiags && !InsertedHeaders[FileID].insert(Header).second)
7780
return llvm::None;
7881

7982
return getOrCreate(FileID).createIncludeInsertion(Header, IsAngled);

clang-tools-extra/clang-tidy/utils/IncludeInserter.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ class IncludeInserter {
5959
/// using \code
6060
/// Options.getLocalOrGlobal("IncludeStyle", <DefaultStyle>)
6161
/// \endcode
62-
explicit IncludeInserter(IncludeSorter::IncludeStyle Style);
62+
explicit IncludeInserter(IncludeSorter::IncludeStyle Style,
63+
bool SelfContainedDiags);
6364

6465
/// Registers this with the Preprocessor \p PP, must be called before this
6566
/// class is used.
@@ -93,6 +94,7 @@ class IncludeInserter {
9394
llvm::DenseMap<FileID, llvm::StringSet<>> InsertedHeaders;
9495
const SourceManager *SourceMgr{nullptr};
9596
const IncludeSorter::IncludeStyle Style;
97+
const bool SelfContainedDiags;
9698
friend class IncludeInserterCallback;
9799
};
98100

clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ static void verifyRule(const RewriteRuleWith<std::string> &Rule) {
3030
TransformerClangTidyCheck::TransformerClangTidyCheck(StringRef Name,
3131
ClangTidyContext *Context)
3232
: ClangTidyCheck(Name, Context),
33-
Inserter(
34-
Options.getLocalOrGlobal("IncludeStyle", IncludeSorter::IS_LLVM)) {}
33+
Inserter(Options.getLocalOrGlobal("IncludeStyle", IncludeSorter::IS_LLVM),
34+
areDiagsSelfContained()) {}
3535

3636
// This constructor cannot dispatch to the simpler one (below), because, in
3737
// order to get meaningful results from `getLangOpts` and `Options`, we need the

clang-tools-extra/clangd/ParsedAST.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
412412
CTContext->setDiagnosticsEngine(&Clang->getDiagnostics());
413413
CTContext->setASTContext(&Clang->getASTContext());
414414
CTContext->setCurrentFile(Filename);
415+
CTContext->setSelfContainedDiags(true);
415416
CTChecks = CTFactories.createChecks(CTContext.getPointer());
416417
llvm::erase_if(CTChecks, [&](const auto &Check) {
417418
return !Check->isLanguageVersionSupported(CTContext->getLangOpts());

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

+61
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,67 @@ TEST(DiagnosticTest, ElseAfterReturnRange) {
678678
Diag(Main.range(), "do not use 'else' after 'return'"))));
679679
}
680680

681+
TEST(DiagnosticTest, ClangTidySelfContainedDiags) {
682+
Annotations Main(R"cpp($MathHeader[[]]
683+
struct Foo{
684+
int A, B;
685+
Foo()$Fix[[]] {
686+
$A[[A = 1;]]
687+
$B[[B = 1;]]
688+
}
689+
};
690+
void InitVariables() {
691+
float $C[[C]]$CFix[[]];
692+
double $D[[D]]$DFix[[]];
693+
}
694+
)cpp");
695+
TestTU TU = TestTU::withCode(Main.code());
696+
TU.ClangTidyProvider =
697+
addTidyChecks("cppcoreguidelines-prefer-member-initializer,"
698+
"cppcoreguidelines-init-variables");
699+
clangd::Fix ExpectedAFix;
700+
ExpectedAFix.Message =
701+
"'A' should be initialized in a member initializer of the constructor";
702+
ExpectedAFix.Edits.push_back(TextEdit{Main.range("Fix"), " : A(1)"});
703+
ExpectedAFix.Edits.push_back(TextEdit{Main.range("A"), ""});
704+
705+
// When invoking clang-tidy normally, this code would produce `, B(1)` as the
706+
// fix the `B` member, as it would think its already included the ` : ` from
707+
// the previous `A` fix.
708+
clangd::Fix ExpectedBFix;
709+
ExpectedBFix.Message =
710+
"'B' should be initialized in a member initializer of the constructor";
711+
ExpectedBFix.Edits.push_back(TextEdit{Main.range("Fix"), " : B(1)"});
712+
ExpectedBFix.Edits.push_back(TextEdit{Main.range("B"), ""});
713+
714+
clangd::Fix ExpectedCFix;
715+
ExpectedCFix.Message = "variable 'C' is not initialized";
716+
ExpectedCFix.Edits.push_back(TextEdit{Main.range("CFix"), " = NAN"});
717+
ExpectedCFix.Edits.push_back(
718+
TextEdit{Main.range("MathHeader"), "#include <math.h>\n\n"});
719+
720+
// Again in clang-tidy only the include directive would be emitted for the
721+
// first warning. However we need the include attaching for both warnings.
722+
clangd::Fix ExpectedDFix;
723+
ExpectedDFix.Message = "variable 'D' is not initialized";
724+
ExpectedDFix.Edits.push_back(TextEdit{Main.range("DFix"), " = NAN"});
725+
ExpectedDFix.Edits.push_back(
726+
TextEdit{Main.range("MathHeader"), "#include <math.h>\n\n"});
727+
EXPECT_THAT(
728+
*TU.build().getDiagnostics(),
729+
UnorderedElementsAre(
730+
AllOf(Diag(Main.range("A"), "'A' should be initialized in a member "
731+
"initializer of the constructor"),
732+
withFix(equalToFix(ExpectedAFix))),
733+
AllOf(Diag(Main.range("B"), "'B' should be initialized in a member "
734+
"initializer of the constructor"),
735+
withFix(equalToFix(ExpectedBFix))),
736+
AllOf(Diag(Main.range("C"), "variable 'C' is not initialized"),
737+
withFix(equalToFix(ExpectedCFix))),
738+
AllOf(Diag(Main.range("D"), "variable 'D' is not initialized"),
739+
withFix(equalToFix(ExpectedDFix)))));
740+
}
741+
681742
TEST(DiagnosticsTest, Preprocessor) {
682743
// This looks like a preamble, but there's an #else in the middle!
683744
// Check that:

clang-tools-extra/docs/ReleaseNotes.rst

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Inlay hints
5353

5454
Diagnostics
5555
^^^^^^^^^^^
56+
- Improved Fix-its of some clang-tidy checks when applied with clangd.
5657

5758
Semantic Highlighting
5859
^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)