Index: clangd/Diagnostics.cpp =================================================================== --- clangd/Diagnostics.cpp +++ clangd/Diagnostics.cpp @@ -297,7 +297,7 @@ return D; }; - auto AddFix = [&]() -> bool { + auto AddFix = [&](bool SyntheticMessage) -> bool { assert(!Info.getFixItHints().empty() && "diagnostic does not have attached fix-its"); if (!InsideMainFile) @@ -312,7 +312,27 @@ } llvm::SmallString<64> Message; - Info.FormatDiagnostic(Message); + // If requested and possible, create a message like "change 'foo' to 'bar'". + if (SyntheticMessage && Info.getNumFixItHints() == 1) { + const auto &FixIt = Info.getFixItHint(0); + bool Invalid = false; + StringRef Remove = Lexer::getSourceText( + FixIt.RemoveRange, Info.getSourceManager(), *LangOpts, &Invalid); + StringRef Insert = FixIt.CodeToInsert; + if (!Invalid && Remove.size() + Insert.size() < 200) { + llvm::raw_svector_ostream M(Message); + if (!Remove.empty() && !Insert.empty()) + M << "change '" << Remove << "' to '" << Insert << "'"; + else if (!Remove.empty()) + M << "remove '" << Remove << "'"; + else if (!Insert.empty()) + M << "insert '" << Insert << "'"; + } + // Don't allow source code to inject newlines into diagnostics. + std::replace(Message.begin(), Message.end(), '\n', ' '); + } + if (Message.empty()) // either !SytheticMessage, or we failed to make one. + Info.FormatDiagnostic(Message); LastDiag->Fixes.push_back(Fix{Message.str(), std::move(Edits)}); return true; }; @@ -325,7 +345,7 @@ FillDiagBase(*LastDiag); if (!Info.getFixItHints().empty()) - AddFix(); + AddFix(true /* try to invent a message instead of repeating the diag */); } else { // Handle a note to an existing diagnostic. if (!LastDiag) { @@ -337,7 +357,7 @@ if (!Info.getFixItHints().empty()) { // A clang note with fix-it is not a separate diagnostic in clangd. We // attach it as a Fix to the main diagnostic instead. - if (!AddFix()) + if (!AddFix(false /* use the note as the message */)) IgnoreDiagnostics::log(DiagLevel, Info); } else { // A clang note without fix-its corresponds to clangd::Note. Index: unittests/clangd/ClangdUnitTests.cpp =================================================================== --- unittests/clangd/ClangdUnitTests.cpp +++ unittests/clangd/ClangdUnitTests.cpp @@ -92,14 +92,6 @@ return Res; } -/// Matches diagnostic that has exactly one fix with the same range and message -/// as the diagnostic itself. -testing::Matcher DiagWithEqualFix(clangd::Range Range, - std::string Replacement, - std::string Message) { - return AllOf(Diag(Range, Message), WithFix(Fix(Range, Replacement, Message))); -} - TEST(DiagnosticsTest, DiagnosticRanges) { // Check we report correct ranges, including various edge-cases. Annotations Test(R"cpp( @@ -111,19 +103,19 @@ $unk[[unknown]](); } )cpp"); - llvm::errs() << Test.code(); EXPECT_THAT( buildDiags(Test.code()), ElementsAre( // This range spans lines. - AllOf(DiagWithEqualFix( - Test.range("typo"), "foo", - "use of undeclared identifier 'goo'; did you mean 'foo'?"), + AllOf(Diag(Test.range("typo"), + "use of undeclared identifier 'goo'; did you mean 'foo'?"), + WithFix( + Fix(Test.range("typo"), "foo", "change 'go\\ o' to 'foo'")), // This is a pretty normal range. WithNote(Diag(Test.range("decl"), "'foo' declared here"))), // This range is zero-width, and at the end of a line. - DiagWithEqualFix(Test.range("semicolon"), ";", - "expected ';' after expression"), + AllOf(Diag(Test.range("semicolon"), "expected ';' after expression"), + WithFix(Fix(Test.range("semicolon"), ";", "insert ';'"))), // This range isn't provided by clang, we expand to the token. Diag(Test.range("unk"), "use of undeclared identifier 'unknown'"))); } @@ -131,8 +123,9 @@ TEST(DiagnosticsTest, FlagsMatter) { Annotations Test("[[void]] main() {}"); EXPECT_THAT(buildDiags(Test.code()), - ElementsAre(DiagWithEqualFix(Test.range(), "int", - "'main' must return 'int'"))); + ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"), + WithFix(Fix(Test.range(), "int", + "change 'void' to 'int'"))))); // Same code built as C gets different diagnostics. EXPECT_THAT( buildDiags(Test.code(), {"-x", "c"}),