[Frontend] Allow attaching an external sema source to compiler instance and extra diags to TypoCorrections

This can be used to append alternative typo corrections to an existing diag.
include-fixer can use it to suggest includes to be added.

Differential Revision: https://reviews.llvm.org/D26745

llvm-svn: 287128
diff --git a/clang/unittests/Frontend/FrontendActionTest.cpp b/clang/unittests/Frontend/FrontendActionTest.cpp
index 39a131f..c3e6adb 100644
--- a/clang/unittests/Frontend/FrontendActionTest.cpp
+++ b/clang/unittests/Frontend/FrontendActionTest.cpp
@@ -13,6 +13,7 @@
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/CompilerInvocation.h"
 #include "clang/Frontend/FrontendAction.h"
+#include "clang/Frontend/FrontendActions.h"
 #include "clang/Lex/Preprocessor.h"
 #include "clang/Lex/PreprocessorOptions.h"
 #include "clang/Sema/Sema.h"
@@ -192,4 +193,66 @@
   ASSERT_TRUE(TestAction.SeenEnd);
 }
 
+class TypoExternalSemaSource : public ExternalSemaSource {
+  CompilerInstance &CI;
+
+public:
+  TypoExternalSemaSource(CompilerInstance &CI) : CI(CI) {}
+
+  TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
+                             Scope *S, CXXScopeSpec *SS,
+                             CorrectionCandidateCallback &CCC,
+                             DeclContext *MemberContext, bool EnteringContext,
+                             const ObjCObjectPointerType *OPT) override {
+    // Generate a fake typo correction with one attached note.
+    ASTContext &Ctx = CI.getASTContext();
+    TypoCorrection TC(DeclarationName(&Ctx.Idents.get("moo")));
+    unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID(
+        DiagnosticsEngine::Note, "This is a note");
+    TC.addExtraDiagnostic(PartialDiagnostic(DiagID, Ctx.getDiagAllocator()));
+    return TC;
+  }
+};
+
+struct TypoDiagnosticConsumer : public DiagnosticConsumer {
+  void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
+                        const Diagnostic &Info) override {
+    // Capture errors and notes. There should be one of each.
+    if (DiagLevel == DiagnosticsEngine::Error) {
+      assert(Error.empty());
+      Info.FormatDiagnostic(Error);
+    } else {
+      assert(Note.empty());
+      Info.FormatDiagnostic(Note);
+    }
+  }
+  SmallString<32> Error;
+  SmallString<32> Note;
+};
+
+TEST(ASTFrontendAction, ExternalSemaSource) {
+  auto *Invocation = new CompilerInvocation;
+  Invocation->getLangOpts()->CPlusPlus = true;
+  Invocation->getPreprocessorOpts().addRemappedFile(
+      "test.cc", MemoryBuffer::getMemBuffer("void fooo();\n"
+                                            "int main() { foo(); }")
+                     .release());
+  Invocation->getFrontendOpts().Inputs.push_back(
+      FrontendInputFile("test.cc", IK_CXX));
+  Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
+  Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
+  CompilerInstance Compiler;
+  Compiler.setInvocation(Invocation);
+  auto *TDC = new TypoDiagnosticConsumer;
+  Compiler.createDiagnostics(TDC, /*ShouldOwnClient=*/true);
+  Compiler.setExternalSemaSource(new TypoExternalSemaSource(Compiler));
+
+  SyntaxOnlyAction TestAction;
+  ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
+  // There should be one error correcting to 'moo' and a note attached to it.
+  EXPECT_EQ("use of undeclared identifier 'foo'; did you mean 'moo'?",
+            TDC->Error.str().str());
+  EXPECT_EQ("This is a note", TDC->Note.str().str());
+}
+
 } // anonymous namespace