10 #include "clang/Format/Format.h" 11 #include "clang/Frontend/CompilerInstance.h" 12 #include "clang/Lex/HeaderSearch.h" 13 #include "clang/Lex/Preprocessor.h" 14 #include "clang/Parse/ParseAST.h" 15 #include "clang/Sema/Sema.h" 16 #include "llvm/Support/Debug.h" 17 #include "llvm/Support/raw_ostream.h" 19 #define DEBUG_TYPE "clang-include-fixer" 21 using namespace clang;
24 namespace include_fixer {
27 class Action :
public clang::ASTFrontendAction {
29 explicit Action(SymbolIndexManager &SymbolIndexMgr,
bool MinimizeIncludePaths)
30 : SemaSource(SymbolIndexMgr, MinimizeIncludePaths,
33 std::unique_ptr<clang::ASTConsumer>
34 CreateASTConsumer(clang::CompilerInstance &Compiler,
35 StringRef InFile)
override {
36 SemaSource.setFilePath(InFile);
37 return std::make_unique<clang::ASTConsumer>();
40 void ExecuteAction()
override {
41 clang::CompilerInstance *Compiler = &getCompilerInstance();
42 assert(!Compiler->hasSema() &&
"CI already has Sema");
45 if (hasCodeCompletionSupport() &&
46 !Compiler->getFrontendOpts().CodeCompletionAt.FileName.empty())
47 Compiler->createCodeCompletionConsumer();
49 clang::CodeCompleteConsumer *CompletionConsumer =
nullptr;
50 if (Compiler->hasCodeCompletionConsumer())
51 CompletionConsumer = &Compiler->getCodeCompletionConsumer();
53 Compiler->createSema(getTranslationUnitKind(), CompletionConsumer);
54 SemaSource.setCompilerInstance(Compiler);
55 Compiler->getSema().addExternalSource(&SemaSource);
57 clang::ParseAST(Compiler->getSema(), Compiler->getFrontendOpts().ShowStats,
58 Compiler->getFrontendOpts().SkipFunctionBodies);
62 getIncludeFixerContext(
const clang::SourceManager &SourceManager,
63 clang::HeaderSearch &HeaderSearch)
const {
64 return SemaSource.getIncludeFixerContext(SourceManager, HeaderSearch,
65 SemaSource.getMatchedSymbols());
69 IncludeFixerSemaSource SemaSource;
76 std::vector<IncludeFixerContext> &Contexts, StringRef StyleName,
77 bool MinimizeIncludePaths)
78 : SymbolIndexMgr(SymbolIndexMgr), Contexts(Contexts),
79 MinimizeIncludePaths(MinimizeIncludePaths) {}
84 std::shared_ptr<clang::CompilerInvocation> Invocation,
85 clang::FileManager *
Files,
86 std::shared_ptr<clang::PCHContainerOperations> PCHContainerOps,
87 clang::DiagnosticConsumer *Diagnostics) {
88 assert(Invocation->getFrontendOpts().Inputs.size() == 1);
91 clang::CompilerInstance Compiler(PCHContainerOps);
92 Compiler.setInvocation(std::move(Invocation));
93 Compiler.setFileManager(Files);
97 Compiler.createDiagnostics(
new clang::IgnoringDiagConsumer,
99 Compiler.createSourceManager(*Files);
103 Compiler.getDiagnostics().setErrorLimit(0);
106 auto ScopedToolAction =
107 std::make_unique<Action>(SymbolIndexMgr, MinimizeIncludePaths);
108 Compiler.ExecuteAction(*ScopedToolAction);
110 Contexts.push_back(ScopedToolAction->getIncludeFixerContext(
111 Compiler.getSourceManager(),
112 Compiler.getPreprocessor().getHeaderSearchInfo()));
117 return !Compiler.getDiagnostics().hasFatalErrorOccurred();
122 StringRef
Code, SourceLocation StartOfFile,
125 Code, Context, format::getLLVMStyle(),
false);
126 if (!Reps || Reps->size() != 1)
129 unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID(
130 DiagnosticsEngine::Note,
"Add '#include %0' to provide the missing " 131 "declaration [clang-include-fixer]");
135 const tooling::Replacement &Placed = *Reps->begin();
137 auto Begin = StartOfFile.getLocWithOffset(Placed.getOffset());
138 auto End = Begin.getLocWithOffset(std::max(0, (
int)Placed.getLength() - 1));
139 PartialDiagnostic PD(DiagID, Ctx.getDiagAllocator());
141 << FixItHint::CreateReplacement(CharSourceRange::getCharRange(Begin, End),
142 Placed.getReplacementText());
143 Correction.addExtraDiagnostic(std::move(PD));
150 clang::SourceLocation
Loc, clang::QualType T) {
152 if (CI->getSema().isSFINAEContext())
155 clang::ASTContext &context = CI->getASTContext();
156 std::string QueryString = QualType(T->getUnqualifiedDesugaredType(), 0)
157 .getAsString(context.getPrintingPolicy());
158 LLVM_DEBUG(llvm::dbgs() <<
"Query missing complete type '" << QueryString
161 std::vector<find_all_symbols::SymbolInfo> MatchedSymbols =
164 if (!MatchedSymbols.empty() && GenerateDiagnostics) {
165 TypoCorrection Correction;
166 FileID FID = CI->getSourceManager().getFileID(Loc);
167 StringRef
Code = CI->getSourceManager().getBufferData(FID);
168 SourceLocation StartOfFile =
169 CI->getSourceManager().getLocForStartOfFile(FID);
172 getIncludeFixerContext(CI->getSourceManager(),
173 CI->getPreprocessor().getHeaderSearchInfo(),
175 Code, StartOfFile, CI->getASTContext());
176 for (
const PartialDiagnostic &PD : Correction.getExtraDiagnostics())
177 CI->getSema().Diag(Loc, PD);
185 const DeclarationNameInfo &Typo,
int LookupKind, Scope *S, CXXScopeSpec *SS,
186 CorrectionCandidateCallback &CCC, DeclContext *MemberContext,
187 bool EnteringContext,
const ObjCObjectPointerType *OPT) {
189 if (CI->getSema().isSFINAEContext())
190 return clang::TypoCorrection();
211 if (!CI->getSourceManager().isWrittenInMainFile(Typo.getLoc()))
212 return clang::TypoCorrection();
214 std::string TypoScopeString;
218 for (
const auto *Context = S->getEntity(); Context;
219 Context = Context->getParent()) {
220 if (
const auto *ND = dyn_cast<NamespaceDecl>(Context)) {
221 if (!ND->getName().empty())
222 TypoScopeString = ND->getNameAsString() +
"::" + TypoScopeString;
227 auto ExtendNestedNameSpecifier = [
this](CharSourceRange
Range) {
229 Lexer::getSourceText(
Range, CI->getSourceManager(), CI->getLangOpts());
247 const char *End = Source.end();
248 while (isIdentifierBody(*End) || *End ==
':')
251 return std::string(Source.begin(), End);
255 std::string QueryString;
257 const auto &SM = CI->getSourceManager();
258 auto CreateToolingRange = [&QueryString, &SM](SourceLocation BeginLoc) {
262 if (SS && SS->getRange().isValid()) {
266 QueryString = ExtendNestedNameSpecifier(
Range);
267 SymbolRange = CreateToolingRange(
Range.getBegin());
268 }
else if (Typo.getName().isIdentifier() && !Typo.getLoc().isMacroID()) {
272 QueryString = ExtendNestedNameSpecifier(
Range);
273 SymbolRange = CreateToolingRange(
Range.getBegin());
275 QueryString = Typo.getAsString();
276 SymbolRange = CreateToolingRange(Typo.getLoc());
279 LLVM_DEBUG(llvm::dbgs() <<
"TypoScopeQualifiers: " << TypoScopeString
281 std::vector<find_all_symbols::SymbolInfo> MatchedSymbols =
282 query(QueryString, TypoScopeString, SymbolRange);
284 if (!MatchedSymbols.empty() && GenerateDiagnostics) {
285 TypoCorrection Correction(Typo.getName());
286 Correction.setCorrectionRange(SS, Typo);
287 FileID FID = SM.getFileID(Typo.getLoc());
288 StringRef
Code = SM.getBufferData(FID);
289 SourceLocation StartOfFile = SM.getLocForStartOfFile(FID);
291 Correction, getIncludeFixerContext(
292 SM, CI->getPreprocessor().getHeaderSearchInfo(),
294 Code, StartOfFile, CI->getASTContext()))
297 return TypoCorrection();
302 StringRef Include,
const clang::SourceManager &SourceManager,
303 clang::HeaderSearch &HeaderSearch)
const {
304 if (!MinimizeIncludePaths)
308 StringRef StrippedInclude = Include.trim(
"\"<>");
309 auto Entry = SourceManager.getFileManager().getFile(StrippedInclude);
316 bool IsSystem =
false;
317 std::string Suggestion =
318 HeaderSearch.suggestPathToFileForDiagnostics(*
Entry,
"", &IsSystem);
320 return IsSystem ?
'<' + Suggestion +
'>' :
'"' + Suggestion +
'"';
325 const clang::SourceManager &SourceManager,
326 clang::HeaderSearch &HeaderSearch,
327 ArrayRef<find_all_symbols::SymbolInfo> MatchedSymbols)
const {
328 std::vector<find_all_symbols::SymbolInfo> SymbolCandidates;
329 for (
const auto &Symbol : MatchedSymbols) {
331 std::string MinimizedFilePath = minimizeInclude(
332 ((FilePath[0] ==
'"' || FilePath[0] ==
'<') ? FilePath
333 :
"\"" + FilePath +
"\""),
334 SourceManager, HeaderSearch);
335 SymbolCandidates.emplace_back(Symbol.getName(), Symbol.getSymbolKind(),
336 MinimizedFilePath, Symbol.getContexts());
341 std::vector<find_all_symbols::SymbolInfo>
342 IncludeFixerSemaSource::query(StringRef Query, StringRef ScopedQualifiers,
344 assert(!Query.empty() &&
"Empty query!");
352 if (!GenerateDiagnostics && !QuerySymbolInfos.empty()) {
353 if (ScopedQualifiers == QuerySymbolInfos.front().ScopedQualifiers &&
354 Query == QuerySymbolInfos.front().RawIdentifier) {
355 QuerySymbolInfos.push_back({Query.str(), ScopedQualifiers, Range});
360 LLVM_DEBUG(llvm::dbgs() <<
"Looking up '" << Query <<
"' at ");
361 LLVM_DEBUG(CI->getSourceManager()
362 .getLocForStartOfFile(CI->getSourceManager().getMainFileID())
363 .getLocWithOffset(Range.getOffset())
364 .print(llvm::dbgs(), CI->getSourceManager()));
365 LLVM_DEBUG(llvm::dbgs() <<
" ...");
366 llvm::StringRef
FileName = CI->getSourceManager().getFilename(
367 CI->getSourceManager().getLocForStartOfFile(
368 CI->getSourceManager().getMainFileID()));
370 QuerySymbolInfos.push_back({Query.str(), ScopedQualifiers, Range});
384 std::string QueryString = ScopedQualifiers.str() + Query.str();
388 std::vector<find_all_symbols::SymbolInfo> MatchedSymbols =
389 SymbolIndexMgr.
search(QueryString,
false, FileName);
390 if (MatchedSymbols.empty())
392 SymbolIndexMgr.
search(Query,
true, FileName);
393 LLVM_DEBUG(llvm::dbgs() <<
"Having found " << MatchedSymbols.size()
397 this->MatchedSymbols = MatchedSymbols;
398 return MatchedSymbols;
405 return tooling::Replacements();
407 std::string IncludeName =
410 clang::tooling::Replacements Insertions;
412 Insertions.add(tooling::Replacement(FilePath, UINT_MAX, 0, IncludeName));
414 return std::move(Err);
416 auto CleanReplaces = cleanupAroundReplacements(Code, Insertions, Style);
418 return CleanReplaces;
420 auto Replaces = std::move(*CleanReplaces);
424 if (
Info.Range.getLength() > 0) {
425 auto R = tooling::Replacement(
426 {FilePath,
Info.Range.getOffset(),
Info.Range.getLength(),
428 auto Err = Replaces.add(R);
430 llvm::consumeError(std::move(Err));
431 R = tooling::Replacement(
432 R.getFilePath(), Replaces.getShiftedCodePosition(R.getOffset()),
433 R.getLength(), R.getReplacementText());
434 Replaces = Replaces.merge(tooling::Replacements(R));
439 return formatReplacements(Code, Replaces, Style);
SourceLocation Loc
'#' location in the include directive
llvm::Expected< tooling::Replacements > createIncludeFixerReplacements(StringRef Code, const IncludeFixerContext &Context, const clang::format::FormatStyle &Style, bool AddQualifiers)
const std::vector< QuerySymbolInfo > & getQuerySymbolInfos() const
Get information of symbols being querid.
StringRef getFilePath() const
Get the file path to the file being processed.
A context for a file being processed.
bool MaybeDiagnoseMissingCompleteType(clang::SourceLocation Loc, clang::QualType T) override
Callback for incomplete types.
const std::vector< HeaderInfo > & getHeaderInfos() const
Get header information.
IncludeFixerContext getIncludeFixerContext(const clang::SourceManager &SourceManager, clang::HeaderSearch &HeaderSearch, ArrayRef< find_all_symbols::SymbolInfo > MatchedSymbols) const
Get the include fixer context for the queried symbol.
clang::TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind, Scope *S, CXXScopeSpec *SS, CorrectionCandidateCallback &CCC, DeclContext *MemberContext, bool EnteringContext, const ObjCObjectPointerType *OPT) override
Callback for unknown identifiers.
bool runInvocation(std::shared_ptr< clang::CompilerInvocation > Invocation, clang::FileManager *Files, std::shared_ptr< clang::PCHContainerOperations > PCHContainerOps, clang::DiagnosticConsumer *Diagnostics) override
static bool addDiagnosticsForContext(TypoCorrection &Correction, const IncludeFixerContext &Context, StringRef Code, SourceLocation StartOfFile, ASTContext &Ctx)
llvm::unique_function< void()> Action
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
This class provides an interface for finding the header files corresponding to an identifier in the s...
~IncludeFixerActionFactory() override
IncludeFixerActionFactory(SymbolIndexManager &SymbolIndexMgr, std::vector< IncludeFixerContext > &Contexts, StringRef StyleName, bool MinimizeIncludePaths=true)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
CharSourceRange Range
SourceRange for the file name.
std::string minimizeInclude(StringRef Include, const clang::SourceManager &SourceManager, clang::HeaderSearch &HeaderSearch) const
Get the minimal include for a given path.
std::vector< find_all_symbols::SymbolInfo > search(llvm::StringRef Identifier, bool IsNestedSearch=true, llvm::StringRef FileName="") const
Search for header files to be included for an identifier.
llvm::StringMap< std::string > Files
static cl::opt< std::string > FormatStyle("format-style", cl::desc(R"(
Style for formatting code around applied fixes:
- 'none' (default) turns off formatting
- 'file' (literally 'file', not a placeholder)
uses .clang-format file in the closest parent
directory
- '{ <json> }' specifies options inline, e.g.
-format-style='{BasedOnStyle: llvm, IndentWidth: 8}'
- 'llvm', 'google', 'webkit', 'mozilla'
See clang-format documentation for the up-to-date
information about formatting styles and options.
This option overrides the 'FormatStyle` option in
.clang-tidy file, if any.
)"), cl::init("none"), cl::cat(ClangTidyCategory))