aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Hines <srhines@google.com>2018-08-24 11:53:54 -0700
committerStephen Hines <srhines@google.com>2018-08-24 11:53:54 -0700
commitcfb10142b0b2d71eaaa2f1893fec6555ead81937 (patch)
tree0620071eba253b665acc636b04c87f947e91e0d2
parent71e63999fdd1d17bb31c18efc15a14f637ea8d51 (diff)
parenta32ea61ae09dc772fd7f688a89a0bd07c1bcc4f1 (diff)
downloadclang-tools-extra-llvm-svn.339409.tar.gz
Merge a32ea61a for LLVM update to 339409llvm-svn.339409
Change-Id: I4f975e04866a6b894b92249c4badcaec38467fbb
-rw-r--r--LICENSE.TXT2
-rw-r--r--change-namespace/ChangeNamespace.cpp19
-rw-r--r--clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp4
-rw-r--r--clang-doc/BitcodeReader.cpp683
-rw-r--r--clang-doc/BitcodeReader.h74
-rw-r--r--clang-doc/BitcodeWriter.cpp75
-rw-r--r--clang-doc/BitcodeWriter.h38
-rw-r--r--clang-doc/CMakeLists.txt6
-rw-r--r--clang-doc/ClangDoc.cpp17
-rw-r--r--clang-doc/ClangDoc.h3
-rw-r--r--clang-doc/Generators.cpp36
-rw-r--r--clang-doc/Generators.h41
-rw-r--r--clang-doc/Mapper.cpp18
-rw-r--r--clang-doc/Mapper.h7
-rw-r--r--clang-doc/Representation.cpp193
-rw-r--r--clang-doc/Representation.h129
-rw-r--r--clang-doc/Serialize.cpp167
-rw-r--r--clang-doc/Serialize.h22
-rw-r--r--clang-doc/YAMLGenerator.cpp280
-rw-r--r--clang-doc/gen_tests.py210
-rw-r--r--clang-doc/tool/ClangDocMain.cpp221
-rw-r--r--clang-move/ClangMove.cpp19
-rw-r--r--clang-move/tool/ClangMoveMain.cpp4
-rw-r--r--clang-tidy-vs/ClangTidy/license.txt2
-rw-r--r--clang-tidy/ClangTidy.cpp6
-rw-r--r--clang-tidy/ClangTidy.h6
-rw-r--r--clang-tidy/ClangTidyDiagnosticConsumer.cpp12
-rw-r--r--clang-tidy/ClangTidyDiagnosticConsumer.h7
-rw-r--r--clang-tidy/ClangTidyProfiling.cpp100
-rw-r--r--clang-tidy/ClangTidyProfiling.h31
-rw-r--r--clang-tidy/abseil/StringFindStartswithCheck.cpp11
-rw-r--r--clang-tidy/android/CloexecCheck.cpp8
-rw-r--r--clang-tidy/android/ComparisonInTempFailureRetryCheck.cpp6
-rw-r--r--clang-tidy/boost/UseToStringCheck.cpp6
-rw-r--r--clang-tidy/bugprone/ArgumentCommentCheck.cpp8
-rw-r--r--clang-tidy/bugprone/AssertSideEffectCheck.cpp2
-rw-r--r--clang-tidy/bugprone/BoolPointerImplicitConversionCheck.cpp6
-rw-r--r--clang-tidy/bugprone/BugproneTidyModule.cpp3
-rw-r--r--clang-tidy/bugprone/CMakeLists.txt1
-rw-r--r--clang-tidy/bugprone/CopyConstructorInitCheck.cpp4
-rw-r--r--clang-tidy/bugprone/DanglingHandleCheck.cpp2
-rw-r--r--clang-tidy/bugprone/ExceptionEscapeCheck.cpp214
-rw-r--r--clang-tidy/bugprone/ExceptionEscapeCheck.h47
-rw-r--r--clang-tidy/bugprone/InaccurateEraseCheck.cpp4
-rw-r--r--clang-tidy/bugprone/IncorrectRoundingsCheck.cpp2
-rw-r--r--clang-tidy/bugprone/IntegerDivisionCheck.cpp2
-rw-r--r--clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp5
-rw-r--r--clang-tidy/bugprone/MisplacedWideningCastCheck.cpp6
-rw-r--r--clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp2
-rw-r--r--clang-tidy/bugprone/MultipleStatementMacroCheck.cpp8
-rw-r--r--clang-tidy/bugprone/SizeofContainerCheck.cpp2
-rw-r--r--clang-tidy/bugprone/SizeofExpressionCheck.cpp28
-rw-r--r--clang-tidy/bugprone/StringConstructorCheck.cpp2
-rw-r--r--clang-tidy/bugprone/StringIntegerAssignmentCheck.cpp4
-rw-r--r--clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp4
-rw-r--r--clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp6
-rw-r--r--clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp2
-rw-r--r--clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp6
-rw-r--r--clang-tidy/bugprone/SuspiciousStringCompareCheck.cpp17
-rw-r--r--clang-tidy/bugprone/SwappedArgumentsCheck.cpp2
-rw-r--r--clang-tidy/bugprone/TerminatingContinueCheck.cpp2
-rw-r--r--clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp2
-rw-r--r--clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp4
-rw-r--r--clang-tidy/bugprone/UndelegatedConstructorCheck.cpp2
-rw-r--r--clang-tidy/bugprone/UnusedRaiiCheck.cpp6
-rw-r--r--clang-tidy/bugprone/UnusedReturnValueCheck.cpp4
-rw-r--r--clang-tidy/bugprone/VirtualNearMissCheck.cpp2
-rw-r--r--clang-tidy/cert/CERTTidyModule.cpp5
-rw-r--r--clang-tidy/cert/CMakeLists.txt1
-rw-r--r--clang-tidy/cert/LimitedRandomnessCheck.cpp2
-rw-r--r--clang-tidy/cert/ProperlySeededRandomGeneratorCheck.cpp124
-rw-r--r--clang-tidy/cert/ProperlySeededRandomGeneratorCheck.h47
-rw-r--r--clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp4
-rw-r--r--clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp4
-rw-r--r--clang-tidy/cppcoreguidelines/NoMallocCheck.cpp4
-rw-r--r--clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp24
-rw-r--r--clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp2
-rw-r--r--clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp8
-rw-r--r--clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp10
-rw-r--r--clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp12
-rw-r--r--clang-tidy/fuchsia/DefaultArgumentsCheck.cpp18
-rw-r--r--clang-tidy/fuchsia/MultipleInheritanceCheck.cpp10
-rw-r--r--clang-tidy/fuchsia/OverloadedOperatorCheck.cpp4
-rw-r--r--clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp2
-rw-r--r--clang-tidy/fuchsia/TrailingReturnCheck.cpp4
-rw-r--r--clang-tidy/fuchsia/VirtualInheritanceCheck.cpp2
-rw-r--r--clang-tidy/google/AvoidCStyleCastsCheck.cpp10
-rw-r--r--clang-tidy/google/ExplicitConstructorCheck.cpp2
-rw-r--r--clang-tidy/google/ExplicitMakePairCheck.cpp6
-rw-r--r--clang-tidy/google/GlobalNamesInHeadersCheck.cpp8
-rw-r--r--clang-tidy/google/GoogleTidyModule.cpp4
-rw-r--r--clang-tidy/google/IntegerTypesCheck.cpp2
-rw-r--r--clang-tidy/google/OverloadedUnaryAndCheck.cpp2
-rw-r--r--clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp2
-rw-r--r--clang-tidy/google/UsingNamespaceDirectiveCheck.cpp2
-rw-r--r--clang-tidy/hicpp/ExceptionBaseclassCheck.cpp4
-rw-r--r--clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp8
-rw-r--r--clang-tidy/hicpp/SignedBitwiseCheck.cpp6
-rw-r--r--clang-tidy/llvm/TwineLocalCheck.cpp4
-rw-r--r--clang-tidy/misc/DefinitionsInHeadersCheck.cpp2
-rw-r--r--clang-tidy/misc/RedundantExpressionCheck.cpp6
-rw-r--r--clang-tidy/misc/StaticAssertCheck.cpp4
-rw-r--r--clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp8
-rw-r--r--clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp4
-rw-r--r--clang-tidy/misc/UnusedAliasDeclsCheck.cpp4
-rw-r--r--clang-tidy/misc/UnusedParametersCheck.cpp40
-rw-r--r--clang-tidy/misc/UnusedParametersCheck.h2
-rw-r--r--clang-tidy/misc/UnusedUsingDeclsCheck.cpp4
-rw-r--r--clang-tidy/modernize/AvoidBindCheck.cpp4
-rw-r--r--clang-tidy/modernize/MakeSmartPtrCheck.cpp6
-rw-r--r--clang-tidy/modernize/PassByValueCheck.cpp6
-rw-r--r--clang-tidy/modernize/RawStringLiteralCheck.cpp6
-rw-r--r--clang-tidy/modernize/RedundantVoidArgCheck.cpp10
-rw-r--r--clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp10
-rw-r--r--clang-tidy/modernize/ShrinkToFitCheck.cpp8
-rw-r--r--clang-tidy/modernize/UnaryStaticAssertCheck.cpp2
-rw-r--r--clang-tidy/modernize/UseAutoCheck.cpp35
-rw-r--r--clang-tidy/modernize/UseBoolLiteralsCheck.cpp2
-rw-r--r--clang-tidy/modernize/UseDefaultMemberInitCheck.cpp2
-rw-r--r--clang-tidy/modernize/UseEmplaceCheck.cpp2
-rw-r--r--clang-tidy/modernize/UseEqualsDefaultCheck.cpp3
-rw-r--r--clang-tidy/modernize/UseEqualsDeleteCheck.cpp2
-rw-r--r--clang-tidy/modernize/UseNullptrCheck.cpp12
-rw-r--r--clang-tidy/modernize/UseTransparentFunctorsCheck.cpp4
-rw-r--r--clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp12
-rw-r--r--clang-tidy/modernize/UseUsingCheck.cpp2
-rw-r--r--clang-tidy/objc/AvoidNSErrorInitCheck.cpp2
-rw-r--r--clang-tidy/objc/AvoidSpinlockCheck.cpp2
-rw-r--r--clang-tidy/objc/PropertyDeclarationCheck.cpp12
-rw-r--r--clang-tidy/performance/FasterStringFindCheck.cpp11
-rw-r--r--clang-tidy/performance/ForRangeCopyCheck.cpp8
-rw-r--r--clang-tidy/performance/ImplicitConversionInLoopCheck.cpp24
-rw-r--r--clang-tidy/performance/ImplicitConversionInLoopCheck.h2
-rw-r--r--clang-tidy/performance/InefficientAlgorithmCheck.cpp4
-rw-r--r--clang-tidy/performance/InefficientVectorOperationCheck.cpp6
-rw-r--r--clang-tidy/performance/MoveConstArgCheck.cpp6
-rw-r--r--clang-tidy/performance/TypePromotionInMathFnCheck.cpp2
-rw-r--r--clang-tidy/performance/UnnecessaryValueParamCheck.cpp83
-rw-r--r--clang-tidy/plugin/CMakeLists.txt6
-rw-r--r--clang-tidy/plugin/ClangTidyPlugin.cpp50
-rw-r--r--clang-tidy/readability/AvoidConstParamsInDecls.cpp8
-rw-r--r--clang-tidy/readability/BracesAroundStatementsCheck.cpp8
-rw-r--r--clang-tidy/readability/ContainerSizeEmptyCheck.cpp4
-rw-r--r--clang-tidy/readability/DeleteNullPointerCheck.cpp6
-rw-r--r--clang-tidy/readability/DeletedDefaultCheck.cpp4
-rw-r--r--clang-tidy/readability/FunctionSizeCheck.cpp8
-rw-r--r--clang-tidy/readability/IdentifierNamingCheck.cpp2
-rw-r--r--clang-tidy/readability/ImplicitBoolConversionCheck.cpp20
-rw-r--r--clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp30
-rw-r--r--clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h4
-rw-r--r--clang-tidy/readability/MisleadingIndentationCheck.cpp8
-rw-r--r--clang-tidy/readability/MisplacedArrayIndexCheck.cpp2
-rw-r--r--clang-tidy/readability/NamedParameterCheck.cpp4
-rw-r--r--clang-tidy/readability/NamespaceCommentCheck.cpp4
-rw-r--r--clang-tidy/readability/NonConstParameterCheck.cpp2
-rw-r--r--clang-tidy/readability/RedundantControlFlowCheck.cpp2
-rw-r--r--clang-tidy/readability/RedundantDeclarationCheck.cpp2
-rw-r--r--clang-tidy/readability/RedundantSmartptrGetCheck.cpp2
-rw-r--r--clang-tidy/readability/RedundantStringCStrCheck.cpp2
-rw-r--r--clang-tidy/readability/SimplifyBooleanExprCheck.cpp22
-rw-r--r--clang-tidy/readability/SimplifySubscriptExprCheck.cpp4
-rw-r--r--clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp4
-rw-r--r--clang-tidy/readability/StringCompareCheck.cpp6
-rw-r--r--clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp10
-rw-r--r--clang-tidy/tool/ClangTidyMain.cpp31
-rw-r--r--clang-tidy/utils/ASTUtils.cpp4
-rw-r--r--clang-tidy/utils/CMakeLists.txt1
-rw-r--r--clang-tidy/utils/ExprMutationAnalyzer.cpp260
-rw-r--r--clang-tidy/utils/ExprMutationAnalyzer.h56
-rw-r--r--clang-tidy/utils/ExprSequence.cpp21
-rw-r--r--clang-tidy/utils/NamespaceAliaser.cpp2
-rw-r--r--clang-tidy/utils/UsingInserter.cpp2
-rw-r--r--clangd/AST.cpp23
-rw-r--r--clangd/AST.h9
-rw-r--r--clangd/CMakeLists.txt7
-rw-r--r--clangd/ClangdLSPServer.cpp186
-rw-r--r--clangd/ClangdLSPServer.h59
-rw-r--r--clangd/ClangdServer.cpp97
-rw-r--r--clangd/ClangdServer.h43
-rw-r--r--clangd/ClangdUnit.cpp79
-rw-r--r--clangd/ClangdUnit.h19
-rw-r--r--clangd/CodeComplete.cpp816
-rw-r--r--clangd/CodeComplete.h120
-rw-r--r--clangd/CodeCompletionStrings.cpp216
-rw-r--r--clangd/CodeCompletionStrings.h23
-rw-r--r--clangd/CompileArgsCache.cpp44
-rw-r--r--clangd/CompileArgsCache.h43
-rw-r--r--clangd/Compiler.cpp2
-rw-r--r--clangd/Context.h2
-rw-r--r--clangd/Diagnostics.cpp22
-rw-r--r--clangd/FSProvider.h42
-rw-r--r--clangd/FileDistance.cpp171
-rw-r--r--clangd/FileDistance.h109
-rw-r--r--clangd/FindSymbols.cpp164
-rw-r--r--clangd/FindSymbols.h10
-rw-r--r--clangd/Function.h66
-rw-r--r--clangd/FuzzyMatch.cpp107
-rw-r--r--clangd/FuzzyMatch.h62
-rw-r--r--clangd/GlobalCompilationDatabase.cpp59
-rw-r--r--clangd/GlobalCompilationDatabase.h51
-rw-r--r--clangd/Headers.cpp148
-rw-r--r--clangd/Headers.h89
-rw-r--r--clangd/JSONExpr.cpp554
-rw-r--r--clangd/JSONExpr.h579
-rw-r--r--clangd/JSONRPCDispatcher.cpp236
-rw-r--r--clangd/JSONRPCDispatcher.h27
-rw-r--r--clangd/Logger.cpp13
-rw-r--r--clangd/Logger.h56
-rw-r--r--clangd/Protocol.cpp210
-rw-r--r--clangd/Protocol.h182
-rw-r--r--clangd/ProtocolHandlers.cpp6
-rw-r--r--clangd/ProtocolHandlers.h1
-rw-r--r--clangd/Quality.cpp321
-rw-r--r--clangd/Quality.h51
-rw-r--r--clangd/SourceCode.cpp27
-rw-r--r--clangd/SourceCode.h8
-rw-r--r--clangd/TUScheduler.cpp172
-rw-r--r--clangd/TUScheduler.h13
-rw-r--r--clangd/Threading.cpp16
-rw-r--r--clangd/Threading.h2
-rw-r--r--clangd/Trace.cpp63
-rw-r--r--clangd/Trace.h8
-rw-r--r--clangd/XRefs.cpp210
-rw-r--r--clangd/XRefs.h3
-rw-r--r--clangd/clients/clangd-vscode/package.json2
-rw-r--r--clangd/fuzzer/ClangdFuzzer.cpp15
-rw-r--r--clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp31
-rw-r--r--clangd/index/FileIndex.cpp18
-rw-r--r--clangd/index/FileIndex.h13
-rw-r--r--clangd/index/Index.cpp18
-rw-r--r--clangd/index/Index.h131
-rw-r--r--clangd/index/MemIndex.cpp8
-rw-r--r--clangd/index/MemIndex.h6
-rw-r--r--clangd/index/Merge.cpp27
-rw-r--r--clangd/index/SymbolCollector.cpp303
-rw-r--r--clangd/index/SymbolCollector.h44
-rw-r--r--clangd/index/SymbolYAML.cpp14
-rw-r--r--clangd/index/SymbolYAML.h2
-rw-r--r--clangd/index/dex/Iterator.cpp244
-rw-r--r--clangd/index/dex/Iterator.h152
-rw-r--r--clangd/index/dex/Token.h112
-rw-r--r--clangd/index/dex/Trigram.cpp132
-rw-r--r--clangd/index/dex/Trigram.h62
-rw-r--r--clangd/tool/ClangdMain.cpp84
-rw-r--r--docs/Doxyfile1808
-rw-r--r--docs/ReleaseNotes.rst197
-rw-r--r--docs/clang-tidy/checks/bugprone-exception-escape.rst37
-rw-r--r--docs/clang-tidy/checks/cert-msc32-c.rst9
-rw-r--r--docs/clang-tidy/checks/cert-msc51-cpp.rst40
-rw-r--r--docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst10
-rw-r--r--docs/clang-tidy/checks/list.rst6
-rw-r--r--docs/clang-tidy/checks/misc-unused-parameters.rst30
-rw-r--r--docs/clang-tidy/checks/modernize-use-auto.rst29
-rw-r--r--docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst14
-rw-r--r--docs/clang-tidy/checks/readability-redundant-smartptr-get.rst4
-rw-r--r--docs/clang-tidy/index.rst267
-rw-r--r--docs/clangd.rst2
-rw-r--r--docs/conf.py4
-rw-r--r--docs/doxygen.cfg.in23
-rw-r--r--include-fixer/tool/ClangIncludeFixer.cpp6
-rw-r--r--test/clang-doc/bc-comment.cpp204
-rw-r--r--test/clang-doc/bc-linkage.cpp844
-rw-r--r--test/clang-doc/bc-module.cpp87
-rw-r--r--test/clang-doc/bc-namespace.cpp121
-rw-r--r--test/clang-doc/bc-record.cpp293
-rw-r--r--test/clang-doc/mapper-class-in-class.cpp40
-rw-r--r--test/clang-doc/mapper-class-in-function.cpp49
-rw-r--r--test/clang-doc/mapper-class.cpp19
-rw-r--r--test/clang-doc/mapper-comment.cpp74
-rw-r--r--test/clang-doc/mapper-comments.cpp181
-rw-r--r--test/clang-doc/mapper-enum.cpp36
-rw-r--r--test/clang-doc/mapper-function.cpp31
-rw-r--r--test/clang-doc/mapper-linkage.cpp402
-rw-r--r--test/clang-doc/mapper-method.cpp59
-rw-r--r--test/clang-doc/mapper-module.cpp51
-rw-r--r--test/clang-doc/mapper-namespace.cpp103
-rw-r--r--test/clang-doc/mapper-record.cpp220
-rw-r--r--test/clang-doc/mapper-struct.cpp25
-rw-r--r--test/clang-doc/mapper-union.cpp33
-rw-r--r--test/clang-doc/public-comment.cpp138
-rw-r--r--test/clang-doc/public-linkage.cpp299
-rw-r--r--test/clang-doc/public-module.cpp51
-rw-r--r--test/clang-doc/public-namespace.cpp96
-rw-r--r--test/clang-doc/public-record.cpp208
-rw-r--r--test/clang-doc/test_cases/comment.cpp28
-rw-r--r--test/clang-doc/test_cases/compile_flags.txt0
-rw-r--r--test/clang-doc/test_cases/linkage.cpp95
-rw-r--r--test/clang-doc/test_cases/module.cpp15
-rw-r--r--test/clang-doc/test_cases/namespace.cpp26
-rw-r--r--test/clang-doc/test_cases/record.cpp40
-rw-r--r--test/clang-doc/yaml-comment.cpp138
-rw-r--r--test/clang-doc/yaml-linkage.cpp529
-rw-r--r--test/clang-doc/yaml-module.cpp63
-rw-r--r--test/clang-doc/yaml-namespace.cpp96
-rw-r--r--test/clang-doc/yaml-record.cpp236
-rw-r--r--test/clang-tidy/abseil-string-find-startswith.cpp19
-rw-r--r--test/clang-tidy/bugprone-exception-escape.cpp265
-rw-r--r--test/clang-tidy/bugprone-use-after-move.cpp31
-rw-r--r--test/clang-tidy/cert-msc32-c.c27
-rw-r--r--test/clang-tidy/cert-msc51-cpp.cpp210
-rw-r--r--test/clang-tidy/clang-tidy-enable-check-profile-one-tu.cpp14
-rw-r--r--test/clang-tidy/clang-tidy-enable-check-profile-two-tu.cpp21
-rw-r--r--test/clang-tidy/clang-tidy-store-check-profile-one-tu.cpp37
-rw-r--r--test/clang-tidy/cppcoreguidelines-pro-bounds-pointer-arithmetic-pr36489.cpp53
-rw-r--r--test/clang-tidy/fuchsia-multiple-inheritance.cpp11
-rw-r--r--test/clang-tidy/misc-unused-parameters-strict.cpp25
-rw-r--r--test/clang-tidy/misc-unused-parameters.cpp53
-rw-r--r--test/clang-tidy/modernize-shrink-to-fit.cpp13
-rw-r--r--test/clang-tidy/modernize-use-auto-min-type-name-length.cpp95
-rw-r--r--test/clang-tidy/modernize-use-equals-default-copy.cpp8
-rw-r--r--test/clang-tidy/objc-property-declaration.m1
-rw-r--r--test/clang-tidy/performance-for-range-copy.cpp14
-rw-r--r--test/clang-tidy/performance-implicit-conversion-in-loop.cpp54
-rw-r--r--test/clang-tidy/performance-unnecessary-value-param.cpp22
-rw-r--r--test/clang-tidy/readability-inconsistent-declaration-parameter-name-strict.cpp11
-rw-r--r--test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp2
-rw-r--r--test/clangd/compile-commands-path-in-initialize.test35
-rw-r--r--test/clangd/compile-commands-path.test42
-rw-r--r--test/clangd/completion-snippets.test2
-rw-r--r--test/clangd/completion.test12
-rw-r--r--test/clangd/diagnostics.test2
-rw-r--r--test/clangd/did-change-configuration-params.test52
-rw-r--r--test/clangd/execute-command.test2
-rw-r--r--test/clangd/extra-flags.test6
-rw-r--r--test/clangd/fixits.test6
-rw-r--r--test/clangd/hover.test5
-rw-r--r--test/clangd/initialize-params-invalid.test1
-rw-r--r--test/clangd/initialize-params.test1
-rw-r--r--test/clangd/protocol.test24
-rw-r--r--test/clangd/symbols.test51
-rw-r--r--test/clangd/test-uri-posix.test2
-rw-r--r--test/clangd/test-uri-windows.test2
-rw-r--r--test/clangd/too_large.test2
-rw-r--r--tool-template/ToolTemplate.cpp4
-rw-r--r--unittests/clang-move/ClangMoveTests.cpp6
-rw-r--r--unittests/clang-tidy/CMakeLists.txt1
-rw-r--r--unittests/clang-tidy/ExprMutationAnalyzerTest.cpp611
-rw-r--r--unittests/clang-tidy/IncludeInserterTest.cpp2
-rw-r--r--unittests/clang-tidy/NamespaceAliaserTest.cpp10
-rw-r--r--unittests/clang-tidy/OverlappingReplacementsTest.cpp6
-rw-r--r--unittests/clang-tidy/UsingInserterTest.cpp4
-rw-r--r--unittests/clangd/CMakeLists.txt6
-rw-r--r--unittests/clangd/ClangdTests.cpp88
-rw-r--r--unittests/clangd/ClangdUnitTests.cpp4
-rw-r--r--unittests/clangd/CodeCompleteTests.cpp631
-rw-r--r--unittests/clangd/CodeCompletionStringsTests.cpp73
-rw-r--r--unittests/clangd/DexIndexTests.cpp317
-rw-r--r--unittests/clangd/FileDistanceTests.cpp99
-rw-r--r--unittests/clangd/FileIndexTests.cpp30
-rw-r--r--unittests/clangd/FindSymbolsTests.cpp409
-rw-r--r--unittests/clangd/FuzzyMatchTests.cpp88
-rw-r--r--unittests/clangd/HeadersTests.cpp103
-rw-r--r--unittests/clangd/IndexTests.cpp26
-rw-r--r--unittests/clangd/JSONExprTests.cpp291
-rw-r--r--unittests/clangd/QualityTests.cpp294
-rw-r--r--unittests/clangd/SymbolCollectorTests.cpp402
-rw-r--r--unittests/clangd/SyncAPI.cpp17
-rw-r--r--unittests/clangd/SyncAPI.h8
-rw-r--r--unittests/clangd/TUSchedulerTests.cpp173
-rw-r--r--unittests/clangd/TestFS.cpp53
-rw-r--r--unittests/clangd/TestFS.h8
-rw-r--r--unittests/clangd/TestTU.cpp35
-rw-r--r--unittests/clangd/TestTU.h10
-rw-r--r--unittests/clangd/URITests.cpp41
-rw-r--r--unittests/clangd/XRefsTests.cpp284
365 files changed, 17093 insertions, 6940 deletions
diff --git a/LICENSE.TXT b/LICENSE.TXT
index 12fb67d0..b886535d 100644
--- a/LICENSE.TXT
+++ b/LICENSE.TXT
@@ -4,7 +4,7 @@ LLVM Release License
University of Illinois/NCSA
Open Source License
-Copyright (c) 2007-2016 University of Illinois at Urbana-Champaign.
+Copyright (c) 2007-2018 University of Illinois at Urbana-Champaign.
All rights reserved.
Developed by:
diff --git a/change-namespace/ChangeNamespace.cpp b/change-namespace/ChangeNamespace.cpp
index 35c321ae..f941e33d 100644
--- a/change-namespace/ChangeNamespace.cpp
+++ b/change-namespace/ChangeNamespace.cpp
@@ -46,7 +46,7 @@ SourceLocation startLocationForType(TypeLoc TLoc) {
return NestedNameSpecifier.getBeginLoc();
TLoc = TLoc.getNextTypeLoc();
}
- return TLoc.getLocStart();
+ return TLoc.getBeginLoc();
}
SourceLocation endLocationForType(TypeLoc TLoc) {
@@ -275,7 +275,7 @@ bool isNestedDeclContext(const DeclContext *D, const DeclContext *Context) {
// Returns true if \p D is visible at \p Loc with DeclContext \p DeclCtx.
bool isDeclVisibleAtLocation(const SourceManager &SM, const Decl *D,
const DeclContext *DeclCtx, SourceLocation Loc) {
- SourceLocation DeclLoc = SM.getSpellingLoc(D->getLocStart());
+ SourceLocation DeclLoc = SM.getSpellingLoc(D->getBeginLoc());
Loc = SM.getSpellingLoc(Loc);
return SM.isBeforeInTranslationUnit(DeclLoc, Loc) &&
(SM.getFileID(DeclLoc) == SM.getFileID(Loc) &&
@@ -634,7 +634,7 @@ static SourceLocation getLocAfterNamespaceLBrace(const NamespaceDecl *NsDecl,
const SourceManager &SM,
const LangOptions &LangOpts) {
std::unique_ptr<Lexer> Lex =
- getLexerStartingFromLoc(NsDecl->getLocStart(), SM, LangOpts);
+ getLexerStartingFromLoc(NsDecl->getBeginLoc(), SM, LangOpts);
assert(Lex.get() &&
"Failed to create lexer from the beginning of namespace.");
if (!Lex.get())
@@ -709,8 +709,8 @@ void ChangeNamespaceTool::moveOldNamespace(
void ChangeNamespaceTool::moveClassForwardDeclaration(
const ast_matchers::MatchFinder::MatchResult &Result,
const NamedDecl *FwdDecl) {
- SourceLocation Start = FwdDecl->getLocStart();
- SourceLocation End = FwdDecl->getLocEnd();
+ SourceLocation Start = FwdDecl->getBeginLoc();
+ SourceLocation End = FwdDecl->getEndLoc();
const SourceManager &SM = *Result.SourceManager;
SourceLocation AfterSemi = Lexer::findLocationAfterToken(
End, tok::semi, SM, Result.Context->getLangOpts(),
@@ -879,7 +879,7 @@ void ChangeNamespaceTool::fixTypeLoc(
if (!llvm::StringRef(D->getQualifiedNameAsString())
.startswith(OldNamespace + "::"))
return false;
- auto ExpansionLoc = Result.SourceManager->getExpansionLoc(D->getLocStart());
+ auto ExpansionLoc = Result.SourceManager->getExpansionLoc(D->getBeginLoc());
if (ExpansionLoc.isInvalid())
return false;
llvm::StringRef Filename = Result.SourceManager->getFilename(ExpansionLoc);
@@ -910,8 +910,8 @@ void ChangeNamespaceTool::fixTypeLoc(
void ChangeNamespaceTool::fixUsingShadowDecl(
const ast_matchers::MatchFinder::MatchResult &Result,
const UsingDecl *UsingDeclaration) {
- SourceLocation Start = UsingDeclaration->getLocStart();
- SourceLocation End = UsingDeclaration->getLocEnd();
+ SourceLocation Start = UsingDeclaration->getBeginLoc();
+ SourceLocation End = UsingDeclaration->getEndLoc();
if (Start.isInvalid() || End.isInvalid())
return;
@@ -989,7 +989,8 @@ void ChangeNamespaceTool::onEndOfTranslationUnit() {
// Add replacements referring to the changed code to existing replacements,
// which refers to the original code.
Replaces = Replaces.merge(NewReplacements);
- auto Style = format::getStyle("file", FilePath, FallbackStyle);
+ auto Style =
+ format::getStyle(format::DefaultFormatStyle, FilePath, FallbackStyle);
if (!Style) {
llvm::errs() << llvm::toString(Style.takeError()) << "\n";
continue;
diff --git a/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp b/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp
index 24a430f0..8977b131 100644
--- a/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp
+++ b/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp
@@ -97,8 +97,8 @@ int main(int argc, char **argv) {
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), DiagOpts.get());
// Determine a formatting style from options.
- auto FormatStyleOrError =
- format::getStyle(FormatStyleOpt, FormatStyleConfig, "LLVM");
+ auto FormatStyleOrError = format::getStyle(FormatStyleOpt, FormatStyleConfig,
+ format::DefaultFallbackStyle);
if (!FormatStyleOrError) {
llvm::errs() << llvm::toString(FormatStyleOrError.takeError()) << "\n";
return 1;
diff --git a/clang-doc/BitcodeReader.cpp b/clang-doc/BitcodeReader.cpp
new file mode 100644
index 00000000..7acf107d
--- /dev/null
+++ b/clang-doc/BitcodeReader.cpp
@@ -0,0 +1,683 @@
+//===-- BitcodeReader.cpp - ClangDoc Bitcode Reader ------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "BitcodeReader.h"
+#include "llvm/ADT/IndexedMap.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace clang {
+namespace doc {
+
+using Record = llvm::SmallVector<uint64_t, 1024>;
+
+bool decodeRecord(Record R, llvm::SmallVectorImpl<char> &Field,
+ llvm::StringRef Blob) {
+ Field.assign(Blob.begin(), Blob.end());
+ return true;
+}
+
+bool decodeRecord(Record R, SymbolID &Field, llvm::StringRef Blob) {
+ if (R[0] != BitCodeConstants::USRHashSize)
+ return false;
+
+ // First position in the record is the length of the following array, so we
+ // copy the following elements to the field.
+ for (int I = 0, E = R[0]; I < E; ++I)
+ Field[I] = R[I + 1];
+ return true;
+}
+
+bool decodeRecord(Record R, bool &Field, llvm::StringRef Blob) {
+ Field = R[0] != 0;
+ return true;
+}
+
+bool decodeRecord(Record R, int &Field, llvm::StringRef Blob) {
+ if (R[0] > INT_MAX)
+ return false;
+ Field = (int)R[0];
+ return true;
+}
+
+bool decodeRecord(Record R, AccessSpecifier &Field, llvm::StringRef Blob) {
+ switch (R[0]) {
+ case AS_public:
+ case AS_private:
+ case AS_protected:
+ case AS_none:
+ Field = (AccessSpecifier)R[0];
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool decodeRecord(Record R, TagTypeKind &Field, llvm::StringRef Blob) {
+ switch (R[0]) {
+ case TTK_Struct:
+ case TTK_Interface:
+ case TTK_Union:
+ case TTK_Class:
+ case TTK_Enum:
+ Field = (TagTypeKind)R[0];
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool decodeRecord(Record R, llvm::Optional<Location> &Field,
+ llvm::StringRef Blob) {
+ if (R[0] > INT_MAX)
+ return false;
+ Field.emplace((int)R[0], Blob);
+ return true;
+}
+
+bool decodeRecord(Record R, InfoType &Field, llvm::StringRef Blob) {
+ switch (auto IT = static_cast<InfoType>(R[0])) {
+ case InfoType::IT_namespace:
+ case InfoType::IT_record:
+ case InfoType::IT_function:
+ case InfoType::IT_default:
+ case InfoType::IT_enum:
+ Field = IT;
+ return true;
+ }
+ return false;
+}
+
+bool decodeRecord(Record R, FieldId &Field, llvm::StringRef Blob) {
+ switch (auto F = static_cast<FieldId>(R[0])) {
+ case FieldId::F_namespace:
+ case FieldId::F_parent:
+ case FieldId::F_vparent:
+ case FieldId::F_type:
+ case FieldId::F_child_namespace:
+ case FieldId::F_child_record:
+ case FieldId::F_default:
+ Field = F;
+ return true;
+ }
+ return false;
+}
+
+bool decodeRecord(Record R, llvm::SmallVectorImpl<llvm::SmallString<16>> &Field,
+ llvm::StringRef Blob) {
+ Field.push_back(Blob);
+ return true;
+}
+
+bool decodeRecord(Record R, llvm::SmallVectorImpl<Location> &Field,
+ llvm::StringRef Blob) {
+ if (R[0] > INT_MAX)
+ return false;
+ Field.emplace_back((int)R[0], Blob);
+ return true;
+}
+
+bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
+ const unsigned VersionNo) {
+ if (ID == VERSION && R[0] == VersionNo)
+ return true;
+ return false;
+}
+
+bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
+ NamespaceInfo *I) {
+ switch (ID) {
+ case NAMESPACE_USR:
+ return decodeRecord(R, I->USR, Blob);
+ case NAMESPACE_NAME:
+ return decodeRecord(R, I->Name, Blob);
+ default:
+ return false;
+ }
+}
+
+bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, RecordInfo *I) {
+ switch (ID) {
+ case RECORD_USR:
+ return decodeRecord(R, I->USR, Blob);
+ case RECORD_NAME:
+ return decodeRecord(R, I->Name, Blob);
+ case RECORD_DEFLOCATION:
+ return decodeRecord(R, I->DefLoc, Blob);
+ case RECORD_LOCATION:
+ return decodeRecord(R, I->Loc, Blob);
+ case RECORD_TAG_TYPE:
+ return decodeRecord(R, I->TagType, Blob);
+ default:
+ return false;
+ }
+}
+
+bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, EnumInfo *I) {
+ switch (ID) {
+ case ENUM_USR:
+ return decodeRecord(R, I->USR, Blob);
+ case ENUM_NAME:
+ return decodeRecord(R, I->Name, Blob);
+ case ENUM_DEFLOCATION:
+ return decodeRecord(R, I->DefLoc, Blob);
+ case ENUM_LOCATION:
+ return decodeRecord(R, I->Loc, Blob);
+ case ENUM_MEMBER:
+ return decodeRecord(R, I->Members, Blob);
+ case ENUM_SCOPED:
+ return decodeRecord(R, I->Scoped, Blob);
+ default:
+ return false;
+ }
+}
+
+bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, FunctionInfo *I) {
+ switch (ID) {
+ case FUNCTION_USR:
+ return decodeRecord(R, I->USR, Blob);
+ case FUNCTION_NAME:
+ return decodeRecord(R, I->Name, Blob);
+ case FUNCTION_DEFLOCATION:
+ return decodeRecord(R, I->DefLoc, Blob);
+ case FUNCTION_LOCATION:
+ return decodeRecord(R, I->Loc, Blob);
+ case FUNCTION_ACCESS:
+ return decodeRecord(R, I->Access, Blob);
+ case FUNCTION_IS_METHOD:
+ return decodeRecord(R, I->IsMethod, Blob);
+ default:
+ return false;
+ }
+}
+
+bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, TypeInfo *I) {
+ return true;
+}
+
+bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
+ FieldTypeInfo *I) {
+ switch (ID) {
+ case FIELD_TYPE_NAME:
+ return decodeRecord(R, I->Name, Blob);
+ default:
+ return false;
+ }
+}
+
+bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
+ MemberTypeInfo *I) {
+ switch (ID) {
+ case MEMBER_TYPE_NAME:
+ return decodeRecord(R, I->Name, Blob);
+ case MEMBER_TYPE_ACCESS:
+ return decodeRecord(R, I->Access, Blob);
+ default:
+ return false;
+ }
+}
+
+bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, CommentInfo *I) {
+ switch (ID) {
+ case COMMENT_KIND:
+ return decodeRecord(R, I->Kind, Blob);
+ case COMMENT_TEXT:
+ return decodeRecord(R, I->Text, Blob);
+ case COMMENT_NAME:
+ return decodeRecord(R, I->Name, Blob);
+ case COMMENT_DIRECTION:
+ return decodeRecord(R, I->Direction, Blob);
+ case COMMENT_PARAMNAME:
+ return decodeRecord(R, I->ParamName, Blob);
+ case COMMENT_CLOSENAME:
+ return decodeRecord(R, I->CloseName, Blob);
+ case COMMENT_ATTRKEY:
+ return decodeRecord(R, I->AttrKeys, Blob);
+ case COMMENT_ATTRVAL:
+ return decodeRecord(R, I->AttrValues, Blob);
+ case COMMENT_ARG:
+ return decodeRecord(R, I->Args, Blob);
+ case COMMENT_SELFCLOSING:
+ return decodeRecord(R, I->SelfClosing, Blob);
+ case COMMENT_EXPLICIT:
+ return decodeRecord(R, I->Explicit, Blob);
+ default:
+ return false;
+ }
+}
+
+bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, Reference *I,
+ FieldId &F) {
+ switch (ID) {
+ case REFERENCE_USR:
+ return decodeRecord(R, I->USR, Blob);
+ case REFERENCE_NAME:
+ return decodeRecord(R, I->Name, Blob);
+ case REFERENCE_TYPE:
+ return decodeRecord(R, I->RefType, Blob);
+ case REFERENCE_FIELD:
+ return decodeRecord(R, F, Blob);
+ default:
+ return false;
+ }
+}
+
+template <typename T> CommentInfo *getCommentInfo(T I) {
+ llvm::errs() << "Cannot have comment subblock.\n";
+ exit(1);
+}
+
+template <> CommentInfo *getCommentInfo(FunctionInfo *I) {
+ I->Description.emplace_back();
+ return &I->Description.back();
+}
+
+template <> CommentInfo *getCommentInfo(NamespaceInfo *I) {
+ I->Description.emplace_back();
+ return &I->Description.back();
+}
+
+template <> CommentInfo *getCommentInfo(RecordInfo *I) {
+ I->Description.emplace_back();
+ return &I->Description.back();
+}
+
+template <> CommentInfo *getCommentInfo(EnumInfo *I) {
+ I->Description.emplace_back();
+ return &I->Description.back();
+}
+
+template <> CommentInfo *getCommentInfo(CommentInfo *I) {
+ I->Children.emplace_back(llvm::make_unique<CommentInfo>());
+ return I->Children.back().get();
+}
+
+template <> CommentInfo *getCommentInfo(std::unique_ptr<CommentInfo> &I) {
+ return getCommentInfo(I.get());
+}
+
+template <typename T, typename TTypeInfo>
+void addTypeInfo(T I, TTypeInfo &&TI) {
+ llvm::errs() << "Invalid type for info.\n";
+ exit(1);
+}
+
+template <> void addTypeInfo(RecordInfo *I, MemberTypeInfo &&T) {
+ I->Members.emplace_back(std::move(T));
+}
+
+template <> void addTypeInfo(FunctionInfo *I, TypeInfo &&T) {
+ I->ReturnType = std::move(T);
+}
+
+template <> void addTypeInfo(FunctionInfo *I, FieldTypeInfo &&T) {
+ I->Params.emplace_back(std::move(T));
+}
+
+template <typename T> void addReference(T I, Reference &&R, FieldId F) {
+ llvm::errs() << "Invalid field type for info.\n";
+ exit(1);
+}
+
+template <> void addReference(TypeInfo *I, Reference &&R, FieldId F) {
+ switch (F) {
+ case FieldId::F_type:
+ I->Type = std::move(R);
+ break;
+ default:
+ llvm::errs() << "Invalid field type for info.\n";
+ exit(1);
+ }
+}
+
+template <> void addReference(FieldTypeInfo *I, Reference &&R, FieldId F) {
+ switch (F) {
+ case FieldId::F_type:
+ I->Type = std::move(R);
+ break;
+ default:
+ llvm::errs() << "Invalid field type for info.\n";
+ exit(1);
+ }
+}
+
+template <> void addReference(MemberTypeInfo *I, Reference &&R, FieldId F) {
+ switch (F) {
+ case FieldId::F_type:
+ I->Type = std::move(R);
+ break;
+ default:
+ llvm::errs() << "Invalid field type for info.\n";
+ exit(1);
+ }
+}
+
+template <> void addReference(EnumInfo *I, Reference &&R, FieldId F) {
+ switch (F) {
+ case FieldId::F_namespace:
+ I->Namespace.emplace_back(std::move(R));
+ break;
+ default:
+ llvm::errs() << "Invalid field type for info.\n";
+ exit(1);
+ }
+}
+
+template <> void addReference(NamespaceInfo *I, Reference &&R, FieldId F) {
+ switch (F) {
+ case FieldId::F_namespace:
+ I->Namespace.emplace_back(std::move(R));
+ break;
+ case FieldId::F_child_namespace:
+ I->ChildNamespaces.emplace_back(std::move(R));
+ break;
+ case FieldId::F_child_record:
+ I->ChildRecords.emplace_back(std::move(R));
+ break;
+ default:
+ llvm::errs() << "Invalid field type for info.\n";
+ exit(1);
+ }
+}
+
+template <> void addReference(FunctionInfo *I, Reference &&R, FieldId F) {
+ switch (F) {
+ case FieldId::F_namespace:
+ I->Namespace.emplace_back(std::move(R));
+ break;
+ case FieldId::F_parent:
+ I->Parent = std::move(R);
+ break;
+ default:
+ llvm::errs() << "Invalid field type for info.\n";
+ exit(1);
+ }
+}
+
+template <> void addReference(RecordInfo *I, Reference &&R, FieldId F) {
+ switch (F) {
+ case FieldId::F_namespace:
+ I->Namespace.emplace_back(std::move(R));
+ break;
+ case FieldId::F_parent:
+ I->Parents.emplace_back(std::move(R));
+ break;
+ case FieldId::F_vparent:
+ I->VirtualParents.emplace_back(std::move(R));
+ break;
+ case FieldId::F_child_record:
+ I->ChildRecords.emplace_back(std::move(R));
+ break;
+ default:
+ llvm::errs() << "Invalid field type for info.\n";
+ exit(1);
+ }
+}
+
+template <typename T, typename ChildInfoType>
+void addChild(T I, ChildInfoType &&R) {
+ llvm::errs() << "Invalid child type for info.\n";
+ exit(1);
+}
+
+template <> void addChild(NamespaceInfo *I, FunctionInfo &&R) {
+ I->ChildFunctions.emplace_back(std::move(R));
+}
+
+template <> void addChild(NamespaceInfo *I, EnumInfo &&R) {
+ I->ChildEnums.emplace_back(std::move(R));
+}
+
+template <> void addChild(RecordInfo *I, FunctionInfo &&R) {
+ I->ChildFunctions.emplace_back(std::move(R));
+}
+
+template <> void addChild(RecordInfo *I, EnumInfo &&R) {
+ I->ChildEnums.emplace_back(std::move(R));
+}
+
+// Read records from bitcode into a given info.
+template <typename T> bool ClangDocBitcodeReader::readRecord(unsigned ID, T I) {
+ Record R;
+ llvm::StringRef Blob;
+ unsigned RecID = Stream.readRecord(ID, R, &Blob);
+ return parseRecord(R, RecID, Blob, I);
+}
+
+template <> bool ClangDocBitcodeReader::readRecord(unsigned ID, Reference *I) {
+ Record R;
+ llvm::StringRef Blob;
+ unsigned RecID = Stream.readRecord(ID, R, &Blob);
+ return parseRecord(R, RecID, Blob, I, CurrentReferenceField);
+}
+
+// Read a block of records into a single info.
+template <typename T> bool ClangDocBitcodeReader::readBlock(unsigned ID, T I) {
+ if (Stream.EnterSubBlock(ID))
+ return false;
+
+ while (true) {
+ unsigned BlockOrCode = 0;
+ Cursor Res = skipUntilRecordOrBlock(BlockOrCode);
+
+ switch (Res) {
+ case Cursor::BadBlock:
+ return false;
+ case Cursor::BlockEnd:
+ return true;
+ case Cursor::BlockBegin:
+ if (readSubBlock(BlockOrCode, I))
+ continue;
+ if (!Stream.SkipBlock())
+ return false;
+ continue;
+ case Cursor::Record:
+ break;
+ }
+ if (!readRecord(BlockOrCode, I))
+ return false;
+ }
+}
+
+template <typename T>
+bool ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
+ switch (ID) {
+ // Blocks can only have Comment, Reference, TypeInfo, FunctionInfo, or
+ // EnumInfo subblocks
+ case BI_COMMENT_BLOCK_ID:
+ if (readBlock(ID, getCommentInfo(I)))
+ return true;
+ return false;
+ case BI_TYPE_BLOCK_ID: {
+ TypeInfo TI;
+ if (readBlock(ID, &TI)) {
+ addTypeInfo(I, std::move(TI));
+ return true;
+ }
+ return false;
+ }
+ case BI_FIELD_TYPE_BLOCK_ID: {
+ FieldTypeInfo TI;
+ if (readBlock(ID, &TI)) {
+ addTypeInfo(I, std::move(TI));
+ return true;
+ }
+ return false;
+ }
+ case BI_MEMBER_TYPE_BLOCK_ID: {
+ MemberTypeInfo TI;
+ if (readBlock(ID, &TI)) {
+ addTypeInfo(I, std::move(TI));
+ return true;
+ }
+ return false;
+ }
+ case BI_REFERENCE_BLOCK_ID: {
+ Reference R;
+ if (readBlock(ID, &R)) {
+ addReference(I, std::move(R), CurrentReferenceField);
+ return true;
+ }
+ return false;
+ }
+ case BI_FUNCTION_BLOCK_ID: {
+ FunctionInfo F;
+ if (readBlock(ID, &F)) {
+ addChild(I, std::move(F));
+ return true;
+ }
+ return false;
+ }
+ case BI_ENUM_BLOCK_ID: {
+ EnumInfo E;
+ if (readBlock(ID, &E)) {
+ addChild(I, std::move(E));
+ return true;
+ }
+ return false;
+ }
+ default:
+ llvm::errs() << "Invalid subblock type.\n";
+ return false;
+ }
+}
+
+ClangDocBitcodeReader::Cursor
+ClangDocBitcodeReader::skipUntilRecordOrBlock(unsigned &BlockOrRecordID) {
+ BlockOrRecordID = 0;
+
+ while (!Stream.AtEndOfStream()) {
+ unsigned Code = Stream.ReadCode();
+
+ switch ((llvm::bitc::FixedAbbrevIDs)Code) {
+ case llvm::bitc::ENTER_SUBBLOCK:
+ BlockOrRecordID = Stream.ReadSubBlockID();
+ return Cursor::BlockBegin;
+ case llvm::bitc::END_BLOCK:
+ if (Stream.ReadBlockEnd())
+ return Cursor::BadBlock;
+ return Cursor::BlockEnd;
+ case llvm::bitc::DEFINE_ABBREV:
+ Stream.ReadAbbrevRecord();
+ continue;
+ case llvm::bitc::UNABBREV_RECORD:
+ return Cursor::BadBlock;
+ default:
+ BlockOrRecordID = Code;
+ return Cursor::Record;
+ }
+ }
+ llvm_unreachable("Premature stream end.");
+}
+
+bool ClangDocBitcodeReader::validateStream() {
+ if (Stream.AtEndOfStream())
+ return false;
+
+ // Sniff for the signature.
+ if (Stream.Read(8) != BitCodeConstants::Signature[0] ||
+ Stream.Read(8) != BitCodeConstants::Signature[1] ||
+ Stream.Read(8) != BitCodeConstants::Signature[2] ||
+ Stream.Read(8) != BitCodeConstants::Signature[3])
+ return false;
+ return true;
+}
+
+bool ClangDocBitcodeReader::readBlockInfoBlock() {
+ BlockInfo = Stream.ReadBlockInfoBlock();
+ if (!BlockInfo)
+ return false;
+ Stream.setBlockInfo(&*BlockInfo);
+ return true;
+}
+
+template <typename T>
+std::unique_ptr<Info> ClangDocBitcodeReader::createInfo(unsigned ID) {
+ std::unique_ptr<Info> I = llvm::make_unique<T>();
+ if (readBlock(ID, static_cast<T *>(I.get())))
+ return I;
+ llvm::errs() << "Error reading from block.\n";
+ return nullptr;
+}
+
+std::unique_ptr<Info> ClangDocBitcodeReader::readBlockToInfo(unsigned ID) {
+ switch (ID) {
+ case BI_NAMESPACE_BLOCK_ID:
+ return createInfo<NamespaceInfo>(ID);
+ case BI_RECORD_BLOCK_ID:
+ return createInfo<RecordInfo>(ID);
+ case BI_ENUM_BLOCK_ID:
+ return createInfo<EnumInfo>(ID);
+ case BI_FUNCTION_BLOCK_ID:
+ return createInfo<FunctionInfo>(ID);
+ default:
+ llvm::errs() << "Error reading from block.\n";
+ return nullptr;
+ }
+}
+
+// Entry point
+llvm::Expected<std::vector<std::unique_ptr<Info>>>
+ClangDocBitcodeReader::readBitcode() {
+ std::vector<std::unique_ptr<Info>> Infos;
+ if (!validateStream())
+ return llvm::make_error<llvm::StringError>("Invalid bitcode stream.\n",
+ llvm::inconvertibleErrorCode());
+ ;
+
+ // Read the top level blocks.
+ while (!Stream.AtEndOfStream()) {
+ unsigned Code = Stream.ReadCode();
+ if (Code != llvm::bitc::ENTER_SUBBLOCK)
+ return llvm::make_error<llvm::StringError>(
+ "Missing subblock in bitcode.\n", llvm::inconvertibleErrorCode());
+ ;
+
+ unsigned ID = Stream.ReadSubBlockID();
+ switch (ID) {
+ // NamedType and Comment blocks should not appear at the top level
+ case BI_TYPE_BLOCK_ID:
+ case BI_FIELD_TYPE_BLOCK_ID:
+ case BI_MEMBER_TYPE_BLOCK_ID:
+ case BI_COMMENT_BLOCK_ID:
+ case BI_REFERENCE_BLOCK_ID:
+ return llvm::make_error<llvm::StringError>(
+ "Invalid top level block in bitcode.\n",
+ llvm::inconvertibleErrorCode());
+ ;
+ case BI_NAMESPACE_BLOCK_ID:
+ case BI_RECORD_BLOCK_ID:
+ case BI_ENUM_BLOCK_ID:
+ case BI_FUNCTION_BLOCK_ID:
+ if (std::unique_ptr<Info> I = readBlockToInfo(ID))
+ Infos.emplace_back(std::move(I));
+ continue;
+ case BI_VERSION_BLOCK_ID:
+ if (readBlock(ID, VersionNumber))
+ continue;
+ return llvm::make_error<llvm::StringError>(
+ "Invalid bitcode version in bitcode.\n",
+ llvm::inconvertibleErrorCode());
+ ;
+ case llvm::bitc::BLOCKINFO_BLOCK_ID:
+ if (readBlockInfoBlock())
+ continue;
+ return llvm::make_error<llvm::StringError>(
+ "Invalid BlockInfo in bitcode.\n", llvm::inconvertibleErrorCode());
+ ;
+ default:
+ if (!Stream.SkipBlock())
+ continue;
+ }
+ }
+ return std::move(Infos);
+}
+
+} // namespace doc
+} // namespace clang
diff --git a/clang-doc/BitcodeReader.h b/clang-doc/BitcodeReader.h
new file mode 100644
index 00000000..aaf25257
--- /dev/null
+++ b/clang-doc/BitcodeReader.h
@@ -0,0 +1,74 @@
+//===-- BitcodeReader.h - ClangDoc Bitcode Reader --------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements a reader for parsing the clang-doc internal
+// representation from LLVM bitcode. The reader takes in a stream of bits and
+// generates the set of infos that it represents.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEREADER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEREADER_H
+
+#include "BitcodeWriter.h"
+#include "Representation.h"
+#include "clang/AST/AST.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Bitcode/BitstreamReader.h"
+
+namespace clang {
+namespace doc {
+
+// Class to read bitstream into an InfoSet collection
+class ClangDocBitcodeReader {
+public:
+ ClangDocBitcodeReader(llvm::BitstreamCursor &Stream) : Stream(Stream) {}
+
+ // Main entry point, calls readBlock to read each block in the given stream.
+ llvm::Expected<std::vector<std::unique_ptr<Info>>> readBitcode();
+
+private:
+ enum class Cursor { BadBlock = 1, Record, BlockEnd, BlockBegin };
+
+ // Top level parsing
+ bool validateStream();
+ bool readVersion();
+ bool readBlockInfoBlock();
+
+ // Read a block of records into a single Info struct, calls readRecord on each
+ // record found.
+ template <typename T> bool readBlock(unsigned ID, T I);
+
+ // Step through a block of records to find the next data field.
+ template <typename T> bool readSubBlock(unsigned ID, T I);
+
+ // Read record data into the given Info data field, calling the appropriate
+ // parseRecord functions to parse and store the data.
+ template <typename T> bool readRecord(unsigned ID, T I);
+
+ // Allocate the relevant type of info and add read data to the object.
+ template <typename T> std::unique_ptr<Info> createInfo(unsigned ID);
+
+ // Helper function to step through blocks to find and dispatch the next record
+ // or block to be read.
+ Cursor skipUntilRecordOrBlock(unsigned &BlockOrRecordID);
+
+ // Helper function to set up the approriate type of Info.
+ std::unique_ptr<Info> readBlockToInfo(unsigned ID);
+
+ llvm::BitstreamCursor &Stream;
+ Optional<llvm::BitstreamBlockInfo> BlockInfo;
+ FieldId CurrentReferenceField;
+};
+
+} // namespace doc
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEREADER_H
diff --git a/clang-doc/BitcodeWriter.cpp b/clang-doc/BitcodeWriter.cpp
index c133d9a6..f73724e4 100644
--- a/clang-doc/BitcodeWriter.cpp
+++ b/clang-doc/BitcodeWriter.cpp
@@ -214,6 +214,8 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
// AbbreviationMap
+constexpr char BitCodeConstants::Signature[];
+
void ClangDocBitcodeWriter::AbbreviationMap::add(RecordId RID,
unsigned AbbrevID) {
assert(RecordIdNameMap[RID] && "Unknown RecordId.");
@@ -232,7 +234,7 @@ unsigned ClangDocBitcodeWriter::AbbreviationMap::get(RecordId RID) const {
/// \brief Emits the magic number header to check that its the right format,
/// in this case, 'DOCS'.
void ClangDocBitcodeWriter::emitHeader() {
- for (char C : llvm::StringRef("DOCS"))
+ for (char C : BitCodeConstants::Signature)
Stream.Emit((unsigned)C, BitCodeConstants::SignatureBitSize);
}
@@ -424,22 +426,32 @@ void ClangDocBitcodeWriter::emitBlock(const CommentInfo &I) {
emitBlock(*C);
}
-#define EMITINFO(X) \
- emitRecord(I.USR, X##_USR); \
- emitRecord(I.Name, X##_NAME); \
- for (const auto &N : I.Namespace) \
- emitBlock(N, FieldId::F_namespace); \
- for (const auto &CI : I.Description) \
- emitBlock(CI);
-
void ClangDocBitcodeWriter::emitBlock(const NamespaceInfo &I) {
StreamSubBlockGuard Block(Stream, BI_NAMESPACE_BLOCK_ID);
- EMITINFO(NAMESPACE)
+ emitRecord(I.USR, NAMESPACE_USR);
+ emitRecord(I.Name, NAMESPACE_NAME);
+ for (const auto &N : I.Namespace)
+ emitBlock(N, FieldId::F_namespace);
+ for (const auto &CI : I.Description)
+ emitBlock(CI);
+ for (const auto &C : I.ChildNamespaces)
+ emitBlock(C, FieldId::F_child_namespace);
+ for (const auto &C : I.ChildRecords)
+ emitBlock(C, FieldId::F_child_record);
+ for (const auto &C : I.ChildFunctions)
+ emitBlock(C);
+ for (const auto &C : I.ChildEnums)
+ emitBlock(C);
}
void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) {
StreamSubBlockGuard Block(Stream, BI_ENUM_BLOCK_ID);
- EMITINFO(ENUM)
+ emitRecord(I.USR, ENUM_USR);
+ emitRecord(I.Name, ENUM_NAME);
+ for (const auto &N : I.Namespace)
+ emitBlock(N, FieldId::F_namespace);
+ for (const auto &CI : I.Description)
+ emitBlock(CI);
if (I.DefLoc)
emitRecord(I.DefLoc.getValue(), ENUM_DEFLOCATION);
for (const auto &L : I.Loc)
@@ -451,7 +463,12 @@ void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) {
void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
StreamSubBlockGuard Block(Stream, BI_RECORD_BLOCK_ID);
- EMITINFO(RECORD)
+ emitRecord(I.USR, RECORD_USR);
+ emitRecord(I.Name, RECORD_NAME);
+ for (const auto &N : I.Namespace)
+ emitBlock(N, FieldId::F_namespace);
+ for (const auto &CI : I.Description)
+ emitBlock(CI);
if (I.DefLoc)
emitRecord(I.DefLoc.getValue(), RECORD_DEFLOCATION);
for (const auto &L : I.Loc)
@@ -463,11 +480,22 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
emitBlock(P, FieldId::F_parent);
for (const auto &P : I.VirtualParents)
emitBlock(P, FieldId::F_vparent);
+ for (const auto &C : I.ChildRecords)
+ emitBlock(C, FieldId::F_child_record);
+ for (const auto &C : I.ChildFunctions)
+ emitBlock(C);
+ for (const auto &C : I.ChildEnums)
+ emitBlock(C);
}
void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) {
StreamSubBlockGuard Block(Stream, BI_FUNCTION_BLOCK_ID);
- EMITINFO(FUNCTION)
+ emitRecord(I.USR, FUNCTION_USR);
+ emitRecord(I.Name, FUNCTION_NAME);
+ for (const auto &N : I.Namespace)
+ emitBlock(N, FieldId::F_namespace);
+ for (const auto &CI : I.Description)
+ emitBlock(CI);
emitRecord(I.IsMethod, FUNCTION_IS_METHOD);
if (I.DefLoc)
emitRecord(I.DefLoc.getValue(), FUNCTION_DEFLOCATION);
@@ -479,7 +507,26 @@ void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) {
emitBlock(N);
}
-#undef EMITINFO
+bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) {
+ switch (I->IT) {
+ case InfoType::IT_namespace:
+ emitBlock(*static_cast<clang::doc::NamespaceInfo *>(I));
+ break;
+ case InfoType::IT_record:
+ emitBlock(*static_cast<clang::doc::RecordInfo *>(I));
+ break;
+ case InfoType::IT_enum:
+ emitBlock(*static_cast<clang::doc::EnumInfo *>(I));
+ break;
+ case InfoType::IT_function:
+ emitBlock(*static_cast<clang::doc::FunctionInfo *>(I));
+ break;
+ default:
+ llvm::errs() << "Unexpected info, unable to write.\n";
+ return true;
+ }
+ return false;
+}
} // namespace doc
} // namespace clang
diff --git a/clang-doc/BitcodeWriter.h b/clang-doc/BitcodeWriter.h
index ab997af7..2ff46c61 100644
--- a/clang-doc/BitcodeWriter.h
+++ b/clang-doc/BitcodeWriter.h
@@ -34,7 +34,7 @@ namespace doc {
static const unsigned VersionNumber = 2;
struct BitCodeConstants {
- static constexpr unsigned RecordSize = 16U;
+ static constexpr unsigned RecordSize = 32U;
static constexpr unsigned SignatureBitSize = 8U;
static constexpr unsigned SubblockIDSize = 4U;
static constexpr unsigned BoolSize = 1U;
@@ -45,6 +45,8 @@ struct BitCodeConstants {
static constexpr unsigned ReferenceTypeSize = 8U;
static constexpr unsigned USRLengthSize = 6U;
static constexpr unsigned USRBitLengthSize = 8U;
+ static constexpr char Signature[4] = {'D', 'O', 'C', 'S'};
+ static constexpr int USRHashSize = 20;
};
// New Ids need to be added to both the enum here and the relevant IdNameMap in
@@ -66,11 +68,10 @@ enum BlockId {
// New Ids need to be added to the enum here, and to the relevant IdNameMap and
// initialization list in the implementation file.
-#define INFORECORDS(X) X##_USR, X##_NAME
-
enum RecordId {
VERSION = 1,
- INFORECORDS(FUNCTION),
+ FUNCTION_USR,
+ FUNCTION_NAME,
FUNCTION_DEFLOCATION,
FUNCTION_LOCATION,
FUNCTION_ACCESS,
@@ -89,13 +90,16 @@ enum RecordId {
FIELD_TYPE_NAME,
MEMBER_TYPE_NAME,
MEMBER_TYPE_ACCESS,
- INFORECORDS(NAMESPACE),
- INFORECORDS(ENUM),
+ NAMESPACE_USR,
+ NAMESPACE_NAME,
+ ENUM_USR,
+ ENUM_NAME,
ENUM_DEFLOCATION,
ENUM_LOCATION,
ENUM_MEMBER,
ENUM_SCOPED,
- INFORECORDS(RECORD),
+ RECORD_USR,
+ RECORD_NAME,
RECORD_DEFLOCATION,
RECORD_LOCATION,
RECORD_TAG_TYPE,
@@ -110,10 +114,16 @@ enum RecordId {
static constexpr unsigned BlockIdCount = BI_LAST - BI_FIRST;
static constexpr unsigned RecordIdCount = RI_LAST - RI_FIRST;
-#undef INFORECORDS
-
// Identifiers for differentiating between subblocks
-enum class FieldId { F_namespace = 1, F_parent, F_vparent, F_type };
+enum class FieldId {
+ F_default,
+ F_namespace,
+ F_parent,
+ F_vparent,
+ F_type,
+ F_child_namespace,
+ F_child_record
+};
class ClangDocBitcodeWriter {
public:
@@ -123,12 +133,8 @@ public:
emitVersionBlock();
}
-#ifndef NDEBUG // Don't want explicit dtor unless needed.
- ~ClangDocBitcodeWriter() {
- // Check that the static size is large-enough.
- assert(Record.capacity() > BitCodeConstants::RecordSize);
- }
-#endif
+ // Write a specific info to a bitcode stream.
+ bool dispatchInfoForWrite(Info *I);
// Block emission of different info types.
void emitBlock(const NamespaceInfo &I);
diff --git a/clang-doc/CMakeLists.txt b/clang-doc/CMakeLists.txt
index 1852baa6..eaebf616 100644
--- a/clang-doc/CMakeLists.txt
+++ b/clang-doc/CMakeLists.txt
@@ -1,12 +1,18 @@
set(LLVM_LINK_COMPONENTS
support
+ BitReader
+ BitWriter
)
add_clang_library(clangDoc
+ BitcodeReader.cpp
BitcodeWriter.cpp
ClangDoc.cpp
+ Generators.cpp
Mapper.cpp
+ Representation.cpp
Serialize.cpp
+ YAMLGenerator.cpp
LINK_LIBS
clangAnalysis
diff --git a/clang-doc/ClangDoc.cpp b/clang-doc/ClangDoc.cpp
index cd737234..a11c5840 100644
--- a/clang-doc/ClangDoc.cpp
+++ b/clang-doc/ClangDoc.cpp
@@ -15,6 +15,7 @@
#include "ClangDoc.h"
#include "Mapper.h"
+#include "Representation.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
@@ -28,33 +29,33 @@ namespace doc {
class MapperActionFactory : public tooling::FrontendActionFactory {
public:
- MapperActionFactory(tooling::ExecutionContext *ECtx) : ECtx(ECtx) {}
+ MapperActionFactory(ClangDocContext CDCtx) : CDCtx(CDCtx) {}
clang::FrontendAction *create() override;
private:
- tooling::ExecutionContext *ECtx;
+ ClangDocContext CDCtx;
};
clang::FrontendAction *MapperActionFactory::create() {
class ClangDocAction : public clang::ASTFrontendAction {
public:
- ClangDocAction(ExecutionContext *ECtx) : ECtx(ECtx) {}
+ ClangDocAction(ClangDocContext CDCtx) : CDCtx(CDCtx) {}
std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance &Compiler,
llvm::StringRef InFile) override {
- return llvm::make_unique<MapASTVisitor>(&Compiler.getASTContext(), ECtx);
+ return llvm::make_unique<MapASTVisitor>(&Compiler.getASTContext(), CDCtx);
}
private:
- ExecutionContext *ECtx;
+ ClangDocContext CDCtx;
};
- return new ClangDocAction(ECtx);
+ return new ClangDocAction(CDCtx);
}
std::unique_ptr<tooling::FrontendActionFactory>
-newMapperActionFactory(tooling::ExecutionContext *ECtx) {
- return llvm::make_unique<MapperActionFactory>(ECtx);
+newMapperActionFactory(ClangDocContext CDCtx) {
+ return llvm::make_unique<MapperActionFactory>(CDCtx);
}
} // namespace doc
diff --git a/clang-doc/ClangDoc.h b/clang-doc/ClangDoc.h
index 9a9817c7..59e9a92b 100644
--- a/clang-doc/ClangDoc.h
+++ b/clang-doc/ClangDoc.h
@@ -17,6 +17,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H
+#include "Representation.h"
#include "clang/Tooling/Execution.h"
#include "clang/Tooling/StandaloneExecution.h"
#include "clang/Tooling/Tooling.h"
@@ -25,7 +26,7 @@ namespace clang {
namespace doc {
std::unique_ptr<tooling::FrontendActionFactory>
-newMapperActionFactory(tooling::ExecutionContext *ECtx);
+newMapperActionFactory(ClangDocContext CDCtx);
} // namespace doc
} // namespace clang
diff --git a/clang-doc/Generators.cpp b/clang-doc/Generators.cpp
new file mode 100644
index 00000000..fe01d610
--- /dev/null
+++ b/clang-doc/Generators.cpp
@@ -0,0 +1,36 @@
+//===---- Generator.cpp - Generator Registry ---------------------*- C++-*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Generators.h"
+
+LLVM_INSTANTIATE_REGISTRY(clang::doc::GeneratorRegistry)
+
+namespace clang {
+namespace doc {
+
+llvm::Expected<std::unique_ptr<Generator>>
+findGeneratorByName(llvm::StringRef Format) {
+ for (auto I = GeneratorRegistry::begin(), E = GeneratorRegistry::end();
+ I != E; ++I) {
+ if (I->getName() != Format)
+ continue;
+ return I->instantiate();
+ }
+ return llvm::make_error<llvm::StringError>("Can't find generator: " + Format,
+ llvm::inconvertibleErrorCode());
+}
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register the generators.
+extern volatile int YAMLGeneratorAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED YAMLGeneratorAnchorDest =
+ YAMLGeneratorAnchorSource;
+
+} // namespace doc
+} // namespace clang
diff --git a/clang-doc/Generators.h b/clang-doc/Generators.h
new file mode 100644
index 00000000..9106d2cf
--- /dev/null
+++ b/clang-doc/Generators.h
@@ -0,0 +1,41 @@
+//===-- Generators.h - ClangDoc Generator ----------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+// Generator classes for converting declaration information into documentation
+// in a specified format.
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_GENERATOR_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_GENERATOR_H
+
+#include "Representation.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/Registry.h"
+
+namespace clang {
+namespace doc {
+
+// Abstract base class for generators.
+// This is expected to be implemented and exposed via the GeneratorRegistry.
+class Generator {
+public:
+ virtual ~Generator() = default;
+
+ // Write out the decl info in the specified format.
+ virtual bool generateDocForInfo(Info *I, llvm::raw_ostream &OS) = 0;
+};
+
+typedef llvm::Registry<Generator> GeneratorRegistry;
+
+llvm::Expected<std::unique_ptr<Generator>>
+findGeneratorByName(llvm::StringRef Format);
+
+} // namespace doc
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_GENERATOR_H
diff --git a/clang-doc/Mapper.cpp b/clang-doc/Mapper.cpp
index f3ef99e6..71e94047 100644
--- a/clang-doc/Mapper.cpp
+++ b/clang-doc/Mapper.cpp
@@ -13,6 +13,7 @@
#include "clang/AST/Comment.h"
#include "clang/Index/USRGeneration.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Error.h"
using clang::comments::FullComment;
@@ -33,10 +34,15 @@ template <typename T> bool MapASTVisitor::mapDecl(const T *D) {
if (index::generateUSRForDecl(D, USR))
return true;
- ECtx->reportResult(llvm::toHex(llvm::toStringRef(serialize::hashUSR(USR))),
- serialize::emitInfo(D, getComment(D, D->getASTContext()),
- getLine(D, D->getASTContext()),
- getFile(D, D->getASTContext())));
+ auto I = serialize::emitInfo(
+ D, getComment(D, D->getASTContext()), getLine(D, D->getASTContext()),
+ getFile(D, D->getASTContext()), CDCtx.PublicOnly);
+
+ // A null in place of I indicates that the serializer is skipping this decl
+ // for some reason (e.g. we're only reporting public decls).
+ if (I)
+ CDCtx.ECtx->reportResult(llvm::toHex(llvm::toStringRef(I->USR)),
+ serialize::serialize(I));
return true;
}
@@ -72,13 +78,13 @@ MapASTVisitor::getComment(const NamedDecl *D, const ASTContext &Context) const {
int MapASTVisitor::getLine(const NamedDecl *D,
const ASTContext &Context) const {
- return Context.getSourceManager().getPresumedLoc(D->getLocStart()).getLine();
+ return Context.getSourceManager().getPresumedLoc(D->getBeginLoc()).getLine();
}
llvm::StringRef MapASTVisitor::getFile(const NamedDecl *D,
const ASTContext &Context) const {
return Context.getSourceManager()
- .getPresumedLoc(D->getLocStart())
+ .getPresumedLoc(D->getBeginLoc())
.getFilename();
}
diff --git a/clang-doc/Mapper.h b/clang-doc/Mapper.h
index 1aa3f463..a0b1ac22 100644
--- a/clang-doc/Mapper.h
+++ b/clang-doc/Mapper.h
@@ -18,6 +18,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_MAPPER_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_MAPPER_H
+#include "Representation.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Tooling/Execution.h"
@@ -30,8 +31,8 @@ namespace doc {
class MapASTVisitor : public clang::RecursiveASTVisitor<MapASTVisitor>,
public ASTConsumer {
public:
- explicit MapASTVisitor(ASTContext *Ctx, ExecutionContext *ECtx)
- : ECtx(ECtx) {}
+ explicit MapASTVisitor(ASTContext *Ctx, ClangDocContext CDCtx)
+ : CDCtx(CDCtx) {}
void HandleTranslationUnit(ASTContext &Context) override;
bool VisitNamespaceDecl(const NamespaceDecl *D);
@@ -48,7 +49,7 @@ private:
comments::FullComment *getComment(const NamedDecl *D,
const ASTContext &Context) const;
- ExecutionContext *ECtx;
+ ClangDocContext CDCtx;
};
} // namespace doc
diff --git a/clang-doc/Representation.cpp b/clang-doc/Representation.cpp
new file mode 100644
index 00000000..eacf11a8
--- /dev/null
+++ b/clang-doc/Representation.cpp
@@ -0,0 +1,193 @@
+///===-- Representation.cpp - ClangDoc Representation -----------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the merging of different types of infos. The data in the
+// calling Info is preserved during a merge unless that field is empty or
+// default. In that case, the data from the parameter Info is used to replace
+// the empty or default data.
+//
+// For most fields, the first decl seen provides the data. Exceptions to this
+// include the location and description fields, which are collections of data on
+// all decls related to a given definition. All other fields are ignored in new
+// decls unless the first seen decl didn't, for whatever reason, incorporate
+// data on that field (e.g. a forward declared class wouldn't have information
+// on members on the forward declaration, but would have the class name).
+//
+//===----------------------------------------------------------------------===//
+#include "Representation.h"
+#include "llvm/Support/Error.h"
+
+namespace clang {
+namespace doc {
+
+namespace {
+
+const SymbolID EmptySID = SymbolID();
+
+template <typename T>
+llvm::Expected<std::unique_ptr<Info>>
+reduce(std::vector<std::unique_ptr<Info>> &Values) {
+ if (Values.empty())
+ return llvm::make_error<llvm::StringError>(" No values to reduce.\n",
+ llvm::inconvertibleErrorCode());
+ std::unique_ptr<Info> Merged = llvm::make_unique<T>(Values[0]->USR);
+ T *Tmp = static_cast<T *>(Merged.get());
+ for (auto &I : Values)
+ Tmp->merge(std::move(*static_cast<T *>(I.get())));
+ return std::move(Merged);
+}
+
+// Return the index of the matching child in the vector, or -1 if merge is not
+// necessary.
+template <typename T>
+int getChildIndexIfExists(std::vector<T> &Children, T &ChildToMerge) {
+ for (unsigned long I = 0; I < Children.size(); I++) {
+ if (ChildToMerge.USR == Children[I].USR)
+ return I;
+ }
+ return -1;
+}
+
+// For References, we don't need to actually merge them, we just don't want
+// duplicates.
+void reduceChildren(std::vector<Reference> &Children,
+ std::vector<Reference> &&ChildrenToMerge) {
+ for (auto &ChildToMerge : ChildrenToMerge) {
+ if (getChildIndexIfExists(Children, ChildToMerge) == -1)
+ Children.push_back(std::move(ChildToMerge));
+ }
+}
+
+void reduceChildren(std::vector<FunctionInfo> &Children,
+ std::vector<FunctionInfo> &&ChildrenToMerge) {
+ for (auto &ChildToMerge : ChildrenToMerge) {
+ int mergeIdx = getChildIndexIfExists(Children, ChildToMerge);
+ if (mergeIdx == -1) {
+ Children.push_back(std::move(ChildToMerge));
+ continue;
+ }
+ Children[mergeIdx].merge(std::move(ChildToMerge));
+ }
+}
+
+void reduceChildren(std::vector<EnumInfo> &Children,
+ std::vector<EnumInfo> &&ChildrenToMerge) {
+ for (auto &ChildToMerge : ChildrenToMerge) {
+ int mergeIdx = getChildIndexIfExists(Children, ChildToMerge);
+ if (mergeIdx == -1) {
+ Children.push_back(std::move(ChildToMerge));
+ continue;
+ }
+ Children[mergeIdx].merge(std::move(ChildToMerge));
+ }
+}
+
+} // namespace
+
+// Dispatch function.
+llvm::Expected<std::unique_ptr<Info>>
+mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
+ if (Values.empty())
+ return llvm::make_error<llvm::StringError>("No info values to merge.\n",
+ llvm::inconvertibleErrorCode());
+
+ switch (Values[0]->IT) {
+ case InfoType::IT_namespace:
+ return reduce<NamespaceInfo>(Values);
+ case InfoType::IT_record:
+ return reduce<RecordInfo>(Values);
+ case InfoType::IT_enum:
+ return reduce<EnumInfo>(Values);
+ case InfoType::IT_function:
+ return reduce<FunctionInfo>(Values);
+ default:
+ return llvm::make_error<llvm::StringError>("Unexpected info type.\n",
+ llvm::inconvertibleErrorCode());
+ }
+}
+
+void Info::mergeBase(Info &&Other) {
+ assert(mergeable(Other));
+ if (USR == EmptySID)
+ USR = Other.USR;
+ if (Name == "")
+ Name = Other.Name;
+ if (Namespace.empty())
+ Namespace = std::move(Other.Namespace);
+ // Unconditionally extend the description, since each decl may have a comment.
+ std::move(Other.Description.begin(), Other.Description.end(),
+ std::back_inserter(Description));
+}
+
+bool Info::mergeable(const Info &Other) {
+ return IT == Other.IT && USR == Other.USR;
+}
+
+void SymbolInfo::merge(SymbolInfo &&Other) {
+ assert(mergeable(Other));
+ if (!DefLoc)
+ DefLoc = std::move(Other.DefLoc);
+ // Unconditionally extend the list of locations, since we want all of them.
+ std::move(Other.Loc.begin(), Other.Loc.end(), std::back_inserter(Loc));
+ mergeBase(std::move(Other));
+}
+
+void NamespaceInfo::merge(NamespaceInfo &&Other) {
+ assert(mergeable(Other));
+ // Reduce children if necessary.
+ reduceChildren(ChildNamespaces, std::move(Other.ChildNamespaces));
+ reduceChildren(ChildRecords, std::move(Other.ChildRecords));
+ reduceChildren(ChildFunctions, std::move(Other.ChildFunctions));
+ reduceChildren(ChildEnums, std::move(Other.ChildEnums));
+ mergeBase(std::move(Other));
+}
+
+void RecordInfo::merge(RecordInfo &&Other) {
+ assert(mergeable(Other));
+ if (!TagType)
+ TagType = Other.TagType;
+ if (Members.empty())
+ Members = std::move(Other.Members);
+ if (Parents.empty())
+ Parents = std::move(Other.Parents);
+ if (VirtualParents.empty())
+ VirtualParents = std::move(Other.VirtualParents);
+ // Reduce children if necessary.
+ reduceChildren(ChildRecords, std::move(Other.ChildRecords));
+ reduceChildren(ChildFunctions, std::move(Other.ChildFunctions));
+ reduceChildren(ChildEnums, std::move(Other.ChildEnums));
+ SymbolInfo::merge(std::move(Other));
+}
+
+void EnumInfo::merge(EnumInfo &&Other) {
+ assert(mergeable(Other));
+ if (!Scoped)
+ Scoped = Other.Scoped;
+ if (Members.empty())
+ Members = std::move(Other.Members);
+ SymbolInfo::merge(std::move(Other));
+}
+
+void FunctionInfo::merge(FunctionInfo &&Other) {
+ assert(mergeable(Other));
+ if (!IsMethod)
+ IsMethod = Other.IsMethod;
+ if (!Access)
+ Access = Other.Access;
+ if (ReturnType.Type.USR == EmptySID && ReturnType.Type.Name == "")
+ ReturnType = std::move(Other.ReturnType);
+ if (Parent.USR == EmptySID && Parent.Name == "")
+ Parent = std::move(Other.Parent);
+ if (Params.empty())
+ Params = std::move(Other.Params);
+ SymbolInfo::merge(std::move(Other));
+}
+
+} // namespace doc
+} // namespace clang
diff --git a/clang-doc/Representation.h b/clang-doc/Representation.h
index 8af16814..9e88bd9c 100644
--- a/clang-doc/Representation.h
+++ b/clang-doc/Representation.h
@@ -1,4 +1,4 @@
-///===-- Representation.h - ClangDoc Represenation --------------*- C++ -*-===//
+///===-- Representation.h - ClangDoc Representation -------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
@@ -17,6 +17,7 @@
#include "clang/AST/Type.h"
#include "clang/Basic/Specifiers.h"
+#include "clang/Tooling/StandaloneExecution.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
@@ -26,9 +27,13 @@
namespace clang {
namespace doc {
+// SHA1'd hash of a USR.
using SymbolID = std::array<uint8_t, 20>;
struct Info;
+struct FunctionInfo;
+struct EnumInfo;
+
enum class InfoType {
IT_default,
IT_namespace,
@@ -40,7 +45,8 @@ enum class InfoType {
// A representation of a parsed comment.
struct CommentInfo {
CommentInfo() = default;
- CommentInfo(CommentInfo &&Other) : Children(std::move(Other.Children)) {}
+ CommentInfo(CommentInfo &Other) = delete;
+ CommentInfo(CommentInfo &&Other) = default;
SmallString<16> Kind; // Kind of comment (TextComment, InlineCommandComment,
// HTMLStartTagComment, HTMLEndTagComment,
@@ -71,6 +77,11 @@ struct Reference {
Reference(SymbolID USR, StringRef Name, InfoType IT)
: USR(USR), Name(Name), RefType(IT) {}
+ bool operator==(const Reference &Other) const {
+ return std::tie(USR, Name, RefType) ==
+ std::tie(Other.USR, Other.Name, Other.RefType);
+ }
+
SymbolID USR = SymbolID(); // Unique identifer for referenced decl
SmallString<16> Name; // Name of type (possibly unresolved).
InfoType RefType = InfoType::IT_default; // Indicates the type of this
@@ -85,6 +96,8 @@ struct TypeInfo {
: Type(Type, Field, IT) {}
TypeInfo(llvm::StringRef RefName) : Type(RefName) {}
+ bool operator==(const TypeInfo &Other) const { return Type == Other.Type; }
+
Reference Type; // Referenced type in this info.
};
@@ -97,6 +110,10 @@ struct FieldTypeInfo : public TypeInfo {
FieldTypeInfo(llvm::StringRef RefName, llvm::StringRef Name)
: TypeInfo(RefName), Name(Name) {}
+ bool operator==(const FieldTypeInfo &Other) const {
+ return std::tie(Type, Name) == std::tie(Other.Type, Other.Name);
+ }
+
SmallString<16> Name; // Name associated with this info.
};
@@ -110,6 +127,11 @@ struct MemberTypeInfo : public FieldTypeInfo {
AccessSpecifier Access)
: FieldTypeInfo(RefName, Name), Access(Access) {}
+ bool operator==(const MemberTypeInfo &Other) const {
+ return std::tie(Type, Name, Access) ==
+ std::tie(Other.Type, Other.Name, Other.Access);
+ }
+
AccessSpecifier Access = AccessSpecifier::AS_none; // Access level associated
// with this info (public,
// protected, private,
@@ -121,6 +143,11 @@ struct Location {
Location(int LineNumber, SmallString<16> Filename)
: LineNumber(LineNumber), Filename(std::move(Filename)) {}
+ bool operator==(const Location &Other) const {
+ return std::tie(LineNumber, Filename) ==
+ std::tie(Other.LineNumber, Other.Filename);
+ }
+
int LineNumber; // Line number of this Location.
SmallString<32> Filename; // File for this Location.
};
@@ -128,21 +155,56 @@ struct Location {
/// A base struct for Infos.
struct Info {
Info() = default;
- Info(Info &&Other) : Description(std::move(Other.Description)) {}
- virtual ~Info() = default;
+ Info(InfoType IT) : IT(IT) {}
+ Info(InfoType IT, SymbolID USR) : USR(USR), IT(IT) {}
+ Info(InfoType IT, SymbolID USR, StringRef Name)
+ : USR(USR), IT(IT), Name(Name) {}
+ Info(const Info &Other) = delete;
+ Info(Info &&Other) = default;
- SymbolID USR; // Unique identifier for the decl described by this Info.
- SmallString<16> Name; // Unqualified name of the decl.
+ SymbolID USR =
+ SymbolID(); // Unique identifier for the decl described by this Info.
+ const InfoType IT = InfoType::IT_default; // InfoType of this particular Info.
+ SmallString<16> Name; // Unqualified name of the decl.
llvm::SmallVector<Reference, 4>
Namespace; // List of parent namespaces for this decl.
std::vector<CommentInfo> Description; // Comment description of this decl.
+
+ void mergeBase(Info &&I);
+ bool mergeable(const Info &Other);
+
+ // Returns a reference to the parent scope (that is, the immediate parent
+ // namespace or class in which this decl resides).
+ llvm::Expected<Reference> getEnclosingScope();
};
// Info for namespaces.
-struct NamespaceInfo : public Info {};
+struct NamespaceInfo : public Info {
+ NamespaceInfo() : Info(InfoType::IT_namespace) {}
+ NamespaceInfo(SymbolID USR) : Info(InfoType::IT_namespace, USR) {}
+ NamespaceInfo(SymbolID USR, StringRef Name)
+ : Info(InfoType::IT_namespace, USR, Name) {}
+
+ void merge(NamespaceInfo &&I);
+
+ // Namespaces and Records are references because they will be properly
+ // documented in their own info, while the entirety of Functions and Enums are
+ // included here because they should not have separate documentation from
+ // their scope.
+ std::vector<Reference> ChildNamespaces;
+ std::vector<Reference> ChildRecords;
+ std::vector<FunctionInfo> ChildFunctions;
+ std::vector<EnumInfo> ChildEnums;
+};
// Info for symbols.
struct SymbolInfo : public Info {
+ SymbolInfo(InfoType IT) : Info(IT) {}
+ SymbolInfo(InfoType IT, SymbolID USR) : Info(IT, USR) {}
+ SymbolInfo(InfoType IT, SymbolID USR, StringRef Name) : Info(IT, USR, Name) {}
+
+ void merge(SymbolInfo &&I);
+
llvm::Optional<Location> DefLoc; // Location where this decl is defined.
llvm::SmallVector<Location, 2> Loc; // Locations where this decl is declared.
};
@@ -150,32 +212,58 @@ struct SymbolInfo : public Info {
// TODO: Expand to allow for documenting templating and default args.
// Info for functions.
struct FunctionInfo : public SymbolInfo {
+ FunctionInfo() : SymbolInfo(InfoType::IT_function) {}
+ FunctionInfo(SymbolID USR) : SymbolInfo(InfoType::IT_function, USR) {}
+
+ void merge(FunctionInfo &&I);
+
bool IsMethod = false; // Indicates whether this function is a class method.
Reference Parent; // Reference to the parent class decl for this method.
TypeInfo ReturnType; // Info about the return type of this function.
- llvm::SmallVector<FieldTypeInfo, 4> Params; // List of parameters.
- AccessSpecifier Access = AccessSpecifier::AS_none; // Access level for this
- // method (public, private,
- // protected, none).
+ llvm::SmallVector<FieldTypeInfo, 4> Params; // List of parameters.
+ // Access level for this method (public, private, protected, none).
+ AccessSpecifier Access = AccessSpecifier::AS_none;
};
// TODO: Expand to allow for documenting templating, inheritance access,
// friend classes
// Info for types.
struct RecordInfo : public SymbolInfo {
- TagTypeKind TagType = TagTypeKind::TTK_Struct; // Type of this record (struct,
- // class, union, interface).
+ RecordInfo() : SymbolInfo(InfoType::IT_record) {}
+ RecordInfo(SymbolID USR) : SymbolInfo(InfoType::IT_record, USR) {}
+ RecordInfo(SymbolID USR, StringRef Name)
+ : SymbolInfo(InfoType::IT_record, USR, Name) {}
+
+ void merge(RecordInfo &&I);
+
+ TagTypeKind TagType = TagTypeKind::TTK_Struct; // Type of this record
+ // (struct, class, union,
+ // interface).
llvm::SmallVector<MemberTypeInfo, 4>
Members; // List of info about record members.
- llvm::SmallVector<Reference, 4> Parents; // List of base/parent records (does
- // not include virtual parents).
+ llvm::SmallVector<Reference, 4> Parents; // List of base/parent records
+ // (does not include virtual
+ // parents).
llvm::SmallVector<Reference, 4>
VirtualParents; // List of virtual base/parent records.
+
+ // Records are references because they will be properly
+ // documented in their own info, while the entirety of Functions and Enums are
+ // included here because they should not have separate documentation from
+ // their scope.
+ std::vector<Reference> ChildRecords;
+ std::vector<FunctionInfo> ChildFunctions;
+ std::vector<EnumInfo> ChildEnums;
};
// TODO: Expand to allow for documenting templating.
// Info for types.
struct EnumInfo : public SymbolInfo {
+ EnumInfo() : SymbolInfo(InfoType::IT_enum) {}
+ EnumInfo(SymbolID USR) : SymbolInfo(InfoType::IT_enum, USR) {}
+
+ void merge(EnumInfo &&I);
+
bool Scoped =
false; // Indicates whether this enum is scoped (e.g. enum class).
llvm::SmallVector<SmallString<16>, 4> Members; // List of enum members.
@@ -183,6 +271,17 @@ struct EnumInfo : public SymbolInfo {
// TODO: Add functionality to include separate markdown pages.
+// A standalone function to call to merge a vector of infos into one.
+// This assumes that all infos in the vector are of the same type, and will fail
+// if they are different.
+llvm::Expected<std::unique_ptr<Info>>
+mergeInfos(std::vector<std::unique_ptr<Info>> &Values);
+
+struct ClangDocContext {
+ tooling::ExecutionContext *ECtx;
+ bool PublicOnly;
+};
+
} // namespace doc
} // namespace clang
diff --git a/clang-doc/Serialize.cpp b/clang-doc/Serialize.cpp
index 16f0762a..873a9bdd 100644
--- a/clang-doc/Serialize.cpp
+++ b/clang-doc/Serialize.cpp
@@ -152,6 +152,21 @@ template <typename T> static std::string serialize(T &I) {
return Buffer.str().str();
}
+std::string serialize(std::unique_ptr<Info> &I) {
+ switch (I->IT) {
+ case InfoType::IT_namespace:
+ return serialize(*static_cast<NamespaceInfo *>(I.get()));
+ case InfoType::IT_record:
+ return serialize(*static_cast<RecordInfo *>(I.get()));
+ case InfoType::IT_enum:
+ return serialize(*static_cast<EnumInfo *>(I.get()));
+ case InfoType::IT_function:
+ return serialize(*static_cast<FunctionInfo *>(I.get()));
+ default:
+ return "";
+ }
+}
+
static void parseFullComment(const FullComment *C, CommentInfo &CI) {
ClangDocCommentVisitor Visitor(CI);
Visitor.parseComment(C);
@@ -171,8 +186,20 @@ static RecordDecl *getDeclForType(const QualType &T) {
return Ty->getDecl()->getDefinition();
}
-static void parseFields(RecordInfo &I, const RecordDecl *D) {
+static bool isPublic(const clang::AccessSpecifier AS,
+ const clang::Linkage Link) {
+ if (AS == clang::AccessSpecifier::AS_private)
+ return false;
+ else if ((Link == clang::Linkage::ModuleLinkage) ||
+ (Link == clang::Linkage::ExternalLinkage))
+ return true;
+ return false; // otherwise, linkage is some form of internal linkage
+}
+
+static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly) {
for (const FieldDecl *F : D->fields()) {
+ if (PublicOnly && !isPublic(F->getAccessUnsafe(), F->getLinkageInternal()))
+ continue;
if (const auto *T = getDeclForType(F->getTypeSourceInfo()->getType())) {
// Use getAccessUnsafe so that we just get the default AS_none if it's not
// valid, as opposed to an assert.
@@ -294,50 +321,106 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
parseParameters(I, D);
}
-std::string emitInfo(const NamespaceDecl *D, const FullComment *FC,
- int LineNumber, llvm::StringRef File) {
- NamespaceInfo I;
- populateInfo(I, D, FC);
- return serialize(I);
+std::unique_ptr<Info> emitInfo(const NamespaceDecl *D, const FullComment *FC,
+ int LineNumber, llvm::StringRef File,
+ bool PublicOnly) {
+ if (PublicOnly && ((D->isAnonymousNamespace()) ||
+ !isPublic(D->getAccess(), D->getLinkageInternal())))
+ return nullptr;
+ auto I = llvm::make_unique<NamespaceInfo>();
+ populateInfo(*I, D, FC);
+ return std::unique_ptr<Info>{std::move(I)};
}
-std::string emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
- llvm::StringRef File) {
- RecordInfo I;
- populateSymbolInfo(I, D, FC, LineNumber, File);
- I.TagType = D->getTagKind();
- parseFields(I, D);
+std::unique_ptr<Info> emitInfo(const RecordDecl *D, const FullComment *FC,
+ int LineNumber, llvm::StringRef File,
+ bool PublicOnly) {
+ if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal()))
+ return nullptr;
+ auto I = llvm::make_unique<RecordInfo>();
+ populateSymbolInfo(*I, D, FC, LineNumber, File);
+ I->TagType = D->getTagKind();
+ parseFields(*I, D, PublicOnly);
if (const auto *C = dyn_cast<CXXRecordDecl>(D))
- parseBases(I, C);
- return serialize(I);
-}
-
-std::string emitInfo(const FunctionDecl *D, const FullComment *FC,
- int LineNumber, llvm::StringRef File) {
- FunctionInfo I;
- populateFunctionInfo(I, D, FC, LineNumber, File);
- I.Access = clang::AccessSpecifier::AS_none;
- return serialize(I);
-}
-
-std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC,
- int LineNumber, llvm::StringRef File) {
- FunctionInfo I;
- populateFunctionInfo(I, D, FC, LineNumber, File);
- I.IsMethod = true;
- I.Parent = Reference{getUSRForDecl(D->getParent()),
- D->getParent()->getNameAsString(), InfoType::IT_record};
- I.Access = D->getAccess();
- return serialize(I);
-}
-
-std::string emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber,
- llvm::StringRef File) {
- EnumInfo I;
- populateSymbolInfo(I, D, FC, LineNumber, File);
- I.Scoped = D->isScoped();
- parseEnumerators(I, D);
- return serialize(I);
+ parseBases(*I, C);
+ return std::unique_ptr<Info>{std::move(I)};
+}
+
+std::unique_ptr<Info> emitInfo(const FunctionDecl *D, const FullComment *FC,
+ int LineNumber, llvm::StringRef File,
+ bool PublicOnly) {
+ if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal()))
+ return nullptr;
+ FunctionInfo Func;
+ populateFunctionInfo(Func, D, FC, LineNumber, File);
+ Func.Access = clang::AccessSpecifier::AS_none;
+
+ // Wrap in enclosing scope
+ auto I = llvm::make_unique<NamespaceInfo>();
+ if (!Func.Namespace.empty())
+ I->USR = Func.Namespace[0].USR;
+ else
+ I->USR = SymbolID();
+ I->ChildFunctions.push_back(std::move(Func));
+ return std::unique_ptr<Info>{std::move(I)};
+}
+
+std::unique_ptr<Info> emitInfo(const CXXMethodDecl *D, const FullComment *FC,
+ int LineNumber, llvm::StringRef File,
+ bool PublicOnly) {
+ if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal()))
+ return nullptr;
+ FunctionInfo Func;
+ populateFunctionInfo(Func, D, FC, LineNumber, File);
+ Func.IsMethod = true;
+
+ SymbolID ParentUSR = getUSRForDecl(D->getParent());
+ Func.Parent = Reference{ParentUSR, D->getParent()->getNameAsString(),
+ InfoType::IT_record};
+ Func.Access = D->getAccess();
+
+ // Wrap in enclosing scope
+ auto I = llvm::make_unique<RecordInfo>();
+ I->USR = ParentUSR;
+ I->ChildFunctions.push_back(std::move(Func));
+ return std::unique_ptr<Info>{std::move(I)};
+}
+
+std::unique_ptr<Info> emitInfo(const EnumDecl *D, const FullComment *FC,
+ int LineNumber, llvm::StringRef File,
+ bool PublicOnly) {
+ if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal()))
+ return nullptr;
+ EnumInfo Enum;
+ populateSymbolInfo(Enum, D, FC, LineNumber, File);
+ Enum.Scoped = D->isScoped();
+ parseEnumerators(Enum, D);
+
+ // Wrap in enclosing scope
+ if (!Enum.Namespace.empty()) {
+ switch (Enum.Namespace[0].RefType) {
+ case InfoType::IT_namespace: {
+ auto I = llvm::make_unique<NamespaceInfo>();
+ I->USR = Enum.Namespace[0].USR;
+ I->ChildEnums.push_back(std::move(Enum));
+ return std::unique_ptr<Info>{std::move(I)};
+ }
+ case InfoType::IT_record: {
+ auto I = llvm::make_unique<RecordInfo>();
+ I->USR = Enum.Namespace[0].USR;
+ I->ChildEnums.push_back(std::move(Enum));
+ return std::unique_ptr<Info>{std::move(I)};
+ }
+ default:
+ break;
+ }
+ }
+
+ // Put in global namespace
+ auto I = llvm::make_unique<NamespaceInfo>();
+ I->USR = SymbolID();
+ I->ChildEnums.push_back(std::move(Enum));
+ return std::unique_ptr<Info>{std::move(I)};
}
} // namespace serialize
diff --git a/clang-doc/Serialize.h b/clang-doc/Serialize.h
index 5f13798b..d89dac80 100644
--- a/clang-doc/Serialize.h
+++ b/clang-doc/Serialize.h
@@ -28,16 +28,16 @@ namespace clang {
namespace doc {
namespace serialize {
-std::string emitInfo(const NamespaceDecl *D, const FullComment *FC,
- int LineNumber, StringRef File);
-std::string emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
- StringRef File);
-std::string emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber,
- StringRef File);
-std::string emitInfo(const FunctionDecl *D, const FullComment *FC,
- int LineNumber, StringRef File);
-std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC,
- int LineNumber, StringRef File);
+std::unique_ptr<Info> emitInfo(const NamespaceDecl *D, const FullComment *FC,
+ int LineNumber, StringRef File, bool PublicOnly);
+std::unique_ptr<Info> emitInfo(const RecordDecl *D, const FullComment *FC,
+ int LineNumber, StringRef File, bool PublicOnly);
+std::unique_ptr<Info> emitInfo(const EnumDecl *D, const FullComment *FC,
+ int LineNumber, StringRef File, bool PublicOnly);
+std::unique_ptr<Info> emitInfo(const FunctionDecl *D, const FullComment *FC,
+ int LineNumber, StringRef File, bool PublicOnly);
+std::unique_ptr<Info> emitInfo(const CXXMethodDecl *D, const FullComment *FC,
+ int LineNumber, StringRef File, bool PublicOnly);
// Function to hash a given USR value for storage.
// As USRs (Unified Symbol Resolution) could be large, especially for functions
@@ -46,6 +46,8 @@ std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC,
// memory (vs storing USRs directly).
SymbolID hashUSR(llvm::StringRef USR);
+std::string serialize(std::unique_ptr<Info> &I);
+
} // namespace serialize
} // namespace doc
} // namespace clang
diff --git a/clang-doc/YAMLGenerator.cpp b/clang-doc/YAMLGenerator.cpp
new file mode 100644
index 00000000..58c1e1f3
--- /dev/null
+++ b/clang-doc/YAMLGenerator.cpp
@@ -0,0 +1,280 @@
+//===-- ClangDocYAML.cpp - ClangDoc YAML -----------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+// Implementation of the YAML generator, converting decl info into YAML output.
+//===----------------------------------------------------------------------===//
+
+#include "Generators.h"
+#include "llvm/Support/YAMLTraits.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang::doc;
+
+LLVM_YAML_IS_SEQUENCE_VECTOR(FieldTypeInfo)
+LLVM_YAML_IS_SEQUENCE_VECTOR(MemberTypeInfo)
+LLVM_YAML_IS_SEQUENCE_VECTOR(Reference)
+LLVM_YAML_IS_SEQUENCE_VECTOR(Location)
+LLVM_YAML_IS_SEQUENCE_VECTOR(CommentInfo)
+LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionInfo)
+LLVM_YAML_IS_SEQUENCE_VECTOR(EnumInfo)
+LLVM_YAML_IS_SEQUENCE_VECTOR(std::unique_ptr<CommentInfo>)
+LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::SmallString<16>)
+
+namespace llvm {
+namespace yaml {
+
+// Enumerations to YAML output.
+
+template <> struct ScalarEnumerationTraits<clang::AccessSpecifier> {
+ static void enumeration(IO &IO, clang::AccessSpecifier &Value) {
+ IO.enumCase(Value, "Public", clang::AccessSpecifier::AS_public);
+ IO.enumCase(Value, "Protected", clang::AccessSpecifier::AS_protected);
+ IO.enumCase(Value, "Private", clang::AccessSpecifier::AS_private);
+ IO.enumCase(Value, "None", clang::AccessSpecifier::AS_none);
+ }
+};
+
+template <> struct ScalarEnumerationTraits<clang::TagTypeKind> {
+ static void enumeration(IO &IO, clang::TagTypeKind &Value) {
+ IO.enumCase(Value, "Struct", clang::TagTypeKind::TTK_Struct);
+ IO.enumCase(Value, "Interface", clang::TagTypeKind::TTK_Interface);
+ IO.enumCase(Value, "Union", clang::TagTypeKind::TTK_Union);
+ IO.enumCase(Value, "Class", clang::TagTypeKind::TTK_Class);
+ IO.enumCase(Value, "Enum", clang::TagTypeKind::TTK_Enum);
+ }
+};
+
+template <> struct ScalarEnumerationTraits<InfoType> {
+ static void enumeration(IO &IO, InfoType &Value) {
+ IO.enumCase(Value, "Namespace", InfoType::IT_namespace);
+ IO.enumCase(Value, "Record", InfoType::IT_record);
+ IO.enumCase(Value, "Function", InfoType::IT_function);
+ IO.enumCase(Value, "Enum", InfoType::IT_enum);
+ IO.enumCase(Value, "Default", InfoType::IT_default);
+ }
+};
+
+// Scalars to YAML output.
+template <unsigned U> struct ScalarTraits<SmallString<U>> {
+
+ static void output(const SmallString<U> &S, void *, llvm::raw_ostream &OS) {
+ for (const auto &C : S)
+ OS << C;
+ }
+
+ static StringRef input(StringRef Scalar, void *, SmallString<U> &Value) {
+ Value.assign(Scalar.begin(), Scalar.end());
+ return StringRef();
+ }
+
+ static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
+};
+
+template <> struct ScalarTraits<std::array<unsigned char, 20>> {
+
+ static void output(const std::array<unsigned char, 20> &S, void *,
+ llvm::raw_ostream &OS) {
+ OS << toHex(toStringRef(S));
+ }
+
+ static StringRef input(StringRef Scalar, void *,
+ std::array<unsigned char, 20> &Value) {
+ if (Scalar.size() != 40)
+ return "Error: Incorrect scalar size for USR.";
+ Value = StringToSymbol(Scalar);
+ return StringRef();
+ }
+
+ static SymbolID StringToSymbol(llvm::StringRef Value) {
+ SymbolID USR;
+ std::string HexString = fromHex(Value);
+ std::copy(HexString.begin(), HexString.end(), USR.begin());
+ return SymbolID(USR);
+ }
+
+ static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
+};
+
+// Helper functions to map infos to YAML.
+
+static void TypeInfoMapping(IO &IO, TypeInfo &I) {
+ IO.mapOptional("Type", I.Type, Reference());
+}
+
+static void FieldTypeInfoMapping(IO &IO, FieldTypeInfo &I) {
+ TypeInfoMapping(IO, I);
+ IO.mapOptional("Name", I.Name, SmallString<16>());
+}
+
+static void InfoMapping(IO &IO, Info &I) {
+ IO.mapRequired("USR", I.USR);
+ IO.mapOptional("Name", I.Name, SmallString<16>());
+ IO.mapOptional("Namespace", I.Namespace, llvm::SmallVector<Reference, 4>());
+ IO.mapOptional("Description", I.Description);
+}
+
+static void SymbolInfoMapping(IO &IO, SymbolInfo &I) {
+ InfoMapping(IO, I);
+ IO.mapOptional("DefLocation", I.DefLoc, Optional<Location>());
+ IO.mapOptional("Location", I.Loc, llvm::SmallVector<Location, 2>());
+}
+
+static void CommentInfoMapping(IO &IO, CommentInfo &I) {
+ IO.mapOptional("Kind", I.Kind, SmallString<16>());
+ IO.mapOptional("Text", I.Text, SmallString<64>());
+ IO.mapOptional("Name", I.Name, SmallString<16>());
+ IO.mapOptional("Direction", I.Direction, SmallString<8>());
+ IO.mapOptional("ParamName", I.ParamName, SmallString<16>());
+ IO.mapOptional("CloseName", I.CloseName, SmallString<16>());
+ IO.mapOptional("SelfClosing", I.SelfClosing, false);
+ IO.mapOptional("Explicit", I.Explicit, false);
+ IO.mapOptional("Args", I.Args, llvm::SmallVector<SmallString<16>, 4>());
+ IO.mapOptional("AttrKeys", I.AttrKeys,
+ llvm::SmallVector<SmallString<16>, 4>());
+ IO.mapOptional("AttrValues", I.AttrValues,
+ llvm::SmallVector<SmallString<16>, 4>());
+ IO.mapOptional("Children", I.Children);
+}
+
+// Template specialization to YAML traits for Infos.
+
+template <> struct MappingTraits<Location> {
+ static void mapping(IO &IO, Location &Loc) {
+ IO.mapOptional("LineNumber", Loc.LineNumber, 0);
+ IO.mapOptional("Filename", Loc.Filename, SmallString<32>());
+ }
+};
+
+template <> struct MappingTraits<Reference> {
+ static void mapping(IO &IO, Reference &Ref) {
+ IO.mapOptional("Type", Ref.RefType, InfoType::IT_default);
+ IO.mapOptional("Name", Ref.Name, SmallString<16>());
+ IO.mapOptional("USR", Ref.USR, SymbolID());
+ }
+};
+
+template <> struct MappingTraits<TypeInfo> {
+ static void mapping(IO &IO, TypeInfo &I) { TypeInfoMapping(IO, I); }
+};
+
+template <> struct MappingTraits<FieldTypeInfo> {
+ static void mapping(IO &IO, FieldTypeInfo &I) {
+ TypeInfoMapping(IO, I);
+ IO.mapOptional("Name", I.Name, SmallString<16>());
+ }
+};
+
+template <> struct MappingTraits<MemberTypeInfo> {
+ static void mapping(IO &IO, MemberTypeInfo &I) {
+ FieldTypeInfoMapping(IO, I);
+ IO.mapOptional("Access", I.Access, clang::AccessSpecifier::AS_none);
+ }
+};
+
+template <> struct MappingTraits<NamespaceInfo> {
+ static void mapping(IO &IO, NamespaceInfo &I) {
+ InfoMapping(IO, I);
+ IO.mapOptional("ChildNamespaces", I.ChildNamespaces,
+ std::vector<Reference>());
+ IO.mapOptional("ChildRecords", I.ChildRecords, std::vector<Reference>());
+ IO.mapOptional("ChildFunctions", I.ChildFunctions);
+ IO.mapOptional("ChildEnums", I.ChildEnums);
+ }
+};
+
+template <> struct MappingTraits<RecordInfo> {
+ static void mapping(IO &IO, RecordInfo &I) {
+ SymbolInfoMapping(IO, I);
+ IO.mapOptional("TagType", I.TagType, clang::TagTypeKind::TTK_Struct);
+ IO.mapOptional("Members", I.Members);
+ IO.mapOptional("Parents", I.Parents, llvm::SmallVector<Reference, 4>());
+ IO.mapOptional("VirtualParents", I.VirtualParents,
+ llvm::SmallVector<Reference, 4>());
+ IO.mapOptional("ChildRecords", I.ChildRecords, std::vector<Reference>());
+ IO.mapOptional("ChildFunctions", I.ChildFunctions);
+ IO.mapOptional("ChildEnums", I.ChildEnums);
+ }
+};
+
+template <> struct MappingTraits<EnumInfo> {
+ static void mapping(IO &IO, EnumInfo &I) {
+ SymbolInfoMapping(IO, I);
+ IO.mapOptional("Scoped", I.Scoped, false);
+ IO.mapOptional("Members", I.Members);
+ }
+};
+
+template <> struct MappingTraits<FunctionInfo> {
+ static void mapping(IO &IO, FunctionInfo &I) {
+ SymbolInfoMapping(IO, I);
+ IO.mapOptional("IsMethod", I.IsMethod, false);
+ IO.mapOptional("Parent", I.Parent, Reference());
+ IO.mapOptional("Params", I.Params);
+ IO.mapOptional("ReturnType", I.ReturnType);
+ IO.mapOptional("Access", I.Access, clang::AccessSpecifier::AS_none);
+ }
+};
+
+template <> struct MappingTraits<CommentInfo> {
+ static void mapping(IO &IO, CommentInfo &I) { CommentInfoMapping(IO, I); }
+};
+
+template <> struct MappingTraits<std::unique_ptr<CommentInfo>> {
+ static void mapping(IO &IO, std::unique_ptr<CommentInfo> &I) {
+ if (I)
+ CommentInfoMapping(IO, *I);
+ }
+};
+
+} // end namespace yaml
+} // end namespace llvm
+
+namespace clang {
+namespace doc {
+
+/// Generator for YAML documentation.
+class YAMLGenerator : public Generator {
+public:
+ static const char *Format;
+
+ bool generateDocForInfo(Info *I, llvm::raw_ostream &OS) override;
+};
+
+const char *YAMLGenerator::Format = "yaml";
+
+bool YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS) {
+ llvm::yaml::Output InfoYAML(OS);
+ switch (I->IT) {
+ case InfoType::IT_namespace:
+ InfoYAML << *static_cast<clang::doc::NamespaceInfo *>(I);
+ break;
+ case InfoType::IT_record:
+ InfoYAML << *static_cast<clang::doc::RecordInfo *>(I);
+ break;
+ case InfoType::IT_enum:
+ InfoYAML << *static_cast<clang::doc::EnumInfo *>(I);
+ break;
+ case InfoType::IT_function:
+ InfoYAML << *static_cast<clang::doc::FunctionInfo *>(I);
+ break;
+ case InfoType::IT_default:
+ llvm::errs() << "Unexpected info type in index.\n";
+ return true;
+ }
+ return false;
+}
+
+static GeneratorRegistry::Add<YAMLGenerator> YAML(YAMLGenerator::Format,
+ "Generator for YAML output.");
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register the generator.
+volatile int YAMLGeneratorAnchorSource = 0;
+
+} // namespace doc
+} // namespace clang
diff --git a/clang-doc/gen_tests.py b/clang-doc/gen_tests.py
new file mode 100644
index 00000000..ccdb069c
--- /dev/null
+++ b/clang-doc/gen_tests.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python3
+#
+#===- gen_tests.py - clang-doc test generator ----------------*- python -*--===#
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+"""
+clang-doc test generator
+==========================
+
+Generates tests for clang-doc given a certain set of flags, a prefix for the
+test file, and a given clang-doc binary. Please check emitted tests for
+accuracy before using.
+
+To generate all current tests:
+- Generate mapper tests:
+ python gen_tests.py -flag='--dump-mapper' -flag='--doxygen' -flag='--extra-arg=-fmodules-ts' -prefix mapper
+
+- Generate reducer tests:
+ python gen_tests.py -flag='--dump-intermediate' -flag='--doxygen' -flag='--extra-arg=-fmodules-ts' -prefix bc
+
+- Generate yaml tests:
+ python gen_tests.py -flag='--format=yaml' -flag='--doxygen' -flag='--extra-arg=-fmodules-ts' -prefix yaml
+
+- Generate public decl tests:
+ python gen_tests.py -flag='--format=yaml' -flag='--doxygen' -flag='--public' -flag='--extra-arg=-fmodules-ts' -prefix public
+
+This script was written on/for Linux, and has not been tested on any other
+platform and so it may not work.
+
+"""
+
+import argparse
+import glob
+import os
+import re
+import shutil
+import subprocess
+
+RUN_CLANG_DOC = """
+// RUN: clang-doc {0} -p %t %t/test.cpp -output=%t/docs
+"""
+RUN = """
+// RUN: {0} %t/{1} | FileCheck %s --check-prefix CHECK-{2}
+"""
+
+CHECK = '// CHECK-{0}: '
+
+CHECK_NEXT = '// CHECK-{0}-NEXT: '
+
+BITCODE_USR = '<USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>'
+BITCODE_USR_REGEX = r'<USR abbrevid=4 op0=20 op1=[0-9]+ op2=[0-9]+ op3=[0-9]+ op4=[0-9]+ op5=[0-9]+ op6=[0-9]+ op7=[0-9]+ op8=[0-9]+ op9=[0-9]+ op10=[0-9]+ op11=[0-9]+ op12=[0-9]+ op13=[0-9]+ op14=[0-9]+ op15=[0-9]+ op16=[0-9]+ op17=[0-9]+ op18=[0-9]+ op19=[0-9]+ op20=[0-9]+/>'
+YAML_USR = "USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'"
+YAML_USR_REGEX = r"USR: '[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]'"
+
+def clear_test_prefix_files(prefix, tests_path):
+ if os.path.isdir(tests_path):
+ for root, dirs, files in os.walk(tests_path):
+ for filename in files:
+ if filename.startswith(prefix):
+ os.remove(os.path.join(root, filename))
+
+
+def copy_to_test_file(test_case_path, test_cases_path):
+ # Copy file to 'test.cpp' to preserve file-dependent USRs
+ test_file = os.path.join(test_cases_path, 'test.cpp')
+ shutil.copyfile(test_case_path, test_file)
+ return test_file
+
+
+def run_clang_doc(args, out_dir, test_file):
+ # Run clang-doc.
+ current_cmd = [args.clangdoc]
+ current_cmd.extend(args.flags)
+ current_cmd.append('--output=' + out_dir)
+ current_cmd.append(test_file)
+ print('Running ' + ' '.join(current_cmd))
+ return_code = subprocess.call(current_cmd)
+ if return_code:
+ return 1
+ return 0
+
+
+def get_test_case_code(test_case_path, flags):
+ # Get the test case code
+ code = ''
+ with open(test_case_path, 'r') as code_file:
+ code = code_file.read()
+
+ code += RUN_CLANG_DOC.format(flags)
+ return code
+
+
+def get_output(root, out_file, case_out_path, flags, checkname, bcanalyzer):
+ output = ''
+ run_cmd = ''
+ if '--dump-mapper' in flags or '--dump-intermediate' in flags:
+ # Run llvm-bcanalyzer
+ output = subprocess.check_output(
+ [bcanalyzer, '--dump',
+ os.path.join(root, out_file)])
+ output = output[:output.find('Summary of ')].rstrip()
+ run_cmd = RUN.format('llvm-bcanalyzer --dump',
+ os.path.join('docs', 'bc', out_file), checkname)
+ else:
+ # Run cat
+ output = subprocess.check_output(['cat', os.path.join(root, out_file)])
+ run_cmd = RUN.format(
+ 'cat',
+ os.path.join('docs', os.path.relpath(root, case_out_path),
+ out_file), checkname)
+
+ # Format output.
+ output = output.replace('blob data = \'test\'', 'blob data = \'{{.*}}\'')
+ output = re.sub(YAML_USR_REGEX, YAML_USR, output)
+ output = re.sub(BITCODE_USR_REGEX, BITCODE_USR, output)
+ output = CHECK.format(checkname) + output.rstrip()
+ output = run_cmd + output.replace('\n',
+ '\n' + CHECK_NEXT.format(checkname))
+
+ return output + '\n'
+
+
+def main():
+ parser = argparse.ArgumentParser(description='Generate clang-doc tests.')
+ parser.add_argument(
+ '-flag',
+ action='append',
+ default=[],
+ dest='flags',
+ help='Flags to pass to clang-doc.')
+ parser.add_argument(
+ '-prefix',
+ type=str,
+ default='',
+ dest='prefix',
+ help='Prefix for this test group.')
+ parser.add_argument(
+ '-clang-doc-binary',
+ dest='clangdoc',
+ metavar="PATH",
+ default='clang-doc',
+ help='path to clang-doc binary')
+ parser.add_argument(
+ '-llvm-bcanalyzer-binary',
+ dest='bcanalyzer',
+ metavar="PATH",
+ default='llvm-bcanalyzer',
+ help='path to llvm-bcanalyzer binary')
+ args = parser.parse_args()
+
+ flags = ' '.join(args.flags)
+
+ clang_doc_path = os.path.dirname(__file__)
+ tests_path = os.path.join(clang_doc_path, '..', 'test', 'clang-doc')
+ test_cases_path = os.path.join(tests_path, 'test_cases')
+
+ clear_test_prefix_files(args.prefix, tests_path)
+
+ for test_case_path in glob.glob(os.path.join(test_cases_path, '*')):
+ if test_case_path.endswith(
+ 'compile_flags.txt') or test_case_path.endswith(
+ 'compile_commands.json'):
+ continue
+
+ # Name of this test case
+ case_name = os.path.basename(test_case_path).split('.')[0]
+
+ test_file = copy_to_test_file(test_case_path, test_cases_path)
+ out_dir = os.path.join(test_cases_path, case_name)
+
+ if run_clang_doc(args, out_dir, test_file):
+ return 1
+
+ # Retrieve output and format as FileCheck tests
+ all_output = ''
+ num_outputs = 0
+ for root, dirs, files in os.walk(out_dir):
+ for out_file in files:
+ # Make the file check the first 3 letters (there's a very small chance
+ # that this will collide, but the fix is to simply change the decl name)
+ usr = os.path.basename(out_file).split('.')
+ # If the usr is less than 2, this isn't one of the test files.
+ if len(usr) < 2:
+ continue
+ all_output += get_output(root, out_file, out_dir, args.flags,
+ num_outputs, args.bcanalyzer)
+ num_outputs += 1
+
+ # Add test case code to test
+ all_output = get_test_case_code(test_case_path,
+ flags) + '\n' + all_output
+
+ # Write to test case file in /test.
+ test_out_path = os.path.join(
+ tests_path, args.prefix + '-' + os.path.basename(test_case_path))
+ with open(test_out_path, 'w+') as o:
+ o.write(all_output)
+
+ # Clean up
+ shutil.rmtree(out_dir)
+ os.remove(test_file)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/clang-doc/tool/ClangDocMain.cpp b/clang-doc/tool/ClangDocMain.cpp
index 51c3aa9d..6e4a92d7 100644
--- a/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-doc/tool/ClangDocMain.cpp
@@ -18,7 +18,11 @@
//
//===----------------------------------------------------------------------===//
+#include "BitcodeReader.h"
+#include "BitcodeWriter.h"
#include "ClangDoc.h"
+#include "Generators.h"
+#include "Representation.h"
#include "clang/AST/AST.h"
#include "clang/AST/Decl.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
@@ -30,6 +34,7 @@
#include "clang/Tooling/StandaloneExecution.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/APFloat.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
@@ -54,15 +59,146 @@ static llvm::cl::opt<bool>
llvm::cl::desc("Dump mapper results to bitcode file."),
llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
+static llvm::cl::opt<bool> DumpIntermediateResult(
+ "dump-intermediate",
+ llvm::cl::desc("Dump intermediate results to bitcode file."),
+ llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
+
+static llvm::cl::opt<bool>
+ PublicOnly("public", llvm::cl::desc("Document only public declarations."),
+ llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
+
+enum OutputFormatTy {
+ yaml,
+};
+
+static llvm::cl::opt<OutputFormatTy> FormatEnum(
+ "format", llvm::cl::desc("Format for outputted docs."),
+ llvm::cl::values(clEnumVal(yaml, "Documentation in YAML format.")),
+ llvm::cl::init(yaml), llvm::cl::cat(ClangDocCategory));
+
static llvm::cl::opt<bool> DoxygenOnly(
"doxygen",
llvm::cl::desc("Use only doxygen-style comments to generate docs."),
llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
+bool CreateDirectory(const Twine &DirName, bool ClearDirectory = false) {
+ std::error_code OK;
+ llvm::SmallString<128> DocsRootPath;
+ if (ClearDirectory) {
+ std::error_code RemoveStatus = llvm::sys::fs::remove_directories(DirName);
+ if (RemoveStatus != OK) {
+ llvm::errs() << "Unable to remove existing documentation directory for "
+ << DirName << ".\n";
+ return true;
+ }
+ }
+ std::error_code DirectoryStatus = llvm::sys::fs::create_directories(DirName);
+ if (DirectoryStatus != OK) {
+ llvm::errs() << "Unable to create documentation directories.\n";
+ return true;
+ }
+ return false;
+}
+
+bool DumpResultToFile(const Twine &DirName, const Twine &FileName,
+ StringRef Buffer, bool ClearDirectory = false) {
+ std::error_code OK;
+ llvm::SmallString<128> IRRootPath;
+ llvm::sys::path::native(OutDirectory, IRRootPath);
+ llvm::sys::path::append(IRRootPath, DirName);
+ if (CreateDirectory(IRRootPath, ClearDirectory))
+ return true;
+ llvm::sys::path::append(IRRootPath, FileName);
+ std::error_code OutErrorInfo;
+ llvm::raw_fd_ostream OS(IRRootPath, OutErrorInfo, llvm::sys::fs::F_None);
+ if (OutErrorInfo != OK) {
+ llvm::errs() << "Error opening documentation file.\n";
+ return true;
+ }
+ OS << Buffer;
+ OS.close();
+ return false;
+}
+
+// A function to extract the appropriate path name for a given info's
+// documentation. The path returned is a composite of the parent namespaces as
+// directories plus the decl name as the filename.
+//
+// Example: Given the below, the <ext> path for class C will be <
+// root>/A/B/C.<ext>
+//
+// namespace A {
+// namesapce B {
+//
+// class C {};
+//
+// }
+// }
+llvm::Expected<llvm::SmallString<128>>
+getInfoOutputFile(StringRef Root,
+ llvm::SmallVectorImpl<doc::Reference> &Namespaces,
+ StringRef Name, StringRef Ext) {
+ std::error_code OK;
+ llvm::SmallString<128> Path;
+ llvm::sys::path::native(Root, Path);
+ for (auto R = Namespaces.rbegin(), E = Namespaces.rend(); R != E; ++R)
+ llvm::sys::path::append(Path, R->Name);
+
+ if (CreateDirectory(Path))
+ return llvm::make_error<llvm::StringError>("Unable to create directory.\n",
+ llvm::inconvertibleErrorCode());
+
+ if (Name.empty())
+ Name = "GlobalNamespace";
+ llvm::sys::path::append(Path, Name + Ext);
+ return Path;
+}
+
+std::string getFormatString(OutputFormatTy Ty) {
+ switch (Ty) {
+ case yaml:
+ return "yaml";
+ }
+ llvm_unreachable("Unknown OutputFormatTy");
+}
+
+// Iterate through tool results and build string map of info vectors from the
+// encoded bitstreams.
+bool bitcodeResultsToInfos(
+ tooling::ToolResults &Results,
+ llvm::StringMap<std::vector<std::unique_ptr<doc::Info>>> &Output) {
+ bool Err = false;
+ Results.forEachResult([&](StringRef Key, StringRef Value) {
+ llvm::BitstreamCursor Stream(Value);
+ doc::ClangDocBitcodeReader Reader(Stream);
+ auto Infos = Reader.readBitcode();
+ if (!Infos) {
+ llvm::errs() << toString(Infos.takeError()) << "\n";
+ Err = true;
+ return;
+ }
+ for (auto &I : Infos.get()) {
+ auto R =
+ Output.try_emplace(Key, std::vector<std::unique_ptr<doc::Info>>());
+ R.first->second.emplace_back(std::move(I));
+ }
+ });
+ return Err;
+}
+
int main(int argc, const char **argv) {
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
std::error_code OK;
+ // Fail early if an invalid format was provided.
+ std::string Format = getFormatString(FormatEnum);
+ auto G = doc::findGeneratorByName(Format);
+ if (!G) {
+ llvm::errs() << toString(G.takeError()) << "\n";
+ return 1;
+ }
+
auto Exec = clang::tooling::createExecutorFromCommandLineArgs(
argc, argv, ClangDocCategory);
@@ -80,34 +216,69 @@ int main(int argc, const char **argv) {
// Mapping phase
llvm::outs() << "Mapping decls...\n";
- auto Err = Exec->get()->execute(doc::newMapperActionFactory(
- Exec->get()->getExecutionContext()),
- ArgAdjuster);
- if (Err)
+ clang::doc::ClangDocContext CDCtx = {Exec->get()->getExecutionContext(),
+ PublicOnly};
+ auto Err =
+ Exec->get()->execute(doc::newMapperActionFactory(CDCtx), ArgAdjuster);
+ if (Err) {
llvm::errs() << toString(std::move(Err)) << "\n";
+ return 1;
+ }
if (DumpMapperResult) {
- Exec->get()->getToolResults()->forEachResult([&](StringRef Key,
- StringRef Value) {
- SmallString<128> IRRootPath;
- llvm::sys::path::native(OutDirectory, IRRootPath);
- llvm::sys::path::append(IRRootPath, "bc");
- std::error_code DirectoryStatus =
- llvm::sys::fs::create_directories(IRRootPath);
- if (DirectoryStatus != OK) {
- llvm::errs() << "Unable to create documentation directories.\n";
- return;
- }
- llvm::sys::path::append(IRRootPath, Key + ".bc");
- std::error_code OutErrorInfo;
- llvm::raw_fd_ostream OS(IRRootPath, OutErrorInfo, llvm::sys::fs::F_None);
- if (OutErrorInfo != OK) {
- llvm::errs() << "Error opening documentation file.\n";
- return;
- }
- OS << Value;
- OS.close();
- });
+ bool Err = false;
+ Exec->get()->getToolResults()->forEachResult(
+ [&](StringRef Key, StringRef Value) {
+ Err = DumpResultToFile("bc", Key + ".bc", Value);
+ });
+ if (Err)
+ llvm::errs() << "Error dumping map results.\n";
+ return Err;
+ }
+
+ // Collect values into output by key.
+ // In ToolResults, the Key is the hashed USR and the value is the
+ // bitcode-encoded representation of the Info object.
+ llvm::outs() << "Collecting infos...\n";
+ llvm::StringMap<std::vector<std::unique_ptr<doc::Info>>> USRToInfos;
+ if (bitcodeResultsToInfos(*Exec->get()->getToolResults(), USRToInfos))
+ return 1;
+
+ // First reducing phase (reduce all decls into one info per decl).
+ llvm::outs() << "Reducing " << USRToInfos.size() << " infos...\n";
+ for (auto &Group : USRToInfos) {
+ auto Reduced = doc::mergeInfos(Group.getValue());
+ if (!Reduced) {
+ llvm::errs() << llvm::toString(Reduced.takeError());
+ continue;
+ }
+
+ if (DumpIntermediateResult) {
+ SmallString<4096> Buffer;
+ llvm::BitstreamWriter Stream(Buffer);
+ doc::ClangDocBitcodeWriter Writer(Stream);
+ Writer.dispatchInfoForWrite(Reduced.get().get());
+ if (DumpResultToFile("bc", Group.getKey() + ".bc", Buffer))
+ llvm::errs() << "Error dumping to bitcode.\n";
+ continue;
+ }
+ doc::Info *I = Reduced.get().get();
+
+ auto InfoPath =
+ getInfoOutputFile(OutDirectory, I->Namespace, I->Name, "." + Format);
+ if (!InfoPath) {
+ llvm::errs() << toString(InfoPath.takeError()) << "\n";
+ continue;
+ }
+ std::error_code FileErr;
+ llvm::raw_fd_ostream InfoOS(InfoPath.get(), FileErr, llvm::sys::fs::F_None);
+ if (FileErr != OK) {
+ llvm::errs() << "Error opening info file: " << FileErr.message() << "\n";
+ continue;
+ }
+
+ if (G->get()->generateDocForInfo(I, InfoOS))
+ llvm::errs() << "Unable to generate docs for info.\n";
}
return 0;
diff --git a/clang-move/ClangMove.cpp b/clang-move/ClangMove.cpp
index e126852e..eac6fcd0 100644
--- a/clang-move/ClangMove.cpp
+++ b/clang-move/ClangMove.cpp
@@ -117,7 +117,7 @@ AST_POLYMORPHIC_MATCHER_P(isExpansionInFile,
AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc),
std::string, AbsoluteFilePath) {
auto &SourceManager = Finder->getASTContext().getSourceManager();
- auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getLocStart());
+ auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getBeginLoc());
if (ExpansionLoc.isInvalid())
return false;
auto FileEntry =
@@ -292,7 +292,7 @@ getLocForEndOfDecl(const clang::Decl *D,
// If the expansion range is a character range, this is the location of
// the first character past the end. Otherwise it's the location of the
// first character in the final token in the range.
- auto EndExpansionLoc = SM.getExpansionRange(D->getLocEnd()).getEnd();
+ auto EndExpansionLoc = SM.getExpansionRange(D->getEndLoc()).getEnd();
std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(EndExpansionLoc);
// Try to load the file buffer.
bool InvalidTemp = false;
@@ -323,16 +323,16 @@ clang::CharSourceRange
getFullRange(const clang::Decl *D,
const clang::LangOptions &options = clang::LangOptions()) {
const auto &SM = D->getASTContext().getSourceManager();
- clang::SourceRange Full(SM.getExpansionLoc(D->getLocStart()),
+ clang::SourceRange Full(SM.getExpansionLoc(D->getBeginLoc()),
getLocForEndOfDecl(D));
// Expand to comments that are associated with the Decl.
if (const auto *Comment = D->getASTContext().getRawCommentForDeclNoCache(D)) {
- if (SM.isBeforeInTranslationUnit(Full.getEnd(), Comment->getLocEnd()))
- Full.setEnd(Comment->getLocEnd());
+ if (SM.isBeforeInTranslationUnit(Full.getEnd(), Comment->getEndLoc()))
+ Full.setEnd(Comment->getEndLoc());
// FIXME: Don't delete a preceding comment, if there are no other entities
// it could refer to.
- if (SM.isBeforeInTranslationUnit(Comment->getLocStart(), Full.getBegin()))
- Full.setBegin(Comment->getLocStart());
+ if (SM.isBeforeInTranslationUnit(Comment->getBeginLoc(), Full.getBegin()))
+ Full.setBegin(Comment->getBeginLoc());
}
return clang::CharSourceRange::getCharRange(Full);
@@ -351,7 +351,7 @@ bool isInHeaderFile(const clang::Decl *D,
const auto &SM = D->getASTContext().getSourceManager();
if (OldHeader.empty())
return false;
- auto ExpansionLoc = SM.getExpansionLoc(D->getLocStart());
+ auto ExpansionLoc = SM.getExpansionLoc(D->getBeginLoc());
if (ExpansionLoc.isInvalid())
return false;
@@ -795,7 +795,8 @@ void ClangMoveTool::removeDeclsInOldFiles() {
// Ignore replacements for new.h/cc.
if (SI == FilePathToFileID.end()) continue;
llvm::StringRef Code = SM.getBufferData(SI->second);
- auto Style = format::getStyle("file", FilePath, Context->FallbackStyle);
+ auto Style = format::getStyle(format::DefaultFormatStyle, FilePath,
+ Context->FallbackStyle);
if (!Style) {
llvm::errs() << llvm::toString(Style.takeError()) << "\n";
continue;
diff --git a/clang-move/tool/ClangMoveMain.cpp b/clang-move/tool/ClangMoveMain.cpp
index 2c898420..e5fee263 100644
--- a/clang-move/tool/ClangMoveMain.cpp
+++ b/clang-move/tool/ClangMoveMain.cpp
@@ -30,8 +30,8 @@ namespace {
std::error_code CreateNewFile(const llvm::Twine &path) {
int fd = 0;
- if (std::error_code ec =
- llvm::sys::fs::openFileForWrite(path, fd, llvm::sys::fs::F_Text))
+ if (std::error_code ec = llvm::sys::fs::openFileForWrite(
+ path, fd, llvm::sys::fs::CD_CreateAlways, llvm::sys::fs::F_Text))
return ec;
return llvm::sys::Process::SafelyCloseFileDescriptor(fd);
diff --git a/clang-tidy-vs/ClangTidy/license.txt b/clang-tidy-vs/ClangTidy/license.txt
index b452ca2e..547f6a48 100644
--- a/clang-tidy-vs/ClangTidy/license.txt
+++ b/clang-tidy-vs/ClangTidy/license.txt
@@ -4,7 +4,7 @@ LLVM Release License
University of Illinois/NCSA
Open Source License
-Copyright (c) 2007-2016 University of Illinois at Urbana-Champaign.
+Copyright (c) 2007-2018 University of Illinois at Urbana-Champaign.
All rights reserved.
Developed by:
diff --git a/clang-tidy/ClangTidy.cpp b/clang-tidy/ClangTidy.cpp
index ff205edd..f497fd74 100644
--- a/clang-tidy/ClangTidy.cpp
+++ b/clang-tidy/ClangTidy.cpp
@@ -363,7 +363,8 @@ ClangTidyASTConsumerFactory::CreateASTConsumer(
std::unique_ptr<ClangTidyProfiling> Profiling;
if (Context.getEnableProfiling()) {
- Profiling = llvm::make_unique<ClangTidyProfiling>();
+ Profiling = llvm::make_unique<ClangTidyProfiling>(
+ Context.getProfileStorageParams());
FinderOptions.CheckProfiling.emplace(Profiling->Records);
}
@@ -492,7 +493,7 @@ void runClangTidy(clang::tidy::ClangTidyContext &Context,
const CompilationDatabase &Compilations,
ArrayRef<std::string> InputFiles,
llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS,
- bool EnableCheckProfile) {
+ bool EnableCheckProfile, llvm::StringRef StoreCheckProfile) {
ClangTool Tool(Compilations, InputFiles,
std::make_shared<PCHContainerOperations>(), BaseFS);
@@ -533,6 +534,7 @@ void runClangTidy(clang::tidy::ClangTidyContext &Context,
Tool.appendArgumentsAdjuster(PerFileExtraArgumentsInserter);
Tool.appendArgumentsAdjuster(PluginArgumentsRemover);
Context.setEnableProfiling(EnableCheckProfile);
+ Context.setProfileStoragePrefix(StoreCheckProfile);
ClangTidyDiagnosticConsumer DiagConsumer(Context);
diff --git a/clang-tidy/ClangTidy.h b/clang-tidy/ClangTidy.h
index 4ee38a04..0ea9a701 100644
--- a/clang-tidy/ClangTidy.h
+++ b/clang-tidy/ClangTidy.h
@@ -227,11 +227,15 @@ getCheckOptions(const ClangTidyOptions &Options,
///
/// \param EnableCheckProfile If provided, it enables check profile collection
/// in MatchFinder, and will contain the result of the profile.
+/// \param StoreCheckProfile If provided, and EnableCheckProfile is true,
+/// the profile will not be output to stderr, but will instead be stored
+/// as a JSON file in the specified directory.
void runClangTidy(clang::tidy::ClangTidyContext &Context,
const tooling::CompilationDatabase &Compilations,
ArrayRef<std::string> InputFiles,
llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS,
- bool EnableCheckProfile = false);
+ bool EnableCheckProfile = false,
+ llvm::StringRef StoreCheckProfile = StringRef());
// FIXME: This interface will need to be significantly extended to be useful.
// FIXME: Implement confidence levels for displaying/fixing errors.
diff --git a/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tidy/ClangTidyDiagnosticConsumer.cpp
index a29deb49..a1c76fba 100644
--- a/clang-tidy/ClangTidyDiagnosticConsumer.cpp
+++ b/clang-tidy/ClangTidyDiagnosticConsumer.cpp
@@ -237,6 +237,18 @@ ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const {
void ClangTidyContext::setEnableProfiling(bool P) { Profile = P; }
+void ClangTidyContext::setProfileStoragePrefix(StringRef Prefix) {
+ ProfilePrefix = Prefix;
+}
+
+llvm::Optional<ClangTidyProfiling::StorageParams>
+ClangTidyContext::getProfileStorageParams() const {
+ if (ProfilePrefix.empty())
+ return llvm::None;
+
+ return ClangTidyProfiling::StorageParams(ProfilePrefix, CurrentFile);
+}
+
bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const {
assert(CheckFilter != nullptr);
return CheckFilter->contains(CheckName);
diff --git a/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tidy/ClangTidyDiagnosticConsumer.h
index d640de4f..ae25013d 100644
--- a/clang-tidy/ClangTidyDiagnosticConsumer.h
+++ b/clang-tidy/ClangTidyDiagnosticConsumer.h
@@ -11,6 +11,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H
#include "ClangTidyOptions.h"
+#include "ClangTidyProfiling.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Tooling/Core/Diagnostic.h"
@@ -169,6 +170,11 @@ public:
void setEnableProfiling(bool Profile);
bool getEnableProfiling() const { return Profile; }
+ /// \brief Control storage of profile date.
+ void setProfileStoragePrefix(StringRef ProfilePrefix);
+ llvm::Optional<ClangTidyProfiling::StorageParams>
+ getProfileStorageParams() const;
+
/// \brief Should be called when starting to process new translation unit.
void setCurrentBuildDirectory(StringRef BuildDirectory) {
CurrentBuildDirectory = BuildDirectory;
@@ -216,6 +222,7 @@ private:
llvm::DenseMap<unsigned, std::string> CheckNamesByDiagnosticID;
bool Profile;
+ std::string ProfilePrefix;
bool AllowEnablingAnalyzerAlphaCheckers;
};
diff --git a/clang-tidy/ClangTidyProfiling.cpp b/clang-tidy/ClangTidyProfiling.cpp
index d332942f..fc0a9697 100644
--- a/clang-tidy/ClangTidyProfiling.cpp
+++ b/clang-tidy/ClangTidyProfiling.cpp
@@ -9,56 +9,84 @@
#include "ClangTidyProfiling.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/YAMLTraits.h"
+#include "llvm/Support/raw_ostream.h"
+#include <system_error>
+#include <utility>
#define DEBUG_TYPE "clang-tidy-profiling"
namespace clang {
namespace tidy {
-void ClangTidyProfiling::preprocess() {
- // Convert from a insertion-friendly map to sort-friendly vector.
- Timers.clear();
- Timers.reserve(Records.size());
- for (const auto &P : Records) {
- Timers.emplace_back(P.getValue(), P.getKey());
- Total += P.getValue();
- }
- assert(Timers.size() == Records.size() && "Size mismatch after processing");
+ClangTidyProfiling::StorageParams::StorageParams(llvm::StringRef ProfilePrefix,
+ llvm::StringRef SourceFile)
+ : Timestamp(std::chrono::system_clock::now()), SourceFilename(SourceFile) {
+ llvm::SmallString<32> TimestampStr;
+ llvm::raw_svector_ostream OS(TimestampStr);
+ llvm::format_provider<decltype(Timestamp)>::format(Timestamp, OS,
+ "%Y%m%d%H%M%S%N");
+
+ llvm::SmallString<256> FinalPrefix(ProfilePrefix);
+ llvm::sys::path::append(FinalPrefix, TimestampStr);
- // We want the measurements to be sorted by decreasing time spent.
- llvm::sort(Timers.begin(), Timers.end());
+ // So the full output name is: /ProfilePrefix/timestamp-inputfilename.json
+ StoreFilename = llvm::Twine(FinalPrefix + "-" +
+ llvm::sys::path::filename(SourceFile) + ".json")
+ .str();
}
-void ClangTidyProfiling::printProfileData(llvm::raw_ostream &OS) const {
- std::string Line = "===" + std::string(73, '-') + "===\n";
- OS << Line;
-
- if (Total.getUserTime())
- OS << " ---User Time---";
- if (Total.getSystemTime())
- OS << " --System Time--";
- if (Total.getProcessTime())
- OS << " --User+System--";
- OS << " ---Wall Time---";
- if (Total.getMemUsed())
- OS << " ---Mem---";
- OS << " --- Name ---\n";
-
- // Loop through all of the timing data, printing it out.
- for (auto I = Timers.rbegin(), E = Timers.rend(); I != E; ++I) {
- I->first.print(Total, OS);
- OS << I->second << '\n';
- }
+void ClangTidyProfiling::printUserFriendlyTable(llvm::raw_ostream &OS) {
+ TG->print(OS);
+ OS.flush();
+}
- Total.print(Total, OS);
- OS << "Total\n";
- OS << Line << "\n";
+void ClangTidyProfiling::printAsJSON(llvm::raw_ostream &OS) {
+ OS << "{\n";
+ OS << "\"file\": \"" << Storage->SourceFilename << "\",\n";
+ OS << "\"timestamp\": \"" << Storage->Timestamp << "\",\n";
+ OS << "\"profile\": {\n";
+ TG->printJSONValues(OS, "");
+ OS << "\n}\n";
+ OS << "}\n";
OS.flush();
}
+void ClangTidyProfiling::storeProfileData() {
+ assert(Storage.hasValue() && "We should have a filename.");
+
+ llvm::SmallString<256> OutputDirectory(Storage->StoreFilename);
+ llvm::sys::path::remove_filename(OutputDirectory);
+ if (std::error_code EC = llvm::sys::fs::create_directories(OutputDirectory)) {
+ llvm::errs() << "Unable to create output directory '" << OutputDirectory
+ << "': " << EC.message() << "\n";
+ return;
+ }
+
+ std::error_code EC;
+ llvm::raw_fd_ostream OS(Storage->StoreFilename, EC, llvm::sys::fs::F_None);
+ if (EC) {
+ llvm::errs() << "Error opening output file '" << Storage->StoreFilename
+ << "': " << EC.message() << "\n";
+ return;
+ }
+
+ printAsJSON(OS);
+}
+
+ClangTidyProfiling::ClangTidyProfiling(llvm::Optional<StorageParams> Storage)
+ : Storage(std::move(Storage)) {}
+
ClangTidyProfiling::~ClangTidyProfiling() {
- preprocess();
- printProfileData(llvm::errs());
+ TG.emplace("clang-tidy", "clang-tidy checks profiling", Records);
+
+ if (!Storage.hasValue())
+ printUserFriendlyTable(llvm::errs());
+ else
+ storeProfileData();
}
} // namespace tidy
diff --git a/clang-tidy/ClangTidyProfiling.h b/clang-tidy/ClangTidyProfiling.h
index 002a1489..9d86b8e9 100644
--- a/clang-tidy/ClangTidyProfiling.h
+++ b/clang-tidy/ClangTidyProfiling.h
@@ -10,10 +10,12 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYPROFILING_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYPROFILING_H
+#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringMap.h"
-#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Chrono.h"
#include "llvm/Support/Timer.h"
#include "llvm/Support/raw_ostream.h"
+#include <string>
#include <utility>
#include <vector>
@@ -21,17 +23,34 @@ namespace clang {
namespace tidy {
class ClangTidyProfiling {
- // Time is first to allow for sorting by it.
- std::vector<std::pair<llvm::TimeRecord, llvm::StringRef>> Timers;
- llvm::TimeRecord Total;
+public:
+ struct StorageParams {
+ llvm::sys::TimePoint<> Timestamp;
+ std::string SourceFilename;
+ std::string StoreFilename;
+
+ StorageParams() = default;
+
+ StorageParams(llvm::StringRef ProfilePrefix, llvm::StringRef SourceFile);
+ };
+
+private:
+ llvm::Optional<llvm::TimerGroup> TG;
- void preprocess();
+ llvm::Optional<StorageParams> Storage;
- void printProfileData(llvm::raw_ostream &OS) const;
+ void printUserFriendlyTable(llvm::raw_ostream &OS);
+ void printAsJSON(llvm::raw_ostream &OS);
+
+ void storeProfileData();
public:
llvm::StringMap<llvm::TimeRecord> Records;
+ ClangTidyProfiling() = default;
+
+ ClangTidyProfiling(llvm::Optional<StorageParams> Storage);
+
~ClangTidyProfiling();
};
diff --git a/clang-tidy/abseil/StringFindStartswithCheck.cpp b/clang-tidy/abseil/StringFindStartswithCheck.cpp
index 44df2646..2701c24b 100644
--- a/clang-tidy/abseil/StringFindStartswithCheck.cpp
+++ b/clang-tidy/abseil/StringFindStartswithCheck.cpp
@@ -36,10 +36,13 @@ void StringFindStartswithCheck::registerMatchers(MatchFinder *Finder) {
auto ZeroLiteral = integerLiteral(equals(0));
auto StringClassMatcher = cxxRecordDecl(hasAnyName(SmallVector<StringRef, 4>(
StringLikeClasses.begin(), StringLikeClasses.end())));
+ auto StringType = hasUnqualifiedDesugaredType(
+ recordType(hasDeclaration(StringClassMatcher)));
auto StringFind = cxxMemberCallExpr(
// .find()-call on a string...
- callee(cxxMethodDecl(hasName("find"), ofClass(StringClassMatcher))),
+ callee(cxxMethodDecl(hasName("find"))),
+ on(hasType(StringType)),
// ... with some search expression ...
hasArgument(0, expr().bind("needle")),
// ... and either "0" as second argument or the default argument (also 0).
@@ -68,7 +71,7 @@ void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) {
->getImplicitObjectArgument();
assert(Haystack != nullptr);
- if (ComparisonExpr->getLocStart().isMacroID())
+ if (ComparisonExpr->getBeginLoc().isMacroID())
return;
// Get the source code blocks (as characters) for both the string object
@@ -91,7 +94,7 @@ void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) {
// Create the warning message and a FixIt hint replacing the original expr.
auto Diagnostic =
- diag(ComparisonExpr->getLocStart(),
+ diag(ComparisonExpr->getBeginLoc(),
(StringRef("use ") + StartswithStr + " instead of find() " +
ComparisonExpr->getOpcodeStr() + " 0")
.str());
@@ -104,7 +107,7 @@ void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) {
// Create a preprocessor #include FixIt hint (CreateIncludeInsertion checks
// whether this already exists).
auto IncludeHint = IncludeInserter->CreateIncludeInsertion(
- Source.getFileID(ComparisonExpr->getLocStart()), AbseilStringsMatchHeader,
+ Source.getFileID(ComparisonExpr->getBeginLoc()), AbseilStringsMatchHeader,
false);
if (IncludeHint) {
Diagnostic << *IncludeHint;
diff --git a/clang-tidy/android/CloexecCheck.cpp b/clang-tidy/android/CloexecCheck.cpp
index 8473f1ab..4cc0f614 100644
--- a/clang-tidy/android/CloexecCheck.cpp
+++ b/clang-tidy/android/CloexecCheck.cpp
@@ -25,7 +25,7 @@ namespace {
// end of the string. Else, add <Mode>.
std::string buildFixMsgForStringFlag(const Expr *Arg, const SourceManager &SM,
const LangOptions &LangOpts, char Mode) {
- if (Arg->getLocStart().isMacroID())
+ if (Arg->getBeginLoc().isMacroID())
return (Lexer::getSourceText(
CharSourceRange::getTokenRange(Arg->getSourceRange()), SM,
LangOpts) +
@@ -64,7 +64,7 @@ void CloexecCheck::insertMacroFlag(const MatchFinder::MatchResult &Result,
return;
SourceLocation EndLoc =
- Lexer::getLocForEndOfToken(SM.getFileLoc(FlagArg->getLocEnd()), 0, SM,
+ Lexer::getLocForEndOfToken(SM.getFileLoc(FlagArg->getEndLoc()), 0, SM,
Result.Context->getLangOpts());
diag(EndLoc, "%0 should use %1 where possible")
@@ -75,7 +75,7 @@ void CloexecCheck::insertMacroFlag(const MatchFinder::MatchResult &Result,
void CloexecCheck::replaceFunc(const MatchFinder::MatchResult &Result,
StringRef WarningMsg, StringRef FixMsg) {
const auto *MatchedCall = Result.Nodes.getNodeAs<CallExpr>(FuncBindingStr);
- diag(MatchedCall->getLocStart(), WarningMsg)
+ diag(MatchedCall->getBeginLoc(), WarningMsg)
<< FixItHint::CreateReplacement(MatchedCall->getSourceRange(), FixMsg);
}
@@ -94,7 +94,7 @@ void CloexecCheck::insertStringFlag(
const std::string &ReplacementText = buildFixMsgForStringFlag(
ModeArg, *Result.SourceManager, Result.Context->getLangOpts(), Mode);
- diag(ModeArg->getLocStart(), "use %0 mode '%1' to set O_CLOEXEC")
+ diag(ModeArg->getBeginLoc(), "use %0 mode '%1' to set O_CLOEXEC")
<< FD << std::string(1, Mode)
<< FixItHint::CreateReplacement(ModeArg->getSourceRange(),
ReplacementText);
diff --git a/clang-tidy/android/ComparisonInTempFailureRetryCheck.cpp b/clang-tidy/android/ComparisonInTempFailureRetryCheck.cpp
index 49db53a0..a98eb8ca 100644
--- a/clang-tidy/android/ComparisonInTempFailureRetryCheck.cpp
+++ b/clang-tidy/android/ComparisonInTempFailureRetryCheck.cpp
@@ -21,15 +21,15 @@ namespace android {
namespace {
AST_MATCHER(BinaryOperator, isRHSATempFailureRetryArg) {
- if (!Node.getLocStart().isMacroID())
+ if (!Node.getBeginLoc().isMacroID())
return false;
const SourceManager &SM = Finder->getASTContext().getSourceManager();
- if (!SM.isMacroArgExpansion(Node.getRHS()->IgnoreParenCasts()->getLocStart()))
+ if (!SM.isMacroArgExpansion(Node.getRHS()->IgnoreParenCasts()->getBeginLoc()))
return false;
const LangOptions &Opts = Finder->getASTContext().getLangOpts();
- SourceLocation LocStart = Node.getLocStart();
+ SourceLocation LocStart = Node.getBeginLoc();
while (LocStart.isMacroID()) {
SourceLocation Invocation = SM.getImmediateMacroCallerLoc(LocStart);
Token Tok;
diff --git a/clang-tidy/boost/UseToStringCheck.cpp b/clang-tidy/boost/UseToStringCheck.cpp
index 1775440d..259f3dfd 100644
--- a/clang-tidy/boost/UseToStringCheck.cpp
+++ b/clang-tidy/boost/UseToStringCheck.cpp
@@ -56,7 +56,7 @@ void UseToStringCheck::check(const MatchFinder::MatchResult &Result) {
else
return;
- auto Loc = Call->getLocStart();
+ auto Loc = Call->getBeginLoc();
auto Diag =
diag(Loc, "use std::to_%0 instead of boost::lexical_cast<std::%0>")
<< StringType;
@@ -65,8 +65,8 @@ void UseToStringCheck::check(const MatchFinder::MatchResult &Result) {
return;
Diag << FixItHint::CreateReplacement(
- CharSourceRange::getCharRange(Call->getLocStart(),
- Call->getArg(0)->getLocStart()),
+ CharSourceRange::getCharRange(Call->getBeginLoc(),
+ Call->getArg(0)->getBeginLoc()),
(llvm::Twine("std::to_") + StringType + "(").str());
}
diff --git a/clang-tidy/bugprone/ArgumentCommentCheck.cpp b/clang-tidy/bugprone/ArgumentCommentCheck.cpp
index a8da703e..068f2c57 100644
--- a/clang-tidy/bugprone/ArgumentCommentCheck.cpp
+++ b/clang-tidy/bugprone/ArgumentCommentCheck.cpp
@@ -242,8 +242,8 @@ void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
}
CharSourceRange BeforeArgument =
- makeFileCharRange(ArgBeginLoc, Args[I]->getLocStart());
- ArgBeginLoc = Args[I]->getLocEnd();
+ makeFileCharRange(ArgBeginLoc, Args[I]->getBeginLoc());
+ ArgBeginLoc = Args[I]->getEndLoc();
std::vector<std::pair<SourceLocation, StringRef>> Comments;
if (BeforeArgument.isValid()) {
@@ -251,7 +251,7 @@ void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
} else {
// Fall back to parsing back from the start of the argument.
CharSourceRange ArgsRange = makeFileCharRange(
- Args[I]->getLocStart(), Args[NumArgs - 1]->getLocEnd());
+ Args[I]->getBeginLoc(), Args[NumArgs - 1]->getEndLoc());
Comments = getCommentsBeforeLoc(Ctx, ArgsRange.getBegin());
}
@@ -287,7 +287,7 @@ void ArgumentCommentCheck::check(const MatchFinder::MatchResult &Result) {
if (!Callee)
return;
- checkCallArgs(Result.Context, Callee, Call->getCallee()->getLocEnd(),
+ checkCallArgs(Result.Context, Callee, Call->getCallee()->getEndLoc(),
llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
} else {
const auto *Construct = cast<CXXConstructExpr>(E);
diff --git a/clang-tidy/bugprone/AssertSideEffectCheck.cpp b/clang-tidy/bugprone/AssertSideEffectCheck.cpp
index 244e7552..c747980b 100644
--- a/clang-tidy/bugprone/AssertSideEffectCheck.cpp
+++ b/clang-tidy/bugprone/AssertSideEffectCheck.cpp
@@ -102,7 +102,7 @@ void AssertSideEffectCheck::registerMatchers(MatchFinder *Finder) {
void AssertSideEffectCheck::check(const MatchFinder::MatchResult &Result) {
const SourceManager &SM = *Result.SourceManager;
const LangOptions LangOpts = getLangOpts();
- SourceLocation Loc = Result.Nodes.getNodeAs<Stmt>("condStmt")->getLocStart();
+ SourceLocation Loc = Result.Nodes.getNodeAs<Stmt>("condStmt")->getBeginLoc();
StringRef AssertMacroName;
while (Loc.isValid() && Loc.isMacroID()) {
diff --git a/clang-tidy/bugprone/BoolPointerImplicitConversionCheck.cpp b/clang-tidy/bugprone/BoolPointerImplicitConversionCheck.cpp
index ed2c2db9..5f33697d 100644
--- a/clang-tidy/bugprone/BoolPointerImplicitConversionCheck.cpp
+++ b/clang-tidy/bugprone/BoolPointerImplicitConversionCheck.cpp
@@ -36,7 +36,7 @@ void BoolPointerImplicitConversionCheck::check(
auto *Var = Result.Nodes.getNodeAs<DeclRefExpr>("expr");
// Ignore macros.
- if (Var->getLocStart().isMacroID())
+ if (Var->getBeginLoc().isMacroID())
return;
// Only allow variable accesses for now, no function calls or member exprs.
@@ -63,9 +63,9 @@ void BoolPointerImplicitConversionCheck::check(
.empty())
return;
- diag(Var->getLocStart(), "dubious check of 'bool *' against 'nullptr', did "
+ diag(Var->getBeginLoc(), "dubious check of 'bool *' against 'nullptr', did "
"you mean to dereference it?")
- << FixItHint::CreateInsertion(Var->getLocStart(), "*");
+ << FixItHint::CreateInsertion(Var->getBeginLoc(), "*");
}
} // namespace bugprone
diff --git a/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tidy/bugprone/BugproneTidyModule.cpp
index 86eb9d1b..09a252b3 100644
--- a/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -16,6 +16,7 @@
#include "BoolPointerImplicitConversionCheck.h"
#include "CopyConstructorInitCheck.h"
#include "DanglingHandleCheck.h"
+#include "ExceptionEscapeCheck.h"
#include "FoldInitTypeCheck.h"
#include "ForwardDeclarationNamespaceCheck.h"
#include "ForwardingReferenceOverloadCheck.h"
@@ -67,6 +68,8 @@ public:
"bugprone-copy-constructor-init");
CheckFactories.registerCheck<DanglingHandleCheck>(
"bugprone-dangling-handle");
+ CheckFactories.registerCheck<ExceptionEscapeCheck>(
+ "bugprone-exception-escape");
CheckFactories.registerCheck<FoldInitTypeCheck>(
"bugprone-fold-init-type");
CheckFactories.registerCheck<ForwardDeclarationNamespaceCheck>(
diff --git a/clang-tidy/bugprone/CMakeLists.txt b/clang-tidy/bugprone/CMakeLists.txt
index 3cce0131..b20997cc 100644
--- a/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tidy/bugprone/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_library(clangTidyBugproneModule
BugproneTidyModule.cpp
CopyConstructorInitCheck.cpp
DanglingHandleCheck.cpp
+ ExceptionEscapeCheck.cpp
FoldInitTypeCheck.cpp
ForwardDeclarationNamespaceCheck.cpp
ForwardingReferenceOverloadCheck.cpp
diff --git a/clang-tidy/bugprone/CopyConstructorInitCheck.cpp b/clang-tidy/bugprone/CopyConstructorInitCheck.cpp
index 151e56c0..f0488254 100644
--- a/clang-tidy/bugprone/CopyConstructorInitCheck.cpp
+++ b/clang-tidy/bugprone/CopyConstructorInitCheck.cpp
@@ -81,7 +81,7 @@ void CopyConstructorInitCheck::check(const MatchFinder::MatchResult &Result) {
if (CtorInitIsWritten) {
if (!ParamName.empty())
SafeFixIts.push_back(
- FixItHint::CreateInsertion(CExpr->getLocEnd(), ParamName));
+ FixItHint::CreateInsertion(CExpr->getEndLoc(), ParamName));
} else {
if (Init->getSourceLocation().isMacroID() ||
Ctor->getLocation().isMacroID() || ShouldNotDoFixit)
@@ -104,7 +104,7 @@ void CopyConstructorInitCheck::check(const MatchFinder::MatchResult &Result) {
SourceLocation FixItLoc;
// There is no initialization list in this constructor.
if (!HasWrittenInitializer) {
- FixItLoc = Ctor->getBody()->getLocStart();
+ FixItLoc = Ctor->getBody()->getBeginLoc();
FixItMsg = " : " + FixItMsg;
} else {
// We apply the missing ctors at the beginning of the initialization list.
diff --git a/clang-tidy/bugprone/DanglingHandleCheck.cpp b/clang-tidy/bugprone/DanglingHandleCheck.cpp
index a22dcad6..81b799ed 100644
--- a/clang-tidy/bugprone/DanglingHandleCheck.cpp
+++ b/clang-tidy/bugprone/DanglingHandleCheck.cpp
@@ -178,7 +178,7 @@ void DanglingHandleCheck::registerMatchers(MatchFinder *Finder) {
void DanglingHandleCheck::check(const MatchFinder::MatchResult &Result) {
auto *Handle = Result.Nodes.getNodeAs<CXXRecordDecl>("handle");
- diag(Result.Nodes.getNodeAs<Stmt>("bad_stmt")->getLocStart(),
+ diag(Result.Nodes.getNodeAs<Stmt>("bad_stmt")->getBeginLoc(),
"%0 outlives its value")
<< Handle->getQualifiedNameAsString();
}
diff --git a/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
new file mode 100644
index 00000000..a5e54c0a
--- /dev/null
+++ b/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
@@ -0,0 +1,214 @@
+//===--- ExceptionEscapeCheck.cpp - clang-tidy-----------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ExceptionEscapeCheck.h"
+
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+#include "llvm/ADT/SmallSet.h"
+#include "llvm/ADT/StringSet.h"
+
+using namespace clang::ast_matchers;
+
+namespace {
+typedef llvm::SmallVector<const clang::Type *, 8> TypeVec;
+} // namespace
+
+namespace clang {
+
+static bool isBaseOf(const Type *DerivedType, const Type *BaseType) {
+ const auto *DerivedClass = DerivedType->getAsCXXRecordDecl();
+ const auto *BaseClass = BaseType->getAsCXXRecordDecl();
+ if (!DerivedClass || !BaseClass)
+ return false;
+
+ return !DerivedClass->forallBases(
+ [BaseClass](const CXXRecordDecl *Cur) { return Cur != BaseClass; });
+}
+
+static const TypeVec
+throwsException(const Stmt *St, const TypeVec &Caught,
+ llvm::SmallSet<const FunctionDecl *, 32> &CallStack);
+
+static const TypeVec
+throwsException(const FunctionDecl *Func,
+ llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
+ if (CallStack.count(Func))
+ return TypeVec();
+
+ if (const Stmt *Body = Func->getBody()) {
+ CallStack.insert(Func);
+ const TypeVec Result = throwsException(Body, TypeVec(), CallStack);
+ CallStack.erase(Func);
+ return Result;
+ }
+
+ TypeVec Result;
+ if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) {
+ for (const QualType Ex : FPT->exceptions()) {
+ Result.push_back(Ex.getTypePtr());
+ }
+ }
+ return Result;
+}
+
+static const TypeVec
+throwsException(const Stmt *St, const TypeVec &Caught,
+ llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
+ TypeVec Results;
+
+ if (!St)
+ return Results;
+
+ if (const auto *Throw = dyn_cast<CXXThrowExpr>(St)) {
+ if (const auto *ThrownExpr = Throw->getSubExpr()) {
+ const auto *ThrownType =
+ ThrownExpr->getType()->getUnqualifiedDesugaredType();
+ if (ThrownType->isReferenceType()) {
+ ThrownType = ThrownType->castAs<ReferenceType>()
+ ->getPointeeType()
+ ->getUnqualifiedDesugaredType();
+ }
+ if (const auto *TD = ThrownType->getAsTagDecl()) {
+ if (TD->getDeclName().isIdentifier() && TD->getName() == "bad_alloc"
+ && TD->isInStdNamespace())
+ return Results;
+ }
+ Results.push_back(ThrownExpr->getType()->getUnqualifiedDesugaredType());
+ } else {
+ Results.append(Caught.begin(), Caught.end());
+ }
+ } else if (const auto *Try = dyn_cast<CXXTryStmt>(St)) {
+ TypeVec Uncaught = throwsException(Try->getTryBlock(), Caught, CallStack);
+ for (unsigned i = 0; i < Try->getNumHandlers(); ++i) {
+ const CXXCatchStmt *Catch = Try->getHandler(i);
+ if (!Catch->getExceptionDecl()) {
+ const TypeVec Rethrown =
+ throwsException(Catch->getHandlerBlock(), Uncaught, CallStack);
+ Results.append(Rethrown.begin(), Rethrown.end());
+ Uncaught.clear();
+ } else {
+ const auto *CaughtType =
+ Catch->getCaughtType()->getUnqualifiedDesugaredType();
+ if (CaughtType->isReferenceType()) {
+ CaughtType = CaughtType->castAs<ReferenceType>()
+ ->getPointeeType()
+ ->getUnqualifiedDesugaredType();
+ }
+ auto NewEnd =
+ llvm::remove_if(Uncaught, [&CaughtType](const Type *ThrownType) {
+ return ThrownType == CaughtType ||
+ isBaseOf(ThrownType, CaughtType);
+ });
+ if (NewEnd != Uncaught.end()) {
+ Uncaught.erase(NewEnd, Uncaught.end());
+ const TypeVec Rethrown = throwsException(
+ Catch->getHandlerBlock(), TypeVec(1, CaughtType), CallStack);
+ Results.append(Rethrown.begin(), Rethrown.end());
+ }
+ }
+ }
+ Results.append(Uncaught.begin(), Uncaught.end());
+ } else if (const auto *Call = dyn_cast<CallExpr>(St)) {
+ if (const FunctionDecl *Func = Call->getDirectCallee()) {
+ TypeVec Excs = throwsException(Func, CallStack);
+ Results.append(Excs.begin(), Excs.end());
+ }
+ } else {
+ for (const Stmt *Child : St->children()) {
+ TypeVec Excs = throwsException(Child, Caught, CallStack);
+ Results.append(Excs.begin(), Excs.end());
+ }
+ }
+ return Results;
+}
+
+static const TypeVec throwsException(const FunctionDecl *Func) {
+ llvm::SmallSet<const FunctionDecl *, 32> CallStack;
+ return throwsException(Func, CallStack);
+}
+
+namespace ast_matchers {
+AST_MATCHER_P(FunctionDecl, throws, internal::Matcher<Type>, InnerMatcher) {
+ TypeVec ExceptionList = throwsException(&Node);
+ auto NewEnd = llvm::remove_if(
+ ExceptionList, [this, Finder, Builder](const Type *Exception) {
+ return !InnerMatcher.matches(*Exception, Finder, Builder);
+ });
+ ExceptionList.erase(NewEnd, ExceptionList.end());
+ return ExceptionList.size();
+}
+
+AST_MATCHER_P(Type, isIgnored, llvm::StringSet<>, IgnoredExceptions) {
+ if (const auto *TD = Node.getAsTagDecl()) {
+ if (TD->getDeclName().isIdentifier())
+ return IgnoredExceptions.count(TD->getName()) > 0;
+ }
+ return false;
+}
+
+AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>,
+ FunctionsThatShouldNotThrow) {
+ return FunctionsThatShouldNotThrow.count(Node.getNameAsString()) > 0;
+}
+} // namespace ast_matchers
+
+namespace tidy {
+namespace bugprone {
+
+ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context), RawFunctionsThatShouldNotThrow(Options.get(
+ "FunctionsThatShouldNotThrow", "")),
+ RawIgnoredExceptions(Options.get("IgnoredExceptions", "")) {
+ llvm::SmallVector<StringRef, 8> FunctionsThatShouldNotThrowVec,
+ IgnoredExceptionsVec;
+ StringRef(RawFunctionsThatShouldNotThrow)
+ .split(FunctionsThatShouldNotThrowVec, ",", -1, false);
+ FunctionsThatShouldNotThrow.insert(FunctionsThatShouldNotThrowVec.begin(),
+ FunctionsThatShouldNotThrowVec.end());
+ StringRef(RawIgnoredExceptions).split(IgnoredExceptionsVec, ",", -1, false);
+ IgnoredExceptions.insert(IgnoredExceptionsVec.begin(),
+ IgnoredExceptionsVec.end());
+}
+
+void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "FunctionsThatShouldNotThrow",
+ RawFunctionsThatShouldNotThrow);
+ Options.store(Opts, "IgnoredExceptions", RawIgnoredExceptions);
+}
+
+void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(
+ functionDecl(allOf(throws(unless(isIgnored(IgnoredExceptions))),
+ anyOf(isNoThrow(), cxxDestructorDecl(),
+ cxxConstructorDecl(isMoveConstructor()),
+ cxxMethodDecl(isMoveAssignmentOperator()),
+ hasName("main"), hasName("swap"),
+ isEnabled(FunctionsThatShouldNotThrow))))
+ .bind("thrower"),
+ this);
+}
+
+void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
+ const FunctionDecl *MatchedDecl =
+ Result.Nodes.getNodeAs<FunctionDecl>("thrower");
+ if (!MatchedDecl)
+ return;
+
+ // FIXME: We should provide more information about the exact location where
+ // the exception is thrown, maybe the full path the exception escapes
+ diag(MatchedDecl->getLocation(), "an exception may be thrown in function %0 "
+ "which should not throw exceptions") << MatchedDecl;
+}
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/bugprone/ExceptionEscapeCheck.h b/clang-tidy/bugprone/ExceptionEscapeCheck.h
new file mode 100644
index 00000000..d690022a
--- /dev/null
+++ b/clang-tidy/bugprone/ExceptionEscapeCheck.h
@@ -0,0 +1,47 @@
+//===--- ExceptionEscapeCheck.h - clang-tidy---------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EXCEPTION_ESCAPE_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EXCEPTION_ESCAPE_H
+
+#include "../ClangTidy.h"
+
+#include "llvm/ADT/StringSet.h"
+
+namespace clang {
+namespace tidy {
+namespace bugprone {
+
+/// Finds functions which should not throw exceptions: Destructors, move
+/// constructors, move assignment operators, the main() function,
+/// swap() functions, functions marked with throw() or noexcept and functions
+/// given as option to the checker.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-exception-escape.html
+class ExceptionEscapeCheck : public ClangTidyCheck {
+public:
+ ExceptionEscapeCheck(StringRef Name, ClangTidyContext *Context);
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ std::string RawFunctionsThatShouldNotThrow;
+ std::string RawIgnoredExceptions;
+
+ llvm::StringSet<> FunctionsThatShouldNotThrow;
+ llvm::StringSet<> IgnoredExceptions;
+};
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EXCEPTION_ESCAPE_H
diff --git a/clang-tidy/bugprone/InaccurateEraseCheck.cpp b/clang-tidy/bugprone/InaccurateEraseCheck.cpp
index cf1be0e7..c1e65b58 100644
--- a/clang-tidy/bugprone/InaccurateEraseCheck.cpp
+++ b/clang-tidy/bugprone/InaccurateEraseCheck.cpp
@@ -57,7 +57,7 @@ void InaccurateEraseCheck::check(const MatchFinder::MatchResult &Result) {
Result.Nodes.getNodeAs<CXXMemberCallExpr>("erase");
const auto *EndExpr =
Result.Nodes.getNodeAs<CXXMemberCallExpr>("end");
- const SourceLocation Loc = MemberCall->getLocStart();
+ const SourceLocation Loc = MemberCall->getBeginLoc();
FixItHint Hint;
@@ -67,7 +67,7 @@ void InaccurateEraseCheck::check(const MatchFinder::MatchResult &Result) {
CharSourceRange::getTokenRange(EndExpr->getSourceRange()),
*Result.SourceManager, getLangOpts());
const SourceLocation EndLoc = Lexer::getLocForEndOfToken(
- AlgCall->getLocEnd(), 0, *Result.SourceManager, getLangOpts());
+ AlgCall->getEndLoc(), 0, *Result.SourceManager, getLangOpts());
Hint = FixItHint::CreateInsertion(EndLoc, ", " + ReplacementText);
}
diff --git a/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp b/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp
index ab7b28d6..549799f1 100644
--- a/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp
+++ b/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp
@@ -61,7 +61,7 @@ void IncorrectRoundingsCheck::registerMatchers(MatchFinder *MatchFinder) {
void IncorrectRoundingsCheck::check(const MatchFinder::MatchResult &Result) {
const auto *CastExpr = Result.Nodes.getNodeAs<ImplicitCastExpr>("CastExpr");
- diag(CastExpr->getLocStart(),
+ diag(CastExpr->getBeginLoc(),
"casting (double + 0.5) to integer leads to incorrect rounding; "
"consider using lround (#include <cmath>) instead");
}
diff --git a/clang-tidy/bugprone/IntegerDivisionCheck.cpp b/clang-tidy/bugprone/IntegerDivisionCheck.cpp
index 1b4eaeab..094d9913 100644
--- a/clang-tidy/bugprone/IntegerDivisionCheck.cpp
+++ b/clang-tidy/bugprone/IntegerDivisionCheck.cpp
@@ -48,7 +48,7 @@ void IntegerDivisionCheck::registerMatchers(MatchFinder *Finder) {
void IntegerDivisionCheck::check(const MatchFinder::MatchResult &Result) {
const auto *IntDiv = Result.Nodes.getNodeAs<BinaryOperator>("IntDiv");
- diag(IntDiv->getLocStart(), "result of integer division used in a floating "
+ diag(IntDiv->getBeginLoc(), "result of integer division used in a floating "
"point context; possible loss of precision");
}
diff --git a/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp b/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp
index f8db0b3e..25544a50 100644
--- a/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp
+++ b/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp
@@ -102,9 +102,10 @@ void MisplacedOperatorInStrlenInAllocCheck::check(
StrLen->getSourceRange(),
(StrLenBegin + LHSText + StrLenEnd + " + " + RHSText).str());
- diag(Alloc->getLocStart(),
+ diag(Alloc->getBeginLoc(),
"addition operator is applied to the argument of %0 instead of its "
- "result") << StrLen->getDirectCallee()->getName() << Hint;
+ "result")
+ << StrLen->getDirectCallee()->getName() << Hint;
}
} // namespace bugprone
diff --git a/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp b/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp
index e263366b..b9cbfebe 100644
--- a/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp
+++ b/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp
@@ -185,11 +185,11 @@ void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Cast = Result.Nodes.getNodeAs<CastExpr>("Cast");
if (!CheckImplicitCasts && isa<ImplicitCastExpr>(Cast))
return;
- if (Cast->getLocStart().isMacroID())
+ if (Cast->getBeginLoc().isMacroID())
return;
const auto *Calc = Result.Nodes.getNodeAs<Expr>("Calc");
- if (Calc->getLocStart().isMacroID())
+ if (Calc->getBeginLoc().isMacroID())
return;
if (Cast->isTypeDependent() || Cast->isValueDependent() ||
@@ -223,7 +223,7 @@ void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) {
if (Context.getIntWidth(CalcType) >= getMaxCalculationWidth(Context, Calc))
return;
- diag(Cast->getLocStart(), "either cast from %0 to %1 is ineffective, or "
+ diag(Cast->getBeginLoc(), "either cast from %0 to %1 is ineffective, or "
"there is loss of precision before the conversion")
<< CalcType << CastType;
}
diff --git a/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp b/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp
index 516ee193..f75a194f 100644
--- a/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp
+++ b/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp
@@ -29,7 +29,7 @@ static void replaceMoveWithForward(const UnresolvedLookupExpr *Callee,
CharSourceRange CallRange =
Lexer::makeFileCharRange(CharSourceRange::getTokenRange(
- Callee->getLocStart(), Callee->getLocEnd()),
+ Callee->getBeginLoc(), Callee->getEndLoc()),
SM, LangOpts);
if (CallRange.isValid()) {
diff --git a/clang-tidy/bugprone/MultipleStatementMacroCheck.cpp b/clang-tidy/bugprone/MultipleStatementMacroCheck.cpp
index 942c8752..5c498213 100644
--- a/clang-tidy/bugprone/MultipleStatementMacroCheck.cpp
+++ b/clang-tidy/bugprone/MultipleStatementMacroCheck.cpp
@@ -19,7 +19,7 @@ namespace bugprone {
namespace {
-AST_MATCHER(Expr, isInMacro) { return Node.getLocStart().isMacroID(); }
+AST_MATCHER(Expr, isInMacro) { return Node.getBeginLoc().isMacroID(); }
/// \brief Find the next statement after `S`.
const Stmt *nextStmt(const MatchFinder::MatchResult &Result, const Stmt *S) {
@@ -73,13 +73,13 @@ void MultipleStatementMacroCheck::check(
if (!Next)
return;
- SourceLocation OuterLoc = Outer->getLocStart();
+ SourceLocation OuterLoc = Outer->getBeginLoc();
if (Result.Nodes.getNodeAs<Stmt>("else"))
OuterLoc = cast<IfStmt>(Outer)->getElseLoc();
- auto InnerRanges = getExpansionRanges(Inner->getLocStart(), Result);
+ auto InnerRanges = getExpansionRanges(Inner->getBeginLoc(), Result);
auto OuterRanges = getExpansionRanges(OuterLoc, Result);
- auto NextRanges = getExpansionRanges(Next->getLocStart(), Result);
+ auto NextRanges = getExpansionRanges(Next->getBeginLoc(), Result);
// Remove all the common ranges, starting from the top (the last ones in the
// list).
diff --git a/clang-tidy/bugprone/SizeofContainerCheck.cpp b/clang-tidy/bugprone/SizeofContainerCheck.cpp
index b4a019e9..f7d63b1a 100644
--- a/clang-tidy/bugprone/SizeofContainerCheck.cpp
+++ b/clang-tidy/bugprone/SizeofContainerCheck.cpp
@@ -40,7 +40,7 @@ void SizeofContainerCheck::check(const MatchFinder::MatchResult &Result) {
Result.Nodes.getNodeAs<UnaryExprOrTypeTraitExpr>("sizeof");
auto Diag =
- diag(SizeOf->getLocStart(), "sizeof() doesn't return the size of the "
+ diag(SizeOf->getBeginLoc(), "sizeof() doesn't return the size of the "
"container; did you mean .size()?");
}
diff --git a/clang-tidy/bugprone/SizeofExpressionCheck.cpp b/clang-tidy/bugprone/SizeofExpressionCheck.cpp
index f05a9006..d662657a 100644
--- a/clang-tidy/bugprone/SizeofExpressionCheck.cpp
+++ b/clang-tidy/bugprone/SizeofExpressionCheck.cpp
@@ -216,29 +216,29 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) {
const ASTContext &Ctx = *Result.Context;
if (const auto *E = Result.Nodes.getNodeAs<Expr>("sizeof-constant")) {
- diag(E->getLocStart(),
+ diag(E->getBeginLoc(),
"suspicious usage of 'sizeof(K)'; did you mean 'K'?");
} else if (const auto *E =
Result.Nodes.getNodeAs<Expr>("sizeof-integer-call")) {
- diag(E->getLocStart(), "suspicious usage of 'sizeof()' on an expression "
+ diag(E->getBeginLoc(), "suspicious usage of 'sizeof()' on an expression "
"that results in an integer");
} else if (const auto *E = Result.Nodes.getNodeAs<Expr>("sizeof-this")) {
- diag(E->getLocStart(),
+ diag(E->getBeginLoc(),
"suspicious usage of 'sizeof(this)'; did you mean 'sizeof(*this)'");
} else if (const auto *E = Result.Nodes.getNodeAs<Expr>("sizeof-charp")) {
- diag(E->getLocStart(),
+ diag(E->getBeginLoc(),
"suspicious usage of 'sizeof(char*)'; do you mean 'strlen'?");
} else if (const auto *E =
Result.Nodes.getNodeAs<Expr>("sizeof-pointer-to-aggregate")) {
- diag(E->getLocStart(),
+ diag(E->getBeginLoc(),
"suspicious usage of 'sizeof(A*)'; pointer to aggregate");
} else if (const auto *E =
Result.Nodes.getNodeAs<Expr>("sizeof-compare-constant")) {
- diag(E->getLocStart(),
+ diag(E->getBeginLoc(),
"suspicious comparison of 'sizeof(expr)' to a constant");
} else if (const auto *E =
Result.Nodes.getNodeAs<Expr>("sizeof-comma-expr")) {
- diag(E->getLocStart(), "suspicious usage of 'sizeof(..., ...)'");
+ diag(E->getBeginLoc(), "suspicious usage of 'sizeof(..., ...)'");
} else if (const auto *E =
Result.Nodes.getNodeAs<Expr>("sizeof-divide-expr")) {
const auto *NumTy = Result.Nodes.getNodeAs<Type>("num-type");
@@ -252,30 +252,30 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) {
if (DenominatorSize > CharUnits::Zero() &&
!NumeratorSize.isMultipleOf(DenominatorSize)) {
- diag(E->getLocStart(), "suspicious usage of 'sizeof(...)/sizeof(...)';"
+ diag(E->getBeginLoc(), "suspicious usage of 'sizeof(...)/sizeof(...)';"
" numerator is not a multiple of denominator");
} else if (ElementSize > CharUnits::Zero() &&
DenominatorSize > CharUnits::Zero() &&
ElementSize != DenominatorSize) {
- diag(E->getLocStart(), "suspicious usage of 'sizeof(...)/sizeof(...)';"
+ diag(E->getBeginLoc(), "suspicious usage of 'sizeof(...)/sizeof(...)';"
" numerator is not a multiple of denominator");
} else if (NumTy && DenomTy && NumTy == DenomTy) {
- diag(E->getLocStart(),
+ diag(E->getBeginLoc(),
"suspicious usage of sizeof pointer 'sizeof(T)/sizeof(T)'");
} else if (PointedTy && DenomTy && PointedTy == DenomTy) {
- diag(E->getLocStart(),
+ diag(E->getBeginLoc(),
"suspicious usage of sizeof pointer 'sizeof(T*)/sizeof(T)'");
} else if (NumTy && DenomTy && NumTy->isPointerType() &&
DenomTy->isPointerType()) {
- diag(E->getLocStart(),
+ diag(E->getBeginLoc(),
"suspicious usage of sizeof pointer 'sizeof(P*)/sizeof(Q*)'");
}
} else if (const auto *E =
Result.Nodes.getNodeAs<Expr>("sizeof-sizeof-expr")) {
- diag(E->getLocStart(), "suspicious usage of 'sizeof(sizeof(...))'");
+ diag(E->getBeginLoc(), "suspicious usage of 'sizeof(sizeof(...))'");
} else if (const auto *E =
Result.Nodes.getNodeAs<Expr>("sizeof-multiply-sizeof")) {
- diag(E->getLocStart(), "suspicious 'sizeof' by 'sizeof' multiplication");
+ diag(E->getBeginLoc(), "suspicious 'sizeof' by 'sizeof' multiplication");
}
}
diff --git a/clang-tidy/bugprone/StringConstructorCheck.cpp b/clang-tidy/bugprone/StringConstructorCheck.cpp
index 420428fc..d8882306 100644
--- a/clang-tidy/bugprone/StringConstructorCheck.cpp
+++ b/clang-tidy/bugprone/StringConstructorCheck.cpp
@@ -106,7 +106,7 @@ void StringConstructorCheck::check(const MatchFinder::MatchResult &Result) {
const ASTContext &Ctx = *Result.Context;
const auto *E = Result.Nodes.getNodeAs<CXXConstructExpr>("constructor");
assert(E && "missing constructor expression");
- SourceLocation Loc = E->getLocStart();
+ SourceLocation Loc = E->getBeginLoc();
if (Result.Nodes.getNodeAs<Expr>("swapped-parameter")) {
const Expr *P0 = E->getArg(0);
diff --git a/clang-tidy/bugprone/StringIntegerAssignmentCheck.cpp b/clang-tidy/bugprone/StringIntegerAssignmentCheck.cpp
index f3736489..f49a5709 100644
--- a/clang-tidy/bugprone/StringIntegerAssignmentCheck.cpp
+++ b/clang-tidy/bugprone/StringIntegerAssignmentCheck.cpp
@@ -39,7 +39,7 @@ void StringIntegerAssignmentCheck::registerMatchers(MatchFinder *Finder) {
void StringIntegerAssignmentCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *Argument = Result.Nodes.getNodeAs<Expr>("expr");
- SourceLocation Loc = Argument->getLocStart();
+ SourceLocation Loc = Argument->getBeginLoc();
auto Diag =
diag(Loc, "an integer is interpreted as a character code when assigning "
@@ -62,7 +62,7 @@ void StringIntegerAssignmentCheck::check(
}
SourceLocation EndLoc = Lexer::getLocForEndOfToken(
- Argument->getLocEnd(), 0, *Result.SourceManager, getLangOpts());
+ Argument->getEndLoc(), 0, *Result.SourceManager, getLangOpts());
if (IsOneDigit) {
Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L'" : "'")
<< FixItHint::CreateInsertion(EndLoc, "'");
diff --git a/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp b/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp
index eaa610fc..b440b616 100644
--- a/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp
+++ b/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp
@@ -68,14 +68,14 @@ void StringLiteralWithEmbeddedNulCheck::check(
SL->getCodeUnit(Offset + 1) == 'x' &&
isDigit(SL->getCodeUnit(Offset + 2)) &&
isDigit(SL->getCodeUnit(Offset + 3))) {
- diag(SL->getLocStart(), "suspicious embedded NUL character");
+ diag(SL->getBeginLoc(), "suspicious embedded NUL character");
return;
}
}
}
if (const auto *SL = Result.Nodes.getNodeAs<StringLiteral>("truncated")) {
- diag(SL->getLocStart(),
+ diag(SL->getBeginLoc(),
"truncated string literal with embedded NUL character");
}
}
diff --git a/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp b/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp
index 8e11a435..2e0a46c9 100644
--- a/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp
+++ b/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp
@@ -61,7 +61,7 @@ void SuspiciousMemsetUsageCheck::check(const MatchFinder::MatchResult &Result) {
SourceRange CharRange = CharZeroFill->getSourceRange();
auto Diag =
- diag(CharZeroFill->getLocStart(), "memset fill value is char '0', "
+ diag(CharZeroFill->getBeginLoc(), "memset fill value is char '0', "
"potentially mistaken for int 0");
// Only suggest a fix if no macros are involved.
@@ -82,7 +82,7 @@ void SuspiciousMemsetUsageCheck::check(const MatchFinder::MatchResult &Result) {
(NumValue >= 0 && NumValue <= UCharMax))
return;
- diag(NumFill->getLocStart(), "memset fill value is out of unsigned "
+ diag(NumFill->getBeginLoc(), "memset fill value is out of unsigned "
"character range, gets truncated");
}
@@ -110,7 +110,7 @@ void SuspiciousMemsetUsageCheck::check(const MatchFinder::MatchResult &Result) {
// `byte_count` is known to be zero at compile time, and `fill_char` is
// either not known or known to be a positive integer. Emit a warning
// and fix-its to swap the arguments.
- auto D = diag(Call->getLocStart(),
+ auto D = diag(Call->getBeginLoc(),
"memset of size zero, potentially swapped arguments");
StringRef RHSString = tooling::fixit::getText(*ByteCount, *Result.Context);
StringRef LHSString = tooling::fixit::getText(*FillChar, *Result.Context);
diff --git a/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp b/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp
index 3831dc3e..a66cf431 100644
--- a/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp
+++ b/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp
@@ -120,7 +120,7 @@ void SuspiciousMissingCommaCheck::check(
if (double(Count) / Size > RatioThreshold)
return;
- diag(ConcatenatedLiteral->getLocStart(),
+ diag(ConcatenatedLiteral->getBeginLoc(),
"suspicious string literal, probably missing a comma");
}
diff --git a/clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp b/clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp
index d7f51d0d..ae6f2ee6 100644
--- a/clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp
+++ b/clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp
@@ -34,7 +34,7 @@ void SuspiciousSemicolonCheck::check(const MatchFinder::MatchResult &Result) {
return;
const auto *Semicolon = Result.Nodes.getNodeAs<NullStmt>("semi");
- SourceLocation LocStart = Semicolon->getLocStart();
+ SourceLocation LocStart = Semicolon->getBeginLoc();
if (LocStart.isMacroID())
return;
@@ -51,7 +51,7 @@ void SuspiciousSemicolonCheck::check(const MatchFinder::MatchResult &Result) {
SM.getSpellingLineNumber(Token.getLocation()) != SemicolonLine)
return;
- SourceLocation LocEnd = Semicolon->getLocEnd();
+ SourceLocation LocEnd = Semicolon->getEndLoc();
FileID FID = SM.getFileID(LocEnd);
llvm::MemoryBuffer *Buffer = SM.getBuffer(FID, LocEnd);
Lexer Lexer(SM.getLocForStartOfFile(FID), Ctxt.getLangOpts(),
@@ -60,7 +60,7 @@ void SuspiciousSemicolonCheck::check(const MatchFinder::MatchResult &Result) {
if (Lexer.LexFromRawLexer(Token))
return;
- unsigned BaseIndent = SM.getSpellingColumnNumber(Statement->getLocStart());
+ unsigned BaseIndent = SM.getSpellingColumnNumber(Statement->getBeginLoc());
unsigned NewTokenIndent = SM.getSpellingColumnNumber(Token.getLocation());
unsigned NewTokenLine = SM.getSpellingLineNumber(Token.getLocation());
diff --git a/clang-tidy/bugprone/SuspiciousStringCompareCheck.cpp b/clang-tidy/bugprone/SuspiciousStringCompareCheck.cpp
index 0cc62157..a16da4af 100644
--- a/clang-tidy/bugprone/SuspiciousStringCompareCheck.cpp
+++ b/clang-tidy/bugprone/SuspiciousStringCompareCheck.cpp
@@ -177,7 +177,7 @@ void SuspiciousStringCompareCheck::check(
Call->getRParenLoc(), 0, Result.Context->getSourceManager(),
getLangOpts());
- diag(Call->getLocStart(),
+ diag(Call->getBeginLoc(),
"function %0 is called without explicitly comparing result")
<< Decl << FixItHint::CreateInsertion(EndLoc, " != 0");
}
@@ -186,29 +186,30 @@ void SuspiciousStringCompareCheck::check(
SourceLocation EndLoc = Lexer::getLocForEndOfToken(
Call->getRParenLoc(), 0, Result.Context->getSourceManager(),
getLangOpts());
- SourceLocation NotLoc = E->getLocStart();
+ SourceLocation NotLoc = E->getBeginLoc();
- diag(Call->getLocStart(),
+ diag(Call->getBeginLoc(),
"function %0 is compared using logical not operator")
- << Decl << FixItHint::CreateRemoval(
- CharSourceRange::getTokenRange(NotLoc, NotLoc))
+ << Decl
+ << FixItHint::CreateRemoval(
+ CharSourceRange::getTokenRange(NotLoc, NotLoc))
<< FixItHint::CreateInsertion(EndLoc, " == 0");
}
if (Result.Nodes.getNodeAs<Stmt>("invalid-comparison")) {
- diag(Call->getLocStart(),
+ diag(Call->getBeginLoc(),
"function %0 is compared to a suspicious constant")
<< Decl;
}
if (const auto *BinOp =
Result.Nodes.getNodeAs<BinaryOperator>("suspicious-operator")) {
- diag(Call->getLocStart(), "results of function %0 used by operator '%1'")
+ diag(Call->getBeginLoc(), "results of function %0 used by operator '%1'")
<< Decl << BinOp->getOpcodeStr();
}
if (Result.Nodes.getNodeAs<Stmt>("invalid-conversion")) {
- diag(Call->getLocStart(), "function %0 has suspicious implicit cast")
+ diag(Call->getBeginLoc(), "function %0 has suspicious implicit cast")
<< Decl;
}
}
diff --git a/clang-tidy/bugprone/SwappedArgumentsCheck.cpp b/clang-tidy/bugprone/SwappedArgumentsCheck.cpp
index 64d3eaf5..c1550f6d 100644
--- a/clang-tidy/bugprone/SwappedArgumentsCheck.cpp
+++ b/clang-tidy/bugprone/SwappedArgumentsCheck.cpp
@@ -84,7 +84,7 @@ void SwappedArgumentsCheck::check(const MatchFinder::MatchResult &Result) {
continue;
// Emit a warning and fix-its that swap the arguments.
- diag(Call->getLocStart(), "argument with implicit conversion from %0 "
+ diag(Call->getBeginLoc(), "argument with implicit conversion from %0 "
"to %1 followed by argument converted from "
"%2 to %3, potentially swapped arguments.")
<< LHS->getType() << LHSFrom->getType() << RHS->getType()
diff --git a/clang-tidy/bugprone/TerminatingContinueCheck.cpp b/clang-tidy/bugprone/TerminatingContinueCheck.cpp
index 3e11e06c..e41fc1fa 100644
--- a/clang-tidy/bugprone/TerminatingContinueCheck.cpp
+++ b/clang-tidy/bugprone/TerminatingContinueCheck.cpp
@@ -39,7 +39,7 @@ void TerminatingContinueCheck::check(const MatchFinder::MatchResult &Result) {
const auto *ContStmt = Result.Nodes.getNodeAs<ContinueStmt>("continue");
auto Diag =
- diag(ContStmt->getLocStart(),
+ diag(ContStmt->getBeginLoc(),
"'continue' in loop with false condition is equivalent to 'break'")
<< tooling::fixit::createReplacement(*ContStmt, "break");
}
diff --git a/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp b/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp
index 350cf3bc..695d9c5f 100644
--- a/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp
+++ b/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp
@@ -42,7 +42,7 @@ void ThrowKeywordMissingCheck::check(const MatchFinder::MatchResult &Result) {
const auto *TemporaryExpr =
Result.Nodes.getNodeAs<Expr>("temporary-exception-not-thrown");
- diag(TemporaryExpr->getLocStart(), "suspicious exception object created but "
+ diag(TemporaryExpr->getBeginLoc(), "suspicious exception object created but "
"not thrown; did you mean 'throw %0'?")
<< TemporaryExpr->getType().getBaseTypeIdentifier()->getName();
}
diff --git a/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp b/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp
index ebfe517d..0665e080 100644
--- a/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp
+++ b/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp
@@ -52,7 +52,7 @@ void UndefinedMemoryManipulationCheck::check(
QualType DestType = Call->getArg(0)->IgnoreImplicit()->getType();
if (!DestType->getPointeeType().isNull())
DestType = DestType->getPointeeType();
- diag(Call->getLocStart(), "undefined behavior, destination object type %0 "
+ diag(Call->getBeginLoc(), "undefined behavior, destination object type %0 "
"is not TriviallyCopyable")
<< DestType;
}
@@ -60,7 +60,7 @@ void UndefinedMemoryManipulationCheck::check(
QualType SourceType = Call->getArg(1)->IgnoreImplicit()->getType();
if (!SourceType->getPointeeType().isNull())
SourceType = SourceType->getPointeeType();
- diag(Call->getLocStart(),
+ diag(Call->getBeginLoc(),
"undefined behavior, source object type %0 is not TriviallyCopyable")
<< SourceType;
}
diff --git a/clang-tidy/bugprone/UndelegatedConstructorCheck.cpp b/clang-tidy/bugprone/UndelegatedConstructorCheck.cpp
index f47ad7e7..90c07b9a 100644
--- a/clang-tidy/bugprone/UndelegatedConstructorCheck.cpp
+++ b/clang-tidy/bugprone/UndelegatedConstructorCheck.cpp
@@ -75,7 +75,7 @@ void UndelegatedConstructorCheck::registerMatchers(MatchFinder *Finder) {
void UndelegatedConstructorCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *E = Result.Nodes.getNodeAs<CXXConstructExpr>("construct");
- diag(E->getLocStart(), "did you intend to call a delegated constructor? "
+ diag(E->getBeginLoc(), "did you intend to call a delegated constructor? "
"A temporary object is created here instead");
}
diff --git a/clang-tidy/bugprone/UnusedRaiiCheck.cpp b/clang-tidy/bugprone/UnusedRaiiCheck.cpp
index e2882f37..e9089b77 100644
--- a/clang-tidy/bugprone/UnusedRaiiCheck.cpp
+++ b/clang-tidy/bugprone/UnusedRaiiCheck.cpp
@@ -52,7 +52,7 @@ void UnusedRaiiCheck::check(const MatchFinder::MatchResult &Result) {
// We ignore code expanded from macros to reduce the number of false
// positives.
- if (E->getLocStart().isMacroID())
+ if (E->getBeginLoc().isMacroID())
return;
// Don't emit a warning for the last statement in the surrounding compund
@@ -62,7 +62,7 @@ void UnusedRaiiCheck::check(const MatchFinder::MatchResult &Result) {
return;
// Emit a warning.
- auto D = diag(E->getLocStart(), "object destroyed immediately after "
+ auto D = diag(E->getBeginLoc(), "object destroyed immediately after "
"creation; did you mean to name the object?");
const char *Replacement = " give_me_a_name";
@@ -84,7 +84,7 @@ void UnusedRaiiCheck::check(const MatchFinder::MatchResult &Result) {
match(expr(hasDescendant(typeLoc().bind("t"))), *E, *Result.Context);
const auto *TL = selectFirst<TypeLoc>("t", Matches);
D << FixItHint::CreateInsertion(
- Lexer::getLocForEndOfToken(TL->getLocEnd(), 0, *Result.SourceManager,
+ Lexer::getLocForEndOfToken(TL->getEndLoc(), 0, *Result.SourceManager,
getLangOpts()),
Replacement);
}
diff --git a/clang-tidy/bugprone/UnusedReturnValueCheck.cpp b/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
index d0c58857..b8de045e 100644
--- a/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
+++ b/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
@@ -86,10 +86,10 @@ void UnusedReturnValueCheck::registerMatchers(MatchFinder *Finder) {
void UnusedReturnValueCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Matched = Result.Nodes.getNodeAs<CallExpr>("match")) {
- diag(Matched->getLocStart(),
+ diag(Matched->getBeginLoc(),
"the value returned by this function should be used")
<< Matched->getSourceRange();
- diag(Matched->getLocStart(),
+ diag(Matched->getBeginLoc(),
"cast the expression to void to silence this warning",
DiagnosticIDs::Note);
}
diff --git a/clang-tidy/bugprone/VirtualNearMissCheck.cpp b/clang-tidy/bugprone/VirtualNearMissCheck.cpp
index bb9c9b65..ede1bf33 100644
--- a/clang-tidy/bugprone/VirtualNearMissCheck.cpp
+++ b/clang-tidy/bugprone/VirtualNearMissCheck.cpp
@@ -257,7 +257,7 @@ void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) {
bool ApplyFix = !BaseMD->isTemplateInstantiation() &&
!DerivedMD->isTemplateInstantiation();
auto Diag =
- diag(DerivedMD->getLocStart(),
+ diag(DerivedMD->getBeginLoc(),
"method '%0' has a similar name and the same signature as "
"virtual method '%1'; did you mean to override it?")
<< DerivedMD->getQualifiedNameAsString()
diff --git a/clang-tidy/cert/CERTTidyModule.cpp b/clang-tidy/cert/CERTTidyModule.cpp
index 1007522d..da09932e 100644
--- a/clang-tidy/cert/CERTTidyModule.cpp
+++ b/clang-tidy/cert/CERTTidyModule.cpp
@@ -21,6 +21,7 @@
#include "FloatLoopCounter.h"
#include "LimitedRandomnessCheck.h"
#include "PostfixOperatorCheck.h"
+#include "ProperlySeededRandomGeneratorCheck.h"
#include "SetLongJmpCheck.h"
#include "StaticObjectExceptionCheck.h"
#include "StrToNumCheck.h"
@@ -58,6 +59,8 @@ public:
"cert-err61-cpp");
// MSC
CheckFactories.registerCheck<LimitedRandomnessCheck>("cert-msc50-cpp");
+ CheckFactories.registerCheck<ProperlySeededRandomGeneratorCheck>(
+ "cert-msc51-cpp");
// C checkers
// DCL
@@ -72,6 +75,8 @@ public:
CheckFactories.registerCheck<StrToNumCheck>("cert-err34-c");
// MSC
CheckFactories.registerCheck<LimitedRandomnessCheck>("cert-msc30-c");
+ CheckFactories.registerCheck<ProperlySeededRandomGeneratorCheck>(
+ "cert-msc32-c");
}
};
diff --git a/clang-tidy/cert/CMakeLists.txt b/clang-tidy/cert/CMakeLists.txt
index 5a7370d3..edc93c8e 100644
--- a/clang-tidy/cert/CMakeLists.txt
+++ b/clang-tidy/cert/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_library(clangTidyCERTModule
FloatLoopCounter.cpp
LimitedRandomnessCheck.cpp
PostfixOperatorCheck.cpp
+ ProperlySeededRandomGeneratorCheck.cpp
SetLongJmpCheck.cpp
StaticObjectExceptionCheck.cpp
StrToNumCheck.cpp
diff --git a/clang-tidy/cert/LimitedRandomnessCheck.cpp b/clang-tidy/cert/LimitedRandomnessCheck.cpp
index f319f9fb..3f6f948d 100644
--- a/clang-tidy/cert/LimitedRandomnessCheck.cpp
+++ b/clang-tidy/cert/LimitedRandomnessCheck.cpp
@@ -30,7 +30,7 @@ void LimitedRandomnessCheck::check(const MatchFinder::MatchResult &Result) {
msg = "; use C++11 random library instead";
const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("randomGenerator");
- diag(MatchedDecl->getLocStart(), "rand() has limited randomness" + msg);
+ diag(MatchedDecl->getBeginLoc(), "rand() has limited randomness" + msg);
}
} // namespace cert
diff --git a/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.cpp b/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.cpp
new file mode 100644
index 00000000..256525e3
--- /dev/null
+++ b/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.cpp
@@ -0,0 +1,124 @@
+//===--- ProperlySeededRandomGeneratorCheck.cpp - clang-tidy---------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ProperlySeededRandomGeneratorCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/ADT/STLExtras.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace cert {
+
+ProperlySeededRandomGeneratorCheck::ProperlySeededRandomGeneratorCheck(
+ StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ RawDisallowedSeedTypes(
+ Options.get("DisallowedSeedTypes", "time_t,std::time_t")) {
+ StringRef(RawDisallowedSeedTypes).split(DisallowedSeedTypes, ',');
+}
+
+void ProperlySeededRandomGeneratorCheck::storeOptions(
+ ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "DisallowedSeedTypes", RawDisallowedSeedTypes);
+}
+
+void ProperlySeededRandomGeneratorCheck::registerMatchers(MatchFinder *Finder) {
+ auto RandomGeneratorEngineDecl = cxxRecordDecl(hasAnyName(
+ "::std::linear_congruential_engine", "::std::mersenne_twister_engine",
+ "::std::subtract_with_carry_engine", "::std::discard_block_engine",
+ "::std::independent_bits_engine", "::std::shuffle_order_engine"));
+ auto RandomGeneratorEngineTypeMatcher = hasType(hasUnqualifiedDesugaredType(
+ recordType(hasDeclaration(RandomGeneratorEngineDecl))));
+
+ // std::mt19937 engine;
+ // engine.seed();
+ // ^
+ // engine.seed(1);
+ // ^
+ // const int x = 1;
+ // engine.seed(x);
+ // ^
+ Finder->addMatcher(
+ cxxMemberCallExpr(
+ has(memberExpr(has(declRefExpr(RandomGeneratorEngineTypeMatcher)),
+ member(hasName("seed")),
+ unless(hasDescendant(cxxThisExpr())))))
+ .bind("seed"),
+ this);
+
+ // std::mt19937 engine;
+ // ^
+ // std::mt19937 engine(1);
+ // ^
+ // const int x = 1;
+ // std::mt19937 engine(x);
+ // ^
+ Finder->addMatcher(
+ cxxConstructExpr(RandomGeneratorEngineTypeMatcher).bind("ctor"), this);
+
+ // srand();
+ // ^
+ // const int x = 1;
+ // srand(x);
+ // ^
+ Finder->addMatcher(
+ callExpr(callee(functionDecl(hasAnyName("::srand", "::std::srand"))))
+ .bind("srand"),
+ this);
+}
+
+void ProperlySeededRandomGeneratorCheck::check(
+ const MatchFinder::MatchResult &Result) {
+ const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
+ if (Ctor)
+ checkSeed(Result, Ctor);
+
+ const auto *Func = Result.Nodes.getNodeAs<CXXMemberCallExpr>("seed");
+ if (Func)
+ checkSeed(Result, Func);
+
+ const auto *Srand = Result.Nodes.getNodeAs<CallExpr>("srand");
+ if (Srand)
+ checkSeed(Result, Srand);
+}
+
+template <class T>
+void ProperlySeededRandomGeneratorCheck::checkSeed(
+ const MatchFinder::MatchResult &Result, const T *Func) {
+ if (Func->getNumArgs() == 0 || Func->getArg(0)->isDefaultArgument()) {
+ diag(Func->getExprLoc(),
+ "random number generator seeded with a default argument will generate "
+ "a predictable sequence of values");
+ return;
+ }
+
+ llvm::APSInt Value;
+ if (Func->getArg(0)->EvaluateAsInt(Value, *Result.Context)) {
+ diag(Func->getExprLoc(),
+ "random number generator seeded with a constant value will generate a "
+ "predictable sequence of values");
+ return;
+ }
+
+ const std::string SeedType(
+ Func->getArg(0)->IgnoreCasts()->getType().getAsString());
+ if (llvm::find(DisallowedSeedTypes, SeedType) != DisallowedSeedTypes.end()) {
+ diag(Func->getExprLoc(),
+ "random number generator seeded with a disallowed source of seed "
+ "value will generate a predictable sequence of values");
+ return;
+ }
+}
+
+} // namespace cert
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.h b/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.h
new file mode 100644
index 00000000..ac5507c9
--- /dev/null
+++ b/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.h
@@ -0,0 +1,47 @@
+//===--- ProperlySeededRandomGeneratorCheck.h - clang-tidy-------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_PROPERLY_SEEDED_RANDOM_GENERATOR_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_PROPERLY_SEEDED_RANDOM_GENERATOR_H
+
+#include "../ClangTidy.h"
+#include <string>
+
+namespace clang {
+namespace tidy {
+namespace cert {
+
+/// Random number generator must be seeded properly.
+///
+/// A random number generator initialized with default value or a
+/// constant expression is a security vulnerability.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/cert-properly-seeded-random-generator.html
+class ProperlySeededRandomGeneratorCheck : public ClangTidyCheck {
+public:
+ ProperlySeededRandomGeneratorCheck(StringRef Name, ClangTidyContext *Context);
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ template <class T>
+ void checkSeed(const ast_matchers::MatchFinder::MatchResult &Result,
+ const T *Func);
+
+ std::string RawDisallowedSeedTypes;
+ SmallVector<StringRef, 5> DisallowedSeedTypes;
+};
+
+} // namespace cert
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_PROPERLY_SEEDED_RANDOM_GENERATOR_H
diff --git a/clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp b/clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp
index 3e800cd5..eaed15f9 100644
--- a/clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp
+++ b/clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp
@@ -19,7 +19,7 @@ namespace cppcoreguidelines {
namespace {
AST_MATCHER(GotoStmt, isForwardJumping) {
- return Node.getLocStart() < Node.getLabel()->getLocStart();
+ return Node.getBeginLoc() < Node.getLabel()->getBeginLoc();
}
} // namespace
@@ -49,7 +49,7 @@ void AvoidGotoCheck::check(const MatchFinder::MatchResult &Result) {
diag(Goto->getGotoLoc(), "avoid using 'goto' for flow control")
<< Goto->getSourceRange();
- diag(Goto->getLabel()->getLocStart(), "label defined here",
+ diag(Goto->getLabel()->getBeginLoc(), "label defined here",
DiagnosticIDs::Note);
}
} // namespace cppcoreguidelines
diff --git a/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp b/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp
index 2c5676e7..3a22c1e9 100644
--- a/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp
+++ b/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp
@@ -52,14 +52,14 @@ void NarrowingConversionsCheck::registerMatchers(MatchFinder *Finder) {
void NarrowingConversionsCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("op")) {
- if (Op->getLocStart().isMacroID())
+ if (Op->getBeginLoc().isMacroID())
return;
diag(Op->getOperatorLoc(), "narrowing conversion from %0 to %1")
<< Op->getRHS()->getType() << Op->getLHS()->getType();
return;
}
const auto *Cast = Result.Nodes.getNodeAs<ImplicitCastExpr>("cast");
- if (Cast->getLocStart().isMacroID())
+ if (Cast->getBeginLoc().isMacroID())
return;
diag(Cast->getExprLoc(), "narrowing conversion from %0 to %1")
<< Cast->getSubExpr()->getType() << Cast->getType();
diff --git a/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp b/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp
index 5a2b8824..f9cec51e 100644
--- a/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp
+++ b/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp
@@ -73,8 +73,8 @@ void NoMallocCheck::check(const MatchFinder::MatchResult &Result) {
assert(Call && "Unhandled binding in the Matcher");
- diag(Call->getLocStart(), "do not manage memory manually; %0")
- << Recommendation << SourceRange(Call->getLocStart(), Call->getLocEnd());
+ diag(Call->getBeginLoc(), "do not manage memory manually; %0")
+ << Recommendation << SourceRange(Call->getBeginLoc(), Call->getEndLoc());
}
} // namespace cppcoreguidelines
diff --git a/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp b/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp
index 6c86e053..ebebfda2 100644
--- a/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp
+++ b/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp
@@ -203,7 +203,7 @@ bool OwningMemoryCheck::handleDeletion(const BoundNodes &Nodes) {
// Deletion of non-owners, with `delete variable;`
if (DeleteStmt) {
- diag(DeleteStmt->getLocStart(),
+ diag(DeleteStmt->getBeginLoc(),
"deleting a pointer through a type that is "
"not marked 'gsl::owner<>'; consider using a "
"smart pointer instead")
@@ -212,7 +212,7 @@ bool OwningMemoryCheck::handleDeletion(const BoundNodes &Nodes) {
// FIXME: The declaration of the variable that was deleted can be
// rewritten.
const ValueDecl *Decl = DeletedVariable->getDecl();
- diag(Decl->getLocStart(), "variable declared here", DiagnosticIDs::Note)
+ diag(Decl->getBeginLoc(), "variable declared here", DiagnosticIDs::Note)
<< Decl->getSourceRange();
return true;
@@ -228,7 +228,7 @@ bool OwningMemoryCheck::handleLegacyConsumers(const BoundNodes &Nodes) {
// as a pointer, which should not be an owner. The argument that is an owner
// is known and the false positive coming from the filename can be avoided.
if (LegacyConsumer) {
- diag(LegacyConsumer->getLocStart(),
+ diag(LegacyConsumer->getBeginLoc(),
"calling legacy resource function without passing a 'gsl::owner<>'")
<< LegacyConsumer->getSourceRange();
return true;
@@ -242,7 +242,7 @@ bool OwningMemoryCheck::handleExpectedOwner(const BoundNodes &Nodes) {
// Expected function argument to be owner.
if (ExpectedOwner) {
- diag(ExpectedOwner->getLocStart(),
+ diag(ExpectedOwner->getBeginLoc(),
"expected argument of type 'gsl::owner<>'; got %0")
<< ExpectedOwner->getType() << ExpectedOwner->getSourceRange();
return true;
@@ -261,7 +261,7 @@ bool OwningMemoryCheck::handleAssignmentAndInit(const BoundNodes &Nodes) {
// Assignments to owners.
if (OwnerAssignment) {
- diag(OwnerAssignment->getLocStart(),
+ diag(OwnerAssignment->getBeginLoc(),
"expected assignment source to be of type 'gsl::owner<>'; got %0")
<< OwnerAssignment->getRHS()->getType()
<< OwnerAssignment->getSourceRange();
@@ -270,7 +270,7 @@ bool OwningMemoryCheck::handleAssignmentAndInit(const BoundNodes &Nodes) {
// Initialization of owners.
if (OwnerInitialization) {
- diag(OwnerInitialization->getLocStart(),
+ diag(OwnerInitialization->getBeginLoc(),
"expected initialization with value of type 'gsl::owner<>'; got %0")
<< OwnerInitialization->getAnyInitializer()->getType()
<< OwnerInitialization->getSourceRange();
@@ -306,7 +306,7 @@ bool OwningMemoryCheck::handleAssignmentFromNewOwner(const BoundNodes &Nodes) {
// Bad assignments to non-owners, where the RHS is a newly created owner.
if (BadOwnerAssignment) {
- diag(BadOwnerAssignment->getLocStart(),
+ diag(BadOwnerAssignment->getBeginLoc(),
"assigning newly created 'gsl::owner<>' to non-owner %0")
<< BadOwnerAssignment->getLHS()->getType()
<< BadOwnerAssignment->getSourceRange();
@@ -315,7 +315,7 @@ bool OwningMemoryCheck::handleAssignmentFromNewOwner(const BoundNodes &Nodes) {
// Bad initialization of non-owners, where the RHS is a newly created owner.
if (BadOwnerInitialization) {
- diag(BadOwnerInitialization->getLocStart(),
+ diag(BadOwnerInitialization->getBeginLoc(),
"initializing non-owner %0 with a newly created 'gsl::owner<>'")
<< BadOwnerInitialization->getType()
<< BadOwnerInitialization->getSourceRange();
@@ -326,7 +326,7 @@ bool OwningMemoryCheck::handleAssignmentFromNewOwner(const BoundNodes &Nodes) {
// If the type of the variable was deduced, the wrapping owner typedef is
// eliminated, therefore the check emits a special note for that case.
if (Nodes.getNodeAs<AutoType>("deduced_type")) {
- diag(BadOwnerInitialization->getLocStart(),
+ diag(BadOwnerInitialization->getBeginLoc(),
"type deduction did not result in an owner", DiagnosticIDs::Note);
}
return true;
@@ -337,7 +337,7 @@ bool OwningMemoryCheck::handleAssignmentFromNewOwner(const BoundNodes &Nodes) {
if (BadOwnerArgument) {
assert(BadOwnerParameter &&
"parameter for the problematic argument not found");
- diag(BadOwnerArgument->getLocStart(), "initializing non-owner argument of "
+ diag(BadOwnerArgument->getBeginLoc(), "initializing non-owner argument of "
"type %0 with a newly created "
"'gsl::owner<>'")
<< BadOwnerParameter->getType() << BadOwnerArgument->getSourceRange();
@@ -356,7 +356,7 @@ bool OwningMemoryCheck::handleReturnValues(const BoundNodes &Nodes) {
if (BadReturnType) {
// The returned value is a resource or variable that was not annotated with
// owner<> and the function return type is not owner<>.
- diag(BadReturnType->getLocStart(),
+ diag(BadReturnType->getBeginLoc(),
"returning a newly created resource of "
"type %0 or 'gsl::owner<>' from a "
"function whose return type is not 'gsl::owner<>'")
@@ -380,7 +380,7 @@ bool OwningMemoryCheck::handleOwnerMembers(const BoundNodes &Nodes) {
assert(DeclaredOwnerMember &&
"match on class with bad destructor but without a declared owner");
- diag(DeclaredOwnerMember->getLocStart(),
+ diag(DeclaredOwnerMember->getBeginLoc(),
"member variable of type 'gsl::owner<>' requires the class %0 to "
"implement a destructor to release the owned resource")
<< BadClass;
diff --git a/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp
index a81496e1..9fbb1219 100644
--- a/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp
+++ b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp
@@ -93,7 +93,7 @@ void ProBoundsConstantArrayIndexCheck::check(
SourceRange(BaseRange.getEnd().getLocWithOffset(1),
IndexRange.getBegin().getLocWithOffset(-1)),
", ")
- << FixItHint::CreateReplacement(Matched->getLocEnd(), ")");
+ << FixItHint::CreateReplacement(Matched->getEndLoc(), ")");
Optional<FixItHint> Insertion = Inserter->CreateIncludeInsertion(
Result.SourceManager->getMainFileID(), GslHeader,
diff --git a/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp
index d664b640..dfbb51be 100644
--- a/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp
+++ b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp
@@ -21,12 +21,16 @@ void ProBoundsPointerArithmeticCheck::registerMatchers(MatchFinder *Finder) {
if (!getLangOpts().CPlusPlus)
return;
+ const auto AllPointerTypes = anyOf(
+ hasType(pointerType()), hasType(autoType(hasDeducedType(pointerType()))),
+ hasType(decltypeType(hasUnderlyingType(pointerType()))));
+
// Flag all operators +, -, +=, -=, ++, -- that result in a pointer
Finder->addMatcher(
binaryOperator(
anyOf(hasOperatorName("+"), hasOperatorName("-"),
hasOperatorName("+="), hasOperatorName("-=")),
- hasType(pointerType()),
+ AllPointerTypes,
unless(hasLHS(ignoringImpCasts(declRefExpr(to(isImplicit()))))))
.bind("expr"),
this);
@@ -41,7 +45,7 @@ void ProBoundsPointerArithmeticCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
arraySubscriptExpr(
hasBase(ignoringImpCasts(
- anyOf(hasType(pointerType()),
+ anyOf(AllPointerTypes,
hasType(decayedType(hasDecayedType(pointerType())))))))
.bind("expr"),
this);
diff --git a/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp
index 52156ad2..bc2418d6 100644
--- a/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp
+++ b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp
@@ -46,7 +46,7 @@ void ProTypeCstyleCastCheck::check(const MatchFinder::MatchResult &Result) {
MatchedCast->getCastKind() == CK_IntegralToPointer ||
MatchedCast->getCastKind() == CK_PointerToIntegral ||
MatchedCast->getCastKind() == CK_ReinterpretMemberPointer) {
- diag(MatchedCast->getLocStart(),
+ diag(MatchedCast->getBeginLoc(),
"do not use C-style cast to convert between unrelated types");
return;
}
@@ -71,7 +71,7 @@ void ProTypeCstyleCastCheck::check(const MatchFinder::MatchResult &Result) {
*Result.SourceManager, getLangOpts());
auto diag_builder = diag(
- MatchedCast->getLocStart(),
+ MatchedCast->getBeginLoc(),
"do not use C-style cast to downcast from a base to a derived class; "
"use dynamic_cast instead");
@@ -81,7 +81,7 @@ void ProTypeCstyleCastCheck::check(const MatchFinder::MatchResult &Result) {
if (!isa<ParenExpr>(SubExpr)) {
CastText.push_back('(');
diag_builder << FixItHint::CreateInsertion(
- Lexer::getLocForEndOfToken(SubExpr->getLocEnd(), 0,
+ Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0,
*Result.SourceManager, getLangOpts()),
")");
}
@@ -90,7 +90,7 @@ void ProTypeCstyleCastCheck::check(const MatchFinder::MatchResult &Result) {
diag_builder << FixItHint::CreateReplacement(ParenRange, CastText);
} else {
diag(
- MatchedCast->getLocStart(),
+ MatchedCast->getBeginLoc(),
"do not use C-style cast to downcast from a base to a derived class");
}
return;
@@ -98,7 +98,7 @@ void ProTypeCstyleCastCheck::check(const MatchFinder::MatchResult &Result) {
if (MatchedCast->getCastKind() == CK_NoOp &&
needsConstCast(SourceType, MatchedCast->getType())) {
- diag(MatchedCast->getLocStart(),
+ diag(MatchedCast->getBeginLoc(),
"do not use C-style cast to cast away constness");
}
}
diff --git a/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp
index 4d9be815..142ac2ed 100644
--- a/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp
+++ b/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp
@@ -120,7 +120,7 @@ struct IntializerInsertion {
switch (Placement) {
case InitializerPlacement::New:
Location = utils::lexer::getPreviousToken(
- Context, Constructor.getBody()->getLocStart())
+ Context, Constructor.getBody()->getBeginLoc())
.getLocation();
break;
case InitializerPlacement::Before:
@@ -230,7 +230,7 @@ void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag,
const CXXConstructorDecl *Ctor,
const SmallPtrSetImpl<const T *> &DeclsToInit) {
// Do not propose fixes in macros since we cannot place them correctly.
- if (Ctor->getLocStart().isMacroID())
+ if (Ctor->getBeginLoc().isMacroID())
return;
SmallVector<const NamedDecl *, 16> OrderedDecls;
@@ -384,7 +384,7 @@ void ProTypeMemberInitCheck::checkMissingMemberInitializer(
return;
DiagnosticBuilder Diag =
- diag(Ctor ? Ctor->getLocStart() : ClassDecl.getLocation(),
+ diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
IsUnion
? "union constructor should initialize one of these fields: %0"
: "constructor does not initialize these fields: %0")
@@ -392,7 +392,7 @@ void ProTypeMemberInitCheck::checkMissingMemberInitializer(
// Do not propose fixes for constructors in macros since we cannot place them
// correctly.
- if (Ctor && Ctor->getLocStart().isMacroID())
+ if (Ctor && Ctor->getBeginLoc().isMacroID())
return;
// Collect all fields but only suggest a fix for the first member of unions,
@@ -462,7 +462,7 @@ void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
return;
DiagnosticBuilder Diag =
- diag(Ctor ? Ctor->getLocStart() : ClassDecl.getLocation(),
+ diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
"constructor does not initialize these bases: %0")
<< toCommaSeparatedString(AllBases, BasesToInit);
@@ -473,7 +473,7 @@ void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
void ProTypeMemberInitCheck::checkUninitializedTrivialType(
const ASTContext &Context, const VarDecl *Var) {
DiagnosticBuilder Diag =
- diag(Var->getLocStart(), "uninitialized record type: %0") << Var;
+ diag(Var->getBeginLoc(), "uninitialized record type: %0") << Var;
Diag << FixItHint::CreateInsertion(
getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
diff --git a/clang-tidy/fuchsia/DefaultArgumentsCheck.cpp b/clang-tidy/fuchsia/DefaultArgumentsCheck.cpp
index 493ce9a2..e6a67e0a 100644
--- a/clang-tidy/fuchsia/DefaultArgumentsCheck.cpp
+++ b/clang-tidy/fuchsia/DefaultArgumentsCheck.cpp
@@ -27,21 +27,20 @@ void DefaultArgumentsCheck::check(const MatchFinder::MatchResult &Result) {
Result.Nodes.getNodeAs<CXXDefaultArgExpr>("stmt")) {
diag(S->getUsedLocation(),
"calling a function that uses a default argument is disallowed");
- diag(S->getParam()->getLocStart(),
- "default parameter was declared here",
+ diag(S->getParam()->getBeginLoc(), "default parameter was declared here",
DiagnosticIDs::Note);
} else if (const ParmVarDecl *D =
Result.Nodes.getNodeAs<ParmVarDecl>("decl")) {
SourceRange DefaultArgRange = D->getDefaultArgRange();
- if (DefaultArgRange.getEnd() != D->getLocEnd()) {
+ if (DefaultArgRange.getEnd() != D->getEndLoc()) {
return;
} else if (DefaultArgRange.getBegin().isMacroID()) {
- diag(D->getLocStart(),
+ diag(D->getBeginLoc(),
"declaring a parameter with a default argument is disallowed");
} else {
- SourceLocation StartLocation = D->getName().empty() ?
- D->getLocStart() : D->getLocation();
+ SourceLocation StartLocation =
+ D->getName().empty() ? D->getBeginLoc() : D->getLocation();
SourceRange RemovalRange(Lexer::getLocForEndOfToken(
StartLocation, 0,
@@ -51,10 +50,9 @@ void DefaultArgumentsCheck::check(const MatchFinder::MatchResult &Result) {
DefaultArgRange.getEnd()
);
- diag(D->getLocStart(),
- "declaring a parameter with a default argument is disallowed")
- << D
- << FixItHint::CreateRemoval(RemovalRange);
+ diag(D->getBeginLoc(),
+ "declaring a parameter with a default argument is disallowed")
+ << D << FixItHint::CreateRemoval(RemovalRange);
}
}
}
diff --git a/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp b/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp
index d4624d01..a49c952f 100644
--- a/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp
+++ b/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp
@@ -30,6 +30,7 @@ AST_MATCHER(CXXRecordDecl, hasBases) {
// previously.
void MultipleInheritanceCheck::addNodeToInterfaceMap(const CXXRecordDecl *Node,
bool isInterface) {
+ assert(Node->getIdentifier());
StringRef Name = Node->getIdentifier()->getName();
InterfaceMap.insert(std::make_pair(Name, isInterface));
}
@@ -39,6 +40,7 @@ void MultipleInheritanceCheck::addNodeToInterfaceMap(const CXXRecordDecl *Node,
// interface status for the current node is not yet known.
bool MultipleInheritanceCheck::getInterfaceStatus(const CXXRecordDecl *Node,
bool &isInterface) const {
+ assert(Node->getIdentifier());
StringRef Name = Node->getIdentifier()->getName();
llvm::StringMapConstIterator<bool> Pair = InterfaceMap.find(Name);
if (Pair == InterfaceMap.end())
@@ -59,6 +61,9 @@ bool MultipleInheritanceCheck::isCurrentClassInterface(
}
bool MultipleInheritanceCheck::isInterface(const CXXRecordDecl *Node) {
+ if (!Node->getIdentifier())
+ return false;
+
// Short circuit the lookup if we have analyzed this record before.
bool PreviousIsInterfaceResult;
if (getInterfaceStatus(Node, PreviousIsInterfaceResult))
@@ -115,9 +120,8 @@ void MultipleInheritanceCheck::check(const MatchFinder::MatchResult &Result) {
}
if (NumConcrete > 1) {
- diag(D->getLocStart(),
- "inheriting mulitple classes that aren't "
- "pure virtual is discouraged");
+ diag(D->getBeginLoc(), "inheriting mulitple classes that aren't "
+ "pure virtual is discouraged");
}
}
}
diff --git a/clang-tidy/fuchsia/OverloadedOperatorCheck.cpp b/clang-tidy/fuchsia/OverloadedOperatorCheck.cpp
index 847f7635..8e6c74f3 100644
--- a/clang-tidy/fuchsia/OverloadedOperatorCheck.cpp
+++ b/clang-tidy/fuchsia/OverloadedOperatorCheck.cpp
@@ -34,8 +34,8 @@ void OverloadedOperatorCheck::registerMatchers(MatchFinder *Finder) {
void OverloadedOperatorCheck::check(const MatchFinder::MatchResult &Result) {
const auto *D = Result.Nodes.getNodeAs<FunctionDecl>("decl");
assert(D && "No FunctionDecl captured!");
-
- SourceLocation Loc = D->getLocStart();
+
+ SourceLocation Loc = D->getBeginLoc();
if (Loc.isValid())
diag(Loc, "overloading %0 is disallowed") << D;
}
diff --git a/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp b/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp
index 16a534b1..e33f90ac 100644
--- a/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp
+++ b/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp
@@ -51,7 +51,7 @@ void StaticallyConstructedObjectsCheck::registerMatchers(MatchFinder *Finder) {
void StaticallyConstructedObjectsCheck::check(
const MatchFinder::MatchResult &Result) {
if (const auto *D = Result.Nodes.getNodeAs<VarDecl>("decl"))
- diag(D->getLocStart(), "static objects are disallowed; if possible, use a "
+ diag(D->getBeginLoc(), "static objects are disallowed; if possible, use a "
"constexpr constructor instead");
}
diff --git a/clang-tidy/fuchsia/TrailingReturnCheck.cpp b/clang-tidy/fuchsia/TrailingReturnCheck.cpp
index 8494da82..4ffa3f79 100644
--- a/clang-tidy/fuchsia/TrailingReturnCheck.cpp
+++ b/clang-tidy/fuchsia/TrailingReturnCheck.cpp
@@ -19,8 +19,6 @@ namespace tidy {
namespace fuchsia {
namespace {
-const internal::VariadicDynCastAllOfMatcher<Type, DecltypeType> decltypeType;
-
AST_MATCHER(FunctionDecl, hasTrailingReturn) {
return Node.getType()->castAs<FunctionProtoType>()->hasTrailingReturn();
}
@@ -45,7 +43,7 @@ void TrailingReturnCheck::registerMatchers(MatchFinder *Finder) {
void TrailingReturnCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *D = Result.Nodes.getNodeAs<Decl>("decl"))
- diag(D->getLocStart(),
+ diag(D->getBeginLoc(),
"a trailing return type is disallowed for this type of declaration");
}
diff --git a/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp b/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp
index f3da2b6e..82f44103 100644
--- a/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp
+++ b/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp
@@ -35,7 +35,7 @@ void VirtualInheritanceCheck::registerMatchers(MatchFinder *Finder) {
void VirtualInheritanceCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *D = Result.Nodes.getNodeAs<CXXRecordDecl>("decl"))
- diag(D->getLocStart(), "direct virtual inheritance is disallowed");
+ diag(D->getBeginLoc(), "direct virtual inheritance is disallowed");
}
} // namespace fuchsia
diff --git a/clang-tidy/google/AvoidCStyleCastsCheck.cpp b/clang-tidy/google/AvoidCStyleCastsCheck.cpp
index 4c7c164b..83b54ad8 100644
--- a/clang-tidy/google/AvoidCStyleCastsCheck.cpp
+++ b/clang-tidy/google/AvoidCStyleCastsCheck.cpp
@@ -82,7 +82,7 @@ void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
const QualType DestType = DestTypeAsWritten.getCanonicalType();
auto ReplaceRange = CharSourceRange::getCharRange(
- CastExpr->getLParenLoc(), CastExpr->getSubExprAsWritten()->getLocStart());
+ CastExpr->getLParenLoc(), CastExpr->getSubExprAsWritten()->getBeginLoc());
bool FnToFnCast =
isFunction(SourceTypeAsWritten) && isFunction(DestTypeAsWritten);
@@ -93,7 +93,7 @@ void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
// in this case. Don't emit "redundant cast" warnings for function
// pointer/reference types.
if (SourceTypeAsWritten == DestTypeAsWritten) {
- diag(CastExpr->getLocStart(), "redundant cast to the same type")
+ diag(CastExpr->getBeginLoc(), "redundant cast to the same type")
<< FixItHint::CreateRemoval(ReplaceRange);
return;
}
@@ -116,7 +116,7 @@ void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
// Ignore code in .c files #included in other files (which shouldn't be done,
// but people still do this for test and other purposes).
- if (SM.getFilename(SM.getSpellingLoc(CastExpr->getLocStart())).endswith(".c"))
+ if (SM.getFilename(SM.getSpellingLoc(CastExpr->getBeginLoc())).endswith(".c"))
return;
// Leave type spelling exactly as it was (unlike
@@ -128,14 +128,14 @@ void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
SM, getLangOpts());
auto Diag =
- diag(CastExpr->getLocStart(), "C-style casts are discouraged; use %0");
+ diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0");
auto ReplaceWithCast = [&](std::string CastText) {
const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts();
if (!isa<ParenExpr>(SubExpr)) {
CastText.push_back('(');
Diag << FixItHint::CreateInsertion(
- Lexer::getLocForEndOfToken(SubExpr->getLocEnd(), 0, SM,
+ Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0, SM,
getLangOpts()),
")");
}
diff --git a/clang-tidy/google/ExplicitConstructorCheck.cpp b/clang-tidy/google/ExplicitConstructorCheck.cpp
index a03fb56d..778ce890 100644
--- a/clang-tidy/google/ExplicitConstructorCheck.cpp
+++ b/clang-tidy/google/ExplicitConstructorCheck.cpp
@@ -118,7 +118,7 @@ void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) {
};
SourceRange ExplicitTokenRange =
FindToken(*Result.SourceManager, getLangOpts(),
- Ctor->getOuterLocStart(), Ctor->getLocEnd(), isKWExplicit);
+ Ctor->getOuterLocStart(), Ctor->getEndLoc(), isKWExplicit);
StringRef ConstructorDescription;
if (Ctor->isMoveConstructor())
ConstructorDescription = "move";
diff --git a/clang-tidy/google/ExplicitMakePairCheck.cpp b/clang-tidy/google/ExplicitMakePairCheck.cpp
index 6c2f0a36..7e827514 100644
--- a/clang-tidy/google/ExplicitMakePairCheck.cpp
+++ b/clang-tidy/google/ExplicitMakePairCheck.cpp
@@ -60,12 +60,12 @@ void ExplicitMakePairCheck::check(const MatchFinder::MatchResult &Result) {
// make_pair.
if (Arg0->getType() != Call->getArg(0)->getType() ||
Arg1->getType() != Call->getArg(1)->getType()) {
- diag(Call->getLocStart(), "for C++11-compatibility, use pair directly")
+ diag(Call->getBeginLoc(), "for C++11-compatibility, use pair directly")
<< FixItHint::CreateReplacement(
- SourceRange(DeclRef->getLocStart(), DeclRef->getLAngleLoc()),
+ SourceRange(DeclRef->getBeginLoc(), DeclRef->getLAngleLoc()),
"std::pair<");
} else {
- diag(Call->getLocStart(),
+ diag(Call->getBeginLoc(),
"for C++11-compatibility, omit template arguments from make_pair")
<< FixItHint::CreateRemoval(
SourceRange(DeclRef->getLAngleLoc(), DeclRef->getRAngleLoc()));
diff --git a/clang-tidy/google/GlobalNamesInHeadersCheck.cpp b/clang-tidy/google/GlobalNamesInHeadersCheck.cpp
index 45c5b706..9f03f7d5 100644
--- a/clang-tidy/google/GlobalNamesInHeadersCheck.cpp
+++ b/clang-tidy/google/GlobalNamesInHeadersCheck.cpp
@@ -48,15 +48,15 @@ void GlobalNamesInHeadersCheck::registerMatchers(
void GlobalNamesInHeadersCheck::check(const MatchFinder::MatchResult &Result) {
const auto *D = Result.Nodes.getNodeAs<Decl>("using_decl");
// If it comes from a macro, we'll assume it is fine.
- if (D->getLocStart().isMacroID())
+ if (D->getBeginLoc().isMacroID())
return;
// Ignore if it comes from the "main" file ...
if (Result.SourceManager->isInMainFile(
- Result.SourceManager->getExpansionLoc(D->getLocStart()))) {
+ Result.SourceManager->getExpansionLoc(D->getBeginLoc()))) {
// unless that file is a header.
if (!utils::isSpellingLocInHeaderFile(
- D->getLocStart(), *Result.SourceManager, HeaderFileExtensions))
+ D->getBeginLoc(), *Result.SourceManager, HeaderFileExtensions))
return;
}
@@ -70,7 +70,7 @@ void GlobalNamesInHeadersCheck::check(const MatchFinder::MatchResult &Result) {
}
}
- diag(D->getLocStart(),
+ diag(D->getBeginLoc(),
"using declarations in the global namespace in headers are prohibited");
}
diff --git a/clang-tidy/google/GoogleTidyModule.cpp b/clang-tidy/google/GoogleTidyModule.cpp
index 8962f7f6..f4f4118b 100644
--- a/clang-tidy/google/GoogleTidyModule.cpp
+++ b/clang-tidy/google/GoogleTidyModule.cpp
@@ -13,7 +13,6 @@
#include "../readability/BracesAroundStatementsCheck.h"
#include "../readability/FunctionSizeCheck.h"
#include "../readability/NamespaceCommentCheck.h"
-#include "../readability/RedundantSmartptrGetCheck.h"
#include "AvoidCStyleCastsCheck.h"
#include "AvoidThrowingObjCExceptionCheck.h"
#include "DefaultArgumentsCheck.h"
@@ -71,9 +70,6 @@ class GoogleModule : public ClangTidyModule {
CheckFactories
.registerCheck<clang::tidy::readability::NamespaceCommentCheck>(
"google-readability-namespace-comments");
- CheckFactories
- .registerCheck<clang::tidy::readability::RedundantSmartptrGetCheck>(
- "google-readability-redundant-smartptr-get");
}
ClangTidyOptions getModuleOptions() override {
diff --git a/clang-tidy/google/IntegerTypesCheck.cpp b/clang-tidy/google/IntegerTypesCheck.cpp
index 7f0b7658..07ee081a 100644
--- a/clang-tidy/google/IntegerTypesCheck.cpp
+++ b/clang-tidy/google/IntegerTypesCheck.cpp
@@ -72,7 +72,7 @@ void IntegerTypesCheck::registerMatchers(MatchFinder *Finder) {
void IntegerTypesCheck::check(const MatchFinder::MatchResult &Result) {
auto TL = *Result.Nodes.getNodeAs<TypeLoc>("tl");
- SourceLocation Loc = TL.getLocStart();
+ SourceLocation Loc = TL.getBeginLoc();
if (Loc.isInvalid() || Loc.isMacroID())
return;
diff --git a/clang-tidy/google/OverloadedUnaryAndCheck.cpp b/clang-tidy/google/OverloadedUnaryAndCheck.cpp
index 84abb5fd..ff67b534 100644
--- a/clang-tidy/google/OverloadedUnaryAndCheck.cpp
+++ b/clang-tidy/google/OverloadedUnaryAndCheck.cpp
@@ -43,7 +43,7 @@ void OverloadedUnaryAndCheck::registerMatchers(
void OverloadedUnaryAndCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Decl = Result.Nodes.getNodeAs<FunctionDecl>("overload");
- diag(Decl->getLocStart(),
+ diag(Decl->getBeginLoc(),
"do not overload unary operator&, it is dangerous.");
}
diff --git a/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp
index 32ff6e0b..94e1729b 100644
--- a/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp
+++ b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp
@@ -48,7 +48,7 @@ void UnnamedNamespaceInHeaderCheck::registerMatchers(
void UnnamedNamespaceInHeaderCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *N = Result.Nodes.getNodeAs<NamespaceDecl>("anonymousNamespace");
- SourceLocation Loc = N->getLocStart();
+ SourceLocation Loc = N->getBeginLoc();
if (!Loc.isValid())
return;
diff --git a/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp b/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp
index 60a46f88..7490f022 100644
--- a/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp
+++ b/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp
@@ -30,7 +30,7 @@ void UsingNamespaceDirectiveCheck::registerMatchers(
void UsingNamespaceDirectiveCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *U = Result.Nodes.getNodeAs<UsingDirectiveDecl>("usingNamespace");
- SourceLocation Loc = U->getLocStart();
+ SourceLocation Loc = U->getBeginLoc();
if (U->isImplicit() || !Loc.isValid())
return;
diff --git a/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp b/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp
index 298759f8..3ea56d2f 100644
--- a/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp
+++ b/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp
@@ -35,14 +35,14 @@ void ExceptionBaseclassCheck::registerMatchers(MatchFinder *Finder) {
void ExceptionBaseclassCheck::check(const MatchFinder::MatchResult &Result) {
const auto *BadThrow = Result.Nodes.getNodeAs<CXXThrowExpr>("bad_throw");
- diag(BadThrow->getSubExpr()->getLocStart(), "throwing an exception whose "
+ diag(BadThrow->getSubExpr()->getBeginLoc(), "throwing an exception whose "
"type %0 is not derived from "
"'std::exception'")
<< BadThrow->getSubExpr()->getType() << BadThrow->getSourceRange();
const auto *TypeDecl = Result.Nodes.getNodeAs<NamedDecl>("decl");
if (TypeDecl != nullptr)
- diag(TypeDecl->getLocStart(), "type defined here", DiagnosticIDs::Note);
+ diag(TypeDecl->getBeginLoc(), "type defined here", DiagnosticIDs::Note);
}
} // namespace hicpp
diff --git a/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp b/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp
index 68ae54e0..f4cad2e9 100644
--- a/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp
+++ b/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp
@@ -94,7 +94,7 @@ static std::size_t getNumberOfPossibleValues(QualType T,
void MultiwayPathsCoveredCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *ElseIfWithoutElse =
Result.Nodes.getNodeAs<IfStmt>("else-if")) {
- diag(ElseIfWithoutElse->getLocStart(),
+ diag(ElseIfWithoutElse->getBeginLoc(),
"potentially uncovered codepath; add an ending else statement");
return;
}
@@ -120,7 +120,7 @@ void MultiwayPathsCoveredCheck::check(const MatchFinder::MatchResult &Result) {
// FIXME: Evaluate, if emitting a fix-it to simplify that statement is
// reasonable.
if (!SwitchHasDefault && SwitchCaseCount == 0) {
- diag(Switch->getLocStart(),
+ diag(Switch->getBeginLoc(),
"switch statement without labels has no effect");
return;
}
@@ -132,7 +132,7 @@ void MultiwayPathsCoveredCheck::handleSwitchWithDefault(
assert(CaseCount > 0 && "Switch statement with supposedly one default "
"branch did not contain any case labels");
if (CaseCount == 1 || CaseCount == 2)
- diag(Switch->getLocStart(),
+ diag(Switch->getBeginLoc(),
CaseCount == 1
? "degenerated switch with default label only"
: "switch could be better written as an if/else statement");
@@ -172,7 +172,7 @@ void MultiwayPathsCoveredCheck::handleSwitchWithoutDefault(
// FIXME: Transform the 'switch' into an 'if' for CaseCount == 1.
if (CaseCount < MaxPathsPossible)
- diag(Switch->getLocStart(),
+ diag(Switch->getBeginLoc(),
CaseCount == 1 ? "switch with only one case; use an if statement"
: "potential uncovered code path; add a default label");
}
diff --git a/clang-tidy/hicpp/SignedBitwiseCheck.cpp b/clang-tidy/hicpp/SignedBitwiseCheck.cpp
index 03900527..5c76b8e5 100644
--- a/clang-tidy/hicpp/SignedBitwiseCheck.cpp
+++ b/clang-tidy/hicpp/SignedBitwiseCheck.cpp
@@ -75,14 +75,14 @@ void SignedBitwiseCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *UnaryOp = N.getNodeAs<UnaryOperator>("unary-signed")) {
IsUnary = true;
- Location = UnaryOp->getLocStart();
+ Location = UnaryOp->getBeginLoc();
} else {
if (const auto *BinaryOp =
N.getNodeAs<BinaryOperator>("binary-no-sign-interference"))
- Location = BinaryOp->getLocStart();
+ Location = BinaryOp->getBeginLoc();
else if (const auto *BinaryOp =
N.getNodeAs<BinaryOperator>("binary-sign-interference"))
- Location = BinaryOp->getLocStart();
+ Location = BinaryOp->getBeginLoc();
else
llvm_unreachable("unexpected matcher result");
}
diff --git a/clang-tidy/llvm/TwineLocalCheck.cpp b/clang-tidy/llvm/TwineLocalCheck.cpp
index 67c85a67..744ddd9d 100644
--- a/clang-tidy/llvm/TwineLocalCheck.cpp
+++ b/clang-tidy/llvm/TwineLocalCheck.cpp
@@ -48,9 +48,9 @@ void TwineLocalCheck::check(const MatchFinder::MatchResult &Result) {
if (VD->getType()->getCanonicalTypeUnqualified() ==
C->getType()->getCanonicalTypeUnqualified()) {
SourceLocation EndLoc = Lexer::getLocForEndOfToken(
- VD->getInit()->getLocEnd(), 0, *Result.SourceManager, getLangOpts());
+ VD->getInit()->getEndLoc(), 0, *Result.SourceManager, getLangOpts());
Diag << FixItHint::CreateReplacement(TypeRange, "std::string")
- << FixItHint::CreateInsertion(VD->getInit()->getLocStart(), "(")
+ << FixItHint::CreateInsertion(VD->getInit()->getBeginLoc(), "(")
<< FixItHint::CreateInsertion(EndLoc, ").str()");
} else {
// Just an implicit conversion. Insert the real type.
diff --git a/clang-tidy/misc/DefinitionsInHeadersCheck.cpp b/clang-tidy/misc/DefinitionsInHeadersCheck.cpp
index 69ef3ef3..f4dab397 100644
--- a/clang-tidy/misc/DefinitionsInHeadersCheck.cpp
+++ b/clang-tidy/misc/DefinitionsInHeadersCheck.cpp
@@ -22,7 +22,7 @@ namespace {
AST_MATCHER_P(NamedDecl, usesHeaderFileExtension,
utils::HeaderFileExtensionsSet, HeaderFileExtensions) {
return utils::isExpansionLocInHeaderFile(
- Node.getLocStart(), Finder->getASTContext().getSourceManager(),
+ Node.getBeginLoc(), Finder->getASTContext().getSourceManager(),
HeaderFileExtensions);
}
diff --git a/clang-tidy/misc/RedundantExpressionCheck.cpp b/clang-tidy/misc/RedundantExpressionCheck.cpp
index e259b6bf..e9b0f4d0 100644
--- a/clang-tidy/misc/RedundantExpressionCheck.cpp
+++ b/clang-tidy/misc/RedundantExpressionCheck.cpp
@@ -884,8 +884,8 @@ void RedundantExpressionCheck::checkBitwiseExpr(
if (exprEvaluatesToZero(Opcode, Value)) {
diag(Loc, "expression always evaluates to 0");
} else if (exprEvaluatesToBitwiseNegatedZero(Opcode, Value)) {
- SourceRange ConstExprRange(ConstExpr->getLocStart(),
- ConstExpr->getLocEnd());
+ SourceRange ConstExprRange(ConstExpr->getBeginLoc(),
+ ConstExpr->getEndLoc());
StringRef ConstExprText = Lexer::getSourceText(
CharSourceRange::getTokenRange(ConstExprRange), *Result.SourceManager,
Result.Context->getLangOpts());
@@ -893,7 +893,7 @@ void RedundantExpressionCheck::checkBitwiseExpr(
diag(Loc, "expression always evaluates to '%0'") << ConstExprText;
} else if (exprEvaluatesToSymbolic(Opcode, Value)) {
- SourceRange SymExprRange(Sym->getLocStart(), Sym->getLocEnd());
+ SourceRange SymExprRange(Sym->getBeginLoc(), Sym->getEndLoc());
StringRef ExprText = Lexer::getSourceText(
CharSourceRange::getTokenRange(SymExprRange), *Result.SourceManager,
diff --git a/clang-tidy/misc/StaticAssertCheck.cpp b/clang-tidy/misc/StaticAssertCheck.cpp
index 77fb58de..583ed7ad 100644
--- a/clang-tidy/misc/StaticAssertCheck.cpp
+++ b/clang-tidy/misc/StaticAssertCheck.cpp
@@ -88,7 +88,7 @@ void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) {
const auto *AssertExprRoot =
Result.Nodes.getNodeAs<BinaryOperator>("assertExprRoot");
const auto *CastExpr = Result.Nodes.getNodeAs<CStyleCastExpr>("castExpr");
- SourceLocation AssertExpansionLoc = CondStmt->getLocStart();
+ SourceLocation AssertExpansionLoc = CondStmt->getBeginLoc();
if (!AssertExpansionLoc.isValid() || !AssertExpansionLoc.isMacroID())
return;
@@ -129,7 +129,7 @@ void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) {
FixItHints.push_back(FixItHint::CreateRemoval(
SourceRange(AssertExprRoot->getOperatorLoc())));
FixItHints.push_back(FixItHint::CreateRemoval(
- SourceRange(AssertMSG->getLocStart(), AssertMSG->getLocEnd())));
+ SourceRange(AssertMSG->getBeginLoc(), AssertMSG->getEndLoc())));
StaticAssertMSG = (Twine(", \"") + AssertMSG->getString() + "\"").str();
}
diff --git a/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp
index 90398ccd..759c3f0e 100644
--- a/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp
+++ b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp
@@ -81,7 +81,7 @@ void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations(
if (declRef && isCatchVariable(declRef)) {
return;
}
- diag(subExpr->getLocStart(), "throw expression throws a pointer; it should "
+ diag(subExpr->getBeginLoc(), "throw expression throws a pointer; it should "
"throw a non-pointer value instead");
}
// If the throw statement does not throw by pointer then it throws by value
@@ -124,7 +124,7 @@ void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations(
}
}
if (emit)
- diag(subExpr->getLocStart(),
+ diag(subExpr->getBeginLoc(),
"throw expression should throw anonymous temporary values instead");
}
}
@@ -144,7 +144,7 @@ void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
// We do not diagnose when catching pointer to strings since we also allow
// throwing string literals.
if (!PT->getPointeeType()->isAnyCharacterType())
- diag(varDecl->getLocStart(), diagMsgCatchReference);
+ diag(varDecl->getBeginLoc(), diagMsgCatchReference);
} else if (!caughtType->isReferenceType()) {
const char *diagMsgCatchReference = "catch handler catches by value; "
"should catch by reference instead";
@@ -152,7 +152,7 @@ void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
// value". In this case we should emit a diagnosis message unless the type
// is trivial.
if (!caughtType.isTrivialType(context))
- diag(varDecl->getLocStart(), diagMsgCatchReference);
+ diag(varDecl->getBeginLoc(), diagMsgCatchReference);
}
}
diff --git a/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp b/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp
index 0fd7e771..84dd410d 100644
--- a/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp
+++ b/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp
@@ -72,7 +72,7 @@ void UnconventionalAssignOperatorCheck::registerMatchers(
void UnconventionalAssignOperatorCheck::check(
const MatchFinder::MatchResult &Result) {
if (const auto *RetStmt = Result.Nodes.getNodeAs<ReturnStmt>("returnStmt")) {
- diag(RetStmt->getLocStart(), "operator=() should always return '*this'");
+ diag(RetStmt->getBeginLoc(), "operator=() should always return '*this'");
} else {
static const char *const Messages[][2] = {
{"ReturnType", "operator=() should return '%0&'"},
@@ -82,7 +82,7 @@ void UnconventionalAssignOperatorCheck::check(
const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>("method");
for (const auto &Message : Messages) {
if (Result.Nodes.getNodeAs<Decl>(Message[0]))
- diag(Method->getLocStart(), Message[1])
+ diag(Method->getBeginLoc(), Message[1])
<< Method->getParent()->getName()
<< (Method->isConst() ? "const" : "virtual");
}
diff --git a/clang-tidy/misc/UnusedAliasDeclsCheck.cpp b/clang-tidy/misc/UnusedAliasDeclsCheck.cpp
index 07165d65..4beb4320 100644
--- a/clang-tidy/misc/UnusedAliasDeclsCheck.cpp
+++ b/clang-tidy/misc/UnusedAliasDeclsCheck.cpp
@@ -34,9 +34,9 @@ void UnusedAliasDeclsCheck::registerMatchers(MatchFinder *Finder) {
void UnusedAliasDeclsCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *AliasDecl = Result.Nodes.getNodeAs<NamedDecl>("alias")) {
FoundDecls[AliasDecl] = CharSourceRange::getCharRange(
- AliasDecl->getLocStart(),
+ AliasDecl->getBeginLoc(),
Lexer::findLocationAfterToken(
- AliasDecl->getLocEnd(), tok::semi, *Result.SourceManager,
+ AliasDecl->getEndLoc(), tok::semi, *Result.SourceManager,
getLangOpts(),
/*SkipTrailingWhitespaceAndNewLine=*/true));
return;
diff --git a/clang-tidy/misc/UnusedParametersCheck.cpp b/clang-tidy/misc/UnusedParametersCheck.cpp
index e09f5148..a70a53ea 100644
--- a/clang-tidy/misc/UnusedParametersCheck.cpp
+++ b/clang-tidy/misc/UnusedParametersCheck.cpp
@@ -12,6 +12,7 @@
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/STLExtras.h"
#include <unordered_set>
using namespace clang::ast_matchers;
@@ -29,11 +30,10 @@ bool isOverrideMethod(const FunctionDecl *Function) {
} // namespace
void UnusedParametersCheck::registerMatchers(MatchFinder *Finder) {
- Finder->addMatcher(functionDecl(isDefinition(),
- hasBody(stmt(hasDescendant(stmt()))),
- hasAnyParameter(decl()))
- .bind("function"),
- this);
+ Finder->addMatcher(
+ functionDecl(isDefinition(), hasBody(stmt()), hasAnyParameter(decl()))
+ .bind("function"),
+ this);
}
template <typename T>
@@ -41,15 +41,15 @@ static CharSourceRange removeNode(const MatchFinder::MatchResult &Result,
const T *PrevNode, const T *Node,
const T *NextNode) {
if (NextNode)
- return CharSourceRange::getCharRange(Node->getLocStart(),
- NextNode->getLocStart());
+ return CharSourceRange::getCharRange(Node->getBeginLoc(),
+ NextNode->getBeginLoc());
if (PrevNode)
return CharSourceRange::getTokenRange(
- Lexer::getLocForEndOfToken(PrevNode->getLocEnd(), 0,
+ Lexer::getLocForEndOfToken(PrevNode->getEndLoc(), 0,
*Result.SourceManager,
Result.Context->getLangOpts()),
- Node->getLocEnd());
+ Node->getEndLoc());
return CharSourceRange::getTokenRange(Node->getSourceRange());
}
@@ -122,7 +122,12 @@ UnusedParametersCheck::~UnusedParametersCheck() = default;
UnusedParametersCheck::UnusedParametersCheck(StringRef Name,
ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context) {}
+ : ClangTidyCheck(Name, Context),
+ StrictMode(Options.getLocalOrGlobal("StrictMode", 0) != 0) {}
+
+void UnusedParametersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "StrictMode", StrictMode);
+}
void UnusedParametersCheck::warnOnUnusedParameter(
const MatchFinder::MatchResult &Result, const FunctionDecl *Function,
@@ -154,8 +159,9 @@ void UnusedParametersCheck::warnOnUnusedParameter(
MyDiag << removeParameter(Result, FD, ParamIndex);
// Fix all call sites.
- for (const auto *Call : Indexer->getFnCalls(Function))
- MyDiag << removeArgument(Result, Call, ParamIndex);
+ for (const CallExpr *Call : Indexer->getFnCalls(Function))
+ if (ParamIndex < Call->getNumArgs()) // See PR38055 for example.
+ MyDiag << removeArgument(Result, Call, ParamIndex);
}
void UnusedParametersCheck::check(const MatchFinder::MatchResult &Result) {
@@ -170,7 +176,15 @@ void UnusedParametersCheck::check(const MatchFinder::MatchResult &Result) {
if (Param->isUsed() || Param->isReferenced() || !Param->getDeclName() ||
Param->hasAttr<UnusedAttr>())
continue;
- warnOnUnusedParameter(Result, Function, i);
+
+ // In non-strict mode ignore function definitions with empty bodies
+ // (constructor initializer counts for non-empty body).
+ if (StrictMode ||
+ (Function->getBody()->child_begin() !=
+ Function->getBody()->child_end()) ||
+ (isa<CXXConstructorDecl>(Function) &&
+ cast<CXXConstructorDecl>(Function)->getNumCtorInitializers() > 0))
+ warnOnUnusedParameter(Result, Function, i);
}
}
diff --git a/clang-tidy/misc/UnusedParametersCheck.h b/clang-tidy/misc/UnusedParametersCheck.h
index 414ef112..b9bae26f 100644
--- a/clang-tidy/misc/UnusedParametersCheck.h
+++ b/clang-tidy/misc/UnusedParametersCheck.h
@@ -24,8 +24,10 @@ public:
~UnusedParametersCheck();
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
private:
+ const bool StrictMode;
class IndexerVisitor;
std::unique_ptr<IndexerVisitor> Indexer;
diff --git a/clang-tidy/misc/UnusedUsingDeclsCheck.cpp b/clang-tidy/misc/UnusedUsingDeclsCheck.cpp
index b3eabdcd..48009b5a 100644
--- a/clang-tidy/misc/UnusedUsingDeclsCheck.cpp
+++ b/clang-tidy/misc/UnusedUsingDeclsCheck.cpp
@@ -68,9 +68,9 @@ void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult &Result) {
UsingDeclContext Context(Using);
Context.UsingDeclRange = CharSourceRange::getCharRange(
- Using->getLocStart(),
+ Using->getBeginLoc(),
Lexer::findLocationAfterToken(
- Using->getLocEnd(), tok::semi, *Result.SourceManager, getLangOpts(),
+ Using->getEndLoc(), tok::semi, *Result.SourceManager, getLangOpts(),
/*SkipTrailingWhitespaceAndNewLine=*/true));
for (const auto *UsingShadow : Using->shadows()) {
const auto *TargetDecl = UsingShadow->getTargetDecl()->getCanonicalDecl();
diff --git a/clang-tidy/modernize/AvoidBindCheck.cpp b/clang-tidy/modernize/AvoidBindCheck.cpp
index 7cdbb66f..bd477026 100644
--- a/clang-tidy/modernize/AvoidBindCheck.cpp
+++ b/clang-tidy/modernize/AvoidBindCheck.cpp
@@ -59,7 +59,7 @@ buildBindArguments(const MatchFinder::MatchResult &Result, const CallExpr *C) {
}
B.Tokens = Lexer::getSourceText(
- CharSourceRange::getTokenRange(E->getLocStart(), E->getLocEnd()),
+ CharSourceRange::getTokenRange(E->getBeginLoc(), E->getEndLoc()),
*Result.SourceManager, Result.Context->getLangOpts());
SmallVector<StringRef, 2> Matches;
@@ -131,7 +131,7 @@ void AvoidBindCheck::registerMatchers(MatchFinder *Finder) {
void AvoidBindCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("bind");
- auto Diag = diag(MatchedDecl->getLocStart(), "prefer a lambda to std::bind");
+ auto Diag = diag(MatchedDecl->getBeginLoc(), "prefer a lambda to std::bind");
const auto Args = buildBindArguments(Result, MatchedDecl);
diff --git a/clang-tidy/modernize/MakeSmartPtrCheck.cpp b/clang-tidy/modernize/MakeSmartPtrCheck.cpp
index 8deaa83a..97580fb7 100644
--- a/clang-tidy/modernize/MakeSmartPtrCheck.cpp
+++ b/clang-tidy/modernize/MakeSmartPtrCheck.cpp
@@ -199,9 +199,9 @@ void MakeSmartPtrCheck::checkReset(SourceManager &SM,
const auto *Expr = cast<MemberExpr>(Reset->getCallee());
SourceLocation OperatorLoc = Expr->getOperatorLoc();
SourceLocation ResetCallStart = Reset->getExprLoc();
- SourceLocation ExprStart = Expr->getLocStart();
+ SourceLocation ExprStart = Expr->getBeginLoc();
SourceLocation ExprEnd =
- Lexer::getLocForEndOfToken(Expr->getLocEnd(), 0, SM, getLangOpts());
+ Lexer::getLocForEndOfToken(Expr->getEndLoc(), 0, SM, getLangOpts());
bool InMacro = ExprStart.isMacroID();
@@ -353,7 +353,7 @@ bool MakeSmartPtrCheck::replaceNew(DiagnosticBuilder &Diag,
// Has to be replaced with:
// smart_ptr<Pair>(Pair{first, second});
InitRange = SourceRange(
- New->getAllocatedTypeSourceInfo()->getTypeLoc().getLocStart(),
+ New->getAllocatedTypeSourceInfo()->getTypeLoc().getBeginLoc(),
New->getInitializer()->getSourceRange().getEnd());
}
Diag << FixItHint::CreateRemoval(
diff --git a/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tidy/modernize/PassByValueCheck.cpp
index 1ab73d50..f54c5754 100644
--- a/clang-tidy/modernize/PassByValueCheck.cpp
+++ b/clang-tidy/modernize/PassByValueCheck.cpp
@@ -194,7 +194,7 @@ void PassByValueCheck::check(const MatchFinder::MatchResult &Result) {
*Result.Context))
return;
- auto Diag = diag(ParamDecl->getLocStart(), "pass by value and use std::move");
+ auto Diag = diag(ParamDecl->getBeginLoc(), "pass by value and use std::move");
// Iterate over all declarations of the constructor.
for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {
@@ -206,8 +206,8 @@ void PassByValueCheck::check(const MatchFinder::MatchResult &Result) {
continue;
TypeLoc ValueTL = RefTL.getPointeeLoc();
- auto TypeRange = CharSourceRange::getTokenRange(ParmDecl->getLocStart(),
- ParamTL.getLocEnd());
+ auto TypeRange = CharSourceRange::getTokenRange(ParmDecl->getBeginLoc(),
+ ParamTL.getEndLoc());
std::string ValueStr = Lexer::getSourceText(CharSourceRange::getTokenRange(
ValueTL.getSourceRange()),
SM, getLangOpts())
diff --git a/clang-tidy/modernize/RawStringLiteralCheck.cpp b/clang-tidy/modernize/RawStringLiteralCheck.cpp
index 868c5c7c..a4aef41f 100644
--- a/clang-tidy/modernize/RawStringLiteralCheck.cpp
+++ b/clang-tidy/modernize/RawStringLiteralCheck.cpp
@@ -129,14 +129,14 @@ void RawStringLiteralCheck::registerMatchers(MatchFinder *Finder) {
void RawStringLiteralCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Literal = Result.Nodes.getNodeAs<StringLiteral>("lit");
- if (Literal->getLocStart().isMacroID())
+ if (Literal->getBeginLoc().isMacroID())
return;
if (containsEscapedCharacters(Result, Literal, DisallowedChars)) {
std::string Replacement = asRawStringLiteral(Literal, DelimiterStem);
if (ReplaceShorterLiterals ||
Replacement.length() <=
- Lexer::MeasureTokenLength(Literal->getLocStart(),
+ Lexer::MeasureTokenLength(Literal->getBeginLoc(),
*Result.SourceManager, getLangOpts()))
replaceWithRawStringLiteral(Result, Literal, Replacement);
}
@@ -148,7 +148,7 @@ void RawStringLiteralCheck::replaceWithRawStringLiteral(
CharSourceRange CharRange = Lexer::makeFileCharRange(
CharSourceRange::getTokenRange(Literal->getSourceRange()),
*Result.SourceManager, getLangOpts());
- diag(Literal->getLocStart(),
+ diag(Literal->getBeginLoc(),
"escaped string literal can be written as a raw string literal")
<< FixItHint::CreateReplacement(CharRange, Replacement);
}
diff --git a/clang-tidy/modernize/RedundantVoidArgCheck.cpp b/clang-tidy/modernize/RedundantVoidArgCheck.cpp
index 6701fa45..f0d1fdb8 100644
--- a/clang-tidy/modernize/RedundantVoidArgCheck.cpp
+++ b/clang-tidy/modernize/RedundantVoidArgCheck.cpp
@@ -103,9 +103,9 @@ void RedundantVoidArgCheck::processFunctionDecl(
const MatchFinder::MatchResult &Result, const FunctionDecl *Function) {
if (Function->isThisDeclarationADefinition()) {
const Stmt *Body = Function->getBody();
- SourceLocation Start = Function->getLocStart();
+ SourceLocation Start = Function->getBeginLoc();
SourceLocation End =
- Body ? Body->getLocStart().getLocWithOffset(-1) : Function->getLocEnd();
+ Body ? Body->getBeginLoc().getLocWithOffset(-1) : Function->getEndLoc();
removeVoidArgumentTokens(Result, SourceRange(Start, End),
"function definition");
} else {
@@ -198,10 +198,10 @@ void RedundantVoidArgCheck::processFieldDecl(
void RedundantVoidArgCheck::processVarDecl(
const MatchFinder::MatchResult &Result, const VarDecl *Var) {
if (protoTypeHasNoParms(Var->getType())) {
- SourceLocation Begin = Var->getLocStart();
+ SourceLocation Begin = Var->getBeginLoc();
if (Var->hasInit()) {
SourceLocation InitStart =
- Result.SourceManager->getExpansionLoc(Var->getInit()->getLocStart())
+ Result.SourceManager->getExpansionLoc(Var->getInit()->getBeginLoc())
.getLocWithOffset(-1);
removeVoidArgumentTokens(Result, SourceRange(Begin, InitStart),
"variable declaration with initializer");
@@ -237,7 +237,7 @@ void RedundantVoidArgCheck::processLambdaExpr(
Lambda->hasExplicitParameters()) {
SourceLocation Begin =
Lambda->getIntroducerRange().getEnd().getLocWithOffset(1);
- SourceLocation End = Lambda->getBody()->getLocStart().getLocWithOffset(-1);
+ SourceLocation End = Lambda->getBody()->getBeginLoc().getLocWithOffset(-1);
removeVoidArgumentTokens(Result, SourceRange(Begin, End),
"lambda expression");
}
diff --git a/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp b/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp
index cd9aa11d..9a715693 100644
--- a/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp
+++ b/clang-tidy/modernize/ReplaceRandomShuffleCheck.cpp
@@ -62,13 +62,13 @@ void ReplaceRandomShuffleCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedArgumentThree = Result.Nodes.getNodeAs<Expr>("randomFunc");
const auto *MatchedCallExpr = Result.Nodes.getNodeAs<CallExpr>("match");
- if (MatchedCallExpr->getLocStart().isMacroID())
+ if (MatchedCallExpr->getBeginLoc().isMacroID())
return;
auto Diag = [&] {
if (MatchedCallExpr->getNumArgs() == 3) {
auto DiagL =
- diag(MatchedCallExpr->getLocStart(),
+ diag(MatchedCallExpr->getBeginLoc(),
"'std::random_shuffle' has been removed in C++17; use "
"'std::shuffle' and an alternative random mechanism instead");
DiagL << FixItHint::CreateReplacement(
@@ -76,7 +76,7 @@ void ReplaceRandomShuffleCheck::check(const MatchFinder::MatchResult &Result) {
"std::mt19937(std::random_device()())");
return DiagL;
} else {
- auto DiagL = diag(MatchedCallExpr->getLocStart(),
+ auto DiagL = diag(MatchedCallExpr->getBeginLoc(),
"'std::random_shuffle' has been removed in C++17; use "
"'std::shuffle' instead");
DiagL << FixItHint::CreateInsertion(
@@ -94,12 +94,12 @@ void ReplaceRandomShuffleCheck::check(const MatchFinder::MatchResult &Result) {
NewName = "std::" + NewName;
Diag << FixItHint::CreateRemoval(MatchedDecl->getSourceRange());
- Diag << FixItHint::CreateInsertion(MatchedDecl->getLocStart(), NewName);
+ Diag << FixItHint::CreateInsertion(MatchedDecl->getBeginLoc(), NewName);
if (Optional<FixItHint> IncludeFixit =
IncludeInserter->CreateIncludeInsertion(
Result.Context->getSourceManager().getFileID(
- MatchedCallExpr->getLocStart()),
+ MatchedCallExpr->getBeginLoc()),
"random", /*IsAngled=*/true))
Diag << IncludeFixit.getValue();
}
diff --git a/clang-tidy/modernize/ShrinkToFitCheck.cpp b/clang-tidy/modernize/ShrinkToFitCheck.cpp
index ef920182..bc0749b9 100644
--- a/clang-tidy/modernize/ShrinkToFitCheck.cpp
+++ b/clang-tidy/modernize/ShrinkToFitCheck.cpp
@@ -43,8 +43,8 @@ void ShrinkToFitCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
cxxMemberCallExpr(
- on(hasType(namedDecl(
- hasAnyName("std::basic_string", "std::deque", "std::vector")))),
+ on(hasType(hasCanonicalType(hasDeclaration(namedDecl(
+ hasAnyName("std::basic_string", "std::deque", "std::vector")))))),
callee(cxxMethodDecl(hasName("swap"))),
has(ignoringParenImpCasts(memberExpr(hasDescendant(CopyCtorCall)))),
hasArgument(0, SwapParam.bind("ContainerToShrink")),
@@ -59,7 +59,7 @@ void ShrinkToFitCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Container = Result.Nodes.getNodeAs<Expr>("ContainerToShrink");
FixItHint Hint;
- if (!MemberCall->getLocStart().isMacroID()) {
+ if (!MemberCall->getBeginLoc().isMacroID()) {
const LangOptions &Opts = getLangOpts();
std::string ReplacementText;
if (const auto *UnaryOp = llvm::dyn_cast<UnaryOperator>(Container)) {
@@ -79,7 +79,7 @@ void ShrinkToFitCheck::check(const MatchFinder::MatchResult &Result) {
ReplacementText);
}
- diag(MemberCall->getLocStart(), "the shrink_to_fit method should be used "
+ diag(MemberCall->getBeginLoc(), "the shrink_to_fit method should be used "
"to reduce the capacity of a shrinkable "
"container")
<< Hint;
diff --git a/clang-tidy/modernize/UnaryStaticAssertCheck.cpp b/clang-tidy/modernize/UnaryStaticAssertCheck.cpp
index 67c00630..5ddbb933 100644
--- a/clang-tidy/modernize/UnaryStaticAssertCheck.cpp
+++ b/clang-tidy/modernize/UnaryStaticAssertCheck.cpp
@@ -32,7 +32,7 @@ void UnaryStaticAssertCheck::check(const MatchFinder::MatchResult &Result) {
SourceLocation Loc = MatchedDecl->getLocation();
if (!AssertMessage || AssertMessage->getLength() ||
- AssertMessage->getLocStart().isMacroID() || Loc.isMacroID())
+ AssertMessage->getBeginLoc().isMacroID() || Loc.isMacroID())
return;
diag(Loc,
diff --git a/clang-tidy/modernize/UseAutoCheck.cpp b/clang-tidy/modernize/UseAutoCheck.cpp
index 23478aef..86871622 100644
--- a/clang-tidy/modernize/UseAutoCheck.cpp
+++ b/clang-tidy/modernize/UseAutoCheck.cpp
@@ -11,6 +11,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/CharInfo.h"
#include "clang/Tooling/FixIt.h"
using namespace clang;
@@ -27,6 +28,34 @@ const char DeclWithNewId[] = "decl_new";
const char DeclWithCastId[] = "decl_cast";
const char DeclWithTemplateCastId[] = "decl_template";
+size_t GetTypeNameLength(bool RemoveStars, StringRef Text) {
+ enum CharType { Space, Alpha, Punctuation };
+ CharType LastChar = Space, BeforeSpace = Punctuation;
+ size_t NumChars = 0;
+ int TemplateTypenameCntr = 0;
+ for (const unsigned char C : Text) {
+ if (C == '<')
+ ++TemplateTypenameCntr;
+ else if (C == '>')
+ --TemplateTypenameCntr;
+ const CharType NextChar =
+ isAlphanumeric(C)
+ ? Alpha
+ : (isWhitespace(C) ||
+ (!RemoveStars && TemplateTypenameCntr == 0 && C == '*'))
+ ? Space
+ : Punctuation;
+ if (NextChar != Space) {
+ ++NumChars; // Count the non-space character.
+ if (LastChar == Space && NextChar == Alpha && BeforeSpace == Alpha)
+ ++NumChars; // Count a single space character between two words.
+ BeforeSpace = NextChar;
+ }
+ LastChar = NextChar;
+ }
+ return NumChars;
+}
+
/// \brief Matches variable declarations that have explicit initializers that
/// are not initializer lists.
///
@@ -419,8 +448,10 @@ void UseAutoCheck::replaceExpr(
SourceRange Range(Loc.getSourceRange());
if (MinTypeNameLength != 0 &&
- tooling::fixit::getText(Loc.getSourceRange(), FirstDecl->getASTContext())
- .size() < MinTypeNameLength)
+ GetTypeNameLength(RemoveStars,
+ tooling::fixit::getText(Loc.getSourceRange(),
+ FirstDecl->getASTContext())) <
+ MinTypeNameLength)
return;
auto Diag = diag(Range.getBegin(), Message);
diff --git a/clang-tidy/modernize/UseBoolLiteralsCheck.cpp b/clang-tidy/modernize/UseBoolLiteralsCheck.cpp
index ece8cd58..13afbb43 100644
--- a/clang-tidy/modernize/UseBoolLiteralsCheck.cpp
+++ b/clang-tidy/modernize/UseBoolLiteralsCheck.cpp
@@ -57,7 +57,7 @@ void UseBoolLiteralsCheck::check(const MatchFinder::MatchResult &Result) {
const Expr *Expression = Cast ? Cast : Literal;
- bool InMacro = Expression->getLocStart().isMacroID();
+ bool InMacro = Expression->getBeginLoc().isMacroID();
if (InMacro && IgnoreMacros)
return;
diff --git a/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp b/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp
index c14c6853..374fcb34 100644
--- a/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp
+++ b/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp
@@ -202,7 +202,7 @@ void UseDefaultMemberInitCheck::checkDefaultInit(
const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) {
const FieldDecl *Field = Init->getAnyMember();
- SourceLocation StartLoc = Field->getLocStart();
+ SourceLocation StartLoc = Field->getBeginLoc();
if (StartLoc.isMacroID() && IgnoreMacros)
return;
diff --git a/clang-tidy/modernize/UseEmplaceCheck.cpp b/clang-tidy/modernize/UseEmplaceCheck.cpp
index 4d5d8014..b6c142d1 100644
--- a/clang-tidy/modernize/UseEmplaceCheck.cpp
+++ b/clang-tidy/modernize/UseEmplaceCheck.cpp
@@ -141,7 +141,7 @@ void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, EmplacePrefix);
const SourceRange CallParensRange =
- MakeCall ? SourceRange(MakeCall->getCallee()->getLocEnd(),
+ MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),
MakeCall->getRParenLoc())
: CtorCall->getParenOrBraceRange();
diff --git a/clang-tidy/modernize/UseEqualsDefaultCheck.cpp b/clang-tidy/modernize/UseEqualsDefaultCheck.cpp
index da716243..d67bb799 100644
--- a/clang-tidy/modernize/UseEqualsDefaultCheck.cpp
+++ b/clang-tidy/modernize/UseEqualsDefaultCheck.cpp
@@ -97,6 +97,7 @@ static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context,
isMemberInitializer(), forField(equalsNode(Field)),
withInitializer(anyOf(
AccessToFieldInParam,
+ initListExpr(has(AccessToFieldInParam)),
cxxConstructExpr(allOf(
hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
argumentCountIs(1),
@@ -297,7 +298,7 @@ void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) {
// expansion locations are reported.
SourceLocation Location = SpecialFunctionDecl->getLocation();
if (Location.isMacroID())
- Location = Body->getLocStart();
+ Location = Body->getBeginLoc();
auto Diag = diag(Location, "use '= default' to define a trivial " +
SpecialFunctionName);
diff --git a/clang-tidy/modernize/UseEqualsDeleteCheck.cpp b/clang-tidy/modernize/UseEqualsDeleteCheck.cpp
index 18190b19..f5adb13f 100644
--- a/clang-tidy/modernize/UseEqualsDeleteCheck.cpp
+++ b/clang-tidy/modernize/UseEqualsDeleteCheck.cpp
@@ -55,7 +55,7 @@ void UseEqualsDeleteCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Func =
Result.Nodes.getNodeAs<CXXMethodDecl>(SpecialFunction)) {
SourceLocation EndLoc = Lexer::getLocForEndOfToken(
- Func->getLocEnd(), 0, *Result.SourceManager, getLangOpts());
+ Func->getEndLoc(), 0, *Result.SourceManager, getLangOpts());
// FIXME: Improve FixItHint to make the method public.
diag(Func->getLocation(),
diff --git a/clang-tidy/modernize/UseNullptrCheck.cpp b/clang-tidy/modernize/UseNullptrCheck.cpp
index 476c549e..3bf09c1f 100644
--- a/clang-tidy/modernize/UseNullptrCheck.cpp
+++ b/clang-tidy/modernize/UseNullptrCheck.cpp
@@ -125,7 +125,7 @@ public:
}
bool VisitStmt(Stmt *S) {
- if (SM.getFileLoc(S->getLocStart()) != CastLoc)
+ if (SM.getFileLoc(S->getBeginLoc()) != CastLoc)
return true;
Visited = true;
@@ -214,8 +214,8 @@ public:
return true;
}
- SourceLocation StartLoc = FirstSubExpr->getLocStart();
- SourceLocation EndLoc = FirstSubExpr->getLocEnd();
+ SourceLocation StartLoc = FirstSubExpr->getBeginLoc();
+ SourceLocation EndLoc = FirstSubExpr->getEndLoc();
// If the location comes from a macro arg expansion, *all* uses of that
// arg must be checked to result in NullTo(Member)Pointer casts.
@@ -269,7 +269,7 @@ private:
/// \brief Tests that all expansions of a macro arg, one of which expands to
/// result in \p CE, yield NullTo(Member)Pointer casts.
bool allArgUsesValid(const CastExpr *CE) {
- SourceLocation CastLoc = CE->getLocStart();
+ SourceLocation CastLoc = CE->getBeginLoc();
// Step 1: Get location of macro arg and location of the macro the arg was
// provided to.
@@ -437,9 +437,9 @@ private:
SourceLocation Loc;
if (const auto *D = Parent.get<Decl>())
- Loc = D->getLocStart();
+ Loc = D->getBeginLoc();
else if (const auto *S = Parent.get<Stmt>())
- Loc = S->getLocStart();
+ Loc = S->getBeginLoc();
// TypeLoc and NestedNameSpecifierLoc are members of the parent map. Skip
// them and keep going up.
diff --git a/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp b/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp
index 7f592007..0389a5ed 100644
--- a/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp
+++ b/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp
@@ -85,8 +85,8 @@ void UseTransparentFunctorsCheck::check(
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("FunctorClass");
if (const auto *FuncInst =
Result.Nodes.getNodeAs<CXXConstructExpr>("FuncInst")) {
- diag(FuncInst->getLocStart(), Message)
- << (FuncClass->getName() + "<>").str();
+ diag(FuncInst->getBeginLoc(), Message)
+ << (FuncClass->getName() + "<>").str();
return;
}
diff --git a/clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp b/clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp
index 9ac6e1d5..0367b139 100644
--- a/clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp
+++ b/clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp
@@ -57,15 +57,15 @@ void UseUncaughtExceptionsCheck::check(const MatchFinder::MatchResult &Result) {
bool WarnOnly = false;
if (C) {
- BeginLoc = C->getLocStart();
- EndLoc = C->getLocEnd();
+ BeginLoc = C->getBeginLoc();
+ EndLoc = C->getEndLoc();
} else if (const auto *E = Result.Nodes.getNodeAs<CallExpr>("call_expr")) {
- BeginLoc = E->getLocStart();
- EndLoc = E->getLocEnd();
+ BeginLoc = E->getBeginLoc();
+ EndLoc = E->getEndLoc();
} else if (const auto *D =
Result.Nodes.getNodeAs<DeclRefExpr>("decl_ref_expr")) {
- BeginLoc = D->getLocStart();
- EndLoc = D->getLocEnd();
+ BeginLoc = D->getBeginLoc();
+ EndLoc = D->getEndLoc();
WarnOnly = true;
} else {
const auto *U = Result.Nodes.getNodeAs<UsingDecl>("using_decl");
diff --git a/clang-tidy/modernize/UseUsingCheck.cpp b/clang-tidy/modernize/UseUsingCheck.cpp
index cc6d77d9..5244aa60 100644
--- a/clang-tidy/modernize/UseUsingCheck.cpp
+++ b/clang-tidy/modernize/UseUsingCheck.cpp
@@ -83,7 +83,7 @@ void UseUsingCheck::check(const MatchFinder::MatchResult &Result) {
auto &Context = *Result.Context;
auto &SM = *Result.SourceManager;
- SourceLocation StartLoc = MatchedDecl->getLocStart();
+ SourceLocation StartLoc = MatchedDecl->getBeginLoc();
if (StartLoc.isMacroID() && IgnoreMacros)
return;
diff --git a/clang-tidy/objc/AvoidNSErrorInitCheck.cpp b/clang-tidy/objc/AvoidNSErrorInitCheck.cpp
index 86c4656c..a4dc48eb 100644
--- a/clang-tidy/objc/AvoidNSErrorInitCheck.cpp
+++ b/clang-tidy/objc/AvoidNSErrorInitCheck.cpp
@@ -31,7 +31,7 @@ void AvoidNSErrorInitCheck::registerMatchers(MatchFinder *Finder) {
void AvoidNSErrorInitCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedExpr =
Result.Nodes.getNodeAs<ObjCMessageExpr>("nserrorInit");
- diag(MatchedExpr->getLocStart(),
+ diag(MatchedExpr->getBeginLoc(),
"use errorWithDomain:code:userInfo: or initWithDomain:code:userInfo: to "
"create a new NSError");
}
diff --git a/clang-tidy/objc/AvoidSpinlockCheck.cpp b/clang-tidy/objc/AvoidSpinlockCheck.cpp
index 21ec3642..319d9456 100644
--- a/clang-tidy/objc/AvoidSpinlockCheck.cpp
+++ b/clang-tidy/objc/AvoidSpinlockCheck.cpp
@@ -27,7 +27,7 @@ void AvoidSpinlockCheck::registerMatchers(MatchFinder *Finder) {
void AvoidSpinlockCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedExpr = Result.Nodes.getNodeAs<CallExpr>("spinlock");
- diag(MatchedExpr->getLocStart(),
+ diag(MatchedExpr->getBeginLoc(),
"use os_unfair_lock_lock() or dispatch queue APIs instead of the "
"deprecated OSSpinLock");
}
diff --git a/clang-tidy/objc/PropertyDeclarationCheck.cpp b/clang-tidy/objc/PropertyDeclarationCheck.cpp
index b61c1b99..d7715fa2 100644
--- a/clang-tidy/objc/PropertyDeclarationCheck.cpp
+++ b/clang-tidy/objc/PropertyDeclarationCheck.cpp
@@ -34,7 +34,7 @@ enum NamingStyle {
CategoryProperty = 2,
};
-/// The acronyms are from
+/// The acronyms are aggregated from multiple sources including
/// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE
///
/// Keep this list sorted.
@@ -45,11 +45,13 @@ constexpr llvm::StringLiteral DefaultSpecialAcronyms[] = {
"AR",
"ARGB",
"ASCII",
+ "AV",
"BGRA",
"CA",
"CF",
"CG",
"CI",
+ "CRC",
"CV",
"CMYK",
"DNS",
@@ -61,6 +63,7 @@ constexpr llvm::StringLiteral DefaultSpecialAcronyms[] = {
"GUID",
"HD",
"HDR",
+ "HMAC",
"HTML",
"HTTP",
"HTTPS",
@@ -70,6 +73,8 @@ constexpr llvm::StringLiteral DefaultSpecialAcronyms[] = {
"JS",
"LAN",
"LZW",
+ "MAC",
+ "MD",
"MDNS",
"MIDI",
"NS",
@@ -85,12 +90,15 @@ constexpr llvm::StringLiteral DefaultSpecialAcronyms[] = {
"RGB",
"RGBA",
"RGBX",
+ "RIPEMD",
"ROM",
"RPC",
"RTF",
"RTL",
"SC",
"SDK",
+ "SHA",
+ "SQL",
"SSO",
"TCP",
"TIFF",
@@ -153,7 +161,7 @@ std::string validPropertyNameRegex(llvm::ArrayRef<std::string> EscapedAcronyms,
std::string StartMatcher = UsedInMatcher ? "::" : "^";
std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms);
return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" +
- AcronymsMatcher + "|([A-Z][a-z0-9]+))*$";
+ AcronymsMatcher + "|([A-Z][a-z0-9]+)|A|I)*$";
}
bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) {
diff --git a/clang-tidy/performance/FasterStringFindCheck.cpp b/clang-tidy/performance/FasterStringFindCheck.cpp
index eddc52b6..0c3d249f 100644
--- a/clang-tidy/performance/FasterStringFindCheck.cpp
+++ b/clang-tidy/performance/FasterStringFindCheck.cpp
@@ -90,13 +90,14 @@ void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) {
if (!Replacement)
return;
- diag(Literal->getLocStart(), "%0 called with a string literal consisting of "
+ diag(Literal->getBeginLoc(), "%0 called with a string literal consisting of "
"a single character; consider using the more "
"effective overload accepting a character")
- << FindFunc << FixItHint::CreateReplacement(
- CharSourceRange::getTokenRange(Literal->getLocStart(),
- Literal->getLocEnd()),
- *Replacement);
+ << FindFunc
+ << FixItHint::CreateReplacement(
+ CharSourceRange::getTokenRange(Literal->getBeginLoc(),
+ Literal->getEndLoc()),
+ *Replacement);
}
} // namespace performance
diff --git a/clang-tidy/performance/ForRangeCopyCheck.cpp b/clang-tidy/performance/ForRangeCopyCheck.cpp
index 0b9dd230..c8c90638 100644
--- a/clang-tidy/performance/ForRangeCopyCheck.cpp
+++ b/clang-tidy/performance/ForRangeCopyCheck.cpp
@@ -8,7 +8,7 @@
//===----------------------------------------------------------------------===//
#include "ForRangeCopyCheck.h"
-#include "../utils/DeclRefExprUtils.h"
+#include "../utils/ExprMutationAnalyzer.h"
#include "../utils/FixItHintUtils.h"
#include "../utils/TypeTraits.h"
@@ -41,7 +41,7 @@ void ForRangeCopyCheck::registerMatchers(MatchFinder *Finder) {
void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Var = Result.Nodes.getNodeAs<VarDecl>("loopVar");
// Ignore code in macros since we can't place the fixes correctly.
- if (Var->getLocStart().isMacroID())
+ if (Var->getBeginLoc().isMacroID())
return;
if (handleConstValueCopy(*Var, *Result.Context))
return;
@@ -79,8 +79,8 @@ bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
if (LoopVar.getType().isConstQualified() || !Expensive || !*Expensive)
return false;
- if (!utils::decl_ref_expr::isOnlyUsedAsConst(LoopVar, *ForRange.getBody(),
- Context))
+ if (utils::ExprMutationAnalyzer(ForRange.getBody(), &Context)
+ .isMutated(&LoopVar))
return false;
diag(LoopVar.getLocation(),
"loop variable is copied but only used as const reference; consider "
diff --git a/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp b/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp
index 2acbca34..6e731190 100644
--- a/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp
+++ b/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp
@@ -28,7 +28,7 @@ namespace performance {
static bool IsNonTrivialImplicitCast(const Stmt *ST) {
if (const auto *ICE = dyn_cast<ImplicitCastExpr>(ST)) {
return (ICE->getCastKind() != CK_NoOp) ||
- IsNonTrivialImplicitCast(ICE->getSubExpr());
+ IsNonTrivialImplicitCast(ICE->getSubExpr());
}
return false;
}
@@ -39,7 +39,9 @@ void ImplicitConversionInLoopCheck::registerMatchers(MatchFinder *Finder) {
// conversion. The check on the implicit conversion is done in check() because
// we can't access implicit conversion subnode via matchers: has() skips casts
// and materialize! We also bind on the call to operator* to get the proper
- // type in the diagnostic message.
+ // type in the diagnostic message. We use both cxxOperatorCallExpr for user
+ // defined operator and unaryOperator when the iterator is a pointer, like
+ // for arrays or std::array.
//
// Note that when the implicit conversion is done through a user defined
// conversion operator, the node is a CXXMemberCallExpr, not a
@@ -47,10 +49,14 @@ void ImplicitConversionInLoopCheck::registerMatchers(MatchFinder *Finder) {
// cxxOperatorCallExpr() matcher.
Finder->addMatcher(
cxxForRangeStmt(hasLoopVariable(
- varDecl(hasType(qualType(references(qualType(isConstQualified())))),
- hasInitializer(expr(hasDescendant(cxxOperatorCallExpr().bind(
- "operator-call")))
- .bind("init")))
+ varDecl(
+ hasType(qualType(references(qualType(isConstQualified())))),
+ hasInitializer(
+ expr(anyOf(hasDescendant(
+ cxxOperatorCallExpr().bind("operator-call")),
+ hasDescendant(unaryOperator(hasOperatorName("*"))
+ .bind("operator-call"))))
+ .bind("init")))
.bind("faulty-var"))),
this);
}
@@ -60,7 +66,7 @@ void ImplicitConversionInLoopCheck::check(
const auto *VD = Result.Nodes.getNodeAs<VarDecl>("faulty-var");
const auto *Init = Result.Nodes.getNodeAs<Expr>("init");
const auto *OperatorCall =
- Result.Nodes.getNodeAs<CXXOperatorCallExpr>("operator-call");
+ Result.Nodes.getNodeAs<Expr>("operator-call");
if (const auto *Cleanup = dyn_cast<ExprWithCleanups>(Init))
Init = Cleanup->getSubExpr();
@@ -79,7 +85,7 @@ void ImplicitConversionInLoopCheck::check(
void ImplicitConversionInLoopCheck::ReportAndFix(
const ASTContext *Context, const VarDecl *VD,
- const CXXOperatorCallExpr *OperatorCall) {
+ const Expr *OperatorCall) {
// We only match on const ref, so we should print a const ref version of the
// type.
QualType ConstType = OperatorCall->getType().withConst();
@@ -90,7 +96,7 @@ void ImplicitConversionInLoopCheck::ReportAndFix(
"change the type to the matching one (%1 but 'const auto&' is always a "
"valid option) or remove the reference to make it explicit that you are "
"creating a new value";
- diag(VD->getLocStart(), Message) << VD << ConstRefType;
+ diag(VD->getBeginLoc(), Message) << VD << ConstRefType;
}
} // namespace performance
diff --git a/clang-tidy/performance/ImplicitConversionInLoopCheck.h b/clang-tidy/performance/ImplicitConversionInLoopCheck.h
index 8a459ab0..55cb84c3 100644
--- a/clang-tidy/performance/ImplicitConversionInLoopCheck.h
+++ b/clang-tidy/performance/ImplicitConversionInLoopCheck.h
@@ -28,7 +28,7 @@ public:
private:
void ReportAndFix(const ASTContext *Context, const VarDecl *VD,
- const CXXOperatorCallExpr *OperatorCall);
+ const Expr *OperatorCall);
};
} // namespace performance
diff --git a/clang-tidy/performance/InefficientAlgorithmCheck.cpp b/clang-tidy/performance/InefficientAlgorithmCheck.cpp
index 4471d077..8cee2817 100644
--- a/clang-tidy/performance/InefficientAlgorithmCheck.cpp
+++ b/clang-tidy/performance/InefficientAlgorithmCheck.cpp
@@ -99,7 +99,7 @@ void InefficientAlgorithmCheck::check(const MatchFinder::MatchResult &Result) {
.getUnqualifiedType()
.getCanonicalType();
if (AlgCmp != ContainerCmp) {
- diag(Arg->getLocStart(),
+ diag(Arg->getBeginLoc(),
"different comparers used in the algorithm and the container");
return;
}
@@ -153,7 +153,7 @@ void InefficientAlgorithmCheck::check(const MatchFinder::MatchResult &Result) {
Hint = FixItHint::CreateReplacement(CallRange, ReplacementText);
}
- diag(AlgCall->getLocStart(),
+ diag(AlgCall->getBeginLoc(),
"this STL algorithm call should be replaced with a container method")
<< Hint;
}
diff --git a/clang-tidy/performance/InefficientVectorOperationCheck.cpp b/clang-tidy/performance/InefficientVectorOperationCheck.cpp
index 33c68f01..5b083761 100644
--- a/clang-tidy/performance/InefficientVectorOperationCheck.cpp
+++ b/clang-tidy/performance/InefficientVectorOperationCheck.cpp
@@ -168,7 +168,7 @@ void InefficientVectorOperationCheck::check(
// FIXME: make it more intelligent to identify the pre-allocating operations
// before the for loop.
if (SM.isBeforeInTranslationUnit(Ref->getLocation(),
- LoopStmt->getLocStart())) {
+ LoopStmt->getBeginLoc())) {
return;
}
}
@@ -200,13 +200,13 @@ void InefficientVectorOperationCheck::check(
}
auto Diag =
- diag(VectorAppendCall->getLocStart(),
+ diag(VectorAppendCall->getBeginLoc(),
"%0 is called inside a loop; "
"consider pre-allocating the vector capacity before the loop")
<< VectorAppendCall->getMethodDecl()->getDeclName();
if (!ReserveStmt.empty())
- Diag << FixItHint::CreateInsertion(LoopStmt->getLocStart(), ReserveStmt);
+ Diag << FixItHint::CreateInsertion(LoopStmt->getBeginLoc(), ReserveStmt);
}
} // namespace performance
diff --git a/clang-tidy/performance/MoveConstArgCheck.cpp b/clang-tidy/performance/MoveConstArgCheck.cpp
index 8d494802..c64769f6 100644
--- a/clang-tidy/performance/MoveConstArgCheck.cpp
+++ b/clang-tidy/performance/MoveConstArgCheck.cpp
@@ -23,11 +23,11 @@ static void ReplaceCallWithArg(const CallExpr *Call, DiagnosticBuilder &Diag,
const Expr *Arg = Call->getArg(0);
CharSourceRange BeforeArgumentsRange = Lexer::makeFileCharRange(
- CharSourceRange::getCharRange(Call->getLocStart(), Arg->getLocStart()),
+ CharSourceRange::getCharRange(Call->getBeginLoc(), Arg->getBeginLoc()),
SM, LangOpts);
CharSourceRange AfterArgumentsRange = Lexer::makeFileCharRange(
- CharSourceRange::getCharRange(Call->getLocEnd(),
- Call->getLocEnd().getLocWithOffset(1)),
+ CharSourceRange::getCharRange(Call->getEndLoc(),
+ Call->getEndLoc().getLocWithOffset(1)),
SM, LangOpts);
if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) {
diff --git a/clang-tidy/performance/TypePromotionInMathFnCheck.cpp b/clang-tidy/performance/TypePromotionInMathFnCheck.cpp
index 441bad38..8ff31a06 100644
--- a/clang-tidy/performance/TypePromotionInMathFnCheck.cpp
+++ b/clang-tidy/performance/TypePromotionInMathFnCheck.cpp
@@ -195,7 +195,7 @@ void TypePromotionInMathFnCheck::check(const MatchFinder::MatchResult &Result) {
// declared in <math.h>.
if (FnInCmath)
if (auto IncludeFixit = IncludeInserter->CreateIncludeInsertion(
- Result.Context->getSourceManager().getFileID(Call->getLocStart()),
+ Result.Context->getSourceManager().getFileID(Call->getBeginLoc()),
"cmath", /*IsAngled=*/true))
Diag << *IncludeFixit;
}
diff --git a/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tidy/performance/UnnecessaryValueParamCheck.cpp
index e277ad34..8c9259c7 100644
--- a/clang-tidy/performance/UnnecessaryValueParamCheck.cpp
+++ b/clang-tidy/performance/UnnecessaryValueParamCheck.cpp
@@ -10,6 +10,7 @@
#include "UnnecessaryValueParamCheck.h"
#include "../utils/DeclRefExprUtils.h"
+#include "../utils/ExprMutationAnalyzer.h"
#include "../utils/FixItHintUtils.h"
#include "../utils/Matchers.h"
#include "../utils/TypeTraits.h"
@@ -31,14 +32,6 @@ std::string paramNameOrIndex(StringRef Name, size_t Index) {
.str();
}
-template <typename S>
-bool isSubset(const S &SubsetCandidate, const S &SupersetCandidate) {
- for (const auto &E : SubsetCandidate)
- if (SupersetCandidate.count(E) == 0)
- return false;
- return true;
-}
-
bool isReferencedOutsideOfCallExpr(const FunctionDecl &Function,
ASTContext &Context) {
auto Matches = match(declRefExpr(to(functionDecl(equalsNode(&Function))),
@@ -98,43 +91,55 @@ void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) {
void UnnecessaryValueParamCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param");
const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("functionDecl");
- const size_t Index = std::find(Function->parameters().begin(),
- Function->parameters().end(), Param) -
- Function->parameters().begin();
- bool IsConstQualified =
- Param->getType().getCanonicalType().isConstQualified();
- auto AllDeclRefExprs = utils::decl_ref_expr::allDeclRefExprs(
- *Param, *Function, *Result.Context);
- auto ConstDeclRefExprs = utils::decl_ref_expr::constReferenceDeclRefExprs(
- *Param, *Function, *Result.Context);
-
- // Do not trigger on non-const value parameters when they are not only used as
- // const.
- if (!isSubset(AllDeclRefExprs, ConstDeclRefExprs))
+ // Do not trigger on non-const value parameters when they are mutated either
+ // within the function body or within init expression(s) when the function is
+ // a ctor.
+ if (utils::ExprMutationAnalyzer(Function->getBody(), Result.Context)
+ .isMutated(Param))
return;
+ // CXXCtorInitializer might also mutate Param but they're not part of function
+ // body, so check them separately here.
+ if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Function)) {
+ for (const auto *Init : Ctor->inits()) {
+ if (utils::ExprMutationAnalyzer(Init->getInit(), Result.Context)
+ .isMutated(Param))
+ return;
+ }
+ }
+
+ const bool IsConstQualified =
+ Param->getType().getCanonicalType().isConstQualified();
// If the parameter is non-const, check if it has a move constructor and is
// only referenced once to copy-construct another object or whether it has a
// move assignment operator and is only referenced once when copy-assigned.
// In this case wrap DeclRefExpr with std::move() to avoid the unnecessary
// copy.
- if (!IsConstQualified && AllDeclRefExprs.size() == 1) {
- auto CanonicalType = Param->getType().getCanonicalType();
- const auto &DeclRefExpr = **AllDeclRefExprs.begin();
-
- if (!hasLoopStmtAncestor(DeclRefExpr, *Function, *Result.Context) &&
- ((utils::type_traits::hasNonTrivialMoveConstructor(CanonicalType) &&
- utils::decl_ref_expr::isCopyConstructorArgument(
- DeclRefExpr, *Function, *Result.Context)) ||
- (utils::type_traits::hasNonTrivialMoveAssignment(CanonicalType) &&
- utils::decl_ref_expr::isCopyAssignmentArgument(
- DeclRefExpr, *Function, *Result.Context)))) {
- handleMoveFix(*Param, DeclRefExpr, *Result.Context);
- return;
+ if (!IsConstQualified) {
+ auto AllDeclRefExprs = utils::decl_ref_expr::allDeclRefExprs(
+ *Param, *Function, *Result.Context);
+ if (AllDeclRefExprs.size() == 1) {
+ auto CanonicalType = Param->getType().getCanonicalType();
+ const auto &DeclRefExpr = **AllDeclRefExprs.begin();
+
+ if (!hasLoopStmtAncestor(DeclRefExpr, *Function, *Result.Context) &&
+ ((utils::type_traits::hasNonTrivialMoveConstructor(CanonicalType) &&
+ utils::decl_ref_expr::isCopyConstructorArgument(
+ DeclRefExpr, *Function, *Result.Context)) ||
+ (utils::type_traits::hasNonTrivialMoveAssignment(CanonicalType) &&
+ utils::decl_ref_expr::isCopyAssignmentArgument(
+ DeclRefExpr, *Function, *Result.Context)))) {
+ handleMoveFix(*Param, DeclRefExpr, *Result.Context);
+ return;
+ }
}
}
+ const size_t Index = std::find(Function->parameters().begin(),
+ Function->parameters().end(), Param) -
+ Function->parameters().begin();
+
auto Diag =
diag(Param->getLocation(),
IsConstQualified ? "the const qualified parameter %0 is "
@@ -151,7 +156,7 @@ void UnnecessaryValueParamCheck::check(const MatchFinder::MatchResult &Result) {
// compilation unit as the signature change could introduce build errors.
// 4. the function is an explicit template specialization.
const auto *Method = llvm::dyn_cast<CXXMethodDecl>(Function);
- if (Param->getLocStart().isMacroID() || (Method && Method->isVirtual()) ||
+ if (Param->getBeginLoc().isMacroID() || (Method && Method->isVirtual()) ||
isReferencedOutsideOfCallExpr(*Function, *Result.Context) ||
isExplicitTemplateSpecialization(*Function))
return;
@@ -184,20 +189,20 @@ void UnnecessaryValueParamCheck::storeOptions(
void UnnecessaryValueParamCheck::handleMoveFix(const ParmVarDecl &Var,
const DeclRefExpr &CopyArgument,
const ASTContext &Context) {
- auto Diag = diag(CopyArgument.getLocStart(),
+ auto Diag = diag(CopyArgument.getBeginLoc(),
"parameter %0 is passed by value and only copied once; "
"consider moving it to avoid unnecessary copies")
<< &Var;
// Do not propose fixes in macros since we cannot place them correctly.
- if (CopyArgument.getLocStart().isMacroID())
+ if (CopyArgument.getBeginLoc().isMacroID())
return;
const auto &SM = Context.getSourceManager();
auto EndLoc = Lexer::getLocForEndOfToken(CopyArgument.getLocation(), 0, SM,
Context.getLangOpts());
- Diag << FixItHint::CreateInsertion(CopyArgument.getLocStart(), "std::move(")
+ Diag << FixItHint::CreateInsertion(CopyArgument.getBeginLoc(), "std::move(")
<< FixItHint::CreateInsertion(EndLoc, ")");
if (auto IncludeFixit = Inserter->CreateIncludeInsertion(
- SM.getFileID(CopyArgument.getLocStart()), "utility",
+ SM.getFileID(CopyArgument.getBeginLoc()), "utility",
/*IsAngled=*/true))
Diag << *IncludeFixit;
}
diff --git a/clang-tidy/plugin/CMakeLists.txt b/clang-tidy/plugin/CMakeLists.txt
index 1979383c..3540b2be 100644
--- a/clang-tidy/plugin/CMakeLists.txt
+++ b/clang-tidy/plugin/CMakeLists.txt
@@ -8,12 +8,15 @@ add_clang_library(clangTidyPlugin
clangFrontend
clangSema
clangTidy
- clangTidyAndroidModule
clangTidyAbseilModule
+ clangTidyAndroidModule
clangTidyBoostModule
+ clangTidyBugproneModule
clangTidyCERTModule
clangTidyCppCoreGuidelinesModule
+ clangTidyFuchsiaModule
clangTidyGoogleModule
+ clangTidyHICPPModule
clangTidyLLVMModule
clangTidyMiscModule
clangTidyModernizeModule
@@ -22,5 +25,6 @@ add_clang_library(clangTidyPlugin
clangTidyPerformanceModule
clangTidyPortabilityModule
clangTidyReadabilityModule
+ clangTidyZirconModule
clangTooling
)
diff --git a/clang-tidy/plugin/ClangTidyPlugin.cpp b/clang-tidy/plugin/ClangTidyPlugin.cpp
index dc567722..34556120 100644
--- a/clang-tidy/plugin/ClangTidyPlugin.cpp
+++ b/clang-tidy/plugin/ClangTidyPlugin.cpp
@@ -78,26 +78,51 @@ static clang::FrontendPluginRegistry::Add<clang::tidy::ClangTidyPluginAction>
namespace clang {
namespace tidy {
+// This anchor is used to force the linker to link the AbseilModule.
+extern volatile int AbseilModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED AbseilModuleAnchorDestination =
+ AbseilModuleAnchorSource;
+
+// This anchor is used to force the linker to link the AndroidModule.
+extern volatile int AndroidModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED AndroidModuleAnchorDestination =
+ AndroidModuleAnchorSource;
+
+// This anchor is used to force the linker to link the BoostModule.
+extern volatile int BoostModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED BoostModuleAnchorDestination =
+ BoostModuleAnchorSource;
+
// This anchor is used to force the linker to link the CERTModule.
extern volatile int CERTModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination =
CERTModuleAnchorSource;
-// This anchor is used to force the linker to link the LLVMModule.
-extern volatile int LLVMModuleAnchorSource;
-static int LLVM_ATTRIBUTE_UNUSED LLVMModuleAnchorDestination =
- LLVMModuleAnchorSource;
-
// This anchor is used to force the linker to link the CppCoreGuidelinesModule.
extern volatile int CppCoreGuidelinesModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination =
CppCoreGuidelinesModuleAnchorSource;
+// This anchor is used to force the linker to link the FuchsiaModule.
+extern volatile int FuchsiaModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED FuchsiaModuleAnchorDestination =
+ FuchsiaModuleAnchorSource;
+
// This anchor is used to force the linker to link the GoogleModule.
extern volatile int GoogleModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED GoogleModuleAnchorDestination =
GoogleModuleAnchorSource;
+// This anchor is used to force the linker to link the HICPPModule.
+extern volatile int HICPPModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED HICPPModuleAnchorDestination =
+ HICPPModuleAnchorSource;
+
+// This anchor is used to force the linker to link the LLVMModule.
+extern volatile int LLVMModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED LLVMModuleAnchorDestination =
+ LLVMModuleAnchorSource;
+
// This anchor is used to force the linker to link the MiscModule.
extern volatile int MiscModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED MiscModuleAnchorDestination =
@@ -111,7 +136,12 @@ static int LLVM_ATTRIBUTE_UNUSED ModernizeModuleAnchorDestination =
// This anchor is used to force the linker to link the MPIModule.
extern volatile int MPIModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED MPIModuleAnchorDestination =
- MPIModuleAnchorSource;
+ MPIModuleAnchorSource;
+
+// This anchor is used to force the linker to link the ObjCModule.
+extern volatile int ObjCModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED ObjCModuleAnchorDestination =
+ ObjCModuleAnchorSource;
// This anchor is used to force the linker to link the PerformanceModule.
extern volatile int PerformanceModuleAnchorSource;
@@ -128,10 +158,10 @@ extern volatile int ReadabilityModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination =
ReadabilityModuleAnchorSource;
-// This anchor is used to force the linker to link the ObjCModule.
-extern volatile int ObjCModuleAnchorSource;
-static int LLVM_ATTRIBUTE_UNUSED ObjCModuleAnchorDestination =
- ObjCModuleAnchorSource;
+// This anchor is used to force the linker to link the ZirconModule.
+extern volatile int ZirconModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED ZirconModuleAnchorDestination =
+ ZirconModuleAnchorSource;
} // namespace tidy
} // namespace clang
diff --git a/clang-tidy/readability/AvoidConstParamsInDecls.cpp b/clang-tidy/readability/AvoidConstParamsInDecls.cpp
index 70329e83..51fc4895 100644
--- a/clang-tidy/readability/AvoidConstParamsInDecls.cpp
+++ b/clang-tidy/readability/AvoidConstParamsInDecls.cpp
@@ -22,8 +22,8 @@ namespace {
SourceRange getTypeRange(const ParmVarDecl &Param) {
if (Param.getIdentifier() != nullptr)
- return SourceRange(Param.getLocStart(),
- Param.getLocEnd().getLocWithOffset(-1));
+ return SourceRange(Param.getBeginLoc(),
+ Param.getEndLoc().getLocWithOffset(-1));
return Param.getSourceRange();
}
@@ -82,7 +82,7 @@ void AvoidConstParamsInDecls::check(const MatchFinder::MatchResult &Result) {
if (!Param->getType().isLocalConstQualified())
return;
- auto Diag = diag(Param->getLocStart(),
+ auto Diag = diag(Param->getBeginLoc(),
"parameter %0 is const-qualified in the function "
"declaration; const-qualification of parameters only has an "
"effect in function definitions");
@@ -97,7 +97,7 @@ void AvoidConstParamsInDecls::check(const MatchFinder::MatchResult &Result) {
Diag << Param;
}
- if (Param->getLocStart().isMacroID() != Param->getLocEnd().isMacroID()) {
+ if (Param->getBeginLoc().isMacroID() != Param->getEndLoc().isMacroID()) {
// Do not offer a suggestion if the part of the variable declaration comes
// from a macro.
return;
diff --git a/clang-tidy/readability/BracesAroundStatementsCheck.cpp b/clang-tidy/readability/BracesAroundStatementsCheck.cpp
index 1bff668d..5f5294c2 100644
--- a/clang-tidy/readability/BracesAroundStatementsCheck.cpp
+++ b/clang-tidy/readability/BracesAroundStatementsCheck.cpp
@@ -174,12 +174,12 @@ BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
const SourceManager &SM,
const ASTContext *Context) {
// Skip macros.
- if (S->getLocStart().isMacroID())
+ if (S->getBeginLoc().isMacroID())
return SourceLocation();
- SourceLocation CondEndLoc = S->getCond()->getLocEnd();
+ SourceLocation CondEndLoc = S->getCond()->getEndLoc();
if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
- CondEndLoc = CondVar->getLocEnd();
+ CondEndLoc = CondVar->getEndLoc();
if (!CondEndLoc.isValid()) {
return SourceLocation();
@@ -232,7 +232,7 @@ bool BracesAroundStatementsCheck::checkStmt(
// level as the start of the statement. We also need file locations for
// Lexer::getLocForEndOfToken working properly.
InitialLoc = Lexer::makeFileCharRange(
- CharSourceRange::getCharRange(InitialLoc, S->getLocStart()),
+ CharSourceRange::getCharRange(InitialLoc, S->getBeginLoc()),
SM, Context->getLangOpts())
.getBegin();
if (InitialLoc.isInvalid())
diff --git a/clang-tidy/readability/ContainerSizeEmptyCheck.cpp b/clang-tidy/readability/ContainerSizeEmptyCheck.cpp
index 5604354f..60a153a8 100644
--- a/clang-tidy/readability/ContainerSizeEmptyCheck.cpp
+++ b/clang-tidy/readability/ContainerSizeEmptyCheck.cpp
@@ -201,12 +201,12 @@ void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
}
if (MemberCall) {
- diag(MemberCall->getLocStart(),
+ diag(MemberCall->getBeginLoc(),
"the 'empty' method should be used to check "
"for emptiness instead of 'size'")
<< Hint;
} else {
- diag(BinCmp->getLocStart(),
+ diag(BinCmp->getBeginLoc(),
"the 'empty' method should be used to check "
"for emptiness instead of comparing to an empty object")
<< Hint;
diff --git a/clang-tidy/readability/DeleteNullPointerCheck.cpp b/clang-tidy/readability/DeleteNullPointerCheck.cpp
index 766dfdac..02b9bbe8 100644
--- a/clang-tidy/readability/DeleteNullPointerCheck.cpp
+++ b/clang-tidy/readability/DeleteNullPointerCheck.cpp
@@ -55,15 +55,15 @@ void DeleteNullPointerCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Compound = Result.Nodes.getNodeAs<CompoundStmt>("compound");
auto Diag = diag(
- IfWithDelete->getLocStart(),
+ IfWithDelete->getBeginLoc(),
"'if' statement is unnecessary; deleting null pointer has no effect");
if (IfWithDelete->getElse())
return;
// FIXME: generate fixit for this case.
Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
- IfWithDelete->getLocStart(),
- Lexer::getLocForEndOfToken(IfWithDelete->getCond()->getLocEnd(), 0,
+ IfWithDelete->getBeginLoc(),
+ Lexer::getLocForEndOfToken(IfWithDelete->getCond()->getEndLoc(), 0,
*Result.SourceManager,
Result.Context->getLangOpts())));
if (Compound) {
diff --git a/clang-tidy/readability/DeletedDefaultCheck.cpp b/clang-tidy/readability/DeletedDefaultCheck.cpp
index a197e8dc..e99ca837 100644
--- a/clang-tidy/readability/DeletedDefaultCheck.cpp
+++ b/clang-tidy/readability/DeletedDefaultCheck.cpp
@@ -41,7 +41,7 @@ void DeletedDefaultCheck::check(const MatchFinder::MatchResult &Result) {
"either be removed or explicitly deleted";
if (const auto *Constructor =
Result.Nodes.getNodeAs<CXXConstructorDecl>("constructor")) {
- auto Diag = diag(Constructor->getLocStart(), Message);
+ auto Diag = diag(Constructor->getBeginLoc(), Message);
if (Constructor->isDefaultConstructor()) {
Diag << "default constructor"
<< "a non-static data member or a base class is lacking a default "
@@ -56,7 +56,7 @@ void DeletedDefaultCheck::check(const MatchFinder::MatchResult &Result) {
}
} else if (const auto *Assignment =
Result.Nodes.getNodeAs<CXXMethodDecl>("method-decl")) {
- diag(Assignment->getLocStart(), Message)
+ diag(Assignment->getBeginLoc(), Message)
<< (Assignment->isCopyAssignmentOperator() ? "copy assignment operator"
: "move assignment operator")
<< "a base class or a non-static data member is not assignable, e.g. "
diff --git a/clang-tidy/readability/FunctionSizeCheck.cpp b/clang-tidy/readability/FunctionSizeCheck.cpp
index 8ed6b907..9547afb8 100644
--- a/clang-tidy/readability/FunctionSizeCheck.cpp
+++ b/clang-tidy/readability/FunctionSizeCheck.cpp
@@ -73,7 +73,7 @@ public:
// is already nested NestingThreshold levels deep, record the start location
// of this new compound statement.
if (CurrentNestingLevel == Info.NestingThreshold)
- Info.NestingThresholders.push_back(Node->getLocStart());
+ Info.NestingThresholders.push_back(Node->getBeginLoc());
++CurrentNestingLevel;
Base::TraverseCompoundStmt(Node);
@@ -162,9 +162,9 @@ void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
// Count the lines including whitespace and comments. Really simple.
if (const Stmt *Body = Func->getBody()) {
SourceManager *SM = Result.SourceManager;
- if (SM->isWrittenInSameFile(Body->getLocStart(), Body->getLocEnd())) {
- FI.Lines = SM->getSpellingLineNumber(Body->getLocEnd()) -
- SM->getSpellingLineNumber(Body->getLocStart());
+ if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
+ FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
+ SM->getSpellingLineNumber(Body->getBeginLoc());
}
}
diff --git a/clang-tidy/readability/IdentifierNamingCheck.cpp b/clang-tidy/readability/IdentifierNamingCheck.cpp
index 46919d3f..90c34b37 100644
--- a/clang-tidy/readability/IdentifierNamingCheck.cpp
+++ b/clang-tidy/readability/IdentifierNamingCheck.cpp
@@ -841,7 +841,7 @@ void IdentifierNamingCheck::check(const MatchFinder::MatchResult &Result) {
if (StringRef(Fixup).equals(Name)) {
if (!IgnoreFailedSplit) {
LLVM_DEBUG(llvm::dbgs()
- << Decl->getLocStart().printToString(*Result.SourceManager)
+ << Decl->getBeginLoc().printToString(*Result.SourceManager)
<< llvm::format(": unable to split words for %s '%s'\n",
KindName.c_str(), Name.str().c_str()));
}
diff --git a/clang-tidy/readability/ImplicitBoolConversionCheck.cpp b/clang-tidy/readability/ImplicitBoolConversionCheck.cpp
index 79022d42..e88c1436 100644
--- a/clang-tidy/readability/ImplicitBoolConversionCheck.cpp
+++ b/clang-tidy/readability/ImplicitBoolConversionCheck.cpp
@@ -24,14 +24,14 @@ namespace {
AST_MATCHER(Stmt, isMacroExpansion) {
SourceManager &SM = Finder->getASTContext().getSourceManager();
- SourceLocation Loc = Node.getLocStart();
+ SourceLocation Loc = Node.getBeginLoc();
return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc);
}
bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) {
SourceManager &SM = Context.getSourceManager();
const LangOptions &LO = Context.getLangOpts();
- SourceLocation Loc = Statement->getLocStart();
+ SourceLocation Loc = Statement->getBeginLoc();
return SM.isMacroBodyExpansion(Loc) &&
Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL";
}
@@ -97,9 +97,9 @@ void fixGenericExprCastToBool(DiagnosticBuilder &Diag,
bool InvertComparison =
Parent != nullptr && isUnaryLogicalNotOperator(Parent);
if (InvertComparison) {
- SourceLocation ParentStartLoc = Parent->getLocStart();
+ SourceLocation ParentStartLoc = Parent->getBeginLoc();
SourceLocation ParentEndLoc =
- cast<UnaryOperator>(Parent)->getSubExpr()->getLocStart();
+ cast<UnaryOperator>(Parent)->getSubExpr()->getBeginLoc();
Diag << FixItHint::CreateRemoval(
CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc));
@@ -122,7 +122,7 @@ void fixGenericExprCastToBool(DiagnosticBuilder &Diag,
}
if (!StartLocInsertion.empty()) {
- Diag << FixItHint::CreateInsertion(Cast->getLocStart(), StartLocInsertion);
+ Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(), StartLocInsertion);
}
std::string EndLocInsertion;
@@ -145,7 +145,7 @@ void fixGenericExprCastToBool(DiagnosticBuilder &Diag,
}
SourceLocation EndLoc = Lexer::getLocForEndOfToken(
- Cast->getLocEnd(), 0, Context.getSourceManager(), Context.getLangOpts());
+ Cast->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts());
Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion);
}
@@ -183,13 +183,13 @@ void fixGenericExprCastFromBool(DiagnosticBuilder &Diag,
bool NeedParens = !isa<ParenExpr>(SubExpr);
Diag << FixItHint::CreateInsertion(
- Cast->getLocStart(),
+ Cast->getBeginLoc(),
(Twine("static_cast<") + OtherType + ">" + (NeedParens ? "(" : ""))
.str());
if (NeedParens) {
SourceLocation EndLoc = Lexer::getLocForEndOfToken(
- Cast->getLocEnd(), 0, Context.getSourceManager(),
+ Cast->getEndLoc(), 0, Context.getSourceManager(),
Context.getLangOpts());
Diag << FixItHint::CreateInsertion(EndLoc, ")");
@@ -354,7 +354,7 @@ void ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr *Cast,
return;
}
- auto Diag = diag(Cast->getLocStart(), "implicit conversion %0 -> bool")
+ auto Diag = diag(Cast->getBeginLoc(), "implicit conversion %0 -> bool")
<< Cast->getSubExpr()->getType();
StringRef EquivalentLiteral =
@@ -371,7 +371,7 @@ void ImplicitBoolConversionCheck::handleCastFromBool(
ASTContext &Context) {
QualType DestType =
NextImplicitCast ? NextImplicitCast->getType() : Cast->getType();
- auto Diag = diag(Cast->getLocStart(), "implicit conversion bool -> %0")
+ auto Diag = diag(Cast->getBeginLoc(), "implicit conversion bool -> %0")
<< DestType;
if (const auto *BoolLiteral =
diff --git a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
index 254ef960..280c354f 100644
--- a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
+++ b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
@@ -90,10 +90,20 @@ bool checkIfFixItHintIsApplicable(
return true;
}
+bool nameMatch(StringRef L, StringRef R, bool Strict) {
+ if (Strict)
+ return L.empty() || R.empty() || L == R;
+ // We allow two names if one is a prefix/suffix of the other, ignoring case.
+ // Important special case: this is true if either parameter has no name!
+ return L.startswith_lower(R) || R.startswith_lower(L) ||
+ L.endswith_lower(R) || R.endswith_lower(L);
+}
+
DifferingParamsContainer
findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration,
const FunctionDecl *OtherDeclaration,
- const FunctionDecl *OriginalDeclaration) {
+ const FunctionDecl *OriginalDeclaration,
+ bool Strict) {
DifferingParamsContainer DifferingParams;
auto SourceParamIt = ParameterSourceDeclaration->param_begin();
@@ -106,8 +116,7 @@ findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration,
// FIXME: Provide a way to extract commented out parameter name from comment
// next to it.
- if (!SourceParamName.empty() && !OtherParamName.empty() &&
- SourceParamName != OtherParamName) {
+ if (!nameMatch(SourceParamName, OtherParamName, Strict)) {
SourceRange OtherParamNameRange =
DeclarationNameInfo((*OtherParamIt)->getDeclName(),
(*OtherParamIt)->getLocation())
@@ -128,9 +137,9 @@ findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration,
}
InconsistentDeclarationsContainer
-findInconsitentDeclarations(const FunctionDecl *OriginalDeclaration,
+findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration,
const FunctionDecl *ParameterSourceDeclaration,
- SourceManager &SM) {
+ SourceManager &SM, bool Strict) {
InconsistentDeclarationsContainer InconsistentDeclarations;
SourceLocation ParameterSourceLocation =
ParameterSourceDeclaration->getLocation();
@@ -141,7 +150,7 @@ findInconsitentDeclarations(const FunctionDecl *OriginalDeclaration,
DifferingParamsContainer DifferingParams =
findDifferingParamsInDeclaration(ParameterSourceDeclaration,
OtherDeclaration,
- OriginalDeclaration);
+ OriginalDeclaration, Strict);
if (!DifferingParams.empty()) {
InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(),
std::move(DifferingParams));
@@ -284,6 +293,7 @@ void formatDiagnostics(
void InconsistentDeclarationParameterNameCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IgnoreMacros", IgnoreMacros);
+ Options.store(Opts, "Strict", Strict);
}
void InconsistentDeclarationParameterNameCheck::registerMatchers(
@@ -305,16 +315,16 @@ void InconsistentDeclarationParameterNameCheck::check(
getParameterSourceDeclaration(OriginalDeclaration);
InconsistentDeclarationsContainer InconsistentDeclarations =
- findInconsitentDeclarations(OriginalDeclaration,
- ParameterSourceDeclaration,
- *Result.SourceManager);
+ findInconsistentDeclarations(OriginalDeclaration,
+ ParameterSourceDeclaration,
+ *Result.SourceManager, Strict);
if (InconsistentDeclarations.empty()) {
// Avoid unnecessary further visits.
markRedeclarationsAsVisited(OriginalDeclaration);
return;
}
- SourceLocation StartLoc = OriginalDeclaration->getLocStart();
+ SourceLocation StartLoc = OriginalDeclaration->getBeginLoc();
if (StartLoc.isMacroID() && IgnoreMacros) {
markRedeclarationsAsVisited(OriginalDeclaration);
return;
diff --git a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h
index a6e0dfc4..602856fa 100644
--- a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h
+++ b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h
@@ -28,7 +28,8 @@ public:
InconsistentDeclarationParameterNameCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
- IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", 1) != 0) {}
+ IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", 1) != 0),
+ Strict(Options.getLocalOrGlobal("Strict", 0) != 0) {}
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
@@ -39,6 +40,7 @@ private:
llvm::DenseSet<const FunctionDecl *> VisitedDeclarations;
const bool IgnoreMacros;
+ const bool Strict;
};
} // namespace readability
diff --git a/clang-tidy/readability/MisleadingIndentationCheck.cpp b/clang-tidy/readability/MisleadingIndentationCheck.cpp
index 8a6c8c47..9791531f 100644
--- a/clang-tidy/readability/MisleadingIndentationCheck.cpp
+++ b/clang-tidy/readability/MisleadingIndentationCheck.cpp
@@ -40,7 +40,7 @@ void MisleadingIndentationCheck::danglingElseCheck(const SourceManager &SM,
if (IfLoc.isMacroID() || ElseLoc.isMacroID())
return;
- if (SM.getExpansionLineNumber(If->getThen()->getLocEnd()) ==
+ if (SM.getExpansionLineNumber(If->getThen()->getEndLoc()) ==
SM.getExpansionLineNumber(ElseLoc))
return;
@@ -79,15 +79,15 @@ void MisleadingIndentationCheck::missingBracesCheck(const SourceManager &SM,
if (isa<CompoundStmt>(Inner))
continue;
- SourceLocation InnerLoc = Inner->getLocStart();
- SourceLocation OuterLoc = CurrentStmt->getLocStart();
+ SourceLocation InnerLoc = Inner->getBeginLoc();
+ SourceLocation OuterLoc = CurrentStmt->getBeginLoc();
if (SM.getExpansionLineNumber(InnerLoc) ==
SM.getExpansionLineNumber(OuterLoc))
continue;
const Stmt *NextStmt = CStmt->body_begin()[i + 1];
- SourceLocation NextLoc = NextStmt->getLocStart();
+ SourceLocation NextLoc = NextStmt->getBeginLoc();
if (InnerLoc.isMacroID() || OuterLoc.isMacroID() || NextLoc.isMacroID())
continue;
diff --git a/clang-tidy/readability/MisplacedArrayIndexCheck.cpp b/clang-tidy/readability/MisplacedArrayIndexCheck.cpp
index f5e09fab..3d1971be 100644
--- a/clang-tidy/readability/MisplacedArrayIndexCheck.cpp
+++ b/clang-tidy/readability/MisplacedArrayIndexCheck.cpp
@@ -30,7 +30,7 @@ void MisplacedArrayIndexCheck::check(const MatchFinder::MatchResult &Result) {
const auto *ArraySubscriptE =
Result.Nodes.getNodeAs<ArraySubscriptExpr>("expr");
- auto Diag = diag(ArraySubscriptE->getLocStart(), "confusing array subscript "
+ auto Diag = diag(ArraySubscriptE->getBeginLoc(), "confusing array subscript "
"expression, usually the "
"index is inside the []");
diff --git a/clang-tidy/readability/NamedParameterCheck.cpp b/clang-tidy/readability/NamedParameterCheck.cpp
index ffdc813b..6fa9e68f 100644
--- a/clang-tidy/readability/NamedParameterCheck.cpp
+++ b/clang-tidy/readability/NamedParameterCheck.cpp
@@ -59,7 +59,7 @@ void NamedParameterCheck::check(const MatchFinder::MatchResult &Result) {
// Sanity check the source locations.
if (!Parm->getLocation().isValid() || Parm->getLocation().isMacroID() ||
- !SM.isWrittenInSameFile(Parm->getLocStart(), Parm->getLocation()))
+ !SM.isWrittenInSameFile(Parm->getBeginLoc(), Parm->getLocation()))
continue;
// Skip gmock testing::Unused parameters.
@@ -73,7 +73,7 @@ void NamedParameterCheck::check(const MatchFinder::MatchResult &Result) {
// Look for comments. We explicitly want to allow idioms like
// void foo(int /*unused*/)
- const char *Begin = SM.getCharacterData(Parm->getLocStart());
+ const char *Begin = SM.getCharacterData(Parm->getBeginLoc());
const char *End = SM.getCharacterData(Parm->getLocation());
StringRef Data(Begin, End - Begin);
if (Data.find("/*") != StringRef::npos)
diff --git a/clang-tidy/readability/NamespaceCommentCheck.cpp b/clang-tidy/readability/NamespaceCommentCheck.cpp
index 409aba90..229cc626 100644
--- a/clang-tidy/readability/NamespaceCommentCheck.cpp
+++ b/clang-tidy/readability/NamespaceCommentCheck.cpp
@@ -69,12 +69,12 @@ void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) {
const auto *ND = Result.Nodes.getNodeAs<NamespaceDecl>("namespace");
const SourceManager &Sources = *Result.SourceManager;
- if (!locationsInSameFile(Sources, ND->getLocStart(), ND->getRBraceLoc()))
+ if (!locationsInSameFile(Sources, ND->getBeginLoc(), ND->getRBraceLoc()))
return;
// Don't require closing comments for namespaces spanning less than certain
// number of lines.
- unsigned StartLine = Sources.getSpellingLineNumber(ND->getLocStart());
+ unsigned StartLine = Sources.getSpellingLineNumber(ND->getBeginLoc());
unsigned EndLine = Sources.getSpellingLineNumber(ND->getRBraceLoc());
if (EndLine - StartLine + 1 <= ShortNamespaceLines)
return;
diff --git a/clang-tidy/readability/NonConstParameterCheck.cpp b/clang-tidy/readability/NonConstParameterCheck.cpp
index bc6507c8..e33191cf 100644
--- a/clang-tidy/readability/NonConstParameterCheck.cpp
+++ b/clang-tidy/readability/NonConstParameterCheck.cpp
@@ -146,7 +146,7 @@ void NonConstParameterCheck::diagnoseNonConstParameters() {
unsigned Index = Par->getFunctionScopeIndex();
for (FunctionDecl *FnDecl : Function->redecls())
Fixes.push_back(FixItHint::CreateInsertion(
- FnDecl->getParamDecl(Index)->getLocStart(), "const "));
+ FnDecl->getParamDecl(Index)->getBeginLoc(), "const "));
diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const")
<< Par->getName() << Fixes;
diff --git a/clang-tidy/readability/RedundantControlFlowCheck.cpp b/clang-tidy/readability/RedundantControlFlowCheck.cpp
index 0788c42e..d5898ed9 100644
--- a/clang-tidy/readability/RedundantControlFlowCheck.cpp
+++ b/clang-tidy/readability/RedundantControlFlowCheck.cpp
@@ -81,7 +81,7 @@ void RedundantControlFlowCheck::issueDiagnostic(
SourceLocation Start;
if (Previous != Block->body_rend())
Start = Lexer::findLocationAfterToken(
- dyn_cast<Stmt>(*Previous)->getLocEnd(), tok::semi, SM, getLangOpts(),
+ dyn_cast<Stmt>(*Previous)->getEndLoc(), tok::semi, SM, getLangOpts(),
/*SkipTrailingWhitespaceAndNewLine=*/true);
if (!Start.isValid())
Start = StmtRange.getBegin();
diff --git a/clang-tidy/readability/RedundantDeclarationCheck.cpp b/clang-tidy/readability/RedundantDeclarationCheck.cpp
index 1df3f055..c5b6cc32 100644
--- a/clang-tidy/readability/RedundantDeclarationCheck.cpp
+++ b/clang-tidy/readability/RedundantDeclarationCheck.cpp
@@ -59,7 +59,7 @@ void RedundantDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *VD = dyn_cast<VarDecl>(D)) {
// Is this a multivariable declaration?
for (const auto Other : VD->getDeclContext()->decls()) {
- if (Other != D && Other->getLocStart() == VD->getLocStart()) {
+ if (Other != D && Other->getBeginLoc() == VD->getBeginLoc()) {
MultiVar = true;
break;
}
diff --git a/clang-tidy/readability/RedundantSmartptrGetCheck.cpp b/clang-tidy/readability/RedundantSmartptrGetCheck.cpp
index 1abd70bd..b03b546f 100644
--- a/clang-tidy/readability/RedundantSmartptrGetCheck.cpp
+++ b/clang-tidy/readability/RedundantSmartptrGetCheck.cpp
@@ -138,7 +138,7 @@ void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) {
*Result.SourceManager, getLangOpts());
// Replace foo->get() with *foo, and foo.get() with foo.
std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str();
- diag(GetCall->getLocStart(), "redundant get() call on smart pointer")
+ diag(GetCall->getBeginLoc(), "redundant get() call on smart pointer")
<< FixItHint::CreateReplacement(GetCall->getSourceRange(), Replacement);
}
diff --git a/clang-tidy/readability/RedundantStringCStrCheck.cpp b/clang-tidy/readability/RedundantStringCStrCheck.cpp
index 21f4a8a5..f0c378f5 100644
--- a/clang-tidy/readability/RedundantStringCStrCheck.cpp
+++ b/clang-tidy/readability/RedundantStringCStrCheck.cpp
@@ -189,7 +189,7 @@ void RedundantStringCStrCheck::check(const MatchFinder::MatchResult &Result) {
if (ArgText.empty())
return;
- diag(Call->getLocStart(), "redundant call to %0")
+ diag(Call->getBeginLoc(), "redundant call to %0")
<< Member->getMemberDecl()
<< FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
}
diff --git a/clang-tidy/readability/SimplifyBooleanExprCheck.cpp b/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
index ba8e5b42..9420c6c3 100644
--- a/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
+++ b/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
@@ -62,7 +62,7 @@ const char SimplifyConditionalReturnDiagnostic[] =
const CXXBoolLiteralExpr *getBoolLiteral(const MatchFinder::MatchResult &Result,
StringRef Id) {
const auto *Literal = Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(Id);
- return (Literal && Literal->getLocStart().isMacroID()) ? nullptr : Literal;
+ return (Literal && Literal->getBeginLoc().isMacroID()) ? nullptr : Literal;
}
internal::Matcher<Stmt> returnsBool(bool Value, StringRef Id = "ignored") {
@@ -372,7 +372,7 @@ void SimplifyBooleanExprCheck::reportBinOp(
else
return;
- if (Bool->getLocStart().isMacroID())
+ if (Bool->getBeginLoc().isMacroID())
return;
// FIXME: why do we need this?
@@ -385,8 +385,8 @@ void SimplifyBooleanExprCheck::reportBinOp(
const Expr *ReplaceWith, bool Negated) {
std::string Replacement =
replacementExpression(Result, Negated, ReplaceWith);
- SourceRange Range(LHS->getLocStart(), RHS->getLocEnd());
- issueDiag(Result, Bool->getLocStart(), SimplifyOperatorDiagnostic, Range,
+ SourceRange Range(LHS->getBeginLoc(), RHS->getEndLoc());
+ issueDiag(Result, Bool->getBeginLoc(), SimplifyOperatorDiagnostic, Range,
Replacement);
};
@@ -577,7 +577,7 @@ void SimplifyBooleanExprCheck::replaceWithThenStatement(
const MatchFinder::MatchResult &Result,
const CXXBoolLiteralExpr *TrueConditionRemoved) {
const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
- issueDiag(Result, TrueConditionRemoved->getLocStart(),
+ issueDiag(Result, TrueConditionRemoved->getBeginLoc(),
SimplifyConditionDiagnostic, IfStatement->getSourceRange(),
getText(Result, *IfStatement->getThen()));
}
@@ -587,7 +587,7 @@ void SimplifyBooleanExprCheck::replaceWithElseStatement(
const CXXBoolLiteralExpr *FalseConditionRemoved) {
const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
const Stmt *ElseStatement = IfStatement->getElse();
- issueDiag(Result, FalseConditionRemoved->getLocStart(),
+ issueDiag(Result, FalseConditionRemoved->getBeginLoc(),
SimplifyConditionDiagnostic, IfStatement->getSourceRange(),
ElseStatement ? getText(Result, *ElseStatement) : "");
}
@@ -597,7 +597,7 @@ void SimplifyBooleanExprCheck::replaceWithCondition(
bool Negated) {
std::string Replacement =
replacementExpression(Result, Negated, Ternary->getCond());
- issueDiag(Result, Ternary->getTrueExpr()->getLocStart(),
+ issueDiag(Result, Ternary->getTrueExpr()->getBeginLoc(),
"redundant boolean literal in ternary expression result",
Ternary->getSourceRange(), Replacement);
}
@@ -608,7 +608,7 @@ void SimplifyBooleanExprCheck::replaceWithReturnCondition(
std::string Condition = replacementExpression(Result, Negated, If->getCond());
std::string Replacement = ("return " + Condition + Terminator).str();
SourceLocation Start =
- Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getLocStart();
+ Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getBeginLoc();
issueDiag(Result, Start, SimplifyConditionalReturnDiagnostic,
If->getSourceRange(), Replacement);
}
@@ -640,8 +640,8 @@ void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
std::string Replacement =
"return " + replacementExpression(Result, Negated, Condition);
issueDiag(
- Result, Lit->getLocStart(), SimplifyConditionalReturnDiagnostic,
- SourceRange(If->getLocStart(), Ret->getLocEnd()), Replacement);
+ Result, Lit->getBeginLoc(), SimplifyConditionalReturnDiagnostic,
+ SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement);
return;
}
@@ -665,7 +665,7 @@ void SimplifyBooleanExprCheck::replaceWithAssignment(
std::string Replacement =
(VariableName + " = " + Condition + Terminator).str();
SourceLocation Location =
- Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getLocStart();
+ Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getBeginLoc();
issueDiag(Result, Location,
"redundant boolean literal in conditional assignment", Range,
Replacement);
diff --git a/clang-tidy/readability/SimplifySubscriptExprCheck.cpp b/clang-tidy/readability/SimplifySubscriptExprCheck.cpp
index 28cc576d..f4c306e9 100644
--- a/clang-tidy/readability/SimplifySubscriptExprCheck.cpp
+++ b/clang-tidy/readability/SimplifySubscriptExprCheck.cpp
@@ -60,10 +60,10 @@ void SimplifySubscriptExprCheck::check(const MatchFinder::MatchResult &Result) {
"accessing an element of the container does not require a call to "
"'data()'; did you mean to use 'operator[]'?");
if (Member->isArrow())
- DiagBuilder << FixItHint::CreateInsertion(Member->getLocStart(), "(*")
+ DiagBuilder << FixItHint::CreateInsertion(Member->getBeginLoc(), "(*")
<< FixItHint::CreateInsertion(Member->getOperatorLoc(), ")");
DiagBuilder << FixItHint::CreateRemoval(
- {Member->getOperatorLoc(), Call->getLocEnd()});
+ {Member->getOperatorLoc(), Call->getEndLoc()});
}
void SimplifySubscriptExprCheck::storeOptions(
diff --git a/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp b/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp
index b1365772..92d78796 100644
--- a/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp
+++ b/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp
@@ -51,7 +51,7 @@ void StaticAccessedThroughInstanceCheck::check(
const auto *MemberExpression =
Result.Nodes.getNodeAs<MemberExpr>("memberExpression");
- if (MemberExpression->getLocStart().isMacroID())
+ if (MemberExpression->getBeginLoc().isMacroID())
return;
const Expr *BaseExpr = MemberExpression->getBase();
@@ -71,7 +71,7 @@ void StaticAccessedThroughInstanceCheck::check(
std::string BaseTypeName =
BaseType.getAsString(PrintingPolicyWithSupressedTag);
- SourceLocation MemberExprStartLoc = MemberExpression->getLocStart();
+ SourceLocation MemberExprStartLoc = MemberExpression->getBeginLoc();
auto Diag =
diag(MemberExprStartLoc, "static member accessed through instance");
diff --git a/clang-tidy/readability/StringCompareCheck.cpp b/clang-tidy/readability/StringCompareCheck.cpp
index e75e80bb..38ac43f2 100644
--- a/clang-tidy/readability/StringCompareCheck.cpp
+++ b/clang-tidy/readability/StringCompareCheck.cpp
@@ -51,7 +51,7 @@ void StringCompareCheck::registerMatchers(MatchFinder *Finder) {
void StringCompareCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Matched = Result.Nodes.getNodeAs<Stmt>("match1")) {
- diag(Matched->getLocStart(), CompareMessage);
+ diag(Matched->getBeginLoc(), CompareMessage);
return;
}
@@ -63,10 +63,10 @@ void StringCompareCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Str2 = Result.Nodes.getNodeAs<Stmt>("str2");
const auto *Compare = Result.Nodes.getNodeAs<Stmt>("compare");
- auto Diag = diag(Matched->getLocStart(), CompareMessage);
+ auto Diag = diag(Matched->getBeginLoc(), CompareMessage);
if (Str1->isArrow())
- Diag << FixItHint::CreateInsertion(Str1->getLocStart(), "*");
+ Diag << FixItHint::CreateInsertion(Str1->getBeginLoc(), "*");
Diag << tooling::fixit::createReplacement(*Zero, *Str2, Ctx)
<< tooling::fixit::createReplacement(*Compare, *Str1->getBase(),
diff --git a/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp
index 3ad346cd..bb2c6901 100644
--- a/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp
+++ b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp
@@ -42,7 +42,7 @@ void UniqueptrDeleteReleaseCheck::check(
const auto *PtrExpr = Result.Nodes.getNodeAs<Expr>("uptr");
const auto *DeleteExpr = Result.Nodes.getNodeAs<Expr>("delete");
- if (PtrExpr->getLocStart().isMacroID())
+ if (PtrExpr->getBeginLoc().isMacroID())
return;
// Ignore dependent types.
@@ -52,15 +52,15 @@ void UniqueptrDeleteReleaseCheck::check(
return;
SourceLocation AfterPtr = Lexer::getLocForEndOfToken(
- PtrExpr->getLocEnd(), 0, *Result.SourceManager, getLangOpts());
+ PtrExpr->getEndLoc(), 0, *Result.SourceManager, getLangOpts());
- diag(DeleteExpr->getLocStart(),
+ diag(DeleteExpr->getBeginLoc(),
"prefer '= nullptr' to 'delete x.release()' to reset unique_ptr<> "
"objects")
<< FixItHint::CreateRemoval(CharSourceRange::getCharRange(
- DeleteExpr->getLocStart(), PtrExpr->getLocStart()))
+ DeleteExpr->getBeginLoc(), PtrExpr->getBeginLoc()))
<< FixItHint::CreateReplacement(
- CharSourceRange::getTokenRange(AfterPtr, DeleteExpr->getLocEnd()),
+ CharSourceRange::getTokenRange(AfterPtr, DeleteExpr->getEndLoc()),
" = nullptr");
}
diff --git a/clang-tidy/tool/ClangTidyMain.cpp b/clang-tidy/tool/ClangTidyMain.cpp
index 97a5a27f..b458b293 100644
--- a/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tidy/tool/ClangTidyMain.cpp
@@ -181,6 +181,15 @@ report to stderr.
cl::init(false),
cl::cat(ClangTidyCategory));
+static cl::opt<std::string> StoreCheckProfile("store-check-profile",
+ cl::desc(R"(
+By default reports are printed in tabulated
+format to stderr. When this option is passed,
+these per-TU profiles are instead stored as JSON.
+)"),
+ cl::value_desc("prefix"),
+ cl::cat(ClangTidyCategory));
+
/// This option allows enabling the experimental alpha checkers from the static
/// analyzer. This option is set to false and not visible in help, because it is
/// highly not recommended for users.
@@ -331,17 +340,27 @@ static int clangTidyMain(int argc, const char **argv) {
if (!OptionsProvider)
return 1;
+ auto MakeAbsolute = [](const std::string &Input) -> SmallString<256> {
+ if (Input.empty())
+ return {};
+ SmallString<256> AbsolutePath(Input);
+ if (std::error_code EC = llvm::sys::fs::make_absolute(AbsolutePath)) {
+ llvm::errs() << "Can't make absolute path from " << Input << ": "
+ << EC.message() << "\n";
+ }
+ return AbsolutePath;
+ };
+
+ SmallString<256> ProfilePrefix = MakeAbsolute(StoreCheckProfile);
+
StringRef FileName("dummy");
auto PathList = OptionsParser.getSourcePathList();
if (!PathList.empty()) {
FileName = PathList.front();
}
- SmallString<256> FilePath(FileName);
- if (std::error_code EC = llvm::sys::fs::make_absolute(FilePath)) {
- llvm::errs() << "Can't make absolute path from " << FileName << ": "
- << EC.message() << "\n";
- }
+ SmallString<256> FilePath = MakeAbsolute(FileName);
+
ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath);
std::vector<std::string> EnabledChecks =
getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
@@ -403,7 +422,7 @@ static int clangTidyMain(int argc, const char **argv) {
ClangTidyContext Context(std::move(OwningOptionsProvider),
AllowEnablingAnalyzerAlphaCheckers);
runClangTidy(Context, OptionsParser.getCompilations(), PathList, BaseFS,
- EnableCheckProfile);
+ EnableCheckProfile, ProfilePrefix);
ArrayRef<ClangTidyError> Errors = Context.getErrors();
bool FoundErrors = llvm::find_if(Errors, [](const ClangTidyError &E) {
return E.DiagLevel == ClangTidyError::Error;
diff --git a/clang-tidy/utils/ASTUtils.cpp b/clang-tidy/utils/ASTUtils.cpp
index ab6077a7..5c6b9843 100644
--- a/clang-tidy/utils/ASTUtils.cpp
+++ b/clang-tidy/utils/ASTUtils.cpp
@@ -45,8 +45,8 @@ bool exprHasBitFlagWithSpelling(const Expr *Flags, const SourceManager &SM,
StringRef FlagName) {
// If the Flag is an integer constant, check it.
if (isa<IntegerLiteral>(Flags)) {
- if (!SM.isMacroBodyExpansion(Flags->getLocStart()) &&
- !SM.isMacroArgExpansion(Flags->getLocStart()))
+ if (!SM.isMacroBodyExpansion(Flags->getBeginLoc()) &&
+ !SM.isMacroArgExpansion(Flags->getBeginLoc()))
return false;
// Get the macro name.
diff --git a/clang-tidy/utils/CMakeLists.txt b/clang-tidy/utils/CMakeLists.txt
index 9162bce1..487b09ac 100644
--- a/clang-tidy/utils/CMakeLists.txt
+++ b/clang-tidy/utils/CMakeLists.txt
@@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS support)
add_clang_library(clangTidyUtils
ASTUtils.cpp
DeclRefExprUtils.cpp
+ ExprMutationAnalyzer.cpp
ExprSequence.cpp
FixItHintUtils.cpp
HeaderFileExtensionsUtils.cpp
diff --git a/clang-tidy/utils/ExprMutationAnalyzer.cpp b/clang-tidy/utils/ExprMutationAnalyzer.cpp
new file mode 100644
index 00000000..424fa8f6
--- /dev/null
+++ b/clang-tidy/utils/ExprMutationAnalyzer.cpp
@@ -0,0 +1,260 @@
+//===---------- ExprMutationAnalyzer.cpp - clang-tidy ---------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "ExprMutationAnalyzer.h"
+
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/ADT/STLExtras.h"
+
+namespace clang {
+namespace tidy {
+namespace utils {
+using namespace ast_matchers;
+
+namespace {
+
+AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr *, E) {
+ return llvm::is_contained(Node.capture_inits(), E);
+}
+
+AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt,
+ ast_matchers::internal::Matcher<DeclStmt>, InnerMatcher) {
+ const DeclStmt *const Range = Node.getRangeStmt();
+ return InnerMatcher.matches(*Range, Finder, Builder);
+}
+
+const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXTypeidExpr>
+ cxxTypeidExpr;
+
+AST_MATCHER(CXXTypeidExpr, isPotentiallyEvaluated) {
+ return Node.isPotentiallyEvaluated();
+}
+
+const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXNoexceptExpr>
+ cxxNoexceptExpr;
+
+const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt,
+ GenericSelectionExpr>
+ genericSelectionExpr;
+
+AST_MATCHER_P(GenericSelectionExpr, hasControllingExpr,
+ ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
+ return InnerMatcher.matches(*Node.getControllingExpr(), Finder, Builder);
+}
+
+const auto nonConstReferenceType = [] {
+ return referenceType(pointee(unless(isConstQualified())));
+};
+
+} // namespace
+
+const Stmt *ExprMutationAnalyzer::findMutation(const Expr *Exp) {
+ const auto Memoized = Results.find(Exp);
+ if (Memoized != Results.end())
+ return Memoized->second;
+
+ if (isUnevaluated(Exp))
+ return Results[Exp] = nullptr;
+
+ for (const auto &Finder : {&ExprMutationAnalyzer::findDirectMutation,
+ &ExprMutationAnalyzer::findMemberMutation,
+ &ExprMutationAnalyzer::findArrayElementMutation,
+ &ExprMutationAnalyzer::findCastMutation,
+ &ExprMutationAnalyzer::findRangeLoopMutation,
+ &ExprMutationAnalyzer::findReferenceMutation}) {
+ if (const Stmt *S = (this->*Finder)(Exp))
+ return Results[Exp] = S;
+ }
+
+ return Results[Exp] = nullptr;
+}
+
+bool ExprMutationAnalyzer::isUnevaluated(const Expr *Exp) {
+ return selectFirst<Expr>(
+ "expr",
+ match(
+ findAll(
+ expr(equalsNode(Exp),
+ anyOf(
+ // `Exp` is part of the underlying expression of
+ // decltype/typeof if it has an ancestor of
+ // typeLoc.
+ hasAncestor(typeLoc(unless(
+ hasAncestor(unaryExprOrTypeTraitExpr())))),
+ hasAncestor(expr(anyOf(
+ // `UnaryExprOrTypeTraitExpr` is unevaluated
+ // unless it's sizeof on VLA.
+ unaryExprOrTypeTraitExpr(unless(sizeOfExpr(
+ hasArgumentOfType(variableArrayType())))),
+ // `CXXTypeidExpr` is unevaluated unless it's
+ // applied to an expression of glvalue of
+ // polymorphic class type.
+ cxxTypeidExpr(
+ unless(isPotentiallyEvaluated())),
+ // The controlling expression of
+ // `GenericSelectionExpr` is unevaluated.
+ genericSelectionExpr(hasControllingExpr(
+ hasDescendant(equalsNode(Exp)))),
+ cxxNoexceptExpr())))))
+ .bind("expr")),
+ *Stm, *Context)) != nullptr;
+}
+
+const Stmt *
+ExprMutationAnalyzer::findExprMutation(ArrayRef<BoundNodes> Matches) {
+ for (const auto &Nodes : Matches) {
+ if (const Stmt *S = findMutation(Nodes.getNodeAs<Expr>("expr")))
+ return S;
+ }
+ return nullptr;
+}
+
+const Stmt *
+ExprMutationAnalyzer::findDeclMutation(ArrayRef<BoundNodes> Matches) {
+ for (const auto &DeclNodes : Matches) {
+ if (const Stmt *S = findDeclMutation(DeclNodes.getNodeAs<Decl>("decl")))
+ return S;
+ }
+ return nullptr;
+}
+
+const Stmt *ExprMutationAnalyzer::findDeclMutation(const Decl *Dec) {
+ const auto Refs = match(
+ findAll(declRefExpr(to(equalsNode(Dec))).bind("expr")), *Stm, *Context);
+ for (const auto &RefNodes : Refs) {
+ const auto *E = RefNodes.getNodeAs<Expr>("expr");
+ if (findMutation(E))
+ return E;
+ }
+ return nullptr;
+}
+
+const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) {
+ // LHS of any assignment operators.
+ const auto AsAssignmentLhs =
+ binaryOperator(isAssignmentOperator(), hasLHS(equalsNode(Exp)));
+
+ // Operand of increment/decrement operators.
+ const auto AsIncDecOperand =
+ unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")),
+ hasUnaryOperand(equalsNode(Exp)));
+
+ // Invoking non-const member function.
+ const auto NonConstMethod = cxxMethodDecl(unless(isConst()));
+ const auto AsNonConstThis =
+ expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), on(equalsNode(Exp))),
+ cxxOperatorCallExpr(callee(NonConstMethod),
+ hasArgument(0, equalsNode(Exp)))));
+
+ // Taking address of 'Exp'.
+ // We're assuming 'Exp' is mutated as soon as its address is taken, though in
+ // theory we can follow the pointer and see whether it escaped `Stm` or is
+ // dereferenced and then mutated. This is left for future improvements.
+ const auto AsAmpersandOperand =
+ unaryOperator(hasOperatorName("&"),
+ // A NoOp implicit cast is adding const.
+ unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))),
+ hasUnaryOperand(equalsNode(Exp)));
+ const auto AsPointerFromArrayDecay =
+ castExpr(hasCastKind(CK_ArrayToPointerDecay),
+ unless(hasParent(arraySubscriptExpr())), has(equalsNode(Exp)));
+
+ // Used as non-const-ref argument when calling a function.
+ const auto NonConstRefParam = forEachArgumentWithParam(
+ equalsNode(Exp), parmVarDecl(hasType(nonConstReferenceType())));
+ const auto AsNonConstRefArg =
+ anyOf(callExpr(NonConstRefParam), cxxConstructExpr(NonConstRefParam));
+
+ // Captured by a lambda by reference.
+ // If we're initializing a capture with 'Exp' directly then we're initializing
+ // a reference capture.
+ // For value captures there will be an ImplicitCastExpr <LValueToRValue>.
+ const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp));
+
+ // Returned as non-const-ref.
+ // If we're returning 'Exp' directly then it's returned as non-const-ref.
+ // For returning by value there will be an ImplicitCastExpr <LValueToRValue>.
+ // For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for
+ // adding const.)
+ const auto AsNonConstRefReturn = returnStmt(hasReturnValue(equalsNode(Exp)));
+
+ const auto Matches =
+ match(findAll(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis,
+ AsAmpersandOperand, AsPointerFromArrayDecay,
+ AsNonConstRefArg, AsLambdaRefCaptureInit,
+ AsNonConstRefReturn))
+ .bind("stmt")),
+ *Stm, *Context);
+ return selectFirst<Stmt>("stmt", Matches);
+}
+
+const Stmt *ExprMutationAnalyzer::findMemberMutation(const Expr *Exp) {
+ // Check whether any member of 'Exp' is mutated.
+ const auto MemberExprs = match(
+ findAll(memberExpr(hasObjectExpression(equalsNode(Exp))).bind("expr")),
+ *Stm, *Context);
+ return findExprMutation(MemberExprs);
+}
+
+const Stmt *ExprMutationAnalyzer::findArrayElementMutation(const Expr *Exp) {
+ // Check whether any element of an array is mutated.
+ const auto SubscriptExprs = match(
+ findAll(arraySubscriptExpr(hasBase(ignoringImpCasts(equalsNode(Exp))))
+ .bind("expr")),
+ *Stm, *Context);
+ return findExprMutation(SubscriptExprs);
+}
+
+const Stmt *ExprMutationAnalyzer::findCastMutation(const Expr *Exp) {
+ // If 'Exp' is casted to any non-const reference type, check the castExpr.
+ const auto Casts =
+ match(findAll(castExpr(hasSourceExpression(equalsNode(Exp)),
+ anyOf(explicitCastExpr(hasDestinationType(
+ nonConstReferenceType())),
+ implicitCastExpr(hasImplicitDestinationType(
+ nonConstReferenceType()))))
+ .bind("expr")),
+ *Stm, *Context);
+ return findExprMutation(Casts);
+}
+
+const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) {
+ // If range for looping over 'Exp' with a non-const reference loop variable,
+ // check all declRefExpr of the loop variable.
+ const auto LoopVars =
+ match(findAll(cxxForRangeStmt(
+ hasLoopVariable(
+ varDecl(hasType(nonConstReferenceType())).bind("decl")),
+ hasRangeInit(equalsNode(Exp)))),
+ *Stm, *Context);
+ return findDeclMutation(LoopVars);
+}
+
+const Stmt *ExprMutationAnalyzer::findReferenceMutation(const Expr *Exp) {
+ // If 'Exp' is bound to a non-const reference, check all declRefExpr to that.
+ const auto Refs = match(
+ stmt(forEachDescendant(
+ varDecl(
+ hasType(nonConstReferenceType()),
+ hasInitializer(anyOf(equalsNode(Exp),
+ conditionalOperator(anyOf(
+ hasTrueExpression(equalsNode(Exp)),
+ hasFalseExpression(equalsNode(Exp)))))),
+ hasParent(declStmt().bind("stmt")),
+ // Don't follow the reference in range statement, we've handled
+ // that separately.
+ unless(hasParent(declStmt(hasParent(
+ cxxForRangeStmt(hasRangeStmt(equalsBoundNode("stmt"))))))))
+ .bind("decl"))),
+ *Stm, *Context);
+ return findDeclMutation(Refs);
+}
+
+} // namespace utils
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/utils/ExprMutationAnalyzer.h b/clang-tidy/utils/ExprMutationAnalyzer.h
new file mode 100644
index 00000000..256bb712
--- /dev/null
+++ b/clang-tidy/utils/ExprMutationAnalyzer.h
@@ -0,0 +1,56 @@
+//===---------- ExprMutationAnalyzer.h - clang-tidy -----------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H
+
+#include <type_traits>
+
+#include "clang/AST/AST.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/DenseMap.h"
+
+namespace clang {
+namespace tidy {
+namespace utils {
+
+/// Analyzes whether any mutative operations are applied to an expression within
+/// a given statement.
+class ExprMutationAnalyzer {
+public:
+ ExprMutationAnalyzer(const Stmt *Stm, ASTContext *Context)
+ : Stm(Stm), Context(Context) {}
+
+ bool isMutated(const Decl *Dec) { return findDeclMutation(Dec) != nullptr; }
+ bool isMutated(const Expr *Exp) { return findMutation(Exp) != nullptr; }
+ const Stmt *findMutation(const Expr *Exp);
+
+private:
+ bool isUnevaluated(const Expr *Exp);
+
+ const Stmt *findExprMutation(ArrayRef<ast_matchers::BoundNodes> Matches);
+ const Stmt *findDeclMutation(ArrayRef<ast_matchers::BoundNodes> Matches);
+ const Stmt *findDeclMutation(const Decl *Dec);
+
+ const Stmt *findDirectMutation(const Expr *Exp);
+ const Stmt *findMemberMutation(const Expr *Exp);
+ const Stmt *findArrayElementMutation(const Expr *Exp);
+ const Stmt *findCastMutation(const Expr *Exp);
+ const Stmt *findRangeLoopMutation(const Expr *Exp);
+ const Stmt *findReferenceMutation(const Expr *Exp);
+
+ const Stmt *const Stm;
+ ASTContext *const Context;
+ llvm::DenseMap<const Expr *, const Stmt *> Results;
+};
+
+} // namespace utils
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H
diff --git a/clang-tidy/utils/ExprSequence.cpp b/clang-tidy/utils/ExprSequence.cpp
index 02d4a0bd..48c3de54 100644
--- a/clang-tidy/utils/ExprSequence.cpp
+++ b/clang-tidy/utils/ExprSequence.cpp
@@ -139,11 +139,26 @@ const Stmt *ExprSequence::getSequenceSuccessor(const Stmt *S) const {
if (S == ForRange->getLoopVarStmt())
return ForRange->getBody();
} else if (const auto *TheIfStmt = dyn_cast<IfStmt>(Parent)) {
- // If statement: If a variable is declared inside the condition, the
- // expression used to initialize the variable is sequenced before the
- // evaluation of the condition.
+ // If statement:
+ // - Sequence init statement before variable declaration.
+ // - Sequence variable declaration (along with the expression used to
+ // initialize it) before the evaluation of the condition.
+ if (S == TheIfStmt->getInit())
+ return TheIfStmt->getConditionVariableDeclStmt();
if (S == TheIfStmt->getConditionVariableDeclStmt())
return TheIfStmt->getCond();
+ } else if (const auto *TheSwitchStmt = dyn_cast<SwitchStmt>(Parent)) {
+ // Ditto for switch statements.
+ if (S == TheSwitchStmt->getInit())
+ return TheSwitchStmt->getConditionVariableDeclStmt();
+ if (S == TheSwitchStmt->getConditionVariableDeclStmt())
+ return TheSwitchStmt->getCond();
+ } else if (const auto *TheWhileStmt = dyn_cast<WhileStmt>(Parent)) {
+ // While statement: Sequence variable declaration (along with the
+ // expression used to initialize it) before the evaluation of the
+ // condition.
+ if (S == TheWhileStmt->getConditionVariableDeclStmt())
+ return TheWhileStmt->getCond();
}
}
diff --git a/clang-tidy/utils/NamespaceAliaser.cpp b/clang-tidy/utils/NamespaceAliaser.cpp
index 1a5120de..25182304 100644
--- a/clang-tidy/utils/NamespaceAliaser.cpp
+++ b/clang-tidy/utils/NamespaceAliaser.cpp
@@ -70,7 +70,7 @@ NamespaceAliaser::createAlias(ASTContext &Context, const Stmt &Statement,
(llvm::Twine("\nnamespace ") + Abbreviation + " = " + Namespace + ";")
.str();
SourceLocation Loc =
- Lexer::getLocForEndOfToken(Function->getBody()->getLocStart(), 0,
+ Lexer::getLocForEndOfToken(Function->getBody()->getBeginLoc(), 0,
SourceMgr, Context.getLangOpts());
AddedAliases[Function][Namespace.str()] = Abbreviation;
return FixItHint::CreateInsertion(Loc, Declaration);
diff --git a/clang-tidy/utils/UsingInserter.cpp b/clang-tidy/utils/UsingInserter.cpp
index e7200c99..e479d591 100644
--- a/clang-tidy/utils/UsingInserter.cpp
+++ b/clang-tidy/utils/UsingInserter.cpp
@@ -41,7 +41,7 @@ Optional<FixItHint> UsingInserter::createUsingDeclaration(
return None;
SourceLocation InsertLoc = Lexer::getLocForEndOfToken(
- Function->getBody()->getLocStart(), 0, SourceMgr, Context.getLangOpts());
+ Function->getBody()->getBeginLoc(), 0, SourceMgr, Context.getLangOpts());
// Only use using declarations in the main file, not in includes.
if (SourceMgr.getFileID(InsertLoc) != SourceMgr.getMainFileID())
diff --git a/clangd/AST.cpp b/clangd/AST.cpp
index cfcde118..1344931a 100644
--- a/clangd/AST.cpp
+++ b/clangd/AST.cpp
@@ -12,6 +12,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/SourceManager.h"
+#include "clang/Index/USRGeneration.h"
namespace clang {
namespace clangd {
@@ -38,5 +39,27 @@ SourceLocation findNameLoc(const clang::Decl* D) {
return SpellingLoc;
}
+std::string printQualifiedName(const NamedDecl &ND) {
+ std::string QName;
+ llvm::raw_string_ostream OS(QName);
+ PrintingPolicy Policy(ND.getASTContext().getLangOpts());
+ // Note that inline namespaces are treated as transparent scopes. This
+ // reflects the way they're most commonly used for lookup. Ideally we'd
+ // include them, but at query time it's hard to find all the inline
+ // namespaces to query: the preamble doesn't have a dedicated list.
+ Policy.SuppressUnwrittenScope = true;
+ ND.printQualifiedName(OS, Policy);
+ OS.flush();
+ assert(!StringRef(QName).startswith("::"));
+ return QName;
+}
+
+llvm::Optional<SymbolID> getSymbolID(const Decl *D) {
+ llvm::SmallString<128> USR;
+ if (index::generateUSRForDecl(D, USR))
+ return None;
+ return SymbolID(USR);
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/AST.h b/clangd/AST.h
index aeeb22d2..9f00f829 100644
--- a/clangd/AST.h
+++ b/clangd/AST.h
@@ -14,7 +14,9 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H_
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H_
+#include "clang/AST/Decl.h"
#include "clang/Basic/SourceLocation.h"
+#include "index/Index.h"
namespace clang {
class SourceManager;
@@ -28,6 +30,13 @@ namespace clangd {
/// decl occurs in the code.
SourceLocation findNameLoc(const clang::Decl *D);
+/// Returns the qualified name of ND. The scope doesn't contain unwritten scopes
+/// like inline namespaces.
+std::string printQualifiedName(const NamedDecl &ND);
+
+/// Gets the symbol ID for a declaration, if possible.
+llvm::Optional<SymbolID> getSymbolID(const Decl *D);
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/CMakeLists.txt b/clangd/CMakeLists.txt
index 6ff7fc55..33a6e842 100644
--- a/clangd/CMakeLists.txt
+++ b/clangd/CMakeLists.txt
@@ -14,16 +14,15 @@ add_clang_library(clangDaemon
ClangdUnit.cpp
CodeComplete.cpp
CodeCompletionStrings.cpp
- CompileArgsCache.cpp
Compiler.cpp
Context.cpp
Diagnostics.cpp
DraftStore.cpp
FindSymbols.cpp
+ FileDistance.cpp
FuzzyMatch.cpp
GlobalCompilationDatabase.cpp
Headers.cpp
- JSONExpr.cpp
JSONRPCDispatcher.cpp
Logger.cpp
Protocol.cpp
@@ -35,6 +34,7 @@ add_clang_library(clangDaemon
TUScheduler.cpp
URI.cpp
XRefs.cpp
+
index/CanonicalIncludes.cpp
index/FileIndex.cpp
index/Index.cpp
@@ -43,6 +43,9 @@ add_clang_library(clangDaemon
index/SymbolCollector.cpp
index/SymbolYAML.cpp
+ index/dex/Iterator.cpp
+ index/dex/Trigram.cpp
+
LINK_LIBS
clangAST
clangASTMatchers
diff --git a/clangd/ClangdLSPServer.cpp b/clangd/ClangdLSPServer.cpp
index e75cea91..99353e6c 100644
--- a/clangd/ClangdLSPServer.cpp
+++ b/clangd/ClangdLSPServer.cpp
@@ -18,6 +18,7 @@
using namespace clang::clangd;
using namespace clang;
+using namespace llvm;
namespace {
@@ -71,6 +72,9 @@ SymbolKindBitset defaultSymbolKinds() {
} // namespace
void ClangdLSPServer::onInitialize(InitializeParams &Params) {
+ if (Params.initializationOptions)
+ applyConfiguration(*Params.initializationOptions);
+
if (Params.rootUri && *Params.rootUri)
Server.setRootPath(Params.rootUri->file());
else if (Params.rootPath && !Params.rootPath->empty())
@@ -87,34 +91,35 @@ void ClangdLSPServer::onInitialize(InitializeParams &Params) {
}
}
- reply(json::obj{
+ reply(json::Object{
{{"capabilities",
- json::obj{
+ json::Object{
{"textDocumentSync", (int)TextDocumentSyncKind::Incremental},
{"documentFormattingProvider", true},
{"documentRangeFormattingProvider", true},
{"documentOnTypeFormattingProvider",
- json::obj{
+ json::Object{
{"firstTriggerCharacter", "}"},
{"moreTriggerCharacter", {}},
}},
{"codeActionProvider", true},
{"completionProvider",
- json::obj{
+ json::Object{
{"resolveProvider", false},
{"triggerCharacters", {".", ">", ":"}},
}},
{"signatureHelpProvider",
- json::obj{
+ json::Object{
{"triggerCharacters", {"(", ","}},
}},
{"definitionProvider", true},
{"documentHighlightProvider", true},
{"hoverProvider", true},
{"renameProvider", true},
+ {"documentSymbolProvider", true},
{"workspaceSymbolProvider", true},
{"executeCommandProvider",
- json::obj{
+ json::Object{
{"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
}},
}}}});
@@ -129,11 +134,10 @@ void ClangdLSPServer::onShutdown(ShutdownParams &Params) {
void ClangdLSPServer::onExit(ExitParams &Params) { IsDone = true; }
void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) {
+ PathRef File = Params.textDocument.uri.file();
if (Params.metadata && !Params.metadata->extraFlags.empty())
- CDB.setExtraFlagsForFile(Params.textDocument.uri.file(),
- std::move(Params.metadata->extraFlags));
+ CDB.setExtraFlagsForFile(File, std::move(Params.metadata->extraFlags));
- PathRef File = Params.textDocument.uri.file();
std::string &Contents = Params.textDocument.text;
DraftMgr.addDraft(File, Contents);
@@ -155,7 +159,8 @@ void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) {
// fail rather than giving wrong results.
DraftMgr.removeDraft(File);
Server.removeDocument(File);
- log(llvm::toString(Contents.takeError()));
+ CDB.invalidate(File);
+ elog("Failed to update {0}: {1}", File, Contents.takeError());
return;
}
@@ -208,7 +213,7 @@ void ClangdLSPServer::onWorkspaceSymbol(WorkspaceSymbolParams &Params) {
for (auto &Sym : *Items)
Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
- reply(json::ary(*Items));
+ reply(json::Array(*Items));
});
}
@@ -242,6 +247,7 @@ void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) {
PathRef File = Params.textDocument.uri.file();
DraftMgr.removeDraft(File);
Server.removeDocument(File);
+ CDB.invalidate(File);
}
void ClangdLSPServer::onDocumentOnTypeFormatting(
@@ -254,7 +260,7 @@ void ClangdLSPServer::onDocumentOnTypeFormatting(
auto ReplacementsOrError = Server.formatOnType(*Code, File, Params.position);
if (ReplacementsOrError)
- reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get())));
+ reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
else
replyError(ErrorCode::UnknownErrorCode,
llvm::toString(ReplacementsOrError.takeError()));
@@ -270,7 +276,7 @@ void ClangdLSPServer::onDocumentRangeFormatting(
auto ReplacementsOrError = Server.formatRange(*Code, File, Params.range);
if (ReplacementsOrError)
- reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get())));
+ reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
else
replyError(ErrorCode::UnknownErrorCode,
llvm::toString(ReplacementsOrError.takeError()));
@@ -285,12 +291,25 @@ void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) {
auto ReplacementsOrError = Server.formatFile(*Code, File);
if (ReplacementsOrError)
- reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get())));
+ reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
else
replyError(ErrorCode::UnknownErrorCode,
llvm::toString(ReplacementsOrError.takeError()));
}
+void ClangdLSPServer::onDocumentSymbol(DocumentSymbolParams &Params) {
+ Server.documentSymbols(
+ Params.textDocument.uri.file(),
+ [this](llvm::Expected<std::vector<SymbolInformation>> Items) {
+ if (!Items)
+ return replyError(ErrorCode::InvalidParams,
+ llvm::toString(Items.takeError()));
+ for (auto &Sym : *Items)
+ Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
+ reply(json::Array(*Items));
+ });
+}
+
void ClangdLSPServer::onCodeAction(CodeActionParams &Params) {
// We provide a code action for each diagnostic at the requested location
// which has FixIts available.
@@ -299,13 +318,13 @@ void ClangdLSPServer::onCodeAction(CodeActionParams &Params) {
return replyError(ErrorCode::InvalidParams,
"onCodeAction called for non-added file");
- json::ary Commands;
+ json::Array Commands;
for (Diagnostic &D : Params.context.diagnostics) {
for (auto &F : getFixes(Params.textDocument.uri.file(), D)) {
WorkspaceEdit WE;
std::vector<TextEdit> Edits(F.Edits.begin(), F.Edits.end());
WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}};
- Commands.push_back(json::obj{
+ Commands.push_back(json::Object{
{"title", llvm::formatv("Apply fix: {0}", F.Message)},
{"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND},
{"arguments", {WE}},
@@ -317,11 +336,15 @@ void ClangdLSPServer::onCodeAction(CodeActionParams &Params) {
void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) {
Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts,
- [](llvm::Expected<CompletionList> List) {
+ [this](llvm::Expected<CodeCompleteResult> List) {
if (!List)
return replyError(ErrorCode::InvalidParams,
llvm::toString(List.takeError()));
- reply(*List);
+ CompletionList LSPList;
+ LSPList.isIncomplete = List->HasMore;
+ for (const auto &R : List->Completions)
+ LSPList.items.push_back(R.render(CCOpts));
+ reply(std::move(LSPList));
});
}
@@ -343,7 +366,7 @@ void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) {
if (!Items)
return replyError(ErrorCode::InvalidParams,
llvm::toString(Items.takeError()));
- reply(json::ary(*Items));
+ reply(json::Array(*Items));
});
}
@@ -359,13 +382,13 @@ void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) {
if (!Highlights)
return replyError(ErrorCode::InternalError,
llvm::toString(Highlights.takeError()));
- reply(json::ary(*Highlights));
+ reply(json::Array(*Highlights));
});
}
void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) {
Server.findHover(Params.textDocument.uri.file(), Params.position,
- [](llvm::Expected<Hover> H) {
+ [](llvm::Expected<llvm::Optional<Hover>> H) {
if (!H) {
replyError(ErrorCode::InternalError,
llvm::toString(H.takeError()));
@@ -376,31 +399,57 @@ void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) {
});
}
-// FIXME: This function needs to be properly tested.
-void ClangdLSPServer::onChangeConfiguration(
- DidChangeConfigurationParams &Params) {
- ClangdConfigurationParamsChange &Settings = Params.settings;
-
+void ClangdLSPServer::applyConfiguration(
+ const ClangdConfigurationParamsChange &Settings) {
// Compilation database change.
if (Settings.compilationDatabasePath.hasValue()) {
CDB.setCompileCommandsDir(Settings.compilationDatabasePath.getValue());
+
reparseOpenedFiles();
}
+
+ // Update to the compilation database.
+ if (Settings.compilationDatabaseChanges) {
+ const auto &CompileCommandUpdates = *Settings.compilationDatabaseChanges;
+ bool ShouldReparseOpenFiles = false;
+ for (auto &Entry : CompileCommandUpdates) {
+ /// The opened files need to be reparsed only when some existing
+ /// entries are changed.
+ PathRef File = Entry.first;
+ if (!CDB.setCompilationCommandForFile(
+ File, tooling::CompileCommand(
+ std::move(Entry.second.workingDirectory), File,
+ std::move(Entry.second.compilationCommand),
+ /*Output=*/"")))
+ ShouldReparseOpenFiles = true;
+ }
+ if (ShouldReparseOpenFiles)
+ reparseOpenedFiles();
+ }
+}
+
+// FIXME: This function needs to be properly tested.
+void ClangdLSPServer::onChangeConfiguration(
+ DidChangeConfigurationParams &Params) {
+ applyConfiguration(Params.settings);
}
ClangdLSPServer::ClangdLSPServer(JSONOutput &Out,
const clangd::CodeCompleteOptions &CCOpts,
llvm::Optional<Path> CompileCommandsDir,
+ bool ShouldUseInMemoryCDB,
const ClangdServer::Options &Opts)
- : Out(Out), CDB(std::move(CompileCommandsDir)), CCOpts(CCOpts),
- SupportedSymbolKinds(defaultSymbolKinds()),
- Server(CDB, FSProvider, /*DiagConsumer=*/*this, Opts) {}
+ : Out(Out), CDB(ShouldUseInMemoryCDB ? CompilationDB::makeInMemory()
+ : CompilationDB::makeDirectoryBased(
+ std::move(CompileCommandsDir))),
+ CCOpts(CCOpts), SupportedSymbolKinds(defaultSymbolKinds()),
+ Server(CDB.getCDB(), FSProvider, /*DiagConsumer=*/*this, Opts) {}
-bool ClangdLSPServer::run(std::istream &In, JSONStreamStyle InputStyle) {
+bool ClangdLSPServer::run(std::FILE *In, JSONStreamStyle InputStyle) {
assert(!IsDone && "Run was called before");
// Set up JSONRPCDispatcher.
- JSONRPCDispatcher Dispatcher([](const json::Expr &Params) {
+ JSONRPCDispatcher Dispatcher([](const json::Value &Params) {
replyError(ErrorCode::MethodNotFound, "method not found");
});
registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this);
@@ -432,12 +481,12 @@ std::vector<Fix> ClangdLSPServer::getFixes(StringRef File,
void ClangdLSPServer::onDiagnosticsReady(PathRef File,
std::vector<Diag> Diagnostics) {
- json::ary DiagnosticsJSON;
+ json::Array DiagnosticsJSON;
DiagnosticToReplacementMap LocalFixIts; // Temporary storage
for (auto &Diag : Diagnostics) {
toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) {
- DiagnosticsJSON.push_back(json::obj{
+ DiagnosticsJSON.push_back(json::Object{
{"range", Diag.range},
{"severity", Diag.severity},
{"message", Diag.message},
@@ -457,11 +506,11 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File,
}
// Publish diagnostics.
- Out.writeMessage(json::obj{
+ Out.writeMessage(json::Object{
{"jsonrpc", "2.0"},
{"method", "textDocument/publishDiagnostics"},
{"params",
- json::obj{
+ json::Object{
{"uri", URIForFile{File}},
{"diagnostics", std::move(DiagnosticsJSON)},
}},
@@ -471,6 +520,69 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File,
void ClangdLSPServer::reparseOpenedFiles() {
for (const Path &FilePath : DraftMgr.getActiveFiles())
Server.addDocument(FilePath, *DraftMgr.getDraft(FilePath),
- WantDiagnostics::Auto,
- /*SkipCache=*/true);
+ WantDiagnostics::Auto);
+}
+
+ClangdLSPServer::CompilationDB ClangdLSPServer::CompilationDB::makeInMemory() {
+ return CompilationDB(llvm::make_unique<InMemoryCompilationDb>(), nullptr,
+ /*IsDirectoryBased=*/false);
+}
+
+ClangdLSPServer::CompilationDB
+ClangdLSPServer::CompilationDB::makeDirectoryBased(
+ llvm::Optional<Path> CompileCommandsDir) {
+ auto CDB = llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(
+ std::move(CompileCommandsDir));
+ auto CachingCDB = llvm::make_unique<CachingCompilationDb>(*CDB);
+ return CompilationDB(std::move(CDB), std::move(CachingCDB),
+ /*IsDirectoryBased=*/true);
+}
+
+void ClangdLSPServer::CompilationDB::invalidate(PathRef File) {
+ if (!IsDirectoryBased)
+ static_cast<InMemoryCompilationDb *>(CDB.get())->invalidate(File);
+ else
+ CachingCDB->invalidate(File);
+}
+
+bool ClangdLSPServer::CompilationDB::setCompilationCommandForFile(
+ PathRef File, tooling::CompileCommand CompilationCommand) {
+ if (IsDirectoryBased) {
+ elog("Trying to set compile command for {0} while using directory-based "
+ "compilation database",
+ File);
+ return false;
+ }
+ return static_cast<InMemoryCompilationDb *>(CDB.get())
+ ->setCompilationCommandForFile(File, std::move(CompilationCommand));
+}
+
+void ClangdLSPServer::CompilationDB::setExtraFlagsForFile(
+ PathRef File, std::vector<std::string> ExtraFlags) {
+ if (!IsDirectoryBased) {
+ elog("Trying to set extra flags for {0} while using in-memory compilation "
+ "database",
+ File);
+ return;
+ }
+ static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
+ ->setExtraFlagsForFile(File, std::move(ExtraFlags));
+ CachingCDB->invalidate(File);
+}
+
+void ClangdLSPServer::CompilationDB::setCompileCommandsDir(Path P) {
+ if (!IsDirectoryBased) {
+ elog("Trying to set compile commands dir while using in-memory compilation "
+ "database");
+ return;
+ }
+ static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
+ ->setCompileCommandsDir(P);
+ CachingCDB->clear();
+}
+
+GlobalCompilationDatabase &ClangdLSPServer::CompilationDB::getCDB() {
+ if (CachingCDB)
+ return *CachingCDB;
+ return *CDB;
}
diff --git a/clangd/ClangdLSPServer.h b/clangd/ClangdLSPServer.h
index f4884d32..3850e4a9 100644
--- a/clangd/ClangdLSPServer.h
+++ b/clangd/ClangdLSPServer.h
@@ -35,15 +35,15 @@ public:
/// for compile_commands.json in all parent directories of each file.
ClangdLSPServer(JSONOutput &Out, const clangd::CodeCompleteOptions &CCOpts,
llvm::Optional<Path> CompileCommandsDir,
- const ClangdServer::Options &Opts);
+ bool ShouldUseInMemoryCDB, const ClangdServer::Options &Opts);
/// Run LSP server loop, receiving input for it from \p In. \p In must be
/// opened in binary mode. Output will be written using Out variable passed to
/// class constructor. This method must not be executed more than once for
/// each instance of ClangdLSPServer.
///
- /// \return Wether we received a 'shutdown' request before an 'exit' request
- bool run(std::istream &In,
+ /// \return Whether we received a 'shutdown' request before an 'exit' request.
+ bool run(std::FILE *In,
JSONStreamStyle InputStyle = JSONStreamStyle::Standard);
private:
@@ -62,6 +62,7 @@ private:
void
onDocumentRangeFormatting(DocumentRangeFormattingParams &Params) override;
void onDocumentFormatting(DocumentFormattingParams &Params) override;
+ void onDocumentSymbol(DocumentSymbolParams &Params) override;
void onCodeAction(CodeActionParams &Params) override;
void onCompletion(TextDocumentPositionParams &Params) override;
void onSignatureHelp(TextDocumentPositionParams &Params) override;
@@ -81,6 +82,7 @@ private:
/// may be very expensive. This method is normally called when the
/// compilation database is changed.
void reparseOpenedFiles();
+ void applyConfiguration(const ClangdConfigurationParamsChange &Settings);
JSONOutput &Out;
/// Used to indicate that the 'shutdown' request was received from the
@@ -98,9 +100,58 @@ private:
/// Caches FixIts per file and diagnostics
llvm::StringMap<DiagnosticToReplacementMap> FixItsMap;
+ /// Encapsulates the directory-based or the in-memory compilation database
+ /// that's used by the LSP server.
+ class CompilationDB {
+ public:
+ static CompilationDB makeInMemory();
+ static CompilationDB
+ makeDirectoryBased(llvm::Optional<Path> CompileCommandsDir);
+
+ void invalidate(PathRef File);
+
+ /// Sets the compilation command for a particular file.
+ /// Only valid for in-memory CDB, no-op and error log on DirectoryBasedCDB.
+ ///
+ /// \returns True if the File had no compilation command before.
+ bool
+ setCompilationCommandForFile(PathRef File,
+ tooling::CompileCommand CompilationCommand);
+
+ /// Adds extra compilation flags to the compilation command for a particular
+ /// file. Only valid for directory-based CDB, no-op and error log on
+ /// InMemoryCDB;
+ void setExtraFlagsForFile(PathRef File,
+ std::vector<std::string> ExtraFlags);
+
+ /// Set the compile commands directory to \p P.
+ /// Only valid for directory-based CDB, no-op and error log on InMemoryCDB;
+ void setCompileCommandsDir(Path P);
+
+ /// Returns a CDB that should be used to get compile commands for the
+ /// current instance of ClangdLSPServer.
+ GlobalCompilationDatabase &getCDB();
+
+ private:
+ CompilationDB(std::unique_ptr<GlobalCompilationDatabase> CDB,
+ std::unique_ptr<CachingCompilationDb> CachingCDB,
+ bool IsDirectoryBased)
+ : CDB(std::move(CDB)), CachingCDB(std::move(CachingCDB)),
+ IsDirectoryBased(IsDirectoryBased) {}
+
+ // if IsDirectoryBased is true, an instance of InMemoryCDB.
+ // If IsDirectoryBased is false, an instance of DirectoryBasedCDB.
+ // unique_ptr<GlobalCompilationDatabase> CDB;
+ std::unique_ptr<GlobalCompilationDatabase> CDB;
+ // Non-null only for directory-based CDB
+ std::unique_ptr<CachingCompilationDb> CachingCDB;
+ bool IsDirectoryBased;
+ };
+
// Various ClangdServer parameters go here. It's important they're created
// before ClangdServer.
- DirectoryBasedGlobalCompilationDatabase CDB;
+ CompilationDB CDB;
+
RealFileSystemProvider FSProvider;
/// Options used for code completion
clangd::CodeCompleteOptions CCOpts;
diff --git a/clangd/ClangdServer.cpp b/clangd/ClangdServer.cpp
index b2bcd5ae..01cc8239 100644
--- a/clangd/ClangdServer.cpp
+++ b/clangd/ClangdServer.cpp
@@ -23,6 +23,7 @@
#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
@@ -67,10 +68,6 @@ public:
} // namespace
-IntrusiveRefCntPtr<vfs::FileSystem> RealFileSystemProvider::getFileSystem() {
- return vfs::getRealFileSystem();
-}
-
ClangdServer::Options ClangdServer::optsForTest() {
ClangdServer::Options Opts;
Opts.UpdateDebounce = std::chrono::steady_clock::duration::zero(); // Faster!
@@ -83,10 +80,11 @@ ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB,
FileSystemProvider &FSProvider,
DiagnosticsConsumer &DiagConsumer,
const Options &Opts)
- : CompileArgs(CDB, Opts.ResourceDir ? Opts.ResourceDir->str()
- : getStandardResourceDir()),
- DiagConsumer(DiagConsumer), FSProvider(FSProvider),
- FileIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr),
+ : CDB(CDB), DiagConsumer(DiagConsumer), FSProvider(FSProvider),
+ ResourceDir(Opts.ResourceDir ? Opts.ResourceDir->str()
+ : getStandardResourceDir()),
+ FileIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex(Opts.URISchemes)
+ : nullptr),
PCHs(std::make_shared<PCHContainerOperations>()),
// Pass a callback into `WorkScheduler` to extract symbols from a newly
// parsed file and rebuild the file index synchronously each time an AST
@@ -113,20 +111,22 @@ ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB,
}
void ClangdServer::setRootPath(PathRef RootPath) {
- std::string NewRootPath = llvm::sys::path::convert_to_slash(
- RootPath, llvm::sys::path::Style::posix);
- if (llvm::sys::fs::is_directory(NewRootPath))
- this->RootPath = NewRootPath;
+ auto FS = FSProvider.getFileSystem();
+ auto Status = FS->status(RootPath);
+ if (!Status)
+ elog("Failed to get status for RootPath {0}: {1}", RootPath,
+ Status.getError().message());
+ else if (Status->isDirectory())
+ this->RootPath = RootPath;
+ else
+ elog("The provided RootPath {0} is not a directory.", RootPath);
}
void ClangdServer::addDocument(PathRef File, StringRef Contents,
- WantDiagnostics WantDiags, bool SkipCache) {
- if (SkipCache)
- CompileArgs.invalidate(File);
-
+ WantDiagnostics WantDiags) {
DocVersion Version = ++InternalVersion[File];
- ParseInputs Inputs = {CompileArgs.getCompileCommand(File),
- FSProvider.getFileSystem(), Contents.str()};
+ ParseInputs Inputs = {getCompileCommand(File), FSProvider.getFileSystem(),
+ Contents.str()};
Path FileStr = File.str();
WorkScheduler.update(File, std::move(Inputs), WantDiags,
@@ -137,13 +137,12 @@ void ClangdServer::addDocument(PathRef File, StringRef Contents,
void ClangdServer::removeDocument(PathRef File) {
++InternalVersion[File];
- CompileArgs.invalidate(File);
WorkScheduler.remove(File);
}
void ClangdServer::codeComplete(PathRef File, Position Pos,
const clangd::CodeCompleteOptions &Opts,
- Callback<CompletionList> CB) {
+ Callback<CodeCompleteResult> CB) {
// Copy completion options for passing them to async task handler.
auto CodeCompleteOpts = Opts;
if (!CodeCompleteOpts.Index) // Respect overridden index.
@@ -153,7 +152,7 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
std::shared_ptr<PCHContainerOperations> PCHs = this->PCHs;
auto FS = FSProvider.getFileSystem();
auto Task = [PCHs, Pos, FS,
- CodeCompleteOpts](Path File, Callback<CompletionList> CB,
+ CodeCompleteOpts](Path File, Callback<CodeCompleteResult> CB,
llvm::Expected<InputsAndPreamble> IP) {
if (!IP)
return CB(IP.takeError());
@@ -162,9 +161,9 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
// FIXME(ibiryukov): even if Preamble is non-null, we may want to check
// both the old and the new version in case only one of them matches.
- CompletionList Result = clangd::codeComplete(
+ CodeCompleteResult Result = clangd::codeComplete(
File, IP->Command, PreambleData ? &PreambleData->Preamble : nullptr,
- PreambleData ? PreambleData->Inclusions : std::vector<Inclusion>(),
+ PreambleData ? PreambleData->Includes : IncludeStructure(),
IP->Contents, Pos, FS, PCHs, CodeCompleteOpts);
CB(std::move(Result));
};
@@ -277,7 +276,7 @@ void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,
}
void ClangdServer::dumpAST(PathRef File,
- UniqueFunction<void(std::string)> Callback) {
+ llvm::unique_function<void(std::string)> Callback) {
auto Action = [](decltype(Callback) Callback,
llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST) {
@@ -298,12 +297,11 @@ void ClangdServer::dumpAST(PathRef File,
void ClangdServer::findDefinitions(PathRef File, Position Pos,
Callback<std::vector<Location>> CB) {
- auto FS = FSProvider.getFileSystem();
- auto Action = [Pos, FS, this](Callback<std::vector<Location>> CB,
- llvm::Expected<InputsAndAST> InpAST) {
+ auto Action = [Pos, this](Callback<std::vector<Location>> CB,
+ llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST)
return CB(InpAST.takeError());
- CB(clangd::findDefinitions(InpAST->AST, Pos, this->FileIdx.get()));
+ CB(clangd::findDefinitions(InpAST->AST, Pos, Index));
};
WorkScheduler.runWithAST("Definitions", File, Bind(Action, std::move(CB)));
@@ -373,7 +371,8 @@ ClangdServer::formatCode(llvm::StringRef Code, PathRef File,
ArrayRef<tooling::Range> Ranges) {
// Call clang-format.
auto FS = FSProvider.getFileSystem();
- auto Style = format::getStyle("file", File, "LLVM", Code, FS.get());
+ auto Style = format::getStyle(format::DefaultFormatStyle, File,
+ format::DefaultFallbackStyle, Code, FS.get());
if (!Style)
return Style.takeError();
@@ -391,10 +390,8 @@ ClangdServer::formatCode(llvm::StringRef Code, PathRef File,
void ClangdServer::findDocumentHighlights(
PathRef File, Position Pos, Callback<std::vector<DocumentHighlight>> CB) {
-
- auto FS = FSProvider.getFileSystem();
- auto Action = [FS, Pos](Callback<std::vector<DocumentHighlight>> CB,
- llvm::Expected<InputsAndAST> InpAST) {
+ auto Action = [Pos](Callback<std::vector<DocumentHighlight>> CB,
+ llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::findDocumentHighlights(InpAST->AST, Pos));
@@ -403,10 +400,10 @@ void ClangdServer::findDocumentHighlights(
WorkScheduler.runWithAST("Highlights", File, Bind(Action, std::move(CB)));
}
-void ClangdServer::findHover(PathRef File, Position Pos, Callback<Hover> CB) {
- auto FS = FSProvider.getFileSystem();
- auto Action = [Pos, FS](Callback<Hover> CB,
- llvm::Expected<InputsAndAST> InpAST) {
+void ClangdServer::findHover(PathRef File, Position Pos,
+ Callback<llvm::Optional<Hover>> CB) {
+ auto Action = [Pos](Callback<llvm::Optional<Hover>> CB,
+ llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::getHover(InpAST->AST, Pos));
@@ -433,6 +430,17 @@ void ClangdServer::consumeDiagnostics(PathRef File, DocVersion Version,
DiagConsumer.onDiagnosticsReady(File, std::move(Diags));
}
+tooling::CompileCommand ClangdServer::getCompileCommand(PathRef File) {
+ llvm::Optional<tooling::CompileCommand> C = CDB.getCompileCommand(File);
+ if (!C) // FIXME: Suppress diagnostics? Let the user know?
+ C = CDB.getFallbackCommand(File);
+
+ // Inject the resource dir.
+ // FIXME: Don't overwrite it if it's already there.
+ C->CommandLine.push_back("-resource-dir=" + ResourceDir);
+ return std::move(*C);
+}
+
void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
// FIXME: Do nothing for now. This will be used for indexing and potentially
// invalidating other caches.
@@ -440,7 +448,20 @@ void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
void ClangdServer::workspaceSymbols(
StringRef Query, int Limit, Callback<std::vector<SymbolInformation>> CB) {
- CB(clangd::getWorkspaceSymbols(Query, Limit, Index));
+ CB(clangd::getWorkspaceSymbols(Query, Limit, Index,
+ RootPath ? *RootPath : ""));
+}
+
+void ClangdServer::documentSymbols(
+ StringRef File, Callback<std::vector<SymbolInformation>> CB) {
+ auto Action = [](Callback<std::vector<SymbolInformation>> CB,
+ llvm::Expected<InputsAndAST> InpAST) {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ CB(clangd::getDocumentSymbols(InpAST->AST));
+ };
+ WorkScheduler.runWithAST("documentSymbols", File,
+ Bind(Action, std::move(CB)));
}
std::vector<std::pair<Path, std::size_t>>
diff --git a/clangd/ClangdServer.h b/clangd/ClangdServer.h
index 1e90e84a..745f8c30 100644
--- a/clangd/ClangdServer.h
+++ b/clangd/ClangdServer.h
@@ -12,7 +12,7 @@
#include "ClangdUnit.h"
#include "CodeComplete.h"
-#include "CompileArgsCache.h"
+#include "FSProvider.h"
#include "Function.h"
#include "GlobalCompilationDatabase.h"
#include "Protocol.h"
@@ -43,22 +43,6 @@ public:
std::vector<Diag> Diagnostics) = 0;
};
-class FileSystemProvider {
-public:
- virtual ~FileSystemProvider() = default;
- /// Called by ClangdServer to obtain a vfs::FileSystem to be used for parsing.
- /// Context::current() will be the context passed to the clang entrypoint,
- /// such as addDocument(), and will also be propagated to result callbacks.
- /// Embedders may use this to isolate filesystem accesses.
- virtual IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() = 0;
-};
-
-class RealFileSystemProvider : public FileSystemProvider {
-public:
- /// Returns getRealFileSystem().
- IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override;
-};
-
/// Provides API to manage ASTs for a collection of C++ files and request
/// various language features.
/// Currently supports async diagnostics, code completion, formatting and goto
@@ -80,6 +64,10 @@ public:
/// opened files and uses the index to augment code completion results.
bool BuildDynamicSymbolIndex = false;
+ /// URI schemes to use when building the dynamic index.
+ /// If empty, the default schemes in SymbolCollector will be used.
+ std::vector<std::string> URISchemes;
+
/// If set, use this index to augment code completion results.
SymbolIndex *StaticIndex = nullptr;
@@ -119,11 +107,8 @@ public:
/// \p File is already tracked. Also schedules parsing of the AST for it on a
/// separate thread. When the parsing is complete, DiagConsumer passed in
/// constructor will receive onDiagnosticsReady callback.
- /// When \p SkipCache is true, compile commands will always be requested from
- /// compilation database even if they were cached in previous invocations.
void addDocument(PathRef File, StringRef Contents,
- WantDiagnostics WD = WantDiagnostics::Auto,
- bool SkipCache = false);
+ WantDiagnostics WD = WantDiagnostics::Auto);
/// Remove \p File from list of tracked files, schedule a request to free
/// resources associated with it.
@@ -139,7 +124,7 @@ public:
/// when codeComplete results become available.
void codeComplete(PathRef File, Position Pos,
const clangd::CodeCompleteOptions &Opts,
- Callback<CompletionList> CB);
+ Callback<CodeCompleteResult> CB);
/// Provide signature help for \p File at \p Pos. This method should only be
/// called for tracked files.
@@ -158,12 +143,17 @@ public:
Callback<std::vector<DocumentHighlight>> CB);
/// Get code hover for a given position.
- void findHover(PathRef File, Position Pos, Callback<Hover> CB);
+ void findHover(PathRef File, Position Pos,
+ Callback<llvm::Optional<Hover>> CB);
/// Retrieve the top symbols from the workspace matching a query.
void workspaceSymbols(StringRef Query, int Limit,
Callback<std::vector<SymbolInformation>> CB);
+ /// Retrieve the symbols within the specified file.
+ void documentSymbols(StringRef File,
+ Callback<std::vector<SymbolInformation>> CB);
+
/// Run formatting for \p Rng inside \p File with content \p Code.
llvm::Expected<tooling::Replacements> formatRange(StringRef Code,
PathRef File, Range Rng);
@@ -185,7 +175,7 @@ public:
/// Only for testing purposes.
/// Waits until all requests to worker thread are finished and dumps AST for
/// \p File. \p File must be in the list of added documents.
- void dumpAST(PathRef File, UniqueFunction<void(std::string)> Callback);
+ void dumpAST(PathRef File, llvm::unique_function<void(std::string)> Callback);
/// Called when an event occurs for a watched file in the workspace.
void onFileEvent(const DidChangeWatchedFilesParams &Params);
@@ -215,13 +205,16 @@ private:
void consumeDiagnostics(PathRef File, DocVersion Version,
std::vector<Diag> Diags);
- CompileArgsCache CompileArgs;
+ tooling::CompileCommand getCompileCommand(PathRef File);
+
+ GlobalCompilationDatabase &CDB;
DiagnosticsConsumer &DiagConsumer;
FileSystemProvider &FSProvider;
/// Used to synchronize diagnostic responses for added and removed files.
llvm::StringMap<DocVersion> InternalVersion;
+ Path ResourceDir;
// The index used to look up symbols. This could be:
// - null (all index functionality is optional)
// - the dynamic index owned by ClangdServer (FileIdx)
diff --git a/clangd/ClangdUnit.cpp b/clangd/ClangdUnit.cpp
index bf5de6ac..86e7497a 100644
--- a/clangd/ClangdUnit.cpp
+++ b/clangd/ClangdUnit.cpp
@@ -24,6 +24,7 @@
#include "clang/Lex/Lexer.h"
#include "clang/Lex/MacroInfo.h"
#include "clang/Lex/Preprocessor.h"
+#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Sema/Sema.h"
#include "clang/Serialization/ASTWriter.h"
#include "clang/Tooling/CompilationDatabase.h"
@@ -51,11 +52,11 @@ template <class T> std::size_t getUsedBytes(const std::vector<T> &Vec) {
class DeclTrackingASTConsumer : public ASTConsumer {
public:
- DeclTrackingASTConsumer(std::vector<const Decl *> &TopLevelDecls)
+ DeclTrackingASTConsumer(std::vector<Decl *> &TopLevelDecls)
: TopLevelDecls(TopLevelDecls) {}
bool HandleTopLevelDecl(DeclGroupRef DG) override {
- for (const Decl *D : DG) {
+ for (Decl *D : DG) {
// ObjCMethodDecl are not actually top-level decls.
if (isa<ObjCMethodDecl>(D))
continue;
@@ -66,14 +67,12 @@ public:
}
private:
- std::vector<const Decl *> &TopLevelDecls;
+ std::vector<Decl *> &TopLevelDecls;
};
class ClangdFrontendAction : public SyntaxOnlyAction {
public:
- std::vector<const Decl *> takeTopLevelDecls() {
- return std::move(TopLevelDecls);
- }
+ std::vector<Decl *> takeTopLevelDecls() { return std::move(TopLevelDecls); }
protected:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
@@ -82,7 +81,7 @@ protected:
}
private:
- std::vector<const Decl *> TopLevelDecls;
+ std::vector<Decl *> TopLevelDecls;
};
class CppFilePreambleCallbacks : public PreambleCallbacks {
@@ -90,7 +89,7 @@ public:
CppFilePreambleCallbacks(PathRef File, PreambleParsedCallback ParsedCallback)
: File(File), ParsedCallback(ParsedCallback) {}
- std::vector<Inclusion> takeInclusions() { return std::move(Inclusions); }
+ IncludeStructure takeIncludes() { return std::move(Includes); }
void AfterExecute(CompilerInstance &CI) override {
if (!ParsedCallback)
@@ -105,15 +104,13 @@ public:
std::unique_ptr<PPCallbacks> createPPCallbacks() override {
assert(SourceMgr && "SourceMgr must be set at this point");
- return collectInclusionsInMainFileCallback(
- *SourceMgr,
- [this](Inclusion Inc) { Inclusions.push_back(std::move(Inc)); });
+ return collectIncludeStructureCallback(*SourceMgr, &Includes);
}
private:
PathRef File;
PreambleParsedCallback ParsedCallback;
- std::vector<Inclusion> Inclusions;
+ IncludeStructure Includes;
SourceManager *SourceMgr = nullptr;
};
@@ -124,7 +121,7 @@ void clangd::dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) {
}
llvm::Optional<ParsedAST>
-ParsedAST::Build(std::unique_ptr<clang::CompilerInvocation> CI,
+ParsedAST::build(std::unique_ptr<clang::CompilerInvocation> CI,
std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<llvm::MemoryBuffer> Buffer,
std::shared_ptr<PCHContainerOperations> PCHs,
@@ -150,23 +147,19 @@ ParsedAST::Build(std::unique_ptr<clang::CompilerInvocation> CI,
auto Action = llvm::make_unique<ClangdFrontendAction>();
const FrontendInputFile &MainInput = Clang->getFrontendOpts().Inputs[0];
if (!Action->BeginSourceFile(*Clang, MainInput)) {
- log("BeginSourceFile() failed when building AST for " +
+ log("BeginSourceFile() failed when building AST for {0}",
MainInput.getFile());
return llvm::None;
}
- std::vector<Inclusion> Inclusions;
// Copy over the includes from the preamble, then combine with the
// non-preamble includes below.
- if (Preamble)
- Inclusions = Preamble->Inclusions;
-
- Clang->getPreprocessor().addPPCallbacks(collectInclusionsInMainFileCallback(
- Clang->getSourceManager(),
- [&Inclusions](Inclusion Inc) { Inclusions.push_back(std::move(Inc)); }));
+ auto Includes = Preamble ? Preamble->Includes : IncludeStructure{};
+ Clang->getPreprocessor().addPPCallbacks(
+ collectIncludeStructureCallback(Clang->getSourceManager(), &Includes));
if (!Action->Execute())
- log("Execute() failed when building AST for " + MainInput.getFile());
+ log("Execute() failed when building AST for {0}", MainInput.getFile());
// UnitDiagsConsumer is local, we can not store it in CompilerInstance that
// has a longer lifetime.
@@ -174,14 +167,14 @@ ParsedAST::Build(std::unique_ptr<clang::CompilerInvocation> CI,
// CompilerInstance won't run this callback, do it directly.
ASTDiags.EndSourceFile();
- std::vector<const Decl *> ParsedDecls = Action->takeTopLevelDecls();
+ std::vector<Decl *> ParsedDecls = Action->takeTopLevelDecls();
std::vector<Diag> Diags = ASTDiags.take();
// Add diagnostics from the preamble, if any.
if (Preamble)
Diags.insert(Diags.begin(), Preamble->Diags.begin(), Preamble->Diags.end());
return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action),
std::move(ParsedDecls), std::move(Diags),
- std::move(Inclusions));
+ std::move(Includes));
}
ParsedAST::ParsedAST(ParsedAST &&Other) = default;
@@ -210,7 +203,7 @@ const Preprocessor &ParsedAST::getPreprocessor() const {
return Clang->getPreprocessor();
}
-ArrayRef<const Decl *> ParsedAST::getLocalTopLevelDecls() {
+ArrayRef<Decl *> ParsedAST::getLocalTopLevelDecls() {
return LocalTopLevelDecls;
}
@@ -248,25 +241,24 @@ std::size_t ParsedAST::getUsedBytes() const {
return Total;
}
-const std::vector<Inclusion> &ParsedAST::getInclusions() const {
- return Inclusions;
+const IncludeStructure &ParsedAST::getIncludeStructure() const {
+ return Includes;
}
PreambleData::PreambleData(PrecompiledPreamble Preamble,
- std::vector<Diag> Diags,
- std::vector<Inclusion> Inclusions)
+ std::vector<Diag> Diags, IncludeStructure Includes)
: Preamble(std::move(Preamble)), Diags(std::move(Diags)),
- Inclusions(std::move(Inclusions)) {}
+ Includes(std::move(Includes)) {}
ParsedAST::ParsedAST(std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<CompilerInstance> Clang,
std::unique_ptr<FrontendAction> Action,
- std::vector<const Decl *> LocalTopLevelDecls,
- std::vector<Diag> Diags, std::vector<Inclusion> Inclusions)
+ std::vector<Decl *> LocalTopLevelDecls,
+ std::vector<Diag> Diags, IncludeStructure Includes)
: Preamble(std::move(Preamble)), Clang(std::move(Clang)),
Action(std::move(Action)), Diags(std::move(Diags)),
LocalTopLevelDecls(std::move(LocalTopLevelDecls)),
- Inclusions(std::move(Inclusions)) {
+ Includes(std::move(Includes)) {
assert(this->Clang);
assert(this->Action);
}
@@ -315,11 +307,11 @@ std::shared_ptr<const PreambleData> clangd::buildPreamble(
compileCommandsAreEqual(Inputs.CompileCommand, OldCompileCommand) &&
OldPreamble->Preamble.CanReuse(CI, ContentsBuffer.get(), Bounds,
Inputs.FS.get())) {
- log("Reusing preamble for file " + Twine(FileName));
+ vlog("Reusing preamble for file {0}", Twine(FileName));
return OldPreamble;
}
- log("Preamble for file " + Twine(FileName) +
- " cannot be reused. Attempting to rebuild it.");
+ vlog("Preamble for file {0} cannot be reused. Attempting to rebuild it.",
+ FileName);
trace::Span Tracer("BuildPreamble");
SPAN_ATTACH(Tracer, "File", FileName);
@@ -332,6 +324,9 @@ std::shared_ptr<const PreambleData> clangd::buildPreamble(
// the preamble and make it smaller.
assert(!CI.getFrontendOpts().SkipFunctionBodies);
CI.getFrontendOpts().SkipFunctionBodies = true;
+ // We don't want to write comment locations into PCH. They are racy and slow
+ // to read back. We rely on dynamic index for the comments instead.
+ CI.getPreprocessorOpts().WriteCommentListToPCH = false;
CppFilePreambleCallbacks SerializedDeclsCollector(FileName, PreambleCallback);
if (Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) {
@@ -348,13 +343,13 @@ std::shared_ptr<const PreambleData> clangd::buildPreamble(
CI.getFrontendOpts().SkipFunctionBodies = false;
if (BuiltPreamble) {
- log("Built preamble of size " + Twine(BuiltPreamble->getSize()) +
- " for file " + Twine(FileName));
+ vlog("Built preamble of size {0} for file {1}", BuiltPreamble->getSize(),
+ FileName);
return std::make_shared<PreambleData>(
std::move(*BuiltPreamble), PreambleDiagnostics.take(),
- SerializedDeclsCollector.takeInclusions());
+ SerializedDeclsCollector.takeIncludes());
} else {
- log("Could not build a preamble for file " + Twine(FileName));
+ elog("Could not build a preamble for file {0}", FileName);
return nullptr;
}
}
@@ -372,7 +367,7 @@ llvm::Optional<ParsedAST> clangd::buildAST(
// dirs.
}
- return ParsedAST::Build(
+ return ParsedAST::build(
llvm::make_unique<CompilerInvocation>(*Invocation), Preamble,
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents), PCHs, Inputs.FS);
}
@@ -384,7 +379,7 @@ SourceLocation clangd::getBeginningOfIdentifier(ParsedAST &Unit,
const SourceManager &SourceMgr = AST.getSourceManager();
auto Offset = positionToOffset(SourceMgr.getBufferData(FID), Pos);
if (!Offset) {
- log("getBeginningOfIdentifier: " + toString(Offset.takeError()));
+ log("getBeginningOfIdentifier: {0}", Offset.takeError());
return SourceLocation();
}
SourceLocation InputLoc = SourceMgr.getComposedLoc(FID, *Offset);
diff --git a/clangd/ClangdUnit.h b/clangd/ClangdUnit.h
index 8d9c3f04..c7aca17e 100644
--- a/clangd/ClangdUnit.h
+++ b/clangd/ClangdUnit.h
@@ -45,14 +45,14 @@ namespace clangd {
// Stores Preamble and associated data.
struct PreambleData {
PreambleData(PrecompiledPreamble Preamble, std::vector<Diag> Diags,
- std::vector<Inclusion> Inclusions);
+ IncludeStructure Includes);
tooling::CompileCommand CompileCommand;
PrecompiledPreamble Preamble;
std::vector<Diag> Diags;
// Processes like code completions and go-to-definitions will need #include
// information, and their compile action skips preamble range.
- std::vector<Inclusion> Inclusions;
+ IncludeStructure Includes;
};
/// Information required to run clang, e.g. to parse AST or do code completion.
@@ -68,7 +68,7 @@ public:
/// Attempts to run Clang and store parsed AST. If \p Preamble is non-null
/// it is reused during parsing.
static llvm::Optional<ParsedAST>
- Build(std::unique_ptr<clang::CompilerInvocation> CI,
+ build(std::unique_ptr<clang::CompilerInvocation> CI,
std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<llvm::MemoryBuffer> Buffer,
std::shared_ptr<PCHContainerOperations> PCHs,
@@ -91,21 +91,22 @@ public:
/// This function returns top-level decls present in the main file of the AST.
/// The result does not include the decls that come from the preamble.
- ArrayRef<const Decl *> getLocalTopLevelDecls();
+ /// (These should be const, but RecursiveASTVisitor requires Decl*).
+ ArrayRef<Decl *> getLocalTopLevelDecls();
const std::vector<Diag> &getDiagnostics() const;
/// Returns the esitmated size of the AST and the accessory structures, in
/// bytes. Does not include the size of the preamble.
std::size_t getUsedBytes() const;
- const std::vector<Inclusion> &getInclusions() const;
+ const IncludeStructure &getIncludeStructure() const;
private:
ParsedAST(std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<CompilerInstance> Clang,
std::unique_ptr<FrontendAction> Action,
- std::vector<const Decl *> LocalTopLevelDecls,
- std::vector<Diag> Diags, std::vector<Inclusion> Inclusions);
+ std::vector<Decl *> LocalTopLevelDecls, std::vector<Diag> Diags,
+ IncludeStructure Includes);
// In-memory preambles must outlive the AST, it is important that this member
// goes before Clang and Action.
@@ -122,8 +123,8 @@ private:
std::vector<Diag> Diags;
// Top-level decls inside the current file. Not that this does not include
// top-level decls from the preamble.
- std::vector<const Decl *> LocalTopLevelDecls;
- std::vector<Inclusion> Inclusions;
+ std::vector<Decl *> LocalTopLevelDecls;
+ IncludeStructure Includes;
};
using PreambleParsedCallback = std::function<void(
diff --git a/clangd/CodeComplete.cpp b/clangd/CodeComplete.cpp
index 40d90580..90ebccb1 100644
--- a/clangd/CodeComplete.cpp
+++ b/clangd/CodeComplete.cpp
@@ -7,16 +7,23 @@
//
//===---------------------------------------------------------------------===//
//
-// AST-based completions are provided using the completion hooks in Sema.
+// Code completion has several moving parts:
+// - AST-based completions are provided using the completion hooks in Sema.
+// - external completions are retrieved from the index (using hints from Sema)
+// - the two sources overlap, and must be merged and overloads bundled
+// - results must be scored and ranked (see Quality.h) before rendering
//
-// Signature help works in a similar way as code completion, but it is simpler
-// as there are typically fewer candidates.
+// Signature help works in a similar way as code completion, but it is simpler:
+// it's purely AST-based, and there are few candidates.
//
//===---------------------------------------------------------------------===//
#include "CodeComplete.h"
+#include "AST.h"
#include "CodeCompletionStrings.h"
#include "Compiler.h"
+#include "Diagnostics.h"
+#include "FileDistance.h"
#include "FuzzyMatch.h"
#include "Headers.h"
#include "Logger.h"
@@ -25,6 +32,7 @@
#include "Trace.h"
#include "URI.h"
#include "index/Index.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/CompilerInstance.h"
@@ -34,78 +42,17 @@
#include "clang/Sema/Sema.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/Support/Format.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/ScopedPrinter.h"
#include <queue>
// We log detailed candidate here if you run with -debug-only=codecomplete.
-#define DEBUG_TYPE "codecomplete"
+#define DEBUG_TYPE "CodeComplete"
namespace clang {
namespace clangd {
namespace {
-CompletionItemKind toCompletionItemKind(CXCursorKind CursorKind) {
- switch (CursorKind) {
- case CXCursor_MacroInstantiation:
- case CXCursor_MacroDefinition:
- return CompletionItemKind::Text;
- case CXCursor_CXXMethod:
- case CXCursor_Destructor:
- return CompletionItemKind::Method;
- case CXCursor_FunctionDecl:
- case CXCursor_FunctionTemplate:
- return CompletionItemKind::Function;
- case CXCursor_Constructor:
- return CompletionItemKind::Constructor;
- case CXCursor_FieldDecl:
- return CompletionItemKind::Field;
- case CXCursor_VarDecl:
- case CXCursor_ParmDecl:
- return CompletionItemKind::Variable;
- // FIXME(ioeric): use LSP struct instead of class when it is suppoted in the
- // protocol.
- case CXCursor_StructDecl:
- case CXCursor_ClassDecl:
- case CXCursor_UnionDecl:
- case CXCursor_ClassTemplate:
- case CXCursor_ClassTemplatePartialSpecialization:
- return CompletionItemKind::Class;
- case CXCursor_Namespace:
- case CXCursor_NamespaceAlias:
- case CXCursor_NamespaceRef:
- return CompletionItemKind::Module;
- case CXCursor_EnumConstantDecl:
- return CompletionItemKind::Value;
- case CXCursor_EnumDecl:
- return CompletionItemKind::Enum;
- // FIXME(ioeric): figure out whether reference is the right type for aliases.
- case CXCursor_TypeAliasDecl:
- case CXCursor_TypeAliasTemplateDecl:
- case CXCursor_TypedefDecl:
- case CXCursor_MemberRef:
- case CXCursor_TypeRef:
- return CompletionItemKind::Reference;
- default:
- return CompletionItemKind::Missing;
- }
-}
-
-CompletionItemKind
-toCompletionItemKind(CodeCompletionResult::ResultKind ResKind,
- CXCursorKind CursorKind) {
- switch (ResKind) {
- case CodeCompletionResult::RK_Declaration:
- return toCompletionItemKind(CursorKind);
- case CodeCompletionResult::RK_Keyword:
- return CompletionItemKind::Keyword;
- case CodeCompletionResult::RK_Macro:
- return CompletionItemKind::Text; // unfortunately, there's no 'Macro'
- // completion items in LSP.
- case CodeCompletionResult::RK_Pattern:
- return CompletionItemKind::Snippet;
- }
- llvm_unreachable("Unhandled CodeCompletionResult::ResultKind.");
-}
-
CompletionItemKind toCompletionItemKind(index::SymbolKind Kind) {
using SK = index::SymbolKind;
switch (Kind) {
@@ -159,6 +106,25 @@ CompletionItemKind toCompletionItemKind(index::SymbolKind Kind) {
llvm_unreachable("Unhandled clang::index::SymbolKind.");
}
+CompletionItemKind
+toCompletionItemKind(CodeCompletionResult::ResultKind ResKind,
+ const NamedDecl *Decl) {
+ if (Decl)
+ return toCompletionItemKind(index::getSymbolInfo(Decl).Kind);
+ switch (ResKind) {
+ case CodeCompletionResult::RK_Declaration:
+ llvm_unreachable("RK_Declaration without Decl");
+ case CodeCompletionResult::RK_Keyword:
+ return CompletionItemKind::Keyword;
+ case CodeCompletionResult::RK_Macro:
+ return CompletionItemKind::Text; // unfortunately, there's no 'Macro'
+ // completion items in LSP.
+ case CodeCompletionResult::RK_Pattern:
+ return CompletionItemKind::Snippet;
+ }
+ llvm_unreachable("Unhandled CodeCompletionResult::ResultKind.");
+}
+
/// Get the optional chunk as a string. This function is possibly recursive.
///
/// The parameter info for each parameter is appended to the Parameters.
@@ -227,101 +193,215 @@ struct CompletionCandidate {
const CodeCompletionResult *SemaResult = nullptr;
const Symbol *IndexResult = nullptr;
- // Builds an LSP completion item.
- CompletionItem build(StringRef FileName, const CompletionItemScores &Scores,
- const CodeCompleteOptions &Opts,
- CodeCompletionString *SemaCCS,
- const IncludeInserter *Includes,
- llvm::StringRef SemaDocComment) const {
- assert(bool(SemaResult) == bool(SemaCCS));
- CompletionItem I;
- bool ShouldInsertInclude = true;
- if (SemaResult) {
- I.kind = toCompletionItemKind(SemaResult->Kind, SemaResult->CursorKind);
- getLabelAndInsertText(*SemaCCS, &I.label, &I.insertText,
- Opts.EnableSnippets);
- I.filterText = getFilterText(*SemaCCS);
- I.documentation = formatDocumentation(*SemaCCS, SemaDocComment);
- I.detail = getDetail(*SemaCCS);
+ // Returns a token identifying the overload set this is part of.
+ // 0 indicates it's not part of any overload set.
+ size_t overloadSet() const {
+ SmallString<256> Scratch;
+ if (IndexResult) {
+ switch (IndexResult->SymInfo.Kind) {
+ case index::SymbolKind::ClassMethod:
+ case index::SymbolKind::InstanceMethod:
+ case index::SymbolKind::StaticMethod:
+ assert(false && "Don't expect members from index in code completion");
+ // fall through
+ case index::SymbolKind::Function:
+ // We can't group overloads together that need different #includes.
+ // This could break #include insertion.
+ return hash_combine(
+ (IndexResult->Scope + IndexResult->Name).toStringRef(Scratch),
+ headerToInsertIfNotPresent().getValueOr(""));
+ default:
+ return 0;
+ }
+ }
+ assert(SemaResult);
+ // We need to make sure we're consistent with the IndexResult case!
+ const NamedDecl *D = SemaResult->Declaration;
+ if (!D || !D->isFunctionOrFunctionTemplate())
+ return 0;
+ {
+ llvm::raw_svector_ostream OS(Scratch);
+ D->printQualifiedName(OS);
+ }
+ return hash_combine(Scratch, headerToInsertIfNotPresent().getValueOr(""));
+ }
+
+ llvm::Optional<llvm::StringRef> headerToInsertIfNotPresent() const {
+ if (!IndexResult || !IndexResult->Detail ||
+ IndexResult->Detail->IncludeHeader.empty())
+ return llvm::None;
+ if (SemaResult && SemaResult->Declaration) {
// Avoid inserting new #include if the declaration is found in the current
// file e.g. the symbol is forward declared.
- if (SemaResult->Kind == CodeCompletionResult::RK_Declaration) {
- if (const auto *D = SemaResult->getDeclaration()) {
- const auto &SM = D->getASTContext().getSourceManager();
- ShouldInsertInclude =
- ShouldInsertInclude &&
- std::none_of(D->redecls_begin(), D->redecls_end(),
- [&SM](const Decl *RD) {
- return SM.isInMainFile(
- SM.getExpansionLoc(RD->getLocStart()));
- });
- }
- }
+ auto &SM = SemaResult->Declaration->getASTContext().getSourceManager();
+ for (const Decl *RD : SemaResult->Declaration->redecls())
+ if (SM.isInMainFile(SM.getExpansionLoc(RD->getBeginLoc())))
+ return llvm::None;
}
- if (IndexResult) {
- if (I.kind == CompletionItemKind::Missing)
- I.kind = toCompletionItemKind(IndexResult->SymInfo.Kind);
- // FIXME: reintroduce a way to show the index source for debugging.
- if (I.label.empty())
- I.label = IndexResult->CompletionLabel;
- if (I.filterText.empty())
- I.filterText = IndexResult->Name;
-
- // FIXME(ioeric): support inserting/replacing scope qualifiers.
- if (I.insertText.empty())
- I.insertText = Opts.EnableSnippets
- ? IndexResult->CompletionSnippetInsertText
- : IndexResult->CompletionPlainInsertText;
-
- if (auto *D = IndexResult->Detail) {
- if (I.documentation.empty())
- I.documentation = D->Documentation;
- if (I.detail.empty())
- I.detail = D->CompletionDetail;
- if (ShouldInsertInclude && Includes && !D->IncludeHeader.empty()) {
- auto Edit = [&]() -> Expected<Optional<TextEdit>> {
- auto ResolvedDeclaring = toHeaderFile(
- IndexResult->CanonicalDeclaration.FileURI, FileName);
- if (!ResolvedDeclaring)
- return ResolvedDeclaring.takeError();
- auto ResolvedInserted = toHeaderFile(D->IncludeHeader, FileName);
- if (!ResolvedInserted)
- return ResolvedInserted.takeError();
- return Includes->insert(*ResolvedDeclaring, *ResolvedInserted);
- }();
- if (!Edit) {
- std::string ErrMsg =
- ("Failed to generate include insertion edits for adding header "
- "(FileURI=\"" +
- IndexResult->CanonicalDeclaration.FileURI +
- "\", IncludeHeader=\"" + D->IncludeHeader + "\") into " +
- FileName)
- .str();
- log(ErrMsg + ":" + llvm::toString(Edit.takeError()));
- } else if (*Edit) {
- I.additionalTextEdits = {std::move(**Edit)};
- }
- }
+ return IndexResult->Detail->IncludeHeader;
+ }
+
+ using Bundle = llvm::SmallVector<CompletionCandidate, 4>;
+};
+using ScoredBundle =
+ std::pair<CompletionCandidate::Bundle, CodeCompletion::Scores>;
+struct ScoredBundleGreater {
+ bool operator()(const ScoredBundle &L, const ScoredBundle &R) {
+ if (L.second.Total != R.second.Total)
+ return L.second.Total > R.second.Total;
+ return L.first.front().Name <
+ R.first.front().Name; // Earlier name is better.
+ }
+};
+
+// Assembles a code completion out of a bundle of >=1 completion candidates.
+// Many of the expensive strings are only computed at this point, once we know
+// the candidate bundle is going to be returned.
+//
+// Many fields are the same for all candidates in a bundle (e.g. name), and are
+// computed from the first candidate, in the constructor.
+// Others vary per candidate, so add() must be called for remaining candidates.
+struct CodeCompletionBuilder {
+ CodeCompletionBuilder(ASTContext &ASTCtx, const CompletionCandidate &C,
+ CodeCompletionString *SemaCCS,
+ const IncludeInserter &Includes, StringRef FileName,
+ const CodeCompleteOptions &Opts)
+ : ASTCtx(ASTCtx), ExtractDocumentation(Opts.IncludeComments) {
+ add(C, SemaCCS);
+ if (C.SemaResult) {
+ Completion.Origin |= SymbolOrigin::AST;
+ Completion.Name = llvm::StringRef(SemaCCS->getTypedText());
+ if (Completion.Scope.empty()) {
+ if ((C.SemaResult->Kind == CodeCompletionResult::RK_Declaration) ||
+ (C.SemaResult->Kind == CodeCompletionResult::RK_Pattern))
+ if (const auto *D = C.SemaResult->getDeclaration())
+ if (const auto *ND = llvm::dyn_cast<NamedDecl>(D))
+ Completion.Scope =
+ splitQualifiedName(printQualifiedName(*ND)).first;
+ }
+ Completion.Kind =
+ toCompletionItemKind(C.SemaResult->Kind, C.SemaResult->Declaration);
+ for (const auto &FixIt : C.SemaResult->FixIts) {
+ Completion.FixIts.push_back(
+ toTextEdit(FixIt, ASTCtx.getSourceManager(), ASTCtx.getLangOpts()));
}
}
- I.scoreInfo = Scores;
- I.sortText = sortText(Scores.finalScore, Name);
- I.insertTextFormat = Opts.EnableSnippets ? InsertTextFormat::Snippet
- : InsertTextFormat::PlainText;
- return I;
+ if (C.IndexResult) {
+ Completion.Origin |= C.IndexResult->Origin;
+ if (Completion.Scope.empty())
+ Completion.Scope = C.IndexResult->Scope;
+ if (Completion.Kind == CompletionItemKind::Missing)
+ Completion.Kind = toCompletionItemKind(C.IndexResult->SymInfo.Kind);
+ if (Completion.Name.empty())
+ Completion.Name = C.IndexResult->Name;
+ }
+ if (auto Inserted = C.headerToInsertIfNotPresent()) {
+ // Turn absolute path into a literal string that can be #included.
+ auto Include = [&]() -> Expected<std::pair<std::string, bool>> {
+ auto ResolvedDeclaring =
+ toHeaderFile(C.IndexResult->CanonicalDeclaration.FileURI, FileName);
+ if (!ResolvedDeclaring)
+ return ResolvedDeclaring.takeError();
+ auto ResolvedInserted = toHeaderFile(*Inserted, FileName);
+ if (!ResolvedInserted)
+ return ResolvedInserted.takeError();
+ return std::make_pair(Includes.calculateIncludePath(*ResolvedDeclaring,
+ *ResolvedInserted),
+ Includes.shouldInsertInclude(*ResolvedDeclaring,
+ *ResolvedInserted));
+ }();
+ if (Include) {
+ Completion.Header = Include->first;
+ if (Include->second)
+ Completion.HeaderInsertion = Includes.insert(Include->first);
+ } else
+ log("Failed to generate include insertion edits for adding header "
+ "(FileURI='{0}', IncludeHeader='{1}') into {2}",
+ C.IndexResult->CanonicalDeclaration.FileURI,
+ C.IndexResult->Detail->IncludeHeader, FileName);
+ }
+ }
+
+ void add(const CompletionCandidate &C, CodeCompletionString *SemaCCS) {
+ assert(bool(C.SemaResult) == bool(SemaCCS));
+ Bundled.emplace_back();
+ BundledEntry &S = Bundled.back();
+ if (C.SemaResult) {
+ getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix,
+ &Completion.RequiredQualifier);
+ S.ReturnType = getReturnType(*SemaCCS);
+ } else if (C.IndexResult) {
+ S.Signature = C.IndexResult->Signature;
+ S.SnippetSuffix = C.IndexResult->CompletionSnippetSuffix;
+ if (auto *D = C.IndexResult->Detail)
+ S.ReturnType = D->ReturnType;
+ }
+ if (ExtractDocumentation && Completion.Documentation.empty()) {
+ if (C.IndexResult && C.IndexResult->Detail)
+ Completion.Documentation = C.IndexResult->Detail->Documentation;
+ else if (C.SemaResult)
+ Completion.Documentation = getDocComment(ASTCtx, *C.SemaResult,
+ /*CommentsFromHeader=*/false);
+ }
}
+
+ CodeCompletion build() {
+ Completion.ReturnType = summarizeReturnType();
+ Completion.Signature = summarizeSignature();
+ Completion.SnippetSuffix = summarizeSnippet();
+ Completion.BundleSize = Bundled.size();
+ return std::move(Completion);
+ }
+
+private:
+ struct BundledEntry {
+ std::string SnippetSuffix;
+ std::string Signature;
+ std::string ReturnType;
+ };
+
+ // If all BundledEntrys have the same value for a property, return it.
+ template <std::string BundledEntry::*Member>
+ const std::string *onlyValue() const {
+ auto B = Bundled.begin(), E = Bundled.end();
+ for (auto I = B + 1; I != E; ++I)
+ if (I->*Member != B->*Member)
+ return nullptr;
+ return &(B->*Member);
+ }
+
+ std::string summarizeReturnType() const {
+ if (auto *RT = onlyValue<&BundledEntry::ReturnType>())
+ return *RT;
+ return "";
+ }
+
+ std::string summarizeSnippet() const {
+ if (auto *Snippet = onlyValue<&BundledEntry::SnippetSuffix>())
+ return *Snippet;
+ // All bundles are function calls.
+ return "(${0})";
+ }
+
+ std::string summarizeSignature() const {
+ if (auto *Signature = onlyValue<&BundledEntry::Signature>())
+ return *Signature;
+ // All bundles are function calls.
+ return "(…)";
+ }
+
+ ASTContext &ASTCtx;
+ CodeCompletion Completion;
+ SmallVector<BundledEntry, 1> Bundled;
+ bool ExtractDocumentation;
};
-using ScoredCandidate = std::pair<CompletionCandidate, CompletionItemScores>;
// Determine the symbol ID for a Sema code completion result, if possible.
llvm::Optional<SymbolID> getSymbolID(const CodeCompletionResult &R) {
switch (R.Kind) {
case CodeCompletionResult::RK_Declaration:
case CodeCompletionResult::RK_Pattern: {
- llvm::SmallString<128> USR;
- if (/*Ignore=*/clang::index::generateUSRForDecl(R.Declaration, USR))
- return None;
- return SymbolID(USR);
+ return clang::clangd::getSymbolID(R.Declaration);
}
case CodeCompletionResult::RK_Macro:
// FIXME: Macros do have USRs, but the CCR doesn't contain enough info.
@@ -466,6 +546,25 @@ bool contextAllowsIndex(enum CodeCompletionContext::Kind K) {
llvm_unreachable("unknown code completion context");
}
+// Some member calls are blacklisted because they're so rarely useful.
+static bool isBlacklistedMember(const NamedDecl &D) {
+ // Destructor completion is rarely useful, and works inconsistently.
+ // (s.^ completes ~string, but s.~st^ is an error).
+ if (D.getKind() == Decl::CXXDestructor)
+ return true;
+ // Injected name may be useful for A::foo(), but who writes A::A::foo()?
+ if (auto *R = dyn_cast_or_null<RecordDecl>(&D))
+ if (R->isInjectedClassName())
+ return true;
+ // Explicit calls to operators are also rare.
+ auto NameKind = D.getDeclName().getNameKind();
+ if (NameKind == DeclarationName::CXXOperatorName ||
+ NameKind == DeclarationName::CXXLiteralOperatorName ||
+ NameKind == DeclarationName::CXXConversionFunctionName)
+ return true;
+ return false;
+}
+
// The CompletionRecorder captures Sema code-complete output, including context.
// It filters out ignored results (but doesn't apply fuzzy-filtering yet).
// It doesn't do scoring or conversion to CompletionItem yet, as we want to
@@ -474,7 +573,7 @@ bool contextAllowsIndex(enum CodeCompletionContext::Kind K) {
// within the callback.
struct CompletionRecorder : public CodeCompleteConsumer {
CompletionRecorder(const CodeCompleteOptions &Opts,
- UniqueFunction<void()> ResultsCallback)
+ llvm::unique_function<void()> ResultsCallback)
: CodeCompleteConsumer(Opts.getClangCompleteOpts(),
/*OutputIsBinary=*/false),
CCContext(CodeCompletionContext::CCC_Other), Opts(Opts),
@@ -491,17 +590,29 @@ struct CompletionRecorder : public CodeCompleteConsumer {
void ProcessCodeCompleteResults(class Sema &S, CodeCompletionContext Context,
CodeCompletionResult *InResults,
unsigned NumResults) override final {
+ // Results from recovery mode are generally useless, and the callback after
+ // recovery (if any) is usually more interesting. To make sure we handle the
+ // future callback from sema, we just ignore all callbacks in recovery mode,
+ // as taking only results from recovery mode results in poor completion
+ // results.
+ // FIXME: in case there is no future sema completion callback after the
+ // recovery mode, we might still want to provide some results (e.g. trivial
+ // identifier-based completion).
+ if (Context.getKind() == CodeCompletionContext::CCC_Recovery) {
+ log("Code complete: Ignoring sema code complete callback with Recovery "
+ "context.");
+ return;
+ }
// If a callback is called without any sema result and the context does not
// support index-based completion, we simply skip it to give way to
// potential future callbacks with results.
if (NumResults == 0 && !contextAllowsIndex(Context.getKind()))
return;
if (CCSema) {
- log(llvm::formatv(
- "Multiple code complete callbacks (parser backtracked?). "
+ log("Multiple code complete callbacks (parser backtracked?). "
"Dropping results from context {0}, keeping results from {1}.",
getCompletionKindString(Context.getKind()),
- getCompletionKindString(this->CCContext.getKind())));
+ getCompletionKindString(this->CCContext.getKind()));
return;
}
// Record the completion context.
@@ -519,9 +630,9 @@ struct CompletionRecorder : public CodeCompleteConsumer {
(Result.Availability == CXAvailability_NotAvailable ||
Result.Availability == CXAvailability_NotAccessible))
continue;
- // Destructor completion is rarely useful, and works inconsistently.
- // (s.^ completes ~string, but s.~st^ is an error).
- if (dyn_cast_or_null<CXXDestructorDecl>(Result.Declaration))
+ if (Result.Declaration &&
+ !Context.getBaseType().isNull() // is this a member-access context?
+ && isBlacklistedMember(*Result.Declaration))
continue;
// We choose to never append '::' to completion results in clangd.
Result.StartsNestedNameSpecifier = false;
@@ -565,15 +676,7 @@ private:
CodeCompleteOptions Opts;
std::shared_ptr<GlobalCodeCompletionAllocator> CCAllocator;
CodeCompletionTUInfo CCTUInfo;
- UniqueFunction<void()> ResultsCallback;
-};
-
-struct ScoredCandidateGreater {
- bool operator()(const ScoredCandidate &L, const ScoredCandidate &R) {
- if (L.second.finalScore != R.second.finalScore)
- return L.second.finalScore > R.second.finalScore;
- return L.first.Name < R.first.Name; // Earlier name is better.
- }
+ llvm::unique_function<void()> ResultsCallback;
};
class SignatureHelpCollector final : public CodeCompleteConsumer {
@@ -603,7 +706,7 @@ public:
CurrentArg, S, *Allocator, CCTUInfo, true);
assert(CCS && "Expected the CodeCompletionString to be non-null");
// FIXME: for headers, we need to get a comment from the index.
- SigHelp.signatures.push_back(ProcessOverloadCandidate(
+ SigHelp.signatures.push_back(processOverloadCandidate(
Candidate, *CCS,
getParameterDocComment(S.getASTContext(), Candidate, CurrentArg,
/*CommentsFromHeaders=*/false)));
@@ -618,7 +721,7 @@ private:
// FIXME(ioeric): consider moving CodeCompletionString logic here to
// CompletionString.h.
SignatureInformation
- ProcessOverloadCandidate(const OverloadCandidate &Candidate,
+ processOverloadCandidate(const OverloadCandidate &Candidate,
const CodeCompletionString &CCS,
llvm::StringRef DocComment) const {
SignatureInformation Result;
@@ -680,7 +783,6 @@ struct SemaCompleteInput {
PathRef FileName;
const tooling::CompileCommand &Command;
PrecompiledPreamble const *Preamble;
- const std::vector<Inclusion> &PreambleInclusions;
StringRef Contents;
Position Pos;
IntrusiveRefCntPtr<vfs::FileSystem> VFS;
@@ -688,12 +790,11 @@ struct SemaCompleteInput {
};
// Invokes Sema code completion on a file.
-// If \p Includes is set, it will be initialized after a compiler instance has
-// been set up.
+// If \p Includes is set, it will be updated based on the compiler invocation.
bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
const clang::CodeCompleteOptions &Options,
const SemaCompleteInput &Input,
- std::unique_ptr<IncludeInserter> *Includes = nullptr) {
+ IncludeStructure *Includes = nullptr) {
trace::Span Tracer("Sema completion");
std::vector<const char *> ArgStrs;
for (const auto &S : Input.Command.CommandLine)
@@ -712,7 +813,7 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
&DummyDiagsConsumer, false),
Input.VFS);
if (!CI) {
- log("Couldn't create CompilerInvocation");
+ elog("Couldn't create CompilerInvocation");
return false;
}
auto &FrontendOpts = CI->getFrontendOpts();
@@ -726,8 +827,7 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
FrontendOpts.CodeCompletionAt.FileName = Input.FileName;
auto Offset = positionToOffset(Input.Contents, Input.Pos);
if (!Offset) {
- log("Code completion position was invalid " +
- llvm::toString(Offset.takeError()));
+ elog("Code completion position was invalid {0}", Offset.takeError());
return false;
}
std::tie(FrontendOpts.CodeCompletionAt.Line,
@@ -750,34 +850,15 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
SyntaxOnlyAction Action;
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
- log("BeginSourceFile() failed when running codeComplete for " +
+ log("BeginSourceFile() failed when running codeComplete for {0}",
Input.FileName);
return false;
}
- if (Includes) {
- // Initialize Includes if provided.
-
- // FIXME(ioeric): needs more consistent style support in clangd server.
- auto Style = format::getStyle("file", Input.FileName, "LLVM",
- Input.Contents, Input.VFS.get());
- if (!Style) {
- log("Failed to get FormatStyle for file" + Input.FileName +
- ". Fall back to use LLVM style. Error: " +
- llvm::toString(Style.takeError()));
- Style = format::getLLVMStyle();
- }
- *Includes = llvm::make_unique<IncludeInserter>(
- Input.FileName, Input.Contents, *Style, Input.Command.Directory,
- Clang->getPreprocessor().getHeaderSearchInfo());
- for (const auto &Inc : Input.PreambleInclusions)
- Includes->get()->addExisting(Inc);
- Clang->getPreprocessor().addPPCallbacks(collectInclusionsInMainFileCallback(
- Clang->getSourceManager(), [Includes](Inclusion Inc) {
- Includes->get()->addExisting(std::move(Inc));
- }));
- }
+ if (Includes)
+ Clang->getPreprocessor().addPPCallbacks(
+ collectIncludeStructureCallback(Clang->getSourceManager(), Includes));
if (!Action.Execute()) {
- log("Execute() failed when running codeComplete for " + Input.FileName);
+ log("Execute() failed when running codeComplete for {0}", Input.FileName);
return false;
}
Action.EndSourceFile();
@@ -830,6 +911,7 @@ clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
// the index can provide results from the preamble.
// Tell Sema not to deserialize the preamble to look for results.
Result.LoadExternal = !Index;
+ Result.IncludeFixIts = IncludeFixIts;
return Result;
}
@@ -865,35 +947,78 @@ clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
// - TopN determines the results with the best score.
class CodeCompleteFlow {
PathRef FileName;
+ IncludeStructure Includes; // Complete once the compiler runs.
const CodeCompleteOptions &Opts;
// Sema takes ownership of Recorder. Recorder is valid until Sema cleanup.
CompletionRecorder *Recorder = nullptr;
int NSema = 0, NIndex = 0, NBoth = 0; // Counters for logging.
bool Incomplete = false; // Would more be available with a higher limit?
llvm::Optional<FuzzyMatcher> Filter; // Initialized once Sema runs.
- std::unique_ptr<IncludeInserter> Includes; // Initialized once compiler runs.
+ std::vector<std::string> QueryScopes; // Initialized once Sema runs.
+ // Include-insertion and proximity scoring rely on the include structure.
+ // This is available after Sema has run.
+ llvm::Optional<IncludeInserter> Inserter; // Available during runWithSema.
+ llvm::Optional<URIDistance> FileProximity; // Initialized once Sema runs.
public:
// A CodeCompleteFlow object is only useful for calling run() exactly once.
- CodeCompleteFlow(PathRef FileName, const CodeCompleteOptions &Opts)
- : FileName(FileName), Opts(Opts) {}
+ CodeCompleteFlow(PathRef FileName, const IncludeStructure &Includes,
+ const CodeCompleteOptions &Opts)
+ : FileName(FileName), Includes(Includes), Opts(Opts) {}
- CompletionList run(const SemaCompleteInput &SemaCCInput) && {
+ CodeCompleteResult run(const SemaCompleteInput &SemaCCInput) && {
trace::Span Tracer("CodeCompleteFlow");
// We run Sema code completion first. It builds an AST and calculates:
// - completion results based on the AST.
// - partial identifier and context. We need these for the index query.
- CompletionList Output;
+ CodeCompleteResult Output;
auto RecorderOwner = llvm::make_unique<CompletionRecorder>(Opts, [&]() {
assert(Recorder && "Recorder is not set");
- assert(Includes && "Includes is not set");
+ auto Style =
+ format::getStyle(format::DefaultFormatStyle, SemaCCInput.FileName,
+ format::DefaultFallbackStyle, SemaCCInput.Contents,
+ SemaCCInput.VFS.get());
+ if (!Style) {
+ log("getStyle() failed for file {0}: {1}. Fallback is LLVM style.",
+ SemaCCInput.FileName, Style.takeError());
+ Style = format::getLLVMStyle();
+ }
// If preprocessor was run, inclusions from preprocessor callback should
- // already be added to Inclusions.
+ // already be added to Includes.
+ Inserter.emplace(
+ SemaCCInput.FileName, SemaCCInput.Contents, *Style,
+ SemaCCInput.Command.Directory,
+ Recorder->CCSema->getPreprocessor().getHeaderSearchInfo());
+ for (const auto &Inc : Includes.MainFileIncludes)
+ Inserter->addExisting(Inc);
+
+ // Most of the cost of file proximity is in initializing the FileDistance
+ // structures based on the observed includes, once per query. Conceptually
+ // that happens here (though the per-URI-scheme initialization is lazy).
+ // The per-result proximity scoring is (amortized) very cheap.
+ FileDistanceOptions ProxOpts{}; // Use defaults.
+ const auto &SM = Recorder->CCSema->getSourceManager();
+ llvm::StringMap<SourceParams> ProxSources;
+ for (auto &Entry : Includes.includeDepth(
+ SM.getFileEntryForID(SM.getMainFileID())->getName())) {
+ auto &Source = ProxSources[Entry.getKey()];
+ Source.Cost = Entry.getValue() * ProxOpts.IncludeCost;
+ // Symbols near our transitive includes are good, but only consider
+ // things in the same directory or below it. Otherwise there can be
+ // many false positives.
+ if (Entry.getValue() > 0)
+ Source.MaxUpTraversals = 1;
+ }
+ FileProximity.emplace(ProxSources, ProxOpts);
+
Output = runWithSema();
- Includes.reset(); // Make sure this doesn't out-live Clang.
+ Inserter.reset(); // Make sure this doesn't out-live Clang.
SPAN_ATTACH(Tracer, "sema_completion_kind",
getCompletionKindString(Recorder->CCContext.getKind()));
+ log("Code complete: sema context {0}, query scopes [{1}]",
+ getCompletionKindString(Recorder->CCContext.getKind()),
+ llvm::join(QueryScopes.begin(), QueryScopes.end(), ","));
});
Recorder = RecorderOwner.get();
@@ -903,13 +1028,13 @@ public:
SPAN_ATTACH(Tracer, "sema_results", NSema);
SPAN_ATTACH(Tracer, "index_results", NIndex);
SPAN_ATTACH(Tracer, "merged_results", NBoth);
- SPAN_ATTACH(Tracer, "returned_results", Output.items.size());
- SPAN_ATTACH(Tracer, "incomplete", Output.isIncomplete);
- log(llvm::formatv("Code complete: {0} results from Sema, {1} from Index, "
- "{2} matched, {3} returned{4}.",
- NSema, NIndex, NBoth, Output.items.size(),
- Output.isIncomplete ? " (incomplete)" : ""));
- assert(!Opts.Limit || Output.items.size() <= Opts.Limit);
+ SPAN_ATTACH(Tracer, "returned_results", int64_t(Output.Completions.size()));
+ SPAN_ATTACH(Tracer, "incomplete", Output.HasMore);
+ log("Code complete: {0} results from Sema, {1} from Index, "
+ "{2} matched, {3} returned{4}.",
+ NSema, NIndex, NBoth, Output.Completions.size(),
+ Output.HasMore ? " (incomplete)" : "");
+ assert(!Opts.Limit || Output.Completions.size() <= Opts.Limit);
// We don't assert that isIncomplete means we hit a limit.
// Indexes may choose to impose their own limits even if we don't have one.
return Output;
@@ -918,30 +1043,35 @@ public:
private:
// This is called by run() once Sema code completion is done, but before the
// Sema data structures are torn down. It does all the real work.
- CompletionList runWithSema() {
+ CodeCompleteResult runWithSema() {
Filter = FuzzyMatcher(
Recorder->CCSema->getPreprocessor().getCodeCompletionFilter());
+ QueryScopes = getQueryScopes(Recorder->CCContext,
+ Recorder->CCSema->getSourceManager());
// Sema provides the needed context to query the index.
// FIXME: in addition to querying for extra/overlapping symbols, we should
// explicitly request symbols corresponding to Sema results.
// We can use their signals even if the index can't suggest them.
// We must copy index results to preserve them, but there are at most Limit.
- auto IndexResults = queryIndex();
+ auto IndexResults = (Opts.Index && allowIndex(Recorder->CCContext))
+ ? queryIndex()
+ : SymbolSlab();
// Merge Sema and Index results, score them, and pick the winners.
auto Top = mergeResults(Recorder->Results, IndexResults);
- // Convert the results to the desired LSP structs.
- CompletionList Output;
- for (auto &C : Top)
- Output.items.push_back(toCompletionItem(C.first, C.second));
- Output.isIncomplete = Incomplete;
+ // Convert the results to final form, assembling the expensive strings.
+ CodeCompleteResult Output;
+ for (auto &C : Top) {
+ Output.Completions.push_back(toCodeCompletion(C.first));
+ Output.Completions.back().Score = C.second;
+ }
+ Output.HasMore = Incomplete;
+ Output.Context = Recorder->CCContext.getKind();
return Output;
}
SymbolSlab queryIndex() {
- if (!Opts.Index || !allowIndex(Recorder->CCContext))
- return SymbolSlab();
trace::Span Tracer("Query index");
- SPAN_ATTACH(Tracer, "limit", Opts.Limit);
+ SPAN_ATTACH(Tracer, "limit", int64_t(Opts.Limit));
SymbolSlab::Builder ResultsBuilder;
// Build the query.
@@ -949,11 +1079,12 @@ private:
if (Opts.Limit)
Req.MaxCandidateCount = Opts.Limit;
Req.Query = Filter->pattern();
- Req.Scopes = getQueryScopes(Recorder->CCContext,
- Recorder->CCSema->getSourceManager());
- log(llvm::formatv("Code complete: fuzzyFind(\"{0}\", scopes=[{1}])",
- Req.Query,
- llvm::join(Req.Scopes.begin(), Req.Scopes.end(), ",")));
+ Req.RestrictForCodeCompletion = true;
+ Req.Scopes = QueryScopes;
+ // FIXME: we should send multiple weighted paths here.
+ Req.ProximityPaths.push_back(FileName);
+ vlog("Code complete: fuzzyFind(\"{0}\", scopes=[{1}])", Req.Query,
+ llvm::join(Req.Scopes.begin(), Req.Scopes.end(), ","));
// Run the query against the index.
if (Opts.Index->fuzzyFind(
Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); }))
@@ -961,15 +1092,31 @@ private:
return std::move(ResultsBuilder).build();
}
- // Merges the Sema and Index results where possible, scores them, and
- // returns the top results from best to worst.
- std::vector<std::pair<CompletionCandidate, CompletionItemScores>>
- mergeResults(const std::vector<CodeCompletionResult> &SemaResults,
- const SymbolSlab &IndexResults) {
+ // Merges Sema and Index results where possible, to form CompletionCandidates.
+ // Groups overloads if desired, to form CompletionCandidate::Bundles.
+ // The bundles are scored and top results are returned, best to worst.
+ std::vector<ScoredBundle>
+ mergeResults(const std::vector<CodeCompletionResult> &SemaResults,
+ const SymbolSlab &IndexResults) {
trace::Span Tracer("Merge and score results");
- // We only keep the best N results at any time, in "native" format.
- TopN<ScoredCandidate, ScoredCandidateGreater> Top(
- Opts.Limit == 0 ? std::numeric_limits<size_t>::max() : Opts.Limit);
+ std::vector<CompletionCandidate::Bundle> Bundles;
+ llvm::DenseMap<size_t, size_t> BundleLookup;
+ auto AddToBundles = [&](const CodeCompletionResult *SemaResult,
+ const Symbol *IndexResult) {
+ CompletionCandidate C;
+ C.SemaResult = SemaResult;
+ C.IndexResult = IndexResult;
+ C.Name = IndexResult ? IndexResult->Name : Recorder->getName(*SemaResult);
+ if (auto OverloadSet = Opts.BundleOverloads ? C.overloadSet() : 0) {
+ auto Ret = BundleLookup.try_emplace(OverloadSet, Bundles.size());
+ if (Ret.second)
+ Bundles.emplace_back();
+ Bundles[Ret.first->second].push_back(std::move(C));
+ } else {
+ Bundles.emplace_back();
+ Bundles.back().push_back(std::move(C));
+ }
+ };
llvm::DenseSet<const Symbol *> UsedIndexResults;
auto CorrespondingIndexResult =
[&](const CodeCompletionResult &SemaResult) -> const Symbol * {
@@ -984,89 +1131,105 @@ private:
};
// Emit all Sema results, merging them with Index results if possible.
for (auto &SemaResult : Recorder->Results)
- addCandidate(Top, &SemaResult, CorrespondingIndexResult(SemaResult));
+ AddToBundles(&SemaResult, CorrespondingIndexResult(SemaResult));
// Now emit any Index-only results.
for (const auto &IndexResult : IndexResults) {
if (UsedIndexResults.count(&IndexResult))
continue;
- addCandidate(Top, /*SemaResult=*/nullptr, &IndexResult);
+ AddToBundles(/*SemaResult=*/nullptr, &IndexResult);
}
+ // We only keep the best N results at any time, in "native" format.
+ TopN<ScoredBundle, ScoredBundleGreater> Top(
+ Opts.Limit == 0 ? std::numeric_limits<size_t>::max() : Opts.Limit);
+ for (auto &Bundle : Bundles)
+ addCandidate(Top, std::move(Bundle));
return std::move(Top).items();
}
- // Scores a candidate and adds it to the TopN structure.
- void addCandidate(TopN<ScoredCandidate, ScoredCandidateGreater> &Candidates,
- const CodeCompletionResult *SemaResult,
- const Symbol *IndexResult) {
- CompletionCandidate C;
- C.SemaResult = SemaResult;
- C.IndexResult = IndexResult;
- C.Name = IndexResult ? IndexResult->Name : Recorder->getName(*SemaResult);
+ Optional<float> fuzzyScore(const CompletionCandidate &C) {
+ // Macros can be very spammy, so we only support prefix completion.
+ // We won't end up with underfull index results, as macros are sema-only.
+ if (C.SemaResult && C.SemaResult->Kind == CodeCompletionResult::RK_Macro &&
+ !C.Name.startswith_lower(Filter->pattern()))
+ return None;
+ return Filter->match(C.Name);
+ }
+ // Scores a candidate and adds it to the TopN structure.
+ void addCandidate(TopN<ScoredBundle, ScoredBundleGreater> &Candidates,
+ CompletionCandidate::Bundle Bundle) {
SymbolQualitySignals Quality;
SymbolRelevanceSignals Relevance;
- if (auto FuzzyScore = Filter->match(C.Name))
+ Relevance.Context = Recorder->CCContext.getKind();
+ Relevance.Query = SymbolRelevanceSignals::CodeComplete;
+ Relevance.FileProximityMatch = FileProximity.getPointer();
+ auto &First = Bundle.front();
+ if (auto FuzzyScore = fuzzyScore(First))
Relevance.NameMatch = *FuzzyScore;
else
return;
- if (IndexResult)
- Quality.merge(*IndexResult);
- if (SemaResult) {
- Quality.merge(*SemaResult);
- Relevance.merge(*SemaResult);
+ SymbolOrigin Origin = SymbolOrigin::Unknown;
+ bool FromIndex = false;
+ for (const auto &Candidate : Bundle) {
+ if (Candidate.IndexResult) {
+ Quality.merge(*Candidate.IndexResult);
+ Relevance.merge(*Candidate.IndexResult);
+ Origin |= Candidate.IndexResult->Origin;
+ FromIndex = true;
+ }
+ if (Candidate.SemaResult) {
+ Quality.merge(*Candidate.SemaResult);
+ Relevance.merge(*Candidate.SemaResult);
+ Origin |= SymbolOrigin::AST;
+ }
}
- float QualScore = Quality.evaluate(), RelScore = Relevance.evaluate();
- CompletionItemScores Scores;
- Scores.finalScore = evaluateSymbolAndRelevance(QualScore, RelScore);
- // The purpose of exporting component scores is to allow NameMatch to be
- // replaced on the client-side. So we export (NameMatch, final/NameMatch)
- // rather than (RelScore, QualScore).
- Scores.filterScore = Relevance.NameMatch;
- Scores.symbolScore =
- Scores.filterScore ? Scores.finalScore / Scores.filterScore : QualScore;
-
- LLVM_DEBUG(llvm::dbgs()
- << "CodeComplete: " << C.Name << (IndexResult ? " (index)" : "")
- << (SemaResult ? " (sema)" : "") << " = " << Scores.finalScore
- << "\n"
- << Quality << Relevance << "\n");
-
- NSema += bool(SemaResult);
- NIndex += bool(IndexResult);
- NBoth += SemaResult && IndexResult;
- if (Candidates.push({C, Scores}))
+ CodeCompletion::Scores Scores;
+ Scores.Quality = Quality.evaluate();
+ Scores.Relevance = Relevance.evaluate();
+ Scores.Total = evaluateSymbolAndRelevance(Scores.Quality, Scores.Relevance);
+ // NameMatch is in fact a multiplier on total score, so rescoring is sound.
+ Scores.ExcludingName = Relevance.NameMatch
+ ? Scores.Total / Relevance.NameMatch
+ : Scores.Quality;
+
+ dlog("CodeComplete: {0} ({1}) = {2}\n{3}{4}\n", First.Name,
+ llvm::to_string(Origin), Scores.Total, llvm::to_string(Quality),
+ llvm::to_string(Relevance));
+
+ NSema += bool(Origin & SymbolOrigin::AST);
+ NIndex += FromIndex;
+ NBoth += bool(Origin & SymbolOrigin::AST) && FromIndex;
+ if (Candidates.push({std::move(Bundle), Scores}))
Incomplete = true;
}
- CompletionItem toCompletionItem(const CompletionCandidate &Candidate,
- const CompletionItemScores &Scores) {
- CodeCompletionString *SemaCCS = nullptr;
- std::string DocComment;
- if (auto *SR = Candidate.SemaResult) {
- SemaCCS = Recorder->codeCompletionString(*SR);
- if (Opts.IncludeComments) {
- assert(Recorder->CCSema);
- DocComment = getDocComment(Recorder->CCSema->getASTContext(), *SR,
- /*CommentsFromHeader=*/false);
- }
+ CodeCompletion toCodeCompletion(const CompletionCandidate::Bundle &Bundle) {
+ llvm::Optional<CodeCompletionBuilder> Builder;
+ for (const auto &Item : Bundle) {
+ CodeCompletionString *SemaCCS =
+ Item.SemaResult ? Recorder->codeCompletionString(*Item.SemaResult)
+ : nullptr;
+ if (!Builder)
+ Builder.emplace(Recorder->CCSema->getASTContext(), Item, SemaCCS,
+ *Inserter, FileName, Opts);
+ else
+ Builder->add(Item, SemaCCS);
}
- return Candidate.build(FileName, Scores, Opts, SemaCCS, Includes.get(),
- DocComment);
+ return Builder->build();
}
};
-CompletionList codeComplete(PathRef FileName,
- const tooling::CompileCommand &Command,
- PrecompiledPreamble const *Preamble,
- const std::vector<Inclusion> &PreambleInclusions,
- StringRef Contents, Position Pos,
- IntrusiveRefCntPtr<vfs::FileSystem> VFS,
- std::shared_ptr<PCHContainerOperations> PCHs,
- CodeCompleteOptions Opts) {
- return CodeCompleteFlow(FileName, Opts)
- .run({FileName, Command, Preamble, PreambleInclusions, Contents, Pos, VFS,
- PCHs});
+CodeCompleteResult codeComplete(PathRef FileName,
+ const tooling::CompileCommand &Command,
+ PrecompiledPreamble const *Preamble,
+ const IncludeStructure &PreambleInclusions,
+ StringRef Contents, Position Pos,
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS,
+ std::shared_ptr<PCHContainerOperations> PCHs,
+ CodeCompleteOptions Opts) {
+ return CodeCompleteFlow(FileName, PreambleInclusions, Opts)
+ .run({FileName, Command, Preamble, Contents, Pos, VFS, PCHs});
}
SignatureHelp signatureHelp(PathRef FileName,
@@ -1081,13 +1244,68 @@ SignatureHelp signatureHelp(PathRef FileName,
Options.IncludeMacros = false;
Options.IncludeCodePatterns = false;
Options.IncludeBriefComments = false;
- std::vector<Inclusion> PreambleInclusions = {}; // Unused for signatureHelp
+ IncludeStructure PreambleInclusions; // Unused for signatureHelp
semaCodeComplete(llvm::make_unique<SignatureHelpCollector>(Options, Result),
Options,
- {FileName, Command, Preamble, PreambleInclusions, Contents,
- Pos, std::move(VFS), std::move(PCHs)});
+ {FileName, Command, Preamble, Contents, Pos, std::move(VFS),
+ std::move(PCHs)});
return Result;
}
+bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx) {
+ using namespace clang::ast_matchers;
+ auto InTopLevelScope = hasDeclContext(
+ anyOf(namespaceDecl(), translationUnitDecl(), linkageSpecDecl()));
+ return !match(decl(anyOf(InTopLevelScope,
+ hasDeclContext(
+ enumDecl(InTopLevelScope, unless(isScoped()))))),
+ ND, ASTCtx)
+ .empty();
+}
+
+CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const {
+ CompletionItem LSP;
+ LSP.label = (HeaderInsertion ? Opts.IncludeIndicator.Insert
+ : Opts.IncludeIndicator.NoInsert) +
+ (Opts.ShowOrigins ? "[" + llvm::to_string(Origin) + "]" : "") +
+ RequiredQualifier + Name + Signature;
+
+ LSP.kind = Kind;
+ LSP.detail = BundleSize > 1 ? llvm::formatv("[{0} overloads]", BundleSize)
+ : ReturnType;
+ if (!Header.empty())
+ LSP.detail += "\n" + Header;
+ LSP.documentation = Documentation;
+ LSP.sortText = sortText(Score.Total, Name);
+ LSP.filterText = Name;
+ // FIXME(kadircet): Use LSP.textEdit instead of insertText, because it causes
+ // undesired behaviours. Like completing "this.^" into "this-push_back".
+ LSP.insertText = RequiredQualifier + Name;
+ if (Opts.EnableSnippets)
+ LSP.insertText += SnippetSuffix;
+ LSP.insertTextFormat = Opts.EnableSnippets ? InsertTextFormat::Snippet
+ : InsertTextFormat::PlainText;
+ LSP.additionalTextEdits.reserve(FixIts.size() + (HeaderInsertion ? 1 : 0));
+ for (const auto &FixIt : FixIts)
+ LSP.additionalTextEdits.push_back(FixIt);
+ if (HeaderInsertion)
+ LSP.additionalTextEdits.push_back(*HeaderInsertion);
+ return LSP;
+}
+
+raw_ostream &operator<<(raw_ostream &OS, const CodeCompletion &C) {
+ // For now just lean on CompletionItem.
+ return OS << C.render(CodeCompleteOptions());
+}
+
+raw_ostream &operator<<(raw_ostream &OS, const CodeCompleteResult &R) {
+ OS << "CodeCompleteResult: " << R.Completions.size() << (R.HasMore ? "+" : "")
+ << " (" << getCompletionKindString(R.Context) << ")"
+ << " items:\n";
+ for (const auto &C : R.Completions)
+ OS << C << "\n";
+ return OS;
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/CodeComplete.h b/clangd/CodeComplete.h
index 4d0ef751..c623be34 100644
--- a/clangd/CodeComplete.h
+++ b/clangd/CodeComplete.h
@@ -21,10 +21,12 @@
#include "Protocol.h"
#include "index/Index.h"
#include "clang/Frontend/PrecompiledPreamble.h"
+#include "clang/Sema/CodeCompleteConsumer.h"
#include "clang/Sema/CodeCompleteOptions.h"
#include "clang/Tooling/CompilationDatabase.h"
namespace clang {
+class NamedDecl;
class PCHContainerOperations;
namespace clangd {
@@ -52,27 +54,118 @@ struct CodeCompleteOptions {
/// For example, private members are usually inaccessible.
bool IncludeIneligibleResults = false;
+ /// Combine overloads into a single completion item where possible.
+ bool BundleOverloads = false;
+
/// Limit the number of results returned (0 means no limit).
/// If more results are available, we set CompletionList.isIncomplete.
size_t Limit = 0;
+ /// A visual indicator to prepend to the completion label to indicate whether
+ /// completion result would trigger an #include insertion or not.
+ struct IncludeInsertionIndicator {
+ std::string Insert = "•";
+ std::string NoInsert = " ";
+ } IncludeIndicator;
+
+ /// Expose origins of completion items in the label (for debugging).
+ bool ShowOrigins = false;
+
// Populated internally by clangd, do not set.
/// If `Index` is set, it is used to augment the code completion
/// results.
/// FIXME(ioeric): we might want a better way to pass the index around inside
/// clangd.
const SymbolIndex *Index = nullptr;
+
+ /// Include completions that require small corrections, e.g. change '.' to
+ /// '->' on member access etc.
+ bool IncludeFixIts = false;
};
+// Semi-structured representation of a code-complete suggestion for our C++ API.
+// We don't use the LSP structures here (unlike most features) as we want
+// to expose more data to allow for more precise testing and evaluation.
+struct CodeCompletion {
+ // The unqualified name of the symbol or other completion item.
+ std::string Name;
+ // The scope qualifier for the symbol name. e.g. "ns1::ns2::"
+ // Empty for non-symbol completions. Not inserted, but may be displayed.
+ std::string Scope;
+ // Text that must be inserted before the name, and displayed (e.g. base::).
+ std::string RequiredQualifier;
+ // Details to be displayed following the name. Not inserted.
+ std::string Signature;
+ // Text to be inserted following the name, in snippet format.
+ std::string SnippetSuffix;
+ // Type to be displayed for this completion.
+ std::string ReturnType;
+ std::string Documentation;
+ CompletionItemKind Kind = CompletionItemKind::Missing;
+ // This completion item may represent several symbols that can be inserted in
+ // the same way, such as function overloads. In this case BundleSize > 1, and
+ // the following fields are summaries:
+ // - Signature is e.g. "(...)" for functions.
+ // - SnippetSuffix is similarly e.g. "(${0})".
+ // - ReturnType may be empty
+ // - Documentation may be from one symbol, or a combination of several
+ // Other fields should apply equally to all bundled completions.
+ unsigned BundleSize = 1;
+ SymbolOrigin Origin = SymbolOrigin::Unknown;
+ // The header through which this symbol could be included.
+ // Quoted string as expected by an #include directive, e.g. "<memory>".
+ // Empty for non-symbol completions, or when not known.
+ std::string Header;
+ // Present if Header is set and should be inserted to use this item.
+ llvm::Optional<TextEdit> HeaderInsertion;
+
+ /// Holds information about small corrections that needs to be done. Like
+ /// converting '->' to '.' on member access.
+ std::vector<TextEdit> FixIts;
+
+ // Scores are used to rank completion items.
+ struct Scores {
+ // The score that items are ranked by.
+ float Total = 0.f;
+
+ // The finalScore with the fuzzy name match score excluded.
+ // When filtering client-side, editors should calculate the new fuzzy score,
+ // whose scale is 0-1 (with 1 = prefix match, special case 2 = exact match),
+ // and recompute finalScore = fuzzyScore * symbolScore.
+ float ExcludingName = 0.f;
+
+ // Component scores that contributed to the final score:
+
+ // Quality describes how important we think this candidate is,
+ // independent of the query.
+ // e.g. symbols with lots of incoming references have higher quality.
+ float Quality = 0.f;
+ // Relevance describes how well this candidate matched the query.
+ // e.g. symbols from nearby files have higher relevance.
+ float Relevance = 0.f;
+ };
+ Scores Score;
+
+ // Serialize this to an LSP completion item. This is a lossy operation.
+ CompletionItem render(const CodeCompleteOptions &) const;
+};
+raw_ostream &operator<<(raw_ostream &, const CodeCompletion &);
+struct CodeCompleteResult {
+ std::vector<CodeCompletion> Completions;
+ bool HasMore = false;
+ CodeCompletionContext::Kind Context = CodeCompletionContext::CCC_Other;
+};
+raw_ostream &operator<<(raw_ostream &, const CodeCompleteResult &);
+
/// Get code completions at a specified \p Pos in \p FileName.
-CompletionList codeComplete(PathRef FileName,
- const tooling::CompileCommand &Command,
- PrecompiledPreamble const *Preamble,
- const std::vector<Inclusion> &PreambleInclusions,
- StringRef Contents, Position Pos,
- IntrusiveRefCntPtr<vfs::FileSystem> VFS,
- std::shared_ptr<PCHContainerOperations> PCHs,
- CodeCompleteOptions Opts);
+CodeCompleteResult codeComplete(PathRef FileName,
+ const tooling::CompileCommand &Command,
+ PrecompiledPreamble const *Preamble,
+ const IncludeStructure &PreambleInclusions,
+ StringRef Contents, Position Pos,
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS,
+ std::shared_ptr<PCHContainerOperations> PCHs,
+ CodeCompleteOptions Opts);
/// Get signature help at a specified \p Pos in \p FileName.
SignatureHelp signatureHelp(PathRef FileName,
@@ -82,6 +175,17 @@ SignatureHelp signatureHelp(PathRef FileName,
IntrusiveRefCntPtr<vfs::FileSystem> VFS,
std::shared_ptr<PCHContainerOperations> PCHs);
+// For index-based completion, we only consider:
+// * symbols in namespaces or translation unit scopes (e.g. no class
+// members, no locals)
+// * enum constants in unscoped enum decl (e.g. "red" in "enum {red};")
+// * primary templates (no specializations)
+// For the other cases, we let Clang do the completion because it does not
+// need any non-local information and it will be much better at following
+// lookup rules. Other symbols still appear in the index for other purposes,
+// like workspace/symbols or textDocument/definition, but are not used for code
+// completion.
+bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx);
} // namespace clangd
} // namespace clang
diff --git a/clangd/CodeCompletionStrings.cpp b/clangd/CodeCompletionStrings.cpp
index 8cdaa338..e601e035 100644
--- a/clangd/CodeCompletionStrings.cpp
+++ b/clangd/CodeCompletionStrings.cpp
@@ -24,31 +24,6 @@ bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) {
StringRef(Chunk.Text).endswith("::");
}
-void processPlainTextChunks(const CodeCompletionString &CCS,
- std::string *LabelOut, std::string *InsertTextOut) {
- std::string &Label = *LabelOut;
- std::string &InsertText = *InsertTextOut;
- for (const auto &Chunk : CCS) {
- // Informative qualifier chunks only clutter completion results, skip
- // them.
- if (isInformativeQualifierChunk(Chunk))
- continue;
-
- switch (Chunk.Kind) {
- case CodeCompletionString::CK_ResultType:
- case CodeCompletionString::CK_Optional:
- break;
- case CodeCompletionString::CK_TypedText:
- InsertText += Chunk.Text;
- Label += Chunk.Text;
- break;
- default:
- Label += Chunk.Text;
- break;
- }
- }
-}
-
void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) {
for (const auto Character : Text) {
if (Character == '$' || Character == '}' || Character == '\\')
@@ -57,11 +32,67 @@ void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) {
}
}
-void processSnippetChunks(const CodeCompletionString &CCS,
- std::string *LabelOut, std::string *InsertTextOut) {
- std::string &Label = *LabelOut;
- std::string &InsertText = *InsertTextOut;
+bool looksLikeDocComment(llvm::StringRef CommentText) {
+ // We don't report comments that only contain "special" chars.
+ // This avoids reporting various delimiters, like:
+ // =================
+ // -----------------
+ // *****************
+ return CommentText.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos;
+}
+
+} // namespace
+std::string getDocComment(const ASTContext &Ctx,
+ const CodeCompletionResult &Result,
+ bool CommentsFromHeaders) {
+ // FIXME: clang's completion also returns documentation for RK_Pattern if they
+ // contain a pattern for ObjC properties. Unfortunately, there is no API to
+ // get this declaration, so we don't show documentation in that case.
+ if (Result.Kind != CodeCompletionResult::RK_Declaration)
+ return "";
+ auto *Decl = Result.getDeclaration();
+ if (!Decl || llvm::isa<NamespaceDecl>(Decl)) {
+ // Namespaces often have too many redecls for any particular redecl comment
+ // to be useful. Moreover, we often confuse file headers or generated
+ // comments with namespace comments. Therefore we choose to just ignore
+ // the comments for namespaces.
+ return "";
+ }
+ const RawComment *RC = getCompletionComment(Ctx, Decl);
+ if (!RC)
+ return "";
+
+ // Sanity check that the comment does not come from the PCH. We choose to not
+ // write them into PCH, because they are racy and slow to load.
+ assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
+ std::string Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
+ if (!looksLikeDocComment(Doc))
+ return "";
+ return Doc;
+}
+
+std::string
+getParameterDocComment(const ASTContext &Ctx,
+ const CodeCompleteConsumer::OverloadCandidate &Result,
+ unsigned ArgIndex, bool CommentsFromHeaders) {
+ auto *Func = Result.getFunction();
+ if (!Func)
+ return "";
+ const RawComment *RC = getParameterComment(Ctx, Result, ArgIndex);
+ if (!RC)
+ return "";
+ // Sanity check that the comment does not come from the PCH. We choose to not
+ // write them into PCH, because they are racy and slow to load.
+ assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
+ std::string Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
+ if (!looksLikeDocComment(Doc))
+ return "";
+ return Doc;
+}
+
+void getSignature(const CodeCompletionString &CCS, std::string *Signature,
+ std::string *Snippet, std::string *RequiredQualifiers) {
unsigned ArgCount = 0;
for (const auto &Chunk : CCS) {
// Informative qualifier chunks only clutter completion results, skip
@@ -71,28 +102,36 @@ void processSnippetChunks(const CodeCompletionString &CCS,
switch (Chunk.Kind) {
case CodeCompletionString::CK_TypedText:
+ // The typed-text chunk is the actual name. We don't record this chunk.
+ // In general our string looks like <qualifiers><name><signature>.
+ // So once we see the name, any text we recorded so far should be
+ // reclassified as qualifiers.
+ if (RequiredQualifiers)
+ *RequiredQualifiers = std::move(*Signature);
+ Signature->clear();
+ Snippet->clear();
+ break;
case CodeCompletionString::CK_Text:
- Label += Chunk.Text;
- InsertText += Chunk.Text;
+ *Signature += Chunk.Text;
+ *Snippet += Chunk.Text;
break;
case CodeCompletionString::CK_Optional:
- // FIXME: Maybe add an option to allow presenting the optional chunks?
break;
case CodeCompletionString::CK_Placeholder:
+ *Signature += Chunk.Text;
++ArgCount;
- InsertText += "${" + std::to_string(ArgCount) + ':';
- appendEscapeSnippet(Chunk.Text, &InsertText);
- InsertText += '}';
- Label += Chunk.Text;
+ *Snippet += "${" + std::to_string(ArgCount) + ':';
+ appendEscapeSnippet(Chunk.Text, Snippet);
+ *Snippet += '}';
break;
case CodeCompletionString::CK_Informative:
// For example, the word "const" for a const method, or the name of
// the base class for methods that are part of the base class.
- Label += Chunk.Text;
- // Don't put the informative chunks in the insertText.
+ *Signature += Chunk.Text;
+ // Don't put the informative chunks in the snippet.
break;
case CodeCompletionString::CK_ResultType:
- // This is retrieved as detail.
+ // This is not part of the signature.
break;
case CodeCompletionString::CK_CurrentParameter:
// This should never be present while collecting completion items,
@@ -113,82 +152,17 @@ void processSnippetChunks(const CodeCompletionString &CCS,
case CodeCompletionString::CK_SemiColon:
case CodeCompletionString::CK_Equal:
case CodeCompletionString::CK_HorizontalSpace:
- InsertText += Chunk.Text;
- Label += Chunk.Text;
+ *Signature += Chunk.Text;
+ *Snippet += Chunk.Text;
break;
case CodeCompletionString::CK_VerticalSpace:
- InsertText += Chunk.Text;
- // Don't even add a space to the label.
+ *Snippet += Chunk.Text;
+ // Don't even add a space to the signature.
break;
}
}
}
-bool canRequestComment(const ASTContext &Ctx, const NamedDecl &D,
- bool CommentsFromHeaders) {
- if (CommentsFromHeaders)
- return true;
- auto &SourceMgr = Ctx.getSourceManager();
- // Accessing comments for decls from invalid preamble can lead to crashes.
- // So we only return comments from the main file when doing code completion.
- // For indexing, we still read all the comments.
- // FIXME: find a better fix, e.g. store file contents in the preamble or get
- // doc comments from the index.
- auto canRequestForDecl = [&](const NamedDecl &D) -> bool {
- for (auto *Redecl : D.redecls()) {
- auto Loc = SourceMgr.getSpellingLoc(Redecl->getLocation());
- if (!SourceMgr.isWrittenInMainFile(Loc))
- return false;
- }
- return true;
- };
- // First, check the decl itself.
- if (!canRequestForDecl(D))
- return false;
- // Completion also returns comments for properties, corresponding to ObjC
- // methods.
- const ObjCMethodDecl *M = dyn_cast<ObjCMethodDecl>(&D);
- const ObjCPropertyDecl *PDecl = M ? M->findPropertyDecl() : nullptr;
- return !PDecl || canRequestForDecl(*PDecl);
-}
-} // namespace
-
-std::string getDocComment(const ASTContext &Ctx,
- const CodeCompletionResult &Result,
- bool CommentsFromHeaders) {
- // FIXME: clang's completion also returns documentation for RK_Pattern if they
- // contain a pattern for ObjC properties. Unfortunately, there is no API to
- // get this declaration, so we don't show documentation in that case.
- if (Result.Kind != CodeCompletionResult::RK_Declaration)
- return "";
- auto *Decl = Result.getDeclaration();
- if (!Decl || !canRequestComment(Ctx, *Decl, CommentsFromHeaders))
- return "";
- const RawComment *RC = getCompletionComment(Ctx, Decl);
- if (!RC)
- return "";
- return RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
-}
-
-std::string
-getParameterDocComment(const ASTContext &Ctx,
- const CodeCompleteConsumer::OverloadCandidate &Result,
- unsigned ArgIndex, bool CommentsFromHeaders) {
- auto *Func = Result.getFunction();
- if (!Func || !canRequestComment(Ctx, *Func, CommentsFromHeaders))
- return "";
- const RawComment *RC = getParameterComment(Ctx, Result, ArgIndex);
- if (!RC)
- return "";
- return RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
-}
-
-void getLabelAndInsertText(const CodeCompletionString &CCS, std::string *Label,
- std::string *InsertText, bool EnableSnippets) {
- return EnableSnippets ? processSnippetChunks(CCS, Label, InsertText)
- : processPlainTextChunks(CCS, Label, InsertText);
-}
-
std::string formatDocumentation(const CodeCompletionString &CCS,
llvm::StringRef DocComment) {
// Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this
@@ -219,30 +193,10 @@ std::string formatDocumentation(const CodeCompletionString &CCS,
return Result;
}
-std::string getDetail(const CodeCompletionString &CCS) {
- for (const auto &Chunk : CCS) {
- // Informative qualifier chunks only clutter completion results, skip
- // them.
- switch (Chunk.Kind) {
- case CodeCompletionString::CK_ResultType:
+std::string getReturnType(const CodeCompletionString &CCS) {
+ for (const auto &Chunk : CCS)
+ if (Chunk.Kind == CodeCompletionString::CK_ResultType)
return Chunk.Text;
- default:
- break;
- }
- }
- return "";
-}
-
-std::string getFilterText(const CodeCompletionString &CCS) {
- for (const auto &Chunk : CCS) {
- switch (Chunk.Kind) {
- case CodeCompletionString::CK_TypedText:
- // There's always exactly one CK_TypedText chunk.
- return Chunk.Text;
- default:
- break;
- }
- }
return "";
}
diff --git a/clangd/CodeCompletionStrings.h b/clangd/CodeCompletionStrings.h
index 81d184cf..dac4ba56 100644
--- a/clangd/CodeCompletionStrings.h
+++ b/clangd/CodeCompletionStrings.h
@@ -45,13 +45,15 @@ getParameterDocComment(const ASTContext &Ctx,
const CodeCompleteConsumer::OverloadCandidate &Result,
unsigned ArgIndex, bool CommentsFromHeaders);
-/// Gets label and insert text for a completion item. For example, for function
-/// `Foo`, this returns <"Foo(int x, int y)", "Foo"> without snippts enabled.
-///
-/// If \p EnableSnippets is true, this will try to use snippet for the insert
-/// text. Otherwise, the insert text will always be plain text.
-void getLabelAndInsertText(const CodeCompletionString &CCS, std::string *Label,
- std::string *InsertText, bool EnableSnippets);
+/// Formats the signature for an item, as a display string and snippet.
+/// e.g. for const_reference std::vector<T>::at(size_type) const, this returns:
+/// *Signature = "(size_type) const"
+/// *Snippet = "(${0:size_type})"
+/// If set, RequiredQualifiers is the text that must be typed before the name.
+/// e.g "Base::" when calling a base class member function that's hidden.
+void getSignature(const CodeCompletionString &CCS, std::string *Signature,
+ std::string *Snippet,
+ std::string *RequiredQualifiers = nullptr);
/// Assembles formatted documentation for a completion result. This includes
/// documentation comments and other relevant information like annotations.
@@ -63,12 +65,7 @@ std::string formatDocumentation(const CodeCompletionString &CCS,
/// Gets detail to be used as the detail field in an LSP completion item. This
/// is usually the return type of a function.
-std::string getDetail(const CodeCompletionString &CCS);
-
-/// Gets the piece of text that the user is expected to type to match the
-/// code-completion string, typically a keyword or the name of a declarator or
-/// macro.
-std::string getFilterText(const CodeCompletionString &CCS);
+std::string getReturnType(const CodeCompletionString &CCS);
} // namespace clangd
} // namespace clang
diff --git a/clangd/CompileArgsCache.cpp b/clangd/CompileArgsCache.cpp
deleted file mode 100644
index 42167e56..00000000
--- a/clangd/CompileArgsCache.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-//===--- CompileArgsCache.cpp --------------------------------------------===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===---------------------------------------------------------------------===//
-
-#include "CompileArgsCache.h"
-
-namespace clang {
-namespace clangd {
-namespace {
-tooling::CompileCommand getCompileCommand(GlobalCompilationDatabase &CDB,
- PathRef File, PathRef ResourceDir) {
- llvm::Optional<tooling::CompileCommand> C = CDB.getCompileCommand(File);
- if (!C) // FIXME: Suppress diagnostics? Let the user know?
- C = CDB.getFallbackCommand(File);
-
- // Inject the resource dir.
- // FIXME: Don't overwrite it if it's already there.
- C->CommandLine.push_back("-resource-dir=" + ResourceDir.str());
- return std::move(*C);
-}
-} // namespace
-
-CompileArgsCache::CompileArgsCache(GlobalCompilationDatabase &CDB,
- Path ResourceDir)
- : CDB(CDB), ResourceDir(std::move(ResourceDir)) {}
-
-tooling::CompileCommand CompileArgsCache::getCompileCommand(PathRef File) {
- auto It = Cached.find(File);
- if (It == Cached.end()) {
- It = Cached.insert({File, clangd::getCompileCommand(CDB, File, ResourceDir)})
- .first;
- }
- return It->second;
-}
-
-void CompileArgsCache::invalidate(PathRef File) { Cached.erase(File); }
-
-} // namespace clangd
-} // namespace clang
diff --git a/clangd/CompileArgsCache.h b/clangd/CompileArgsCache.h
deleted file mode 100644
index 49d88758..00000000
--- a/clangd/CompileArgsCache.h
+++ /dev/null
@@ -1,43 +0,0 @@
-//===--- CompileArgsCache.h -------------------------------------*- C++-*-===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===---------------------------------------------------------------------===//
-
-#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILEARGSCACHE_H
-#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILEARGSCACHE_H
-
-#include "GlobalCompilationDatabase.h"
-#include "Path.h"
-#include "clang/Tooling/CompilationDatabase.h"
-
-namespace clang {
-namespace clangd {
-
-/// A helper class used by ClangdServer to get compile commands from CDB.
-/// Also caches CompileCommands produced by compilation database on per-file
-/// basis. This avoids queries to CDB that can be much more expensive than a
-/// table lookup.
-class CompileArgsCache {
-public:
- CompileArgsCache(GlobalCompilationDatabase &CDB, Path ResourceDir);
-
- /// Gets compile command for \p File from cache or CDB if it's not in the
- /// cache.
- tooling::CompileCommand getCompileCommand(PathRef File);
-
- /// Removes a cache entry for \p File, if it's present in the cache.
- void invalidate(PathRef File);
-
-private:
- GlobalCompilationDatabase &CDB;
- const Path ResourceDir;
- llvm::StringMap<tooling::CompileCommand> Cached;
-};
-
-} // namespace clangd
-} // namespace clang
-#endif
diff --git a/clangd/Compiler.cpp b/clangd/Compiler.cpp
index 80d5c5ac..8fcc9a97 100644
--- a/clangd/Compiler.cpp
+++ b/clangd/Compiler.cpp
@@ -31,7 +31,7 @@ void IgnoreDiagnostics::log(DiagnosticsEngine::Level DiagLevel,
OS << ":";
}
- clangd::log(llvm::formatv("Ignored diagnostic. {0}{1}", Location, Message));
+ clangd::log("Ignored diagnostic. {0}{1}", Location, Message);
}
void IgnoreDiagnostics::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
diff --git a/clangd/Context.h b/clangd/Context.h
index 486b1235..7e14f86c 100644
--- a/clangd/Context.h
+++ b/clangd/Context.h
@@ -43,7 +43,7 @@ public:
static_assert(!std::is_reference<Type>::value,
"Reference arguments to Key<> are not allowed");
- Key() = default;
+ constexpr Key() = default;
Key(Key const &) = delete;
Key &operator=(Key const &) = delete;
diff --git a/clangd/Diagnostics.cpp b/clangd/Diagnostics.cpp
index a52891bb..3c293dde 100644
--- a/clangd/Diagnostics.cpp
+++ b/clangd/Diagnostics.cpp
@@ -70,15 +70,6 @@ Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) {
return halfOpenToRange(M, R);
}
-TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M,
- const LangOptions &L) {
- TextEdit Result;
- Result.range =
- halfOpenToRange(M, Lexer::makeFileCharRange(FixIt.RemoveRange, M, L));
- Result.newText = FixIt.CodeToInsert;
- return Result;
-}
-
bool isInsideMainFile(const SourceLocation Loc, const SourceManager &M) {
return Loc.isValid() && M.isInMainFile(Loc);
}
@@ -145,6 +136,13 @@ void printDiag(llvm::raw_string_ostream &OS, const DiagBase &D) {
OS << diagLeveltoString(D.Severity) << ": " << D.Message;
}
+/// Capitalizes the first word in the diagnostic's message.
+std::string capitalize(std::string Message) {
+ if (!Message.empty())
+ Message[0] = llvm::toUpper(Message[0]);
+ return Message;
+}
+
/// Returns a message sent to LSP for the main diagnostic in \p D.
/// The message includes all the notes with their corresponding locations.
/// However, notes with fix-its are excluded as those usually only contain a
@@ -166,7 +164,7 @@ std::string mainMessage(const Diag &D) {
printDiag(OS, Note);
}
OS.flush();
- return Result;
+ return capitalize(std::move(Result));
}
/// Returns a message sent to LSP for the note of the main diagnostic.
@@ -179,7 +177,7 @@ std::string noteMessage(const Diag &Main, const DiagBase &Note) {
OS << "\n\n";
printDiag(OS, Main);
OS.flush();
- return Result;
+ return capitalize(std::move(Result));
}
} // namespace
@@ -375,7 +373,7 @@ void StoreDiags::flushLastDiag() {
if (mentionsMainFile(*LastDiag))
Output.push_back(std::move(*LastDiag));
else
- log(Twine("Dropped diagnostic outside main file:") + LastDiag->File + ":" +
+ log("Dropped diagnostic outside main file: {0}: {1}", LastDiag->File,
LastDiag->Message);
LastDiag.reset();
}
diff --git a/clangd/FSProvider.h b/clangd/FSProvider.h
new file mode 100644
index 00000000..70dfb441
--- /dev/null
+++ b/clangd/FSProvider.h
@@ -0,0 +1,42 @@
+//===--- FSProvider.h - VFS provider for ClangdServer ------------*- C++-*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FSPROVIDER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FSPROVIDER_H
+
+#include "clang/Basic/VirtualFileSystem.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+
+namespace clang {
+namespace clangd {
+
+// Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
+// As FileSystem is not threadsafe, concurrent threads must each obtain one.
+class FileSystemProvider {
+public:
+ virtual ~FileSystemProvider() = default;
+ /// Called by ClangdServer to obtain a vfs::FileSystem to be used for parsing.
+ /// Context::current() will be the context passed to the clang entrypoint,
+ /// such as addDocument(), and will also be propagated to result callbacks.
+ /// Embedders may use this to isolate filesystem accesses.
+ virtual IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() = 0;
+};
+
+class RealFileSystemProvider : public FileSystemProvider {
+public:
+ // FIXME: returns the single real FS instance, which is not threadsafe.
+ IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override {
+ return vfs::getRealFileSystem();
+ }
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
diff --git a/clangd/FileDistance.cpp b/clangd/FileDistance.cpp
new file mode 100644
index 00000000..a0ce25b4
--- /dev/null
+++ b/clangd/FileDistance.cpp
@@ -0,0 +1,171 @@
+//===--- FileDistance.cpp - File contents container -------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// The FileDistance structure allows calculating the minimum distance to paths
+// in a single tree.
+// We simply walk up the path's ancestors until we find a node whose cost is
+// known, and add the cost of walking back down. Initialization ensures this
+// gives the correct path to the roots.
+// We cache the results, so that the runtime is O(|A|), where A is the set of
+// all distinct ancestors of visited paths.
+//
+// Example after initialization with /=2, /bar=0, DownCost = 1:
+// / = 2
+// /bar = 0
+//
+// After querying /foo/bar and /bar/foo:
+// / = 2
+// /bar = 0
+// /bar/foo = 1
+// /foo = 3
+// /foo/bar = 4
+//
+// URIDistance creates FileDistance lazily for each URI scheme encountered. In
+// practice this is a small constant factor.
+//
+//===-------------------------------------------------------------------------//
+
+#include "FileDistance.h"
+#include "Logger.h"
+#include "llvm/ADT/STLExtras.h"
+#include <queue>
+
+namespace clang {
+namespace clangd {
+using namespace llvm;
+
+// Convert a path into the canonical form.
+// Canonical form is either "/", or "/segment" * N:
+// C:\foo\bar --> /c:/foo/bar
+// /foo/ --> /foo
+// a/b/c --> /a/b/c
+static SmallString<128> canonicalize(StringRef Path) {
+ SmallString<128> Result = Path.rtrim('/');
+ native(Result, sys::path::Style::posix);
+ if (Result.empty() || Result.front() != '/')
+ Result.insert(Result.begin(), '/');
+ return Result;
+}
+
+constexpr const unsigned FileDistance::kUnreachable;
+
+FileDistance::FileDistance(StringMap<SourceParams> Sources,
+ const FileDistanceOptions &Opts)
+ : Opts(Opts) {
+ llvm::DenseMap<hash_code, SmallVector<hash_code, 4>> DownEdges;
+ // Compute the best distance following only up edges.
+ // Keep track of down edges, in case we can use them to improve on this.
+ for (const auto &S : Sources) {
+ auto Canonical = canonicalize(S.getKey());
+ dlog("Source {0} = {1}, MaxUp = {2}", Canonical, S.second.Cost,
+ S.second.MaxUpTraversals);
+ // Walk up to ancestors of this source, assigning cost.
+ StringRef Rest = Canonical;
+ llvm::hash_code Hash = hash_value(Rest);
+ for (unsigned I = 0; !Rest.empty(); ++I) {
+ Rest = parent_path(Rest, sys::path::Style::posix);
+ auto NextHash = hash_value(Rest);
+ auto &Down = DownEdges[NextHash];
+ if (std::find(Down.begin(), Down.end(), Hash) == Down.end())
+ DownEdges[NextHash].push_back(Hash);
+ // We can't just break after MaxUpTraversals, must still set DownEdges.
+ if (I > S.getValue().MaxUpTraversals) {
+ if (Cache.find(Hash) != Cache.end())
+ break;
+ } else {
+ unsigned Cost = S.getValue().Cost + I * Opts.UpCost;
+ auto R = Cache.try_emplace(Hash, Cost);
+ if (!R.second) {
+ if (Cost < R.first->second) {
+ R.first->second = Cost;
+ } else {
+ // If we're not the best way to get to this path, stop assigning.
+ break;
+ }
+ }
+ }
+ Hash = NextHash;
+ }
+ }
+ // Now propagate scores parent -> child if that's an improvement.
+ // BFS ensures we propagate down chains (must visit parents before children).
+ std::queue<hash_code> Next;
+ for (auto Child : DownEdges.lookup(hash_value(llvm::StringRef(""))))
+ Next.push(Child);
+ while (!Next.empty()) {
+ auto ParentCost = Cache.lookup(Next.front());
+ for (auto Child : DownEdges.lookup(Next.front())) {
+ auto &ChildCost =
+ Cache.try_emplace(Child, kUnreachable).first->getSecond();
+ if (ParentCost + Opts.DownCost < ChildCost)
+ ChildCost = ParentCost + Opts.DownCost;
+ Next.push(Child);
+ }
+ Next.pop();
+ }
+}
+
+unsigned FileDistance::distance(StringRef Path) {
+ auto Canonical = canonicalize(Path);
+ unsigned Cost = kUnreachable;
+ SmallVector<hash_code, 16> Ancestors;
+ // Walk up ancestors until we find a path we know the distance for.
+ for (StringRef Rest = Canonical; !Rest.empty();
+ Rest = parent_path(Rest, sys::path::Style::posix)) {
+ auto Hash = hash_value(Rest);
+ auto It = Cache.find(Hash);
+ if (It != Cache.end()) {
+ Cost = It->second;
+ break;
+ }
+ Ancestors.push_back(Hash);
+ }
+ // Now we know the costs for (known node, queried node].
+ // Fill these in, walking down the directory tree.
+ for (hash_code Hash : reverse(Ancestors)) {
+ if (Cost != kUnreachable)
+ Cost += Opts.DownCost;
+ Cache.try_emplace(Hash, Cost);
+ }
+ dlog("distance({0} = {1})", Path, Cost);
+ return Cost;
+}
+
+unsigned URIDistance::distance(llvm::StringRef URI) {
+ auto R = Cache.try_emplace(llvm::hash_value(URI), FileDistance::kUnreachable);
+ if (!R.second)
+ return R.first->getSecond();
+ if (auto U = clangd::URI::parse(URI)) {
+ dlog("distance({0} = {1})", URI, U->body());
+ R.first->second = forScheme(U->scheme()).distance(U->body());
+ } else {
+ log("URIDistance::distance() of unparseable {0}: {1}", URI, U.takeError());
+ }
+ return R.first->second;
+}
+
+FileDistance &URIDistance::forScheme(llvm::StringRef Scheme) {
+ auto &Delegate = ByScheme[Scheme];
+ if (!Delegate) {
+ llvm::StringMap<SourceParams> SchemeSources;
+ for (const auto &Source : Sources) {
+ if (auto U = clangd::URI::create(Source.getKey(), Scheme))
+ SchemeSources.try_emplace(U->body(), Source.getValue());
+ else
+ consumeError(U.takeError());
+ }
+ dlog("FileDistance for scheme {0}: {1}/{2} sources", Scheme,
+ SchemeSources.size(), Sources.size());
+ Delegate.reset(new FileDistance(std::move(SchemeSources), Opts));
+ }
+ return *Delegate;
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clangd/FileDistance.h b/clangd/FileDistance.h
new file mode 100644
index 00000000..f07804fc
--- /dev/null
+++ b/clangd/FileDistance.h
@@ -0,0 +1,109 @@
+//===--- FileDistance.h - File proximity scoring -----------------*- C++-*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This library measures the distance between file paths.
+// It's used for ranking symbols, e.g. in code completion.
+// |foo/bar.h -> foo/bar.h| = 0.
+// |foo/bar.h -> foo/baz.h| < |foo/bar.h -> baz.h|.
+// This is an edit-distance, where edits go up or down the directory tree.
+// It's not symmetrical, the costs of going up and down may not match.
+//
+// Dealing with multiple sources:
+// In practice we care about the distance from a source file, but files near
+// its main-header and #included files are considered "close".
+// So we start with a set of (anchor, cost) pairs, and call the distance to a
+// path the minimum of `cost + |source -> path|`.
+//
+// We allow each source to limit the number of up-traversals paths may start
+// with. Up-traversals may reach things that are not "semantically near".
+//
+// Symbol URI schemes:
+// Symbol locations may be represented by URIs rather than file paths directly.
+// In this case we want to perform distance computations in URI space rather
+// than in file-space, without performing redundant conversions.
+// Therefore we have a lookup structure that accepts URIs, so that intermediate
+// calculations for the same scheme can be reused.
+//
+// Caveats:
+// Assuming up and down traversals each have uniform costs is simplistic.
+// Often there are "semantic roots" whose children are almost unrelated.
+// (e.g. /usr/include/, or / in an umbrella repository). We ignore this.
+//
+//===----------------------------------------------------------------------===//
+
+#include "URI.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseMapInfo.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/StringSaver.h"
+
+namespace clang {
+namespace clangd {
+
+struct FileDistanceOptions {
+ unsigned UpCost = 2; // |foo/bar.h -> foo|
+ unsigned DownCost = 1; // |foo -> foo/bar.h|
+ unsigned IncludeCost = 2; // |foo.cc -> included_header.h|
+};
+
+struct SourceParams {
+ // Base cost for paths starting at this source.
+ unsigned Cost = 0;
+ // Limits the number of upwards traversals allowed from this source.
+ unsigned MaxUpTraversals = std::numeric_limits<unsigned>::max();
+};
+
+// Supports lookups to find the minimum distance to a file from any source.
+// This object should be reused, it memoizes intermediate computations.
+class FileDistance {
+public:
+ static constexpr unsigned kUnreachable = std::numeric_limits<unsigned>::max();
+
+ FileDistance(llvm::StringMap<SourceParams> Sources,
+ const FileDistanceOptions &Opts = {});
+
+ // Computes the minimum distance from any source to the file path.
+ unsigned distance(llvm::StringRef Path);
+
+private:
+ // Costs computed so far. Always contains sources and their ancestors.
+ // We store hash codes only. Collisions are rare and consequences aren't dire.
+ llvm::DenseMap<llvm::hash_code, unsigned> Cache;
+ FileDistanceOptions Opts;
+};
+
+// Supports lookups like FileDistance, but the lookup keys are URIs.
+// We convert each of the sources to the scheme of the URI and do a FileDistance
+// comparison on the bodies.
+class URIDistance {
+public:
+ URIDistance(llvm::StringMap<SourceParams> Sources,
+ const FileDistanceOptions &Opts = {})
+ : Sources(Sources), Opts(Opts) {}
+
+ // Computes the minimum distance from any source to the URI.
+ // Only sources that can be mapped into the URI's scheme are considered.
+ unsigned distance(llvm::StringRef URI);
+
+private:
+ // Returns the FileDistance for a URI scheme, creating it if needed.
+ FileDistance &forScheme(llvm::StringRef Scheme);
+
+ // We cache the results using the original strings so we can skip URI parsing.
+ llvm::DenseMap<llvm::hash_code, unsigned> Cache;
+ llvm::StringMap<SourceParams> Sources;
+ llvm::StringMap<std::unique_ptr<FileDistance>> ByScheme;
+ FileDistanceOptions Opts;
+};
+
+} // namespace clangd
+} // namespace clang
diff --git a/clangd/FindSymbols.cpp b/clangd/FindSymbols.cpp
index f2e7dd25..c1ee6789 100644
--- a/clangd/FindSymbols.cpp
+++ b/clangd/FindSymbols.cpp
@@ -8,13 +8,21 @@
//===----------------------------------------------------------------------===//
#include "FindSymbols.h"
+#include "AST.h"
+#include "ClangdUnit.h"
+#include "FuzzyMatch.h"
#include "Logger.h"
+#include "Quality.h"
#include "SourceCode.h"
#include "index/Index.h"
+#include "clang/Index/IndexDataConsumer.h"
#include "clang/Index/IndexSymbol.h"
+#include "clang/Index/IndexingAction.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
+#define DEBUG_TYPE "FindSymbols"
+
namespace clang {
namespace clangd {
@@ -79,11 +87,20 @@ SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
llvm_unreachable("invalid symbol kind");
}
+using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
+struct ScoredSymbolGreater {
+ bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) {
+ if (L.first != R.first)
+ return L.first > R.first;
+ return L.second.name < R.second.name; // Earlier name is better.
+ }
+};
+
} // namespace
llvm::Expected<std::vector<SymbolInformation>>
-getWorkspaceSymbols(StringRef Query, int Limit,
- const SymbolIndex *const Index) {
+getWorkspaceSymbols(StringRef Query, int Limit, const SymbolIndex *const Index,
+ StringRef HintPath) {
std::vector<SymbolInformation> Result;
if (Query.empty() || !Index)
return Result;
@@ -101,23 +118,22 @@ getWorkspaceSymbols(StringRef Query, int Limit,
Req.Scopes = {Names.first};
if (Limit)
Req.MaxCandidateCount = Limit;
- Index->fuzzyFind(Req, [&Result](const Symbol &Sym) {
+ TopN<ScoredSymbolInfo, ScoredSymbolGreater> Top(Req.MaxCandidateCount);
+ FuzzyMatcher Filter(Req.Query);
+ Index->fuzzyFind(Req, [HintPath, &Top, &Filter](const Symbol &Sym) {
// Prefer the definition over e.g. a function declaration in a header
auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration;
auto Uri = URI::parse(CD.FileURI);
if (!Uri) {
- log(llvm::formatv(
- "Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.",
- CD.FileURI, Sym.Name));
+ log("Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.",
+ CD.FileURI, Sym.Name);
return;
}
- // FIXME: Passing no HintPath here will work for "file" and "test" schemes
- // because they don't use it but this might not work for other custom ones.
- auto Path = URI::resolve(*Uri);
+ auto Path = URI::resolve(*Uri, HintPath);
if (!Path) {
- log(llvm::formatv("Workspace symbol: Could not resolve path for URI "
- "'{0}' for symbol '{1}'.",
- (*Uri).toString(), Sym.Name.str()));
+ log("Workspace symbol: Could not resolve path for URI '{0}' for symbol "
+ "'{1}'.",
+ Uri->toString(), Sym.Name);
return;
}
Location L;
@@ -132,10 +148,132 @@ getWorkspaceSymbols(StringRef Query, int Limit,
std::string Scope = Sym.Scope;
StringRef ScopeRef = Scope;
ScopeRef.consume_back("::");
- Result.push_back({Sym.Name, SK, L, ScopeRef});
+ SymbolInformation Info = {Sym.Name, SK, L, ScopeRef};
+
+ SymbolQualitySignals Quality;
+ Quality.merge(Sym);
+ SymbolRelevanceSignals Relevance;
+ Relevance.Query = SymbolRelevanceSignals::Generic;
+ if (auto NameMatch = Filter.match(Sym.Name))
+ Relevance.NameMatch = *NameMatch;
+ else {
+ log("Workspace symbol: {0} didn't match query {1}", Sym.Name,
+ Filter.pattern());
+ return;
+ }
+ Relevance.merge(Sym);
+ auto Score =
+ evaluateSymbolAndRelevance(Quality.evaluate(), Relevance.evaluate());
+ dlog("FindSymbols: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score,
+ Quality, Relevance);
+
+ Top.push({Score, std::move(Info)});
});
+ for (auto &R : std::move(Top).items())
+ Result.push_back(std::move(R.second));
return Result;
}
+namespace {
+/// Finds document symbols in the main file of the AST.
+class DocumentSymbolsConsumer : public index::IndexDataConsumer {
+ ASTContext &AST;
+ std::vector<SymbolInformation> Symbols;
+ // We are always list document for the same file, so cache the value.
+ llvm::Optional<URIForFile> MainFileUri;
+
+public:
+ DocumentSymbolsConsumer(ASTContext &AST) : AST(AST) {}
+ std::vector<SymbolInformation> takeSymbols() { return std::move(Symbols); }
+
+ void initialize(ASTContext &Ctx) override {
+ // Compute the absolute path of the main file which we will use for all
+ // results.
+ const SourceManager &SM = AST.getSourceManager();
+ const FileEntry *F = SM.getFileEntryForID(SM.getMainFileID());
+ if (!F)
+ return;
+ auto FilePath = getAbsoluteFilePath(F, SM);
+ if (FilePath)
+ MainFileUri = URIForFile(*FilePath);
+ }
+
+ bool shouldIncludeSymbol(const NamedDecl *ND) {
+ if (!ND || ND->isImplicit())
+ return false;
+ // Skip anonymous declarations, e.g (anonymous enum/class/struct).
+ if (ND->getDeclName().isEmpty())
+ return false;
+ return true;
+ }
+
+ bool
+ handleDeclOccurence(const Decl *, index::SymbolRoleSet Roles,
+ ArrayRef<index::SymbolRelation> Relations,
+ SourceLocation Loc,
+ index::IndexDataConsumer::ASTNodeInfo ASTNode) override {
+ assert(ASTNode.OrigD);
+ // No point in continuing the index consumer if we could not get the
+ // absolute path of the main file.
+ if (!MainFileUri)
+ return false;
+ // We only want declarations and definitions, i.e. no references.
+ if (!(Roles & static_cast<unsigned>(index::SymbolRole::Declaration) ||
+ Roles & static_cast<unsigned>(index::SymbolRole::Definition)))
+ return true;
+ SourceLocation NameLoc = findNameLoc(ASTNode.OrigD);
+ const SourceManager &SourceMgr = AST.getSourceManager();
+ // We should be only be looking at "local" decls in the main file.
+ if (!SourceMgr.isWrittenInMainFile(NameLoc)) {
+ // Even thought we are visiting only local (non-preamble) decls,
+ // we can get here when in the presense of "extern" decls.
+ return true;
+ }
+ const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(ASTNode.OrigD);
+ if (!shouldIncludeSymbol(ND))
+ return true;
+
+ SourceLocation EndLoc =
+ Lexer::getLocForEndOfToken(NameLoc, 0, SourceMgr, AST.getLangOpts());
+ Position Begin = sourceLocToPosition(SourceMgr, NameLoc);
+ Position End = sourceLocToPosition(SourceMgr, EndLoc);
+ Range R = {Begin, End};
+ Location L;
+ L.uri = *MainFileUri;
+ L.range = R;
+
+ std::string QName = printQualifiedName(*ND);
+ StringRef Scope, Name;
+ std::tie(Scope, Name) = splitQualifiedName(QName);
+ Scope.consume_back("::");
+
+ index::SymbolInfo SymInfo = index::getSymbolInfo(ND);
+ SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind);
+
+ SymbolInformation SI;
+ SI.name = Name;
+ SI.kind = SK;
+ SI.location = L;
+ SI.containerName = Scope;
+ Symbols.push_back(std::move(SI));
+ return true;
+ }
+};
+} // namespace
+
+llvm::Expected<std::vector<SymbolInformation>>
+getDocumentSymbols(ParsedAST &AST) {
+ DocumentSymbolsConsumer DocumentSymbolsCons(AST.getASTContext());
+
+ index::IndexingOptions IndexOpts;
+ IndexOpts.SystemSymbolFilter =
+ index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly;
+ IndexOpts.IndexFunctionLocals = false;
+ indexTopLevelDecls(AST.getASTContext(), AST.getLocalTopLevelDecls(),
+ DocumentSymbolsCons, IndexOpts);
+
+ return DocumentSymbolsCons.takeSymbols();
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/FindSymbols.h b/clangd/FindSymbols.h
index 11891756..4e6e75a9 100644
--- a/clangd/FindSymbols.h
+++ b/clangd/FindSymbols.h
@@ -18,6 +18,7 @@
namespace clang {
namespace clangd {
+class ParsedAST;
class SymbolIndex;
/// Searches for the symbols matching \p Query. The syntax of \p Query can be
@@ -27,9 +28,16 @@ class SymbolIndex;
/// "::". For example, "std::" will list all children of the std namespace and
/// "::" alone will list all children of the global namespace.
/// \p Limit limits the number of results returned (0 means no limit).
+/// \p HintPath This is used when resolving URIs. If empty, URI resolution can
+/// fail if a hint path is required for the scheme of a specific URI.
llvm::Expected<std::vector<SymbolInformation>>
getWorkspaceSymbols(llvm::StringRef Query, int Limit,
- const SymbolIndex *const Index);
+ const SymbolIndex *const Index, llvm::StringRef HintPath);
+
+/// Retrieves the symbols contained in the "main file" section of an AST in the
+/// same order that they appear.
+llvm::Expected<std::vector<SymbolInformation>>
+getDocumentSymbols(ParsedAST &AST);
} // namespace clangd
} // namespace clang
diff --git a/clangd/Function.h b/clangd/Function.h
index 975134b5..88a5bc00 100644
--- a/clangd/Function.h
+++ b/clangd/Function.h
@@ -7,83 +7,25 @@
//
//===----------------------------------------------------------------------===//
//
-// This file provides an analogue to std::function that supports move semantics.
+// This file provides utilities for callable objects.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FUNCTION_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FUNCTION_H
-#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/FunctionExtras.h"
#include "llvm/Support/Error.h"
-#include <cassert>
-#include <memory>
#include <tuple>
-#include <type_traits>
#include <utility>
namespace clang {
namespace clangd {
-/// A move-only type-erasing function wrapper. Similar to `std::function`, but
-/// allows to store move-only callables.
-template <class> class UniqueFunction;
/// A Callback<T> is a void function that accepts Expected<T>.
/// This is accepted by ClangdServer functions that logically return T.
-template <typename T> using Callback = UniqueFunction<void(llvm::Expected<T>)>;
-
-template <class Ret, class... Args> class UniqueFunction<Ret(Args...)> {
-public:
- UniqueFunction() = default;
- UniqueFunction(std::nullptr_t) : UniqueFunction(){};
-
- UniqueFunction(UniqueFunction const &) = delete;
- UniqueFunction &operator=(UniqueFunction const &) = delete;
-
- UniqueFunction(UniqueFunction &&) noexcept = default;
- UniqueFunction &operator=(UniqueFunction &&) noexcept = default;
-
- template <class Callable,
- /// A sfinae-check that Callable can be called with Args... and
- class = typename std::enable_if<std::is_convertible<
- decltype(std::declval<Callable>()(std::declval<Args>()...)),
- Ret>::value>::type>
- UniqueFunction(Callable &&Func)
- : CallablePtr(llvm::make_unique<
- FunctionCallImpl<typename std::decay<Callable>::type>>(
- std::forward<Callable>(Func))) {}
-
- explicit operator bool() { return bool(CallablePtr); }
-
- Ret operator()(Args... As) {
- assert(CallablePtr);
- return CallablePtr->Call(std::forward<Args>(As)...);
- }
-
-private:
- class FunctionCallBase {
- public:
- virtual ~FunctionCallBase() = default;
- virtual Ret Call(Args... As) = 0;
- };
-
- template <class Callable>
- class FunctionCallImpl final : public FunctionCallBase {
- static_assert(
- std::is_same<Callable, typename std::decay<Callable>::type>::value,
- "FunctionCallImpl must be instanstiated with std::decay'ed types");
-
- public:
- FunctionCallImpl(Callable Func) : Func(std::move(Func)) {}
-
- Ret Call(Args... As) override { return Func(std::forward<Args>(As)...); }
-
- private:
- Callable Func;
- };
-
- std::unique_ptr<FunctionCallBase> CallablePtr;
-};
+template <typename T>
+using Callback = llvm::unique_function<void(llvm::Expected<T>)>;
/// Stores a callable object (Func) and arguments (Args) and allows to call the
/// callable with provided arguments later using `operator ()`. The arguments
diff --git a/clangd/FuzzyMatch.cpp b/clangd/FuzzyMatch.cpp
index 152f1799..b81020af 100644
--- a/clangd/FuzzyMatch.cpp
+++ b/clangd/FuzzyMatch.cpp
@@ -87,8 +87,8 @@ FuzzyMatcher::FuzzyMatcher(StringRef Pattern)
for (int W = 0; W < P; ++W)
for (Action A : {Miss, Match})
Scores[P][W][A] = {AwfulScore, Miss};
- if (PatN > 0)
- calculateRoles(Pat, PatRole, PatTypeSet, PatN);
+ PatTypeSet =
+ calculateRoles(StringRef(Pat, PatN), makeMutableArrayRef(PatRole, PatN));
}
Optional<float> FuzzyMatcher::match(StringRef Word) {
@@ -101,28 +101,15 @@ Optional<float> FuzzyMatcher::match(StringRef Word) {
Scores[PatN][WordN][Match].Score);
if (isAwful(Best))
return None;
- return ScoreScale * std::min(PerfectBonus * PatN, std::max<int>(0, Best));
+ float Score =
+ ScoreScale * std::min(PerfectBonus * PatN, std::max<int>(0, Best));
+ // If the pattern is as long as the word, we have an exact string match,
+ // since every pattern character must match something.
+ if (WordN == PatN)
+ Score *= 2; // May not be perfect 2 if case differs in a significant way.
+ return Score;
}
-// Segmentation of words and patterns.
-// A name like "fooBar_baz" consists of several parts foo, bar, baz.
-// Aligning segmentation of word and pattern improves the fuzzy-match.
-// For example: [lol] matches "LaughingOutLoud" better than "LionPopulation"
-//
-// First we classify each character into types (uppercase, lowercase, etc).
-// Then we look at the sequence: e.g. [upper, lower] is the start of a segment.
-
-// We only distinguish the types of characters that affect segmentation.
-// It's not obvious how to segment digits, we treat them as lowercase letters.
-// As we don't decode UTF-8, we treat bytes over 127 as lowercase too.
-// This means we require exact (case-sensitive) match.
-enum FuzzyMatcher::CharType : unsigned char {
- Empty = 0, // Before-the-start and after-the-end (and control chars).
- Lower = 1, // Lowercase letters, digits, and non-ASCII bytes.
- Upper = 2, // Uppercase letters.
- Punctuation = 3, // ASCII punctuation (including Space)
-};
-
// We get CharTypes from a lookup table. Each is 2 bits, 4 fit in each byte.
// The top 6 bits of the char select the byte, the bottom 2 select the offset.
// e.g. 'q' = 010100 01 = byte 28 (55), bits 3-2 (01) -> Lower.
@@ -141,17 +128,6 @@ constexpr static uint8_t CharTypes[] = {
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
};
-// Each character's Role is the Head or Tail of a segment, or a Separator.
-// e.g. XMLHttpRequest_Async
-// +--+---+------ +----
-// ^Head ^Tail ^Separator
-enum FuzzyMatcher::CharRole : unsigned char {
- Unknown = 0, // Stray control characters or impossible states.
- Tail = 1, // Part of a word segment, but not the first character.
- Head = 2, // The first character of a word segment.
- Separator = 3, // Punctuation characters that separate word segments.
-};
-
// The Role can be determined from the Type of a character and its neighbors:
//
// Example | Chars | Type | Role
@@ -177,26 +153,28 @@ constexpr static uint8_t CharRoles[] = {
template <typename T> static T packedLookup(const uint8_t *Data, int I) {
return static_cast<T>((Data[I >> 2] >> ((I & 3) * 2)) & 3);
}
-void FuzzyMatcher::calculateRoles(const char *Text, CharRole *Out, int &TypeSet,
- int N) {
- assert(N > 0);
+CharTypeSet calculateRoles(StringRef Text, MutableArrayRef<CharRole> Roles) {
+ assert(Text.size() == Roles.size());
+ if (Text.size() == 0)
+ return 0;
CharType Type = packedLookup<CharType>(CharTypes, Text[0]);
- TypeSet = 1 << Type;
+ CharTypeSet TypeSet = 1 << Type;
// Types holds a sliding window of (Prev, Curr, Next) types.
// Initial value is (Empty, Empty, type of Text[0]).
int Types = Type;
// Rotate slides in the type of the next character.
auto Rotate = [&](CharType T) { Types = ((Types << 2) | T) & 0x3f; };
- for (int I = 0; I < N - 1; ++I) {
+ for (unsigned I = 0; I < Text.size() - 1; ++I) {
// For each character, rotate in the next, and look up the role.
Type = packedLookup<CharType>(CharTypes, Text[I + 1]);
TypeSet |= 1 << Type;
Rotate(Type);
- *Out++ = packedLookup<CharRole>(CharRoles, Types);
+ Roles[I] = packedLookup<CharRole>(CharRoles, Types);
}
// For the last character, the "next character" is Empty.
Rotate(Empty);
- *Out++ = packedLookup<CharRole>(CharRoles, Types);
+ Roles[Text.size() - 1] = packedLookup<CharRole>(CharRoles, Types);
+ return TypeSet;
}
// Sets up the data structures matching Word.
@@ -222,7 +200,8 @@ bool FuzzyMatcher::init(StringRef NewWord) {
// FIXME: some words are hard to tokenize algorithmically.
// e.g. vsprintf is V S Print F, and should match [pri] but not [int].
// We could add a tokenization dictionary for common stdlib names.
- calculateRoles(Word, WordRole, WordTypeSet, WordN);
+ WordTypeSet = calculateRoles(StringRef(Word, WordN),
+ makeMutableArrayRef(WordRole, WordN));
return true;
}
@@ -255,33 +234,37 @@ void FuzzyMatcher::buildGraph() {
? ScoreInfo{MatchMissScore, Match}
: ScoreInfo{MissMissScore, Miss};
- if (!allowMatch(P, W)) {
- Score[Match] = {AwfulScore, Miss};
- } else {
- auto &PreMatch = Scores[P][W];
- auto MatchMatchScore = PreMatch[Match].Score + matchBonus(P, W, Match);
- auto MissMatchScore = PreMatch[Miss].Score + matchBonus(P, W, Miss);
- Score[Match] = (MatchMatchScore > MissMatchScore)
- ? ScoreInfo{MatchMatchScore, Match}
- : ScoreInfo{MissMatchScore, Miss};
- }
+ auto &PreMatch = Scores[P][W];
+ auto MatchMatchScore =
+ allowMatch(P, W, Match)
+ ? PreMatch[Match].Score + matchBonus(P, W, Match)
+ : AwfulScore;
+ auto MissMatchScore = allowMatch(P, W, Miss)
+ ? PreMatch[Miss].Score + matchBonus(P, W, Miss)
+ : AwfulScore;
+ Score[Match] = (MatchMatchScore > MissMatchScore)
+ ? ScoreInfo{MatchMatchScore, Match}
+ : ScoreInfo{MissMatchScore, Miss};
}
}
}
-bool FuzzyMatcher::allowMatch(int P, int W) const {
+bool FuzzyMatcher::allowMatch(int P, int W, Action Last) const {
if (LowPat[P] != LowWord[W])
return false;
- // We require a "strong" match for the first pattern character only.
- if (P > 0)
- return true;
- // Obvious "strong match" for first char: match against a word head.
- // We're banning matches outright, so conservatively accept some other cases
- // where our segmentation might be wrong:
- // - allow matching B in ABCDef (but not in NDEBUG)
- // - we'd like to accept print in sprintf, but too many false positives
- return WordRole[W] != Tail ||
- (Word[W] != LowWord[W] && WordTypeSet & 1 << Lower);
+ // We require a "strong" match:
+ // - for the first pattern character. [foo] !~ "barefoot"
+ // - after a gap. [pat] !~ "patnther"
+ if (Last == Miss) {
+ // We're banning matches outright, so conservatively accept some other cases
+ // where our segmentation might be wrong:
+ // - allow matching B in ABCDef (but not in NDEBUG)
+ // - we'd like to accept print in sprintf, but too many false positives
+ if (WordRole[W] == Tail &&
+ (Word[W] == LowWord[W] || !(WordTypeSet & 1 << Lower)))
+ return false;
+ }
+ return true;
}
int FuzzyMatcher::skipPenalty(int W, Action Last) const {
diff --git a/clangd/FuzzyMatch.h b/clangd/FuzzyMatch.h
index 13a25e28..f0c7e722 100644
--- a/clangd/FuzzyMatch.h
+++ b/clangd/FuzzyMatch.h
@@ -16,6 +16,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FUZZYMATCH_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FUZZYMATCH_H
+#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
@@ -24,6 +25,48 @@
namespace clang {
namespace clangd {
+// Utilities for word segmentation.
+// FuzzyMatcher already incorporates this logic, so most users don't need this.
+//
+// A name like "fooBar_baz" consists of several parts foo, bar, baz.
+// Aligning segmentation of word and pattern improves the fuzzy-match.
+// For example: [lol] matches "LaughingOutLoud" better than "LionPopulation"
+//
+// First we classify each character into types (uppercase, lowercase, etc).
+// Then we look at the sequence: e.g. [upper, lower] is the start of a segment.
+
+// We distinguish the types of characters that affect segmentation.
+// It's not obvious how to segment digits, we treat them as lowercase letters.
+// As we don't decode UTF-8, we treat bytes over 127 as lowercase too.
+// This means we require exact (case-sensitive) match for those characters.
+enum CharType : unsigned char {
+ Empty = 0, // Before-the-start and after-the-end (and control chars).
+ Lower = 1, // Lowercase letters, digits, and non-ASCII bytes.
+ Upper = 2, // Uppercase letters.
+ Punctuation = 3, // ASCII punctuation (including Space)
+};
+// A CharTypeSet is a bitfield representing all the character types in a word.
+// Its bits are 1<<Empty, 1<<Lower, etc.
+using CharTypeSet = unsigned char;
+
+// Each character's Role is the Head or Tail of a segment, or a Separator.
+// e.g. XMLHttpRequest_Async
+// +--+---+------ +----
+// ^Head ^Tail ^Separator
+enum CharRole : unsigned char {
+ Unknown = 0, // Stray control characters or impossible states.
+ Tail = 1, // Part of a word segment, but not the first character.
+ Head = 2, // The first character of a word segment.
+ Separator = 3, // Punctuation characters that separate word segments.
+};
+
+// Compute segmentation of Text.
+// Character roles are stored in Roles (Roles.size() must equal Text.size()).
+// The set of character types encountered is returned, this may inform
+// heuristics for dealing with poorly-segmented identifiers like "strndup".
+CharTypeSet calculateRoles(llvm::StringRef Text,
+ llvm::MutableArrayRef<CharRole> Roles);
+
// A matcher capable of matching and scoring strings against a single pattern.
// It's optimized for matching against many strings - match() does not allocate.
class FuzzyMatcher {
@@ -31,7 +74,9 @@ public:
// Characters beyond MaxPat are ignored.
FuzzyMatcher(llvm::StringRef Pattern);
- // If Word matches the pattern, return a score in [0,1] (higher is better).
+ // If Word matches the pattern, return a score indicating the quality match.
+ // Scores usually fall in a [0,1] range, with 1 being a very good score.
+ // "Super" scores in (1,2] are possible if the pattern is the full word.
// Characters beyond MaxWord are ignored.
llvm::Optional<float> match(llvm::StringRef Word);
@@ -46,18 +91,17 @@ public:
private:
// We truncate the pattern and the word to bound the cost of matching.
constexpr static int MaxPat = 63, MaxWord = 127;
- enum CharRole : unsigned char; // For segmentation.
- enum CharType : unsigned char; // For segmentation.
- // Action should be an enum, but this causes bitfield problems:
+ // Action describes how a word character was matched to the pattern.
+ // It should be an enum, but this causes bitfield problems:
// - for MSVC the enum type must be explicitly unsigned for correctness
// - GCC 4.8 complains not all values fit if the type is unsigned
using Action = bool;
- constexpr static Action Miss = false, Match = true;
+ constexpr static Action Miss = false; // Word character was skipped.
+ constexpr static Action Match = true; // Matched against a pattern character.
bool init(llvm::StringRef Word);
void buildGraph();
- void calculateRoles(const char *Text, CharRole *Out, int &Types, int N);
- bool allowMatch(int P, int W) const;
+ bool allowMatch(int P, int W, Action Last) const;
int skipPenalty(int W, Action Last) const;
int matchBonus(int P, int W, Action Last) const;
@@ -66,7 +110,7 @@ private:
int PatN; // Length
char LowPat[MaxPat]; // Pattern in lowercase
CharRole PatRole[MaxPat]; // Pattern segmentation info
- int PatTypeSet; // Bitmask of 1<<CharType
+ CharTypeSet PatTypeSet; // Bitmask of 1<<CharType for all Pattern characters
float ScoreScale; // Normalizes scores for the pattern length.
// Word data is initialized on each call to match(), mostly by init().
@@ -74,7 +118,7 @@ private:
int WordN; // Length
char LowWord[MaxWord]; // Word in lowercase
CharRole WordRole[MaxWord]; // Word segmentation info
- int WordTypeSet; // Bitmask of 1<<CharType
+ CharTypeSet WordTypeSet; // Bitmask of 1<<CharType for all Word characters
bool WordContainsPattern; // Simple substring check
// Cumulative best-match score table.
diff --git a/clangd/GlobalCompilationDatabase.cpp b/clangd/GlobalCompilationDatabase.cpp
index ca223f6b..3bfc4eca 100644
--- a/clangd/GlobalCompilationDatabase.cpp
+++ b/clangd/GlobalCompilationDatabase.cpp
@@ -47,7 +47,7 @@ DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const {
return std::move(Candidates.front());
}
} else {
- log("Failed to find compilation database for " + Twine(File));
+ log("Failed to find compilation database for {0}", File);
}
return llvm::None;
}
@@ -119,5 +119,62 @@ DirectoryBasedGlobalCompilationDatabase::getCDBForFile(PathRef File) const {
return nullptr;
}
+CachingCompilationDb::CachingCompilationDb(
+ const GlobalCompilationDatabase &InnerCDB)
+ : InnerCDB(InnerCDB) {}
+
+llvm::Optional<tooling::CompileCommand>
+CachingCompilationDb::getCompileCommand(PathRef File) const {
+ std::unique_lock<std::mutex> Lock(Mut);
+ auto It = Cached.find(File);
+ if (It != Cached.end())
+ return It->second;
+
+ Lock.unlock();
+ llvm::Optional<tooling::CompileCommand> Command =
+ InnerCDB.getCompileCommand(File);
+ Lock.lock();
+ return Cached.try_emplace(File, std::move(Command)).first->getValue();
+}
+
+tooling::CompileCommand
+CachingCompilationDb::getFallbackCommand(PathRef File) const {
+ return InnerCDB.getFallbackCommand(File);
+}
+
+void CachingCompilationDb::invalidate(PathRef File) {
+ std::unique_lock<std::mutex> Lock(Mut);
+ Cached.erase(File);
+}
+
+void CachingCompilationDb::clear() {
+ std::unique_lock<std::mutex> Lock(Mut);
+ Cached.clear();
+}
+
+llvm::Optional<tooling::CompileCommand>
+InMemoryCompilationDb::getCompileCommand(PathRef File) const {
+ std::lock_guard<std::mutex> Lock(Mutex);
+ auto It = Commands.find(File);
+ if (It == Commands.end())
+ return None;
+ return It->second;
+}
+
+bool InMemoryCompilationDb::setCompilationCommandForFile(
+ PathRef File, tooling::CompileCommand CompilationCommand) {
+ std::unique_lock<std::mutex> Lock(Mutex);
+ auto ItInserted = Commands.insert(std::make_pair(File, CompilationCommand));
+ if (ItInserted.second)
+ return true;
+ ItInserted.first->setValue(std::move(CompilationCommand));
+ return false;
+}
+
+void InMemoryCompilationDb::invalidate(PathRef File) {
+ std::unique_lock<std::mutex> Lock(Mutex);
+ Commands.erase(File);
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/GlobalCompilationDatabase.h b/clangd/GlobalCompilationDatabase.h
index cf1e613f..b64028fc 100644
--- a/clangd/GlobalCompilationDatabase.h
+++ b/clangd/GlobalCompilationDatabase.h
@@ -85,6 +85,57 @@ private:
/// is located.
llvm::Optional<Path> CompileCommandsDir;
};
+
+/// A wrapper around GlobalCompilationDatabase that caches the compile commands.
+/// Note that only results of getCompileCommand are cached.
+class CachingCompilationDb : public GlobalCompilationDatabase {
+public:
+ explicit CachingCompilationDb(const GlobalCompilationDatabase &InnerCDB);
+
+ /// Gets compile command for \p File from cache or CDB if it's not in the
+ /// cache.
+ llvm::Optional<tooling::CompileCommand>
+ getCompileCommand(PathRef File) const override;
+
+ /// Forwards to the inner CDB. Results of this function are not cached.
+ tooling::CompileCommand getFallbackCommand(PathRef File) const override;
+
+ /// Removes an entry for \p File if it's present in the cache.
+ void invalidate(PathRef File);
+
+ /// Removes all cached compile commands.
+ void clear();
+
+private:
+ const GlobalCompilationDatabase &InnerCDB;
+ mutable std::mutex Mut;
+ mutable llvm::StringMap<llvm::Optional<tooling::CompileCommand>>
+ Cached; /* GUARDED_BY(Mut) */
+};
+
+/// Gets compile args from an in-memory mapping based on a filepath. Typically
+/// used by clients who provide the compile commands themselves.
+class InMemoryCompilationDb : public GlobalCompilationDatabase {
+public:
+ /// Gets compile command for \p File from the stored mapping.
+ llvm::Optional<tooling::CompileCommand>
+ getCompileCommand(PathRef File) const override;
+
+ /// Sets the compilation command for a particular file.
+ ///
+ /// \returns True if the File had no compilation command before.
+ bool setCompilationCommandForFile(PathRef File,
+ tooling::CompileCommand CompilationCommand);
+
+ /// Removes the compilation command for \p File if it's present in the
+ /// mapping.
+ void invalidate(PathRef File);
+
+private:
+ mutable std::mutex Mutex;
+ llvm::StringMap<tooling::CompileCommand> Commands; /* GUARDED_BY(Mut) */
+};
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/Headers.cpp b/clangd/Headers.cpp
index c6f18fa3..137b29aa 100644
--- a/clangd/Headers.cpp
+++ b/clangd/Headers.cpp
@@ -23,9 +23,8 @@ namespace {
class RecordHeaders : public PPCallbacks {
public:
- RecordHeaders(const SourceManager &SM,
- std::function<void(Inclusion)> Callback)
- : SM(SM), Callback(std::move(Callback)) {}
+ RecordHeaders(const SourceManager &SM, IncludeStructure *Out)
+ : SM(SM), Out(Out) {}
// Record existing #includes - both written and resolved paths. Only #includes
// in the main file are collected.
@@ -36,21 +35,28 @@ public:
llvm::StringRef /*RelativePath*/,
const Module * /*Imported*/,
SrcMgr::CharacteristicKind /*FileType*/) override {
- // Only inclusion directives in the main file make sense. The user cannot
- // select directives not in the main file.
- if (HashLoc.isInvalid() || !SM.isInMainFile(HashLoc))
- return;
- std::string Written =
- (IsAngled ? "<" + FileName + ">" : "\"" + FileName + "\"").str();
- std::string Resolved = (!File || File->tryGetRealPathName().empty())
- ? ""
- : File->tryGetRealPathName();
- Callback({halfOpenToRange(SM, FilenameRange), Written, Resolved});
+ if (SM.isInMainFile(HashLoc))
+ Out->MainFileIncludes.push_back({
+ halfOpenToRange(SM, FilenameRange),
+ (IsAngled ? "<" + FileName + ">" : "\"" + FileName + "\"").str(),
+ File ? File->tryGetRealPathName() : "",
+ });
+ if (File) {
+ auto *IncludingFileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc));
+ if (!IncludingFileEntry) {
+ assert(SM.getBufferName(HashLoc).startswith("<") &&
+ "Expected #include location to be a file or <built-in>");
+ // Treat as if included from the main file.
+ IncludingFileEntry = SM.getFileEntryForID(SM.getMainFileID());
+ }
+ Out->recordInclude(IncludingFileEntry->getName(), File->getName(),
+ File->tryGetRealPathName());
+ }
}
private:
const SourceManager &SM;
- std::function<void(Inclusion)> Callback;
+ IncludeStructure *Out;
};
} // namespace
@@ -65,20 +71,68 @@ bool HeaderFile::valid() const {
}
std::unique_ptr<PPCallbacks>
-collectInclusionsInMainFileCallback(const SourceManager &SM,
- std::function<void(Inclusion)> Callback) {
- return llvm::make_unique<RecordHeaders>(SM, std::move(Callback));
+collectIncludeStructureCallback(const SourceManager &SM,
+ IncludeStructure *Out) {
+ return llvm::make_unique<RecordHeaders>(SM, Out);
+}
+
+void IncludeStructure::recordInclude(llvm::StringRef IncludingName,
+ llvm::StringRef IncludedName,
+ llvm::StringRef IncludedRealName) {
+ auto Child = fileIndex(IncludedName);
+ if (!IncludedRealName.empty() && RealPathNames[Child].empty())
+ RealPathNames[Child] = IncludedRealName;
+ auto Parent = fileIndex(IncludingName);
+ IncludeChildren[Parent].push_back(Child);
+}
+
+unsigned IncludeStructure::fileIndex(llvm::StringRef Name) {
+ auto R = NameToIndex.try_emplace(Name, RealPathNames.size());
+ if (R.second)
+ RealPathNames.emplace_back();
+ return R.first->getValue();
+}
+
+llvm::StringMap<unsigned>
+IncludeStructure::includeDepth(llvm::StringRef Root) const {
+ // Include depth 0 is the main file only.
+ llvm::StringMap<unsigned> Result;
+ Result[Root] = 0;
+ std::vector<unsigned> CurrentLevel;
+ llvm::DenseSet<unsigned> Seen;
+ auto It = NameToIndex.find(Root);
+ if (It != NameToIndex.end()) {
+ CurrentLevel.push_back(It->second);
+ Seen.insert(It->second);
+ }
+
+ // Each round of BFS traversal finds the next depth level.
+ std::vector<unsigned> PreviousLevel;
+ for (unsigned Level = 1; !CurrentLevel.empty(); ++Level) {
+ PreviousLevel.clear();
+ PreviousLevel.swap(CurrentLevel);
+ for (const auto &Parent : PreviousLevel) {
+ for (const auto &Child : IncludeChildren.lookup(Parent)) {
+ if (Seen.insert(Child).second) {
+ CurrentLevel.push_back(Child);
+ const auto &Name = RealPathNames[Child];
+ // Can't include files if we don't have their real path.
+ if (!Name.empty())
+ Result[Name] = Level;
+ }
+ }
+ }
+ }
+ return Result;
}
/// FIXME(ioeric): we might not want to insert an absolute include path if the
/// path is not shortened.
-llvm::Expected<std::string> calculateIncludePath(
- PathRef File, StringRef BuildDir, HeaderSearch &HeaderSearchInfo,
- const std::vector<Inclusion> &Inclusions, const HeaderFile &DeclaringHeader,
- const HeaderFile &InsertedHeader) {
+bool IncludeInserter::shouldInsertInclude(
+ const HeaderFile &DeclaringHeader, const HeaderFile &InsertedHeader) const {
assert(DeclaringHeader.valid() && InsertedHeader.valid());
- if (File == DeclaringHeader.File || File == InsertedHeader.File)
- return "";
+ if (FileName == DeclaringHeader.File || FileName == InsertedHeader.File)
+ return false;
llvm::StringSet<> IncludedHeaders;
for (const auto &Inc : Inclusions) {
IncludedHeaders.insert(Inc.Written);
@@ -88,53 +142,31 @@ llvm::Expected<std::string> calculateIncludePath(
auto Included = [&](llvm::StringRef Header) {
return IncludedHeaders.find(Header) != IncludedHeaders.end();
};
- if (Included(DeclaringHeader.File) || Included(InsertedHeader.File))
- return "";
-
- bool IsSystem = false;
+ return !Included(DeclaringHeader.File) && !Included(InsertedHeader.File);
+}
+std::string
+IncludeInserter::calculateIncludePath(const HeaderFile &DeclaringHeader,
+ const HeaderFile &InsertedHeader) const {
+ assert(DeclaringHeader.valid() && InsertedHeader.valid());
if (InsertedHeader.Verbatim)
return InsertedHeader.File;
-
+ bool IsSystem = false;
std::string Suggested = HeaderSearchInfo.suggestPathToFileForDiagnostics(
InsertedHeader.File, BuildDir, &IsSystem);
if (IsSystem)
Suggested = "<" + Suggested + ">";
else
Suggested = "\"" + Suggested + "\"";
-
- log("Suggested #include for " + InsertedHeader.File + " is: " + Suggested);
return Suggested;
}
-Expected<Optional<TextEdit>>
-IncludeInserter::insert(const HeaderFile &DeclaringHeader,
- const HeaderFile &InsertedHeader) const {
- auto Validate = [](const HeaderFile &Header) {
- return Header.valid()
- ? llvm::Error::success()
- : llvm::make_error<llvm::StringError>(
- "Invalid HeaderFile: " + Header.File +
- " (verbatim=" + std::to_string(Header.Verbatim) + ").",
- llvm::inconvertibleErrorCode());
- };
- if (auto Err = Validate(DeclaringHeader))
- return std::move(Err);
- if (auto Err = Validate(InsertedHeader))
- return std::move(Err);
- auto Include =
- calculateIncludePath(FileName, BuildDir, HeaderSearchInfo, Inclusions,
- DeclaringHeader, InsertedHeader);
- if (!Include)
- return Include.takeError();
- if (Include->empty())
- return llvm::None;
- StringRef IncludeRef = *Include;
- auto Insertion =
- Inserter.insert(IncludeRef.trim("\"<>"), IncludeRef.startswith("<"));
- if (!Insertion)
- return llvm::None;
- return replacementToEdit(Code, *Insertion);
+Optional<TextEdit> IncludeInserter::insert(StringRef VerbatimHeader) const {
+ Optional<TextEdit> Edit = None;
+ if (auto Insertion = Inserter.insert(VerbatimHeader.trim("\"<>"),
+ VerbatimHeader.startswith("<")))
+ Edit = replacementToEdit(Code, *Insertion);
+ return Edit;
}
} // namespace clangd
diff --git a/clangd/Headers.h b/clangd/Headers.h
index 987832fc..2abf27c6 100644
--- a/clangd/Headers.h
+++ b/clangd/Headers.h
@@ -45,29 +45,47 @@ struct Inclusion {
Path Resolved; // Resolved path of included file. Empty if not resolved.
};
+// Information captured about the inclusion graph in a translation unit.
+// This includes detailed information about the direct #includes, and summary
+// information about all transitive includes.
+//
+// It should be built incrementally with collectIncludeStructureCallback().
+// When we build the preamble, we capture and store its include structure along
+// with the preamble data. When we use the preamble, we can copy its
+// IncludeStructure and use another collectIncludeStructureCallback() to fill
+// in any non-preamble inclusions.
+class IncludeStructure {
+public:
+ std::vector<Inclusion> MainFileIncludes;
+
+ // Return all transitively reachable files, and their minimum include depth.
+ // All transitive includes (absolute paths), with their minimum include depth.
+ // Root --> 0, #included file --> 1, etc.
+ // Root is clang's name for a file, which may not be absolute.
+ // Usually it should be SM.getFileEntryForID(SM.getMainFileID())->getName().
+ llvm::StringMap<unsigned> includeDepth(llvm::StringRef Root) const;
+
+ // This updates IncludeDepth(), but not MainFileIncludes.
+ void recordInclude(llvm::StringRef IncludingName,
+ llvm::StringRef IncludedName,
+ llvm::StringRef IncludedRealName);
+
+private:
+ // Identifying files in a way that persists from preamble build to subsequent
+ // builds is surprisingly hard. FileID is unavailable in InclusionDirective(),
+ // and RealPathName and UniqueID are not preseved in the preamble.
+ // We use the FileEntry::Name, which is stable, interned into a "file index".
+ // The paths we want to expose are the RealPathName, so store those too.
+ std::vector<std::string> RealPathNames; // In file index order.
+ unsigned fileIndex(llvm::StringRef Name);
+ llvm::StringMap<unsigned> NameToIndex; // Values are file indexes.
+ // Maps a file's index to that of the files it includes.
+ llvm::DenseMap<unsigned, SmallVector<unsigned, 8>> IncludeChildren;
+};
+
/// Returns a PPCallback that visits all inclusions in the main file.
std::unique_ptr<PPCallbacks>
-collectInclusionsInMainFileCallback(const SourceManager &SM,
- std::function<void(Inclusion)> Callback);
-
-/// Determines the preferred way to #include a file, taking into account the
-/// search path. Usually this will prefer a shorter representation like
-/// 'Foo/Bar.h' over a longer one like 'Baz/include/Foo/Bar.h'.
-///
-/// \param File is an absolute file path.
-/// \param Inclusions Existing inclusions in the main file.
-/// \param DeclaringHeader is the original header corresponding to \p
-/// InsertedHeader e.g. the header that declares a symbol.
-/// \param InsertedHeader The preferred header to be inserted. This could be the
-/// same as DeclaringHeader but must be provided.
-// \return A quoted "path" or <path>. This returns an empty string if:
-/// - Either \p DeclaringHeader or \p InsertedHeader is already (directly)
-/// in \p Inclusions (including those included via different paths).
-/// - \p DeclaringHeader or \p InsertedHeader is the same as \p File.
-llvm::Expected<std::string> calculateIncludePath(
- PathRef File, StringRef BuildDir, HeaderSearch &HeaderSearchInfo,
- const std::vector<Inclusion> &Inclusions, const HeaderFile &DeclaringHeader,
- const HeaderFile &InsertedHeader);
+collectIncludeStructureCallback(const SourceManager &SM, IncludeStructure *Out);
// Calculates insertion edit for including a new header in a file.
class IncludeInserter {
@@ -81,16 +99,35 @@ public:
void addExisting(Inclusion Inc) { Inclusions.push_back(std::move(Inc)); }
- /// Returns a TextEdit that inserts a new header; if the header is not
- /// inserted e.g. it's an existing header, this returns None. If any header is
- /// invalid, this returns error.
+ /// Checks whether to add an #include of the header into \p File.
+ /// An #include will not be added if:
+ /// - Either \p DeclaringHeader or \p InsertedHeader is already (directly)
+ /// in \p Inclusions (including those included via different paths).
+ /// - \p DeclaringHeader or \p InsertedHeader is the same as \p File.
///
/// \param DeclaringHeader is the original header corresponding to \p
/// InsertedHeader e.g. the header that declares a symbol.
/// \param InsertedHeader The preferred header to be inserted. This could be
/// the same as DeclaringHeader but must be provided.
- Expected<Optional<TextEdit>> insert(const HeaderFile &DeclaringHeader,
- const HeaderFile &InsertedHeader) const;
+ bool shouldInsertInclude(const HeaderFile &DeclaringHeader,
+ const HeaderFile &InsertedHeader) const;
+
+ /// Determines the preferred way to #include a file, taking into account the
+ /// search path. Usually this will prefer a shorter representation like
+ /// 'Foo/Bar.h' over a longer one like 'Baz/include/Foo/Bar.h'.
+ ///
+ /// \param DeclaringHeader is the original header corresponding to \p
+ /// InsertedHeader e.g. the header that declares a symbol.
+ /// \param InsertedHeader The preferred header to be inserted. This could be
+ /// the same as DeclaringHeader but must be provided.
+ ///
+ /// \return A quoted "path" or <path> to be included.
+ std::string calculateIncludePath(const HeaderFile &DeclaringHeader,
+ const HeaderFile &InsertedHeader) const;
+
+ /// Calculates an edit that inserts \p VerbatimHeader into code. If the header
+ /// is already included, this returns None.
+ llvm::Optional<TextEdit> insert(llvm::StringRef VerbatimHeader) const;
private:
StringRef FileName;
diff --git a/clangd/JSONExpr.cpp b/clangd/JSONExpr.cpp
deleted file mode 100644
index 3f87c286..00000000
--- a/clangd/JSONExpr.cpp
+++ /dev/null
@@ -1,554 +0,0 @@
-//=== JSONExpr.cpp - JSON expressions, parsing and serialization - C++ -*-===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===---------------------------------------------------------------------===//
-
-#include "JSONExpr.h"
-#include "llvm/Support/Format.h"
-#include <cctype>
-
-using namespace llvm;
-namespace clang {
-namespace clangd {
-namespace json {
-
-void Expr::copyFrom(const Expr &M) {
- Type = M.Type;
- switch (Type) {
- case T_Null:
- case T_Boolean:
- case T_Number:
- memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer));
- break;
- case T_StringRef:
- create<StringRef>(M.as<StringRef>());
- break;
- case T_String:
- create<std::string>(M.as<std::string>());
- break;
- case T_Object:
- create<ObjectExpr>(M.as<ObjectExpr>());
- break;
- case T_Array:
- create<ArrayExpr>(M.as<ArrayExpr>());
- break;
- }
-}
-
-void Expr::moveFrom(const Expr &&M) {
- Type = M.Type;
- switch (Type) {
- case T_Null:
- case T_Boolean:
- case T_Number:
- memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer));
- break;
- case T_StringRef:
- create<StringRef>(M.as<StringRef>());
- break;
- case T_String:
- create<std::string>(std::move(M.as<std::string>()));
- M.Type = T_Null;
- break;
- case T_Object:
- create<ObjectExpr>(std::move(M.as<ObjectExpr>()));
- M.Type = T_Null;
- break;
- case T_Array:
- create<ArrayExpr>(std::move(M.as<ArrayExpr>()));
- M.Type = T_Null;
- break;
- }
-}
-
-void Expr::destroy() {
- switch (Type) {
- case T_Null:
- case T_Boolean:
- case T_Number:
- break;
- case T_StringRef:
- as<StringRef>().~StringRef();
- break;
- case T_String:
- as<std::string>().~basic_string();
- break;
- case T_Object:
- as<ObjectExpr>().~ObjectExpr();
- break;
- case T_Array:
- as<ArrayExpr>().~ArrayExpr();
- break;
- }
-}
-
-namespace {
-// Simple recursive-descent JSON parser.
-class Parser {
-public:
- Parser(StringRef JSON)
- : Start(JSON.begin()), P(JSON.begin()), End(JSON.end()) {}
-
- bool parseExpr(Expr &Out);
-
- bool assertEnd() {
- eatWhitespace();
- if (P == End)
- return true;
- return parseError("Text after end of document");
- }
-
- Error takeError() {
- assert(Err);
- return std::move(*Err);
- }
-
-private:
- void eatWhitespace() {
- while (P != End && (*P == ' ' || *P == '\r' || *P == '\n' || *P == '\t'))
- ++P;
- }
-
- // On invalid syntax, parseX() functions return false and set Err.
- bool parseNumber(char First, double &Out);
- bool parseString(std::string &Out);
- bool parseUnicode(std::string &Out);
- bool parseError(const char *Msg); // always returns false
-
- char next() { return P == End ? 0 : *P++; }
- char peek() { return P == End ? 0 : *P; }
- static bool isNumber(char C) {
- return C == '0' || C == '1' || C == '2' || C == '3' || C == '4' ||
- C == '5' || C == '6' || C == '7' || C == '8' || C == '9' ||
- C == 'e' || C == 'E' || C == '+' || C == '-' || C == '.';
- }
- static void encodeUtf8(uint32_t Rune, std::string &Out);
-
- Optional<Error> Err;
- const char *Start, *P, *End;
-};
-
-bool Parser::parseExpr(Expr &Out) {
- eatWhitespace();
- if (P == End)
- return parseError("Unexpected EOF");
- switch (char C = next()) {
- // Bare null/true/false are easy - first char identifies them.
- case 'n':
- Out = nullptr;
- return (next() == 'u' && next() == 'l' && next() == 'l') ||
- parseError("Invalid bareword");
- case 't':
- Out = true;
- return (next() == 'r' && next() == 'u' && next() == 'e') ||
- parseError("Invalid bareword");
- case 'f':
- Out = false;
- return (next() == 'a' && next() == 'l' && next() == 's' && next() == 'e') ||
- parseError("Invalid bareword");
- case '"': {
- std::string S;
- if (parseString(S)) {
- Out = std::move(S);
- return true;
- }
- return false;
- }
- case '[': {
- Out = json::ary{};
- json::ary &A = *Out.asArray();
- eatWhitespace();
- if (peek() == ']') {
- ++P;
- return true;
- }
- for (;;) {
- A.emplace_back(nullptr);
- if (!parseExpr(A.back()))
- return false;
- eatWhitespace();
- switch (next()) {
- case ',':
- eatWhitespace();
- continue;
- case ']':
- return true;
- default:
- return parseError("Expected , or ] after array element");
- }
- }
- }
- case '{': {
- Out = json::obj{};
- json::obj &O = *Out.asObject();
- eatWhitespace();
- if (peek() == '}') {
- ++P;
- return true;
- }
- for (;;) {
- if (next() != '"')
- return parseError("Expected object key");
- std::string K;
- if (!parseString(K))
- return false;
- eatWhitespace();
- if (next() != ':')
- return parseError("Expected : after object key");
- eatWhitespace();
- if (!parseExpr(O[std::move(K)]))
- return false;
- eatWhitespace();
- switch (next()) {
- case ',':
- eatWhitespace();
- continue;
- case '}':
- return true;
- default:
- return parseError("Expected , or } after object property");
- }
- }
- }
- default:
- if (isNumber(C)) {
- double Num;
- if (parseNumber(C, Num)) {
- Out = Num;
- return true;
- } else {
- return false;
- }
- }
- return parseError("Expected JSON value");
- }
-}
-
-bool Parser::parseNumber(char First, double &Out) {
- SmallString<24> S;
- S.push_back(First);
- while (isNumber(peek()))
- S.push_back(next());
- char *End;
- Out = std::strtod(S.c_str(), &End);
- return End == S.end() || parseError("Invalid number");
-}
-
-bool Parser::parseString(std::string &Out) {
- // leading quote was already consumed.
- for (char C = next(); C != '"'; C = next()) {
- if (LLVM_UNLIKELY(P == End))
- return parseError("Unterminated string");
- if (LLVM_UNLIKELY((C & 0x1f) == C))
- return parseError("Control character in string");
- if (LLVM_LIKELY(C != '\\')) {
- Out.push_back(C);
- continue;
- }
- // Handle escape sequence.
- switch (C = next()) {
- case '"':
- case '\\':
- case '/':
- Out.push_back(C);
- break;
- case 'b':
- Out.push_back('\b');
- break;
- case 'f':
- Out.push_back('\f');
- break;
- case 'n':
- Out.push_back('\n');
- break;
- case 'r':
- Out.push_back('\r');
- break;
- case 't':
- Out.push_back('\t');
- break;
- case 'u':
- if (!parseUnicode(Out))
- return false;
- break;
- default:
- return parseError("Invalid escape sequence");
- }
- }
- return true;
-}
-
-void Parser::encodeUtf8(uint32_t Rune, std::string &Out) {
- if (Rune <= 0x7F) {
- Out.push_back(Rune & 0x7F);
- } else if (Rune <= 0x7FF) {
- uint8_t FirstByte = 0xC0 | ((Rune & 0x7C0) >> 6);
- uint8_t SecondByte = 0x80 | (Rune & 0x3F);
- Out.push_back(FirstByte);
- Out.push_back(SecondByte);
- } else if (Rune <= 0xFFFF) {
- uint8_t FirstByte = 0xE0 | ((Rune & 0xF000) >> 12);
- uint8_t SecondByte = 0x80 | ((Rune & 0xFC0) >> 6);
- uint8_t ThirdByte = 0x80 | (Rune & 0x3F);
- Out.push_back(FirstByte);
- Out.push_back(SecondByte);
- Out.push_back(ThirdByte);
- } else if (Rune <= 0x10FFFF) {
- uint8_t FirstByte = 0xF0 | ((Rune & 0x1F0000) >> 18);
- uint8_t SecondByte = 0x80 | ((Rune & 0x3F000) >> 12);
- uint8_t ThirdByte = 0x80 | ((Rune & 0xFC0) >> 6);
- uint8_t FourthByte = 0x80 | (Rune & 0x3F);
- Out.push_back(FirstByte);
- Out.push_back(SecondByte);
- Out.push_back(ThirdByte);
- Out.push_back(FourthByte);
- } else {
- llvm_unreachable("Invalid codepoint");
- }
-}
-
-// Parse a \uNNNN escape sequence, the \u have already been consumed.
-// May parse multiple escapes in the presence of surrogate pairs.
-bool Parser::parseUnicode(std::string &Out) {
- // Note that invalid unicode is not a JSON error. It gets replaced by U+FFFD.
- auto Invalid = [&] { Out.append(/* UTF-8 */ {'\xef', '\xbf', '\xbd'}); };
- auto Parse4Hex = [this](uint16_t &Out) {
- Out = 0;
- char Bytes[] = {next(), next(), next(), next()};
- for (unsigned char C : Bytes) {
- if (!std::isxdigit(C))
- return parseError("Invalid \\u escape sequence");
- Out <<= 4;
- Out |= (C > '9') ? (C & ~0x20) - 'A' + 10 : (C - '0');
- }
- return true;
- };
- uint16_t First;
- if (!Parse4Hex(First))
- return false;
-
- // We loop to allow proper surrogate-pair error handling.
- while (true) {
- if (LLVM_LIKELY(First < 0xD800 || First >= 0xE000)) { // BMP.
- encodeUtf8(First, Out);
- return true;
- }
-
- if (First >= 0xDC00) {
- Invalid(); // Lone trailing surrogate.
- return true;
- }
-
- // We have a leading surrogate, and need a trailing one.
- // Don't advance P: a lone surrogate is valid JSON (but invalid unicode)
- if (P + 2 > End || *P != '\\' || *(P + 1) != 'u') {
- Invalid(); // Lone leading not followed by \u...
- return true;
- }
- P += 2;
- uint16_t Second;
- if (!Parse4Hex(Second))
- return false;
- if (Second < 0xDC00 || Second >= 0xE000) {
- Invalid(); // Leading surrogate not followed by trailing.
- First = Second; // Second escape still needs to be processed.
- continue;
- }
-
- // Valid surrogate pair.
- encodeUtf8(0x10000 | ((First - 0xD800) << 10) | (Second - 0xDC00), Out);
- return true;
- }
-}
-
-bool Parser::parseError(const char *Msg) {
- int Line = 1;
- const char *StartOfLine = Start;
- for (const char *X = Start; X < P; ++X) {
- if (*X == 0x0A) {
- ++Line;
- StartOfLine = X + 1;
- }
- }
- Err.emplace(
- llvm::make_unique<ParseError>(Msg, Line, P - StartOfLine, P - Start));
- return false;
-}
-} // namespace
-
-Expected<Expr> parse(StringRef JSON) {
- Parser P(JSON);
- json::Expr E = nullptr;
- if (P.parseExpr(E))
- if (P.assertEnd())
- return std::move(E);
- return P.takeError();
-}
-char ParseError::ID = 0;
-
-} // namespace json
-} // namespace clangd
-} // namespace clang
-
-namespace {
-void quote(llvm::raw_ostream &OS, llvm::StringRef S) {
- OS << '\"';
- for (unsigned char C : S) {
- if (C == 0x22 || C == 0x5C)
- OS << '\\';
- if (C >= 0x20) {
- OS << C;
- continue;
- }
- OS << '\\';
- switch (C) {
- // A few characters are common enough to make short escapes worthwhile.
- case '\t':
- OS << 't';
- break;
- case '\n':
- OS << 'n';
- break;
- case '\r':
- OS << 'r';
- break;
- default:
- OS << 'u';
- llvm::write_hex(OS, C, llvm::HexPrintStyle::Lower, 4);
- break;
- }
- }
- OS << '\"';
-}
-
-enum IndenterAction {
- Indent,
- Outdent,
- Newline,
- Space,
-};
-} // namespace
-
-// Prints JSON. The indenter can be used to control formatting.
-template <typename Indenter>
-void clang::clangd::json::Expr::print(raw_ostream &OS,
- const Indenter &I) const {
- switch (Type) {
- case T_Null:
- OS << "null";
- break;
- case T_Boolean:
- OS << (as<bool>() ? "true" : "false");
- break;
- case T_Number:
- OS << format("%g", as<double>());
- break;
- case T_StringRef:
- quote(OS, as<StringRef>());
- break;
- case T_String:
- quote(OS, as<std::string>());
- break;
- case T_Object: {
- bool Comma = false;
- OS << '{';
- I(Indent);
- for (const auto &P : as<Expr::ObjectExpr>()) {
- if (Comma)
- OS << ',';
- Comma = true;
- I(Newline);
- quote(OS, P.first);
- OS << ':';
- I(Space);
- P.second.print(OS, I);
- }
- I(Outdent);
- if (Comma)
- I(Newline);
- OS << '}';
- break;
- }
- case T_Array: {
- bool Comma = false;
- OS << '[';
- I(Indent);
- for (const auto &E : as<Expr::ArrayExpr>()) {
- if (Comma)
- OS << ',';
- Comma = true;
- I(Newline);
- E.print(OS, I);
- }
- I(Outdent);
- if (Comma)
- I(Newline);
- OS << ']';
- break;
- }
- }
-}
-
-namespace clang {
-namespace clangd {
-namespace json {
-llvm::raw_ostream &operator<<(raw_ostream &OS, const Expr &E) {
- E.print(OS, [](IndenterAction A) { /*ignore*/ });
- return OS;
-}
-
-bool operator==(const Expr &L, const Expr &R) {
- if (L.kind() != R.kind())
- return false;
- switch (L.kind()) {
- case Expr::Null:
- return *L.asNull() == *R.asNull();
- case Expr::Boolean:
- return *L.asBoolean() == *R.asBoolean();
- case Expr::Number:
- return *L.asNumber() == *R.asNumber();
- case Expr::String:
- return *L.asString() == *R.asString();
- case Expr::Array:
- return *L.asArray() == *R.asArray();
- case Expr::Object:
- return *L.asObject() == *R.asObject();
- }
- llvm_unreachable("Unknown expression kind");
-}
-} // namespace json
-} // namespace clangd
-} // namespace clang
-
-void llvm::format_provider<clang::clangd::json::Expr>::format(
- const clang::clangd::json::Expr &E, raw_ostream &OS, StringRef Options) {
- if (Options.empty()) {
- OS << E;
- return;
- }
- unsigned IndentAmount = 0;
- if (Options.getAsInteger(/*Radix=*/10, IndentAmount))
- assert(false && "json::Expr format options should be an integer");
- unsigned IndentLevel = 0;
- E.print(OS, [&](IndenterAction A) {
- switch (A) {
- case Newline:
- OS << '\n';
- OS.indent(IndentLevel);
- break;
- case Space:
- OS << ' ';
- break;
- case Indent:
- IndentLevel += IndentAmount;
- break;
- case Outdent:
- IndentLevel -= IndentAmount;
- break;
- };
- });
-}
diff --git a/clangd/JSONExpr.h b/clangd/JSONExpr.h
deleted file mode 100644
index 8841ff2b..00000000
--- a/clangd/JSONExpr.h
+++ /dev/null
@@ -1,579 +0,0 @@
-//===--- JSONExpr.h - JSON expressions, parsing and serialization - C++ -*-===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===---------------------------------------------------------------------===//
-
-// FIXME: rename to JSON.h now that the scope is wider?
-
-#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSON_H
-#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSON_H
-
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/Error.h"
-#include "llvm/Support/FormatVariadic.h"
-#include "llvm/Support/raw_ostream.h"
-#include <map>
-
-namespace clang {
-namespace clangd {
-namespace json {
-
-// An Expr is an JSON value of unknown type.
-// They can be copied, but should generally be moved.
-//
-// === Composing expressions ===
-//
-// You can implicitly construct Exprs from:
-// - strings: std::string, SmallString, formatv, StringRef, char*
-// (char*, and StringRef are references, not copies!)
-// - numbers
-// - booleans
-// - null: nullptr
-// - arrays: {"foo", 42.0, false}
-// - serializable things: types with toJSON(const T&)->Expr, found by ADL
-//
-// They can also be constructed from object/array helpers:
-// - json::obj is a type like map<StringExpr, Expr>
-// - json::ary is a type like vector<Expr>
-// These can be list-initialized, or used to build up collections in a loop.
-// json::ary(Collection) converts all items in a collection to Exprs.
-//
-// === Inspecting expressions ===
-//
-// Each Expr is one of the JSON kinds:
-// null (nullptr_t)
-// boolean (bool)
-// number (double)
-// string (StringRef)
-// array (json::ary)
-// object (json::obj)
-//
-// The kind can be queried directly, or implicitly via the typed accessors:
-// if (Optional<StringRef> S = E.asString()
-// assert(E.kind() == Expr::String);
-//
-// Array and Object also have typed indexing accessors for easy traversal:
-// Expected<Expr> E = parse(R"( {"options": {"font": "sans-serif"}} )");
-// if (json::obj* O = E->asObject())
-// if (json::obj* Opts = O->getObject("options"))
-// if (Optional<StringRef> Font = Opts->getString("font"))
-// assert(Opts->at("font").kind() == Expr::String);
-//
-// === Converting expressions to objects ===
-//
-// The convention is to have a deserializer function findable via ADL:
-// fromJSON(const json::Expr&, T&)->bool
-// Deserializers are provided for:
-// - bool
-// - int
-// - double
-// - std::string
-// - vector<T>, where T is deserializable
-// - map<string, T>, where T is deserializable
-// - Optional<T>, where T is deserializable
-//
-// ObjectMapper can help writing fromJSON() functions for object types:
-// bool fromJSON(const Expr &E, MyStruct &R) {
-// ObjectMapper O(E);
-// if (!O || !O.map("mandatory_field", R.MandatoryField))
-// return false;
-// O.map("optional_field", R.OptionalField);
-// return true;
-// }
-//
-// === Serialization ===
-//
-// Exprs can be serialized to JSON:
-// 1) raw_ostream << Expr // Basic formatting.
-// 2) raw_ostream << formatv("{0}", Expr) // Basic formatting.
-// 3) raw_ostream << formatv("{0:2}", Expr) // Pretty-print with indent 2.
-//
-// And parsed:
-// Expected<Expr> E = json::parse("[1, 2, null]");
-// assert(E && E->kind() == Expr::Array);
-class Expr {
-public:
- enum Kind {
- Null,
- Boolean,
- Number,
- String,
- Array,
- Object,
- };
- class ObjectExpr;
- class ObjectKey;
- class ArrayExpr;
-
- // It would be nice to have Expr() be null. But that would make {} null too...
- Expr(const Expr &M) { copyFrom(M); }
- Expr(Expr &&M) { moveFrom(std::move(M)); }
- // "cheating" move-constructor for moving from initializer_list.
- Expr(const Expr &&M) { moveFrom(std::move(M)); }
- Expr(std::initializer_list<Expr> Elements) : Expr(ArrayExpr(Elements)) {}
- Expr(ArrayExpr &&Elements) : Type(T_Array) {
- create<ArrayExpr>(std::move(Elements));
- }
- Expr(ObjectExpr &&Properties) : Type(T_Object) {
- create<ObjectExpr>(std::move(Properties));
- }
- // Strings: types with value semantics.
- Expr(std::string &&V) : Type(T_String) { create<std::string>(std::move(V)); }
- Expr(const std::string &V) : Type(T_String) { create<std::string>(V); }
- Expr(const llvm::SmallVectorImpl<char> &V) : Type(T_String) {
- create<std::string>(V.begin(), V.end());
- }
- Expr(const llvm::formatv_object_base &V) : Expr(V.str()){};
- // Strings: types with reference semantics.
- Expr(llvm::StringRef V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
- Expr(const char *V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
- Expr(std::nullptr_t) : Type(T_Null) {}
- // Prevent implicit conversions to boolean.
- template <typename T, typename = typename std::enable_if<
- std::is_same<T, bool>::value>::type>
- Expr(T B) : Type(T_Boolean) {
- create<bool>(B);
- }
- // Numbers: arithmetic types that are not boolean.
- template <
- typename T,
- typename = typename std::enable_if<std::is_arithmetic<T>::value>::type,
- typename = typename std::enable_if<std::integral_constant<
- bool, !std::is_same<T, bool>::value>::value>::type>
- Expr(T D) : Type(T_Number) {
- create<double>(D);
- }
- // Types with a toJSON(const T&)->Expr function, found by ADL.
- template <typename T,
- typename = typename std::enable_if<std::is_same<
- Expr, decltype(toJSON(*(const T *)nullptr))>::value>>
- Expr(const T &V) : Expr(toJSON(V)) {}
-
- Expr &operator=(const Expr &M) {
- destroy();
- copyFrom(M);
- return *this;
- }
- Expr &operator=(Expr &&M) {
- destroy();
- moveFrom(std::move(M));
- return *this;
- }
- ~Expr() { destroy(); }
-
- Kind kind() const {
- switch (Type) {
- case T_Null:
- return Null;
- case T_Boolean:
- return Boolean;
- case T_Number:
- return Number;
- case T_String:
- case T_StringRef:
- return String;
- case T_Object:
- return Object;
- case T_Array:
- return Array;
- }
- llvm_unreachable("Unknown kind");
- }
-
- // Typed accessors return None/nullptr if the Expr is not of this type.
- llvm::Optional<std::nullptr_t> asNull() const {
- if (LLVM_LIKELY(Type == T_Null))
- return nullptr;
- return llvm::None;
- }
- llvm::Optional<bool> asBoolean() const {
- if (LLVM_LIKELY(Type == T_Boolean))
- return as<bool>();
- return llvm::None;
- }
- llvm::Optional<double> asNumber() const {
- if (LLVM_LIKELY(Type == T_Number))
- return as<double>();
- return llvm::None;
- }
- llvm::Optional<int64_t> asInteger() const {
- if (LLVM_LIKELY(Type == T_Number)) {
- double D = as<double>();
- if (LLVM_LIKELY(std::modf(D, &D) == 0 &&
- D >= std::numeric_limits<int64_t>::min() &&
- D <= std::numeric_limits<int64_t>::max()))
- return D;
- }
- return llvm::None;
- }
- llvm::Optional<llvm::StringRef> asString() const {
- if (Type == T_String)
- return llvm::StringRef(as<std::string>());
- if (LLVM_LIKELY(Type == T_StringRef))
- return as<llvm::StringRef>();
- return llvm::None;
- }
- const ObjectExpr *asObject() const {
- return LLVM_LIKELY(Type == T_Object) ? &as<ObjectExpr>() : nullptr;
- }
- ObjectExpr *asObject() {
- return LLVM_LIKELY(Type == T_Object) ? &as<ObjectExpr>() : nullptr;
- }
- const ArrayExpr *asArray() const {
- return LLVM_LIKELY(Type == T_Array) ? &as<ArrayExpr>() : nullptr;
- }
- ArrayExpr *asArray() {
- return LLVM_LIKELY(Type == T_Array) ? &as<ArrayExpr>() : nullptr;
- }
-
- friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Expr &);
-
-private:
- void destroy();
- void copyFrom(const Expr &M);
- // We allow moving from *const* Exprs, by marking all members as mutable!
- // This hack is needed to support initializer-list syntax efficiently.
- // (std::initializer_list<T> is a container of const T).
- void moveFrom(const Expr &&M);
-
- template <typename T, typename... U> void create(U &&... V) {
- new (&as<T>()) T(std::forward<U>(V)...);
- }
- template <typename T> T &as() const {
- return *reinterpret_cast<T *>(Union.buffer);
- }
-
- template <typename Indenter>
- void print(llvm::raw_ostream &, const Indenter &) const;
- friend struct llvm::format_provider<clang::clangd::json::Expr>;
-
- enum ExprType : char {
- T_Null,
- T_Boolean,
- T_Number,
- T_StringRef,
- T_String,
- T_Object,
- T_Array,
- };
- mutable ExprType Type;
-
-public:
- // ObjectKey is a used to capture keys in Expr::ObjectExpr. Like Expr but:
- // - only strings are allowed
- // - it's optimized for the string literal case (Owned == nullptr)
- class ObjectKey {
- public:
- ObjectKey(const char *S) : Data(S) {}
- ObjectKey(llvm::StringRef S) : Data(S) {}
- ObjectKey(std::string &&V)
- : Owned(new std::string(std::move(V))), Data(*Owned) {}
- ObjectKey(const std::string &V) : Owned(new std::string(V)), Data(*Owned) {}
- ObjectKey(const llvm::SmallVectorImpl<char> &V)
- : ObjectKey(std::string(V.begin(), V.end())) {}
- ObjectKey(const llvm::formatv_object_base &V) : ObjectKey(V.str()) {}
-
- ObjectKey(const ObjectKey &C) { *this = C; }
- ObjectKey(ObjectKey &&C) : ObjectKey(static_cast<const ObjectKey &&>(C)) {}
- ObjectKey &operator=(const ObjectKey &C) {
- if (C.Owned) {
- Owned.reset(new std::string(*C.Owned));
- Data = *Owned;
- } else {
- Data = C.Data;
- }
- return *this;
- }
- ObjectKey &operator=(ObjectKey &&) = default;
-
- operator llvm::StringRef() const { return Data; }
-
- friend bool operator<(const ObjectKey &L, const ObjectKey &R) {
- return L.Data < R.Data;
- }
-
- // "cheating" move-constructor for moving from initializer_list.
- ObjectKey(const ObjectKey &&V) {
- Owned = std::move(V.Owned);
- Data = V.Data;
- }
-
- private:
- mutable std::unique_ptr<std::string> Owned; // mutable for cheating.
- llvm::StringRef Data;
- };
-
- class ObjectExpr : public std::map<ObjectKey, Expr> {
- public:
- explicit ObjectExpr() {}
- // Use a custom struct for list-init, because pair forces extra copies.
- struct KV;
- explicit ObjectExpr(std::initializer_list<KV> Properties);
-
- // Allow [] as if Expr was default-constructible as null.
- Expr &operator[](const ObjectKey &K) {
- return emplace(K, Expr(nullptr)).first->second;
- }
- Expr &operator[](ObjectKey &&K) {
- return emplace(std::move(K), Expr(nullptr)).first->second;
- }
-
- // Look up a property, returning nullptr if it doesn't exist.
- json::Expr *get(const ObjectKey &K) {
- auto I = find(K);
- if (I == end())
- return nullptr;
- return &I->second;
- }
- const json::Expr *get(const ObjectKey &K) const {
- auto I = find(K);
- if (I == end())
- return nullptr;
- return &I->second;
- }
- // Typed accessors return None/nullptr if
- // - the property doesn't exist
- // - or it has the wrong type
- llvm::Optional<std::nullptr_t> getNull(const ObjectKey &K) const {
- if (auto *V = get(K))
- return V->asNull();
- return llvm::None;
- }
- llvm::Optional<bool> getBoolean(const ObjectKey &K) const {
- if (auto *V = get(K))
- return V->asBoolean();
- return llvm::None;
- }
- llvm::Optional<double> getNumber(const ObjectKey &K) const {
- if (auto *V = get(K))
- return V->asNumber();
- return llvm::None;
- }
- llvm::Optional<int64_t> getInteger(const ObjectKey &K) const {
- if (auto *V = get(K))
- return V->asInteger();
- return llvm::None;
- }
- llvm::Optional<llvm::StringRef> getString(const ObjectKey &K) const {
- if (auto *V = get(K))
- return V->asString();
- return llvm::None;
- }
- const ObjectExpr *getObject(const ObjectKey &K) const {
- if (auto *V = get(K))
- return V->asObject();
- return nullptr;
- }
- ObjectExpr *getObject(const ObjectKey &K) {
- if (auto *V = get(K))
- return V->asObject();
- return nullptr;
- }
- const ArrayExpr *getArray(const ObjectKey &K) const {
- if (auto *V = get(K))
- return V->asArray();
- return nullptr;
- }
- ArrayExpr *getArray(const ObjectKey &K) {
- if (auto *V = get(K))
- return V->asArray();
- return nullptr;
- }
- };
-
- class ArrayExpr : public std::vector<Expr> {
- public:
- explicit ArrayExpr() {}
- explicit ArrayExpr(std::initializer_list<Expr> Elements) {
- reserve(Elements.size());
- for (const Expr &V : Elements)
- emplace_back(std::move(V));
- };
- template <typename Collection> explicit ArrayExpr(const Collection &C) {
- for (const auto &V : C)
- emplace_back(V);
- }
-
- // Typed accessors return None/nullptr if the element has the wrong type.
- llvm::Optional<std::nullptr_t> getNull(size_t I) const {
- return (*this)[I].asNull();
- }
- llvm::Optional<bool> getBoolean(size_t I) const {
- return (*this)[I].asBoolean();
- }
- llvm::Optional<double> getNumber(size_t I) const {
- return (*this)[I].asNumber();
- }
- llvm::Optional<int64_t> getInteger(size_t I) const {
- return (*this)[I].asInteger();
- }
- llvm::Optional<llvm::StringRef> getString(size_t I) const {
- return (*this)[I].asString();
- }
- const ObjectExpr *getObject(size_t I) const {
- return (*this)[I].asObject();
- }
- ObjectExpr *getObject(size_t I) { return (*this)[I].asObject(); }
- const ArrayExpr *getArray(size_t I) const { return (*this)[I].asArray(); }
- ArrayExpr *getArray(size_t I) { return (*this)[I].asArray(); }
- };
-
-private:
- mutable llvm::AlignedCharArrayUnion<bool, double, llvm::StringRef,
- std::string, ArrayExpr, ObjectExpr>
- Union;
-};
-
-bool operator==(const Expr &, const Expr &);
-inline bool operator!=(const Expr &L, const Expr &R) { return !(L == R); }
-inline bool operator==(const Expr::ObjectKey &L, const Expr::ObjectKey &R) {
- return llvm::StringRef(L) == llvm::StringRef(R);
-}
-inline bool operator!=(const Expr::ObjectKey &L, const Expr::ObjectKey &R) {
- return !(L == R);
-}
-
-struct Expr::ObjectExpr::KV {
- ObjectKey K;
- Expr V;
-};
-
-inline Expr::ObjectExpr::ObjectExpr(std::initializer_list<KV> Properties) {
- for (const auto &P : Properties)
- emplace(std::move(P.K), std::move(P.V));
-}
-
-// Give Expr::{Object,Array} more convenient names for literal use.
-using obj = Expr::ObjectExpr;
-using ary = Expr::ArrayExpr;
-
-// Standard deserializers.
-inline bool fromJSON(const json::Expr &E, std::string &Out) {
- if (auto S = E.asString()) {
- Out = *S;
- return true;
- }
- return false;
-}
-inline bool fromJSON(const json::Expr &E, int &Out) {
- if (auto S = E.asInteger()) {
- Out = *S;
- return true;
- }
- return false;
-}
-inline bool fromJSON(const json::Expr &E, double &Out) {
- if (auto S = E.asNumber()) {
- Out = *S;
- return true;
- }
- return false;
-}
-inline bool fromJSON(const json::Expr &E, bool &Out) {
- if (auto S = E.asBoolean()) {
- Out = *S;
- return true;
- }
- return false;
-}
-template <typename T>
-bool fromJSON(const json::Expr &E, llvm::Optional<T> &Out) {
- if (E.asNull()) {
- Out = llvm::None;
- return true;
- }
- T Result;
- if (!fromJSON(E, Result))
- return false;
- Out = std::move(Result);
- return true;
-}
-template <typename T> bool fromJSON(const json::Expr &E, std::vector<T> &Out) {
- if (auto *A = E.asArray()) {
- Out.clear();
- Out.resize(A->size());
- for (size_t I = 0; I < A->size(); ++I)
- if (!fromJSON((*A)[I], Out[I]))
- return false;
- return true;
- }
- return false;
-}
-template <typename T>
-bool fromJSON(const json::Expr &E, std::map<std::string, T> &Out) {
- if (auto *O = E.asObject()) {
- Out.clear();
- for (const auto &KV : *O)
- if (!fromJSON(KV.second, Out[llvm::StringRef(KV.first)]))
- return false;
- return true;
- }
- return false;
-}
-
-// Helper for mapping JSON objects onto protocol structs.
-// See file header for example.
-class ObjectMapper {
-public:
- ObjectMapper(const json::Expr &E) : O(E.asObject()) {}
-
- // True if the expression is an object.
- // Must be checked before calling map().
- operator bool() { return O; }
-
- // Maps a property to a field, if it exists.
- template <typename T> bool map(const char *Prop, T &Out) {
- assert(*this && "Must check this is an object before calling map()");
- if (const json::Expr *E = O->get(Prop))
- return fromJSON(*E, Out);
- return false;
- }
-
- // Optional requires special handling, because missing keys are OK.
- template <typename T> bool map(const char *Prop, llvm::Optional<T> &Out) {
- assert(*this && "Must check this is an object before calling map()");
- if (const json::Expr *E = O->get(Prop))
- return fromJSON(*E, Out);
- Out = llvm::None;
- return true;
- }
-
-private:
- const json::obj *O;
-};
-
-llvm::Expected<Expr> parse(llvm::StringRef JSON);
-
-class ParseError : public llvm::ErrorInfo<ParseError> {
- const char *Msg;
- unsigned Line, Column, Offset;
-
-public:
- static char ID;
- ParseError(const char *Msg, unsigned Line, unsigned Column, unsigned Offset)
- : Msg(Msg), Line(Line), Column(Column), Offset(Offset) {}
- void log(llvm::raw_ostream &OS) const override {
- OS << llvm::formatv("[{0}:{1}, byte={2}]: {3}", Line, Column, Offset, Msg);
- }
- std::error_code convertToErrorCode() const override {
- return llvm::inconvertibleErrorCode();
- }
-};
-
-} // namespace json
-} // namespace clangd
-} // namespace clang
-
-namespace llvm {
-template <> struct format_provider<clang::clangd::json::Expr> {
- static void format(const clang::clangd::json::Expr &, raw_ostream &,
- StringRef);
-};
-} // namespace llvm
-
-#endif
diff --git a/clangd/JSONRPCDispatcher.cpp b/clangd/JSONRPCDispatcher.cpp
index e86b09da..2741c665 100644
--- a/clangd/JSONRPCDispatcher.cpp
+++ b/clangd/JSONRPCDispatcher.cpp
@@ -8,28 +8,31 @@
//===----------------------------------------------------------------------===//
#include "JSONRPCDispatcher.h"
-#include "JSONExpr.h"
#include "ProtocolHandlers.h"
#include "Trace.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Chrono.h"
+#include "llvm/Support/Errno.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
#include "llvm/Support/SourceMgr.h"
#include <istream>
+using namespace llvm;
using namespace clang;
using namespace clangd;
namespace {
-static Key<json::Expr> RequestID;
+static Key<json::Value> RequestID;
static Key<JSONOutput *> RequestOut;
// When tracing, we trace a request and attach the repsonse in reply().
// Because the Span isn't available, we find the current request using Context.
class RequestSpan {
- RequestSpan(json::obj *Args) : Args(Args) {}
+ RequestSpan(llvm::json::Object *Args) : Args(Args) {}
std::mutex Mu;
- json::obj *Args;
+ llvm::json::Object *Args;
static Key<std::unique_ptr<RequestSpan>> RSKey;
public:
@@ -40,7 +43,7 @@ public:
}
// If there's an enclosing request and the tracer is interested, calls \p F
- // with a json::obj where request info can be added.
+ // with a json::Object where request info can be added.
template <typename Func> static void attach(Func &&F) {
auto *RequestArgs = Context::current().get(RSKey);
if (!RequestArgs || !*RequestArgs || !(*RequestArgs)->Args)
@@ -52,7 +55,7 @@ public:
Key<std::unique_ptr<RequestSpan>> RequestSpan::RSKey;
} // namespace
-void JSONOutput::writeMessage(const json::Expr &Message) {
+void JSONOutput::writeMessage(const json::Value &Message) {
std::string S;
llvm::raw_string_ostream OS(S);
if (Pretty)
@@ -66,14 +69,18 @@ void JSONOutput::writeMessage(const json::Expr &Message) {
Outs << "Content-Length: " << S.size() << "\r\n\r\n" << S;
Outs.flush();
}
- log(llvm::Twine("--> ") + S);
+ vlog(">>> {0}\n", S);
}
-void JSONOutput::log(const Twine &Message) {
+void JSONOutput::log(Logger::Level Level,
+ const llvm::formatv_object_base &Message) {
+ if (Level < MinLevel)
+ return;
llvm::sys::TimePoint<> Timestamp = std::chrono::system_clock::now();
trace::log(Message);
std::lock_guard<std::mutex> Guard(StreamMutex);
- Logs << llvm::formatv("[{0:%H:%M:%S.%L}] {1}\n", Timestamp, Message);
+ Logs << llvm::formatv("{0}[{1:%H:%M:%S.%L}] {2}\n", indicator(Level),
+ Timestamp, Message);
Logs.flush();
}
@@ -85,52 +92,56 @@ void JSONOutput::mirrorInput(const Twine &Message) {
InputMirror->flush();
}
-void clangd::reply(json::Expr &&Result) {
+void clangd::reply(json::Value &&Result) {
auto ID = Context::current().get(RequestID);
if (!ID) {
- log("Attempted to reply to a notification!");
+ elog("Attempted to reply to a notification!");
return;
}
- RequestSpan::attach([&](json::obj &Args) { Args["Reply"] = Result; });
+ RequestSpan::attach([&](json::Object &Args) { Args["Reply"] = Result; });
+ log("--> reply({0})", *ID);
Context::current()
.getExisting(RequestOut)
- ->writeMessage(json::obj{
+ ->writeMessage(json::Object{
{"jsonrpc", "2.0"},
{"id", *ID},
{"result", std::move(Result)},
});
}
-void clangd::replyError(ErrorCode code, const llvm::StringRef &Message) {
- log("Error " + Twine(static_cast<int>(code)) + ": " + Message);
- RequestSpan::attach([&](json::obj &Args) {
- Args["Error"] =
- json::obj{{"code", static_cast<int>(code)}, {"message", Message.str()}};
+void clangd::replyError(ErrorCode Code, const llvm::StringRef &Message) {
+ elog("Error {0}: {1}", static_cast<int>(Code), Message);
+ RequestSpan::attach([&](json::Object &Args) {
+ Args["Error"] = json::Object{{"code", static_cast<int>(Code)},
+ {"message", Message.str()}};
});
if (auto ID = Context::current().get(RequestID)) {
+ log("--> reply({0}) error: {1}", *ID, Message);
Context::current()
.getExisting(RequestOut)
- ->writeMessage(json::obj{
+ ->writeMessage(json::Object{
{"jsonrpc", "2.0"},
{"id", *ID},
- {"error",
- json::obj{{"code", static_cast<int>(code)}, {"message", Message}}},
+ {"error", json::Object{{"code", static_cast<int>(Code)},
+ {"message", Message}}},
});
}
}
-void clangd::call(StringRef Method, json::Expr &&Params) {
+void clangd::call(StringRef Method, json::Value &&Params) {
+ RequestSpan::attach([&](json::Object &Args) {
+ Args["Call"] = json::Object{{"method", Method.str()}, {"params", Params}};
+ });
// FIXME: Generate/Increment IDs for every request so that we can get proper
// replies once we need to.
- RequestSpan::attach([&](json::obj &Args) {
- Args["Call"] = json::obj{{"method", Method.str()}, {"params", Params}};
- });
+ auto ID = 1;
+ log("--> {0}({1})", Method, ID);
Context::current()
.getExisting(RequestOut)
- ->writeMessage(json::obj{
+ ->writeMessage(json::Object{
{"jsonrpc", "2.0"},
- {"id", 1},
+ {"id", ID},
{"method", Method},
{"params", std::move(Params)},
});
@@ -141,21 +152,39 @@ void JSONRPCDispatcher::registerHandler(StringRef Method, Handler H) {
Handlers[Method] = std::move(H);
}
-bool JSONRPCDispatcher::call(const json::Expr &Message, JSONOutput &Out) const {
+static void logIncomingMessage(const llvm::Optional<json::Value> &ID,
+ llvm::Optional<StringRef> Method,
+ const json::Object *Error) {
+ if (Method) { // incoming request
+ if (ID) // call
+ log("<-- {0}({1})", *Method, *ID);
+ else // notification
+ log("<-- {0}", *Method);
+ } else if (ID) { // response, ID must be provided
+ if (Error)
+ log("<-- reply({0}) error: {1}", *ID,
+ Error->getString("message").getValueOr("<no message>"));
+ else
+ log("<-- reply({0})", *ID);
+ }
+}
+
+bool JSONRPCDispatcher::call(const json::Value &Message,
+ JSONOutput &Out) const {
// Message must be an object with "jsonrpc":"2.0".
- auto *Object = Message.asObject();
+ auto *Object = Message.getAsObject();
if (!Object || Object->getString("jsonrpc") != Optional<StringRef>("2.0"))
return false;
// ID may be any JSON value. If absent, this is a notification.
- llvm::Optional<json::Expr> ID;
+ llvm::Optional<json::Value> ID;
if (auto *I = Object->get("id"))
ID = std::move(*I);
- // Method must be given.
auto Method = Object->getString("method");
- if (!Method)
+ logIncomingMessage(ID, Method, Object->getObject("error"));
+ if (!Method) // We only handle incoming requests, and ignore responses.
return false;
// Params should be given, use null if not.
- json::Expr Params = nullptr;
+ json::Value Params = nullptr;
if (auto *P = Object->get("params"))
Params = std::move(*P);
@@ -180,27 +209,43 @@ bool JSONRPCDispatcher::call(const json::Expr &Message, JSONOutput &Out) const {
return true;
}
-static llvm::Optional<std::string> readStandardMessage(std::istream &In,
+// Tries to read a line up to and including \n.
+// If failing, feof() or ferror() will be set.
+static bool readLine(std::FILE *In, std::string &Out) {
+ static constexpr int BufSize = 1024;
+ size_t Size = 0;
+ Out.clear();
+ for (;;) {
+ Out.resize(Size + BufSize);
+ // Handle EINTR which is sent when a debugger attaches on some platforms.
+ if (!llvm::sys::RetryAfterSignal(nullptr, ::fgets, &Out[Size], BufSize, In))
+ return false;
+ clearerr(In);
+ // If the line contained null bytes, anything after it (including \n) will
+ // be ignored. Fortunately this is not a legal header or JSON.
+ size_t Read = std::strlen(&Out[Size]);
+ if (Read > 0 && Out[Size + Read - 1] == '\n') {
+ Out.resize(Size + Read);
+ return true;
+ }
+ Size += Read;
+ }
+}
+
+// Returns None when:
+// - ferror() or feof() are set.
+// - Content-Length is missing or empty (protocol error)
+static llvm::Optional<std::string> readStandardMessage(std::FILE *In,
JSONOutput &Out) {
// A Language Server Protocol message starts with a set of HTTP headers,
// delimited by \r\n, and terminated by an empty line (\r\n).
unsigned long long ContentLength = 0;
- while (In.good()) {
- std::string Line;
- std::getline(In, Line);
- if (!In.good() && errno == EINTR) {
- In.clear();
- continue;
- }
+ std::string Line;
+ while (true) {
+ if (feof(In) || ferror(In) || !readLine(In, Line))
+ return llvm::None;
Out.mirrorInput(Line);
- // Mirror '\n' that gets consumed by std::getline, but is not included in
- // the resulting Line.
- // Note that '\r' is part of Line, so we don't need to mirror it
- // separately.
- if (!In.eof())
- Out.mirrorInput("\n");
-
llvm::StringRef LineRef(Line);
// We allow comments in headers. Technically this isn't part
@@ -208,19 +253,13 @@ static llvm::Optional<std::string> readStandardMessage(std::istream &In,
if (LineRef.startswith("#"))
continue;
- // Content-Type is a specified header, but does nothing.
- // Content-Length is a mandatory header. It specifies the length of the
- // following JSON.
- // It is unspecified what sequence headers must be supplied in, so we
- // allow any sequence.
- // The end of headers is signified by an empty line.
+ // Content-Length is a mandatory header, and the only one we handle.
if (LineRef.consume_front("Content-Length: ")) {
if (ContentLength != 0) {
- log("Warning: Duplicate Content-Length header received. "
- "The previous value for this message (" +
- llvm::Twine(ContentLength) + ") was ignored.\n");
+ elog("Warning: Duplicate Content-Length header received. "
+ "The previous value for this message ({0}) was ignored.",
+ ContentLength);
}
-
llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength);
continue;
} else if (!LineRef.trim().empty()) {
@@ -233,46 +272,46 @@ static llvm::Optional<std::string> readStandardMessage(std::istream &In,
}
}
- // Guard against large messages. This is usually a bug in the client code
- // and we don't want to crash downstream because of it.
+ // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999"
if (ContentLength > 1 << 30) { // 1024M
- In.ignore(ContentLength);
- log("Skipped overly large message of " + Twine(ContentLength) +
- " bytes.\n");
+ elog("Refusing to read message with long Content-Length: {0}. "
+ "Expect protocol errors",
+ ContentLength);
+ return llvm::None;
+ }
+ if (ContentLength == 0) {
+ log("Warning: Missing Content-Length header, or zero-length message.");
return llvm::None;
}
- if (ContentLength > 0) {
- std::string JSON(ContentLength, '\0');
- In.read(&JSON[0], ContentLength);
- Out.mirrorInput(StringRef(JSON.data(), In.gcount()));
-
- // If the stream is aborted before we read ContentLength bytes, In
- // will have eofbit and failbit set.
- if (!In) {
- log("Input was aborted. Read only " + llvm::Twine(In.gcount()) +
- " bytes of expected " + llvm::Twine(ContentLength) + ".\n");
+ std::string JSON(ContentLength, '\0');
+ for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
+ // Handle EINTR which is sent when a debugger attaches on some platforms.
+ Read = llvm::sys::RetryAfterSignal(0u, ::fread, &JSON[Pos], 1,
+ ContentLength - Pos, In);
+ Out.mirrorInput(StringRef(&JSON[Pos], Read));
+ if (Read == 0) {
+ elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos,
+ ContentLength);
return llvm::None;
}
- return std::move(JSON);
- } else {
- log("Warning: Missing Content-Length header, or message has zero "
- "length.\n");
- return llvm::None;
+ clearerr(In); // If we're done, the error was transient. If we're not done,
+ // either it was transient or we'll see it again on retry.
+ Pos += Read;
}
+ return std::move(JSON);
}
// For lit tests we support a simplified syntax:
// - messages are delimited by '---' on a line by itself
// - lines starting with # are ignored.
// This is a testing path, so favor simplicity over performance here.
-static llvm::Optional<std::string> readDelimitedMessage(std::istream &In,
+// When returning None, feof() or ferror() will be set.
+static llvm::Optional<std::string> readDelimitedMessage(std::FILE *In,
JSONOutput &Out) {
std::string JSON;
std::string Line;
- while (std::getline(In, Line)) {
- Line.push_back('\n'); // getline() consumed the newline.
-
+ while (readLine(In, Line)) {
auto LineRef = llvm::StringRef(Line).trim();
if (LineRef.startswith("#")) // comment
continue;
@@ -284,39 +323,46 @@ static llvm::Optional<std::string> readDelimitedMessage(std::istream &In,
JSON += Line;
}
- if (In.bad()) {
- log("Input error while reading message!");
+ if (ferror(In)) {
+ elog("Input error while reading message!");
return llvm::None;
- } else {
+ } else { // Including EOF
Out.mirrorInput(
llvm::formatv("Content-Length: {0}\r\n\r\n{1}", JSON.size(), JSON));
return std::move(JSON);
}
}
-void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
+// The use of C-style std::FILE* IO deserves some explanation.
+// Previously, std::istream was used. When a debugger attached on MacOS, the
+// process received EINTR, the stream went bad, and clangd exited.
+// A retry-on-EINTR loop around reads solved this problem, but caused clangd to
+// sometimes hang rather than exit on other OSes. The interaction between
+// istreams and signals isn't well-specified, so it's hard to get this right.
+// The C APIs seem to be clearer in this respect.
+void clangd::runLanguageServerLoop(std::FILE *In, JSONOutput &Out,
JSONStreamStyle InputStyle,
JSONRPCDispatcher &Dispatcher,
bool &IsDone) {
auto &ReadMessage =
(InputStyle == Delimited) ? readDelimitedMessage : readStandardMessage;
- while (In.good()) {
+ while (!IsDone && !feof(In)) {
+ if (ferror(In)) {
+ elog("IO error: {0}", llvm::sys::StrError());
+ return;
+ }
if (auto JSON = ReadMessage(In, Out)) {
if (auto Doc = json::parse(*JSON)) {
// Log the formatted message.
- log(llvm::formatv(Out.Pretty ? "<-- {0:2}\n" : "<-- {0}\n", *Doc));
+ vlog(Out.Pretty ? "<<< {0:2}\n" : "<<< {0}\n", *Doc);
// Finally, execute the action for this JSON message.
if (!Dispatcher.call(*Doc, Out))
- log("JSON dispatch failed!\n");
+ elog("JSON dispatch failed!");
} else {
// Parse error. Log the raw message.
- log(llvm::formatv("<-- {0}\n" , *JSON));
- log(llvm::Twine("JSON parse error: ") +
- llvm::toString(Doc.takeError()) + "\n");
+ vlog("<<< {0}\n", *JSON);
+ elog("JSON parse error: {0}", llvm::toString(Doc.takeError()));
}
}
- // If we're done, exit the loop.
- if (IsDone)
- break;
}
}
diff --git a/clangd/JSONRPCDispatcher.h b/clangd/JSONRPCDispatcher.h
index ce7e744e..e8c96fc9 100644
--- a/clangd/JSONRPCDispatcher.h
+++ b/clangd/JSONRPCDispatcher.h
@@ -10,13 +10,13 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H
-#include "JSONExpr.h"
#include "Logger.h"
#include "Protocol.h"
#include "Trace.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/JSON.h"
#include <iosfwd>
#include <mutex>
@@ -30,14 +30,16 @@ class JSONOutput : public Logger {
// JSONOutput now that we pass Context everywhere.
public:
JSONOutput(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs,
- llvm::raw_ostream *InputMirror = nullptr, bool Pretty = false)
- : Pretty(Pretty), Outs(Outs), Logs(Logs), InputMirror(InputMirror) {}
+ Logger::Level MinLevel, llvm::raw_ostream *InputMirror = nullptr,
+ bool Pretty = false)
+ : Pretty(Pretty), MinLevel(MinLevel), Outs(Outs), Logs(Logs),
+ InputMirror(InputMirror) {}
/// Emit a JSONRPC message.
- void writeMessage(const json::Expr &Result);
+ void writeMessage(const llvm::json::Value &Result);
/// Write a line to the logging stream.
- void log(const Twine &Message) override;
+ void log(Level, const llvm::formatv_object_base &Message) override;
/// Mirror \p Message into InputMirror stream. Does nothing if InputMirror is
/// null.
@@ -48,6 +50,7 @@ public:
const bool Pretty;
private:
+ Logger::Level MinLevel;
llvm::raw_ostream &Outs;
llvm::raw_ostream &Logs;
llvm::raw_ostream *InputMirror;
@@ -57,20 +60,20 @@ private:
/// Sends a successful reply.
/// Current context must derive from JSONRPCDispatcher::Handler.
-void reply(json::Expr &&Result);
+void reply(llvm::json::Value &&Result);
/// Sends an error response to the client, and logs it.
/// Current context must derive from JSONRPCDispatcher::Handler.
-void replyError(ErrorCode code, const llvm::StringRef &Message);
+void replyError(ErrorCode Code, const llvm::StringRef &Message);
/// Sends a request to the client.
/// Current context must derive from JSONRPCDispatcher::Handler.
-void call(llvm::StringRef Method, json::Expr &&Params);
+void call(llvm::StringRef Method, llvm::json::Value &&Params);
/// Main JSONRPC entry point. This parses the JSONRPC "header" and calls the
/// registered Handler for the method received.
class JSONRPCDispatcher {
public:
// A handler responds to requests for a particular method name.
- using Handler = std::function<void(const json::Expr &)>;
+ using Handler = std::function<void(const llvm::json::Value &)>;
/// Create a new JSONRPCDispatcher. UnknownHandler is called when an unknown
/// method is received.
@@ -81,7 +84,7 @@ public:
void registerHandler(StringRef Method, Handler H);
/// Parses a JSONRPC message and calls the Handler for it.
- bool call(const json::Expr &Message, JSONOutput &Out) const;
+ bool call(const llvm::json::Value &Message, JSONOutput &Out) const;
private:
llvm::StringMap<Handler> Handlers;
@@ -102,7 +105,9 @@ enum JSONStreamStyle {
/// if it is.
/// Input stream(\p In) must be opened in binary mode to avoid preliminary
/// replacements of \r\n with \n.
-void runLanguageServerLoop(std::istream &In, JSONOutput &Out,
+/// We use C-style FILE* for reading as std::istream has unclear interaction
+/// with signals, which are sent by debuggers on some OSs.
+void runLanguageServerLoop(std::FILE *In, JSONOutput &Out,
JSONStreamStyle InputStyle,
JSONRPCDispatcher &Dispatcher, bool &IsDone);
diff --git a/clangd/Logger.cpp b/clangd/Logger.cpp
index 08253844..5ce3351f 100644
--- a/clangd/Logger.cpp
+++ b/clangd/Logger.cpp
@@ -25,9 +25,10 @@ LoggingSession::LoggingSession(clangd::Logger &Instance) {
LoggingSession::~LoggingSession() { L = nullptr; }
-void log(const llvm::Twine &Message) {
+void detail::log(Logger::Level Level,
+ const llvm::formatv_object_base &Message) {
if (L)
- L->log(Message);
+ L->log(Level, Message);
else {
static std::mutex Mu;
std::lock_guard<std::mutex> Guard(Mu);
@@ -35,5 +36,13 @@ void log(const llvm::Twine &Message) {
}
}
+const char *detail::debugType(const char *Filename) {
+ if (const char *Slash = strrchr(Filename, '/'))
+ return Slash + 1;
+ if (const char *Backslash = strrchr(Filename, '\\'))
+ return Backslash + 1;
+ return Filename;
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/Logger.h b/clangd/Logger.h
index 7811111a..cc6e3a0a 100644
--- a/clangd/Logger.h
+++ b/clangd/Logger.h
@@ -11,24 +11,68 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_LOGGER_H
#include "llvm/ADT/Twine.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FormatAdapters.h"
+#include "llvm/Support/FormatVariadic.h"
namespace clang {
namespace clangd {
-/// Main logging function.
-/// Logs messages to a global logger, which can be set up by LoggingSesssion.
-/// If no logger is registered, writes to llvm::errs().
-void log(const llvm::Twine &Message);
-
/// Interface to allow custom logging in clangd.
class Logger {
public:
virtual ~Logger() = default;
+ enum Level { Debug, Verbose, Info, Error };
+ static char indicator(Level L) { return "DVIE"[L]; }
+
/// Implementations of this method must be thread-safe.
- virtual void log(const llvm::Twine &Message) = 0;
+ virtual void log(Level, const llvm::formatv_object_base &Message) = 0;
};
+namespace detail {
+const char *debugType(const char *Filename);
+void log(Logger::Level, const llvm::formatv_object_base &);
+
+// We often want to consume llvm::Errors by value when passing them to log().
+// We automatically wrap them in llvm::fmt_consume() as formatv requires.
+template <typename T> T &&wrap(T &&V) { return std::forward<T>(V); }
+inline decltype(fmt_consume(llvm::Error::success())) wrap(llvm::Error &&V) {
+ return fmt_consume(std::move(V));
+}
+template <typename... Ts>
+void log(Logger::Level L, const char *Fmt, Ts &&... Vals) {
+ detail::log(L, llvm::formatv(Fmt, detail::wrap(std::forward<Ts>(Vals))...));
+}
+} // namespace detail
+
+// Clangd logging functions write to a global logger set by LoggingSession.
+// If no logger is registered, writes to llvm::errs().
+// All accept llvm::formatv()-style arguments, e.g. log("Text={0}", Text).
+
+// elog() is used for "loud" errors and warnings.
+// This level is often visible to users.
+template <typename... Ts> void elog(const char *Fmt, Ts &&... Vals) {
+ detail::log(Logger::Error, Fmt, std::forward<Ts>(Vals)...);
+}
+// log() is used for information important to understanding a clangd session.
+// e.g. the names of LSP messages sent are logged at this level.
+// This level could be enabled in production builds to allow later inspection.
+template <typename... Ts> void log(const char *Fmt, Ts &&... Vals) {
+ detail::log(Logger::Info, Fmt, std::forward<Ts>(Vals)...);
+}
+// vlog() is used for details often needed for debugging clangd sessions.
+// This level would typically be enabled for clangd developers.
+template <typename... Ts> void vlog(const char *Fmt, Ts &&... Vals) {
+ detail::log(Logger::Verbose, Fmt, std::forward<Ts>(Vals)...);
+}
+// dlog only logs if --debug was passed, or --debug_only=Basename.
+// This level would be enabled in a targeted way when debugging.
+#define dlog(...) \
+ DEBUG_WITH_TYPE(::clang::clangd::detail::debugType(__FILE__), \
+ ::clang::clangd::detail::log(Logger::Debug, __VA_ARGS__))
+
/// Only one LoggingSession can be active at a time.
class LoggingSession {
public:
diff --git a/clangd/Protocol.cpp b/clangd/Protocol.cpp
index b3290e15..202fbfb0 100644
--- a/clangd/Protocol.cpp
+++ b/clangd/Protocol.cpp
@@ -23,26 +23,28 @@
namespace clang {
namespace clangd {
+using namespace llvm;
URIForFile::URIForFile(std::string AbsPath) {
assert(llvm::sys::path::is_absolute(AbsPath) && "the path is relative");
File = std::move(AbsPath);
}
-bool fromJSON(const json::Expr &E, URIForFile &R) {
- if (auto S = E.asString()) {
+bool fromJSON(const json::Value &E, URIForFile &R) {
+ if (auto S = E.getAsString()) {
auto U = URI::parse(*S);
if (!U) {
- log("Failed to parse URI " + *S + ": " + llvm::toString(U.takeError()));
+ elog("Failed to parse URI {0}: {1}", *S, U.takeError());
return false;
}
if (U->scheme() != "file" && U->scheme() != "test") {
- log("Clangd only supports 'file' URI scheme for workspace files: " + *S);
+ elog("Clangd only supports 'file' URI scheme for workspace files: {0}",
+ *S);
return false;
}
auto Path = URI::resolve(*U);
if (!Path) {
- log(llvm::toString(Path.takeError()));
+ log("{0}", Path.takeError());
return false;
}
R = URIForFile(*Path);
@@ -51,28 +53,28 @@ bool fromJSON(const json::Expr &E, URIForFile &R) {
return false;
}
-json::Expr toJSON(const URIForFile &U) { return U.uri(); }
+json::Value toJSON(const URIForFile &U) { return U.uri(); }
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const URIForFile &U) {
return OS << U.uri();
}
-json::Expr toJSON(const TextDocumentIdentifier &R) {
- return json::obj{{"uri", R.uri}};
+json::Value toJSON(const TextDocumentIdentifier &R) {
+ return json::Object{{"uri", R.uri}};
}
-bool fromJSON(const json::Expr &Params, TextDocumentIdentifier &R) {
+bool fromJSON(const json::Value &Params, TextDocumentIdentifier &R) {
json::ObjectMapper O(Params);
return O && O.map("uri", R.uri);
}
-bool fromJSON(const json::Expr &Params, Position &R) {
+bool fromJSON(const json::Value &Params, Position &R) {
json::ObjectMapper O(Params);
return O && O.map("line", R.line) && O.map("character", R.character);
}
-json::Expr toJSON(const Position &P) {
- return json::obj{
+json::Value toJSON(const Position &P) {
+ return json::Object{
{"line", P.line},
{"character", P.character},
};
@@ -82,13 +84,13 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Position &P) {
return OS << P.line << ':' << P.character;
}
-bool fromJSON(const json::Expr &Params, Range &R) {
+bool fromJSON(const json::Value &Params, Range &R) {
json::ObjectMapper O(Params);
return O && O.map("start", R.start) && O.map("end", R.end);
}
-json::Expr toJSON(const Range &P) {
- return json::obj{
+json::Value toJSON(const Range &P) {
+ return json::Object{
{"start", P.start},
{"end", P.end},
};
@@ -98,8 +100,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Range &R) {
return OS << R.start << '-' << R.end;
}
-json::Expr toJSON(const Location &P) {
- return json::obj{
+json::Value toJSON(const Location &P) {
+ return json::Object{
{"uri", P.uri},
{"range", P.range},
};
@@ -109,13 +111,13 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Location &L) {
return OS << L.range << '@' << L.uri;
}
-bool fromJSON(const json::Expr &Params, TextDocumentItem &R) {
+bool fromJSON(const json::Value &Params, TextDocumentItem &R) {
json::ObjectMapper O(Params);
return O && O.map("uri", R.uri) && O.map("languageId", R.languageId) &&
O.map("version", R.version) && O.map("text", R.text);
}
-bool fromJSON(const json::Expr &Params, Metadata &R) {
+bool fromJSON(const json::Value &Params, Metadata &R) {
json::ObjectMapper O(Params);
if (!O)
return false;
@@ -123,13 +125,13 @@ bool fromJSON(const json::Expr &Params, Metadata &R) {
return true;
}
-bool fromJSON(const json::Expr &Params, TextEdit &R) {
+bool fromJSON(const json::Value &Params, TextEdit &R) {
json::ObjectMapper O(Params);
return O && O.map("range", R.range) && O.map("newText", R.newText);
}
-json::Expr toJSON(const TextEdit &P) {
- return json::obj{
+json::Value toJSON(const TextEdit &P) {
+ return json::Object{
{"range", P.range},
{"newText", P.newText},
};
@@ -141,8 +143,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const TextEdit &TE) {
return OS << '"';
}
-bool fromJSON(const json::Expr &E, TraceLevel &Out) {
- if (auto S = E.asString()) {
+bool fromJSON(const json::Value &E, TraceLevel &Out) {
+ if (auto S = E.getAsString()) {
if (*S == "off") {
Out = TraceLevel::Off;
return true;
@@ -157,7 +159,7 @@ bool fromJSON(const json::Expr &E, TraceLevel &Out) {
return false;
}
-bool fromJSON(const json::Expr &Params, CompletionItemClientCapabilities &R) {
+bool fromJSON(const json::Value &Params, CompletionItemClientCapabilities &R) {
json::ObjectMapper O(Params);
if (!O)
return false;
@@ -166,7 +168,7 @@ bool fromJSON(const json::Expr &Params, CompletionItemClientCapabilities &R) {
return true;
}
-bool fromJSON(const json::Expr &Params, CompletionClientCapabilities &R) {
+bool fromJSON(const json::Value &Params, CompletionClientCapabilities &R) {
json::ObjectMapper O(Params);
if (!O)
return false;
@@ -176,8 +178,8 @@ bool fromJSON(const json::Expr &Params, CompletionClientCapabilities &R) {
return true;
}
-bool fromJSON(const json::Expr &E, SymbolKind &Out) {
- if (auto T = E.asInteger()) {
+bool fromJSON(const json::Value &E, SymbolKind &Out) {
+ if (auto T = E.getAsInteger()) {
if (*T < static_cast<int>(SymbolKind::File) ||
*T > static_cast<int>(SymbolKind::TypeParameter))
return false;
@@ -187,8 +189,8 @@ bool fromJSON(const json::Expr &E, SymbolKind &Out) {
return false;
}
-bool fromJSON(const json::Expr &E, std::vector<SymbolKind> &Out) {
- if (auto *A = E.asArray()) {
+bool fromJSON(const json::Value &E, std::vector<SymbolKind> &Out) {
+ if (auto *A = E.getAsArray()) {
Out.clear();
for (size_t I = 0; I < A->size(); ++I) {
SymbolKind KindOut;
@@ -200,16 +202,16 @@ bool fromJSON(const json::Expr &E, std::vector<SymbolKind> &Out) {
return false;
}
-bool fromJSON(const json::Expr &Params, SymbolKindCapabilities &R) {
+bool fromJSON(const json::Value &Params, SymbolKindCapabilities &R) {
json::ObjectMapper O(Params);
return O && O.map("valueSet", R.valueSet);
}
SymbolKind adjustKindToCapability(SymbolKind Kind,
- SymbolKindBitset &supportedSymbolKinds) {
+ SymbolKindBitset &SupportedSymbolKinds) {
auto KindVal = static_cast<size_t>(Kind);
- if (KindVal >= SymbolKindMin && KindVal <= supportedSymbolKinds.size() &&
- supportedSymbolKinds[KindVal])
+ if (KindVal >= SymbolKindMin && KindVal <= SupportedSymbolKinds.size() &&
+ SupportedSymbolKinds[KindVal])
return Kind;
switch (Kind) {
@@ -223,17 +225,17 @@ SymbolKind adjustKindToCapability(SymbolKind Kind,
}
}
-bool fromJSON(const json::Expr &Params, WorkspaceSymbolCapabilities &R) {
+bool fromJSON(const json::Value &Params, WorkspaceSymbolCapabilities &R) {
json::ObjectMapper O(Params);
return O && O.map("symbolKind", R.symbolKind);
}
-bool fromJSON(const json::Expr &Params, WorkspaceClientCapabilities &R) {
+bool fromJSON(const json::Value &Params, WorkspaceClientCapabilities &R) {
json::ObjectMapper O(Params);
return O && O.map("symbol", R.symbol);
}
-bool fromJSON(const json::Expr &Params, TextDocumentClientCapabilities &R) {
+bool fromJSON(const json::Value &Params, TextDocumentClientCapabilities &R) {
json::ObjectMapper O(Params);
if (!O)
return false;
@@ -241,7 +243,7 @@ bool fromJSON(const json::Expr &Params, TextDocumentClientCapabilities &R) {
return true;
}
-bool fromJSON(const json::Expr &Params, ClientCapabilities &R) {
+bool fromJSON(const json::Value &Params, ClientCapabilities &R) {
json::ObjectMapper O(Params);
if (!O)
return false;
@@ -250,7 +252,7 @@ bool fromJSON(const json::Expr &Params, ClientCapabilities &R) {
return true;
}
-bool fromJSON(const json::Expr &Params, InitializeParams &R) {
+bool fromJSON(const json::Value &Params, InitializeParams &R) {
json::ObjectMapper O(Params);
if (!O)
return false;
@@ -261,30 +263,30 @@ bool fromJSON(const json::Expr &Params, InitializeParams &R) {
O.map("rootPath", R.rootPath);
O.map("capabilities", R.capabilities);
O.map("trace", R.trace);
- // initializationOptions, capabilities unused
+ O.map("initializationOptions", R.initializationOptions);
return true;
}
-bool fromJSON(const json::Expr &Params, DidOpenTextDocumentParams &R) {
+bool fromJSON(const json::Value &Params, DidOpenTextDocumentParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("metadata", R.metadata);
}
-bool fromJSON(const json::Expr &Params, DidCloseTextDocumentParams &R) {
+bool fromJSON(const json::Value &Params, DidCloseTextDocumentParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument);
}
-bool fromJSON(const json::Expr &Params, DidChangeTextDocumentParams &R) {
+bool fromJSON(const json::Value &Params, DidChangeTextDocumentParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("contentChanges", R.contentChanges) &&
O.map("wantDiagnostics", R.wantDiagnostics);
}
-bool fromJSON(const json::Expr &E, FileChangeType &Out) {
- if (auto T = E.asInteger()) {
+bool fromJSON(const json::Value &E, FileChangeType &Out) {
+ if (auto T = E.getAsInteger()) {
if (*T < static_cast<int>(FileChangeType::Created) ||
*T > static_cast<int>(FileChangeType::Deleted))
return false;
@@ -294,55 +296,60 @@ bool fromJSON(const json::Expr &E, FileChangeType &Out) {
return false;
}
-bool fromJSON(const json::Expr &Params, FileEvent &R) {
+bool fromJSON(const json::Value &Params, FileEvent &R) {
json::ObjectMapper O(Params);
return O && O.map("uri", R.uri) && O.map("type", R.type);
}
-bool fromJSON(const json::Expr &Params, DidChangeWatchedFilesParams &R) {
+bool fromJSON(const json::Value &Params, DidChangeWatchedFilesParams &R) {
json::ObjectMapper O(Params);
return O && O.map("changes", R.changes);
}
-bool fromJSON(const json::Expr &Params, TextDocumentContentChangeEvent &R) {
+bool fromJSON(const json::Value &Params, TextDocumentContentChangeEvent &R) {
json::ObjectMapper O(Params);
return O && O.map("range", R.range) && O.map("rangeLength", R.rangeLength) &&
O.map("text", R.text);
}
-bool fromJSON(const json::Expr &Params, FormattingOptions &R) {
+bool fromJSON(const json::Value &Params, FormattingOptions &R) {
json::ObjectMapper O(Params);
return O && O.map("tabSize", R.tabSize) &&
O.map("insertSpaces", R.insertSpaces);
}
-json::Expr toJSON(const FormattingOptions &P) {
- return json::obj{
+json::Value toJSON(const FormattingOptions &P) {
+ return json::Object{
{"tabSize", P.tabSize},
{"insertSpaces", P.insertSpaces},
};
}
-bool fromJSON(const json::Expr &Params, DocumentRangeFormattingParams &R) {
+bool fromJSON(const json::Value &Params, DocumentRangeFormattingParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("range", R.range) && O.map("options", R.options);
}
-bool fromJSON(const json::Expr &Params, DocumentOnTypeFormattingParams &R) {
+bool fromJSON(const json::Value &Params, DocumentOnTypeFormattingParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position) && O.map("ch", R.ch) &&
O.map("options", R.options);
}
-bool fromJSON(const json::Expr &Params, DocumentFormattingParams &R) {
+bool fromJSON(const json::Value &Params, DocumentFormattingParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("options", R.options);
}
-bool fromJSON(const json::Expr &Params, Diagnostic &R) {
+bool fromJSON(const json::Value &Params, DocumentSymbolParams &R) {
+ json::ObjectMapper O(Params);
+ return O && O.map("textDocument", R.textDocument);
+}
+
+bool fromJSON(const json::Value &Params, Diagnostic &R) {
json::ObjectMapper O(Params);
if (!O || !O.map("range", R.range) || !O.map("message", R.message))
return false;
@@ -350,7 +357,7 @@ bool fromJSON(const json::Expr &Params, Diagnostic &R) {
return true;
}
-bool fromJSON(const json::Expr &Params, CodeActionContext &R) {
+bool fromJSON(const json::Value &Params, CodeActionContext &R) {
json::ObjectMapper O(Params);
return O && O.map("diagnostics", R.diagnostics);
}
@@ -377,25 +384,25 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diagnostic &D) {
return OS << '(' << D.severity << "): " << D.message << "]";
}
-bool fromJSON(const json::Expr &Params, CodeActionParams &R) {
+bool fromJSON(const json::Value &Params, CodeActionParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("range", R.range) && O.map("context", R.context);
}
-bool fromJSON(const json::Expr &Params, WorkspaceEdit &R) {
+bool fromJSON(const json::Value &Params, WorkspaceEdit &R) {
json::ObjectMapper O(Params);
return O && O.map("changes", R.changes);
}
const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND =
"clangd.applyFix";
-bool fromJSON(const json::Expr &Params, ExecuteCommandParams &R) {
+bool fromJSON(const json::Value &Params, ExecuteCommandParams &R) {
json::ObjectMapper O(Params);
if (!O || !O.map("command", R.command))
return false;
- auto Args = Params.asObject()->getArray("arguments");
+ auto Args = Params.getAsObject()->getArray("arguments");
if (R.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) {
return Args && Args->size() == 1 &&
fromJSON(Args->front(), R.workspaceEdit);
@@ -403,8 +410,8 @@ bool fromJSON(const json::Expr &Params, ExecuteCommandParams &R) {
return false; // Unrecognized command.
}
-json::Expr toJSON(const SymbolInformation &P) {
- return json::obj{
+json::Value toJSON(const SymbolInformation &P) {
+ return json::Object{
{"name", P.name},
{"kind", static_cast<int>(P.kind)},
{"location", P.location},
@@ -418,32 +425,32 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
return O;
}
-bool fromJSON(const json::Expr &Params, WorkspaceSymbolParams &R) {
+bool fromJSON(const json::Value &Params, WorkspaceSymbolParams &R) {
json::ObjectMapper O(Params);
return O && O.map("query", R.query);
}
-json::Expr toJSON(const Command &C) {
- auto Cmd = json::obj{{"title", C.title}, {"command", C.command}};
+json::Value toJSON(const Command &C) {
+ auto Cmd = json::Object{{"title", C.title}, {"command", C.command}};
if (C.workspaceEdit)
Cmd["arguments"] = {*C.workspaceEdit};
return std::move(Cmd);
}
-json::Expr toJSON(const WorkspaceEdit &WE) {
+json::Value toJSON(const WorkspaceEdit &WE) {
if (!WE.changes)
- return json::obj{};
- json::obj FileChanges;
+ return json::Object{};
+ json::Object FileChanges;
for (auto &Change : *WE.changes)
- FileChanges[Change.first] = json::ary(Change.second);
- return json::obj{{"changes", std::move(FileChanges)}};
+ FileChanges[Change.first] = json::Array(Change.second);
+ return json::Object{{"changes", std::move(FileChanges)}};
}
-json::Expr toJSON(const ApplyWorkspaceEditParams &Params) {
- return json::obj{{"edit", Params.edit}};
+json::Value toJSON(const ApplyWorkspaceEditParams &Params) {
+ return json::Object{{"edit", Params.edit}};
}
-bool fromJSON(const json::Expr &Params, TextDocumentPositionParams &R) {
+bool fromJSON(const json::Value &Params, TextDocumentPositionParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position);
@@ -459,18 +466,18 @@ static StringRef toTextKind(MarkupKind Kind) {
llvm_unreachable("Invalid MarkupKind");
}
-json::Expr toJSON(const MarkupContent &MC) {
+json::Value toJSON(const MarkupContent &MC) {
if (MC.value.empty())
return nullptr;
- return json::obj{
+ return json::Object{
{"kind", toTextKind(MC.kind)},
{"value", MC.value},
};
}
-json::Expr toJSON(const Hover &H) {
- json::obj Result{{"contents", toJSON(H.contents)}};
+json::Value toJSON(const Hover &H) {
+ json::Object Result{{"contents", toJSON(H.contents)}};
if (H.range.hasValue())
Result["range"] = toJSON(*H.range);
@@ -478,9 +485,9 @@ json::Expr toJSON(const Hover &H) {
return std::move(Result);
}
-json::Expr toJSON(const CompletionItem &CI) {
+json::Value toJSON(const CompletionItem &CI) {
assert(!CI.label.empty() && "completion item label is required");
- json::obj Result{{"label", CI.label}};
+ json::Object Result{{"label", CI.label}};
if (CI.kind != CompletionItemKind::Missing)
Result["kind"] = static_cast<int>(CI.kind);
if (!CI.detail.empty())
@@ -498,7 +505,7 @@ json::Expr toJSON(const CompletionItem &CI) {
if (CI.textEdit)
Result["textEdit"] = *CI.textEdit;
if (!CI.additionalTextEdits.empty())
- Result["additionalTextEdits"] = json::ary(CI.additionalTextEdits);
+ Result["additionalTextEdits"] = json::Array(CI.additionalTextEdits);
return std::move(Result);
}
@@ -512,26 +519,26 @@ bool operator<(const CompletionItem &L, const CompletionItem &R) {
(R.sortText.empty() ? R.label : R.sortText);
}
-json::Expr toJSON(const CompletionList &L) {
- return json::obj{
+json::Value toJSON(const CompletionList &L) {
+ return json::Object{
{"isIncomplete", L.isIncomplete},
- {"items", json::ary(L.items)},
+ {"items", json::Array(L.items)},
};
}
-json::Expr toJSON(const ParameterInformation &PI) {
+json::Value toJSON(const ParameterInformation &PI) {
assert(!PI.label.empty() && "parameter information label is required");
- json::obj Result{{"label", PI.label}};
+ json::Object Result{{"label", PI.label}};
if (!PI.documentation.empty())
Result["documentation"] = PI.documentation;
return std::move(Result);
}
-json::Expr toJSON(const SignatureInformation &SI) {
+json::Value toJSON(const SignatureInformation &SI) {
assert(!SI.label.empty() && "signature information label is required");
- json::obj Result{
+ json::Object Result{
{"label", SI.label},
- {"parameters", json::ary(SI.parameters)},
+ {"parameters", json::Array(SI.parameters)},
};
if (!SI.documentation.empty())
Result["documentation"] = SI.documentation;
@@ -544,26 +551,26 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
return O;
}
-json::Expr toJSON(const SignatureHelp &SH) {
+json::Value toJSON(const SignatureHelp &SH) {
assert(SH.activeSignature >= 0 &&
"Unexpected negative value for number of active signatures.");
assert(SH.activeParameter >= 0 &&
"Unexpected negative value for active parameter index");
- return json::obj{
+ return json::Object{
{"activeSignature", SH.activeSignature},
{"activeParameter", SH.activeParameter},
- {"signatures", json::ary(SH.signatures)},
+ {"signatures", json::Array(SH.signatures)},
};
}
-bool fromJSON(const json::Expr &Params, RenameParams &R) {
+bool fromJSON(const json::Value &Params, RenameParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position) && O.map("newName", R.newName);
}
-json::Expr toJSON(const DocumentHighlight &DH) {
- return json::obj{
+json::Value toJSON(const DocumentHighlight &DH) {
+ return json::Object{
{"range", toJSON(DH.range)},
{"kind", static_cast<int>(DH.kind)},
};
@@ -579,14 +586,23 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
return O;
}
-bool fromJSON(const json::Expr &Params, DidChangeConfigurationParams &CCP) {
+bool fromJSON(const json::Value &Params, DidChangeConfigurationParams &CCP) {
json::ObjectMapper O(Params);
return O && O.map("settings", CCP.settings);
}
-bool fromJSON(const json::Expr &Params, ClangdConfigurationParamsChange &CCPC) {
+bool fromJSON(const llvm::json::Value &Params,
+ ClangdCompileCommand &CDbUpdate) {
+ json::ObjectMapper O(Params);
+ return O && O.map("workingDirectory", CDbUpdate.workingDirectory) &&
+ O.map("compilationCommand", CDbUpdate.compilationCommand);
+}
+
+bool fromJSON(const json::Value &Params,
+ ClangdConfigurationParamsChange &CCPC) {
json::ObjectMapper O(Params);
- return O && O.map("compilationDatabasePath", CCPC.compilationDatabasePath);
+ return O && O.map("compilationDatabasePath", CCPC.compilationDatabasePath) &&
+ O.map("compilationDatabaseChanges", CCPC.compilationDatabaseChanges);
}
} // namespace clangd
diff --git a/clangd/Protocol.h b/clangd/Protocol.h
index c3941583..b0d810e8 100644
--- a/clangd/Protocol.h
+++ b/clangd/Protocol.h
@@ -14,7 +14,7 @@
// when they're needed.
//
// Each struct has a toJSON and fromJSON function, that converts between
-// the struct and a JSON representation. (See JSONExpr.h)
+// the struct and a JSON representation. (See JSON.h)
//
// Some structs also have operator<< serialization. This is for debugging and
// tests, and is not generally machine-readable.
@@ -24,9 +24,9 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H
-#include "JSONExpr.h"
#include "URI.h"
#include "llvm/ADT/Optional.h"
+#include "llvm/Support/JSON.h"
#include <bitset>
#include <string>
#include <vector>
@@ -76,15 +76,15 @@ private:
};
/// Serialize/deserialize \p URIForFile to/from a string URI.
-json::Expr toJSON(const URIForFile &U);
-bool fromJSON(const json::Expr &, URIForFile &);
+llvm::json::Value toJSON(const URIForFile &U);
+bool fromJSON(const llvm::json::Value &, URIForFile &);
struct TextDocumentIdentifier {
/// The text document's URI.
URIForFile uri;
};
-json::Expr toJSON(const TextDocumentIdentifier &);
-bool fromJSON(const json::Expr &, TextDocumentIdentifier &);
+llvm::json::Value toJSON(const TextDocumentIdentifier &);
+bool fromJSON(const llvm::json::Value &, TextDocumentIdentifier &);
struct Position {
/// Line position in a document (zero-based).
@@ -111,8 +111,8 @@ struct Position {
std::tie(RHS.line, RHS.character);
}
};
-bool fromJSON(const json::Expr &, Position &);
-json::Expr toJSON(const Position &);
+bool fromJSON(const llvm::json::Value &, Position &);
+llvm::json::Value toJSON(const Position &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Position &);
struct Range {
@@ -134,8 +134,8 @@ struct Range {
bool contains(Position Pos) const { return start <= Pos && Pos < end; }
};
-bool fromJSON(const json::Expr &, Range &);
-json::Expr toJSON(const Range &);
+bool fromJSON(const llvm::json::Value &, Range &);
+llvm::json::Value toJSON(const Range &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Range &);
struct Location {
@@ -155,13 +155,13 @@ struct Location {
return std::tie(LHS.uri, LHS.range) < std::tie(RHS.uri, RHS.range);
}
};
-json::Expr toJSON(const Location &);
+llvm::json::Value toJSON(const Location &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Location &);
struct Metadata {
std::vector<std::string> extraFlags;
};
-bool fromJSON(const json::Expr &, Metadata &);
+bool fromJSON(const llvm::json::Value &, Metadata &);
struct TextEdit {
/// The range of the text document to be manipulated. To insert
@@ -171,9 +171,13 @@ struct TextEdit {
/// The string to be inserted. For delete operations use an
/// empty string.
std::string newText;
+
+ bool operator==(const TextEdit &rhs) const {
+ return newText == rhs.newText && range == rhs.range;
+ }
};
-bool fromJSON(const json::Expr &, TextEdit &);
-json::Expr toJSON(const TextEdit &);
+bool fromJSON(const llvm::json::Value &, TextEdit &);
+llvm::json::Value toJSON(const TextEdit &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TextEdit &);
struct TextDocumentItem {
@@ -189,17 +193,17 @@ struct TextDocumentItem {
/// The content of the opened text document.
std::string text;
};
-bool fromJSON(const json::Expr &, TextDocumentItem &);
+bool fromJSON(const llvm::json::Value &, TextDocumentItem &);
enum class TraceLevel {
Off = 0,
Messages = 1,
Verbose = 2,
};
-bool fromJSON(const json::Expr &E, TraceLevel &Out);
+bool fromJSON(const llvm::json::Value &E, TraceLevel &Out);
struct NoParams {};
-inline bool fromJSON(const json::Expr &, NoParams &) { return true; }
+inline bool fromJSON(const llvm::json::Value &, NoParams &) { return true; }
using ShutdownParams = NoParams;
using ExitParams = NoParams;
@@ -227,7 +231,7 @@ struct CompletionItemClientCapabilities {
// NOTE: not used by clangd at the moment.
// std::vector<MarkupKind> documentationFormat;
};
-bool fromJSON(const json::Expr &, CompletionItemClientCapabilities &);
+bool fromJSON(const llvm::json::Value &, CompletionItemClientCapabilities &);
struct CompletionClientCapabilities {
/// Whether completion supports dynamic registration.
@@ -241,7 +245,7 @@ struct CompletionClientCapabilities {
/// `textDocument/completion` request.
bool contextSupport = false;
};
-bool fromJSON(const json::Expr &, CompletionClientCapabilities &);
+bool fromJSON(const llvm::json::Value &, CompletionClientCapabilities &);
/// A symbol kind.
enum class SymbolKind {
@@ -277,7 +281,7 @@ constexpr auto SymbolKindMin = static_cast<size_t>(SymbolKind::File);
constexpr auto SymbolKindMax = static_cast<size_t>(SymbolKind::TypeParameter);
using SymbolKindBitset = std::bitset<SymbolKindMax + 1>;
-bool fromJSON(const json::Expr &, SymbolKind &);
+bool fromJSON(const llvm::json::Value &, SymbolKind &);
struct SymbolKindCapabilities {
/// The SymbolKinds that the client supports. If not set, the client only
@@ -285,8 +289,8 @@ struct SymbolKindCapabilities {
/// value.
llvm::Optional<std::vector<SymbolKind>> valueSet;
};
-bool fromJSON(const json::Expr &, std::vector<SymbolKind> &);
-bool fromJSON(const json::Expr &, SymbolKindCapabilities &);
+bool fromJSON(const llvm::json::Value &, std::vector<SymbolKind> &);
+bool fromJSON(const llvm::json::Value &, SymbolKindCapabilities &);
SymbolKind adjustKindToCapability(SymbolKind Kind,
SymbolKindBitset &supportedSymbolKinds);
@@ -294,7 +298,7 @@ struct WorkspaceSymbolCapabilities {
/// Capabilities SymbolKind.
llvm::Optional<SymbolKindCapabilities> symbolKind;
};
-bool fromJSON(const json::Expr &, WorkspaceSymbolCapabilities &);
+bool fromJSON(const llvm::json::Value &, WorkspaceSymbolCapabilities &);
// FIXME: most of the capabilities are missing from this struct. Only the ones
// used by clangd are currently there.
@@ -302,7 +306,7 @@ struct WorkspaceClientCapabilities {
/// Capabilities specific to `workspace/symbol`.
llvm::Optional<WorkspaceSymbolCapabilities> symbol;
};
-bool fromJSON(const json::Expr &, WorkspaceClientCapabilities &);
+bool fromJSON(const llvm::json::Value &, WorkspaceClientCapabilities &);
// FIXME: most of the capabilities are missing from this struct. Only the ones
// used by clangd are currently there.
@@ -310,7 +314,7 @@ struct TextDocumentClientCapabilities {
/// Capabilities specific to the `textDocument/completion`
CompletionClientCapabilities completion;
};
-bool fromJSON(const json::Expr &, TextDocumentClientCapabilities &);
+bool fromJSON(const llvm::json::Value &, TextDocumentClientCapabilities &);
struct ClientCapabilities {
// Workspace specific client capabilities.
@@ -320,7 +324,31 @@ struct ClientCapabilities {
TextDocumentClientCapabilities textDocument;
};
-bool fromJSON(const json::Expr &, ClientCapabilities &);
+bool fromJSON(const llvm::json::Value &, ClientCapabilities &);
+
+/// Clangd extension that's used in the 'compilationDatabaseChanges' in
+/// workspace/didChangeConfiguration to record updates to the in-memory
+/// compilation database.
+struct ClangdCompileCommand {
+ std::string workingDirectory;
+ std::vector<std::string> compilationCommand;
+};
+bool fromJSON(const llvm::json::Value &, ClangdCompileCommand &);
+
+/// Clangd extension to set clangd-specific "initializationOptions" in the
+/// "initialize" request and for the "workspace/didChangeConfiguration"
+/// notification since the data received is described as 'any' type in LSP.
+struct ClangdConfigurationParamsChange {
+ llvm::Optional<std::string> compilationDatabasePath;
+
+ // The changes that happened to the compilation database.
+ // The key of the map is a file name.
+ llvm::Optional<std::map<std::string, ClangdCompileCommand>>
+ compilationDatabaseChanges;
+};
+bool fromJSON(const llvm::json::Value &, ClangdConfigurationParamsChange &);
+
+struct ClangdInitializationOptions : public ClangdConfigurationParamsChange {};
struct InitializeParams {
/// The process Id of the parent process that started
@@ -348,8 +376,12 @@ struct InitializeParams {
/// The initial trace setting. If omitted trace is disabled ('off').
llvm::Optional<TraceLevel> trace;
+
+ // We use this predefined struct because it is easier to use
+ // than the protocol specified type of 'any'.
+ llvm::Optional<ClangdInitializationOptions> initializationOptions;
};
-bool fromJSON(const json::Expr &, InitializeParams &);
+bool fromJSON(const llvm::json::Value &, InitializeParams &);
struct DidOpenTextDocumentParams {
/// The document that was opened.
@@ -358,13 +390,13 @@ struct DidOpenTextDocumentParams {
/// Extension storing per-file metadata, such as compilation flags.
llvm::Optional<Metadata> metadata;
};
-bool fromJSON(const json::Expr &, DidOpenTextDocumentParams &);
+bool fromJSON(const llvm::json::Value &, DidOpenTextDocumentParams &);
struct DidCloseTextDocumentParams {
/// The document that was closed.
TextDocumentIdentifier textDocument;
};
-bool fromJSON(const json::Expr &, DidCloseTextDocumentParams &);
+bool fromJSON(const llvm::json::Value &, DidCloseTextDocumentParams &);
struct TextDocumentContentChangeEvent {
/// The range of the document that changed.
@@ -376,7 +408,7 @@ struct TextDocumentContentChangeEvent {
/// The new text of the range/document.
std::string text;
};
-bool fromJSON(const json::Expr &, TextDocumentContentChangeEvent &);
+bool fromJSON(const llvm::json::Value &, TextDocumentContentChangeEvent &);
struct DidChangeTextDocumentParams {
/// The document that did change. The version number points
@@ -393,7 +425,7 @@ struct DidChangeTextDocumentParams {
/// This is a clangd extension.
llvm::Optional<bool> wantDiagnostics;
};
-bool fromJSON(const json::Expr &, DidChangeTextDocumentParams &);
+bool fromJSON(const llvm::json::Value &, DidChangeTextDocumentParams &);
enum class FileChangeType {
/// The file got created.
@@ -403,7 +435,7 @@ enum class FileChangeType {
/// The file got deleted.
Deleted = 3
};
-bool fromJSON(const json::Expr &E, FileChangeType &Out);
+bool fromJSON(const llvm::json::Value &E, FileChangeType &Out);
struct FileEvent {
/// The file's URI.
@@ -411,27 +443,20 @@ struct FileEvent {
/// The change type.
FileChangeType type = FileChangeType::Created;
};
-bool fromJSON(const json::Expr &, FileEvent &);
+bool fromJSON(const llvm::json::Value &, FileEvent &);
struct DidChangeWatchedFilesParams {
/// The actual file events.
std::vector<FileEvent> changes;
};
-bool fromJSON(const json::Expr &, DidChangeWatchedFilesParams &);
-
-/// Clangd extension to manage a workspace/didChangeConfiguration notification
-/// since the data received is described as 'any' type in LSP.
-struct ClangdConfigurationParamsChange {
- llvm::Optional<std::string> compilationDatabasePath;
-};
-bool fromJSON(const json::Expr &, ClangdConfigurationParamsChange &);
+bool fromJSON(const llvm::json::Value &, DidChangeWatchedFilesParams &);
struct DidChangeConfigurationParams {
// We use this predefined struct because it is easier to use
// than the protocol specified type of 'any'.
ClangdConfigurationParamsChange settings;
};
-bool fromJSON(const json::Expr &, DidChangeConfigurationParams &);
+bool fromJSON(const llvm::json::Value &, DidChangeConfigurationParams &);
struct FormattingOptions {
/// Size of a tab in spaces.
@@ -440,8 +465,8 @@ struct FormattingOptions {
/// Prefer spaces over tabs.
bool insertSpaces = false;
};
-bool fromJSON(const json::Expr &, FormattingOptions &);
-json::Expr toJSON(const FormattingOptions &);
+bool fromJSON(const llvm::json::Value &, FormattingOptions &);
+llvm::json::Value toJSON(const FormattingOptions &);
struct DocumentRangeFormattingParams {
/// The document to format.
@@ -453,7 +478,7 @@ struct DocumentRangeFormattingParams {
/// The format options
FormattingOptions options;
};
-bool fromJSON(const json::Expr &, DocumentRangeFormattingParams &);
+bool fromJSON(const llvm::json::Value &, DocumentRangeFormattingParams &);
struct DocumentOnTypeFormattingParams {
/// The document to format.
@@ -468,7 +493,7 @@ struct DocumentOnTypeFormattingParams {
/// The format options.
FormattingOptions options;
};
-bool fromJSON(const json::Expr &, DocumentOnTypeFormattingParams &);
+bool fromJSON(const llvm::json::Value &, DocumentOnTypeFormattingParams &);
struct DocumentFormattingParams {
/// The document to format.
@@ -477,7 +502,13 @@ struct DocumentFormattingParams {
/// The format options
FormattingOptions options;
};
-bool fromJSON(const json::Expr &, DocumentFormattingParams &);
+bool fromJSON(const llvm::json::Value &, DocumentFormattingParams &);
+
+struct DocumentSymbolParams {
+ // The text document to find symbols in.
+ TextDocumentIdentifier textDocument;
+};
+bool fromJSON(const llvm::json::Value &, DocumentSymbolParams &);
struct Diagnostic {
/// The range at which the message applies.
@@ -510,14 +541,14 @@ struct LSPDiagnosticCompare {
return std::tie(LHS.range, LHS.message) < std::tie(RHS.range, RHS.message);
}
};
-bool fromJSON(const json::Expr &, Diagnostic &);
+bool fromJSON(const llvm::json::Value &, Diagnostic &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Diagnostic &);
struct CodeActionContext {
/// An array of diagnostics.
std::vector<Diagnostic> diagnostics;
};
-bool fromJSON(const json::Expr &, CodeActionContext &);
+bool fromJSON(const llvm::json::Value &, CodeActionContext &);
struct CodeActionParams {
/// The document in which the command was invoked.
@@ -529,7 +560,7 @@ struct CodeActionParams {
/// Context carrying additional information.
CodeActionContext context;
};
-bool fromJSON(const json::Expr &, CodeActionParams &);
+bool fromJSON(const llvm::json::Value &, CodeActionParams &);
struct WorkspaceEdit {
/// Holds changes to existing resources.
@@ -538,8 +569,8 @@ struct WorkspaceEdit {
/// Note: "documentChanges" is not currently used because currently there is
/// no support for versioned edits.
};
-bool fromJSON(const json::Expr &, WorkspaceEdit &);
-json::Expr toJSON(const WorkspaceEdit &WE);
+bool fromJSON(const llvm::json::Value &, WorkspaceEdit &);
+llvm::json::Value toJSON(const WorkspaceEdit &WE);
/// Exact commands are not specified in the protocol so we define the
/// ones supported by Clangd here. The protocol specifies the command arguments
@@ -559,13 +590,13 @@ struct ExecuteCommandParams {
// Arguments
llvm::Optional<WorkspaceEdit> workspaceEdit;
};
-bool fromJSON(const json::Expr &, ExecuteCommandParams &);
+bool fromJSON(const llvm::json::Value &, ExecuteCommandParams &);
struct Command : public ExecuteCommandParams {
std::string title;
};
-json::Expr toJSON(const Command &C);
+llvm::json::Value toJSON(const Command &C);
/// Represents information about programming constructs like variables, classes,
/// interfaces etc.
@@ -582,7 +613,7 @@ struct SymbolInformation {
/// The name of the symbol containing this symbol.
std::string containerName;
};
-json::Expr toJSON(const SymbolInformation &);
+llvm::json::Value toJSON(const SymbolInformation &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolInformation &);
/// The parameters of a Workspace Symbol Request.
@@ -590,12 +621,12 @@ struct WorkspaceSymbolParams {
/// A non-empty query string
std::string query;
};
-bool fromJSON(const json::Expr &, WorkspaceSymbolParams &);
+bool fromJSON(const llvm::json::Value &, WorkspaceSymbolParams &);
struct ApplyWorkspaceEditParams {
WorkspaceEdit edit;
};
-json::Expr toJSON(const ApplyWorkspaceEditParams &);
+llvm::json::Value toJSON(const ApplyWorkspaceEditParams &);
struct TextDocumentPositionParams {
/// The text document.
@@ -604,7 +635,7 @@ struct TextDocumentPositionParams {
/// The position inside the text document.
Position position;
};
-bool fromJSON(const json::Expr &, TextDocumentPositionParams &);
+bool fromJSON(const llvm::json::Value &, TextDocumentPositionParams &);
enum class MarkupKind {
PlainText,
@@ -615,7 +646,7 @@ struct MarkupContent {
MarkupKind kind = MarkupKind::PlainText;
std::string value;
};
-json::Expr toJSON(const MarkupContent &MC);
+llvm::json::Value toJSON(const MarkupContent &MC);
struct Hover {
/// The hover's content
@@ -625,7 +656,7 @@ struct Hover {
/// that is used to visualize a hover, e.g. by changing the background color.
llvm::Optional<Range> range;
};
-json::Expr toJSON(const Hover &H);
+llvm::json::Value toJSON(const Hover &H);
/// The kind of a completion entry.
enum class CompletionItemKind {
@@ -668,20 +699,6 @@ enum class InsertTextFormat {
Snippet = 2,
};
-/// Provides details for how a completion item was scored.
-/// This can be used for client-side filtering of completion items as the
-/// user keeps typing.
-/// This is a clangd extension.
-struct CompletionItemScores {
- /// The score that items are ranked by.
- /// This is filterScore * symbolScore.
- float finalScore = 0.f;
- /// How the partial identifier matched filterText. [0-1]
- float filterScore = 0.f;
- /// How the symbol fits, ignoring the partial identifier.
- float symbolScore = 0.f;
-};
-
struct CompletionItem {
/// The label of this completion item. By default also the text that is
/// inserted when selecting this completion.
@@ -702,9 +719,6 @@ struct CompletionItem {
/// When `falsy` the label is used.
std::string sortText;
- /// Details about the quality of this completion item. (clangd extension)
- llvm::Optional<CompletionItemScores> scoreInfo;
-
/// A string that should be used when filtering a set of completion items.
/// When `falsy` the label is used.
std::string filterText;
@@ -735,7 +749,7 @@ struct CompletionItem {
// data?: any - A data entry field that is preserved on a completion item
// between a completion and a completion resolve request.
};
-json::Expr toJSON(const CompletionItem &);
+llvm::json::Value toJSON(const CompletionItem &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const CompletionItem &);
bool operator<(const CompletionItem &, const CompletionItem &);
@@ -749,7 +763,7 @@ struct CompletionList {
/// The completion items.
std::vector<CompletionItem> items;
};
-json::Expr toJSON(const CompletionList &);
+llvm::json::Value toJSON(const CompletionList &);
/// A single parameter of a particular signature.
struct ParameterInformation {
@@ -760,7 +774,7 @@ struct ParameterInformation {
/// The documentation of this parameter. Optional.
std::string documentation;
};
-json::Expr toJSON(const ParameterInformation &);
+llvm::json::Value toJSON(const ParameterInformation &);
/// Represents the signature of something callable.
struct SignatureInformation {
@@ -774,7 +788,7 @@ struct SignatureInformation {
/// The parameters of this signature.
std::vector<ParameterInformation> parameters;
};
-json::Expr toJSON(const SignatureInformation &);
+llvm::json::Value toJSON(const SignatureInformation &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &,
const SignatureInformation &);
@@ -790,7 +804,7 @@ struct SignatureHelp {
/// The active parameter of the active signature.
int activeParameter = 0;
};
-json::Expr toJSON(const SignatureHelp &);
+llvm::json::Value toJSON(const SignatureHelp &);
struct RenameParams {
/// The document that was opened.
@@ -802,7 +816,7 @@ struct RenameParams {
/// The new name of the symbol.
std::string newName;
};
-bool fromJSON(const json::Expr &, RenameParams &);
+bool fromJSON(const llvm::json::Value &, RenameParams &);
enum class DocumentHighlightKind { Text = 1, Read = 2, Write = 3 };
@@ -829,7 +843,7 @@ struct DocumentHighlight {
return LHS.kind == RHS.kind && LHS.range == RHS.range;
}
};
-json::Expr toJSON(const DocumentHighlight &DH);
+llvm::json::Value toJSON(const DocumentHighlight &DH);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const DocumentHighlight &);
} // namespace clangd
diff --git a/clangd/ProtocolHandlers.cpp b/clangd/ProtocolHandlers.cpp
index 0432714a..deb5b9d0 100644
--- a/clangd/ProtocolHandlers.cpp
+++ b/clangd/ProtocolHandlers.cpp
@@ -15,6 +15,7 @@
using namespace clang;
using namespace clang::clangd;
+using namespace llvm;
namespace {
@@ -27,12 +28,12 @@ struct HandlerRegisterer {
void operator()(StringRef Method, void (ProtocolCallbacks::*Handler)(Param)) {
// Capture pointers by value, as the lambda will outlive this object.
auto *Callbacks = this->Callbacks;
- Dispatcher.registerHandler(Method, [=](const json::Expr &RawParams) {
+ Dispatcher.registerHandler(Method, [=](const json::Value &RawParams) {
typename std::remove_reference<Param>::type P;
if (fromJSON(RawParams, P)) {
(Callbacks->*Handler)(P);
} else {
- log("Failed to decode " + Method + " request.");
+ elog("Failed to decode {0} request.", Method);
}
});
}
@@ -66,6 +67,7 @@ void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher,
&ProtocolCallbacks::onSwitchSourceHeader);
Register("textDocument/rename", &ProtocolCallbacks::onRename);
Register("textDocument/hover", &ProtocolCallbacks::onHover);
+ Register("textDocument/documentSymbol", &ProtocolCallbacks::onDocumentSymbol);
Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent);
Register("workspace/executeCommand", &ProtocolCallbacks::onCommand);
Register("textDocument/documentHighlight",
diff --git a/clangd/ProtocolHandlers.h b/clangd/ProtocolHandlers.h
index dd181494..63fd99dc 100644
--- a/clangd/ProtocolHandlers.h
+++ b/clangd/ProtocolHandlers.h
@@ -38,6 +38,7 @@ public:
virtual void onDocumentDidChange(DidChangeTextDocumentParams &Params) = 0;
virtual void onDocumentDidClose(DidCloseTextDocumentParams &Params) = 0;
virtual void onDocumentFormatting(DocumentFormattingParams &Params) = 0;
+ virtual void onDocumentSymbol(DocumentSymbolParams &Params) = 0;
virtual void
onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams &Params) = 0;
virtual void
diff --git a/clangd/Quality.cpp b/clangd/Quality.cpp
index 050b0bf6..8cf794b0 100644
--- a/clangd/Quality.cpp
+++ b/clangd/Quality.cpp
@@ -7,25 +7,182 @@
//
//===---------------------------------------------------------------------===//
#include "Quality.h"
+#include "FileDistance.h"
+#include "URI.h"
#include "index/Index.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/DeclVisitor.h"
+#include "clang/Basic/CharInfo.h"
+#include "clang/Basic/SourceManager.h"
#include "clang/Sema/CodeCompleteConsumer.h"
+#include "llvm/Support/Casting.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
+#include <cmath>
namespace clang {
namespace clangd {
using namespace llvm;
+static bool isReserved(StringRef Name) {
+ // FIXME: Should we exclude _Bool and others recognized by the standard?
+ return Name.size() >= 2 && Name[0] == '_' &&
+ (isUppercase(Name[1]) || Name[1] == '_');
+}
-void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) {
- SemaCCPriority = SemaCCResult.Priority;
+static bool hasDeclInMainFile(const Decl &D) {
+ auto &SourceMgr = D.getASTContext().getSourceManager();
+ for (auto *Redecl : D.redecls()) {
+ auto Loc = SourceMgr.getSpellingLoc(Redecl->getLocation());
+ if (SourceMgr.isWrittenInMainFile(Loc))
+ return true;
+ }
+ return false;
+}
+
+static bool hasUsingDeclInMainFile(const CodeCompletionResult &R) {
+ const auto &Context = R.Declaration->getASTContext();
+ const auto &SourceMgr = Context.getSourceManager();
+ if (R.ShadowDecl) {
+ const auto Loc = SourceMgr.getExpansionLoc(R.ShadowDecl->getLocation());
+ if (SourceMgr.isWrittenInMainFile(Loc))
+ return true;
+ }
+ return false;
+}
+
+static SymbolQualitySignals::SymbolCategory categorize(const NamedDecl &ND) {
+ class Switch
+ : public ConstDeclVisitor<Switch, SymbolQualitySignals::SymbolCategory> {
+ public:
+#define MAP(DeclType, Category) \
+ SymbolQualitySignals::SymbolCategory Visit##DeclType(const DeclType *) { \
+ return SymbolQualitySignals::Category; \
+ }
+ MAP(NamespaceDecl, Namespace);
+ MAP(NamespaceAliasDecl, Namespace);
+ MAP(TypeDecl, Type);
+ MAP(TypeAliasTemplateDecl, Type);
+ MAP(ClassTemplateDecl, Type);
+ MAP(CXXConstructorDecl, Constructor);
+ MAP(ValueDecl, Variable);
+ MAP(VarTemplateDecl, Variable);
+ MAP(FunctionDecl, Function);
+ MAP(FunctionTemplateDecl, Function);
+ MAP(Decl, Unknown);
+#undef MAP
+ };
+ return Switch().Visit(&ND);
+}
+
+static SymbolQualitySignals::SymbolCategory
+categorize(const CodeCompletionResult &R) {
+ if (R.Declaration)
+ return categorize(*R.Declaration);
+ if (R.Kind == CodeCompletionResult::RK_Macro)
+ return SymbolQualitySignals::Macro;
+ // Everything else is a keyword or a pattern. Patterns are mostly keywords
+ // too, except a few which we recognize by cursor kind.
+ switch (R.CursorKind) {
+ case CXCursor_CXXMethod:
+ return SymbolQualitySignals::Function;
+ case CXCursor_ModuleImportDecl:
+ return SymbolQualitySignals::Namespace;
+ case CXCursor_MacroDefinition:
+ return SymbolQualitySignals::Macro;
+ case CXCursor_TypeRef:
+ return SymbolQualitySignals::Type;
+ case CXCursor_MemberRef:
+ return SymbolQualitySignals::Variable;
+ case CXCursor_Constructor:
+ return SymbolQualitySignals::Constructor;
+ default:
+ return SymbolQualitySignals::Keyword;
+ }
+}
+
+static SymbolQualitySignals::SymbolCategory
+categorize(const index::SymbolInfo &D) {
+ switch (D.Kind) {
+ case index::SymbolKind::Namespace:
+ case index::SymbolKind::NamespaceAlias:
+ return SymbolQualitySignals::Namespace;
+ case index::SymbolKind::Macro:
+ return SymbolQualitySignals::Macro;
+ case index::SymbolKind::Enum:
+ case index::SymbolKind::Struct:
+ case index::SymbolKind::Class:
+ case index::SymbolKind::Protocol:
+ case index::SymbolKind::Extension:
+ case index::SymbolKind::Union:
+ case index::SymbolKind::TypeAlias:
+ return SymbolQualitySignals::Type;
+ case index::SymbolKind::Function:
+ case index::SymbolKind::ClassMethod:
+ case index::SymbolKind::InstanceMethod:
+ case index::SymbolKind::StaticMethod:
+ case index::SymbolKind::InstanceProperty:
+ case index::SymbolKind::ClassProperty:
+ case index::SymbolKind::StaticProperty:
+ case index::SymbolKind::Destructor:
+ case index::SymbolKind::ConversionFunction:
+ return SymbolQualitySignals::Function;
+ case index::SymbolKind::Constructor:
+ return SymbolQualitySignals::Constructor;
+ case index::SymbolKind::Variable:
+ case index::SymbolKind::Field:
+ case index::SymbolKind::EnumConstant:
+ case index::SymbolKind::Parameter:
+ return SymbolQualitySignals::Variable;
+ case index::SymbolKind::Using:
+ case index::SymbolKind::Module:
+ case index::SymbolKind::Unknown:
+ return SymbolQualitySignals::Unknown;
+ }
+ llvm_unreachable("Unknown index::SymbolKind");
+}
+static bool isInstanceMember(const NamedDecl *ND) {
+ if (!ND)
+ return false;
+ if (const auto *TP = dyn_cast<FunctionTemplateDecl>(ND))
+ ND = TP->TemplateDecl::getTemplatedDecl();
+ if (const auto *CM = dyn_cast<CXXMethodDecl>(ND))
+ return !CM->isStatic();
+ return isa<FieldDecl>(ND); // Note that static fields are VarDecl.
+}
+
+static bool isInstanceMember(const index::SymbolInfo &D) {
+ switch (D.Kind) {
+ case index::SymbolKind::InstanceMethod:
+ case index::SymbolKind::InstanceProperty:
+ case index::SymbolKind::Field:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) {
if (SemaCCResult.Availability == CXAvailability_Deprecated)
Deprecated = true;
+
+ Category = categorize(SemaCCResult);
+
+ if (SemaCCResult.Declaration) {
+ if (auto *ID = SemaCCResult.Declaration->getIdentifier())
+ ReservedName = ReservedName || isReserved(ID->getName());
+ } else if (SemaCCResult.Kind == CodeCompletionResult::RK_Macro)
+ ReservedName = ReservedName || isReserved(SemaCCResult.Macro->getName());
}
void SymbolQualitySignals::merge(const Symbol &IndexResult) {
References = std::max(IndexResult.References, References);
+ Category = categorize(IndexResult.SymInfo);
+ ReservedName = ReservedName || isReserved(IndexResult.Name);
}
float SymbolQualitySignals::evaluate() const {
@@ -33,44 +190,184 @@ float SymbolQualitySignals::evaluate() const {
// This avoids a sharp gradient for tail symbols, and also neatly avoids the
// question of whether 0 references means a bad symbol or missing data.
- if (References >= 3)
- Score *= std::log(References);
-
- if (SemaCCPriority)
- // Map onto a 0-2 interval, so we don't reward/penalize non-Sema results.
- // Priority 80 is a really bad score.
- Score *= 2 - std::min<float>(80, SemaCCPriority) / 40;
+ if (References >= 10) {
+ // Use a sigmoid style boosting function, which flats out nicely for large
+ // numbers (e.g. 2.58 for 1M refererences).
+ // The following boosting function is equivalent to:
+ // m = 0.06
+ // f = 12.0
+ // boost = f * sigmoid(m * std::log(References)) - 0.5 * f + 0.59
+ // Sample data points: (10, 1.00), (100, 1.41), (1000, 1.82),
+ // (10K, 2.21), (100K, 2.58), (1M, 2.94)
+ float S = std::pow(References, -0.06);
+ Score *= 6.0 * (1 - S) / (1 + S) + 0.59;
+ }
if (Deprecated)
Score *= 0.1f;
+ if (ReservedName)
+ Score *= 0.1f;
+
+ switch (Category) {
+ case Keyword: // Often relevant, but misses most signals.
+ Score *= 4; // FIXME: important keywords should have specific boosts.
+ break;
+ case Type:
+ case Function:
+ case Variable:
+ Score *= 1.1f;
+ break;
+ case Namespace:
+ Score *= 0.8f;
+ break;
+ case Macro:
+ Score *= 0.2f;
+ break;
+ case Unknown:
+ case Constructor: // No boost constructors so they are after class types.
+ break;
+ }
return Score;
}
raw_ostream &operator<<(raw_ostream &OS, const SymbolQualitySignals &S) {
OS << formatv("=== Symbol quality: {0}\n", S.evaluate());
- if (S.SemaCCPriority)
- OS << formatv("\tSemaCCPriority: {0}\n", S.SemaCCPriority);
OS << formatv("\tReferences: {0}\n", S.References);
OS << formatv("\tDeprecated: {0}\n", S.Deprecated);
+ OS << formatv("\tReserved name: {0}\n", S.ReservedName);
+ OS << formatv("\tCategory: {0}\n", static_cast<int>(S.Category));
return OS;
}
+static SymbolRelevanceSignals::AccessibleScope
+computeScope(const NamedDecl *D) {
+ // Injected "Foo" within the class "Foo" has file scope, not class scope.
+ const DeclContext *DC = D->getDeclContext();
+ if (auto *R = dyn_cast_or_null<RecordDecl>(D))
+ if (R->isInjectedClassName())
+ DC = DC->getParent();
+ // Class constructor should have the same scope as the class.
+ if (isa<CXXConstructorDecl>(D))
+ DC = DC->getParent();
+ bool InClass = false;
+ for (; !DC->isFileContext(); DC = DC->getParent()) {
+ if (DC->isFunctionOrMethod())
+ return SymbolRelevanceSignals::FunctionScope;
+ InClass = InClass || DC->isRecord();
+ }
+ if (InClass)
+ return SymbolRelevanceSignals::ClassScope;
+ // This threshold could be tweaked, e.g. to treat module-visible as global.
+ if (D->getLinkageInternal() < ExternalLinkage)
+ return SymbolRelevanceSignals::FileScope;
+ return SymbolRelevanceSignals::GlobalScope;
+}
+
+void SymbolRelevanceSignals::merge(const Symbol &IndexResult) {
+ // FIXME: Index results always assumed to be at global scope. If Scope becomes
+ // relevant to non-completion requests, we should recognize class members etc.
+
+ SymbolURI = IndexResult.CanonicalDeclaration.FileURI;
+ IsInstanceMember |= isInstanceMember(IndexResult.SymInfo);
+}
+
void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
if (SemaCCResult.Availability == CXAvailability_NotAvailable ||
SemaCCResult.Availability == CXAvailability_NotAccessible)
Forbidden = true;
+
+ if (SemaCCResult.Declaration) {
+ // We boost things that have decls in the main file. We give a fixed score
+ // for all other declarations in sema as they are already included in the
+ // translation unit.
+ float DeclProximity = (hasDeclInMainFile(*SemaCCResult.Declaration) ||
+ hasUsingDeclInMainFile(SemaCCResult))
+ ? 1.0
+ : 0.6;
+ SemaProximityScore = std::max(DeclProximity, SemaProximityScore);
+ IsInstanceMember |= isInstanceMember(SemaCCResult.Declaration);
+ }
+
+ // Declarations are scoped, others (like macros) are assumed global.
+ if (SemaCCResult.Declaration)
+ Scope = std::min(Scope, computeScope(SemaCCResult.Declaration));
+
+ NeedsFixIts = !SemaCCResult.FixIts.empty();
+}
+
+static std::pair<float, unsigned> proximityScore(llvm::StringRef SymbolURI,
+ URIDistance *D) {
+ if (!D || SymbolURI.empty())
+ return {0.f, 0u};
+ unsigned Distance = D->distance(SymbolURI);
+ // Assume approximately default options are used for sensible scoring.
+ return {std::exp(Distance * -0.4f / FileDistanceOptions().UpCost), Distance};
}
float SymbolRelevanceSignals::evaluate() const {
+ float Score = 1;
+
if (Forbidden)
return 0;
- return NameMatch;
+
+ Score *= NameMatch;
+
+ // Proximity scores are [0,1] and we translate them into a multiplier in the
+ // range from 1 to 3.
+ Score *= 1 + 2 * std::max(proximityScore(SymbolURI, FileProximityMatch).first,
+ SemaProximityScore);
+
+ // Symbols like local variables may only be referenced within their scope.
+ // Conversely if we're in that scope, it's likely we'll reference them.
+ if (Query == CodeComplete) {
+ // The narrower the scope where a symbol is visible, the more likely it is
+ // to be relevant when it is available.
+ switch (Scope) {
+ case GlobalScope:
+ break;
+ case FileScope:
+ Score *= 1.5;
+ break;
+ case ClassScope:
+ Score *= 2;
+ break;
+ case FunctionScope:
+ Score *= 4;
+ break;
+ }
+ }
+
+ // Penalize non-instance members when they are accessed via a class instance.
+ if (!IsInstanceMember &&
+ (Context == CodeCompletionContext::CCC_DotMemberAccess ||
+ Context == CodeCompletionContext::CCC_ArrowMemberAccess)) {
+ Score *= 0.5;
+ }
+
+ // Penalize for FixIts.
+ if (NeedsFixIts)
+ Score *= 0.5;
+
+ return Score;
}
+
raw_ostream &operator<<(raw_ostream &OS, const SymbolRelevanceSignals &S) {
OS << formatv("=== Symbol relevance: {0}\n", S.evaluate());
OS << formatv("\tName match: {0}\n", S.NameMatch);
OS << formatv("\tForbidden: {0}\n", S.Forbidden);
+ OS << formatv("\tNeedsFixIts: {0}\n", S.NeedsFixIts);
+ OS << formatv("\tIsInstanceMember: {0}\n", S.IsInstanceMember);
+ OS << formatv("\tContext: {0}\n", getCompletionKindString(S.Context));
+ OS << formatv("\tSymbol URI: {0}\n", S.SymbolURI);
+ if (S.FileProximityMatch) {
+ auto Score = proximityScore(S.SymbolURI, S.FileProximityMatch);
+ OS << formatv("\tIndex proximity: {0} (distance={1})\n", Score.first,
+ Score.second);
+ }
+ OS << formatv("\tSema proximity: {0}\n", S.SemaProximityScore);
+ OS << formatv("\tQuery type: {0}\n", static_cast<int>(S.Query));
+ OS << formatv("\tScope: {0}\n", static_cast<int>(S.Scope));
return OS;
}
diff --git a/clangd/Quality.h b/clangd/Quality.h
index a67af4cd..54778b9f 100644
--- a/clangd/Quality.h
+++ b/clangd/Quality.h
@@ -26,6 +26,8 @@
//===---------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_QUALITY_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_QUALITY_H
+#include "clang/Sema/CodeCompleteConsumer.h"
+#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
#include <algorithm>
#include <functional>
@@ -37,6 +39,7 @@ namespace clang {
class CodeCompletionResult;
namespace clangd {
struct Symbol;
+class URIDistance;
// Signals structs are designed to be aggregated from 0 or more sources.
// A default instance has neutral signals, and sources are merged into it.
@@ -44,12 +47,22 @@ struct Symbol;
/// Attributes of a symbol that affect how much we like it.
struct SymbolQualitySignals {
- unsigned SemaCCPriority = 0; // 1-80, 1 is best. 0 means absent.
- // FIXME: this is actually a mix of symbol
- // quality and relevance. Untangle this.
bool Deprecated = false;
+ bool ReservedName = false; // __foo, _Foo are usually implementation details.
+ // FIXME: make these findable once user types _.
unsigned References = 0;
+ enum SymbolCategory {
+ Unknown = 0,
+ Variable,
+ Macro,
+ Type,
+ Function,
+ Constructor,
+ Namespace,
+ Keyword,
+ } Category = Unknown;
+
void merge(const CodeCompletionResult &SemaCCResult);
void merge(const Symbol &IndexResult);
@@ -61,11 +74,41 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &,
/// Attributes of a symbol-query pair that affect how much we like it.
struct SymbolRelevanceSignals {
- // 0-1 fuzzy-match score for unqualified name. Must be explicitly assigned.
+ /// 0-1+ fuzzy-match score for unqualified name. Must be explicitly assigned.
float NameMatch = 1;
bool Forbidden = false; // Unavailable (e.g const) or inaccessible (private).
+ /// Whether fixits needs to be applied for that completion or not.
+ bool NeedsFixIts = false;
+
+ URIDistance *FileProximityMatch = nullptr;
+ /// This is used to calculate proximity between the index symbol and the
+ /// query.
+ llvm::StringRef SymbolURI;
+ /// Proximity between best declaration and the query. [0-1], 1 is closest.
+ /// FIXME: unify with index proximity score - signals should be
+ /// source-independent.
+ float SemaProximityScore = 0;
+
+ // An approximate measure of where we expect the symbol to be used.
+ enum AccessibleScope {
+ FunctionScope,
+ ClassScope,
+ FileScope,
+ GlobalScope,
+ } Scope = GlobalScope;
+
+ enum QueryType {
+ CodeComplete,
+ Generic,
+ } Query = Generic;
+
+ CodeCompletionContext::Kind Context = CodeCompletionContext::CCC_Other;
+
+ // Whether symbol is an instance member of a class.
+ bool IsInstanceMember = false;
void merge(const CodeCompletionResult &SemaResult);
+ void merge(const Symbol &IndexResult);
// Condense these signals down to a single number, higher is better.
float evaluate() const;
diff --git a/clangd/SourceCode.cpp b/clangd/SourceCode.cpp
index 50a7f4e6..88ec2c95 100644
--- a/clangd/SourceCode.cpp
+++ b/clangd/SourceCode.cpp
@@ -8,9 +8,13 @@
//===----------------------------------------------------------------------===//
#include "SourceCode.h"
+#include "Logger.h"
+#include "clang/AST/ASTContext.h"
#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
+#include "llvm/Support/Path.h"
namespace clang {
namespace clangd {
@@ -181,5 +185,28 @@ std::vector<TextEdit> replacementsToEdits(StringRef Code,
return Edits;
}
+llvm::Optional<std::string>
+getAbsoluteFilePath(const FileEntry *F, const SourceManager &SourceMgr) {
+ SmallString<64> FilePath = F->tryGetRealPathName();
+ if (FilePath.empty())
+ FilePath = F->getName();
+ if (!llvm::sys::path::is_absolute(FilePath)) {
+ if (!SourceMgr.getFileManager().makeAbsolutePath(FilePath)) {
+ log("Could not turn relative path to absolute: {0}", FilePath);
+ return llvm::None;
+ }
+ }
+ return FilePath.str().str();
+}
+
+TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M,
+ const LangOptions &L) {
+ TextEdit Result;
+ Result.range =
+ halfOpenToRange(M, Lexer::makeFileCharRange(FixIt.RemoveRange, M, L));
+ Result.newText = FixIt.CodeToInsert;
+ return Result;
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/SourceCode.h b/clangd/SourceCode.h
index a61d411f..cb0c6728 100644
--- a/clangd/SourceCode.h
+++ b/clangd/SourceCode.h
@@ -14,6 +14,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H
#include "Protocol.h"
+#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Tooling/Core/Replacement.h"
@@ -61,6 +62,13 @@ TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R);
std::vector<TextEdit> replacementsToEdits(StringRef Code,
const tooling::Replacements &Repls);
+/// Get the absolute file path of a given file entry.
+llvm::Optional<std::string> getAbsoluteFilePath(const FileEntry *F,
+ const SourceManager &SourceMgr);
+
+TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M,
+ const LangOptions &L);
+
} // namespace clangd
} // namespace clang
#endif
diff --git a/clangd/TUScheduler.cpp b/clangd/TUScheduler.cpp
index c887dc90..f985e7d7 100644
--- a/clangd/TUScheduler.cpp
+++ b/clangd/TUScheduler.cpp
@@ -63,6 +63,14 @@ namespace {
class ASTWorker;
}
+static clang::clangd::Key<std::string> kFileBeingProcessed;
+
+llvm::Optional<llvm::StringRef> TUScheduler::getFileBeingProcessedInContext() {
+ if (auto *File = Context::current().get(kFileBeingProcessed))
+ return StringRef(*File);
+ return llvm::None;
+}
+
/// An LRU cache of idle ASTs.
/// Because we want to limit the overall number of these we retain, the cache
/// owns ASTs (and may evict them) while their workers are idle.
@@ -159,7 +167,7 @@ public:
/// is null, all requests will be processed on the calling thread
/// synchronously instead. \p Barrier is acquired when processing each
/// request, it is be used to limit the number of actively running threads.
- static ASTWorkerHandle Create(PathRef FileName,
+ static ASTWorkerHandle create(PathRef FileName,
TUScheduler::ASTCache &IdleASTs,
AsyncTaskRunner *Tasks, Semaphore &Barrier,
steady_clock::duration UpdateDebounce,
@@ -169,12 +177,19 @@ public:
~ASTWorker();
void update(ParseInputs Inputs, WantDiagnostics,
- UniqueFunction<void(std::vector<Diag>)> OnUpdated);
- void runWithAST(llvm::StringRef Name,
- UniqueFunction<void(llvm::Expected<InputsAndAST>)> Action);
+ llvm::unique_function<void(std::vector<Diag>)> OnUpdated);
+ void
+ runWithAST(llvm::StringRef Name,
+ llvm::unique_function<void(llvm::Expected<InputsAndAST>)> Action);
bool blockUntilIdle(Deadline Timeout) const;
std::shared_ptr<const PreambleData> getPossiblyStalePreamble() const;
+ /// Wait for the first build of preamble to finish. Preamble itself can be
+ /// accessed via getPossibleStalePreamble(). Note that this function will
+ /// return after an unsuccessful build of the preamble too, i.e. result of
+ /// getPossiblyStalePreamble() can be null even after this function returns.
+ void waitForFirstPreamble() const;
+
std::size_t getUsedBytes() const;
bool isASTCached() const;
@@ -186,7 +201,7 @@ private:
/// Signal that run() should finish processing pending requests and exit.
void stop();
/// Adds a new task to the end of the request queue.
- void startTask(llvm::StringRef Name, UniqueFunction<void()> Task,
+ void startTask(llvm::StringRef Name, llvm::unique_function<void()> Task,
llvm::Optional<WantDiagnostics> UpdateType);
/// Determines the next action to perform.
/// All actions that should never run are disarded.
@@ -197,7 +212,7 @@ private:
bool shouldSkipHeadLocked() const;
struct Request {
- UniqueFunction<void()> Action;
+ llvm::unique_function<void()> Action;
std::string Name;
steady_clock::time_point AddTime;
Context Ctx;
@@ -221,12 +236,15 @@ private:
Semaphore &Barrier;
/// Inputs, corresponding to the current state of AST.
ParseInputs FileInputs;
- /// CompilerInvocation used for FileInputs.
- std::unique_ptr<CompilerInvocation> Invocation;
+ /// Whether the diagnostics for the current FileInputs were reported to the
+ /// users before.
+ bool DiagsWereReported = false;
/// Size of the last AST
/// Guards members used by both TUScheduler and the worker thread.
mutable std::mutex Mutex;
std::shared_ptr<const PreambleData> LastBuiltPreamble; /* GUARDED_BY(Mutex) */
+ /// Becomes ready when the first preamble build finishes.
+ Notification PreambleWasBuilt;
/// Set to true to signal run() to finish processing.
bool Done; /* GUARDED_BY(Mutex) */
std::deque<Request> Requests; /* GUARDED_BY(Mutex) */
@@ -275,7 +293,7 @@ private:
std::shared_ptr<ASTWorker> Worker;
};
-ASTWorkerHandle ASTWorker::Create(PathRef FileName,
+ASTWorkerHandle ASTWorker::create(PathRef FileName,
TUScheduler::ASTCache &IdleASTs,
AsyncTaskRunner *Tasks, Semaphore &Barrier,
steady_clock::duration UpdateDebounce,
@@ -313,44 +331,95 @@ ASTWorker::~ASTWorker() {
#endif
}
-void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags,
- UniqueFunction<void(std::vector<Diag>)> OnUpdated) {
+void ASTWorker::update(
+ ParseInputs Inputs, WantDiagnostics WantDiags,
+ llvm::unique_function<void(std::vector<Diag>)> OnUpdated) {
auto Task = [=](decltype(OnUpdated) OnUpdated) mutable {
+ // Will be used to check if we can avoid rebuilding the AST.
+ bool InputsAreTheSame =
+ std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
+ std::tie(Inputs.CompileCommand, Inputs.Contents);
+
tooling::CompileCommand OldCommand = std::move(FileInputs.CompileCommand);
+ bool PrevDiagsWereReported = DiagsWereReported;
FileInputs = Inputs;
- // Remove the old AST if it's still in cache.
- IdleASTs.take(this);
+ DiagsWereReported = false;
- log("Updating file " + FileName + " with command [" +
- Inputs.CompileCommand.Directory + "] " +
+ log("Updating file {0} with command [{1}] {2}", FileName,
+ Inputs.CompileCommand.Directory,
llvm::join(Inputs.CompileCommand.CommandLine, " "));
// Rebuild the preamble and the AST.
- Invocation = buildCompilerInvocation(Inputs);
+ std::unique_ptr<CompilerInvocation> Invocation =
+ buildCompilerInvocation(Inputs);
if (!Invocation) {
- log("Could not build CompilerInvocation for file " + FileName);
+ elog("Could not build CompilerInvocation for file {0}", FileName);
+ // Remove the old AST if it's still in cache.
+ IdleASTs.take(this);
+ // Make sure anyone waiting for the preamble gets notified it could not
+ // be built.
+ PreambleWasBuilt.notify();
return;
}
- std::shared_ptr<const PreambleData> NewPreamble = buildPreamble(
- FileName, *Invocation, getPossiblyStalePreamble(), OldCommand, Inputs,
- PCHs, StorePreambleInMemory, PreambleCallback);
+ std::shared_ptr<const PreambleData> OldPreamble =
+ getPossiblyStalePreamble();
+ std::shared_ptr<const PreambleData> NewPreamble =
+ buildPreamble(FileName, *Invocation, OldPreamble, OldCommand, Inputs,
+ PCHs, StorePreambleInMemory, PreambleCallback);
+
+ bool CanReuseAST = InputsAreTheSame && (OldPreamble == NewPreamble);
{
std::lock_guard<std::mutex> Lock(Mutex);
if (NewPreamble)
LastBuiltPreamble = NewPreamble;
}
- // Build the AST for diagnostics.
- llvm::Optional<ParsedAST> AST =
- buildAST(FileName, llvm::make_unique<CompilerInvocation>(*Invocation),
- Inputs, NewPreamble, PCHs);
+ // Before doing the expensive AST reparse, we want to release our reference
+ // to the old preamble, so it can be freed if there are no other references
+ // to it.
+ OldPreamble.reset();
+ PreambleWasBuilt.notify();
+
+ if (!CanReuseAST) {
+ IdleASTs.take(this); // Remove the old AST if it's still in cache.
+ } else {
+ // Since we don't need to rebuild the AST, we might've already reported
+ // the diagnostics for it.
+ if (PrevDiagsWereReported) {
+ DiagsWereReported = true;
+ // Take a shortcut and don't report the diagnostics, since they should
+ // not changed. All the clients should handle the lack of OnUpdated()
+ // call anyway to handle empty result from buildAST.
+ // FIXME(ibiryukov): the AST could actually change if non-preamble
+ // includes changed, but we choose to ignore it.
+ // FIXME(ibiryukov): should we refresh the cache in IdleASTs for the
+ // current file at this point?
+ log("Skipping rebuild of the AST for {0}, inputs are the same.",
+ FileName);
+ return;
+ }
+ }
+
+ // We only need to build the AST if diagnostics were requested.
+ if (WantDiags == WantDiagnostics::No)
+ return;
+
+ // Get the AST for diagnostics.
+ llvm::Optional<std::unique_ptr<ParsedAST>> AST = IdleASTs.take(this);
+ if (!AST) {
+ llvm::Optional<ParsedAST> NewAST =
+ buildAST(FileName, std::move(Invocation), Inputs, NewPreamble, PCHs);
+ AST = NewAST ? llvm::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
+ }
// We want to report the diagnostics even if this update was cancelled.
// It seems more useful than making the clients wait indefinitely if they
// spam us with updates.
- if (WantDiags != WantDiagnostics::No && AST)
- OnUpdated(AST->getDiagnostics());
+ // Note *AST can be still be null if buildAST fails.
+ if (*AST) {
+ OnUpdated((*AST)->getDiagnostics());
+ DiagsWereReported = true;
+ }
// Stash the AST in the cache for further use.
- IdleASTs.put(this,
- AST ? llvm::make_unique<ParsedAST>(std::move(*AST)) : nullptr);
+ IdleASTs.put(this, std::move(*AST));
};
startTask("Update", Bind(Task, std::move(OnUpdated)), WantDiags);
@@ -358,10 +427,12 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags,
void ASTWorker::runWithAST(
llvm::StringRef Name,
- UniqueFunction<void(llvm::Expected<InputsAndAST>)> Action) {
+ llvm::unique_function<void(llvm::Expected<InputsAndAST>)> Action) {
auto Task = [=](decltype(Action) Action) {
llvm::Optional<std::unique_ptr<ParsedAST>> AST = IdleASTs.take(this);
if (!AST) {
+ std::unique_ptr<CompilerInvocation> Invocation =
+ buildCompilerInvocation(FileInputs);
// Try rebuilding the AST.
llvm::Optional<ParsedAST> NewAST =
Invocation
@@ -390,6 +461,10 @@ ASTWorker::getPossiblyStalePreamble() const {
return LastBuiltPreamble;
}
+void ASTWorker::waitForFirstPreamble() const {
+ PreambleWasBuilt.wait();
+}
+
std::size_t ASTWorker::getUsedBytes() const {
// Note that we don't report the size of ASTs currently used for processing
// the in-flight requests. We used this information for debugging purposes
@@ -411,7 +486,8 @@ void ASTWorker::stop() {
RequestsCV.notify_all();
}
-void ASTWorker::startTask(llvm::StringRef Name, UniqueFunction<void()> Task,
+void ASTWorker::startTask(llvm::StringRef Name,
+ llvm::unique_function<void()> Task,
llvm::Optional<WantDiagnostics> UpdateType) {
if (RunSync) {
assert(!Done && "running a task after stop()");
@@ -423,8 +499,9 @@ void ASTWorker::startTask(llvm::StringRef Name, UniqueFunction<void()> Task,
{
std::lock_guard<std::mutex> Lock(Mutex);
assert(!Done && "running a task after stop()");
- Requests.push_back({std::move(Task), Name, steady_clock::now(),
- Context::current().clone(), UpdateType});
+ Requests.push_back(
+ {std::move(Task), Name, steady_clock::now(),
+ Context::current().derive(kFileBeingProcessed, FileName), UpdateType});
}
RequestsCV.notify_all();
}
@@ -586,13 +663,13 @@ bool TUScheduler::blockUntilIdle(Deadline D) const {
return true;
}
-void TUScheduler::update(PathRef File, ParseInputs Inputs,
- WantDiagnostics WantDiags,
- UniqueFunction<void(std::vector<Diag>)> OnUpdated) {
+void TUScheduler::update(
+ PathRef File, ParseInputs Inputs, WantDiagnostics WantDiags,
+ llvm::unique_function<void(std::vector<Diag>)> OnUpdated) {
std::unique_ptr<FileData> &FD = Files[File];
if (!FD) {
// Create a new worker to process the AST-related tasks.
- ASTWorkerHandle Worker = ASTWorker::Create(
+ ASTWorkerHandle Worker = ASTWorker::create(
File, *IdleASTs, WorkerThreads ? WorkerThreads.getPointer() : nullptr,
Barrier, UpdateDebounce, PCHOps, StorePreamblesInMemory,
PreambleCallback);
@@ -608,13 +685,13 @@ void TUScheduler::update(PathRef File, ParseInputs Inputs,
void TUScheduler::remove(PathRef File) {
bool Removed = Files.erase(File);
if (!Removed)
- log("Trying to remove file from TUScheduler that is not tracked. File:" +
- File);
+ elog("Trying to remove file from TUScheduler that is not tracked: {0}",
+ File);
}
void TUScheduler::runWithAST(
llvm::StringRef Name, PathRef File,
- UniqueFunction<void(llvm::Expected<InputsAndAST>)> Action) {
+ llvm::unique_function<void(llvm::Expected<InputsAndAST>)> Action) {
auto It = Files.find(File);
if (It == Files.end()) {
Action(llvm::make_error<llvm::StringError>(
@@ -628,7 +705,7 @@ void TUScheduler::runWithAST(
void TUScheduler::runWithPreamble(
llvm::StringRef Name, PathRef File,
- UniqueFunction<void(llvm::Expected<InputsAndPreamble>)> Action) {
+ llvm::unique_function<void(llvm::Expected<InputsAndPreamble>)> Action) {
auto It = Files.find(File);
if (It == Files.end()) {
Action(llvm::make_error<llvm::StringError>(
@@ -652,6 +729,11 @@ void TUScheduler::runWithPreamble(
std::string Contents,
tooling::CompileCommand Command, Context Ctx,
decltype(Action) Action) mutable {
+ // We don't want to be running preamble actions before the preamble was
+ // built for the first time. This avoids extra work of processing the
+ // preamble headers in parallel multiple times.
+ Worker->waitForFirstPreamble();
+
std::lock_guard<Semaphore> BarrierLock(Barrier);
WithContext Guard(std::move(Ctx));
trace::Span Tracer(Name);
@@ -661,10 +743,12 @@ void TUScheduler::runWithPreamble(
Action(InputsAndPreamble{Contents, Command, Preamble.get()});
};
- PreambleTasks->runAsync("task:" + llvm::sys::path::filename(File),
- Bind(Task, std::string(Name), std::string(File),
- It->second->Contents, It->second->Command,
- Context::current().clone(), std::move(Action)));
+ PreambleTasks->runAsync(
+ "task:" + llvm::sys::path::filename(File),
+ Bind(Task, std::string(Name), std::string(File), It->second->Contents,
+ It->second->Command,
+ Context::current().derive(kFileBeingProcessed, File),
+ std::move(Action)));
}
std::vector<std::pair<Path, std::size_t>>
diff --git a/clangd/TUScheduler.h b/clangd/TUScheduler.h
index dfd4bfd8..90f3fe7b 100644
--- a/clangd/TUScheduler.h
+++ b/clangd/TUScheduler.h
@@ -79,7 +79,7 @@ public:
/// \p File was not part of it before.
/// FIXME(ibiryukov): remove the callback from this function.
void update(PathRef File, ParseInputs Inputs, WantDiagnostics WD,
- UniqueFunction<void(std::vector<Diag>)> OnUpdated);
+ llvm::unique_function<void(std::vector<Diag>)> OnUpdated);
/// Remove \p File from the list of tracked files and schedule removal of its
/// resources.
@@ -101,6 +101,9 @@ public:
/// - validate that the preamble is still valid, and only use it in this case
/// - accept that preamble contents may be outdated, and try to avoid reading
/// source code from headers.
+ /// If there's no preamble yet (because the file was just opened), we'll wait
+ /// for it to build. The preamble may still be null if it fails to build or is
+ /// empty.
/// If an error occurs during processing, it is forwarded to the \p Action
/// callback.
void runWithPreamble(llvm::StringRef Name, PathRef File,
@@ -119,6 +122,13 @@ public:
/// an LRU cache.
class ASTCache;
+ // The file being built/processed in the current thread. This is a hack in
+ // order to get the file name into the index implementations. Do not depend on
+ // this inside clangd.
+ // FIXME: remove this when there is proper index support via build system
+ // integration.
+ static llvm::Optional<llvm::StringRef> getFileBeingProcessedInContext();
+
private:
const bool StorePreamblesInMemory;
const std::shared_ptr<PCHContainerOperations> PCHOps;
@@ -132,6 +142,7 @@ private:
llvm::Optional<AsyncTaskRunner> WorkerThreads;
std::chrono::steady_clock::duration UpdateDebounce;
};
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/Threading.cpp b/clangd/Threading.cpp
index 22206114..aa9dd8fc 100644
--- a/clangd/Threading.cpp
+++ b/clangd/Threading.cpp
@@ -1,4 +1,5 @@
#include "Threading.h"
+#include "Trace.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Threading.h"
@@ -23,9 +24,14 @@ void Notification::wait() const {
Semaphore::Semaphore(std::size_t MaxLocks) : FreeSlots(MaxLocks) {}
void Semaphore::lock() {
- std::unique_lock<std::mutex> Lock(Mutex);
- SlotsChanged.wait(Lock, [&]() { return FreeSlots > 0; });
- --FreeSlots;
+ trace::Span Span("WaitForFreeSemaphoreSlot");
+ // trace::Span can also acquire locks in ctor and dtor, we make sure it
+ // happens when Semaphore's own lock is not held.
+ {
+ std::unique_lock<std::mutex> Lock(Mutex);
+ SlotsChanged.wait(Lock, [&]() { return FreeSlots > 0; });
+ --FreeSlots;
+ }
}
void Semaphore::unlock() {
@@ -44,8 +50,8 @@ bool AsyncTaskRunner::wait(Deadline D) const {
[&] { return InFlightTasks == 0; });
}
-void AsyncTaskRunner::runAsync(llvm::Twine Name,
- UniqueFunction<void()> Action) {
+void AsyncTaskRunner::runAsync(const llvm::Twine &Name,
+ llvm::unique_function<void()> Action) {
{
std::lock_guard<std::mutex> Lock(Mutex);
++InFlightTasks;
diff --git a/clangd/Threading.h b/clangd/Threading.h
index 3f5e5fc3..6dad841d 100644
--- a/clangd/Threading.h
+++ b/clangd/Threading.h
@@ -108,7 +108,7 @@ public:
void wait() const { (void)wait(Deadline::infinity()); }
LLVM_NODISCARD bool wait(Deadline D) const;
// The name is used for tracing and debugging (e.g. to name a spawned thread).
- void runAsync(llvm::Twine Name, UniqueFunction<void()> Action);
+ void runAsync(const llvm::Twine &Name, llvm::unique_function<void()> Action);
private:
mutable std::mutex Mutex;
diff --git a/clangd/Trace.cpp b/clangd/Trace.cpp
index ccbf6dc2..24c1fdd7 100644
--- a/clangd/Trace.cpp
+++ b/clangd/Trace.cpp
@@ -36,9 +36,9 @@ public:
// calculations!
Out << R"({"displayTimeUnit":"ns","traceEvents":[)"
<< "\n";
- rawEvent("M", json::obj{
+ rawEvent("M", json::Object{
{"name", "process_name"},
- {"args", json::obj{{"name", "clangd"}}},
+ {"args", json::Object{{"name", "clangd"}}},
});
}
@@ -49,7 +49,7 @@ public:
// We stash a Span object in the context. It will record the start/end,
// and this also allows us to look up the parent Span's information.
- Context beginSpan(llvm::StringRef Name, json::obj *Args) override {
+ Context beginSpan(llvm::StringRef Name, json::Object *Args) override {
return Context::current().derive(
SpanKey, llvm::make_unique<JSONSpan>(this, Name, Args));
}
@@ -62,18 +62,17 @@ public:
Context::current().getExisting(SpanKey)->markEnded();
}
- void instant(llvm::StringRef Name, json::obj &&Args) override {
+ void instant(llvm::StringRef Name, json::Object &&Args) override {
captureThreadMetadata();
- jsonEvent("i", json::obj{{"name", Name}, {"args", std::move(Args)}});
+ jsonEvent("i", json::Object{{"name", Name}, {"args", std::move(Args)}});
}
// Record an event on the current thread. ph, pid, tid, ts are set.
// Contents must be a list of the other JSON key/values.
- void jsonEvent(StringRef Phase, json::obj &&Contents,
- uint64_t TID = get_threadid(),
- double Timestamp = 0) {
+ void jsonEvent(StringRef Phase, json::Object &&Contents,
+ uint64_t TID = get_threadid(), double Timestamp = 0) {
Contents["ts"] = Timestamp ? Timestamp : timestamp();
- Contents["tid"] = TID;
+ Contents["tid"] = int64_t(TID);
std::lock_guard<std::mutex> Lock(Mu);
rawEvent(Phase, std::move(Contents));
}
@@ -81,7 +80,7 @@ public:
private:
class JSONSpan {
public:
- JSONSpan(JSONTracer *Tracer, llvm::StringRef Name, json::obj *Args)
+ JSONSpan(JSONTracer *Tracer, llvm::StringRef Name, json::Object *Args)
: StartTime(Tracer->timestamp()), EndTime(0), Name(Name),
TID(get_threadid()), Tracer(Tracer), Args(Args) {
// ~JSONSpan() may run in a different thread, so we need to capture now.
@@ -102,15 +101,15 @@ private:
auto FlowID = nextID();
Tracer->jsonEvent("s",
- json::obj{{"id", FlowID},
- {"name", "Context crosses threads"},
- {"cat", "dummy"}},
+ json::Object{{"id", FlowID},
+ {"name", "Context crosses threads"},
+ {"cat", "dummy"}},
(*Parent)->TID, (*Parent)->StartTime);
Tracer->jsonEvent("f",
- json::obj{{"id", FlowID},
- {"bp", "e"},
- {"name", "Context crosses threads"},
- {"cat", "dummy"}},
+ json::Object{{"id", FlowID},
+ {"bp", "e"},
+ {"name", "Context crosses threads"},
+ {"cat", "dummy"}},
TID);
}
}
@@ -118,9 +117,9 @@ private:
~JSONSpan() {
// Finally, record the event (ending at EndTime, not timestamp())!
Tracer->jsonEvent("X",
- json::obj{{"name", std::move(Name)},
- {"args", std::move(*Args)},
- {"dur", EndTime - StartTime}},
+ json::Object{{"name", std::move(Name)},
+ {"args", std::move(*Args)},
+ {"dur", EndTime - StartTime}},
TID, StartTime);
}
@@ -130,8 +129,8 @@ private:
}
private:
- static uint64_t nextID() {
- static std::atomic<uint64_t> Next = {0};
+ static int64_t nextID() {
+ static std::atomic<int64_t> Next = {0};
return Next++;
}
@@ -140,17 +139,17 @@ private:
std::string Name;
uint64_t TID;
JSONTracer *Tracer;
- json::obj *Args;
+ json::Object *Args;
};
static Key<std::unique_ptr<JSONSpan>> SpanKey;
// Record an event. ph and pid are set.
// Contents must be a list of the other JSON key/values.
- void rawEvent(StringRef Phase, json::obj &&Event) /*REQUIRES(Mu)*/ {
+ void rawEvent(StringRef Phase, json::Object &&Event) /*REQUIRES(Mu)*/ {
// PID 0 represents the clangd process.
Event["pid"] = 0;
Event["ph"] = Phase;
- Out << Sep << formatv(JSONFormat, json::Expr(std::move(Event)));
+ Out << Sep << formatv(JSONFormat, json::Value(std::move(Event)));
Sep = ",\n";
}
@@ -162,10 +161,10 @@ private:
SmallString<32> Name;
get_thread_name(Name);
if (!Name.empty()) {
- rawEvent("M", json::obj{
- {"tid", TID},
+ rawEvent("M", json::Object{
+ {"tid", int64_t(TID)},
{"name", "thread_name"},
- {"args", json::obj{{"name", Name}}},
+ {"args", json::Object{{"name", Name}}},
});
}
}
@@ -204,14 +203,14 @@ std::unique_ptr<EventTracer> createJSONTracer(llvm::raw_ostream &OS,
void log(const Twine &Message) {
if (!T)
return;
- T->instant("Log", json::obj{{"Message", Message.str()}});
+ T->instant("Log", json::Object{{"Message", Message.str()}});
}
// Returned context owns Args.
-static Context makeSpanContext(llvm::Twine Name, json::obj *Args) {
+static Context makeSpanContext(llvm::Twine Name, json::Object *Args) {
if (!T)
return Context::current().clone();
- WithContextValue WithArgs{std::unique_ptr<json::obj>(Args)};
+ WithContextValue WithArgs{std::unique_ptr<json::Object>(Args)};
return T->beginSpan(Name.isSingleStringRef() ? Name.getSingleStringRef()
: llvm::StringRef(Name.str()),
Args);
@@ -221,7 +220,7 @@ static Context makeSpanContext(llvm::Twine Name, json::obj *Args) {
// The args are owned by the context though. They stick around until the
// beginSpan() context is destroyed, when the tracing engine will consume them.
Span::Span(llvm::Twine Name)
- : Args(T ? new json::obj() : nullptr),
+ : Args(T ? new json::Object() : nullptr),
RestoreCtx(makeSpanContext(Name, Args)) {}
Span::~Span() {
diff --git a/clangd/Trace.h b/clangd/Trace.h
index 131ace55..0c4c4618 100644
--- a/clangd/Trace.h
+++ b/clangd/Trace.h
@@ -20,8 +20,8 @@
#include "Context.h"
#include "Function.h"
-#include "JSONExpr.h"
#include "llvm/ADT/Twine.h"
+#include "llvm/Support/JSON.h"
#include "llvm/Support/raw_ostream.h"
namespace clang {
@@ -39,7 +39,7 @@ public:
/// Usually implementations will store an object in the returned context
/// whose destructor records the end of the event.
/// The args are *Args, only complete when the event ends.
- virtual Context beginSpan(llvm::StringRef Name, json::obj *Args) = 0;
+ virtual Context beginSpan(llvm::StringRef Name, llvm::json::Object *Args) = 0;
// Called when a Span is destroyed (it may still be active on other threads).
// beginSpan() and endSpan() will always form a proper stack on each thread.
// The Context returned by beginSpan is active, but Args is not ready.
@@ -48,7 +48,7 @@ public:
virtual void endSpan(){};
/// Called for instant events.
- virtual void instant(llvm::StringRef Name, json::obj &&Args) = 0;
+ virtual void instant(llvm::StringRef Name, llvm::json::Object &&Args) = 0;
};
/// Sets up a global EventTracer that consumes events produced by Span and
@@ -87,7 +87,7 @@ public:
/// Mutable metadata, if this span is interested.
/// Prefer to use SPAN_ATTACH rather than accessing this directly.
- json::obj *const Args;
+ llvm::json::Object *const Args;
private:
WithContext RestoreCtx;
diff --git a/clangd/XRefs.cpp b/clangd/XRefs.cpp
index 2995fd92..e95730ba 100644
--- a/clangd/XRefs.cpp
+++ b/clangd/XRefs.cpp
@@ -12,6 +12,7 @@
#include "SourceCode.h"
#include "URI.h"
#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Index/IndexDataConsumer.h"
#include "clang/Index/IndexingAction.h"
#include "clang/Index/USRGeneration.h"
@@ -24,7 +25,7 @@ namespace {
// Get the definition from a given declaration `D`.
// Return nullptr if no definition is found, or the declaration type of `D` is
// not supported.
-const Decl *GetDefinition(const Decl *D) {
+const Decl *getDefinition(const Decl *D) {
assert(D);
if (const auto *TD = dyn_cast<TagDecl>(D))
return TD->getDefinition();
@@ -39,18 +40,18 @@ const Decl *GetDefinition(const Decl *D) {
// HintPath is used to resolve the path of URI.
// FIXME: figure out a good home for it, and share the implementation with
// FindSymbols.
-llvm::Optional<Location> ToLSPLocation(const SymbolLocation &Loc,
+llvm::Optional<Location> toLSPLocation(const SymbolLocation &Loc,
llvm::StringRef HintPath) {
if (!Loc)
return llvm::None;
auto Uri = URI::parse(Loc.FileURI);
if (!Uri) {
- log("Could not parse URI: " + Loc.FileURI);
+ log("Could not parse URI: {0}", Loc.FileURI);
return llvm::None;
}
auto Path = URI::resolve(*Uri, HintPath);
if (!Path) {
- log("Could not resolve URI: " + Loc.FileURI);
+ log("Could not resolve URI: {0}", Loc.FileURI);
return llvm::None;
}
Location LSPLoc;
@@ -115,7 +116,7 @@ public:
// We don't use parameter `D`, as Parameter `D` is the canonical
// declaration, which is the first declaration of a redeclarable
// declaration, and it could be a forward declaration.
- if (const auto *Def = GetDefinition(D)) {
+ if (const auto *Def = getDefinition(D)) {
Decls.push_back(Def);
} else {
// Couldn't find a definition, fall back to use `D`.
@@ -174,20 +175,6 @@ IdentifiedSymbol getSymbolAtPosition(ParsedAST &AST, SourceLocation Pos) {
return {DeclMacrosFinder.takeDecls(), DeclMacrosFinder.takeMacroInfos()};
}
-llvm::Optional<std::string>
-getAbsoluteFilePath(const FileEntry *F, const SourceManager &SourceMgr) {
- SmallString<64> FilePath = F->tryGetRealPathName();
- if (FilePath.empty())
- FilePath = F->getName();
- if (!llvm::sys::path::is_absolute(FilePath)) {
- if (!SourceMgr.getFileManager().makeAbsolutePath(FilePath)) {
- log("Could not turn relative path to absolute: " + FilePath);
- return llvm::None;
- }
- }
- return FilePath.str().str();
-}
-
llvm::Optional<Location>
makeLocation(ParsedAST &AST, const SourceRange &ValSourceRange) {
const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
@@ -215,27 +202,15 @@ makeLocation(ParsedAST &AST, const SourceRange &ValSourceRange) {
return L;
}
-// Get the symbol ID for a declaration, if possible.
-llvm::Optional<SymbolID> getSymbolID(const Decl *D) {
- llvm::SmallString<128> USR;
- if (index::generateUSRForDecl(D, USR)) {
- return None;
- }
- return SymbolID(USR);
-}
-
} // namespace
std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos,
const SymbolIndex *Index) {
const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
- SourceLocation SourceLocationBeg =
- getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID());
std::vector<Location> Result;
// Handle goto definition for #include.
- for (auto &Inc : AST.getInclusions()) {
- Position Pos = sourceLocToPosition(SourceMgr, SourceLocationBeg);
+ for (auto &Inc : AST.getIncludeStructure().MainFileIncludes) {
if (!Inc.Resolved.empty() && Inc.R.contains(Pos))
Result.push_back(Location{URIForFile{Inc.Resolved}, {}});
}
@@ -243,6 +218,8 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos,
return Result;
// Identified symbols at a specific position.
+ SourceLocation SourceLocationBeg =
+ getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID());
auto Symbols = getSymbolAtPosition(AST, SourceLocationBeg);
for (auto Item : Symbols.Macros) {
@@ -293,7 +270,7 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos,
auto L = makeLocation(AST, SourceRange(Loc, Loc));
// The declaration in the identified symbols is a definition if possible
// otherwise it is declaration.
- bool IsDef = GetDefinition(D) == D;
+ bool IsDef = getDefinition(D) == D;
// Populate one of the slots with location for the AST.
if (!IsDef)
Candidate.Decl = L;
@@ -319,9 +296,9 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos,
auto &Value = It->second;
if (!Value.Def)
- Value.Def = ToLSPLocation(Sym.Definition, HintPath);
+ Value.Def = toLSPLocation(Sym.Definition, HintPath);
if (!Value.Decl)
- Value.Decl = ToLSPLocation(Sym.CanonicalDeclaration, HintPath);
+ Value.Decl = toLSPLocation(Sym.CanonicalDeclaration, HintPath);
});
}
@@ -424,7 +401,7 @@ std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
return DocHighlightsFinder.takeHighlights();
}
-static PrintingPolicy PrintingPolicyForDecls(PrintingPolicy Base) {
+static PrintingPolicy printingPolicyForDecls(PrintingPolicy Base) {
PrintingPolicy Policy(Base);
Policy.AnonymousTagLocations = false;
@@ -438,11 +415,11 @@ static PrintingPolicy PrintingPolicyForDecls(PrintingPolicy Base) {
/// Return a string representation (e.g. "class MyNamespace::MyClass") of
/// the type declaration \p TD.
-static std::string TypeDeclToString(const TypeDecl *TD) {
+static std::string typeDeclToString(const TypeDecl *TD) {
QualType Type = TD->getASTContext().getTypeDeclType(TD);
PrintingPolicy Policy =
- PrintingPolicyForDecls(TD->getASTContext().getPrintingPolicy());
+ printingPolicyForDecls(TD->getASTContext().getPrintingPolicy());
std::string Name;
llvm::raw_string_ostream Stream(Name);
@@ -453,10 +430,10 @@ static std::string TypeDeclToString(const TypeDecl *TD) {
/// Return a string representation (e.g. "namespace ns1::ns2") of
/// the named declaration \p ND.
-static std::string NamedDeclQualifiedName(const NamedDecl *ND,
+static std::string namedDeclQualifiedName(const NamedDecl *ND,
StringRef Prefix) {
PrintingPolicy Policy =
- PrintingPolicyForDecls(ND->getASTContext().getPrintingPolicy());
+ printingPolicyForDecls(ND->getASTContext().getPrintingPolicy());
std::string Name;
llvm::raw_string_ostream Stream(Name);
@@ -475,11 +452,11 @@ static llvm::Optional<std::string> getScopeName(const Decl *D) {
if (isa<TranslationUnitDecl>(DC))
return std::string("global namespace");
if (const TypeDecl *TD = dyn_cast<TypeDecl>(DC))
- return TypeDeclToString(TD);
+ return typeDeclToString(TD);
else if (const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC))
- return NamedDeclQualifiedName(ND, "namespace");
+ return namedDeclQualifiedName(ND, "namespace");
else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(DC))
- return NamedDeclQualifiedName(FD, "function");
+ return namedDeclQualifiedName(FD, "function");
return llvm::None;
}
@@ -506,7 +483,7 @@ static Hover getHoverContents(const Decl *D) {
llvm::raw_string_ostream OS(DeclText);
PrintingPolicy Policy =
- PrintingPolicyForDecls(D->getASTContext().getPrintingPolicy());
+ printingPolicyForDecls(D->getASTContext().getPrintingPolicy());
D->print(OS, Policy);
@@ -516,6 +493,18 @@ static Hover getHoverContents(const Decl *D) {
return H;
}
+/// Generate a \p Hover object given the type \p T.
+static Hover getHoverContents(QualType T, ASTContext &ASTCtx) {
+ Hover H;
+ std::string TypeText;
+ llvm::raw_string_ostream OS(TypeText);
+ PrintingPolicy Policy = printingPolicyForDecls(ASTCtx.getPrintingPolicy());
+ T.print(OS, Policy);
+ OS.flush();
+ H.contents.value += TypeText;
+ return H;
+}
+
/// Generate a \p Hover object given the macro \p MacroInf.
static Hover getHoverContents(StringRef MacroName) {
Hover H;
@@ -526,7 +515,132 @@ static Hover getHoverContents(StringRef MacroName) {
return H;
}
-Hover getHover(ParsedAST &AST, Position Pos) {
+namespace {
+/// Computes the deduced type at a given location by visiting the relevant
+/// nodes. We use this to display the actual type when hovering over an "auto"
+/// keyword or "decltype()" expression.
+/// FIXME: This could have been a lot simpler by visiting AutoTypeLocs but it
+/// seems that the AutoTypeLocs that can be visited along with their AutoType do
+/// not have the deduced type set. Instead, we have to go to the appropriate
+/// DeclaratorDecl/FunctionDecl and work our back to the AutoType that does have
+/// a deduced type set. The AST should be improved to simplify this scenario.
+class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
+ SourceLocation SearchedLocation;
+ llvm::Optional<QualType> DeducedType;
+
+public:
+ DeducedTypeVisitor(SourceLocation SearchedLocation)
+ : SearchedLocation(SearchedLocation) {}
+
+ llvm::Optional<QualType> getDeducedType() { return DeducedType; }
+
+ // Handle auto initializers:
+ //- auto i = 1;
+ //- decltype(auto) i = 1;
+ //- auto& i = 1;
+ bool VisitDeclaratorDecl(DeclaratorDecl *D) {
+ if (!D->getTypeSourceInfo() ||
+ D->getTypeSourceInfo()->getTypeLoc().getBeginLoc() != SearchedLocation)
+ return true;
+
+ auto DeclT = D->getType();
+ // "auto &" is represented as a ReferenceType containing an AutoType
+ if (const ReferenceType *RT = dyn_cast<ReferenceType>(DeclT.getTypePtr()))
+ DeclT = RT->getPointeeType();
+
+ const AutoType *AT = dyn_cast<AutoType>(DeclT.getTypePtr());
+ if (AT && !AT->getDeducedType().isNull()) {
+ // For auto, use the underlying type because the const& would be
+ // represented twice: written in the code and in the hover.
+ // Example: "const auto I = 1", we only want "int" when hovering on auto,
+ // not "const int".
+ //
+ // For decltype(auto), take the type as is because it cannot be written
+ // with qualifiers or references but its decuded type can be const-ref.
+ DeducedType = AT->isDecltypeAuto() ? DeclT : DeclT.getUnqualifiedType();
+ }
+ return true;
+ }
+
+ // Handle auto return types:
+ //- auto foo() {}
+ //- auto& foo() {}
+ //- auto foo() -> decltype(1+1) {}
+ //- operator auto() const { return 10; }
+ bool VisitFunctionDecl(FunctionDecl *D) {
+ if (!D->getTypeSourceInfo())
+ return true;
+ // Loc of auto in return type (c++14).
+ auto CurLoc = D->getReturnTypeSourceRange().getBegin();
+ // Loc of "auto" in operator auto()
+ if (CurLoc.isInvalid() && dyn_cast<CXXConversionDecl>(D))
+ CurLoc = D->getTypeSourceInfo()->getTypeLoc().getBeginLoc();
+ // Loc of "auto" in function with traling return type (c++11).
+ if (CurLoc.isInvalid())
+ CurLoc = D->getSourceRange().getBegin();
+ if (CurLoc != SearchedLocation)
+ return true;
+
+ auto T = D->getReturnType();
+ // "auto &" is represented as a ReferenceType containing an AutoType.
+ if (const ReferenceType *RT = dyn_cast<ReferenceType>(T.getTypePtr()))
+ T = RT->getPointeeType();
+
+ const AutoType *AT = dyn_cast<AutoType>(T.getTypePtr());
+ if (AT && !AT->getDeducedType().isNull()) {
+ DeducedType = T.getUnqualifiedType();
+ } else { // auto in a trailing return type just points to a DecltypeType.
+ const DecltypeType *DT = dyn_cast<DecltypeType>(T.getTypePtr());
+ if (!DT->getUnderlyingType().isNull())
+ DeducedType = DT->getUnderlyingType();
+ }
+ return true;
+ }
+
+ // Handle non-auto decltype, e.g.:
+ // - auto foo() -> decltype(expr) {}
+ // - decltype(expr);
+ bool VisitDecltypeTypeLoc(DecltypeTypeLoc TL) {
+ if (TL.getBeginLoc() != SearchedLocation)
+ return true;
+
+ // A DecltypeType's underlying type can be another DecltypeType! E.g.
+ // int I = 0;
+ // decltype(I) J = I;
+ // decltype(J) K = J;
+ const DecltypeType *DT = dyn_cast<DecltypeType>(TL.getTypePtr());
+ while (DT && !DT->getUnderlyingType().isNull()) {
+ DeducedType = DT->getUnderlyingType();
+ DT = dyn_cast<DecltypeType>(DeducedType->getTypePtr());
+ }
+ return true;
+ }
+};
+} // namespace
+
+/// Retrieves the deduced type at a given location (auto, decltype).
+llvm::Optional<QualType> getDeducedType(ParsedAST &AST,
+ SourceLocation SourceLocationBeg) {
+ Token Tok;
+ auto &ASTCtx = AST.getASTContext();
+ // Only try to find a deduced type if the token is auto or decltype.
+ if (!SourceLocationBeg.isValid() ||
+ Lexer::getRawToken(SourceLocationBeg, Tok, ASTCtx.getSourceManager(),
+ ASTCtx.getLangOpts(), false) ||
+ !Tok.is(tok::raw_identifier)) {
+ return {};
+ }
+ AST.getPreprocessor().LookUpIdentifierInfo(Tok);
+ if (!(Tok.is(tok::kw_auto) || Tok.is(tok::kw_decltype)))
+ return {};
+
+ DeducedTypeVisitor V(SourceLocationBeg);
+ for (Decl *D : AST.getLocalTopLevelDecls())
+ V.TraverseDecl(D);
+ return V.getDeducedType();
+}
+
+Optional<Hover> getHover(ParsedAST &AST, Position Pos) {
const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
SourceLocation SourceLocationBeg =
getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID());
@@ -539,7 +653,11 @@ Hover getHover(ParsedAST &AST, Position Pos) {
if (!Symbols.Decls.empty())
return getHoverContents(Symbols.Decls[0]);
- return Hover();
+ auto DeducedType = getDeducedType(AST, SourceLocationBeg);
+ if (DeducedType && !DeducedType->isNull())
+ return getHoverContents(*DeducedType, AST.getASTContext());
+
+ return None;
}
} // namespace clangd
diff --git a/clangd/XRefs.h b/clangd/XRefs.h
index 89b39ebe..d698a61c 100644
--- a/clangd/XRefs.h
+++ b/clangd/XRefs.h
@@ -16,6 +16,7 @@
#include "ClangdUnit.h"
#include "Protocol.h"
#include "index/Index.h"
+#include "llvm/ADT/Optional.h"
#include <vector>
namespace clang {
@@ -30,7 +31,7 @@ std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
Position Pos);
/// Get the hover information when hovering at \p Pos.
-Hover getHover(ParsedAST &AST, Position Pos);
+llvm::Optional<Hover> getHover(ParsedAST &AST, Position Pos);
} // namespace clangd
} // namespace clang
diff --git a/clangd/clients/clangd-vscode/package.json b/clangd/clients/clangd-vscode/package.json
index a8cb158a..eeb51128 100644
--- a/clangd/clients/clangd-vscode/package.json
+++ b/clangd/clients/clangd-vscode/package.json
@@ -9,7 +9,7 @@
"vscode": "^1.18.0"
},
"categories": [
- "Languages",
+ "Programming Languages",
"Linters",
"Snippets"
],
diff --git a/clangd/fuzzer/ClangdFuzzer.cpp b/clangd/fuzzer/ClangdFuzzer.cpp
index 33212226..d521e62a 100644
--- a/clangd/fuzzer/ClangdFuzzer.cpp
+++ b/clangd/fuzzer/ClangdFuzzer.cpp
@@ -17,17 +17,22 @@
#include "ClangdServer.h"
#include "CodeComplete.h"
#include <sstream>
+#include <stdio.h>
extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
- clang::clangd::JSONOutput Out(llvm::nulls(), llvm::nulls(), nullptr);
+ if (size == 0)
+ return 0;
+
+ clang::clangd::JSONOutput Out(llvm::nulls(), llvm::nulls(),
+ clang::clangd::Logger::Error, nullptr);
clang::clangd::CodeCompleteOptions CCOpts;
CCOpts.EnableSnippets = false;
clang::clangd::ClangdServer::Options Opts;
// Initialize and run ClangdLSPServer.
- clang::clangd::ClangdLSPServer LSPServer(Out, CCOpts, llvm::None, Opts);
-
- std::istringstream In(std::string(reinterpret_cast<char *>(data), size));
- LSPServer.run(In);
+ clang::clangd::ClangdLSPServer LSPServer(Out, CCOpts, llvm::None, false,
+ Opts);
+ // fmemopen isn't portable, but I think we only run the fuzzer on Linux.
+ LSPServer.run(fmemopen(data, size, "r"));
return 0;
}
diff --git a/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp b/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp
index 1ea73b55..0cc04802 100644
--- a/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp
+++ b/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp
@@ -82,6 +82,15 @@ public:
void EndSourceFileAction() override {
WrapperFrontendAction::EndSourceFileAction();
+ const auto &CI = getCompilerInstance();
+ if (CI.hasDiagnostics() &&
+ CI.getDiagnostics().hasUncompilableErrorOccurred()) {
+ llvm::errs()
+ << "Found uncompilable errors in the translation unit. Igoring "
+ "collected symbols...\n";
+ return;
+ }
+
auto Symbols = Collector->takeSymbols();
for (const auto &Sym : Symbols) {
Ctx->reportResult(Sym.ID.str(), SymbolToYAML(Sym));
@@ -103,6 +112,7 @@ public:
CollectorOpts.FallbackDir = AssumedHeaderDir;
CollectorOpts.CollectIncludePath = true;
CollectorOpts.CountReferences = true;
+ CollectorOpts.Origin = SymbolOrigin::Static;
auto Includes = llvm::make_unique<CanonicalIncludes>();
addSystemHeadersMapping(Includes.get());
CollectorOpts.Includes = Includes.get();
@@ -140,10 +150,23 @@ SymbolSlab mergeSymbols(tooling::ToolResults *Results) {
int main(int argc, const char **argv) {
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
- const char* Overview =
- "This is an **experimental** tool to generate YAML-format "
- "project-wide symbols for clangd (global code completion). It would be "
- "changed and deprecated eventually. Don't use it in production code!";
+ const char *Overview = R"(
+ This is an **experimental** tool to generate YAML-format project-wide symbols
+ for clangd (global code completion). It would be changed and deprecated
+ eventually. Don't use it in production code!
+
+ Example usage for building index for the whole project using CMake compile
+ commands:
+
+ $ global-symbol-builder --executor=all-TUs compile_commands.json > index.yaml
+
+ Example usage for file sequence index without flags:
+
+ $ global-symbol-builder File1.cpp File2.cpp ... FileN.cpp > index.yaml
+
+ Note: only symbols from header files will be collected.
+ )";
+
auto Executor = clang::tooling::createExecutorFromCommandLineArgs(
argc, argv, cl::GeneralCategory, Overview);
diff --git a/clangd/index/FileIndex.cpp b/clangd/index/FileIndex.cpp
index 7e19b004..8ee5018e 100644
--- a/clangd/index/FileIndex.cpp
+++ b/clangd/index/FileIndex.cpp
@@ -9,13 +9,15 @@
#include "FileIndex.h"
#include "SymbolCollector.h"
+#include "../Logger.h"
#include "clang/Index/IndexingAction.h"
#include "clang/Lex/Preprocessor.h"
namespace clang {
namespace clangd {
-SymbolSlab indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP) {
+SymbolSlab indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
+ llvm::ArrayRef<std::string> URISchemes) {
SymbolCollector::Options CollectorOpts;
// FIXME(ioeric): we might also want to collect include headers. We would need
// to make sure all includes are canonicalized (with CanonicalIncludes), which
@@ -24,6 +26,9 @@ SymbolSlab indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP) {
// CommentHandler for IWYU pragma) to canonicalize includes.
CollectorOpts.CollectIncludePath = false;
CollectorOpts.CountReferences = false;
+ if (!URISchemes.empty())
+ CollectorOpts.URISchemes = URISchemes;
+ CollectorOpts.Origin = SymbolOrigin::Dynamic;
SymbolCollector Collector(std::move(CollectorOpts));
Collector.setPreprocessor(PP);
@@ -41,6 +46,9 @@ SymbolSlab indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP) {
return Collector.takeSymbols();
}
+FileIndex::FileIndex(std::vector<std::string> URISchemes)
+ : URISchemes(std::move(URISchemes)) {}
+
void FileSymbols::update(PathRef Path, std::unique_ptr<SymbolSlab> Slab) {
std::lock_guard<std::mutex> Lock(Mutex);
if (!Slab)
@@ -79,7 +87,7 @@ void FileIndex::update(PathRef Path, ASTContext *AST,
} else {
assert(PP);
auto Slab = llvm::make_unique<SymbolSlab>();
- *Slab = indexAST(*AST, PP);
+ *Slab = indexAST(*AST, PP, URISchemes);
FSymbols.update(Path, std::move(Slab));
}
auto Symbols = FSymbols.allSymbols();
@@ -98,5 +106,11 @@ void FileIndex::lookup(
Index.lookup(Req, Callback);
}
+void FileIndex::findOccurrences(
+ const OccurrencesRequest &Req,
+ llvm::function_ref<void(const SymbolOccurrence &)> Callback) const {
+ log("findOccurrences is not implemented.");
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/index/FileIndex.h b/clangd/index/FileIndex.h
index f25ed174..35c9f7ef 100644
--- a/clangd/index/FileIndex.h
+++ b/clangd/index/FileIndex.h
@@ -56,6 +56,10 @@ private:
/// \brief This manages symbls from files and an in-memory index on all symbols.
class FileIndex : public SymbolIndex {
public:
+ /// If URISchemes is empty, the default schemes in SymbolCollector will be
+ /// used.
+ FileIndex(std::vector<std::string> URISchemes = {});
+
/// \brief Update symbols in \p Path with symbols in \p AST. If \p AST is
/// nullptr, this removes all symbols in the file.
/// If \p AST is not null, \p PP cannot be null and it should be the
@@ -69,14 +73,21 @@ public:
void lookup(const LookupRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const override;
+
+ void findOccurrences(const OccurrencesRequest &Req,
+ llvm::function_ref<void(const SymbolOccurrence &)>
+ Callback) const override;
private:
FileSymbols FSymbols;
MemIndex Index;
+ std::vector<std::string> URISchemes;
};
/// Retrieves namespace and class level symbols in \p AST.
/// Exposed to assist in unit tests.
-SymbolSlab indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP);
+/// If URISchemes is empty, the default schemes in SymbolCollector will be used.
+SymbolSlab indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
+ llvm::ArrayRef<std::string> URISchemes = {});
} // namespace clangd
} // namespace clang
diff --git a/clangd/index/Index.cpp b/clangd/index/Index.cpp
index 7f84d756..1ae3d542 100644
--- a/clangd/index/Index.cpp
+++ b/clangd/index/Index.cpp
@@ -44,6 +44,16 @@ void operator>>(StringRef Str, SymbolID &ID) {
std::copy(HexString.begin(), HexString.end(), ID.HashValue.begin());
}
+raw_ostream &operator<<(raw_ostream &OS, SymbolOrigin O) {
+ if (O == SymbolOrigin::Unknown)
+ return OS << "unknown";
+ constexpr static char Sigils[] = "ADSM4567";
+ for (unsigned I = 0; I < sizeof(Sigils); ++I)
+ if (static_cast<uint8_t>(O) & 1u << I)
+ OS << Sigils[I];
+ return OS;
+}
+
raw_ostream &operator<<(raw_ostream &OS, const Symbol &S) {
return OS << S.Scope << S.Name;
}
@@ -84,10 +94,8 @@ static void own(Symbol &S, DenseSet<StringRef> &Strings,
Intern(S.CanonicalDeclaration.FileURI);
Intern(S.Definition.FileURI);
- Intern(S.CompletionLabel);
- Intern(S.CompletionFilterText);
- Intern(S.CompletionPlainInsertText);
- Intern(S.CompletionSnippetInsertText);
+ Intern(S.Signature);
+ Intern(S.CompletionSnippetSuffix);
if (S.Detail) {
// Copy values of StringRefs into arena.
@@ -95,7 +103,7 @@ static void own(Symbol &S, DenseSet<StringRef> &Strings,
*Detail = *S.Detail;
// Intern the actual strings.
Intern(Detail->Documentation);
- Intern(Detail->CompletionDetail);
+ Intern(Detail->ReturnType);
Intern(Detail->IncludeHeader);
// Replace the detail pointer with our copy.
S.Detail = Detail;
diff --git a/clangd/index/Index.h b/clangd/index/Index.h
index b406097d..6d63335d 100644
--- a/clangd/index/Index.h
+++ b/clangd/index/Index.h
@@ -30,22 +30,23 @@ struct SymbolLocation {
uint32_t Line = 0; // 0-based
// Using UTF-16 code units.
uint32_t Column = 0; // 0-based
+ bool operator==(const Position& P) const {
+ return Line == P.Line && Column == P.Column;
+ }
};
// The URI of the source file where a symbol occurs.
llvm::StringRef FileURI;
- // The 0-based offsets of the symbol from the beginning of the source file,
- // using half-open range, [StartOffset, EndOffset).
- // DO NOT use these fields, as they will be removed immediately.
- // FIXME(hokein): remove these fields in favor of Position.
- unsigned StartOffset = 0;
- unsigned EndOffset = 0;
/// The symbol range, using half-open range [Start, End).
Position Start;
Position End;
- operator bool() const { return !FileURI.empty(); }
+ explicit operator bool() const { return !FileURI.empty(); }
+ bool operator==(const SymbolLocation& Loc) const {
+ return std::tie(FileURI, Start, End) ==
+ std::tie(Loc.FileURI, Loc.Start, Loc.End);
+ }
};
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolLocation &);
@@ -123,6 +124,30 @@ template <> struct DenseMapInfo<clang::clangd::SymbolID> {
namespace clang {
namespace clangd {
+// Describes the source of information about a symbol.
+// Mainly useful for debugging, e.g. understanding code completion reuslts.
+// This is a bitfield as information can be combined from several sources.
+enum class SymbolOrigin : uint8_t {
+ Unknown = 0,
+ AST = 1 << 0, // Directly from the AST (indexes should not set this).
+ Dynamic = 1 << 1, // From the dynamic index of opened files.
+ Static = 1 << 2, // From the static, externally-built index.
+ Merge = 1 << 3, // A non-trivial index merge was performed.
+ // Remaining bits reserved for index implementations.
+};
+inline SymbolOrigin operator|(SymbolOrigin A, SymbolOrigin B) {
+ return static_cast<SymbolOrigin>(static_cast<uint8_t>(A) |
+ static_cast<uint8_t>(B));
+}
+inline SymbolOrigin &operator|=(SymbolOrigin &A, SymbolOrigin B) {
+ return A = A | B;
+}
+inline SymbolOrigin operator&(SymbolOrigin A, SymbolOrigin B) {
+ return static_cast<SymbolOrigin>(static_cast<uint8_t>(A) &
+ static_cast<uint8_t>(B));
+}
+raw_ostream &operator<<(raw_ostream &, SymbolOrigin);
+
// The class presents a C++ symbol, e.g. class, function.
//
// WARNING: Symbols do not own much of their underlying data - typically strings
@@ -131,6 +156,11 @@ namespace clangd {
// When adding new unowned data fields to Symbol, remember to update:
// - SymbolSlab::Builder in Index.cpp, to copy them to the slab's storage.
// - mergeSymbol in Merge.cpp, to properly combine two Symbols.
+//
+// A fully documented symbol can be split as:
+// size_type std::map<k, t>::count(const K& key) const
+// | Return | Scope |Name| Signature |
+// We split up these components to allow display flexibility later.
struct Symbol {
// The ID of the symbol.
SymbolID ID;
@@ -155,20 +185,18 @@ struct Symbol {
// The number of translation units that reference this symbol from their main
// file. This number is only meaningful if aggregated in an index.
unsigned References = 0;
-
- /// A brief description of the symbol that can be displayed in the completion
- /// candidate list. For example, "Foo(X x, Y y) const" is a labal for a
- /// function.
- llvm::StringRef CompletionLabel;
- /// The piece of text that the user is expected to type to match the
- /// code-completion string, typically a keyword or the name of a declarator or
- /// macro.
- llvm::StringRef CompletionFilterText;
- /// What to insert when completing this symbol (plain text version).
- llvm::StringRef CompletionPlainInsertText;
- /// What to insert when completing this symbol (snippet version). This is
- /// empty if it is the same as the plain insert text above.
- llvm::StringRef CompletionSnippetInsertText;
+ /// Whether or not this symbol is meant to be used for the code completion.
+ /// See also isIndexedForCodeCompletion().
+ bool IsIndexedForCodeCompletion = false;
+ /// Where this symbol came from. Usually an index provides a constant value.
+ SymbolOrigin Origin = SymbolOrigin::Unknown;
+ /// A brief description of the symbol that can be appended in the completion
+ /// candidate list. For example, "(X x, Y y) const" is a function signature.
+ llvm::StringRef Signature;
+ /// What to insert when completing this symbol, after the symbol name.
+ /// This is in LSP snippet syntax (e.g. "({$0})" for a no-args function).
+ /// (When snippets are disabled, the symbol name alone is used).
+ llvm::StringRef CompletionSnippetSuffix;
/// Optional symbol details that are not required to be set. For example, an
/// index fuzzy match can return a large number of symbol candidates, and it
@@ -178,9 +206,9 @@ struct Symbol {
struct Details {
/// Documentation including comment for the symbol declaration.
llvm::StringRef Documentation;
- /// This is what goes into the LSP detail field in a completion item. For
- /// example, the result type of a function.
- llvm::StringRef CompletionDetail;
+ /// Type when this symbol is used in an expression. (Short display form).
+ /// e.g. return type of a function, or type of a variable.
+ llvm::StringRef ReturnType;
/// This can be either a URI of the header to be #include'd for this symbol,
/// or a literal header quoted with <> or "" that is suitable to be included
/// directly. When this is a URI, the exact #include path needs to be
@@ -259,6 +287,40 @@ private:
std::vector<Symbol> Symbols; // Sorted by SymbolID to allow lookup.
};
+// Describes the kind of a symbol occurrence.
+//
+// This is a bitfield which can be combined from different kinds.
+enum class SymbolOccurrenceKind : uint8_t {
+ Unknown = 0,
+ Declaration = static_cast<uint8_t>(index::SymbolRole::Declaration),
+ Definition = static_cast<uint8_t>(index::SymbolRole::Definition),
+ Reference = static_cast<uint8_t>(index::SymbolRole::Reference),
+};
+inline SymbolOccurrenceKind operator|(SymbolOccurrenceKind L,
+ SymbolOccurrenceKind R) {
+ return static_cast<SymbolOccurrenceKind>(static_cast<uint8_t>(L) |
+ static_cast<uint8_t>(R));
+}
+inline SymbolOccurrenceKind &operator|=(SymbolOccurrenceKind &L,
+ SymbolOccurrenceKind R) {
+ return L = L | R;
+}
+inline SymbolOccurrenceKind operator&(SymbolOccurrenceKind A,
+ SymbolOccurrenceKind B) {
+ return static_cast<SymbolOccurrenceKind>(static_cast<uint8_t>(A) &
+ static_cast<uint8_t>(B));
+}
+
+// Represents a symbol occurrence in the source file. It could be a
+// declaration/definition/reference occurrence.
+//
+// WARNING: Location does not own the underlying data - Copies are shallow.
+struct SymbolOccurrence {
+ // The location of the occurrence.
+ SymbolLocation Location;
+ SymbolOccurrenceKind Kind = SymbolOccurrenceKind::Unknown;
+};
+
struct FuzzyFindRequest {
/// \brief A query string for the fuzzy find. This is matched against symbols'
/// un-qualified identifiers and should not contain qualifiers like "::".
@@ -273,12 +335,22 @@ struct FuzzyFindRequest {
/// \brief The number of top candidates to return. The index may choose to
/// return more than this, e.g. if it doesn't know which candidates are best.
size_t MaxCandidateCount = UINT_MAX;
+ /// If set to true, only symbols for completion support will be considered.
+ bool RestrictForCodeCompletion = false;
+ /// Contextually relevant files (e.g. the file we're code-completing in).
+ /// Paths should be absolute.
+ std::vector<std::string> ProximityPaths;
};
struct LookupRequest {
llvm::DenseSet<SymbolID> IDs;
};
+struct OccurrencesRequest {
+ llvm::DenseSet<SymbolID> IDs;
+ SymbolOccurrenceKind Filter;
+};
+
/// \brief Interface for symbol indexes that can be used for searching or
/// matching symbols among a set of symbols based on names or unique IDs.
class SymbolIndex {
@@ -301,8 +373,15 @@ public:
lookup(const LookupRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const = 0;
- // FIXME: add interfaces for more index use cases:
- // - getAllOccurrences(SymbolID);
+ /// CrossReference finds all symbol occurrences (e.g. references,
+ /// declarations, definitions) and applies \p Callback on each result.
+ ///
+ /// Resutls are returned in arbitrary order.
+ ///
+ /// The returned result must be deep-copied if it's used outside Callback.
+ virtual void findOccurrences(
+ const OccurrencesRequest &Req,
+ llvm::function_ref<void(const SymbolOccurrence &)> Callback) const = 0;
};
} // namespace clangd
diff --git a/clangd/index/MemIndex.cpp b/clangd/index/MemIndex.cpp
index e1be6e2d..eca0bfe7 100644
--- a/clangd/index/MemIndex.cpp
+++ b/clangd/index/MemIndex.cpp
@@ -45,6 +45,8 @@ bool MemIndex::fuzzyFind(
// Exact match against all possible scopes.
if (!Req.Scopes.empty() && !llvm::is_contained(Req.Scopes, Sym->Scope))
continue;
+ if (Req.RestrictForCodeCompletion && !Sym->IsIndexedForCodeCompletion)
+ continue;
if (auto Score = Filter.match(Sym->Name)) {
Top.emplace(-*Score * quality(*Sym), Sym);
@@ -85,5 +87,11 @@ std::unique_ptr<SymbolIndex> MemIndex::build(SymbolSlab Slab) {
return std::move(MemIdx);
}
+void MemIndex::findOccurrences(
+ const OccurrencesRequest &Req,
+ llvm::function_ref<void(const SymbolOccurrence &)> Callback) const {
+ log("findOccurrences is not implemented.");
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/index/MemIndex.h b/clangd/index/MemIndex.h
index 3147a6c2..0f59b890 100644
--- a/clangd/index/MemIndex.h
+++ b/clangd/index/MemIndex.h
@@ -31,10 +31,14 @@ public:
fuzzyFind(const FuzzyFindRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const override;
- virtual void
+ void
lookup(const LookupRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const override;
+ void findOccurrences(const OccurrencesRequest &Req,
+ llvm::function_ref<void(const SymbolOccurrence &)>
+ Callback) const override;
+
private:
std::shared_ptr<std::vector<const Symbol *>> Symbols;
// Index is a set of symbols that are deduplicated by symbol IDs.
diff --git a/clangd/index/Merge.cpp b/clangd/index/Merge.cpp
index 41d5345d..58d15648 100644
--- a/clangd/index/Merge.cpp
+++ b/clangd/index/Merge.cpp
@@ -7,6 +7,7 @@
//
//===---------------------------------------------------------------------===//
#include "Merge.h"
+#include "../Logger.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/raw_ostream.h"
namespace clang {
@@ -74,10 +75,16 @@ class MergedIndex : public SymbolIndex {
Callback(*Sym);
}
+ void findOccurrences(const OccurrencesRequest &Req,
+ llvm::function_ref<void(const SymbolOccurrence &)>
+ Callback) const override {
+ log("findOccurrences is not implemented.");
+ }
+
private:
const SymbolIndex *Dynamic, *Static;
};
-}
+} // namespace
Symbol
mergeSymbol(const Symbol &L, const Symbol &R, Symbol::Details *Scratch) {
@@ -96,14 +103,10 @@ mergeSymbol(const Symbol &L, const Symbol &R, Symbol::Details *Scratch) {
if (!S.CanonicalDeclaration)
S.CanonicalDeclaration = O.CanonicalDeclaration;
S.References += O.References;
- if (S.CompletionLabel == "")
- S.CompletionLabel = O.CompletionLabel;
- if (S.CompletionFilterText == "")
- S.CompletionFilterText = O.CompletionFilterText;
- if (S.CompletionPlainInsertText == "")
- S.CompletionPlainInsertText = O.CompletionPlainInsertText;
- if (S.CompletionSnippetInsertText == "")
- S.CompletionSnippetInsertText = O.CompletionSnippetInsertText;
+ if (S.Signature == "")
+ S.Signature = O.Signature;
+ if (S.CompletionSnippetSuffix == "")
+ S.CompletionSnippetSuffix = O.CompletionSnippetSuffix;
if (O.Detail) {
if (S.Detail) {
@@ -111,14 +114,16 @@ mergeSymbol(const Symbol &L, const Symbol &R, Symbol::Details *Scratch) {
*Scratch = *S.Detail;
if (Scratch->Documentation == "")
Scratch->Documentation = O.Detail->Documentation;
- if (Scratch->CompletionDetail == "")
- Scratch->CompletionDetail = O.Detail->CompletionDetail;
+ if (Scratch->ReturnType == "")
+ Scratch->ReturnType = O.Detail->ReturnType;
if (Scratch->IncludeHeader == "")
Scratch->IncludeHeader = O.Detail->IncludeHeader;
S.Detail = Scratch;
} else
S.Detail = O.Detail;
}
+
+ S.Origin |= O.Origin | SymbolOrigin::Merge;
return S;
}
diff --git a/clangd/index/SymbolCollector.cpp b/clangd/index/SymbolCollector.cpp
index 482a4652..6c6a7924 100644
--- a/clangd/index/SymbolCollector.cpp
+++ b/clangd/index/SymbolCollector.cpp
@@ -9,6 +9,7 @@
#include "SymbolCollector.h"
#include "../AST.h"
+#include "../CodeComplete.h"
#include "../CodeCompletionStrings.h"
#include "../Logger.h"
#include "../SourceCode.h"
@@ -51,7 +52,7 @@ llvm::Optional<std::string> toURI(const SourceManager &SM, StringRef Path,
if (std::error_code EC =
SM.getFileManager().getVirtualFileSystem()->makeAbsolute(
AbsolutePath))
- log("Warning: could not make absolute file: " + EC.message());
+ log("Warning: could not make absolute file: {0}", EC.message());
if (llvm::sys::path::is_absolute(AbsolutePath)) {
// Handle the symbolic link path case where the current working directory
// (getCurrentWorkingDirectory) is a symlink./ We always want to the real
@@ -74,9 +75,10 @@ llvm::Optional<std::string> toURI(const SourceManager &SM, StringRef Path,
}
} else if (!Opts.FallbackDir.empty()) {
llvm::sys::fs::make_absolute(Opts.FallbackDir, AbsolutePath);
- llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true);
}
+ llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true);
+
std::string ErrMsg;
for (const auto &Scheme : Opts.URISchemes) {
auto U = URI::create(AbsolutePath, Scheme);
@@ -84,8 +86,7 @@ llvm::Optional<std::string> toURI(const SourceManager &SM, StringRef Path,
return U->toString();
ErrMsg += llvm::toString(U.takeError()) + "\n";
}
- log(llvm::Twine("Failed to create an URI for file ") + AbsolutePath + ": " +
- ErrMsg);
+ log("Failed to create an URI for file {0}: {1}", AbsolutePath, ErrMsg);
return llvm::None;
}
@@ -129,52 +130,6 @@ bool isPrivateProtoDecl(const NamedDecl &ND) {
std::any_of(Name.begin(), Name.end(), islower);
}
-bool shouldFilterDecl(const NamedDecl *ND, ASTContext *ASTCtx,
- const SymbolCollector::Options &Opts) {
- using namespace clang::ast_matchers;
- if (ND->isImplicit())
- return true;
- // Skip anonymous declarations, e.g (anonymous enum/class/struct).
- if (ND->getDeclName().isEmpty())
- return true;
-
- // FIXME: figure out a way to handle internal linkage symbols (e.g. static
- // variables, function) defined in the .cc files. Also we skip the symbols
- // in anonymous namespace as the qualifier names of these symbols are like
- // `foo::<anonymous>::bar`, which need a special handling.
- // In real world projects, we have a relatively large set of header files
- // that define static variables (like "static const int A = 1;"), we still
- // want to collect these symbols, although they cause potential ODR
- // violations.
- if (ND->isInAnonymousNamespace())
- return true;
-
- // We only want:
- // * symbols in namespaces or translation unit scopes (e.g. no class
- // members)
- // * enum constants in unscoped enum decl (e.g. "red" in "enum {red};")
- auto InTopLevelScope = hasDeclContext(
- anyOf(namespaceDecl(), translationUnitDecl(), linkageSpecDecl()));
- // Don't index template specializations.
- auto IsSpecialization =
- anyOf(functionDecl(isExplicitTemplateSpecialization()),
- cxxRecordDecl(isExplicitTemplateSpecialization()),
- varDecl(isExplicitTemplateSpecialization()));
- if (match(decl(allOf(unless(isExpansionInMainFile()),
- anyOf(InTopLevelScope,
- hasDeclContext(enumDecl(InTopLevelScope,
- unless(isScoped())))),
- unless(IsSpecialization))),
- *ND, *ASTCtx)
- .empty())
- return true;
-
- // Avoid indexing internal symbols in protobuf generated headers.
- if (isPrivateProtoDecl(*ND))
- return true;
- return false;
-}
-
// We only collect #include paths for symbols that are suitable for global code
// completion, except for namespaces since #include path for a namespace is hard
// to define.
@@ -227,22 +182,19 @@ getIncludeHeader(llvm::StringRef QName, const SourceManager &SM,
return toURI(SM, Header, Opts);
}
-// Return the symbol location of the given declaration `D`.
-//
-// For symbols defined inside macros:
-// * use expansion location, if the symbol is formed via macro concatenation.
-// * use spelling location, otherwise.
-llvm::Optional<SymbolLocation> getSymbolLocation(
- const NamedDecl &D, SourceManager &SM, const SymbolCollector::Options &Opts,
- const clang::LangOptions &LangOpts, std::string &FileURIStorage) {
- SourceLocation NameLoc = findNameLoc(&D);
- auto U = toURI(SM, SM.getFilename(NameLoc), Opts);
+// Return the symbol location of the token at \p Loc.
+llvm::Optional<SymbolLocation>
+getTokenLocation(SourceLocation TokLoc, const SourceManager &SM,
+ const SymbolCollector::Options &Opts,
+ const clang::LangOptions &LangOpts,
+ std::string &FileURIStorage) {
+ auto U = toURI(SM, SM.getFilename(TokLoc), Opts);
if (!U)
return llvm::None;
FileURIStorage = std::move(*U);
SymbolLocation Result;
Result.FileURI = FileURIStorage;
- auto TokenLength = clang::Lexer::MeasureTokenLength(NameLoc, SM, LangOpts);
+ auto TokenLength = clang::Lexer::MeasureTokenLength(TokLoc, SM, LangOpts);
auto CreatePosition = [&SM](SourceLocation Loc) {
auto LSPLoc = sourceLocToPosition(SM, Loc);
@@ -252,8 +204,8 @@ llvm::Optional<SymbolLocation> getSymbolLocation(
return Pos;
};
- Result.Start = CreatePosition(NameLoc);
- auto EndLoc = NameLoc.getLocWithOffset(TokenLength);
+ Result.Start = CreatePosition(TokLoc);
+ auto EndLoc = TokLoc.getLocWithOffset(TokenLength);
Result.End = CreatePosition(EndLoc);
return std::move(Result);
@@ -283,6 +235,52 @@ void SymbolCollector::initialize(ASTContext &Ctx) {
llvm::make_unique<CodeCompletionTUInfo>(CompletionAllocator);
}
+bool SymbolCollector::shouldCollectSymbol(const NamedDecl &ND,
+ ASTContext &ASTCtx,
+ const Options &Opts) {
+ using namespace clang::ast_matchers;
+ if (ND.isImplicit())
+ return false;
+ // Skip anonymous declarations, e.g (anonymous enum/class/struct).
+ if (ND.getDeclName().isEmpty())
+ return false;
+
+ // FIXME: figure out a way to handle internal linkage symbols (e.g. static
+ // variables, function) defined in the .cc files. Also we skip the symbols
+ // in anonymous namespace as the qualifier names of these symbols are like
+ // `foo::<anonymous>::bar`, which need a special handling.
+ // In real world projects, we have a relatively large set of header files
+ // that define static variables (like "static const int A = 1;"), we still
+ // want to collect these symbols, although they cause potential ODR
+ // violations.
+ if (ND.isInAnonymousNamespace())
+ return false;
+
+ // We want most things but not "local" symbols such as symbols inside
+ // FunctionDecl, BlockDecl, ObjCMethodDecl and OMPDeclareReductionDecl.
+ // FIXME: Need a matcher for ExportDecl in order to include symbols declared
+ // within an export.
+ auto InNonLocalContext = hasDeclContext(anyOf(
+ translationUnitDecl(), namespaceDecl(), linkageSpecDecl(), recordDecl(),
+ enumDecl(), objcProtocolDecl(), objcInterfaceDecl(), objcCategoryDecl(),
+ objcCategoryImplDecl(), objcImplementationDecl()));
+ // Don't index template specializations and expansions in main files.
+ auto IsSpecialization =
+ anyOf(functionDecl(isExplicitTemplateSpecialization()),
+ cxxRecordDecl(isExplicitTemplateSpecialization()),
+ varDecl(isExplicitTemplateSpecialization()));
+ if (match(decl(allOf(unless(isExpansionInMainFile()), InNonLocalContext,
+ unless(IsSpecialization))),
+ ND, ASTCtx)
+ .empty())
+ return false;
+
+ // Avoid indexing internal symbols in protobuf generated headers.
+ if (isPrivateProtoDecl(ND))
+ return false;
+ return true;
+}
+
// Always return true to continue indexing.
bool SymbolCollector::handleDeclOccurence(
const Decl *D, index::SymbolRoleSet Roles,
@@ -290,6 +288,19 @@ bool SymbolCollector::handleDeclOccurence(
index::IndexDataConsumer::ASTNodeInfo ASTNode) {
assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set.");
assert(CompletionAllocator && CompletionTUInfo);
+ assert(ASTNode.OrigD);
+ // If OrigD is an declaration associated with a friend declaration and it's
+ // not a definition, skip it. Note that OrigD is the occurrence that the
+ // collector is currently visiting.
+ if ((ASTNode.OrigD->getFriendObjectKind() !=
+ Decl::FriendObjectKind::FOK_None) &&
+ !(Roles & static_cast<unsigned>(index::SymbolRole::Definition)))
+ return true;
+ // A declaration created for a friend declaration should not be used as the
+ // canonical declaration in the index. Use OrigD instead, unless we've already
+ // picked a replacement for D
+ if (D->getFriendObjectKind() != Decl::FriendObjectKind::FOK_None)
+ D = CanonicalDecls.try_emplace(D, ASTNode.OrigD).first->second;
const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(D);
if (!ND)
return true;
@@ -306,42 +317,127 @@ bool SymbolCollector::handleDeclOccurence(
if (!(Roles & static_cast<unsigned>(index::SymbolRole::Declaration) ||
Roles & static_cast<unsigned>(index::SymbolRole::Definition)))
return true;
- if (shouldFilterDecl(ND, ASTCtx, Opts))
+ if (!shouldCollectSymbol(*ND, *ASTCtx, Opts))
return true;
- llvm::SmallString<128> USR;
- if (index::generateUSRForDecl(ND, USR))
+ auto ID = getSymbolID(ND);
+ if (!ID)
return true;
- SymbolID ID(USR);
const NamedDecl &OriginalDecl = *cast<NamedDecl>(ASTNode.OrigD);
- const Symbol *BasicSymbol = Symbols.find(ID);
+ const Symbol *BasicSymbol = Symbols.find(*ID);
if (!BasicSymbol) // Regardless of role, ND is the canonical declaration.
- BasicSymbol = addDeclaration(*ND, std::move(ID));
+ BasicSymbol = addDeclaration(*ND, std::move(*ID));
else if (isPreferredDeclaration(OriginalDecl, Roles))
// If OriginalDecl is preferred, replace the existing canonical
// declaration (e.g. a class forward declaration). There should be at most
// one duplicate as we expect to see only one preferred declaration per
// TU, because in practice they are definitions.
- BasicSymbol = addDeclaration(OriginalDecl, std::move(ID));
+ BasicSymbol = addDeclaration(OriginalDecl, std::move(*ID));
if (Roles & static_cast<unsigned>(index::SymbolRole::Definition))
addDefinition(OriginalDecl, *BasicSymbol);
return true;
}
+bool SymbolCollector::handleMacroOccurence(const IdentifierInfo *Name,
+ const MacroInfo *MI,
+ index::SymbolRoleSet Roles,
+ SourceLocation Loc) {
+ if (!Opts.CollectMacro)
+ return true;
+ assert(PP.get());
+
+ const auto &SM = PP->getSourceManager();
+ if (SM.isInMainFile(SM.getExpansionLoc(MI->getDefinitionLoc())))
+ return true;
+ // Header guards are not interesting in index. Builtin macros don't have
+ // useful locations and are not needed for code completions.
+ if (MI->isUsedForHeaderGuard() || MI->isBuiltinMacro())
+ return true;
+
+ // Mark the macro as referenced if this is a reference coming from the main
+ // file. The macro may not be an interesting symbol, but it's cheaper to check
+ // at the end.
+ if (Opts.CountReferences &&
+ (Roles & static_cast<unsigned>(index::SymbolRole::Reference)) &&
+ SM.getFileID(SM.getSpellingLoc(Loc)) == SM.getMainFileID())
+ ReferencedMacros.insert(Name);
+ // Don't continue indexing if this is a mere reference.
+ // FIXME: remove macro with ID if it is undefined.
+ if (!(Roles & static_cast<unsigned>(index::SymbolRole::Declaration) ||
+ Roles & static_cast<unsigned>(index::SymbolRole::Definition)))
+ return true;
+
+ llvm::SmallString<128> USR;
+ if (index::generateUSRForMacro(Name->getName(), MI->getDefinitionLoc(), SM,
+ USR))
+ return true;
+ SymbolID ID(USR);
+
+ // Only collect one instance in case there are multiple.
+ if (Symbols.find(ID) != nullptr)
+ return true;
+
+ Symbol S;
+ S.ID = std::move(ID);
+ S.Name = Name->getName();
+ S.IsIndexedForCodeCompletion = true;
+ S.SymInfo = index::getSymbolInfoForMacro(*MI);
+ std::string FileURI;
+ if (auto DeclLoc = getTokenLocation(MI->getDefinitionLoc(), SM, Opts,
+ PP->getLangOpts(), FileURI))
+ S.CanonicalDeclaration = *DeclLoc;
+
+ CodeCompletionResult SymbolCompletion(Name);
+ const auto *CCS = SymbolCompletion.CreateCodeCompletionStringForMacro(
+ *PP, *CompletionAllocator, *CompletionTUInfo);
+ std::string Signature;
+ std::string SnippetSuffix;
+ getSignature(*CCS, &Signature, &SnippetSuffix);
+
+ std::string Include;
+ if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) {
+ if (auto Header =
+ getIncludeHeader(Name->getName(), SM,
+ SM.getExpansionLoc(MI->getDefinitionLoc()), Opts))
+ Include = std::move(*Header);
+ }
+ S.Signature = Signature;
+ S.CompletionSnippetSuffix = SnippetSuffix;
+ Symbol::Details Detail;
+ Detail.IncludeHeader = Include;
+ S.Detail = &Detail;
+ Symbols.insert(S);
+ return true;
+}
+
void SymbolCollector::finish() {
- // At the end of the TU, add 1 to the refcount of the ReferencedDecls.
- for (const auto *ND : ReferencedDecls) {
- llvm::SmallString<128> USR;
- if (!index::generateUSRForDecl(ND, USR))
- if (const auto *S = Symbols.find(SymbolID(USR))) {
- Symbol Inc = *S;
- ++Inc.References;
- Symbols.insert(Inc);
- }
+ // At the end of the TU, add 1 to the refcount of all referenced symbols.
+ auto IncRef = [this](const SymbolID &ID) {
+ if (const auto *S = Symbols.find(ID)) {
+ Symbol Inc = *S;
+ ++Inc.References;
+ Symbols.insert(Inc);
+ }
+ };
+ for (const NamedDecl *ND : ReferencedDecls) {
+ if (auto ID = getSymbolID(ND)) {
+ IncRef(*ID);
+ }
+ }
+ if (Opts.CollectMacro) {
+ assert(PP);
+ for (const IdentifierInfo *II : ReferencedMacros) {
+ llvm::SmallString<128> USR;
+ if (const auto *MI = PP->getMacroDefinition(II).getMacroInfo())
+ if (!index::generateUSRForMacro(II->getName(), MI->getDefinitionLoc(),
+ PP->getSourceManager(), USR))
+ IncRef(SymbolID(USR));
+ }
}
ReferencedDecls.clear();
+ ReferencedMacros.clear();
}
const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
@@ -349,25 +445,18 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
auto &Ctx = ND.getASTContext();
auto &SM = Ctx.getSourceManager();
- std::string QName;
- llvm::raw_string_ostream OS(QName);
- PrintingPolicy Policy(ASTCtx->getLangOpts());
- // Note that inline namespaces are treated as transparent scopes. This
- // reflects the way they're most commonly used for lookup. Ideally we'd
- // include them, but at query time it's hard to find all the inline
- // namespaces to query: the preamble doesn't have a dedicated list.
- Policy.SuppressUnwrittenScope = true;
- ND.printQualifiedName(OS, Policy);
- OS.flush();
- assert(!StringRef(QName).startswith("::"));
-
Symbol S;
S.ID = std::move(ID);
+ std::string QName = printQualifiedName(ND);
std::tie(S.Scope, S.Name) = splitQualifiedName(QName);
+ // FIXME: this returns foo:bar: for objective-C methods, we prefer only foo:
+ // for consistency with CodeCompletionString and a clean name/signature split.
+
+ S.IsIndexedForCodeCompletion = isIndexedForCodeCompletion(ND, Ctx);
S.SymInfo = index::getSymbolInfo(&ND);
std::string FileURI;
- if (auto DeclLoc =
- getSymbolLocation(ND, SM, Opts, ASTCtx->getLangOpts(), FileURI))
+ if (auto DeclLoc = getTokenLocation(findNameLoc(&ND), SM, Opts,
+ ASTCtx->getLangOpts(), FileURI))
S.CanonicalDeclaration = *DeclLoc;
// Add completion info.
@@ -379,19 +468,13 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
*ASTCtx, *PP, CodeCompletionContext::CCC_Name, *CompletionAllocator,
*CompletionTUInfo,
/*IncludeBriefComments*/ false);
- std::string Label;
- std::string SnippetInsertText;
- std::string IgnoredLabel;
- std::string PlainInsertText;
- getLabelAndInsertText(*CCS, &Label, &SnippetInsertText,
- /*EnableSnippets=*/true);
- getLabelAndInsertText(*CCS, &IgnoredLabel, &PlainInsertText,
- /*EnableSnippets=*/false);
- std::string FilterText = getFilterText(*CCS);
+ std::string Signature;
+ std::string SnippetSuffix;
+ getSignature(*CCS, &Signature, &SnippetSuffix);
std::string Documentation =
formatDocumentation(*CCS, getDocComment(Ctx, SymbolCompletion,
/*CommentsFromHeaders=*/true));
- std::string CompletionDetail = getDetail(*CCS);
+ std::string ReturnType = getReturnType(*CCS);
std::string Include;
if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) {
@@ -401,16 +484,15 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
QName, SM, SM.getExpansionLoc(ND.getLocation()), Opts))
Include = std::move(*Header);
}
- S.CompletionFilterText = FilterText;
- S.CompletionLabel = Label;
- S.CompletionPlainInsertText = PlainInsertText;
- S.CompletionSnippetInsertText = SnippetInsertText;
+ S.Signature = Signature;
+ S.CompletionSnippetSuffix = SnippetSuffix;
Symbol::Details Detail;
Detail.Documentation = Documentation;
- Detail.CompletionDetail = CompletionDetail;
+ Detail.ReturnType = ReturnType;
Detail.IncludeHeader = Include;
S.Detail = &Detail;
+ S.Origin = Opts.Origin;
Symbols.insert(S);
return Symbols.find(S.ID);
}
@@ -424,8 +506,9 @@ void SymbolCollector::addDefinition(const NamedDecl &ND,
// in clang::index. We should only see one definition.
Symbol S = DeclSym;
std::string FileURI;
- if (auto DefLoc = getSymbolLocation(ND, ND.getASTContext().getSourceManager(),
- Opts, ASTCtx->getLangOpts(), FileURI))
+ if (auto DefLoc = getTokenLocation(findNameLoc(&ND),
+ ND.getASTContext().getSourceManager(),
+ Opts, ASTCtx->getLangOpts(), FileURI))
S.Definition = *DefLoc;
Symbols.insert(S);
}
diff --git a/clangd/index/SymbolCollector.h b/clangd/index/SymbolCollector.h
index 269c184b..bc882e47 100644
--- a/clangd/index/SymbolCollector.h
+++ b/clangd/index/SymbolCollector.h
@@ -18,13 +18,18 @@
namespace clang {
namespace clangd {
-/// \brief Collect top-level symbols from an AST. These are symbols defined
-/// immediately inside a namespace or a translation unit scope. For example,
-/// symbols in classes or functions are not collected. Note that this only
-/// collects symbols that declared in at least one file that is not a main
-/// file (i.e. the source file corresponding to a TU). These are symbols that
-/// can be imported by other files by including the file where symbols are
-/// declared.
+/// \brief Collect declarations (symbols) from an AST.
+/// It collects most declarations except:
+/// - Implicit declarations
+/// - Anonymous declarations (anonymous enum/class/struct, etc)
+/// - Declarations in anonymous namespaces
+/// - Local declarations (in function bodies, blocks, etc)
+/// - Declarations in main files
+/// - Template specializations
+/// - Library-specific private declarations (e.g. private declaration generated
+/// by protobuf compiler)
+///
+/// See also shouldCollectSymbol(...).
///
/// Clients (e.g. clangd) can use SymbolCollector together with
/// index::indexTopLevelDecls to retrieve all symbols when the source file is
@@ -47,10 +52,22 @@ public:
const CanonicalIncludes *Includes = nullptr;
// Populate the Symbol.References field.
bool CountReferences = false;
+ // Every symbol collected will be stamped with this origin.
+ SymbolOrigin Origin = SymbolOrigin::Unknown;
+ /// Collect macros.
+ /// Note that SymbolCollector must be run with preprocessor in order to
+ /// collect macros. For example, `indexTopLevelDecls` will not index any
+ /// macro even if this is true.
+ bool CollectMacro = false;
};
SymbolCollector(Options Opts);
+ /// Returns true is \p ND should be collected.
+ /// AST matchers require non-const ASTContext.
+ static bool shouldCollectSymbol(const NamedDecl &ND, ASTContext &ASTCtx,
+ const Options &Opts);
+
void initialize(ASTContext &Ctx) override;
void setPreprocessor(std::shared_ptr<Preprocessor> PP) override {
@@ -63,6 +80,10 @@ public:
SourceLocation Loc,
index::IndexDataConsumer::ASTNodeInfo ASTNode) override;
+ bool handleMacroOccurence(const IdentifierInfo *Name, const MacroInfo *MI,
+ index::SymbolRoleSet Roles,
+ SourceLocation Loc) override;
+
SymbolSlab takeSymbols() { return std::move(Symbols).build(); }
void finish() override;
@@ -78,8 +99,15 @@ private:
std::shared_ptr<GlobalCodeCompletionAllocator> CompletionAllocator;
std::unique_ptr<CodeCompletionTUInfo> CompletionTUInfo;
Options Opts;
- // Decls referenced from the current TU, flushed on finish().
+ // Symbols referenced from the current TU, flushed on finish().
llvm::DenseSet<const NamedDecl *> ReferencedDecls;
+ llvm::DenseSet<const IdentifierInfo *> ReferencedMacros;
+ // Maps canonical declaration provided by clang to canonical declaration for
+ // an index symbol, if clangd prefers a different declaration than that
+ // provided by clang. For example, friend declaration might be considered
+ // canonical by clang but should not be considered canonical in the index
+ // unless it's a definition.
+ llvm::DenseMap<const Decl *, const Decl *> CanonicalDecls;
};
} // namespace clangd
diff --git a/clangd/index/SymbolYAML.cpp b/clangd/index/SymbolYAML.cpp
index 26b88865..1701b5a0 100644
--- a/clangd/index/SymbolYAML.cpp
+++ b/clangd/index/SymbolYAML.cpp
@@ -69,7 +69,7 @@ template <> struct MappingTraits<SymbolInfo> {
template <> struct MappingTraits<Symbol::Details> {
static void mapping(IO &io, Symbol::Details &Detail) {
io.mapOptional("Documentation", Detail.Documentation);
- io.mapOptional("CompletionDetail", Detail.CompletionDetail);
+ io.mapOptional("ReturnType", Detail.ReturnType);
io.mapOptional("IncludeHeader", Detail.IncludeHeader);
}
};
@@ -108,12 +108,10 @@ template <> struct MappingTraits<Symbol> {
SymbolLocation());
IO.mapOptional("Definition", Sym.Definition, SymbolLocation());
IO.mapOptional("References", Sym.References, 0u);
- IO.mapRequired("CompletionLabel", Sym.CompletionLabel);
- IO.mapRequired("CompletionFilterText", Sym.CompletionFilterText);
- IO.mapRequired("CompletionPlainInsertText", Sym.CompletionPlainInsertText);
-
- IO.mapOptional("CompletionSnippetInsertText",
- Sym.CompletionSnippetInsertText);
+ IO.mapOptional("IsIndexedForCodeCompletion", Sym.IsIndexedForCodeCompletion,
+ false);
+ IO.mapOptional("Signature", Sym.Signature);
+ IO.mapOptional("CompletionSnippetSuffix", Sym.CompletionSnippetSuffix);
IO.mapOptional("Detail", NDetail->Opt);
}
};
@@ -170,7 +168,7 @@ template <> struct ScalarEnumerationTraits<SymbolKind> {
namespace clang {
namespace clangd {
-SymbolSlab SymbolsFromYAML(llvm::StringRef YAMLContent) {
+SymbolSlab symbolsFromYAML(llvm::StringRef YAMLContent) {
// Store data of pointer fields (excl. `StringRef`) like `Detail`.
llvm::BumpPtrAllocator Arena;
llvm::yaml::Input Yin(YAMLContent, &Arena);
diff --git a/clangd/index/SymbolYAML.h b/clangd/index/SymbolYAML.h
index 9d16cc5d..726af6c6 100644
--- a/clangd/index/SymbolYAML.h
+++ b/clangd/index/SymbolYAML.h
@@ -27,7 +27,7 @@ namespace clang {
namespace clangd {
// Read symbols from a YAML-format string.
-SymbolSlab SymbolsFromYAML(llvm::StringRef YAMLContent);
+SymbolSlab symbolsFromYAML(llvm::StringRef YAMLContent);
// Read one symbol from a YAML-stream.
// The arena must be the Input's context! (i.e. yaml::Input Input(Text, &Arena))
diff --git a/clangd/index/dex/Iterator.cpp b/clangd/index/dex/Iterator.cpp
new file mode 100644
index 00000000..25107f92
--- /dev/null
+++ b/clangd/index/dex/Iterator.cpp
@@ -0,0 +1,244 @@
+//===--- Iterator.cpp - Query Symbol Retrieval ------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Iterator.h"
+#include <algorithm>
+#include <cassert>
+#include <numeric>
+
+namespace clang {
+namespace clangd {
+namespace dex {
+
+namespace {
+
+/// Implements Iterator over a PostingList. DocumentIterator is the most basic
+/// iterator: it doesn't have any children (hence it is the leaf of iterator
+/// tree) and is simply a wrapper around PostingList::const_iterator.
+class DocumentIterator : public Iterator {
+public:
+ DocumentIterator(PostingListRef Documents)
+ : Documents(Documents), Index(std::begin(Documents)) {}
+
+ bool reachedEnd() const override { return Index == std::end(Documents); }
+
+ /// Advances cursor to the next item.
+ void advance() override {
+ assert(!reachedEnd() && "DocumentIterator can't advance at the end.");
+ ++Index;
+ }
+
+ /// Applies binary search to advance cursor to the next item with DocID equal
+ /// or higher than the given one.
+ void advanceTo(DocID ID) override {
+ assert(!reachedEnd() && "DocumentIterator can't advance at the end.");
+ Index = std::lower_bound(Index, std::end(Documents), ID);
+ }
+
+ DocID peek() const override {
+ assert(!reachedEnd() && "DocumentIterator can't call peek() at the end.");
+ return *Index;
+ }
+
+ llvm::raw_ostream &dump(llvm::raw_ostream &OS) const override {
+ OS << '[';
+ auto Separator = "";
+ for (const auto &ID : Documents) {
+ OS << Separator << ID;
+ Separator = ", ";
+ }
+ OS << ']';
+ return OS;
+ }
+
+private:
+ PostingListRef Documents;
+ PostingListRef::const_iterator Index;
+};
+
+/// Implements Iterator over the intersection of other iterators.
+///
+/// AndIterator iterates through common items among all children. It becomes
+/// exhausted as soon as any child becomes exhausted. After each mutation, the
+/// iterator restores the invariant: all children must point to the same item.
+class AndIterator : public Iterator {
+public:
+ AndIterator(std::vector<std::unique_ptr<Iterator>> AllChildren)
+ : Children(std::move(AllChildren)) {
+ assert(!Children.empty() && "AndIterator should have at least one child.");
+ // Establish invariants.
+ sync();
+ }
+
+ bool reachedEnd() const override { return ReachedEnd; }
+
+ /// Advances all children to the next common item.
+ void advance() override {
+ assert(!reachedEnd() && "AndIterator can't call advance() at the end.");
+ Children.front()->advance();
+ sync();
+ }
+
+ /// Advances all children to the next common item with DocumentID >= ID.
+ void advanceTo(DocID ID) override {
+ assert(!reachedEnd() && "AndIterator can't call advanceTo() at the end.");
+ Children.front()->advanceTo(ID);
+ sync();
+ }
+
+ DocID peek() const override { return Children.front()->peek(); }
+
+ llvm::raw_ostream &dump(llvm::raw_ostream &OS) const override {
+ OS << "(& ";
+ auto Separator = "";
+ for (const auto &Child : Children) {
+ OS << Separator << *Child;
+ Separator = " ";
+ }
+ OS << ')';
+ return OS;
+ }
+
+private:
+ /// Restores class invariants: each child will point to the same element after
+ /// sync.
+ void sync() {
+ ReachedEnd |= Children.front()->reachedEnd();
+ if (ReachedEnd)
+ return;
+ auto SyncID = Children.front()->peek();
+ // Indicates whether any child needs to be advanced to new SyncID.
+ bool NeedsAdvance = false;
+ do {
+ NeedsAdvance = false;
+ for (auto &Child : Children) {
+ Child->advanceTo(SyncID);
+ ReachedEnd |= Child->reachedEnd();
+ // If any child reaches end And iterator can not match any other items.
+ // In this case, just terminate the process.
+ if (ReachedEnd)
+ return;
+ // If any child goes beyond given ID (i.e. ID is not the common item),
+ // all children should be advanced to the next common item.
+ // FIXME(kbobyrev): This is not a very optimized version; after costs
+ // are introduced, cycle should break whenever ID exceeds current one
+ // and cheapest children should be advanced over again.
+ if (Child->peek() > SyncID) {
+ SyncID = Child->peek();
+ NeedsAdvance = true;
+ }
+ }
+ } while (NeedsAdvance);
+ }
+
+ /// AndIterator owns its children and ensures that all of them point to the
+ /// same element. As soon as one child gets exhausted, AndIterator can no
+ /// longer advance and has reached its end.
+ std::vector<std::unique_ptr<Iterator>> Children;
+ /// Indicates whether any child is exhausted. It is cheaper to maintain and
+ /// update the field, rather than traversing the whole subtree in each
+ /// reachedEnd() call.
+ bool ReachedEnd = false;
+};
+
+/// Implements Iterator over the union of other iterators.
+///
+/// OrIterator iterates through all items which can be pointed to by at least
+/// one child. To preserve the sorted order, this iterator always advances the
+/// child with smallest Child->peek() value. OrIterator becomes exhausted as
+/// soon as all of its children are exhausted.
+class OrIterator : public Iterator {
+public:
+ OrIterator(std::vector<std::unique_ptr<Iterator>> AllChildren)
+ : Children(std::move(AllChildren)) {
+ assert(Children.size() > 0 && "Or Iterator must have at least one child.");
+ }
+
+ /// Returns true if all children are exhausted.
+ bool reachedEnd() const override {
+ return std::all_of(begin(Children), end(Children),
+ [](const std::unique_ptr<Iterator> &Child) {
+ return Child->reachedEnd();
+ });
+ }
+
+ /// Moves each child pointing to the smallest DocID to the next item.
+ void advance() override {
+ assert(!reachedEnd() &&
+ "OrIterator must have at least one child to advance().");
+ const auto SmallestID = peek();
+ for (const auto &Child : Children)
+ if (!Child->reachedEnd() && Child->peek() == SmallestID)
+ Child->advance();
+ }
+
+ /// Advances each child to the next existing element with DocumentID >= ID.
+ void advanceTo(DocID ID) override {
+ assert(!reachedEnd() && "Can't advance iterator after it reached the end.");
+ for (const auto &Child : Children)
+ if (!Child->reachedEnd())
+ Child->advanceTo(ID);
+ }
+
+ /// Returns the element under cursor of the child with smallest Child->peek()
+ /// value.
+ DocID peek() const override {
+ assert(!reachedEnd() &&
+ "OrIterator must have at least one child to peek().");
+ DocID Result = std::numeric_limits<DocID>::max();
+
+ for (const auto &Child : Children)
+ if (!Child->reachedEnd())
+ Result = std::min(Result, Child->peek());
+
+ return Result;
+ }
+
+ llvm::raw_ostream &dump(llvm::raw_ostream &OS) const override {
+ OS << "(| ";
+ auto Separator = "";
+ for (const auto &Child : Children) {
+ OS << Separator << *Child;
+ Separator = " ";
+ }
+ OS << ')';
+ return OS;
+ }
+
+private:
+ // FIXME(kbobyrev): Would storing Children in min-heap be faster?
+ std::vector<std::unique_ptr<Iterator>> Children;
+};
+
+} // end namespace
+
+std::vector<DocID> consume(Iterator &It) {
+ std::vector<DocID> Result;
+ for (; !It.reachedEnd(); It.advance())
+ Result.push_back(It.peek());
+ return Result;
+}
+
+std::unique_ptr<Iterator> create(PostingListRef Documents) {
+ return llvm::make_unique<DocumentIterator>(Documents);
+}
+
+std::unique_ptr<Iterator>
+createAnd(std::vector<std::unique_ptr<Iterator>> Children) {
+ return llvm::make_unique<AndIterator>(move(Children));
+}
+
+std::unique_ptr<Iterator>
+createOr(std::vector<std::unique_ptr<Iterator>> Children) {
+ return llvm::make_unique<OrIterator>(move(Children));
+}
+
+} // namespace dex
+} // namespace clangd
+} // namespace clang
diff --git a/clangd/index/dex/Iterator.h b/clangd/index/dex/Iterator.h
new file mode 100644
index 00000000..f6270f12
--- /dev/null
+++ b/clangd/index/dex/Iterator.h
@@ -0,0 +1,152 @@
+//===--- Iterator.h - Query Symbol Retrieval --------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Symbol index queries consist of specific requirements for the requested
+// symbol, such as high fuzzy matching score, scope, type etc. The lists of all
+// symbols matching some criteria (e.g. belonging to "clang::clangd::" scope)
+// are expressed in a form of Search Tokens which are stored in the inverted
+// index. Inverted index maps these tokens to the posting lists - sorted ( by
+// symbol quality) sequences of symbol IDs matching the token, e.g. scope token
+// "clangd::clangd::" is mapped to the list of IDs of all symbols which are
+// declared in this namespace. Search queries are build from a set of
+// requirements which can be combined with each other forming the query trees.
+// The leafs of such trees are posting lists, and the nodes are operations on
+// these posting lists, e.g. intersection or union. Efficient processing of
+// these multi-level queries is handled by Iterators. Iterators advance through
+// all leaf posting lists producing the result of search query, which preserves
+// the sorted order of IDs. Having the resulting IDs sorted is important,
+// because it allows receiving a certain number of the most valuable items (e.g.
+// symbols with highest quality which was the sorting key in the first place)
+// without processing all items with requested properties (this might not be
+// computationally effective if search request is not very restrictive).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_DEX_ITERATOR_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_DEX_ITERATOR_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Support/raw_ostream.h"
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+namespace clang {
+namespace clangd {
+namespace dex {
+
+/// Symbol position in the list of all index symbols sorted by a pre-computed
+/// symbol quality.
+using DocID = uint32_t;
+/// Contains sorted sequence of DocIDs all of which belong to symbols matching
+/// certain criteria, i.e. containing a Search Token. PostingLists are values
+/// for the inverted index.
+using PostingList = std::vector<DocID>;
+/// Immutable reference to PostingList object.
+using PostingListRef = llvm::ArrayRef<DocID>;
+
+/// Iterator is the interface for Query Tree node. The simplest type of Iterator
+/// is DocumentIterator which is simply a wrapper around PostingList iterator
+/// and serves as the Query Tree leaf. More sophisticated examples of iterators
+/// can manage intersection, union of the elements produced by other iterators
+/// (their children) to form a multi-level Query Tree. The interface is designed
+/// to be extensible in order to support multiple types of iterators.
+class Iterator {
+ // FIXME(kbobyrev): Provide callback for matched documents.
+ // FIXME(kbobyrev): Implement new types of iterators: Label, Boost (with
+ // scoring), Limit.
+ // FIXME(kbobyrev): Implement iterator cost, an estimate of advance() calls
+ // before iterator exhaustion.
+public:
+ /// Returns true if all valid DocIDs were processed and hence the iterator is
+ /// exhausted.
+ virtual bool reachedEnd() const = 0;
+ /// Moves to next valid DocID. If it doesn't exist, the iterator is exhausted
+ /// and proceeds to the END.
+ ///
+ /// Note: reachedEnd() must be false.
+ virtual void advance() = 0;
+ /// Moves to the first valid DocID which is equal or higher than given ID. If
+ /// it doesn't exist, the iterator is exhausted and proceeds to the END.
+ ///
+ /// Note: reachedEnd() must be false.
+ virtual void advanceTo(DocID ID) = 0;
+ /// Returns the current element this iterator points to.
+ ///
+ /// Note: reachedEnd() must be false.
+ virtual DocID peek() const = 0;
+
+ virtual ~Iterator() {}
+
+ /// Prints a convenient human-readable iterator representation by recursively
+ /// dumping iterators in the following format:
+ ///
+ /// (Type Child1 Child2 ...)
+ ///
+ /// Where Type is the iterator type representation: "&" for And, "|" for Or,
+ /// ChildN is N-th iterator child. Raw iterators over PostingList are
+ /// represented as "[ID1, ID2, ...]" where IDN is N-th PostingList entry.
+ friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ const Iterator &Iterator) {
+ return Iterator.dump(OS);
+ }
+
+private:
+ virtual llvm::raw_ostream &dump(llvm::raw_ostream &OS) const = 0;
+};
+
+/// Exhausts given iterator and returns all processed DocIDs. The result
+/// contains sorted DocumentIDs.
+std::vector<DocID> consume(Iterator &It);
+
+/// Returns a document iterator over given PostingList.
+std::unique_ptr<Iterator> create(PostingListRef Documents);
+
+/// Returns AND Iterator which performs the intersection of the PostingLists of
+/// its children.
+std::unique_ptr<Iterator>
+createAnd(std::vector<std::unique_ptr<Iterator>> Children);
+
+/// Returns OR Iterator which performs the union of the PostingLists of its
+/// children.
+std::unique_ptr<Iterator>
+createOr(std::vector<std::unique_ptr<Iterator>> Children);
+
+/// This allows createAnd(create(...), create(...)) syntax.
+template <typename... Args> std::unique_ptr<Iterator> createAnd(Args... args) {
+ std::vector<std::unique_ptr<Iterator>> Children;
+ populateChildren(Children, args...);
+ return createAnd(move(Children));
+}
+
+/// This allows createOr(create(...), create(...)) syntax.
+template <typename... Args> std::unique_ptr<Iterator> createOr(Args... args) {
+ std::vector<std::unique_ptr<Iterator>> Children;
+ populateChildren(Children, args...);
+ return createOr(move(Children));
+}
+
+template <typename HeadT, typename... TailT>
+void populateChildren(std::vector<std::unique_ptr<Iterator>> &Children,
+ HeadT &Head, TailT &... Tail) {
+ Children.push_back(move(Head));
+ populateChildren(Children, Tail...);
+}
+
+template <typename HeadT>
+void populateChildren(std::vector<std::unique_ptr<Iterator>> &Children,
+ HeadT &Head) {
+ Children.push_back(move(Head));
+}
+
+} // namespace dex
+} // namespace clangd
+} // namespace clang
+
+#endif
diff --git a/clangd/index/dex/Token.h b/clangd/index/dex/Token.h
new file mode 100644
index 00000000..33a16d36
--- /dev/null
+++ b/clangd/index/dex/Token.h
@@ -0,0 +1,112 @@
+//===--- Token.h - Symbol Search primitive ----------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Token objects represent a characteristic of a symbol, which can be used to
+// perform efficient search. Tokens are keys for inverted index which are mapped
+// to the corresponding posting lists.
+//
+// The symbol std::cout might have the tokens:
+// * Scope "std::"
+// * Trigram "cou"
+// * Trigram "out"
+// * Type "std::ostream"
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DEX_TOKEN_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DEX_TOKEN_H
+
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace clangd {
+namespace dex {
+
+/// A Token represents an attribute of a symbol, such as a particular trigram
+/// present in the name (used for fuzzy search).
+///
+/// Tokens can be used to perform more sophisticated search queries by
+/// constructing complex iterator trees.
+struct Token {
+ /// Kind specifies Token type which defines semantics for the internal
+ /// representation. Each Kind has different representation stored in Data
+ /// field.
+ enum class Kind {
+ /// Represents trigram used for fuzzy search of unqualified symbol names.
+ ///
+ /// Data contains 3 bytes with trigram contents.
+ Trigram,
+ /// Scope primitives, e.g. "symbol belongs to namespace foo::bar".
+ ///
+ /// Data stroes full scope name , e.g. "foo::bar::baz::" or "" (for global
+ /// scope).
+ Scope,
+ /// Internal Token type for invalid/special tokens, e.g. empty tokens for
+ /// llvm::DenseMap.
+ Sentinel,
+ /// FIXME(kbobyrev): Add other Token Kinds
+ /// * Path with full or relative path to the directory in which symbol is
+ /// defined
+ /// * Type with qualified type name or its USR
+ };
+
+ Token(Kind TokenKind, llvm::StringRef Data)
+ : Data(Data), TokenKind(TokenKind) {}
+
+ bool operator==(const Token &Other) const {
+ return TokenKind == Other.TokenKind && Data == Other.Data;
+ }
+
+ /// Representation which is unique among Token with the same Kind.
+ std::string Data;
+ Kind TokenKind;
+
+ friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Token &T) {
+ return OS << T.Data;
+ }
+
+private:
+ friend llvm::hash_code hash_value(const Token &Token) {
+ return llvm::hash_combine(static_cast<int>(Token.TokenKind), Token.Data);
+ }
+};
+
+} // namespace dex
+} // namespace clangd
+} // namespace clang
+
+namespace llvm {
+
+// Support Tokens as DenseMap keys.
+template <> struct DenseMapInfo<clang::clangd::dex::Token> {
+ static inline clang::clangd::dex::Token getEmptyKey() {
+ return {clang::clangd::dex::Token::Kind::Sentinel, "EmptyKey"};
+ }
+
+ static inline clang::clangd::dex::Token getTombstoneKey() {
+ return {clang::clangd::dex::Token::Kind::Sentinel, "TombstoneKey"};
+ }
+
+ static unsigned getHashValue(const clang::clangd::dex::Token &Tag) {
+ return hash_value(Tag);
+ }
+
+ static bool isEqual(const clang::clangd::dex::Token &LHS,
+ const clang::clangd::dex::Token &RHS) {
+ return LHS == RHS;
+ }
+};
+
+} // namespace llvm
+
+#endif
diff --git a/clangd/index/dex/Trigram.cpp b/clangd/index/dex/Trigram.cpp
new file mode 100644
index 00000000..549093df
--- /dev/null
+++ b/clangd/index/dex/Trigram.cpp
@@ -0,0 +1,132 @@
+//===--- Trigram.cpp - Trigram generation for Fuzzy Matching ----*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Trigram.h"
+#include "../../FuzzyMatch.h"
+#include "Token.h"
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/StringExtras.h"
+
+#include <cctype>
+#include <queue>
+#include <string>
+
+using namespace llvm;
+
+namespace clang {
+namespace clangd {
+namespace dex {
+
+// FIXME(kbobyrev): Deal with short symbol symbol names. A viable approach would
+// be generating unigrams and bigrams here, too. This would prevent symbol index
+// from applying fuzzy matching on a tremendous number of symbols and allow
+// supplementary retrieval for short queries.
+//
+// Short names (total segment length <3 characters) are currently ignored.
+std::vector<Token> generateIdentifierTrigrams(llvm::StringRef Identifier) {
+ // Apply fuzzy matching text segmentation.
+ std::vector<CharRole> Roles(Identifier.size());
+ calculateRoles(Identifier,
+ llvm::makeMutableArrayRef(Roles.data(), Identifier.size()));
+
+ std::string LowercaseIdentifier = Identifier.lower();
+
+ // For each character, store indices of the characters to which fuzzy matching
+ // algorithm can jump. There are 3 possible variants:
+ //
+ // * Next Tail - next character from the same segment
+ // * Next Head - front character of the next segment
+ // * Skip-1-Next Head - front character of the skip-1-next segment
+ //
+ // Next stores tuples of three indices in the presented order, if a variant is
+ // not available then 0 is stored.
+ std::vector<std::array<unsigned, 3>> Next(LowercaseIdentifier.size());
+ unsigned NextTail = 0, NextHead = 0, NextNextHead = 0;
+ for (int I = LowercaseIdentifier.size() - 1; I >= 0; --I) {
+ Next[I] = {{NextTail, NextHead, NextNextHead}};
+ NextTail = Roles[I] == Tail ? I : 0;
+ if (Roles[I] == Head) {
+ NextNextHead = NextHead;
+ NextHead = I;
+ }
+ }
+
+ DenseSet<Token> UniqueTrigrams;
+ std::array<char, 4> Chars;
+ for (size_t I = 0; I < LowercaseIdentifier.size(); ++I) {
+ // Skip delimiters.
+ if (Roles[I] != Head && Roles[I] != Tail)
+ continue;
+ for (const unsigned J : Next[I]) {
+ if (!J)
+ continue;
+ for (const unsigned K : Next[J]) {
+ if (!K)
+ continue;
+ Chars = {{LowercaseIdentifier[I], LowercaseIdentifier[J],
+ LowercaseIdentifier[K], 0}};
+ auto Trigram = Token(Token::Kind::Trigram, Chars.data());
+ // Push unique trigrams to the result.
+ if (!UniqueTrigrams.count(Trigram)) {
+ UniqueTrigrams.insert(Trigram);
+ }
+ }
+ }
+ }
+
+ std::vector<Token> Result;
+ for (const auto &Trigram : UniqueTrigrams)
+ Result.push_back(Trigram);
+
+ return Result;
+}
+
+// FIXME(kbobyrev): Similarly, to generateIdentifierTrigrams, this ignores short
+// inputs (total segment length <3 characters).
+std::vector<Token> generateQueryTrigrams(llvm::StringRef Query) {
+ // Apply fuzzy matching text segmentation.
+ std::vector<CharRole> Roles(Query.size());
+ calculateRoles(Query, llvm::makeMutableArrayRef(Roles.data(), Query.size()));
+
+ std::string LowercaseQuery = Query.lower();
+
+ DenseSet<Token> UniqueTrigrams;
+ std::deque<char> Chars;
+
+ for (size_t I = 0; I < LowercaseQuery.size(); ++I) {
+ // If current symbol is delimiter, just skip it.
+ if (Roles[I] != Head && Roles[I] != Tail)
+ continue;
+
+ Chars.push_back(LowercaseQuery[I]);
+
+ if (Chars.size() > 3)
+ Chars.pop_front();
+ if (Chars.size() == 3) {
+ auto Trigram =
+ Token(Token::Kind::Trigram, std::string(begin(Chars), end(Chars)));
+ // Push unique trigrams to the result.
+ if (!UniqueTrigrams.count(Trigram)) {
+ UniqueTrigrams.insert(Trigram);
+ }
+ }
+ }
+
+ std::vector<Token> Result;
+ for (const auto &Trigram : UniqueTrigrams)
+ Result.push_back(Trigram);
+
+ return Result;
+}
+
+} // namespace dex
+} // namespace clangd
+} // namespace clang
diff --git a/clangd/index/dex/Trigram.h b/clangd/index/dex/Trigram.h
new file mode 100644
index 00000000..0e21c7d1
--- /dev/null
+++ b/clangd/index/dex/Trigram.h
@@ -0,0 +1,62 @@
+//===--- Trigram.h - Trigram generation for Fuzzy Matching ------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Trigrams are attributes of the symbol unqualified name used to effectively
+// extract symbols which can be fuzzy-matched given user query from the inverted
+// index. To match query with the extracted set of trigrams Q, the set of
+// generated trigrams T for identifier (unqualified symbol name) should contain
+// all items of Q, i.e. Q ⊆ T.
+//
+// Trigram sets extracted from unqualified name and from query are different:
+// the set of query trigrams only contains consecutive sequences of three
+// characters (which is only a subset of all trigrams generated for an
+// identifier).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DEX_TRIGRAM_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DEX_TRIGRAM_H
+
+#include "Token.h"
+
+#include <string>
+
+namespace clang {
+namespace clangd {
+namespace dex {
+
+/// Returns list of unique fuzzy-search trigrams from unqualified symbol.
+///
+/// First, given Identifier (unqualified symbol name) is segmented using
+/// FuzzyMatch API and lowercased. After segmentation, the following technique
+/// is applied for generating trigrams: for each letter or digit in the input
+/// string the algorithms looks for the possible next and skip-1-next symbols
+/// which can be jumped to during fuzzy matching. Each combination of such three
+/// symbols is inserted into the result.
+///
+/// Trigrams can start at any character in the input. Then we can choose to move
+/// to the next character, move to the start of the next segment, or skip over a
+/// segment.
+///
+/// Note: the returned list of trigrams does not have duplicates, if any trigram
+/// belongs to more than one class it is only inserted once.
+std::vector<Token> generateIdentifierTrigrams(llvm::StringRef Identifier);
+
+/// Returns list of unique fuzzy-search trigrams given a query.
+///
+/// Query is segmented using FuzzyMatch API and downcasted to lowercase. Then,
+/// the simplest trigrams - sequences of three consecutive letters and digits
+/// are extracted and returned after deduplication.
+std::vector<Token> generateQueryTrigrams(llvm::StringRef Query);
+
+} // namespace dex
+} // namespace clangd
+} // namespace clang
+
+#endif
diff --git a/clangd/tool/ClangdMain.cpp b/clangd/tool/ClangdMain.cpp
index 4f507372..9e161f5e 100644
--- a/clangd/tool/ClangdMain.cpp
+++ b/clangd/tool/ClangdMain.cpp
@@ -12,6 +12,7 @@
#include "Path.h"
#include "Trace.h"
#include "index/SymbolYAML.h"
+#include "clang/Basic/Version.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
@@ -39,7 +40,7 @@ std::unique_ptr<SymbolIndex> buildStaticIndex(llvm::StringRef YamlSymbolFile) {
llvm::errs() << "Can't open " << YamlSymbolFile << "\n";
return nullptr;
}
- auto Slab = SymbolsFromYAML(Buffer.get()->getBuffer());
+ auto Slab = symbolsFromYAML(Buffer.get()->getBuffer());
SymbolSlab::Builder SymsBuilder;
for (auto Sym : Slab)
SymsBuilder.insert(Sym);
@@ -59,6 +60,23 @@ static llvm::cl::opt<unsigned>
llvm::cl::desc("Number of async workers used by clangd"),
llvm::cl::init(getDefaultAsyncThreadsCount()));
+// FIXME: also support "plain" style where signatures are always omitted.
+enum CompletionStyleFlag {
+ Detailed,
+ Bundled,
+};
+static llvm::cl::opt<CompletionStyleFlag> CompletionStyle(
+ "completion-style",
+ llvm::cl::desc("Granularity of code completion suggestions"),
+ llvm::cl::values(
+ clEnumValN(Detailed, "detailed",
+ "One completion item for each semantically distinct "
+ "completion, with full type information."),
+ clEnumValN(Bundled, "bundled",
+ "Similar completion items (e.g. function overloads) are "
+ "combined. Type information shown where possible.")),
+ llvm::cl::init(Detailed));
+
// FIXME: Flags are the wrong mechanism for user preferences.
// We should probably read a dotfile or similar.
static llvm::cl::opt<bool> IncludeIneligibleResults(
@@ -80,6 +98,14 @@ static llvm::cl::opt<bool>
PrettyPrint("pretty", llvm::cl::desc("Pretty-print JSON output"),
llvm::cl::init(false));
+static llvm::cl::opt<Logger::Level> LogLevel(
+ "log", llvm::cl::desc("Verbosity of log messages written to stderr"),
+ llvm::cl::values(clEnumValN(Logger::Error, "error", "Error messages only"),
+ clEnumValN(Logger::Info, "info",
+ "High level execution tracing"),
+ clEnumValN(Logger::Debug, "verbose", "Low level details")),
+ llvm::cl::init(Logger::Info));
+
static llvm::cl::opt<bool> Test(
"lit-test",
llvm::cl::desc(
@@ -125,6 +151,19 @@ static llvm::cl::opt<bool> EnableIndex(
"Clang uses an index built from symbols in opened files"),
llvm::cl::init(true));
+static llvm::cl::opt<bool>
+ ShowOrigins("debug-origin",
+ llvm::cl::desc("Show origins of completion items"),
+ llvm::cl::init(clangd::CodeCompleteOptions().ShowOrigins),
+ llvm::cl::Hidden);
+
+static llvm::cl::opt<bool> HeaderInsertionDecorators(
+ "header-insertion-decorators",
+ llvm::cl::desc("Prepend a circular dot or space before the completion "
+ "label, depending on wether "
+ "an include line will be inserted or not."),
+ llvm::cl::init(true));
+
static llvm::cl::opt<Path> YamlSymbolFile(
"yaml-symbol-file",
llvm::cl::desc(
@@ -134,9 +173,30 @@ static llvm::cl::opt<Path> YamlSymbolFile(
"eventually. Don't rely on it."),
llvm::cl::init(""), llvm::cl::Hidden);
+enum CompileArgsFrom { LSPCompileArgs, FilesystemCompileArgs };
+
+static llvm::cl::opt<CompileArgsFrom> CompileArgsFrom(
+ "compile_args_from", llvm::cl::desc("The source of compile commands"),
+ llvm::cl::values(clEnumValN(LSPCompileArgs, "lsp",
+ "All compile commands come from LSP and "
+ "'compile_commands.json' files are ignored"),
+ clEnumValN(FilesystemCompileArgs, "filesystem",
+ "All compile commands come from the "
+ "'compile_commands.json' files")),
+ llvm::cl::init(FilesystemCompileArgs), llvm::cl::Hidden);
+
int main(int argc, char *argv[]) {
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
- llvm::cl::ParseCommandLineOptions(argc, argv, "clangd");
+ llvm::cl::SetVersionPrinter([](llvm::raw_ostream &OS) {
+ OS << clang::getClangToolFullVersion("clangd") << "\n";
+ });
+ llvm::cl::ParseCommandLineOptions(
+ argc, argv,
+ "clangd is a language server that provides IDE-like features to editors. "
+ "\n\nIt should be used via an editor plugin rather than invoked directly."
+ "For more information, see:"
+ "\n\thttps://clang.llvm.org/extra/clangd.html"
+ "\n\thttps://microsoft.github.io/language-server-protocol/");
if (Test) {
RunSynchronously = true;
InputStyle = JSONStreamStyle::Delimited;
@@ -159,7 +219,8 @@ int main(int argc, char *argv[]) {
llvm::Optional<llvm::raw_fd_ostream> InputMirrorStream;
if (!InputMirrorFile.empty()) {
std::error_code EC;
- InputMirrorStream.emplace(InputMirrorFile, /*ref*/ EC, llvm::sys::fs::F_RW);
+ InputMirrorStream.emplace(InputMirrorFile, /*ref*/ EC,
+ llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write);
if (EC) {
InputMirrorStream.reset();
llvm::errs() << "Error while opening an input mirror file: "
@@ -174,7 +235,8 @@ int main(int argc, char *argv[]) {
std::unique_ptr<trace::EventTracer> Tracer;
if (auto *TraceFile = getenv("CLANGD_TRACE")) {
std::error_code EC;
- TraceStream.emplace(TraceFile, /*ref*/ EC, llvm::sys::fs::F_RW);
+ TraceStream.emplace(TraceFile, /*ref*/ EC,
+ llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write);
if (EC) {
TraceStream.reset();
llvm::errs() << "Error while opening trace file " << TraceFile << ": "
@@ -188,7 +250,7 @@ int main(int argc, char *argv[]) {
if (Tracer)
TracingSession.emplace(*Tracer);
- JSONOutput Out(llvm::outs(), llvm::errs(),
+ JSONOutput Out(llvm::outs(), llvm::errs(), LogLevel,
InputMirrorStream ? InputMirrorStream.getPointer() : nullptr,
PrettyPrint);
@@ -231,12 +293,20 @@ int main(int argc, char *argv[]) {
clangd::CodeCompleteOptions CCOpts;
CCOpts.IncludeIneligibleResults = IncludeIneligibleResults;
CCOpts.Limit = LimitResults;
+ CCOpts.BundleOverloads = CompletionStyle != Detailed;
+ CCOpts.ShowOrigins = ShowOrigins;
+ if (!HeaderInsertionDecorators) {
+ CCOpts.IncludeIndicator.Insert.clear();
+ CCOpts.IncludeIndicator.NoInsert.clear();
+ }
// Initialize and run ClangdLSPServer.
- ClangdLSPServer LSPServer(Out, CCOpts, CompileCommandsDirPath, Opts);
+ ClangdLSPServer LSPServer(
+ Out, CCOpts, CompileCommandsDirPath,
+ /*ShouldUseInMemoryCDB=*/CompileArgsFrom == LSPCompileArgs, Opts);
constexpr int NoShutdownRequestErrorCode = 1;
llvm::set_thread_name("clangd.main");
// Change stdin to binary to not lose \r\n on windows.
llvm::sys::ChangeStdinToBinary();
- return LSPServer.run(std::cin, InputStyle) ? 0 : NoShutdownRequestErrorCode;
+ return LSPServer.run(stdin, InputStyle) ? 0 : NoShutdownRequestErrorCode;
}
diff --git a/docs/Doxyfile b/docs/Doxyfile
deleted file mode 100644
index d674390f..00000000
--- a/docs/Doxyfile
+++ /dev/null
@@ -1,1808 +0,0 @@
-# Doxyfile 1.7.6.1
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project
-#
-# All text after a hash (#) is considered a comment and will be ignored
-# The format is:
-# TAG = value [value, ...]
-# For lists items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (" ")
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all
-# text before the first occurrence of this tag. Doxygen uses libiconv (or the
-# iconv built into libc) for the transcoding. See
-# http://www.gnu.org/software/libiconv for the list of possible encodings.
-
-DOXYFILE_ENCODING = UTF-8
-
-# The PROJECT_NAME tag is a single word (or sequence of words) that should
-# identify the project. Note that if you do not use Doxywizard you need
-# to put quotes around the project name if it contains spaces.
-
-PROJECT_NAME = clang-tools-extra
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number.
-# This could be handy for archiving the generated documentation or
-# if some version control system is used.
-
-PROJECT_NUMBER =
-
-# Using the PROJECT_BRIEF tag one can provide an optional one line description
-# for a project that appears at the top of each page and should give viewer
-# a quick idea about the purpose of the project. Keep the description short.
-
-PROJECT_BRIEF =
-
-# With the PROJECT_LOGO tag one can specify an logo or icon that is
-# included in the documentation. The maximum height of the logo should not
-# exceed 55 pixels and the maximum width should not exceed 200 pixels.
-# Doxygen will copy the logo to the output directory.
-
-PROJECT_LOGO =
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
-# base path where the generated documentation will be put.
-# If a relative path is entered, it will be relative to the location
-# where doxygen was started. If left blank the current directory will be used.
-
-# Same directory that Sphinx uses.
-OUTPUT_DIRECTORY = ./_build/
-
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
-# 4096 sub-directories (in 2 levels) under the output directory of each output
-# format and will distribute the generated files over these directories.
-# Enabling this option can be useful when feeding doxygen a huge amount of
-# source files, where putting all generated files in the same directory would
-# otherwise cause performance problems for the file system.
-
-CREATE_SUBDIRS = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# The default language is English, other supported languages are:
-# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
-# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
-# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
-# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
-# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
-# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
-
-OUTPUT_LANGUAGE = English
-
-# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
-# include brief member descriptions after the members that are listed in
-# the file and class documentation (similar to JavaDoc).
-# Set to NO to disable this.
-
-BRIEF_MEMBER_DESC = YES
-
-# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
-# the brief description of a member or function before the detailed description.
-# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-
-REPEAT_BRIEF = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator
-# that is used to form the text in various listings. Each string
-# in this list, if found as the leading text of the brief description, will be
-# stripped from the text and the result after processing the whole list, is
-# used as the annotated text. Otherwise, the brief description is used as-is.
-# If left blank, the following values are used ("$name" is automatically
-# replaced with the name of the entity): "The $name class" "The $name widget"
-# "The $name file" "is" "provides" "specifies" "contains"
-# "represents" "a" "an" "the"
-
-ABBREVIATE_BRIEF =
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# Doxygen will generate a detailed section even if there is only a brief
-# description.
-
-ALWAYS_DETAILED_SEC = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-
-INLINE_INHERITED_MEMB = NO
-
-# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
-# path before files name in the file list and in the header files. If set
-# to NO the shortest path that makes the file name unique will be used.
-
-FULL_PATH_NAMES = NO
-
-# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
-# can be used to strip a user-defined part of the path. Stripping is
-# only done if one of the specified strings matches the left-hand part of
-# the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the
-# path to strip.
-
-STRIP_FROM_PATH =
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
-# the path mentioned in the documentation of a class, which tells
-# the reader which header file to include in order to use a class.
-# If left blank only the name of the header file containing the class
-# definition is used. Otherwise one should specify the include paths that
-# are normally passed to the compiler using the -I flag.
-
-STRIP_FROM_INC_PATH =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
-# (but less readable) file names. This can be useful if your file system
-# doesn't support long names like on DOS, Mac, or CD-ROM.
-
-SHORT_NAMES = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
-# will interpret the first line (until the first dot) of a JavaDoc-style
-# comment as the brief description. If set to NO, the JavaDoc
-# comments will behave just like regular Qt-style comments
-# (thus requiring an explicit @brief command for a brief description.)
-
-JAVADOC_AUTOBRIEF = NO
-
-# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
-# interpret the first line (until the first dot) of a Qt-style
-# comment as the brief description. If set to NO, the comments
-# will behave just like regular Qt-style comments (thus requiring
-# an explicit \brief command for a brief description.)
-
-QT_AUTOBRIEF = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
-# treat a multi-line C++ special comment block (i.e. a block of //! or ///
-# comments) as a brief description. This used to be the default behaviour.
-# The new default is to treat a multi-line C++ comment block as a detailed
-# description. Set this tag to YES if you prefer the old behaviour instead.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
-# member inherits the documentation from any documented member that it
-# re-implements.
-
-INHERIT_DOCS = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
-# a new page for each member. If set to NO, the documentation of a member will
-# be part of the file/class/namespace that contains it.
-
-SEPARATE_MEMBER_PAGES = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab.
-# Doxygen uses this value to replace tabs by spaces in code fragments.
-
-TAB_SIZE = 2
-
-# This tag can be used to specify a number of aliases that acts
-# as commands in the documentation. An alias has the form "name=value".
-# For example adding "sideeffect=\par Side Effects:\n" will allow you to
-# put the command \sideeffect (or @sideeffect) in the documentation, which
-# will result in a user-defined paragraph with heading "Side Effects:".
-# You can put \n's in the value part of an alias to insert newlines.
-
-ALIASES =
-
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding
-# "class=itcl::class" will allow you to use the command class in the
-# itcl::class meaning.
-
-TCL_SUBST =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
-# sources only. Doxygen will then generate output that is more tailored for C.
-# For instance, some of the names that are used will be different. The list
-# of all members will be omitted, etc.
-
-OPTIMIZE_OUTPUT_FOR_C = NO
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
-# sources only. Doxygen will then generate output that is more tailored for
-# Java. For instance, namespaces will be presented as packages, qualified
-# scopes will look different, etc.
-
-OPTIMIZE_OUTPUT_JAVA = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources only. Doxygen will then generate output that is more tailored for
-# Fortran.
-
-OPTIMIZE_FOR_FORTRAN = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for
-# VHDL.
-
-OPTIMIZE_OUTPUT_VHDL = NO
-
-# Doxygen selects the parser to use depending on the extension of the files it
-# parses. With this tag you can assign which parser to use for a given extension.
-# Doxygen has a built-in mapping, but you can override or extend it using this
-# tag. The format is ext=language, where ext is a file extension, and language
-# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
-# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
-# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
-# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
-# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
-
-EXTENSION_MAPPING =
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should
-# set this tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
-# func(std::string) {}). This also makes the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-
-BUILTIN_STL_SUPPORT = NO
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-
-CPP_CLI_SUPPORT = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
-# Doxygen will parse them like normal C++ but will assume all classes use public
-# instead of private inheritance when no explicit protection keyword is present.
-
-SIP_SUPPORT = NO
-
-# For Microsoft's IDL there are propget and propput attributes to indicate getter
-# and setter methods for a property. Setting this option to YES (the default)
-# will make doxygen replace the get and set methods by a property in the
-# documentation. This will only work if the methods are indeed getting or
-# setting a simple type. If this is not the case, or you want to show the
-# methods anyway, you should set this option to NO.
-
-IDL_PROPERTY_SUPPORT = YES
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-
-DISTRIBUTE_GROUP_DOC = NO
-
-# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
-# the same type (for instance a group of public functions) to be put as a
-# subgroup of that type (e.g. under the Public Functions section). Set it to
-# NO to prevent subgrouping. Alternatively, this can be done per class using
-# the \nosubgrouping command.
-
-SUBGROUPING = YES
-
-# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
-# unions are shown inside the group in which they are included (e.g. using
-# @ingroup) instead of on a separate page (for HTML and Man pages) or
-# section (for LaTeX and RTF).
-
-INLINE_GROUPED_CLASSES = NO
-
-# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
-# unions with only public data fields will be shown inline in the documentation
-# of the scope in which they are defined (i.e. file, namespace, or group
-# documentation), provided this scope is documented. If set to NO (the default),
-# structs, classes, and unions are shown on a separate page (for HTML and Man
-# pages) or section (for LaTeX and RTF).
-
-INLINE_SIMPLE_STRUCTS = NO
-
-# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
-# is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically
-# be useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-
-TYPEDEF_HIDES_STRUCT = NO
-
-# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
-# determine which symbols to keep in memory and which to flush to disk.
-# When the cache is full, less often used symbols will be written to disk.
-# For small to medium size projects (<1000 input files) the default value is
-# probably good enough. For larger projects a too small cache size can cause
-# doxygen to be busy swapping symbols to and from disk most of the time
-# causing a significant performance penalty.
-# If the system has enough physical memory increasing the cache will improve the
-# performance by keeping more symbols in memory. Note that the value works on
-# a logarithmic scale so increasing the size by one will roughly double the
-# memory usage. The cache size is given by this formula:
-# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
-# corresponding to a cache size of 2^16 = 65536 symbols.
-
-SYMBOL_CACHE_SIZE = 0
-
-# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
-# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
-# their name and scope. Since this can be an expensive process and often the
-# same symbol appear multiple times in the code, doxygen keeps a cache of
-# pre-resolved symbols. If the cache is too small doxygen will become slower.
-# If the cache is too large, memory is wasted. The cache size is given by this
-# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
-# corresponding to a cache size of 2^16 = 65536 symbols.
-
-LOOKUP_CACHE_SIZE = 0
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available.
-# Private class members and static file members will be hidden unless
-# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
-
-EXTRACT_ALL = YES
-
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
-# will be included in the documentation.
-
-EXTRACT_PRIVATE = YES
-
-# If the EXTRACT_STATIC tag is set to YES all static members of a file
-# will be included in the documentation.
-
-EXTRACT_STATIC = YES
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
-# defined locally in source files will be included in the documentation.
-# If set to NO only classes defined in header files are included.
-
-EXTRACT_LOCAL_CLASSES = YES
-
-# This flag is only useful for Objective-C code. When set to YES local
-# methods, which are defined in the implementation section but not in
-# the interface are included in the documentation.
-# If set to NO (the default) only methods in the interface are included.
-
-EXTRACT_LOCAL_METHODS = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base
-# name of the file that contains the anonymous namespace. By default
-# anonymous namespaces are hidden.
-
-EXTRACT_ANON_NSPACES = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
-# undocumented members of documented classes, files or namespaces.
-# If set to NO (the default) these members will be included in the
-# various overviews, but no documentation section is generated.
-# This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_MEMBERS = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy.
-# If set to NO (the default) these classes will be included in the various
-# overviews. This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_CLASSES = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
-# friend (class|struct|union) declarations.
-# If set to NO (the default) these declarations will be included in the
-# documentation.
-
-HIDE_FRIEND_COMPOUNDS = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
-# documentation blocks found inside the body of a function.
-# If set to NO (the default) these blocks will be appended to the
-# function's detailed documentation block.
-
-HIDE_IN_BODY_DOCS = NO
-
-# The INTERNAL_DOCS tag determines if documentation
-# that is typed after a \internal command is included. If the tag is set
-# to NO (the default) then the documentation will be excluded.
-# Set it to YES to include the internal documentation.
-
-INTERNAL_DOCS = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
-# file names in lower-case letters. If set to YES upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-
-CASE_SENSE_NAMES = YES
-
-# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
-# will show members with their full class and namespace scopes in the
-# documentation. If set to YES the scope will be hidden.
-
-HIDE_SCOPE_NAMES = NO
-
-# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
-# will put a list of the files that are included by a file in the documentation
-# of that file.
-
-SHOW_INCLUDE_FILES = YES
-
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
-# will list include files with double quotes in the documentation
-# rather than with sharp brackets.
-
-FORCE_LOCAL_INCLUDES = NO
-
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
-# is inserted in the documentation for inline members.
-
-INLINE_INFO = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
-# will sort the (detailed) documentation of file and class members
-# alphabetically by member name. If set to NO the members will appear in
-# declaration order.
-
-SORT_MEMBER_DOCS = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
-# brief documentation of file, namespace and class members alphabetically
-# by member name. If set to NO (the default) the members will appear in
-# declaration order.
-
-SORT_BRIEF_DOCS = NO
-
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
-# will sort the (brief and detailed) documentation of class members so that
-# constructors and destructors are listed first. If set to NO (the default)
-# the constructors will appear in the respective orders defined by
-# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
-# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
-# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
-
-SORT_MEMBERS_CTORS_1ST = NO
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
-# hierarchy of group names into alphabetical order. If set to NO (the default)
-# the group names will appear in their defined order.
-
-SORT_GROUP_NAMES = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
-# sorted by fully-qualified names, including namespaces. If set to
-# NO (the default), the class list will be sorted only by class name,
-# not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the
-# alphabetical list.
-
-SORT_BY_SCOPE_NAME = NO
-
-# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
-# do proper type resolution of all parameters of a function it will reject a
-# match between the prototype and the implementation of a member function even
-# if there is only one candidate or it is obvious which candidate to choose
-# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
-# will still accept a match between prototype and implementation in such cases.
-
-STRICT_PROTO_MATCHING = NO
-
-# The GENERATE_TODOLIST tag can be used to enable (YES) or
-# disable (NO) the todo list. This list is created by putting \todo
-# commands in the documentation.
-
-GENERATE_TODOLIST = YES
-
-# The GENERATE_TESTLIST tag can be used to enable (YES) or
-# disable (NO) the test list. This list is created by putting \test
-# commands in the documentation.
-
-GENERATE_TESTLIST = YES
-
-# The GENERATE_BUGLIST tag can be used to enable (YES) or
-# disable (NO) the bug list. This list is created by putting \bug
-# commands in the documentation.
-
-GENERATE_BUGLIST = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
-# disable (NO) the deprecated list. This list is created by putting
-# \deprecated commands in the documentation.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional
-# documentation sections, marked by \if sectionname ... \endif.
-
-ENABLED_SECTIONS =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
-# the initial value of a variable or macro consists of for it to appear in
-# the documentation. If the initializer consists of more lines than specified
-# here it will be hidden. Use a value of 0 to hide initializers completely.
-# The appearance of the initializer of individual variables and macros in the
-# documentation can be controlled using \showinitializer or \hideinitializer
-# command in the documentation regardless of this setting.
-
-MAX_INITIALIZER_LINES = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
-# at the bottom of the documentation of classes and structs. If set to YES the
-# list will mention the files that were used to generate the documentation.
-
-SHOW_USED_FILES = YES
-
-# If the sources in your project are distributed over multiple directories
-# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
-# in the documentation. The default is NO.
-
-SHOW_DIRECTORIES = YES
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
-# This will remove the Files entry from the Quick Index and from the
-# Folder Tree View (if specified). The default is YES.
-
-SHOW_FILES = YES
-
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
-# Namespaces page. This will remove the Namespaces entry from the Quick Index
-# and from the Folder Tree View (if specified). The default is YES.
-
-SHOW_NAMESPACES = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command <command> <input-file>, where <command> is the value of
-# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
-# provided by doxygen. Whatever the program writes to standard output
-# is used as the file version. See the manual for examples.
-
-FILE_VERSION_FILTER =
-
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
-# by doxygen. The layout file controls the global structure of the generated
-# output files in an output format independent way. The create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option.
-# You can optionally specify a file name after the option, if omitted
-# DoxygenLayout.xml will be used as the name of the layout file.
-
-LAYOUT_FILE =
-
-# The CITE_BIB_FILES tag can be used to specify one or more bib files
-# containing the references data. This must be a list of .bib files. The
-# .bib extension is automatically appended if omitted. Using this command
-# requires the bibtex tool to be installed. See also
-# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
-# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
-# feature you need bibtex and perl available in the search path.
-
-CITE_BIB_FILES =
-
-#---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated
-# by doxygen. Possible values are YES and NO. If left blank NO is used.
-
-QUIET = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated by doxygen. Possible values are YES and NO. If left blank
-# NO is used.
-
-WARNINGS = YES
-
-# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
-# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
-# automatically be disabled.
-
-WARN_IF_UNDOCUMENTED = YES
-
-# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some
-# parameters in a documented function, or documenting parameters that
-# don't exist or using markup commands wrongly.
-
-WARN_IF_DOC_ERROR = YES
-
-# The WARN_NO_PARAMDOC option can be enabled to get warnings for
-# functions that are documented, but have no documentation for their parameters
-# or return value. If set to NO (the default) doxygen will only warn about
-# wrong or incomplete parameter documentation, but not about the absence of
-# documentation.
-
-WARN_NO_PARAMDOC = NO
-
-# The WARN_FORMAT tag determines the format of the warning messages that
-# doxygen can produce. The string should contain the $file, $line, and $text
-# tags, which will be replaced by the file and line number from which the
-# warning originated and the warning text. Optionally the format may contain
-# $version, which will be replaced by the version of the file (if it could
-# be obtained via FILE_VERSION_FILTER)
-
-WARN_FORMAT = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning
-# and error messages should be written. If left blank the output is written
-# to stderr.
-
-WARN_LOGFILE =
-
-#---------------------------------------------------------------------------
-# configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag can be used to specify the files and/or directories that contain
-# documented source files. You may enter file names like "myfile.cpp" or
-# directories like "/usr/src/myproject". Separate the files or directories
-# with spaces.
-
-INPUT = ../clang-modernize ../clang-apply-replacements ../clang-tidy
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
-# also the default input encoding. Doxygen uses libiconv (or the iconv built
-# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
-# the list of possible encodings.
-
-INPUT_ENCODING = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank the following patterns are tested:
-# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
-# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
-# *.f90 *.f *.for *.vhd *.vhdl
-
-FILE_PATTERNS = *.c \
- *.cc \
- *.cxx \
- *.cpp \
- *.c++ \
- *.d \
- *.java \
- *.ii \
- *.ixx \
- *.ipp \
- *.i++ \
- *.inl \
- *.h \
- *.hh \
- *.hxx \
- *.hpp \
- *.h++ \
- *.idl \
- *.odl \
- *.cs \
- *.php \
- *.php3 \
- *.inc \
- *.m \
- *.mm \
- *.dox \
- *.py \
- *.f90 \
- *.f \
- *.for \
- *.vhd \
- *.vhdl
-
-# The RECURSIVE tag can be used to turn specify whether or not subdirectories
-# should be searched for input files as well. Possible values are YES and NO.
-# If left blank NO is used.
-
-RECURSIVE = YES
-
-# The EXCLUDE tag can be used to specify files and/or directories that should be
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-# Note that relative paths are relative to the directory from which doxygen is
-# run.
-
-EXCLUDE =
-
-# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
-# directories that are symbolic links (a Unix file system feature) are excluded
-# from the input.
-
-EXCLUDE_SYMLINKS = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories. Note that the wildcards are matched
-# against the file with absolute path, so to exclude all test directories
-# for example use the pattern */test/*
-
-EXCLUDE_PATTERNS =
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-
-EXCLUDE_SYMBOLS =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or
-# directories that contain example code fragments that are included (see
-# the \include command).
-
-EXAMPLE_PATH =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank all files are included.
-
-EXAMPLE_PATTERNS = *
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude
-# commands irrespective of the value of the RECURSIVE tag.
-# Possible values are YES and NO. If left blank NO is used.
-
-EXAMPLE_RECURSIVE = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or
-# directories that contain image that are included in the documentation (see
-# the \image command).
-
-IMAGE_PATH =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command <filter> <input-file>, where <filter>
-# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
-# input file. Doxygen will then use the output that the filter program writes
-# to standard output. If FILTER_PATTERNS is specified, this tag will be
-# ignored.
-
-INPUT_FILTER =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form:
-# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
-# info on how filters are used. If FILTER_PATTERNS is empty or if
-# none of the patterns match the file name, INPUT_FILTER is applied.
-
-FILTER_PATTERNS =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will be used to filter the input files when producing source
-# files to browse (i.e. when SOURCE_BROWSER is set to YES).
-
-FILTER_SOURCE_FILES = NO
-
-# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
-# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
-# and it is also possible to disable source filtering for a specific pattern
-# using *.ext= (so without naming a filter). This option only has effect when
-# FILTER_SOURCE_FILES is enabled.
-
-FILTER_SOURCE_PATTERNS =
-
-#---------------------------------------------------------------------------
-# configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will
-# be generated. Documented entities will be cross-referenced with these sources.
-# Note: To get rid of all source code in the generated output, make sure also
-# VERBATIM_HEADERS is set to NO.
-
-SOURCE_BROWSER = YES
-
-# Setting the INLINE_SOURCES tag to YES will include the body
-# of functions and classes directly in the documentation.
-
-INLINE_SOURCES = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
-# doxygen to hide any special comment blocks from generated source code
-# fragments. Normal C and C++ comments will always remain visible.
-
-STRIP_CODE_COMMENTS = NO
-
-# If the REFERENCED_BY_RELATION tag is set to YES
-# then for each documented function all documented
-# functions referencing it will be listed.
-
-REFERENCED_BY_RELATION = YES
-
-# If the REFERENCES_RELATION tag is set to YES
-# then for each documented function all documented entities
-# called/used by that function will be listed.
-
-REFERENCES_RELATION = YES
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
-# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
-# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
-# link to the source code. Otherwise they will link to the documentation.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code
-# will point to the HTML generated by the htags(1) tool instead of doxygen
-# built-in source browser. The htags tool is part of GNU's global source
-# tagging system (see http://www.gnu.org/software/global/global.html). You
-# will need version 4.8.6 or higher.
-
-USE_HTAGS = NO
-
-# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
-# will generate a verbatim copy of the header file for each class for
-# which an include is specified. Set to NO to disable this.
-
-VERBATIM_HEADERS = YES
-
-#---------------------------------------------------------------------------
-# configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
-# of all compounds will be generated. Enable this if the project
-# contains a lot of classes, structs, unions or interfaces.
-
-ALPHABETICAL_INDEX = YES
-
-# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
-# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
-# in which this list will be split (can be a number in the range [1..20])
-
-COLS_IN_ALPHA_INDEX = 4
-
-# In case all classes in a project start with a common prefix, all
-# classes will be put under the same header in the alphabetical index.
-# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
-# should be ignored while generating the index headers.
-
-IGNORE_PREFIX = clang::
-
-#---------------------------------------------------------------------------
-# configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
-# generate HTML output.
-
-GENERATE_HTML = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `html' will be used as the default path.
-
-# This directory nicely fits with the way the Sphinx outputs html, so that
-# the doxygen documentation will be visible in the doxygen/ path in the web
-# output (e.g. github pages).
-HTML_OUTPUT = html/doxygen/
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
-# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
-# doxygen will generate files with .html extension.
-
-HTML_FILE_EXTENSION = .html
-
-# The HTML_HEADER tag can be used to specify a personal HTML header for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard header. Note that when using a custom header you are responsible
-# for the proper inclusion of any scripts and style sheets that doxygen
-# needs, which is dependent on the configuration options used.
-# It is advised to generate a default header using "doxygen -w html
-# header.html footer.html stylesheet.css YourConfigFile" and then modify
-# that header. Note that the header is subject to change so you typically
-# have to redo this when upgrading to a newer version of doxygen or when
-# changing the value of configuration settings such as GENERATE_TREEVIEW!
-
-HTML_HEADER =
-
-# The HTML_FOOTER tag can be used to specify a personal HTML footer for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard footer.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
-# style sheet that is used by each HTML page. It can be used to
-# fine-tune the look of the HTML output. If the tag is left blank doxygen
-# will generate a default style sheet. Note that doxygen will try to copy
-# the style sheet file to the HTML output directory, so don't put your own
-# stylesheet in the HTML output directory as well, or it will be erased!
-
-HTML_STYLESHEET =
-
-# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the HTML output directory. Note
-# that these files will be copied to the base HTML output directory. Use the
-# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
-# files. In the HTML_STYLESHEET file, use the file name only. Also note that
-# the files will be copied as-is; there are no commands or markers available.
-
-HTML_EXTRA_FILES =
-
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
-# Doxygen will adjust the colors in the style sheet and background images
-# according to this color. Hue is specified as an angle on a colorwheel,
-# see http://en.wikipedia.org/wiki/Hue for more information.
-# For instance the value 0 represents red, 60 is yellow, 120 is green,
-# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
-# The allowed range is 0 to 359.
-
-HTML_COLORSTYLE_HUE = 220
-
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
-# the colors in the HTML output. For a value of 0 the output will use
-# grayscales only. A value of 255 will produce the most vivid colors.
-
-HTML_COLORSTYLE_SAT = 100
-
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
-# the luminance component of the colors in the HTML output. Values below
-# 100 gradually make the output lighter, whereas values above 100 make
-# the output darker. The value divided by 100 is the actual gamma applied,
-# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
-# and 100 does not change the gamma.
-
-HTML_COLORSTYLE_GAMMA = 80
-
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting
-# this to NO can help when comparing the output of multiple runs.
-
-HTML_TIMESTAMP = YES
-
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
-# files or namespaces will be aligned in HTML using tables. If set to
-# NO a bullet list will be used.
-
-HTML_ALIGN_MEMBERS = YES
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded. For this to work a browser that supports
-# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
-# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
-
-HTML_DYNAMIC_SECTIONS = NO
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files
-# will be generated that can be used as input for Apple's Xcode 3
-# integrated development environment, introduced with OSX 10.5 (Leopard).
-# To create a documentation set, doxygen will generate a Makefile in the
-# HTML output directory. Running make will produce the docset in that
-# directory and running "make install" will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
-# it at startup.
-# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
-
-GENERATE_DOCSET = NO
-
-# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
-# feed. A documentation feed provides an umbrella under which multiple
-# documentation sets from a single provider (such as a company or product suite)
-# can be grouped.
-
-DOCSET_FEEDNAME = "Doxygen generated docs"
-
-# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
-# should uniquely identify the documentation set bundle. This should be a
-# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
-# will append .docset to the name.
-
-DOCSET_BUNDLE_ID = org.doxygen.Project
-
-# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
-# the documentation publisher. This should be a reverse domain-name style
-# string, e.g. com.mycompany.MyDocSet.documentation.
-
-DOCSET_PUBLISHER_ID = org.doxygen.Publisher
-
-# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
-
-DOCSET_PUBLISHER_NAME = Publisher
-
-# If the GENERATE_HTMLHELP tag is set to YES, additional index files
-# will be generated that can be used as input for tools like the
-# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
-# of the generated HTML documentation.
-
-GENERATE_HTMLHELP = NO
-
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
-# be used to specify the file name of the resulting .chm file. You
-# can add a path in front of the file if the result should not be
-# written to the html output directory.
-
-CHM_FILE =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
-# be used to specify the location (absolute path including file name) of
-# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
-# the HTML help compiler on the generated index.hhp.
-
-HHC_LOCATION =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
-# controls if a separate .chi index file is generated (YES) or that
-# it should be included in the master .chm file (NO).
-
-GENERATE_CHI = NO
-
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
-# is used to encode HtmlHelp index (hhk), content (hhc) and project file
-# content.
-
-CHM_INDEX_ENCODING =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
-# controls whether a binary table of contents is generated (YES) or a
-# normal table of contents (NO) in the .chm file.
-
-BINARY_TOC = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members
-# to the contents of the HTML help documentation and to the tree view.
-
-TOC_EXPAND = NO
-
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
-# that can be used as input for Qt's qhelpgenerator to generate a
-# Qt Compressed Help (.qch) of the generated HTML documentation.
-
-GENERATE_QHP = NO
-
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
-# be used to specify the file name of the resulting .qch file.
-# The path specified is relative to the HTML output folder.
-
-QCH_FILE =
-
-# The QHP_NAMESPACE tag specifies the namespace to use when generating
-# Qt Help Project output. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#namespace
-
-QHP_NAMESPACE = org.doxygen.Project
-
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
-# Qt Help Project output. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#virtual-folders
-
-QHP_VIRTUAL_FOLDER = doc
-
-# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
-# add. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#custom-filters
-
-QHP_CUST_FILTER_NAME =
-
-# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see
-# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
-# Qt Help Project / Custom Filters</a>.
-
-QHP_CUST_FILTER_ATTRS =
-
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's
-# filter section matches.
-# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
-# Qt Help Project / Filter Attributes</a>.
-
-QHP_SECT_FILTER_ATTRS =
-
-# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
-# be used to specify the location of Qt's qhelpgenerator.
-# If non-empty doxygen will try to run qhelpgenerator on the generated
-# .qhp file.
-
-QHG_LOCATION =
-
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
-# will be generated, which together with the HTML files, form an Eclipse help
-# plugin. To install this plugin and make it available under the help contents
-# menu in Eclipse, the contents of the directory containing the HTML and XML
-# files needs to be copied into the plugins directory of eclipse. The name of
-# the directory within the plugins directory should be the same as
-# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
-# the help appears.
-
-GENERATE_ECLIPSEHELP = NO
-
-# A unique identifier for the eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have
-# this name.
-
-ECLIPSE_DOC_ID = org.doxygen.Project
-
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
-# at top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it. Since the tabs have the same information as the
-# navigation tree you can set this option to NO if you already set
-# GENERATE_TREEVIEW to YES.
-
-DISABLE_INDEX = NO
-
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information.
-# If the tag value is set to YES, a side panel will be generated
-# containing a tree-like index structure (just like the one that
-# is generated for HTML Help). For this to work a browser that supports
-# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
-# Windows users are probably better off using the HTML help feature.
-# Since the tree basically has the same information as the tab index you
-# could consider to set DISABLE_INDEX to NO when enabling this option.
-
-GENERATE_TREEVIEW = NO
-
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
-# (range [0,1..20]) that doxygen will group on one line in the generated HTML
-# documentation. Note that a value of 0 will completely suppress the enum
-# values from appearing in the overview section.
-
-ENUM_VALUES_PER_LINE = 4
-
-# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
-# and Class Hierarchy pages using a tree view instead of an ordered list.
-
-USE_INLINE_TREES = NO
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
-# used to set the initial width (in pixels) of the frame in which the tree
-# is shown.
-
-TREEVIEW_WIDTH = 250
-
-# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
-# links to external symbols imported via tag files in a separate window.
-
-EXT_LINKS_IN_WINDOW = NO
-
-# Use this tag to change the font size of Latex formulas included
-# as images in the HTML documentation. The default is 10. Note that
-# when you change the font size after a successful doxygen run you need
-# to manually remove any form_*.png images from the HTML output directory
-# to force them to be regenerated.
-
-FORMULA_FONTSIZE = 10
-
-# Use the FORMULA_TRANPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are
-# not supported properly for IE 6.0, but are supported on all modern browsers.
-# Note that when changing this option you need to delete any form_*.png files
-# in the HTML output before the changes have effect.
-
-FORMULA_TRANSPARENT = YES
-
-# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
-# (see http://www.mathjax.org) which uses client side Javascript for the
-# rendering instead of using prerendered bitmaps. Use this if you do not
-# have LaTeX installed or if you want to formulas look prettier in the HTML
-# output. When enabled you also need to install MathJax separately and
-# configure the path to it using the MATHJAX_RELPATH option.
-
-USE_MATHJAX = NO
-
-# When MathJax is enabled you need to specify the location relative to the
-# HTML output directory using the MATHJAX_RELPATH option. The destination
-# directory should contain the MathJax.js script. For instance, if the mathjax
-# directory is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to the
-# mathjax.org site, so you can quickly see the result without installing
-# MathJax, but it is strongly recommended to install a local copy of MathJax
-# before deployment.
-
-MATHJAX_RELPATH = http://www.mathjax.org/mathjax
-
-# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
-# names that should be enabled during MathJax rendering.
-
-MATHJAX_EXTENSIONS =
-
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box
-# for the HTML output. The underlying search engine uses javascript
-# and DHTML and should work on any modern browser. Note that when using
-# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
-# (GENERATE_DOCSET) there is already a search function so this one should
-# typically be disabled. For large projects the javascript based search engine
-# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
-
-SEARCHENGINE = YES
-
-# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a PHP enabled web server instead of at the web client
-# using Javascript. Doxygen will generate the search PHP script and index
-# file to put on the web server. The advantage of the server
-# based approach is that it scales better to large projects and allows
-# full text search. The disadvantages are that it is more difficult to setup
-# and does not have live searching capabilities.
-
-SERVER_BASED_SEARCH = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
-# generate Latex output.
-
-# For now, no latex output.
-GENERATE_LATEX = NO
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `latex' will be used as the default path.
-
-LATEX_OUTPUT = latex/doxygen
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked. If left blank `latex' will be used as the default command name.
-# Note that when enabling USE_PDFLATEX this option is only used for
-# generating bitmaps for formulas in the HTML output, but not in the
-# Makefile that is written to the output directory.
-
-LATEX_CMD_NAME = latex
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
-# generate index for LaTeX. If left blank `makeindex' will be used as the
-# default command name.
-
-MAKEINDEX_CMD_NAME = makeindex
-
-# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
-# LaTeX documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_LATEX = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used
-# by the printer. Possible values are: a4, letter, legal and
-# executive. If left blank a4wide will be used.
-
-PAPER_TYPE = letter
-
-# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
-# packages that should be included in the LaTeX output.
-
-EXTRA_PACKAGES =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
-# the generated latex document. The header should contain everything until
-# the first chapter. If it is left blank doxygen will generate a
-# standard header. Notice: only use this tag if you know what you are doing!
-
-LATEX_HEADER =
-
-# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
-# the generated latex document. The footer should contain everything after
-# the last chapter. If it is left blank doxygen will generate a
-# standard footer. Notice: only use this tag if you know what you are doing!
-
-LATEX_FOOTER =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
-# is prepared for conversion to pdf (using ps2pdf). The pdf file will
-# contain links (just like the HTML output) instead of page references
-# This makes the output suitable for online browsing using a pdf viewer.
-
-PDF_HYPERLINKS = NO
-
-# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
-# plain latex in the generated Makefile. Set this option to YES to get a
-# higher quality PDF documentation.
-
-USE_PDFLATEX = NO
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
-# command to the generated LaTeX files. This will instruct LaTeX to keep
-# running if errors occur, instead of asking the user for help.
-# This option is also used when generating formulas in HTML.
-
-LATEX_BATCHMODE = NO
-
-# If LATEX_HIDE_INDICES is set to YES then doxygen will not
-# include the index chapters (such as File Index, Compound Index, etc.)
-# in the output.
-
-LATEX_HIDE_INDICES = NO
-
-# If LATEX_SOURCE_CODE is set to YES then doxygen will include
-# source code with syntax highlighting in the LaTeX output.
-# Note that which sources are shown also depends on other settings
-# such as SOURCE_BROWSER.
-
-LATEX_SOURCE_CODE = NO
-
-# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
-# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
-# http://en.wikipedia.org/wiki/BibTeX for more info.
-
-LATEX_BIB_STYLE = plain
-
-#---------------------------------------------------------------------------
-# configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
-# The RTF output is optimized for Word 97 and may not look very pretty with
-# other RTF readers or editors.
-
-GENERATE_RTF = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `rtf' will be used as the default path.
-
-RTF_OUTPUT = rtf
-
-# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
-# RTF documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_RTF = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
-# will contain hyperlink fields. The RTF file will
-# contain links (just like the HTML output) instead of page references.
-# This makes the output suitable for online browsing using WORD or other
-# programs which support those fields.
-# Note: wordpad (write) and others do not support links.
-
-RTF_HYPERLINKS = NO
-
-# Load stylesheet definitions from file. Syntax is similar to doxygen's
-# config file, i.e. a series of assignments. You only have to provide
-# replacements, missing definitions are set to their default value.
-
-RTF_STYLESHEET_FILE =
-
-# Set optional variables used in the generation of an rtf document.
-# Syntax is similar to doxygen's config file.
-
-RTF_EXTENSIONS_FILE =
-
-#---------------------------------------------------------------------------
-# configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
-# generate man pages
-
-GENERATE_MAN = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `man' will be used as the default path.
-
-MAN_OUTPUT =
-
-# The MAN_EXTENSION tag determines the extension that is added to
-# the generated man pages (default is the subroutine's section .3)
-
-MAN_EXTENSION =
-
-# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
-# then it will generate one additional man file for each entity
-# documented in the real man page(s). These additional files
-# only source the real man page, but without them the man command
-# would be unable to find the correct page. The default is NO.
-
-MAN_LINKS = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES Doxygen will
-# generate an XML file that captures the structure of
-# the code including all documentation.
-
-GENERATE_XML = NO
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `xml' will be used as the default path.
-
-XML_OUTPUT = xml
-
-# The XML_SCHEMA tag can be used to specify an XML schema,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_SCHEMA =
-
-# The XML_DTD tag can be used to specify an XML DTD,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_DTD =
-
-# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
-# dump the program listings (including syntax highlighting
-# and cross-referencing information) to the XML output. Note that
-# enabling this will significantly increase the size of the XML output.
-
-XML_PROGRAMLISTING = YES
-
-#---------------------------------------------------------------------------
-# configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
-# generate an AutoGen Definitions (see autogen.sf.net) file
-# that captures the structure of the code including all
-# documentation. Note that this feature is still experimental
-# and incomplete at the moment.
-
-GENERATE_AUTOGEN_DEF = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES Doxygen will
-# generate a Perl module file that captures the structure of
-# the code including all documentation. Note that this
-# feature is still experimental and incomplete at the
-# moment.
-
-GENERATE_PERLMOD = NO
-
-# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
-# the necessary Makefile rules, Perl scripts and LaTeX code to be able
-# to generate PDF and DVI output from the Perl module output.
-
-PERLMOD_LATEX = NO
-
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
-# nicely formatted so it can be parsed by a human reader. This is useful
-# if you want to understand what is going on. On the other hand, if this
-# tag is set to NO the size of the Perl module output will be much smaller
-# and Perl will parse it just the same.
-
-PERLMOD_PRETTY = YES
-
-# The names of the make variables in the generated doxyrules.make file
-# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
-# This is useful so different doxyrules.make files included by the same
-# Makefile don't overwrite each other's variables.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
-# evaluate all C-preprocessor directives found in the sources and include
-# files.
-
-ENABLE_PREPROCESSING = YES
-
-# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
-# names in the source code. If set to NO (the default) only conditional
-# compilation will be performed. Macro expansion can be done in a controlled
-# way by setting EXPAND_ONLY_PREDEF to YES.
-
-MACRO_EXPANSION = NO
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
-# then the macro expansion is limited to the macros specified with the
-# PREDEFINED and EXPAND_AS_DEFINED tags.
-
-EXPAND_ONLY_PREDEF = NO
-
-# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
-# pointed to by INCLUDE_PATH will be searched when a #include is found.
-
-SEARCH_INCLUDES = YES
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by
-# the preprocessor.
-
-INCLUDE_PATH =
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will
-# be used.
-
-INCLUDE_FILE_PATTERNS =
-
-# The PREDEFINED tag can be used to specify one or more macro names that
-# are defined before the preprocessor is started (similar to the -D option of
-# gcc). The argument of the tag is a list of macros of the form: name
-# or name=definition (no spaces). If the definition and the = are
-# omitted =1 is assumed. To prevent a macro definition from being
-# undefined via #undef or recursively expanded use the := operator
-# instead of the = operator.
-
-PREDEFINED =
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
-# this tag can be used to specify a list of macro names that should be expanded.
-# The macro definition that is found in the sources will be used.
-# Use the PREDEFINED tag if you want to use a different macro definition that
-# overrules the definition found in the source code.
-
-EXPAND_AS_DEFINED =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
-# doxygen's preprocessor will remove all references to function-like macros
-# that are alone on a line, have an all uppercase name, and do not end with a
-# semicolon, because these will confuse the parser if not removed.
-
-SKIP_FUNCTION_MACROS = YES
-
-#---------------------------------------------------------------------------
-# Configuration::additions related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES option can be used to specify one or more tagfiles.
-# Optionally an initial location of the external documentation
-# can be added for each tagfile. The format of a tag file without
-# this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where "loc1" and "loc2" can be relative or absolute paths or
-# URLs. If a location is present for each tag, the installdox tool
-# does not have to be run to correct the links.
-# Note that each tag file must have a unique name
-# (where the name does NOT include the path)
-# If a tag file is not located in the directory in which doxygen
-# is run, you must also specify the path to the tagfile here.
-
-TAGFILES =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create
-# a tag file that is based on the input files it reads.
-
-GENERATE_TAGFILE =
-
-# If the ALLEXTERNALS tag is set to YES all external classes will be listed
-# in the class index. If set to NO only the inherited external classes
-# will be listed.
-
-ALLEXTERNALS = YES
-
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will
-# be listed.
-
-EXTERNAL_GROUPS = YES
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of `which perl').
-
-PERL_PATH =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
-# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
-# or super classes. Setting the tag to NO turns the diagrams off. Note that
-# this option also works with HAVE_DOT disabled, but it is recommended to
-# install and use dot, since it yields more powerful graphs.
-
-CLASS_DIAGRAMS = YES
-
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see
-# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH =
-
-# If set to YES, the inheritance and collaboration graphs will hide
-# inheritance and usage relations if the target is undocumented
-# or is not a class.
-
-HIDE_UNDOC_RELATIONS = NO
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz, a graph visualization
-# toolkit from AT&T and Lucent Bell Labs. The other options in this section
-# have no effect if this option is set to NO (the default)
-
-HAVE_DOT = YES
-
-# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
-# allowed to run in parallel. When set to 0 (the default) doxygen will
-# base this on the number of processors available in the system. You can set it
-# explicitly to a value larger than 0 to get control over the balance
-# between CPU load and processing speed.
-
-DOT_NUM_THREADS = 0
-
-# By default doxygen will use the Helvetica font for all dot files that
-# doxygen generates. When you want a differently looking font you can specify
-# the font name using DOT_FONTNAME. You need to make sure dot is able to find
-# the font, which can be done by putting it in a standard location or by setting
-# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
-# directory containing the font.
-
-DOT_FONTNAME = Helvetica
-
-# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
-# The default size is 10pt.
-
-DOT_FONTSIZE = 10
-
-# By default doxygen will tell dot to use the Helvetica font.
-# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
-# set the path where dot can find it.
-
-DOT_FONTPATH =
-
-# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect inheritance relations. Setting this tag to YES will force the
-# CLASS_DIAGRAMS tag to NO.
-
-CLASS_GRAPH = YES
-
-# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect implementation dependencies (inheritance, containment, and
-# class references variables) of the class with other documented classes.
-
-COLLABORATION_GRAPH = YES
-
-# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for groups, showing the direct groups dependencies
-
-GROUP_GRAPHS = YES
-
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-
-UML_LOOK = NO
-
-# If set to YES, the inheritance and collaboration graphs will show the
-# relations between templates and their instances.
-
-TEMPLATE_RELATIONS = YES
-
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
-# tags are set to YES then doxygen will generate a graph for each documented
-# file showing the direct and indirect include dependencies of the file with
-# other documented files.
-
-INCLUDE_GRAPH = YES
-
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
-# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
-# documented header file showing the documented files that directly or
-# indirectly include this file.
-
-INCLUDED_BY_GRAPH = YES
-
-# If the CALL_GRAPH and HAVE_DOT options are set to YES then
-# doxygen will generate a call dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable call graphs
-# for selected functions only using the \callgraph command.
-
-CALL_GRAPH = NO
-
-# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
-# doxygen will generate a caller dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable caller
-# graphs for selected functions only using the \callergraph command.
-
-CALLER_GRAPH = NO
-
-# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
-# will generate a graphical hierarchy of all classes instead of a textual one.
-
-GRAPHICAL_HIERARCHY = YES
-
-# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
-# then doxygen will show the dependencies a directory has on other directories
-# in a graphical way. The dependency relations are determined by the #include
-# relations between the files in the directories.
-
-DIRECTORY_GRAPH = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. Possible values are svg, png, jpg, or gif.
-# If left blank png will be used. If you choose svg you need to set
-# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
-# visible in IE 9+ (other browsers do not have this requirement).
-
-DOT_IMAGE_FORMAT = png
-
-# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
-# enable generation of interactive SVG images that allow zooming and panning.
-# Note that this requires a modern browser other than Internet Explorer.
-# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
-# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
-# visible. Older versions of IE do not have SVG support.
-
-INTERACTIVE_SVG = NO
-
-# The tag DOT_PATH can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-
-DOT_PATH =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the
-# \dotfile command).
-
-DOTFILE_DIRS =
-
-# The MSCFILE_DIRS tag can be used to specify one or more directories that
-# contain msc files that are included in the documentation (see the
-# \mscfile command).
-
-MSCFILE_DIRS =
-
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
-# nodes that will be shown in the graph. If the number of nodes in a graph
-# becomes larger than this value, doxygen will truncate the graph, which is
-# visualized by representing a node as a red box. Note that doxygen if the
-# number of direct children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
-# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
-
-DOT_GRAPH_MAX_NODES = 50
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
-# graphs generated by dot. A depth value of 3 means that only nodes reachable
-# from the root by following a path via at most 3 edges will be shown. Nodes
-# that lay further from the root node will be omitted. Note that setting this
-# option to 1 or 2 may greatly reduce the computation time needed for large
-# code bases. Also note that the size of a graph can be further restricted by
-# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
-
-MAX_DOT_GRAPH_DEPTH = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not
-# seem to support this out of the box. Warning: Depending on the platform used,
-# enabling this option may lead to badly anti-aliased labels on the edges of
-# a graph (i.e. they become hard to read).
-
-DOT_TRANSPARENT = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10)
-# support this, this feature is disabled by default.
-
-DOT_MULTI_TARGETS = NO
-
-# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
-# generate a legend page explaining the meaning of the various boxes and
-# arrows in the dot generated graphs.
-
-GENERATE_LEGEND = YES
-
-# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
-# remove the intermediate dot files that are used to generate
-# the various graphs.
-
-DOT_CLEANUP = YES
diff --git a/docs/ReleaseNotes.rst b/docs/ReleaseNotes.rst
index 56b62a18..26f47c1f 100644
--- a/docs/ReleaseNotes.rst
+++ b/docs/ReleaseNotes.rst
@@ -1,5 +1,5 @@
===================================================
-Extra Clang Tools 7.0.0 (In-Progress) Release Notes
+Extra Clang Tools 8.0.0 (In-Progress) Release Notes
===================================================
.. contents::
@@ -10,7 +10,7 @@ Written by the `LLVM Team <http://llvm.org/>`_
.. warning::
- These are in-progress notes for the upcoming Extra Clang Tools 7 release.
+ These are in-progress notes for the upcoming Extra Clang Tools 8 release.
Release notes for previous releases can be found on
`the Download Page <http://releases.llvm.org/download.html>`_.
@@ -18,7 +18,7 @@ Introduction
============
This document contains the release notes for the Extra Clang Tools, part of the
-Clang release 7.0.0. Here we describe the status of the Extra Clang Tools in
+Clang release 8.0.0. Here we describe the status of the Extra Clang Tools in
some detail, including major improvements from the previous release and new
feature work. All LLVM releases may be downloaded from the `LLVM releases web
site <http://llvm.org/releases/>`_.
@@ -32,7 +32,7 @@ main Clang web page, this document applies to the *next* release, not
the current one. To see the release notes for a specific release, please
see the `releases page <http://llvm.org/releases/>`_.
-What's New in Extra Clang Tools 7.0.0?
+What's New in Extra Clang Tools 8.0.0?
======================================
Some of the major new features and improvements to Extra Clang Tools are listed
@@ -57,194 +57,7 @@ The improvements are...
Improvements to clang-tidy
--------------------------
-- New module `abseil` for checks related to the `Abseil <https://abseil.io>`_
- library.
-
-- New module ``portability``.
-
-- New module ``zircon`` for checks related to Fuchsia's Zircon kernel.
-
-- New :doc:`android-comparison-in-temp-failure-retry
- <clang-tidy/checks/android-comparison-in-temp-failure-retry>` check.
-
- Diagnoses comparisons that appear to be incorrectly placed in the argument to
- the ``TEMP_FAILURE_RETRY`` macro.
-
-- New :doc:`bugprone-parent-virtual-call
- <clang-tidy/checks/bugprone-parent-virtual-call>` check.
-
- Detects and fixes calls to grand-...parent virtual methods instead of calls
- to overridden parent's virtual methods.
-
-- New :doc:`bugprone-terminating-continue
- <clang-tidy/checks/bugprone-terminating-continue>` check
-
- Checks if a ``continue`` statement terminates the loop.
-
-- New :doc:`bugprone-throw-keyword-missing
- <clang-tidy/checks/bugprone-throw-keyword-missing>` check.
-
- Diagnoses when a temporary object that appears to be an exception is
- constructed but not thrown.
-
-- New :doc:`bugprone-unused-return-value
- <clang-tidy/checks/bugprone-unused-return-value>` check.
-
- Warns on unused function return values.
-
-- New :doc:`cppcoreguidelines-avoid-goto
- <clang-tidy/checks/cppcoreguidelines-avoid-goto>` check.
-
- The usage of ``goto`` for control flow is error prone and should be replaced
- with looping constructs. Every backward jump is rejected. Forward jumps are
- only allowed in nested loops.
-
-- New :doc:`cppcoreguidelines-narrowing-conversions
- <clang-tidy/checks/cppcoreguidelines-narrowing-conversions>` check
-
- Checks for narrowing conversions, e. g. ``int i = 0; i += 0.1;``.
-
-- New :doc:`fuchsia-multiple-inheritance
- <clang-tidy/checks/fuchsia-multiple-inheritance>` check.
-
- Warns if a class inherits from multiple classes that are not pure virtual.
-
-- New :doc:`abseil-string-find-startswith
- <clang-tidy/checks/abseil-string-find-startswith>` check.
-
- Checks whether a ``std::string::find()`` result is compared with 0, and
- suggests replacing with ``absl::StartsWith()``.
-
-- New `fuchsia-restrict-system-includes
- <http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-restrict-system-includes.html>`_ check
-
- Checks for allowed system includes and suggests removal of any others.
-
-- New `fuchsia-statically-constructed-objects
- <http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-statically-constructed-objects.html>`_ check
-
- Warns if global, non-trivial objects with static storage are constructed,
- unless the object is statically initialized with a ``constexpr`` constructor
- or has no explicit constructor.
-
-- New :doc:`fuchsia-trailing-return
- <clang-tidy/checks/fuchsia-trailing-return>` check.
-
- Functions that have trailing returns are disallowed, except for those
- using ``decltype`` specifiers and lambda with otherwise unutterable
- return types.
-
-- New :doc:`hicpp-multiway-paths-covered
- <clang-tidy/checks/hicpp-multiway-paths-covered>` check.
-
- Checks on ``switch`` and ``if`` - ``else if`` constructs that do not cover all possible code paths.
-
-- New :doc:`modernize-use-uncaught-exceptions
- <clang-tidy/checks/modernize-use-uncaught-exceptions>` check.
-
- Finds and replaces deprecated uses of ``std::uncaught_exception`` to
- ``std::uncaught_exceptions``.
-
-- New :doc:`portability-simd-intrinsics
- <clang-tidy/checks/portability-simd-intrinsics>` check.
-
- Warns or suggests alternatives if SIMD intrinsics are used which can be replaced by
- ``std::experimental::simd`` operations.
-
-- New :doc:`readability-simplify-subscript-expr
- <clang-tidy/checks/readability-simplify-subscript-expr>` check.
-
- Simplifies subscript expressions like ``s.data()[i]`` into ``s[i]``.
-
-- New :doc:`zircon-temporary-objects
- <clang-tidy/checks/zircon-temporary-objects>` check.
-
- Warns on construction of specific temporary objects in the Zircon kernel.
-
-- Added the missing bitwise assignment operations to
- :doc:`hicpp-signed-bitwise <clang-tidy/checks/hicpp-signed-bitwise>`.
-
-- New option `MinTypeNameLength` for :doc:`modernize-use-auto
- <clang-tidy/checks/modernize-use-auto>` check to limit the minimal length of
- type names to be replaced with ``auto``. Use to skip replacing short type
- names like ``int``/``bool`` with ``auto``. Default value is 5 which means
- replace types with the name length >= 5 letters only (ex. ``double``,
- ``unsigned``).
-
-- Add `VariableThreshold` option to :doc:`readability-function-size
- <clang-tidy/checks/readability-function-size>` check.
-
- Flags functions that have more than a specified number of variables declared
- in the body.
-
-- The `AnalyzeTemporaryDtors` option was removed, since the corresponding
- `cfg-temporary-dtors` option of the Static Analyzer now defaults to `true`.
-
-- New alias :doc:`fuchsia-header-anon-namespaces
- <clang-tidy/checks/fuchsia-header-anon-namespaces>` to :doc:`google-build-namespaces
- <clang-tidy/checks/google-build-namespaces>`
- added.
-
-- New alias :doc:`hicpp-avoid-goto
- <clang-tidy/checks/hicpp-avoid-goto>` to :doc:`cppcoreguidelines-avoid-goto
- <clang-tidy/checks/cppcoreguidelines-avoid-goto>`
- added.
-
-- The 'misc-forwarding-reference-overload' check was renamed to :doc:`bugprone-forwarding-reference-overload
- <clang-tidy/checks/bugprone-forwarding-reference-overload>`
-
-- The 'misc-incorrect-roundings' check was renamed to :doc:`bugprone-incorrect-roundings
- <clang-tidy/checks/bugprone-incorrect-roundings>`
-
-- The 'misc-lambda-function-name' check was renamed to :doc:`bugprone-lambda-function-name
- <clang-tidy/checks/bugprone-lambda-function-name>`
-
-- The 'misc-macro-parentheses' check was renamed to :doc:`bugprone-macro-parentheses
- <clang-tidy/checks/bugprone-macro-parentheses>`
-
-- The 'misc-macro-repeated-side-effects' check was renamed to :doc:`bugprone-macro-repeated-side-effects
- <clang-tidy/checks/bugprone-macro-repeated-side-effects>`
-
-- The 'misc-misplaced-widening-cast' check was renamed to :doc:`bugprone-misplaced-widening-cast
- <clang-tidy/checks/bugprone-misplaced-widening-cast>`
-
-- The 'misc-sizeof-container' check was renamed to :doc:`bugprone-sizeof-container
- <clang-tidy/checks/bugprone-sizeof-container>`
-
-- The 'misc-sizeof-expression' check was renamed to :doc:`bugprone-sizeof-expression
- <clang-tidy/checks/bugprone-sizeof-expression>`
-
-- The 'misc-string-compare' check was renamed to :doc:`readability-string-compare
- <clang-tidy/checks/readability-string-compare>`
-
-- The 'misc-string-integer-assignment' check was renamed to :doc:`bugprone-string-integer-assignment
- <clang-tidy/checks/bugprone-string-integer-assignment>`
-
-- The 'misc-string-literal-with-embedded-nul' check was renamed to :doc:`bugprone-string-literal-with-embedded-nul
- <clang-tidy/checks/bugprone-string-literal-with-embedded-nul>`
-
-- The 'misc-suspicious-enum-usage' check was renamed to :doc:`bugprone-suspicious-enum-usage
- <clang-tidy/checks/bugprone-suspicious-enum-usage>`
-
-- The 'misc-suspicious-missing-comma' check was renamed to :doc:`bugprone-suspicious-missing-comma
- <clang-tidy/checks/bugprone-suspicious-missing-comma>`
-
-- The 'misc-suspicious-semicolon' check was renamed to :doc:`bugprone-suspicious-semicolon
- <clang-tidy/checks/bugprone-suspicious-semicolon>`
-
-- The 'misc-suspicious-string-compare' check was renamed to :doc:`bugprone-suspicious-string-compare
- <clang-tidy/checks/bugprone-suspicious-string-compare>`
-
-- The 'misc-swapped-arguments' check was renamed to :doc:`bugprone-swapped-arguments
- <clang-tidy/checks/bugprone-swapped-arguments>`
-
-- The 'misc-undelegated-constructor' check was renamed to :doc:`bugprone-undelegated-constructor
- <clang-tidy/checks/bugprone-undelegated-constructor>`
-
-- The 'misc-unused-raii' check was renamed to :doc:`bugprone-unused-raii
- <clang-tidy/checks/bugprone-unused-raii>`
-
-- The 'google-runtime-member-string-references' check was removed.
+The improvements are...
Improvements to include-fixer
-----------------------------
diff --git a/docs/clang-tidy/checks/bugprone-exception-escape.rst b/docs/clang-tidy/checks/bugprone-exception-escape.rst
new file mode 100644
index 00000000..3037f5e3
--- /dev/null
+++ b/docs/clang-tidy/checks/bugprone-exception-escape.rst
@@ -0,0 +1,37 @@
+.. title:: clang-tidy - bugprone-exception-escape
+
+bugprone-exception-escape
+=========================
+
+Finds functions which may throw an exception directly or indirectly, but they
+should not. The functions which should not throw exceptions are the following:
+* Destructors
+* Move constructors
+* Move assignment operators
+* The ``main()`` functions
+* ``swap()`` functions
+* Functions marked with ``throw()`` or ``noexcept``
+* Other functions given as option
+
+A destructor throwing an exception may result in undefined behavior, resource
+leaks or unexpected termination of the program. Throwing move constructor or
+move assignment also may result in undefined behavior or resource leak. The
+``swap()`` operations expected to be non throwing most of the cases and they
+are always possible to implement in a non throwing way. Non throwing ``swap()``
+operations are also used to create move operations. A throwing ``main()``
+function also results in unexpected termination.
+
+Options
+-------
+
+.. option:: FunctionsThatShouldNotThrow
+
+ Comma separated list containing function names which should not throw. An
+ example value for this parameter can be ``WinMain`` which adds function
+ ``WinMain()`` in the Windows API to the list of the funcions which should
+ not throw. Default value is an empty string.
+
+.. option:: IgnoredExceptions
+
+ Comma separated list containing type names which are not counted as thrown
+ exceptions in the check. Default value is an empty string.
diff --git a/docs/clang-tidy/checks/cert-msc32-c.rst b/docs/clang-tidy/checks/cert-msc32-c.rst
new file mode 100644
index 00000000..df527ec1
--- /dev/null
+++ b/docs/clang-tidy/checks/cert-msc32-c.rst
@@ -0,0 +1,9 @@
+.. title:: clang-tidy - cert-msc32-c
+.. meta::
+ :http-equiv=refresh: 5;URL=cert-msc51-cpp.html
+
+cert-msc32-c
+============
+
+The cert-msc32-c check is an alias, please see
+`cert-msc51-cpp <cert-msc51-cpp.html>`_ for more information.
diff --git a/docs/clang-tidy/checks/cert-msc51-cpp.rst b/docs/clang-tidy/checks/cert-msc51-cpp.rst
new file mode 100644
index 00000000..53653698
--- /dev/null
+++ b/docs/clang-tidy/checks/cert-msc51-cpp.rst
@@ -0,0 +1,40 @@
+.. title:: clang-tidy - cert-msc51-cpp
+
+cert-msc51-cpp
+==============
+
+This check flags all pseudo-random number engines, engine adaptor
+instantiations and ``srand()`` when initialized or seeded with default argument,
+constant expression or any user-configurable type. Pseudo-random number
+engines seeded with a predictable value may cause vulnerabilities e.g. in
+security protocols.
+This is a CERT security rule, see
+`MSC51-CPP. Ensure your random number generator is properly seeded
+<https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC51-CPP.+Ensure+your+random+number+generator+is+properly+seeded>`_ and
+`MSC32-C. Properly seed pseudorandom number generators
+<https://wiki.sei.cmu.edu/confluence/display/c/MSC32-C.+Properly+seed+pseudorandom+number+generators>`_.
+
+Examples:
+
+.. code-block:: c++
+
+ void foo() {
+ std::mt19937 engine1; // Diagnose, always generate the same sequence
+ std::mt19937 engine2(1); // Diagnose
+ engine1.seed(); // Diagnose
+ engine2.seed(1); // Diagnose
+
+ std::time_t t;
+ engine1.seed(std::time(&t)); // Diagnose, system time might be controlled by user
+
+ int x = atoi(argv[1]);
+ std::mt19937 engine3(x); // Will not warn
+ }
+
+Options
+-------
+
+.. option:: DisallowedSeedTypes
+
+ A comma-separated list of the type names which are disallowed.
+ Default values are ``time_t``, ``std::time_t``.
diff --git a/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst b/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst
deleted file mode 100644
index e77fd64a..00000000
--- a/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-.. title:: clang-tidy - google-readability-redundant-smartptr-get
-.. meta::
- :http-equiv=refresh: 5;URL=readability-redundant-smartptr-get.html
-
-google-readability-redundant-smartptr-get
-=========================================
-
-The google-readability-redundant-smartptr-get check is an alias, please see
-`readability-redundant-smartptr-get <readability-redundant-smartptr-get.html>`_
-for more information.
diff --git a/docs/clang-tidy/checks/list.rst b/docs/clang-tidy/checks/list.rst
index 50f074fc..9bb68bcd 100644
--- a/docs/clang-tidy/checks/list.rst
+++ b/docs/clang-tidy/checks/list.rst
@@ -24,6 +24,7 @@ Clang-Tidy Checks
bugprone-bool-pointer-implicit-conversion
bugprone-copy-constructor-init
bugprone-dangling-handle
+ bugprone-exception-escape
bugprone-fold-init-type
bugprone-forward-declaration-namespace
bugprone-forwarding-reference-overload
@@ -73,7 +74,9 @@ Clang-Tidy Checks
cert-fio38-c (redirects to misc-non-copyable-objects) <cert-fio38-c>
cert-flp30-c
cert-msc30-c (redirects to cert-msc50-cpp) <cert-msc30-c>
+ cert-msc32-c (redirects to cert-msc51-cpp) <cert-msc32-c>
cert-msc50-cpp
+ cert-msc51-cpp
cert-oop11-cpp (redirects to performance-move-constructor-init) <cert-oop11-cpp>
cppcoreguidelines-avoid-goto
cppcoreguidelines-c-copy-assignment-signature (redirects to misc-unconventional-assign-operator) <cppcoreguidelines-c-copy-assignment-signature>
@@ -93,8 +96,8 @@ Clang-Tidy Checks
cppcoreguidelines-pro-type-vararg
cppcoreguidelines-slicing
cppcoreguidelines-special-member-functions
- fuchsia-header-anon-namespaces (redirects to google-build-namespaces) <fuchsia-header-anon-namespaces>
fuchsia-default-arguments
+ fuchsia-header-anon-namespaces (redirects to google-build-namespaces) <fuchsia-header-anon-namespaces>
fuchsia-multiple-inheritance
fuchsia-overloaded-operator
fuchsia-restrict-system-includes
@@ -113,7 +116,6 @@ Clang-Tidy Checks
google-readability-casting
google-readability-function-size (redirects to readability-function-size) <google-readability-function-size>
google-readability-namespace-comments (redirects to llvm-namespace-comment) <google-readability-namespace-comments>
- google-readability-redundant-smartptr-get (redirects to readability-redundant-smartptr-get) <google-readability-redundant-smartptr-get>
google-readability-todo
google-runtime-int
google-runtime-operator
diff --git a/docs/clang-tidy/checks/misc-unused-parameters.rst b/docs/clang-tidy/checks/misc-unused-parameters.rst
index de868b91..3dfeb299 100644
--- a/docs/clang-tidy/checks/misc-unused-parameters.rst
+++ b/docs/clang-tidy/checks/misc-unused-parameters.rst
@@ -3,24 +3,40 @@
misc-unused-parameters
======================
-Finds unused parameters and fixes them, so that `-Wunused-parameter` can be
-turned on.
+Finds unused function parameters. Unused parameters may signify a bug in the
+code (e.g. when a different parameter is used instead). The suggested fixes
+either comment parameter name out or remove the parameter completely, if all
+callers of the function are in the same translation unit and can be updated.
+
+The check is similar to the `-Wunused-parameter` compiler diagnostic and can be
+used to prepare a codebase to enabling of that diagnostic. By default the check
+is more permissive (see :option:`StrictMode`).
.. code-block:: c++
- void a(int i) {}
+ void a(int i) { /*some code that doesn't use `i`*/ }
// becomes
- void a(int /*i*/) {}
-
+ void a(int /*i*/) { /*some code that doesn't use `i`*/ }
.. code-block:: c++
static void staticFunctionA(int i);
- static void staticFunctionA(int i) {}
+ static void staticFunctionA(int i) { /*some code that doesn't use `i`*/ }
// becomes
static void staticFunctionA()
- static void staticFunctionA() {}
+ static void staticFunctionA() { /*some code that doesn't use `i`*/ }
+
+Options
+-------
+
+.. option:: StrictMode
+
+ When zero (default value), the check will ignore trivially unused parameters,
+ i.e. when the corresponding function has an empty body (and in case of
+ constructors - no constructor initializers). When the function body is empty,
+ an unused parameter is unlikely to be unnoticed by a human reader, and
+ there's basically no place for a bug to hide.
diff --git a/docs/clang-tidy/checks/modernize-use-auto.rst b/docs/clang-tidy/checks/modernize-use-auto.rst
index 658de9a9..a73e86ef 100644
--- a/docs/clang-tidy/checks/modernize-use-auto.rst
+++ b/docs/clang-tidy/checks/modernize-use-auto.rst
@@ -182,20 +182,37 @@ Options
If the option is set to non-zero (default `5`), the check will ignore type
names having a length less than the option value. The option affects
expressions only, not iterators.
+ Spaces between multi-lexeme type names (``long int``) are considered as one.
+ If ``RemoveStars`` option (see below) is set to non-zero, then ``*s`` in
+ the type are also counted as a part of the type name.
.. code-block:: c++
- // MinTypeNameLength = 0
+ // MinTypeNameLength = 0, RemoveStars=0
int a = static_cast<int>(foo()); // ---> auto a = ...
- bool b = new bool; // ---> auto b = ...
+ // length(bool *) = 4
+ bool *b = new bool; // ---> auto *b = ...
unsigned c = static_cast<unsigned>(foo()); // ---> auto c = ...
- // MinTypeNameLength = 8
+ // MinTypeNameLength = 5, RemoveStars=0
- int a = static_cast<int>(foo()); // ---> int a = ...
- bool b = new bool; // ---> bool b = ...
- unsigned c = static_cast<unsigned>(foo()); // ---> auto c = ...
+ int a = static_cast<int>(foo()); // ---> int a = ...
+ bool b = static_cast<bool>(foo()); // ---> bool b = ...
+ bool *pb = static_cast<bool*>(foo()); // ---> bool *pb = ...
+ unsigned c = static_cast<unsigned>(foo()); // ---> auto c = ...
+ // length(long <on-or-more-spaces> int) = 8
+ long int d = static_cast<long int>(foo()); // ---> auto d = ...
+
+ // MinTypeNameLength = 5, RemoveStars=1
+
+ int a = static_cast<int>(foo()); // ---> int a = ...
+ // length(int * * ) = 5
+ int **pa = static_cast<int**>(foo()); // ---> auto pa = ...
+ bool b = static_cast<bool>(foo()); // ---> bool b = ...
+ bool *pb = static_cast<bool*>(foo()); // ---> auto pb = ...
+ unsigned c = static_cast<unsigned>(foo()); // ---> auto c = ...
+ long int d = static_cast<long int>(foo()); // ---> auto d = ...
.. option:: RemoveStars
diff --git a/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst b/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst
index cd262e2d..a47bb41b 100644
--- a/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst
+++ b/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst
@@ -29,6 +29,15 @@ function declarations, for example:
void foo(int a);
void foo(int); // no warning
+One name is also allowed to be a case-insensitive prefix/suffix of the other:
+
+.. code-block:: c++
+
+ void foo(int count);
+ void foo(int count_input) { // no warning
+ int count = adjustCount(count_input);
+ }
+
To help with refactoring, in some cases fix-it hints are generated to align
parameter names to a single naming convention. This works with the assumption
that the function definition is the most up-to-date version, as it directly
@@ -47,3 +56,8 @@ the definition or the first declaration seen in a translation unit.
If this option is set to non-zero (default is `1`), the check will not warn
about names declared inside macros.
+
+.. option:: Strict
+
+ If this option is set to non-zero (default is `0`), then names must match
+ exactly (or be absent).
diff --git a/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst b/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst
index acf79860..3fc77c5f 100644
--- a/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst
+++ b/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst
@@ -3,9 +3,6 @@
readability-redundant-smartptr-get
==================================
-`google-readability-redundant-smartptr-get` redirects here as an alias for this
-check.
-
Find and remove redundant calls to smart pointer's ``.get()`` method.
Examples:
@@ -15,4 +12,5 @@ Examples:
ptr.get()->Foo() ==> ptr->Foo()
*ptr.get() ==> *ptr
*ptr->get() ==> **ptr
+ if (ptr.get() == nullptr) ... => if (ptr == nullptr) ...
diff --git a/docs/clang-tidy/index.rst b/docs/clang-tidy/index.rst
index 5a1e37c7..ec6c24ff 100644
--- a/docs/clang-tidy/index.rst
+++ b/docs/clang-tidy/index.rst
@@ -99,114 +99,121 @@ An overview of all the command-line options:
.. code-block:: console
- $ clang-tidy -help
+ $ clang-tidy --help
USAGE: clang-tidy [options] <source0> [... <sourceN>]
OPTIONS:
Generic Options:
- -help - Display available options (-help-hidden for more)
- -help-list - Display list of available options (-help-list-hidden for more)
- -version - Display the version of this program
+ -help - Display available options (-help-hidden for more)
+ -help-list - Display list of available options (-help-list-hidden for more)
+ -version - Display the version of this program
clang-tidy options:
- -checks=<string> -
- Comma-separated list of globs with optional '-'
- prefix. Globs are processed in order of
- appearance in the list. Globs without '-'
- prefix add checks with matching names to the
- set, globs with the '-' prefix remove checks
- with matching names from the set of enabled
- checks. This option's value is appended to the
- value of the 'Checks' option in .clang-tidy
- file, if any.
- -config=<string> -
- Specifies a configuration in YAML/JSON format:
- -config="{Checks: '*',
- CheckOptions: [{key: x,
- value: y}]}"
- When the value is empty, clang-tidy will
- attempt to find a file named .clang-tidy for
- each source file in its parent directories.
- -dump-config -
- Dumps configuration in the YAML format to
- stdout. This option can be used along with a
- file name (and '--' if the file is outside of a
- project with configured compilation database).
- The configuration used for this file will be
- printed.
- Use along with -checks=* to include
- configuration of all checks.
- -enable-check-profile -
- Enable per-check timing profiles, and print a
- report to stderr.
- -explain-config -
- For each enabled check explains, where it is
- enabled, i.e. in clang-tidy binary, command
- line or a specific configuration file.
- -export-fixes=<filename> -
- YAML file to store suggested fixes in. The
- stored fixes can be applied to the input source
- code with clang-apply-replacements.
- -extra-arg=<string> - Additional argument to append to the compiler command line
- -extra-arg-before=<string> - Additional argument to prepend to the compiler command line
- -fix -
- Apply suggested fixes. Without -fix-errors
- clang-tidy will bail out if any compilation
- errors were found.
- -fix-errors -
- Apply suggested fixes even if compilation
- errors were found. If compiler errors have
- attached fix-its, clang-tidy will apply them as
- well.
- -format-style=<string> -
- 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.
- -header-filter=<string> -
- Regular expression matching the names of the
- headers to output diagnostics from. Diagnostics
- from the main file of each translation unit are
- always displayed.
- Can be used together with -line-filter.
- This option overrides the 'HeaderFilter' option
- in .clang-tidy file, if any.
- -line-filter=<string> -
- List of files with line ranges to filter the
- warnings. Can be used together with
- -header-filter. The format of the list is a
- JSON array of objects:
- [
- {"name":"file1.cpp","lines":[[1,3],[5,7]]},
- {"name":"file2.h"}
- ]
- -list-checks -
- List all enabled checks and exit. Use with
- -checks=* to list all available checks.
- -p=<string> - Build path
- -quiet -
- Run clang-tidy in quiet mode. This suppresses
- printing statistics about ignored warnings and
- warnings treated as errors if the respective
- options are specified.
- -system-headers - Display the errors from system headers.
- -warnings-as-errors=<string> -
- Upgrades warnings to errors. Same format as
- '-checks'.
- This option's value is appended to the value of
- the 'WarningsAsErrors' option in .clang-tidy
- file, if any.
+ -checks=<string> -
+ Comma-separated list of globs with optional '-'
+ prefix. Globs are processed in order of
+ appearance in the list. Globs without '-'
+ prefix add checks with matching names to the
+ set, globs with the '-' prefix remove checks
+ with matching names from the set of enabled
+ checks. This option's value is appended to the
+ value of the 'Checks' option in .clang-tidy
+ file, if any.
+ -config=<string> -
+ Specifies a configuration in YAML/JSON format:
+ -config="{Checks: '*',
+ CheckOptions: [{key: x,
+ value: y}]}"
+ When the value is empty, clang-tidy will
+ attempt to find a file named .clang-tidy for
+ each source file in its parent directories.
+ -dump-config -
+ Dumps configuration in the YAML format to
+ stdout. This option can be used along with a
+ file name (and '--' if the file is outside of a
+ project with configured compilation database).
+ The configuration used for this file will be
+ printed.
+ Use along with -checks=* to include
+ configuration of all checks.
+ -enable-check-profile -
+ Enable per-check timing profiles, and print a
+ report to stderr.
+ -explain-config -
+ For each enabled check explains, where it is
+ enabled, i.e. in clang-tidy binary, command
+ line or a specific configuration file.
+ -export-fixes=<filename> -
+ YAML file to store suggested fixes in. The
+ stored fixes can be applied to the input source
+ code with clang-apply-replacements.
+ -extra-arg=<string> - Additional argument to append to the compiler command line
+ -extra-arg-before=<string> - Additional argument to prepend to the compiler command line
+ -fix -
+ Apply suggested fixes. Without -fix-errors
+ clang-tidy will bail out if any compilation
+ errors were found.
+ -fix-errors -
+ Apply suggested fixes even if compilation
+ errors were found. If compiler errors have
+ attached fix-its, clang-tidy will apply them as
+ well.
+ -format-style=<string> -
+ 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.
+ -header-filter=<string> -
+ Regular expression matching the names of the
+ headers to output diagnostics from. Diagnostics
+ from the main file of each translation unit are
+ always displayed.
+ Can be used together with -line-filter.
+ This option overrides the 'HeaderFilter' option
+ in .clang-tidy file, if any.
+ -line-filter=<string> -
+ List of files with line ranges to filter the
+ warnings. Can be used together with
+ -header-filter. The format of the list is a
+ JSON array of objects:
+ [
+ {"name":"file1.cpp","lines":[[1,3],[5,7]]},
+ {"name":"file2.h"}
+ ]
+ -list-checks -
+ List all enabled checks and exit. Use with
+ -checks=* to list all available checks.
+ -p=<string> - Build path
+ -quiet -
+ Run clang-tidy in quiet mode. This suppresses
+ printing statistics about ignored warnings and
+ warnings treated as errors if the respective
+ options are specified.
+ -store-check-profile=<prefix> -
+ By default reports are printed in tabulated
+ format to stderr. When this option is passed,
+ these per-TU profiles are instead stored as JSON.
+ -system-headers - Display the errors from system headers.
+ -vfsoverlay=<filename> -
+ Overlay the virtual filesystem described by file
+ over the real file system.
+ -warnings-as-errors=<string> -
+ Upgrades warnings to errors. Same format as
+ '-checks'.
+ This option's value is appended to the value of
+ the 'WarningsAsErrors' option in .clang-tidy
+ file, if any.
-p <build-path> is used to read a compile command database.
@@ -739,3 +746,65 @@ The script provides multiple configuration flags.
all changes in a temporary directory and applies them. Passing ``-format``
will run clang-format over changed lines.
+
+On checks profiling
+-------------------
+
+:program:`clang-tidy` can collect per-check profiling info, and output it
+for each processed source file (translation unit).
+
+To enable profiling info collection, use the ``-enable-check-profile`` argument.
+The timings will be output to ``stderr`` as a table. Example output:
+
+.. code-block:: console
+
+ $ clang-tidy -enable-check-profile -checks=-*,readability-function-size source.cpp
+ ===-------------------------------------------------------------------------===
+ clang-tidy checks profiling
+ ===-------------------------------------------------------------------------===
+ Total Execution Time: 1.0282 seconds (1.0258 wall clock)
+
+ ---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name ---
+ 0.9136 (100.0%) 0.1146 (100.0%) 1.0282 (100.0%) 1.0258 (100.0%) readability-function-size
+ 0.9136 (100.0%) 0.1146 (100.0%) 1.0282 (100.0%) 1.0258 (100.0%) Total
+
+It can also store that data as JSON files for further processing. Example output:
+
+.. code-block:: console
+
+ $ clang-tidy -enable-check-profile -store-check-profile=. -checks=-*,readability-function-size source.cpp
+ $ # Note that there won't be timings table printed to the console.
+ $ ls /tmp/out/
+ 20180516161318717446360-source.cpp.json
+ $ cat 20180516161318717446360-source.cpp.json
+ {
+ "file": "/path/to/source.cpp",
+ "timestamp": "2018-05-16 16:13:18.717446360",
+ "profile": {
+ "time.clang-tidy.readability-function-size.wall": 1.0421266555786133e+00,
+ "time.clang-tidy.readability-function-size.user": 9.2088400000005421e-01,
+ "time.clang-tidy.readability-function-size.sys": 1.2418899999999974e-01
+ }
+ }
+
+There is only one argument that controls profile storage:
+
+* ``-store-check-profile=<prefix>``
+
+ By default reports are printed in tabulated format to stderr. When this option
+ is passed, these per-TU profiles are instead stored as JSON.
+ If the prefix is not an absolute path, it is considered to be relative to the
+ directory from where you have run :program:`clang-tidy`. All ``.`` and ``..``
+ patterns in the path are collapsed, and symlinks are resolved.
+
+ Example:
+ Let's suppose you have a source file named ``example.cpp``, located in the
+ ``/source`` directory. Only the input filename is used, not the full path
+ to the source file. Additionally, it is prefixed with the current timestamp.
+
+ * If you specify ``-store-check-profile=/tmp``, then the profile will be saved
+ to ``/tmp/<ISO8601-like timestamp>-example.cpp.json``
+
+ * If you run :program:`clang-tidy` from within ``/foo`` directory, and specify
+ ``-store-check-profile=.``, then the profile will still be saved to
+ ``/foo/<ISO8601-like timestamp>-example.cpp.json``
diff --git a/docs/clangd.rst b/docs/clangd.rst
index 8218e3cf..97736efd 100644
--- a/docs/clangd.rst
+++ b/docs/clangd.rst
@@ -81,7 +81,7 @@ extension to the protocol.
+-------------------------------------+------------+----------+
| Code Lens | Yes | No |
+-------------------------------------+------------+----------+
-| Document Symbols | Yes | No |
+| Document Symbols | Yes | Yes |
+-------------------------------------+------------+----------+
| Workspace Symbols | Yes | No |
+-------------------------------------+------------+----------+
diff --git a/docs/conf.py b/docs/conf.py
index 5ac18216..1b07e8ef 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -49,9 +49,9 @@ copyright = u'2007-%d, The Clang Team' % date.today().year
# built documents.
#
# The short version.
-version = '7'
+version = '8'
# The full version, including alpha/beta/rc tags.
-release = '7'
+release = '8'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/docs/doxygen.cfg.in b/docs/doxygen.cfg.in
index 6dbf6db7..88095889 100644
--- a/docs/doxygen.cfg.in
+++ b/docs/doxygen.cfg.in
@@ -743,15 +743,20 @@ WARN_LOGFILE =
# spaces.
# Note: If this tag is empty the current directory is searched.
-INPUT = \
- @abs_srcdir@/../clang-tidy \
- @abs_srcdir@/../clang-apply-replacements \
- @abs_srcdir@/../clang-query \
- @abs_srcdir@/../clang-rename \
- @abs_srcdir@/../modularize \
- @abs_srcdir@/../pp-trace \
- @abs_srcdir@/../tool-template \
- @abs_srcdir@/doxygen-mainpage.dox
+INPUT = \
+ @abs_srcdir@/../change-namespace \
+ @abs_srcdir@/../clang-apply-replacements \
+ @abs_srcdir@/../clang-doc \
+ @abs_srcdir@/../clang-move \
+ @abs_srcdir@/../clang-query \
+ @abs_srcdir@/../clang-reorder-fields \
+ @abs_srcdir@/../clang-tidy \
+ @abs_srcdir@/../clangd \
+ @abs_srcdir@/../include-fixer \
+ @abs_srcdir@/../modularize \
+ @abs_srcdir@/../pp-trace \
+ @abs_srcdir@/../tool-template \
+ @abs_srcdir@/doxygen-mainpage.dox
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/include-fixer/tool/ClangIncludeFixer.cpp b/include-fixer/tool/ClangIncludeFixer.cpp
index 2f2e45ea..d9d97d23 100644
--- a/include-fixer/tool/ClangIncludeFixer.cpp
+++ b/include-fixer/tool/ClangIncludeFixer.cpp
@@ -324,7 +324,8 @@ int includeFixerMain(int argc, const char **argv) {
const IncludeFixerContext::HeaderInfo &RHS) {
return LHS.QualifiedName == RHS.QualifiedName;
});
- auto InsertStyle = format::getStyle("file", Context.getFilePath(), Style);
+ auto InsertStyle = format::getStyle(format::DefaultFormatStyle,
+ Context.getFilePath(), Style);
if (!InsertStyle) {
llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n";
return 1;
@@ -402,7 +403,8 @@ int includeFixerMain(int argc, const char **argv) {
std::vector<tooling::Replacements> FixerReplacements;
for (const auto &Context : Contexts) {
StringRef FilePath = Context.getFilePath();
- auto InsertStyle = format::getStyle("file", FilePath, Style);
+ auto InsertStyle =
+ format::getStyle(format::DefaultFormatStyle, FilePath, Style);
if (!InsertStyle) {
llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n";
return 1;
diff --git a/test/clang-doc/bc-comment.cpp b/test/clang-doc/bc-comment.cpp
new file mode 100644
index 00000000..3b006ab8
--- /dev/null
+++ b/test/clang-doc/bc-comment.cpp
@@ -0,0 +1,204 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+/// \brief Brief description.
+///
+/// Extended description that
+/// continues onto the next line.
+///
+/// <ul class="test">
+/// <li> Testing.
+/// </ul>
+///
+/// \verbatim
+/// The description continues.
+/// \endverbatim
+/// --
+/// \param [out] I is a parameter.
+/// \param J is a parameter.
+/// \return void
+void F(int I, int J);
+
+/// Bonus comment on definition
+void F(int I, int J) {}
+
+// RUN: clang-doc --dump-intermediate --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/0000000000000000000000000000000000000000.bc | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: <BLOCKINFO_BLOCK/>
+// CHECK-0-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-0-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-0-NEXT: </VersionBlock>
+// CHECK-0-NEXT: <NamespaceBlock NumWords=432 BlockCodeSize=4>
+// CHECK-0-NEXT: <FunctionBlock NumWords=429 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'F'
+// CHECK-0-NEXT: <CommentBlock NumWords=354 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'FullComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=31 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'BlockCommandComment'
+// CHECK-0-NEXT: <Name abbrevid=6 op0=5/> blob data = 'brief'
+// CHECK-0-NEXT: <CommentBlock NumWords=19 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=11 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=19/> blob data = ' Brief description.'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=37 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=26/> blob data = ' Extended description that'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=14 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=30/> blob data = ' continues onto the next line.'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=76 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=14 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'HTMLStartTagComment'
+// CHECK-0-NEXT: <Name abbrevid=6 op0=2/> blob data = 'ul'
+// CHECK-0-NEXT: <AttrKey abbrevid=12 op0=5/> blob data = 'class'
+// CHECK-0-NEXT: <AttrVal abbrevid=13 op0=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'HTMLStartTagComment'
+// CHECK-0-NEXT: <Name abbrevid=6 op0=2/> blob data = 'li'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=9/> blob data = ' Testing.'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=17/> blob data = 'HTMLEndTagComment'
+// CHECK-0-NEXT: <Name abbrevid=6 op0=2/> blob data = 'ul'
+// CHECK-0-NEXT: <SelfClosing abbrevid=10 op0=1/>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=32 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=20/> blob data = 'VerbatimBlockComment'
+// CHECK-0-NEXT: <Name abbrevid=6 op0=8/> blob data = 'verbatim'
+// CHECK-0-NEXT: <CloseName abbrevid=9 op0=11/> blob data = 'endverbatim'
+// CHECK-0-NEXT: <CommentBlock NumWords=16 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=24/> blob data = 'VerbatimBlockLineComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=27/> blob data = ' The description continues.'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=22 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=7 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=3/> blob data = ' --'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=39 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'ParamCommandComment'
+// CHECK-0-NEXT: <Direction abbrevid=7 op0=5/> blob data = '[out]'
+// CHECK-0-NEXT: <ParamName abbrevid=8 op0=1/> blob data = 'I'
+// CHECK-0-NEXT: <Explicit abbrevid=11 op0=1/>
+// CHECK-0-NEXT: <CommentBlock NumWords=25 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=16/> blob data = ' is a parameter.'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=38 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'ParamCommandComment'
+// CHECK-0-NEXT: <Direction abbrevid=7 op0=4/> blob data = '[in]'
+// CHECK-0-NEXT: <ParamName abbrevid=8 op0=1/> blob data = 'J'
+// CHECK-0-NEXT: <CommentBlock NumWords=25 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=16/> blob data = ' is a parameter.'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=28 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'BlockCommandComment'
+// CHECK-0-NEXT: <Name abbrevid=6 op0=6/> blob data = 'return'
+// CHECK-0-NEXT: <CommentBlock NumWords=16 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=8 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=5/> blob data = ' void'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <CommentBlock NumWords=28 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'FullComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=21 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=28/> blob data = ' Bonus comment on definition'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=28 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <Location abbrevid=7 op0=25 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <Name abbrevid=4 op0=1/> blob data = 'I'
+// CHECK-0-NEXT: </FieldTypeBlock>
+// CHECK-0-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <Name abbrevid=4 op0=1/> blob data = 'J'
+// CHECK-0-NEXT: </FieldTypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: </NamespaceBlock>
diff --git a/test/clang-doc/bc-linkage.cpp b/test/clang-doc/bc-linkage.cpp
new file mode 100644
index 00000000..8fec0d35
--- /dev/null
+++ b/test/clang-doc/bc-linkage.cpp
@@ -0,0 +1,844 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+void function(int x);
+
+inline int inlinedFunction(int x);
+
+int functionWithInnerClass(int x) {
+ class InnerClass { //NoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+inline int inlinedFunctionWithInnerClass(int x) {
+ class InnerClass { //VisibleNoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+class Class {
+public:
+ void publicMethod();
+ int publicField;
+
+protected:
+ void protectedMethod();
+ int protectedField;
+
+private:
+ void privateMethod();
+ int privateField;
+};
+
+namespace named {
+class NamedClass {
+public:
+ void namedPublicMethod();
+ int namedPublicField;
+
+protected:
+ void namedProtectedMethod();
+ int namedProtectedField;
+
+private:
+ void namedPrivateMethod();
+ int namedPrivateField;
+};
+
+void namedFunction();
+static void namedStaticFunction();
+inline void namedInlineFunction();
+} // namespace named
+
+static void staticFunction(int x); //Internal Linkage
+
+static int staticFunctionWithInnerClass(int x) {
+ class InnerClass { //NoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+namespace {
+class AnonClass {
+public:
+ void anonPublicMethod();
+ int anonPublicField;
+
+protected:
+ void anonProtectedMethod();
+ int anonProtectedField;
+
+private:
+ void anonPrivateMethod();
+ int anonPrivateField;
+};
+
+void anonFunction();
+static void anonStaticFunction();
+inline void anonInlineFunction();
+} // namespace
+
+// RUN: clang-doc --dump-intermediate --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/C9B3B71ACDD84C5BB320D34E97677715CDB3EA32.bc | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: <BLOCKINFO_BLOCK/>
+// CHECK-0-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-0-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-0-NEXT: </VersionBlock>
+// CHECK-0-NEXT: <RecordBlock NumWords=107 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=17 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=29/> blob data = 'inlinedFunctionWithInnerClass'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=24 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-0-NEXT: <FunctionBlock NumWords=71 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=17/> blob data = 'innerPublicMethod'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=17 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=29/> blob data = 'inlinedFunctionWithInnerClass'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=26 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/8960B5C9247D6F5C532756E53A1AD1240FA2146F.bc | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: <BLOCKINFO_BLOCK/>
+// CHECK-1-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-1-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-1-NEXT: </VersionBlock>
+// CHECK-1-NEXT: <NamespaceBlock NumWords=126 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=5/> blob data = 'named'
+// CHECK-1-NEXT: <FunctionBlock NumWords=36 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=13/> blob data = 'namedFunction'
+// CHECK-1-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=5/> blob data = 'named'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <Location abbrevid=7 op0=61 op1=4/> blob data = '{{.*}}'
+// CHECK-1-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-1-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: </TypeBlock>
+// CHECK-1-NEXT: </FunctionBlock>
+// CHECK-1-NEXT: <FunctionBlock NumWords=37 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=19/> blob data = 'namedStaticFunction'
+// CHECK-1-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=5/> blob data = 'named'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <Location abbrevid=7 op0=62 op1=4/> blob data = '{{.*}}'
+// CHECK-1-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-1-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: </TypeBlock>
+// CHECK-1-NEXT: </FunctionBlock>
+// CHECK-1-NEXT: <FunctionBlock NumWords=37 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=19/> blob data = 'namedInlineFunction'
+// CHECK-1-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=5/> blob data = 'named'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <Location abbrevid=7 op0=63 op1=4/> blob data = '{{.*}}'
+// CHECK-1-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-1-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: </TypeBlock>
+// CHECK-1-NEXT: </FunctionBlock>
+// CHECK-1-NEXT: </NamespaceBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/74A031CBE68C101F3E83F60ED17F20C11EC19D48.bc | FileCheck %s --check-prefix CHECK-2
+// CHECK-2: <BLOCKINFO_BLOCK/>
+// CHECK-2-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-2-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-2-NEXT: </VersionBlock>
+// CHECK-2-NEXT: <RecordBlock NumWords=105 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-2-NEXT: <ReferenceBlock NumWords=16 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=28/> blob data = 'staticFunctionWithInnerClass'
+// CHECK-2-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-2-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-2-NEXT: </ReferenceBlock>
+// CHECK-2-NEXT: <DefLocation abbrevid=6 op0=69 op1=4/> blob data = '{{.*}}'
+// CHECK-2-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-2-NEXT: <FunctionBlock NumWords=70 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=17/> blob data = 'innerPublicMethod'
+// CHECK-2-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-2-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-2-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-2-NEXT: </ReferenceBlock>
+// CHECK-2-NEXT: <ReferenceBlock NumWords=16 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=28/> blob data = 'staticFunctionWithInnerClass'
+// CHECK-2-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-2-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-2-NEXT: </ReferenceBlock>
+// CHECK-2-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-2-NEXT: <DefLocation abbrevid=6 op0=71 op1=4/> blob data = '{{.*}}'
+// CHECK-2-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-2-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-2-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-2-NEXT: </ReferenceBlock>
+// CHECK-2-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-2-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-2-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-2-NEXT: </ReferenceBlock>
+// CHECK-2-NEXT: </TypeBlock>
+// CHECK-2-NEXT: </FunctionBlock>
+// CHECK-2-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/7CDD73DCD6CD72F7E5CE25502810A182C66C4B45.bc | FileCheck %s --check-prefix CHECK-3
+// CHECK-3: <BLOCKINFO_BLOCK/>
+// CHECK-3-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-3-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-3-NEXT: </VersionBlock>
+// CHECK-3-NEXT: <RecordBlock NumWords=203 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=5/> blob data = 'Class'
+// CHECK-3-NEXT: <DefLocation abbrevid=6 op0=32 op1=4/> blob data = '{{.*}}'
+// CHECK-3-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-3-NEXT: <MemberTypeBlock NumWords=10 BlockCodeSize=4>
+// CHECK-3-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-3-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <Name abbrevid=4 op0=11/> blob data = 'publicField'
+// CHECK-3-NEXT: <Access abbrevid=5 op0=3/>
+// CHECK-3-NEXT: </MemberTypeBlock>
+// CHECK-3-NEXT: <MemberTypeBlock NumWords=11 BlockCodeSize=4>
+// CHECK-3-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-3-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <Name abbrevid=4 op0=14/> blob data = 'protectedField'
+// CHECK-3-NEXT: <Access abbrevid=5 op0=1/>
+// CHECK-3-NEXT: </MemberTypeBlock>
+// CHECK-3-NEXT: <MemberTypeBlock NumWords=10 BlockCodeSize=4>
+// CHECK-3-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-3-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <Name abbrevid=4 op0=12/> blob data = 'privateField'
+// CHECK-3-NEXT: <Access abbrevid=5 op0=2/>
+// CHECK-3-NEXT: </MemberTypeBlock>
+// CHECK-3-NEXT: <FunctionBlock NumWords=48 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=12/> blob data = 'publicMethod'
+// CHECK-3-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=5/> blob data = 'Class'
+// CHECK-3-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-3-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-3-NEXT: <Location abbrevid=7 op0=34 op1=4/> blob data = '{{.*}}'
+// CHECK-3-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=5/> blob data = 'Class'
+// CHECK-3-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-3-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-3-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-3-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: </TypeBlock>
+// CHECK-3-NEXT: </FunctionBlock>
+// CHECK-3-NEXT: <FunctionBlock NumWords=49 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=15/> blob data = 'protectedMethod'
+// CHECK-3-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=5/> blob data = 'Class'
+// CHECK-3-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-3-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-3-NEXT: <Location abbrevid=7 op0=38 op1=4/> blob data = '{{.*}}'
+// CHECK-3-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=5/> blob data = 'Class'
+// CHECK-3-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-3-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-3-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-3-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: </TypeBlock>
+// CHECK-3-NEXT: </FunctionBlock>
+// CHECK-3-NEXT: <FunctionBlock NumWords=49 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=13/> blob data = 'privateMethod'
+// CHECK-3-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=5/> blob data = 'Class'
+// CHECK-3-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-3-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-3-NEXT: <Location abbrevid=7 op0=42 op1=4/> blob data = '{{.*}}'
+// CHECK-3-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=5/> blob data = 'Class'
+// CHECK-3-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-3-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-3-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-3-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: </TypeBlock>
+// CHECK-3-NEXT: </FunctionBlock>
+// CHECK-3-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/85427901413EC77C961019EBB3ADEF7B0BAAFE78.bc | FileCheck %s --check-prefix CHECK-4
+// CHECK-4: <BLOCKINFO_BLOCK/>
+// CHECK-4-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-4-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-4-NEXT: </VersionBlock>
+// CHECK-4-NEXT: <RecordBlock NumWords=103 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-4-NEXT: <ReferenceBlock NumWords=15 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=22/> blob data = 'functionWithInnerClass'
+// CHECK-4-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-4-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: <DefLocation abbrevid=6 op0=15 op1=4/> blob data = '{{.*}}'
+// CHECK-4-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-4-NEXT: <FunctionBlock NumWords=69 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=17/> blob data = 'innerPublicMethod'
+// CHECK-4-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-4-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-4-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: <ReferenceBlock NumWords=15 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=22/> blob data = 'functionWithInnerClass'
+// CHECK-4-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-4-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-4-NEXT: <DefLocation abbrevid=6 op0=17 op1=4/> blob data = '{{.*}}'
+// CHECK-4-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-4-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-4-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-4-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-4-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: </TypeBlock>
+// CHECK-4-NEXT: </FunctionBlock>
+// CHECK-4-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/0000000000000000000000000000000000000000.bc | FileCheck %s --check-prefix CHECK-5
+// CHECK-5: <BLOCKINFO_BLOCK/>
+// CHECK-5-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-5-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-5-NEXT: </VersionBlock>
+// CHECK-5-NEXT: <NamespaceBlock NumWords=218 BlockCodeSize=4>
+// CHECK-5-NEXT: <FunctionBlock NumWords=31 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=8/> blob data = 'function'
+// CHECK-5-NEXT: <Location abbrevid=7 op0=10 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: </TypeBlock>
+// CHECK-5-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: <Name abbrevid=4 op0=1/> blob data = 'x'
+// CHECK-5-NEXT: </FieldTypeBlock>
+// CHECK-5-NEXT: </FunctionBlock>
+// CHECK-5-NEXT: <FunctionBlock NumWords=33 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=15/> blob data = 'inlinedFunction'
+// CHECK-5-NEXT: <Location abbrevid=7 op0=12 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: </TypeBlock>
+// CHECK-5-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: <Name abbrevid=4 op0=1/> blob data = 'x'
+// CHECK-5-NEXT: </FieldTypeBlock>
+// CHECK-5-NEXT: </FunctionBlock>
+// CHECK-5-NEXT: <FunctionBlock NumWords=35 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=22/> blob data = 'functionWithInnerClass'
+// CHECK-5-NEXT: <DefLocation abbrevid=6 op0=14 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: </TypeBlock>
+// CHECK-5-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: <Name abbrevid=4 op0=1/> blob data = 'x'
+// CHECK-5-NEXT: </FieldTypeBlock>
+// CHECK-5-NEXT: </FunctionBlock>
+// CHECK-5-NEXT: <FunctionBlock NumWords=37 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=29/> blob data = 'inlinedFunctionWithInnerClass'
+// CHECK-5-NEXT: <DefLocation abbrevid=6 op0=23 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: </TypeBlock>
+// CHECK-5-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: <Name abbrevid=4 op0=1/> blob data = 'x'
+// CHECK-5-NEXT: </FieldTypeBlock>
+// CHECK-5-NEXT: </FunctionBlock>
+// CHECK-5-NEXT: <FunctionBlock NumWords=33 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=14/> blob data = 'staticFunction'
+// CHECK-5-NEXT: <Location abbrevid=7 op0=66 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: </TypeBlock>
+// CHECK-5-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: <Name abbrevid=4 op0=1/> blob data = 'x'
+// CHECK-5-NEXT: </FieldTypeBlock>
+// CHECK-5-NEXT: </FunctionBlock>
+// CHECK-5-NEXT: <FunctionBlock NumWords=36 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=28/> blob data = 'staticFunctionWithInnerClass'
+// CHECK-5-NEXT: <DefLocation abbrevid=6 op0=68 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: </TypeBlock>
+// CHECK-5-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: <Name abbrevid=4 op0=1/> blob data = 'x'
+// CHECK-5-NEXT: </FieldTypeBlock>
+// CHECK-5-NEXT: </FunctionBlock>
+// CHECK-5-NEXT: </NamespaceBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/4712C5FA37B298A25501D1033C619B65B0ECC449.bc | FileCheck %s --check-prefix CHECK-6
+// CHECK-6: <BLOCKINFO_BLOCK/>
+// CHECK-6-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-6-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-6-NEXT: </VersionBlock>
+// CHECK-6-NEXT: <RecordBlock NumWords=270 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=10/> blob data = 'NamedClass'
+// CHECK-6-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=5/> blob data = 'named'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <DefLocation abbrevid=6 op0=47 op1=4/> blob data = '{{.*}}'
+// CHECK-6-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-6-NEXT: <MemberTypeBlock NumWords=11 BlockCodeSize=4>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-6-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <Name abbrevid=4 op0=16/> blob data = 'namedPublicField'
+// CHECK-6-NEXT: <Access abbrevid=5 op0=3/>
+// CHECK-6-NEXT: </MemberTypeBlock>
+// CHECK-6-NEXT: <MemberTypeBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-6-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <Name abbrevid=4 op0=19/> blob data = 'namedProtectedField'
+// CHECK-6-NEXT: <Access abbrevid=5 op0=1/>
+// CHECK-6-NEXT: </MemberTypeBlock>
+// CHECK-6-NEXT: <MemberTypeBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-6-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <Name abbrevid=4 op0=17/> blob data = 'namedPrivateField'
+// CHECK-6-NEXT: <Access abbrevid=5 op0=2/>
+// CHECK-6-NEXT: </MemberTypeBlock>
+// CHECK-6-NEXT: <FunctionBlock NumWords=65 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=17/> blob data = 'namedPublicMethod'
+// CHECK-6-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=10/> blob data = 'NamedClass'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=5/> blob data = 'named'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-6-NEXT: <Location abbrevid=7 op0=49 op1=4/> blob data = '{{.*}}'
+// CHECK-6-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=10/> blob data = 'NamedClass'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-6-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: </TypeBlock>
+// CHECK-6-NEXT: </FunctionBlock>
+// CHECK-6-NEXT: <FunctionBlock NumWords=65 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=20/> blob data = 'namedProtectedMethod'
+// CHECK-6-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=10/> blob data = 'NamedClass'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=5/> blob data = 'named'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-6-NEXT: <Location abbrevid=7 op0=53 op1=4/> blob data = '{{.*}}'
+// CHECK-6-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=10/> blob data = 'NamedClass'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-6-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: </TypeBlock>
+// CHECK-6-NEXT: </FunctionBlock>
+// CHECK-6-NEXT: <FunctionBlock NumWords=65 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=18/> blob data = 'namedPrivateMethod'
+// CHECK-6-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=10/> blob data = 'NamedClass'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=5/> blob data = 'named'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-6-NEXT: <Location abbrevid=7 op0=57 op1=4/> blob data = '{{.*}}'
+// CHECK-6-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=10/> blob data = 'NamedClass'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-6-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: </TypeBlock>
+// CHECK-6-NEXT: </FunctionBlock>
+// CHECK-6-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/6E8FB72A89761E77020BFCEE9A9A6E64B15CC2A9.bc | FileCheck %s --check-prefix CHECK-7
+// CHECK-7: <BLOCKINFO_BLOCK/>
+// CHECK-7-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-7-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-7-NEXT: </VersionBlock>
+// CHECK-7-NEXT: <RecordBlock NumWords=252 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=9/> blob data = 'AnonClass'
+// CHECK-7-NEXT: <ReferenceBlock NumWords=7 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <DefLocation abbrevid=6 op0=78 op1=4/> blob data = '{{.*}}'
+// CHECK-7-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-7-NEXT: <MemberTypeBlock NumWords=11 BlockCodeSize=4>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-7-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <Name abbrevid=4 op0=15/> blob data = 'anonPublicField'
+// CHECK-7-NEXT: <Access abbrevid=5 op0=3/>
+// CHECK-7-NEXT: </MemberTypeBlock>
+// CHECK-7-NEXT: <MemberTypeBlock NumWords=12 BlockCodeSize=4>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-7-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <Name abbrevid=4 op0=18/> blob data = 'anonProtectedField'
+// CHECK-7-NEXT: <Access abbrevid=5 op0=1/>
+// CHECK-7-NEXT: </MemberTypeBlock>
+// CHECK-7-NEXT: <MemberTypeBlock NumWords=11 BlockCodeSize=4>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-7-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <Name abbrevid=4 op0=16/> blob data = 'anonPrivateField'
+// CHECK-7-NEXT: <Access abbrevid=5 op0=2/>
+// CHECK-7-NEXT: </MemberTypeBlock>
+// CHECK-7-NEXT: <FunctionBlock NumWords=60 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=16/> blob data = 'anonPublicMethod'
+// CHECK-7-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=9/> blob data = 'AnonClass'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=7 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-7-NEXT: <Location abbrevid=7 op0=80 op1=4/> blob data = '{{.*}}'
+// CHECK-7-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=9/> blob data = 'AnonClass'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-7-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: </TypeBlock>
+// CHECK-7-NEXT: </FunctionBlock>
+// CHECK-7-NEXT: <FunctionBlock NumWords=61 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=19/> blob data = 'anonProtectedMethod'
+// CHECK-7-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=9/> blob data = 'AnonClass'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=7 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-7-NEXT: <Location abbrevid=7 op0=84 op1=4/> blob data = '{{.*}}'
+// CHECK-7-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=9/> blob data = 'AnonClass'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-7-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: </TypeBlock>
+// CHECK-7-NEXT: </FunctionBlock>
+// CHECK-7-NEXT: <FunctionBlock NumWords=61 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=17/> blob data = 'anonPrivateMethod'
+// CHECK-7-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=9/> blob data = 'AnonClass'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=7 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-7-NEXT: <Location abbrevid=7 op0=88 op1=4/> blob data = '{{.*}}'
+// CHECK-7-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=9/> blob data = 'AnonClass'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-7-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: </TypeBlock>
+// CHECK-7-NEXT: </FunctionBlock>
+// CHECK-7-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/83CC52D32583E0771710A7742DE81C839E953AC8.bc | FileCheck %s --check-prefix CHECK-8
+// CHECK-8: <BLOCKINFO_BLOCK/>
+// CHECK-8-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-8-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-8-NEXT: </VersionBlock>
+// CHECK-8-NEXT: <NamespaceBlock NumWords=109 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <FunctionBlock NumWords=31 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=12/> blob data = 'anonFunction'
+// CHECK-8-NEXT: <ReferenceBlock NumWords=7 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-8-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: <Location abbrevid=7 op0=92 op1=4/> blob data = '{{.*}}'
+// CHECK-8-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-8-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-8-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: </TypeBlock>
+// CHECK-8-NEXT: </FunctionBlock>
+// CHECK-8-NEXT: <FunctionBlock NumWords=33 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=18/> blob data = 'anonStaticFunction'
+// CHECK-8-NEXT: <ReferenceBlock NumWords=7 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-8-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: <Location abbrevid=7 op0=93 op1=4/> blob data = '{{.*}}'
+// CHECK-8-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-8-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-8-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: </TypeBlock>
+// CHECK-8-NEXT: </FunctionBlock>
+// CHECK-8-NEXT: <FunctionBlock NumWords=33 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=18/> blob data = 'anonInlineFunction'
+// CHECK-8-NEXT: <ReferenceBlock NumWords=7 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-8-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: <Location abbrevid=7 op0=94 op1=4/> blob data = '{{.*}}'
+// CHECK-8-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-8-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-8-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: </TypeBlock>
+// CHECK-8-NEXT: </FunctionBlock>
+// CHECK-8-NEXT: </NamespaceBlock>
diff --git a/test/clang-doc/bc-module.cpp b/test/clang-doc/bc-module.cpp
new file mode 100644
index 00000000..101d8da8
--- /dev/null
+++ b/test/clang-doc/bc-module.cpp
@@ -0,0 +1,87 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+export module M;
+
+int moduleFunction(int x); // ModuleLinkage
+
+static int staticModuleFunction(int x); // ModuleInternalLinkage
+
+export double exportedModuleFunction(double y, int z); // ExternalLinkage
+
+// RUN: clang-doc --dump-intermediate --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/0000000000000000000000000000000000000000.bc | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: <BLOCKINFO_BLOCK/>
+// CHECK-0-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-0-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-0-NEXT: </VersionBlock>
+// CHECK-0-NEXT: <NamespaceBlock NumWords=121 BlockCodeSize=4>
+// CHECK-0-NEXT: <FunctionBlock NumWords=33 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=14/> blob data = 'moduleFunction'
+// CHECK-0-NEXT: <Location abbrevid=7 op0=11 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <Name abbrevid=4 op0=1/> blob data = 'x'
+// CHECK-0-NEXT: </FieldTypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: <FunctionBlock NumWords=34 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=20/> blob data = 'staticModuleFunction'
+// CHECK-0-NEXT: <Location abbrevid=7 op0=13 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <Name abbrevid=4 op0=1/> blob data = 'x'
+// CHECK-0-NEXT: </FieldTypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: <FunctionBlock NumWords=47 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=22/> blob data = 'exportedModuleFunction'
+// CHECK-0-NEXT: <Location abbrevid=7 op0=15 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <TypeBlock NumWords=7 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=4 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=6/> blob data = 'double'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: <FieldTypeBlock NumWords=9 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=4 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=6/> blob data = 'double'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <Name abbrevid=4 op0=1/> blob data = 'y'
+// CHECK-0-NEXT: </FieldTypeBlock>
+// CHECK-0-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <Name abbrevid=4 op0=1/> blob data = 'z'
+// CHECK-0-NEXT: </FieldTypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: </NamespaceBlock>
diff --git a/test/clang-doc/bc-namespace.cpp b/test/clang-doc/bc-namespace.cpp
new file mode 100644
index 00000000..79b35bd9
--- /dev/null
+++ b/test/clang-doc/bc-namespace.cpp
@@ -0,0 +1,121 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+namespace A {
+
+void f();
+
+} // namespace A
+
+namespace A {
+
+void f(){};
+
+namespace B {
+
+enum E { X };
+
+E func(int i) { return X; }
+
+} // namespace B
+} // namespace A
+
+// RUN: clang-doc --dump-intermediate --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/8D042EFFC98B373450BC6B5B90A330C25A150E9C.bc | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: <BLOCKINFO_BLOCK/>
+// CHECK-0-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-0-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-0-NEXT: </VersionBlock>
+// CHECK-0-NEXT: <NamespaceBlock NumWords=46 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
+// CHECK-0-NEXT: <FunctionBlock NumWords=35 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'f'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=17 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <Location abbrevid=7 op0=11 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: </NamespaceBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/E21AF79E2A9D02554BA090D10DF39FE273F5CDB5.bc | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: <BLOCKINFO_BLOCK/>
+// CHECK-1-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-1-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-1-NEXT: </VersionBlock>
+// CHECK-1-NEXT: <NamespaceBlock NumWords=119 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'B'
+// CHECK-1-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <FunctionBlock NumWords=56 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=4/> blob data = 'func'
+// CHECK-1-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'B'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <DefLocation abbrevid=6 op0=23 op1=4/> blob data = '{{.*}}'
+// CHECK-1-NEXT: <TypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=5 BlockCodeSize=4>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=12/> blob data = 'enum A::B::E'
+// CHECK-1-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: </TypeBlock>
+// CHECK-1-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-1-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <Name abbrevid=4 op0=1/> blob data = 'i'
+// CHECK-1-NEXT: </FieldTypeBlock>
+// CHECK-1-NEXT: </FunctionBlock>
+// CHECK-1-NEXT: <EnumBlock NumWords=38 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-1-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'B'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <DefLocation abbrevid=6 op0=21 op1=4/> blob data = '{{.*}}'
+// CHECK-1-NEXT: <Member abbrevid=8 op0=1/> blob data = 'X'
+// CHECK-1-NEXT: </EnumBlock>
+// CHECK-1-NEXT: </NamespaceBlock>
diff --git a/test/clang-doc/bc-record.cpp b/test/clang-doc/bc-record.cpp
new file mode 100644
index 00000000..a0e22448
--- /dev/null
+++ b/test/clang-doc/bc-record.cpp
@@ -0,0 +1,293 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// This test requires Linux due to system-dependent USR for the inner class.
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+void H() {
+ class I {};
+}
+
+union A { int X; int Y; };
+
+enum B { X, Y };
+
+enum class Bc { A, B };
+
+struct C { int i; };
+
+class D {};
+
+class E {
+public:
+ E() {}
+ ~E() {}
+
+protected:
+ void ProtectedMethod();
+};
+
+void E::ProtectedMethod() {}
+
+class F : virtual private D, public E {};
+
+class X {
+ class Y {};
+};
+
+// RUN: clang-doc --dump-intermediate --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/289584A8E0FF4178A794622A547AA622503967A1.bc | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: <BLOCKINFO_BLOCK/>
+// CHECK-0-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-0-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-0-NEXT: </VersionBlock>
+// CHECK-0-NEXT: <RecordBlock NumWords=157 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=25 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-0-NEXT: <FunctionBlock NumWords=44 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=27 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: <FunctionBlock NumWords=44 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=2/> blob data = '~E'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=28 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: <FunctionBlock NumWords=50 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=15/> blob data = 'ProtectedMethod'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=34 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <Location abbrevid=7 op0=31 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/3FB542274573CAEAD54CEBFFCAEE3D77FB9713D8.bc | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: <BLOCKINFO_BLOCK/>
+// CHECK-1-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-1-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-1-NEXT: </VersionBlock>
+// CHECK-1-NEXT: <RecordBlock NumWords=24 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'I'
+// CHECK-1-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'H'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <DefLocation abbrevid=6 op0=12 op1=4/> blob data = '{{.*}}'
+// CHECK-1-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-1-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/CA7C7935730B5EACD25F080E9C83FA087CCDC75E.bc | FileCheck %s --check-prefix CHECK-2
+// CHECK-2: <BLOCKINFO_BLOCK/>
+// CHECK-2-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-2-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-2-NEXT: </VersionBlock>
+// CHECK-2-NEXT: <RecordBlock NumWords=12 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=1/> blob data = 'X'
+// CHECK-2-NEXT: <DefLocation abbrevid=6 op0=38 op1=4/> blob data = '{{.*}}'
+// CHECK-2-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-2-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/06B5F6A19BA9F6A832E127C9968282B94619B210.bc | FileCheck %s --check-prefix CHECK-3
+// CHECK-3: <BLOCKINFO_BLOCK/>
+// CHECK-3-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-3-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-3-NEXT: </VersionBlock>
+// CHECK-3-NEXT: <RecordBlock NumWords=22 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=1/> blob data = 'C'
+// CHECK-3-NEXT: <DefLocation abbrevid=6 op0=21 op1=4/> blob data = '{{.*}}'
+// CHECK-3-NEXT: <MemberTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-3-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-3-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <Name abbrevid=4 op0=1/> blob data = 'i'
+// CHECK-3-NEXT: <Access abbrevid=5 op0=3/>
+// CHECK-3-NEXT: </MemberTypeBlock>
+// CHECK-3-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/641AB4A3D36399954ACDE29C7A8833032BF40472.bc | FileCheck %s --check-prefix CHECK-4
+// CHECK-4: <BLOCKINFO_BLOCK/>
+// CHECK-4-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-4-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-4-NEXT: </VersionBlock>
+// CHECK-4-NEXT: <RecordBlock NumWords=24 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=1/> blob data = 'Y'
+// CHECK-4-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=1/> blob data = 'X'
+// CHECK-4-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-4-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: <DefLocation abbrevid=6 op0=39 op1=4/> blob data = '{{.*}}'
+// CHECK-4-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-4-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/0000000000000000000000000000000000000000.bc | FileCheck %s --check-prefix CHECK-5
+// CHECK-5: <BLOCKINFO_BLOCK/>
+// CHECK-5-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-5-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-5-NEXT: </VersionBlock>
+// CHECK-5-NEXT: <NamespaceBlock NumWords=59 BlockCodeSize=4>
+// CHECK-5-NEXT: <FunctionBlock NumWords=20 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=1/> blob data = 'H'
+// CHECK-5-NEXT: <DefLocation abbrevid=6 op0=11 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: </TypeBlock>
+// CHECK-5-NEXT: </FunctionBlock>
+// CHECK-5-NEXT: <EnumBlock NumWords=16 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=1/> blob data = 'B'
+// CHECK-5-NEXT: <DefLocation abbrevid=6 op0=17 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <Member abbrevid=8 op0=1/> blob data = 'X'
+// CHECK-5-NEXT: <Member abbrevid=8 op0=1/> blob data = 'Y'
+// CHECK-5-NEXT: </EnumBlock>
+// CHECK-5-NEXT: <EnumBlock NumWords=16 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=2/> blob data = 'Bc'
+// CHECK-5-NEXT: <DefLocation abbrevid=6 op0=19 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <Scoped abbrevid=9 op0=1/>
+// CHECK-5-NEXT: <Member abbrevid=8 op0=1/> blob data = 'A'
+// CHECK-5-NEXT: <Member abbrevid=8 op0=1/> blob data = 'B'
+// CHECK-5-NEXT: </EnumBlock>
+// CHECK-5-NEXT: </NamespaceBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/0921737541208B8FA9BB42B60F78AC1D779AA054.bc | FileCheck %s --check-prefix CHECK-6
+// CHECK-6: <BLOCKINFO_BLOCK/>
+// CHECK-6-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-6-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-6-NEXT: </VersionBlock>
+// CHECK-6-NEXT: <RecordBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=1/> blob data = 'D'
+// CHECK-6-NEXT: <DefLocation abbrevid=6 op0=23 op1=4/> blob data = '{{.*}}'
+// CHECK-6-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-6-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/E3B54702FABFF4037025BA194FC27C47006330B5.bc | FileCheck %s --check-prefix CHECK-7
+// CHECK-7: <BLOCKINFO_BLOCK/>
+// CHECK-7-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-7-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-7-NEXT: </VersionBlock>
+// CHECK-7-NEXT: <RecordBlock NumWords=37 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=1/> blob data = 'F'
+// CHECK-7-NEXT: <DefLocation abbrevid=6 op0=36 op1=4/> blob data = '{{.*}}'
+// CHECK-7-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=1/> blob data = 'D'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=3/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/ACE81AFA6627B4CEF2B456FB6E1252925674AF7E.bc | FileCheck %s --check-prefix CHECK-8
+// CHECK-8: <BLOCKINFO_BLOCK/>
+// CHECK-8-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-8-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-8-NEXT: </VersionBlock>
+// CHECK-8-NEXT: <RecordBlock NumWords=33 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
+// CHECK-8-NEXT: <DefLocation abbrevid=6 op0=15 op1=4/> blob data = '{{.*}}'
+// CHECK-8-NEXT: <TagType abbrevid=8 op0=2/>
+// CHECK-8-NEXT: <MemberTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-8-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-8-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: <Name abbrevid=4 op0=1/> blob data = 'X'
+// CHECK-8-NEXT: <Access abbrevid=5 op0=3/>
+// CHECK-8-NEXT: </MemberTypeBlock>
+// CHECK-8-NEXT: <MemberTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-8-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-8-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: <Name abbrevid=4 op0=1/> blob data = 'Y'
+// CHECK-8-NEXT: <Access abbrevid=5 op0=3/>
+// CHECK-8-NEXT: </MemberTypeBlock>
+// CHECK-8-NEXT: </RecordBlock>
diff --git a/test/clang-doc/mapper-class-in-class.cpp b/test/clang-doc/mapper-class-in-class.cpp
deleted file mode 100644
index 6d8a75be..00000000
--- a/test/clang-doc/mapper-class-in-class.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-// RUN: rm -rf %t
-// RUN: mkdir %t
-// RUN: echo "" > %t/compile_flags.txt
-// RUN: cp "%s" "%t/test.cpp"
-// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
-// RUN: llvm-bcanalyzer %t/docs/bc/641AB4A3D36399954ACDE29C7A8833032BF40472.bc --dump | FileCheck %s --check-prefix CHECK-X-Y
-// RUN: llvm-bcanalyzer %t/docs/bc/CA7C7935730B5EACD25F080E9C83FA087CCDC75E.bc --dump | FileCheck %s --check-prefix CHECK-X
-
-class X {
- class Y {};
-};
-
-// CHECK-X: <BLOCKINFO_BLOCK/>
-// CHECK-X-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-X-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-X-NEXT: </VersionBlock>
-// CHECK-X-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-X-NEXT: <USR abbrevid=4 op0=20 op1=202 op2=124 op3=121 op4=53 op5=115 op6=11 op7=94 op8=172 op9=210 op10=95 op11=8 op12=14 op13=156 op14=131 op15=250 op16=8 op17=124 op18=205 op19=199 op20=94/>
- // CHECK-X-NEXT: <Name abbrevid=5 op0=1/> blob data = 'X'
- // CHECK-X-NEXT: <DefLocation abbrevid=6 op0=9 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-X-NEXT: <TagType abbrevid=8 op0=3/>
-// CHECK-X-NEXT: </RecordBlock>
-
-
-// CHECK-X-Y: <BLOCKINFO_BLOCK/>
-// CHECK-X-Y-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-X-Y-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-X-Y-NEXT: </VersionBlock>
-// CHECK-X-Y-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-X-Y-NEXT: <USR abbrevid=4 op0=20 op1=100 op2=26 op3=180 op4=163 op5=211 op6=99 op7=153 op8=149 op9=74 op10=205 op11=226 op12=156 op13=122 op14=136 op15=51 op16=3 op17=43 op18=244 op19=4 op20=114/>
- // CHECK-X-Y-NEXT: <Name abbrevid=5 op0=1/> blob data = 'Y'
- // CHECK-X-Y-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
- // CHECK-X-Y-NEXT: <USR abbrevid=4 op0=20 op1=202 op2=124 op3=121 op4=53 op5=115 op6=11 op7=94 op8=172 op9=210 op10=95 op11=8 op12=14 op13=156 op14=131 op15=250 op16=8 op17=124 op18=205 op19=199 op20=94/>
- // CHECK-X-Y-NEXT: <Name abbrevid=5 op0=1/> blob data = 'X'
- // CHECK-X-Y-NEXT: <RefType abbrevid=6 op0=2/>
- // CHECK-X-Y-NEXT: <Field abbrevid=7 op0=1/>
- // CHECK-X-Y-NEXT: </ReferenceBlock>
- // CHECK-X-Y-NEXT: <DefLocation abbrevid=6 op0=10 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-X-Y-NEXT: <TagType abbrevid=8 op0=3/>
-// CHECK-X-Y-NEXT: </RecordBlock>
diff --git a/test/clang-doc/mapper-class-in-function.cpp b/test/clang-doc/mapper-class-in-function.cpp
deleted file mode 100644
index aeac72b9..00000000
--- a/test/clang-doc/mapper-class-in-function.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-// This test requires Linux due to the system-dependent USR for the
-// inner class.
-// REQUIRES: system-linux
-// RUN: rm -rf %t
-// RUN: mkdir %t
-// RUN: echo "" > %t/compile_flags.txt
-// RUN: cp "%s" "%t/test.cpp"
-// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
-// RUN: llvm-bcanalyzer %t/docs/bc/B6AC4C5C9F2EA3F2B3ECE1A33D349F4EE502B24E.bc --dump | FileCheck %s --check-prefix CHECK-H
-// RUN: llvm-bcanalyzer %t/docs/bc/01A95F3F73F53281B3E50109A577FD2493159365.bc --dump | FileCheck %s --check-prefix CHECK-H-I
-
-void H() {
- class I {};
-}
-
-// CHECK-H: <BLOCKINFO_BLOCK/>
-// CHECK-H-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-H-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-H-NEXT: </VersionBlock>
-// CHECK-H-NEXT: <FunctionBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-H-NEXT: <USR abbrevid=4 op0=20 op1=182 op2=172 op3=76 op4=92 op5=159 op6=46 op7=163 op8=242 op9=179 op10=236 op11=225 op12=163 op13=61 op14=52 op15=159 op16=78 op17=229 op18=2 op19=178 op20=78/>
- // CHECK-H-NEXT: <Name abbrevid=5 op0=1/> blob data = 'H'
- // CHECK-H-NEXT: <DefLocation abbrevid=6 op0=12 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-H-NEXT: <TypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-H-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-H-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
- // CHECK-H-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-H-NEXT: </ReferenceBlock>
- // CHECK-H-NEXT: </TypeBlock>
-// CHECK-H-NEXT: </FunctionBlock>
-
-// CHECK-H-I: <BLOCKINFO_BLOCK/>
-// CHECK-H-I-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-H-I-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-H-I-NEXT: </VersionBlock>
-// CHECK-H-I-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-H-I-NEXT: <USR abbrevid=4 op0=20 op1=1 op2=169 op3=95 op4=63 op5=115 op6=245 op7=50 op8=129 op9=179 op10=229 op11=1 op12=9 op13=165 op14=119 op15=253 op16=36 op17=147 op18=21 op19=147 op20=101/>
- // CHECK-H-I-NEXT: <Name abbrevid=5 op0=1/> blob data = 'I'
- // CHECK-H-I-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-H-I-NEXT: <USR abbrevid=4 op0=20 op1=182 op2=172 op3=76 op4=92 op5=159 op6=46 op7=163 op8=242 op9=179 op10=236 op11=225 op12=163 op13=61 op14=52 op15=159 op16=78 op17=229 op18=2 op19=178 op20=78/>
- // CHECK-H-I-NEXT: <Name abbrevid=5 op0=1/> blob data = 'H'
- // CHECK-H-I-NEXT: <RefType abbrevid=6 op0=3/>
- // CHECK-H-I-NEXT: <Field abbrevid=7 op0=1/>
- // CHECK-H-I-NEXT: </ReferenceBlock>
- // CHECK-H-I-NEXT: <DefLocation abbrevid=6 op0=13 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-H-I-NEXT: <TagType abbrevid=8 op0=3/>
-// CHECK-H-I-NEXT: </RecordBlock>
-
-
diff --git a/test/clang-doc/mapper-class.cpp b/test/clang-doc/mapper-class.cpp
deleted file mode 100644
index c8aa1583..00000000
--- a/test/clang-doc/mapper-class.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
-// RUN: rm -rf %t
-// RUN: mkdir %t
-// RUN: echo "" > %t/compile_flags.txt
-// RUN: cp "%s" "%t/test.cpp"
-// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
-// RUN: llvm-bcanalyzer %t/docs/bc/289584A8E0FF4178A794622A547AA622503967A1.bc --dump | FileCheck %s
-
-class E {};
-
-// CHECK: <BLOCKINFO_BLOCK/>
-// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-NEXT: </VersionBlock>
-// CHECK-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=40 op2=149 op3=132 op4=168 op5=224 op6=255 op7=65 op8=120 op9=167 op10=148 op11=98 op12=42 op13=84 op14=122 op15=166 op16=34 op17=80 op18=57 op19=103 op20=161/>
- // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
- // CHECK-NEXT: <DefLocation abbrevid=6 op0=8 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-NEXT: <TagType abbrevid=8 op0=3/>
-// CHECK-NEXT: </RecordBlock>
diff --git a/test/clang-doc/mapper-comment.cpp b/test/clang-doc/mapper-comment.cpp
new file mode 100644
index 00000000..efd3dc54
--- /dev/null
+++ b/test/clang-doc/mapper-comment.cpp
@@ -0,0 +1,74 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+/// \brief Brief description.
+///
+/// Extended description that
+/// continues onto the next line.
+///
+/// <ul class="test">
+/// <li> Testing.
+/// </ul>
+///
+/// \verbatim
+/// The description continues.
+/// \endverbatim
+/// --
+/// \param [out] I is a parameter.
+/// \param J is a parameter.
+/// \return void
+void F(int I, int J);
+
+/// Bonus comment on definition
+void F(int I, int J) {}
+
+// RUN: clang-doc --dump-mapper --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/0000000000000000000000000000000000000000.bc | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: <BLOCKINFO_BLOCK/>
+// CHECK-0-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-0-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-0-NEXT: </VersionBlock>
+// CHECK-0-NEXT: <NamespaceBlock NumWords=73 BlockCodeSize=4>
+// CHECK-0-NEXT: <FunctionBlock NumWords=70 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'F'
+// CHECK-0-NEXT: <CommentBlock NumWords=28 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'FullComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=21 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
+// CHECK-0-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
+// CHECK-0-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
+// CHECK-0-NEXT: <Text abbrevid=5 op0=28/> blob data = ' Bonus comment on definition'
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: </CommentBlock>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=28 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <Name abbrevid=4 op0=1/> blob data = 'I'
+// CHECK-0-NEXT: </FieldTypeBlock>
+// CHECK-0-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <Name abbrevid=4 op0=1/> blob data = 'J'
+// CHECK-0-NEXT: </FieldTypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: </NamespaceBlock>
diff --git a/test/clang-doc/mapper-comments.cpp b/test/clang-doc/mapper-comments.cpp
deleted file mode 100644
index 45c84903..00000000
--- a/test/clang-doc/mapper-comments.cpp
+++ /dev/null
@@ -1,181 +0,0 @@
-// RUN: rm -rf %t
-// RUN: mkdir %t
-// RUN: echo "" > %t/compile_flags.txt
-// RUN: cp "%s" "%t/test.cpp"
-// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
-// RUN: llvm-bcanalyzer %t/docs/bc/7574630614A535710E5A6ABCFFF98BCA2D06A4CA.bc --dump | FileCheck %s
-
-/// \brief Brief description.
-///
-/// Extended description that
-/// continues onto the next line.
-///
-/// <ul> class="test">
-/// <li> Testing.
-/// </ul>
-///
-/// \verbatim
-/// The description continues.
-/// \endverbatim
-///
-/// \param [out] I is a parameter.
-/// \param J is a parameter.
-/// \return int
-int F(int I, int J);
-
-// CHECK: <BLOCKINFO_BLOCK/>
-// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-NEXT: </VersionBlock>
-// CHECK-NEXT: <FunctionBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=117 op2=116 op3=99 op4=6 op5=20 op6=165 op7=53 op8=113 op9=14 op10=90 op11=106 op12=188 op13=255 op14=249 op15=139 op16=202 op17=45 op18=6 op19=164 op20=202/>
- // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'F'
- // CHECK-NEXT: <CommentBlock NumWords=351 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'FullComment'
- // CHECK-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
- // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=31 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'BlockCommandComment'
- // CHECK-NEXT: <Name abbrevid=6 op0=5/> blob data = 'brief'
- // CHECK-NEXT: <CommentBlock NumWords=19 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
- // CHECK-NEXT: <CommentBlock NumWords=11 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: <Text abbrevid=5 op0=19/> blob data = ' Brief description.'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=37 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
- // CHECK-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: <Text abbrevid=5 op0=26/> blob data = ' Extended description that'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=14 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: <Text abbrevid=5 op0=30/> blob data = ' continues onto the next line.'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=83 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
- // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'HTMLStartTagComment'
- // CHECK-NEXT: <Name abbrevid=6 op0=2/> blob data = 'ul'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=10 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: <Text abbrevid=5 op0=14/> blob data = ' class="test">'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'HTMLStartTagComment'
- // CHECK-NEXT: <Name abbrevid=6 op0=2/> blob data = 'li'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: <Text abbrevid=5 op0=9/> blob data = ' Testing.'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=9 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=17/> blob data = 'HTMLEndTagComment'
- // CHECK-NEXT: <Name abbrevid=6 op0=2/> blob data = 'ul'
- // CHECK-NEXT: <SelfClosing abbrevid=10 op0=1/>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
- // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=32 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=20/> blob data = 'VerbatimBlockComment'
- // CHECK-NEXT: <Name abbrevid=6 op0=8/> blob data = 'verbatim'
- // CHECK-NEXT: <CloseName abbrevid=9 op0=11/> blob data = 'endverbatim'
- // CHECK-NEXT: <CommentBlock NumWords=16 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=24/> blob data = 'VerbatimBlockLineComment'
- // CHECK-NEXT: <Text abbrevid=5 op0=27/> blob data = ' The description continues.'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=13 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
- // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=39 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'ParamCommandComment'
- // CHECK-NEXT: <Direction abbrevid=7 op0=5/> blob data = '[out]'
- // CHECK-NEXT: <ParamName abbrevid=8 op0=1/> blob data = 'I'
- // CHECK-NEXT: <Explicit abbrevid=11 op0=1/>
- // CHECK-NEXT: <CommentBlock NumWords=25 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
- // CHECK-NEXT: <CommentBlock NumWords=10 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: <Text abbrevid=5 op0=16/> blob data = ' is a parameter.'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=38 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'ParamCommandComment'
- // CHECK-NEXT: <Direction abbrevid=7 op0=4/> blob data = '[in]'
- // CHECK-NEXT: <ParamName abbrevid=8 op0=1/> blob data = 'J'
- // CHECK-NEXT: <CommentBlock NumWords=25 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
- // CHECK-NEXT: <CommentBlock NumWords=10 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: <Text abbrevid=5 op0=16/> blob data = ' is a parameter.'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=5 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <CommentBlock NumWords=27 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=19/> blob data = 'BlockCommandComment'
- // CHECK-NEXT: <Name abbrevid=6 op0=6/> blob data = 'return'
- // CHECK-NEXT: <CommentBlock NumWords=15 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=16/> blob data = 'ParagraphComment'
- // CHECK-NEXT: <CommentBlock NumWords=7 BlockCodeSize=4>
- // CHECK-NEXT: <Kind abbrevid=4 op0=11/> blob data = 'TextComment'
- // CHECK-NEXT: <Text abbrevid=5 op0=4/> blob data = ' int'
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: </CommentBlock>
- // CHECK-NEXT: <Location abbrevid=7 op0=24 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-NEXT: <TypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
- // CHECK-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-NEXT: </ReferenceBlock>
- // CHECK-NEXT: </TypeBlock>
- // CHECK-NEXT: <FieldTypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
- // CHECK-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-NEXT: </ReferenceBlock>
- // CHECK-NEXT: <Name abbrevid=4 op0=1/> blob data = 'I'
- // CHECK-NEXT: </FieldTypeBlock>
- // CHECK-NEXT: <FieldTypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
- // CHECK-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-NEXT: </ReferenceBlock>
- // CHECK-NEXT: <Name abbrevid=4 op0=1/> blob data = 'J'
- // CHECK-NEXT: </FieldTypeBlock>
-// CHECK-NEXT: </FunctionBlock>
diff --git a/test/clang-doc/mapper-enum.cpp b/test/clang-doc/mapper-enum.cpp
deleted file mode 100644
index 1c75103a..00000000
--- a/test/clang-doc/mapper-enum.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-// RUN: rm -rf %t
-// RUN: mkdir %t
-// RUN: echo "" > %t/compile_flags.txt
-// RUN: cp "%s" "%t/test.cpp"
-// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
-// RUN: llvm-bcanalyzer %t/docs/bc/FC07BD34D5E77782C263FA944447929EA8753740.bc --dump | FileCheck %s --check-prefix CHECK-B
-// RUN: llvm-bcanalyzer %t/docs/bc/020E6C32A700C3170C009FCCD41671EDDBEAF575.bc --dump | FileCheck %s --check-prefix CHECK-C
-
-enum B { X, Y };
-
-// CHECK-B: <BLOCKINFO_BLOCK/>
-// CHECK-B-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-B-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-B-NEXT: </VersionBlock>
-// CHECK-B-NEXT: <EnumBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-B-NEXT: <USR abbrevid=4 op0=20 op1=252 op2=7 op3=189 op4=52 op5=213 op6=231 op7=119 op8=130 op9=194 op10=99 op11=250 op12=148 op13=68 op14=71 op15=146 op16=158 op17=168 op18=117 op19=55 op20=64/>
- // CHECK-B-NEXT: <Name abbrevid=5 op0=1/> blob data = 'B'
- // CHECK-B-NEXT: <DefLocation abbrevid=6 op0=9 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-B-NEXT: <Member abbrevid=8 op0=1/> blob data = 'X'
- // CHECK-B-NEXT: <Member abbrevid=8 op0=1/> blob data = 'Y'
-// CHECK-B-NEXT: </EnumBlock>
-
-enum class C { A, B };
-
-// CHECK-C: <BLOCKINFO_BLOCK/>
-// CHECK-C-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-C-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-C-NEXT: </VersionBlock>
-// CHECK-C-NEXT: <EnumBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-C-NEXT: <USR abbrevid=4 op0=20 op1=2 op2=14 op3=108 op4=50 op5=167 op6=0 op7=195 op8=23 op9=12 op10=0 op11=159 op12=204 op13=212 op14=22 op15=113 op16=237 op17=219 op18=234 op19=245 op20=117/>
- // CHECK-C-NEXT: <Name abbrevid=5 op0=1/> blob data = 'C'
- // CHECK-C-NEXT: <DefLocation abbrevid=6 op0=23 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-C-NEXT: <Scoped abbrevid=9 op0=1/>
- // CHECK-C-NEXT: <Member abbrevid=8 op0=1/> blob data = 'A'
- // CHECK-C-NEXT: <Member abbrevid=8 op0=1/> blob data = 'B'
-// CHECK-C-NEXT: </EnumBlock>
diff --git a/test/clang-doc/mapper-function.cpp b/test/clang-doc/mapper-function.cpp
deleted file mode 100644
index e05c5701..00000000
--- a/test/clang-doc/mapper-function.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-// RUN: rm -rf %t
-// RUN: mkdir %t
-// RUN: echo "" > %t/compile_flags.txt
-// RUN: cp "%s" "%t/test.cpp"
-// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
-// RUN: llvm-bcanalyzer %t/docs/bc/A44B32CC3C087C9AF75DAF50DE193E85E7B2C16B.bc --dump | FileCheck %s
-
-int F(int param) { return param; }
-
-// CHECK: <BLOCKINFO_BLOCK/>
-// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-NEXT: </VersionBlock>
-// CHECK-NEXT: <FunctionBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=164 op2=75 op3=50 op4=204 op5=60 op6=8 op7=124 op8=154 op9=247 op10=93 op11=175 op12=80 op13=222 op14=25 op15=62 op16=133 op17=231 op18=178 op19=193 op20=107/>
- // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'F'
- // CHECK-NEXT: <DefLocation abbrevid=6 op0=8 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-NEXT: <TypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
- // CHECK-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-NEXT: </ReferenceBlock>
- // CHECK-NEXT: </TypeBlock>
- // CHECK-NEXT: <FieldTypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
- // CHECK-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-NEXT: </ReferenceBlock>
- // CHECK-NEXT: <Name abbrevid=4 op0=5/> blob data = 'param'
- // CHECK-NEXT: </FieldTypeBlock>
-// CHECK-NEXT: </FunctionBlock>
diff --git a/test/clang-doc/mapper-linkage.cpp b/test/clang-doc/mapper-linkage.cpp
new file mode 100644
index 00000000..5b4fe7df
--- /dev/null
+++ b/test/clang-doc/mapper-linkage.cpp
@@ -0,0 +1,402 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+void function(int x);
+
+inline int inlinedFunction(int x);
+
+int functionWithInnerClass(int x) {
+ class InnerClass { //NoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+inline int inlinedFunctionWithInnerClass(int x) {
+ class InnerClass { //VisibleNoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+class Class {
+public:
+ void publicMethod();
+ int publicField;
+
+protected:
+ void protectedMethod();
+ int protectedField;
+
+private:
+ void privateMethod();
+ int privateField;
+};
+
+namespace named {
+class NamedClass {
+public:
+ void namedPublicMethod();
+ int namedPublicField;
+
+protected:
+ void namedProtectedMethod();
+ int namedProtectedField;
+
+private:
+ void namedPrivateMethod();
+ int namedPrivateField;
+};
+
+void namedFunction();
+static void namedStaticFunction();
+inline void namedInlineFunction();
+} // namespace named
+
+static void staticFunction(int x); //Internal Linkage
+
+static int staticFunctionWithInnerClass(int x) {
+ class InnerClass { //NoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+namespace {
+class AnonClass {
+public:
+ void anonPublicMethod();
+ int anonPublicField;
+
+protected:
+ void anonProtectedMethod();
+ int anonProtectedField;
+
+private:
+ void anonPrivateMethod();
+ int anonPrivateField;
+};
+
+void anonFunction();
+static void anonStaticFunction();
+inline void anonInlineFunction();
+} // namespace
+
+// RUN: clang-doc --dump-mapper --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/C9B3B71ACDD84C5BB320D34E97677715CDB3EA32.bc | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: <BLOCKINFO_BLOCK/>
+// CHECK-0-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-0-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-0-NEXT: </VersionBlock>
+// CHECK-0-NEXT: <RecordBlock NumWords=79 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <FunctionBlock NumWords=71 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=17/> blob data = 'innerPublicMethod'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=17 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=29/> blob data = 'inlinedFunctionWithInnerClass'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=26 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/8960B5C9247D6F5C532756E53A1AD1240FA2146F.bc | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: <BLOCKINFO_BLOCK/>
+// CHECK-1-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-1-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-1-NEXT: </VersionBlock>
+// CHECK-1-NEXT: <NamespaceBlock NumWords=45 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <FunctionBlock NumWords=37 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=19/> blob data = 'namedInlineFunction'
+// CHECK-1-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=5/> blob data = 'named'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <Location abbrevid=7 op0=63 op1=4/> blob data = '{{.*}}'
+// CHECK-1-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-1-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: </TypeBlock>
+// CHECK-1-NEXT: </FunctionBlock>
+// CHECK-1-NEXT: </NamespaceBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/74A031CBE68C101F3E83F60ED17F20C11EC19D48.bc | FileCheck %s --check-prefix CHECK-2
+// CHECK-2: <BLOCKINFO_BLOCK/>
+// CHECK-2-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-2-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-2-NEXT: </VersionBlock>
+// CHECK-2-NEXT: <RecordBlock NumWords=78 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <FunctionBlock NumWords=70 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=17/> blob data = 'innerPublicMethod'
+// CHECK-2-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-2-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-2-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-2-NEXT: </ReferenceBlock>
+// CHECK-2-NEXT: <ReferenceBlock NumWords=16 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=28/> blob data = 'staticFunctionWithInnerClass'
+// CHECK-2-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-2-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-2-NEXT: </ReferenceBlock>
+// CHECK-2-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-2-NEXT: <DefLocation abbrevid=6 op0=71 op1=4/> blob data = '{{.*}}'
+// CHECK-2-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-2-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-2-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-2-NEXT: </ReferenceBlock>
+// CHECK-2-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-2-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-2-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-2-NEXT: </ReferenceBlock>
+// CHECK-2-NEXT: </TypeBlock>
+// CHECK-2-NEXT: </FunctionBlock>
+// CHECK-2-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/7CDD73DCD6CD72F7E5CE25502810A182C66C4B45.bc | FileCheck %s --check-prefix CHECK-3
+// CHECK-3: <BLOCKINFO_BLOCK/>
+// CHECK-3-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-3-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-3-NEXT: </VersionBlock>
+// CHECK-3-NEXT: <RecordBlock NumWords=57 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <FunctionBlock NumWords=49 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=13/> blob data = 'privateMethod'
+// CHECK-3-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=5/> blob data = 'Class'
+// CHECK-3-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-3-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-3-NEXT: <Location abbrevid=7 op0=42 op1=4/> blob data = '{{.*}}'
+// CHECK-3-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=5/> blob data = 'Class'
+// CHECK-3-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-3-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-3-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-3-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: </TypeBlock>
+// CHECK-3-NEXT: </FunctionBlock>
+// CHECK-3-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/85427901413EC77C961019EBB3ADEF7B0BAAFE78.bc | FileCheck %s --check-prefix CHECK-4
+// CHECK-4: <BLOCKINFO_BLOCK/>
+// CHECK-4-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-4-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-4-NEXT: </VersionBlock>
+// CHECK-4-NEXT: <RecordBlock NumWords=77 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <FunctionBlock NumWords=69 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=17/> blob data = 'innerPublicMethod'
+// CHECK-4-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-4-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-4-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: <ReferenceBlock NumWords=15 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=22/> blob data = 'functionWithInnerClass'
+// CHECK-4-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-4-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-4-NEXT: <DefLocation abbrevid=6 op0=17 op1=4/> blob data = '{{.*}}'
+// CHECK-4-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=10/> blob data = 'InnerClass'
+// CHECK-4-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-4-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-4-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-4-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: </TypeBlock>
+// CHECK-4-NEXT: </FunctionBlock>
+// CHECK-4-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/0000000000000000000000000000000000000000.bc | FileCheck %s --check-prefix CHECK-5
+// CHECK-5: <BLOCKINFO_BLOCK/>
+// CHECK-5-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-5-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-5-NEXT: </VersionBlock>
+// CHECK-5-NEXT: <NamespaceBlock NumWords=39 BlockCodeSize=4>
+// CHECK-5-NEXT: <FunctionBlock NumWords=36 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=28/> blob data = 'staticFunctionWithInnerClass'
+// CHECK-5-NEXT: <DefLocation abbrevid=6 op0=68 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: </TypeBlock>
+// CHECK-5-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-5-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-5-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-5-NEXT: </ReferenceBlock>
+// CHECK-5-NEXT: <Name abbrevid=4 op0=1/> blob data = 'x'
+// CHECK-5-NEXT: </FieldTypeBlock>
+// CHECK-5-NEXT: </FunctionBlock>
+// CHECK-5-NEXT: </NamespaceBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/4712C5FA37B298A25501D1033C619B65B0ECC449.bc | FileCheck %s --check-prefix CHECK-6
+// CHECK-6: <BLOCKINFO_BLOCK/>
+// CHECK-6-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-6-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-6-NEXT: </VersionBlock>
+// CHECK-6-NEXT: <RecordBlock NumWords=73 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <FunctionBlock NumWords=65 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=18/> blob data = 'namedPrivateMethod'
+// CHECK-6-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=10/> blob data = 'NamedClass'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=11 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=5/> blob data = 'named'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-6-NEXT: <Location abbrevid=7 op0=57 op1=4/> blob data = '{{.*}}'
+// CHECK-6-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=10/> blob data = 'NamedClass'
+// CHECK-6-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-6-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-6-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-6-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-6-NEXT: </ReferenceBlock>
+// CHECK-6-NEXT: </TypeBlock>
+// CHECK-6-NEXT: </FunctionBlock>
+// CHECK-6-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/6E8FB72A89761E77020BFCEE9A9A6E64B15CC2A9.bc | FileCheck %s --check-prefix CHECK-7
+// CHECK-7: <BLOCKINFO_BLOCK/>
+// CHECK-7-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-7-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-7-NEXT: </VersionBlock>
+// CHECK-7-NEXT: <RecordBlock NumWords=69 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <FunctionBlock NumWords=61 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=17/> blob data = 'anonPrivateMethod'
+// CHECK-7-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=9/> blob data = 'AnonClass'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=7 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-7-NEXT: <Location abbrevid=7 op0=88 op1=4/> blob data = '{{.*}}'
+// CHECK-7-NEXT: <ReferenceBlock NumWords=12 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=9/> blob data = 'AnonClass'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-7-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: </TypeBlock>
+// CHECK-7-NEXT: </FunctionBlock>
+// CHECK-7-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/83CC52D32583E0771710A7742DE81C839E953AC8.bc | FileCheck %s --check-prefix CHECK-8
+// CHECK-8: <BLOCKINFO_BLOCK/>
+// CHECK-8-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-8-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-8-NEXT: </VersionBlock>
+// CHECK-8-NEXT: <NamespaceBlock NumWords=41 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <FunctionBlock NumWords=33 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=18/> blob data = 'anonInlineFunction'
+// CHECK-8-NEXT: <ReferenceBlock NumWords=7 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-8-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: <Location abbrevid=7 op0=94 op1=4/> blob data = '{{.*}}'
+// CHECK-8-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-8-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-8-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: </TypeBlock>
+// CHECK-8-NEXT: </FunctionBlock>
+// CHECK-8-NEXT: </NamespaceBlock>
diff --git a/test/clang-doc/mapper-method.cpp b/test/clang-doc/mapper-method.cpp
deleted file mode 100644
index a9d3a072..00000000
--- a/test/clang-doc/mapper-method.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-// RUN: rm -rf %t
-// RUN: mkdir %t
-// RUN: echo "" > %t/compile_flags.txt
-// RUN: cp "%s" "%t/test.cpp"
-// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
-// RUN: llvm-bcanalyzer %t/docs/bc/F0F9FC65FC90F54F690144A7AFB15DFC3D69B6E6.bc --dump | FileCheck %s --check-prefix CHECK-G-F
-// RUN: llvm-bcanalyzer %t/docs/bc/4202E8BF0ECB12AE354C8499C52725B0EE30AED5.bc --dump | FileCheck %s --check-prefix CHECK-G
-
-class G {
-public:
- int Method(int param) { return param; }
-};
-
-// CHECK-G: <BLOCKINFO_BLOCK/>
-// CHECK-G-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-G-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-G-NEXT: </VersionBlock>
-// CHECK-G-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-G-NEXT: <USR abbrevid=4 op0=20 op1=66 op2=2 op3=232 op4=191 op5=14 op6=203 op7=18 op8=174 op9=53 op10=76 op11=132 op12=153 op13=197 op14=39 op15=37 op16=176 op17=238 op18=48 op19=174 op20=213/>
- // CHECK-G-NEXT: <Name abbrevid=5 op0=1/> blob data = 'G'
- // CHECK-G-NEXT: <DefLocation abbrevid=6 op0=9 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-G-NEXT: <TagType abbrevid=8 op0=3/>
-// CHECK-G-NEXT: </RecordBlock>
-
-// CHECK-G-F: <BLOCKINFO_BLOCK/>
-// CHECK-G-F-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-G-F-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-G-F-NEXT: </VersionBlock>
-// CHECK-G-F-NEXT: <FunctionBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-G-F-NEXT: <USR abbrevid=4 op0=20 op1=240 op2=249 op3=252 op4=101 op5=252 op6=144 op7=245 op8=79 op9=105 op10=1 op11=68 op12=167 op13=175 op14=177 op15=93 op16=252 op17=61 op18=105 op19=182 op20=230/>
- // CHECK-G-F-NEXT: <Name abbrevid=5 op0=6/> blob data = 'Method'
- // CHECK-G-F-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-G-F-NEXT: <USR abbrevid=4 op0=20 op1=66 op2=2 op3=232 op4=191 op5=14 op6=203 op7=18 op8=174 op9=53 op10=76 op11=132 op12=153 op13=197 op14=39 op15=37 op16=176 op17=238 op18=48 op19=174 op20=213/>
- // CHECK-G-F-NEXT: <Name abbrevid=5 op0=1/> blob data = 'G'
- // CHECK-G-F-NEXT: <RefType abbrevid=6 op0=2/>
- // CHECK-G-F-NEXT: <Field abbrevid=7 op0=1/>
- // CHECK-G-F-NEXT: </ReferenceBlock>
- // CHECK-G-F-NEXT: <IsMethod abbrevid=9 op0=1/>
- // CHECK-G-F-NEXT: <DefLocation abbrevid=6 op0=11 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-G-F-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-G-F-NEXT: <USR abbrevid=4 op0=20 op1=66 op2=2 op3=232 op4=191 op5=14 op6=203 op7=18 op8=174 op9=53 op10=76 op11=132 op12=153 op13=197 op14=39 op15=37 op16=176 op17=238 op18=48 op19=174 op20=213/>
- // CHECK-G-F-NEXT: <Name abbrevid=5 op0=1/> blob data = 'G'
- // CHECK-G-F-NEXT: <RefType abbrevid=6 op0=2/>
- // CHECK-G-F-NEXT: <Field abbrevid=7 op0=2/>
- // CHECK-G-F-NEXT: </ReferenceBlock>
- // CHECK-G-F-NEXT: <TypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-G-F-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-G-F-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
- // CHECK-G-F-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-G-F-NEXT: </ReferenceBlock>
- // CHECK-G-F-NEXT: </TypeBlock>
- // CHECK-G-F-NEXT: <FieldTypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-G-F-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-G-F-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
- // CHECK-G-F-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-G-F-NEXT: </ReferenceBlock>
- // CHECK-G-F-NEXT: <Name abbrevid=4 op0=5/> blob data = 'param'
- // CHECK-G-F-NEXT: </FieldTypeBlock>
-// CHECK-G-F-NEXT: </FunctionBlock>
diff --git a/test/clang-doc/mapper-module.cpp b/test/clang-doc/mapper-module.cpp
new file mode 100644
index 00000000..04a34c68
--- /dev/null
+++ b/test/clang-doc/mapper-module.cpp
@@ -0,0 +1,51 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+export module M;
+
+int moduleFunction(int x); // ModuleLinkage
+
+static int staticModuleFunction(int x); // ModuleInternalLinkage
+
+export double exportedModuleFunction(double y, int z); // ExternalLinkage
+
+// RUN: clang-doc --dump-mapper --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/0000000000000000000000000000000000000000.bc | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: <BLOCKINFO_BLOCK/>
+// CHECK-0-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-0-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-0-NEXT: </VersionBlock>
+// CHECK-0-NEXT: <NamespaceBlock NumWords=50 BlockCodeSize=4>
+// CHECK-0-NEXT: <FunctionBlock NumWords=47 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=22/> blob data = 'exportedModuleFunction'
+// CHECK-0-NEXT: <Location abbrevid=7 op0=15 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <TypeBlock NumWords=7 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=4 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=6/> blob data = 'double'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: <FieldTypeBlock NumWords=9 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=4 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=6/> blob data = 'double'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <Name abbrevid=4 op0=1/> blob data = 'y'
+// CHECK-0-NEXT: </FieldTypeBlock>
+// CHECK-0-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <Name abbrevid=4 op0=1/> blob data = 'z'
+// CHECK-0-NEXT: </FieldTypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: </NamespaceBlock>
diff --git a/test/clang-doc/mapper-namespace.cpp b/test/clang-doc/mapper-namespace.cpp
index 032a05b8..d0008233 100644
--- a/test/clang-doc/mapper-namespace.cpp
+++ b/test/clang-doc/mapper-namespace.cpp
@@ -1,17 +1,94 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
// RUN: rm -rf %t
// RUN: mkdir %t
// RUN: echo "" > %t/compile_flags.txt
// RUN: cp "%s" "%t/test.cpp"
-// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
-// RUN: llvm-bcanalyzer %t/docs/bc/8D042EFFC98B373450BC6B5B90A330C25A150E9C.bc --dump | FileCheck %s
-
-namespace A {}
-
-// CHECK: <BLOCKINFO_BLOCK/>
-// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-NEXT: </VersionBlock>
-// CHECK-NEXT: <NamespaceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=141 op2=4 op3=46 op4=255 op5=201 op6=139 op7=55 op8=52 op9=80 op10=188 op11=107 op12=91 op13=144 op14=163 op15=48 op16=194 op17=90 op18=21 op19=14 op20=156/>
- // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
-// CHECK-NEXT: </NamespaceBlock>
+
+namespace A {
+
+void f();
+
+} // namespace A
+
+namespace A {
+
+void f(){};
+
+namespace B {
+
+enum E { X };
+
+E func(int i) { return X; }
+
+} // namespace B
+} // namespace A
+
+// RUN: clang-doc --dump-mapper --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/8D042EFFC98B373450BC6B5B90A330C25A150E9C.bc | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: <BLOCKINFO_BLOCK/>
+// CHECK-0-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-0-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-0-NEXT: </VersionBlock>
+// CHECK-0-NEXT: <NamespaceBlock NumWords=40 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <FunctionBlock NumWords=32 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'f'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=17 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: </NamespaceBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/E21AF79E2A9D02554BA090D10DF39FE273F5CDB5.bc | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: <BLOCKINFO_BLOCK/>
+// CHECK-1-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-1-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-1-NEXT: </VersionBlock>
+// CHECK-1-NEXT: <NamespaceBlock NumWords=64 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <FunctionBlock NumWords=56 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=4/> blob data = 'func'
+// CHECK-1-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'B'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=1/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <DefLocation abbrevid=6 op0=23 op1=4/> blob data = '{{.*}}'
+// CHECK-1-NEXT: <TypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=5 BlockCodeSize=4>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=12/> blob data = 'enum A::B::E'
+// CHECK-1-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: </TypeBlock>
+// CHECK-1-NEXT: <FieldTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-1-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-1-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <Name abbrevid=4 op0=1/> blob data = 'i'
+// CHECK-1-NEXT: </FieldTypeBlock>
+// CHECK-1-NEXT: </FunctionBlock>
+// CHECK-1-NEXT: </NamespaceBlock>
diff --git a/test/clang-doc/mapper-record.cpp b/test/clang-doc/mapper-record.cpp
new file mode 100644
index 00000000..dbabd8fd
--- /dev/null
+++ b/test/clang-doc/mapper-record.cpp
@@ -0,0 +1,220 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// This test requires Linux due to system-dependent USR for the inner class.
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+void H() {
+ class I {};
+}
+
+union A { int X; int Y; };
+
+enum B { X, Y };
+
+enum class Bc { A, B };
+
+struct C { int i; };
+
+class D {};
+
+class E {
+public:
+ E() {}
+ ~E() {}
+
+protected:
+ void ProtectedMethod();
+};
+
+void E::ProtectedMethod() {}
+
+class F : virtual private D, public E {};
+
+class X {
+ class Y {};
+};
+
+// RUN: clang-doc --dump-mapper --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/289584A8E0FF4178A794622A547AA622503967A1.bc | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: <BLOCKINFO_BLOCK/>
+// CHECK-0-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-0-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-0-NEXT: </VersionBlock>
+// CHECK-0-NEXT: <RecordBlock NumWords=55 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <FunctionBlock NumWords=47 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=15/> blob data = 'ProtectedMethod'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <IsMethod abbrevid=9 op0=1/>
+// CHECK-0-NEXT: <DefLocation abbrevid=6 op0=34 op1=4/> blob data = '{{.*}}'
+// CHECK-0-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-0-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-0-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-0-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: <TypeBlock NumWords=6 BlockCodeSize=4>
+// CHECK-0-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-0-NEXT: <Name abbrevid=5 op0=4/> blob data = 'void'
+// CHECK-0-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-0-NEXT: </ReferenceBlock>
+// CHECK-0-NEXT: </TypeBlock>
+// CHECK-0-NEXT: </FunctionBlock>
+// CHECK-0-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/3FB542274573CAEAD54CEBFFCAEE3D77FB9713D8.bc | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: <BLOCKINFO_BLOCK/>
+// CHECK-1-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-1-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-1-NEXT: </VersionBlock>
+// CHECK-1-NEXT: <RecordBlock NumWords=24 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'I'
+// CHECK-1-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-1-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-1-NEXT: <Name abbrevid=5 op0=1/> blob data = 'H'
+// CHECK-1-NEXT: <RefType abbrevid=6 op0=3/>
+// CHECK-1-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-1-NEXT: </ReferenceBlock>
+// CHECK-1-NEXT: <DefLocation abbrevid=6 op0=12 op1=4/> blob data = '{{.*}}'
+// CHECK-1-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-1-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/CA7C7935730B5EACD25F080E9C83FA087CCDC75E.bc | FileCheck %s --check-prefix CHECK-2
+// CHECK-2: <BLOCKINFO_BLOCK/>
+// CHECK-2-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-2-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-2-NEXT: </VersionBlock>
+// CHECK-2-NEXT: <RecordBlock NumWords=12 BlockCodeSize=4>
+// CHECK-2-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-2-NEXT: <Name abbrevid=5 op0=1/> blob data = 'X'
+// CHECK-2-NEXT: <DefLocation abbrevid=6 op0=38 op1=4/> blob data = '{{.*}}'
+// CHECK-2-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-2-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/06B5F6A19BA9F6A832E127C9968282B94619B210.bc | FileCheck %s --check-prefix CHECK-3
+// CHECK-3: <BLOCKINFO_BLOCK/>
+// CHECK-3-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-3-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-3-NEXT: </VersionBlock>
+// CHECK-3-NEXT: <RecordBlock NumWords=22 BlockCodeSize=4>
+// CHECK-3-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=1/> blob data = 'C'
+// CHECK-3-NEXT: <DefLocation abbrevid=6 op0=21 op1=4/> blob data = '{{.*}}'
+// CHECK-3-NEXT: <MemberTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-3-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-3-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-3-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-3-NEXT: </ReferenceBlock>
+// CHECK-3-NEXT: <Name abbrevid=4 op0=1/> blob data = 'i'
+// CHECK-3-NEXT: </MemberTypeBlock>
+// CHECK-3-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/641AB4A3D36399954ACDE29C7A8833032BF40472.bc | FileCheck %s --check-prefix CHECK-4
+// CHECK-4: <BLOCKINFO_BLOCK/>
+// CHECK-4-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-4-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-4-NEXT: </VersionBlock>
+// CHECK-4-NEXT: <RecordBlock NumWords=24 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=1/> blob data = 'Y'
+// CHECK-4-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-4-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-4-NEXT: <Name abbrevid=5 op0=1/> blob data = 'X'
+// CHECK-4-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-4-NEXT: <Field abbrevid=7 op0=1/>
+// CHECK-4-NEXT: </ReferenceBlock>
+// CHECK-4-NEXT: <DefLocation abbrevid=6 op0=39 op1=4/> blob data = '{{.*}}'
+// CHECK-4-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-4-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/0000000000000000000000000000000000000000.bc | FileCheck %s --check-prefix CHECK-5
+// CHECK-5: <BLOCKINFO_BLOCK/>
+// CHECK-5-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-5-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-5-NEXT: </VersionBlock>
+// CHECK-5-NEXT: <NamespaceBlock NumWords=19 BlockCodeSize=4>
+// CHECK-5-NEXT: <EnumBlock NumWords=16 BlockCodeSize=4>
+// CHECK-5-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-5-NEXT: <Name abbrevid=5 op0=2/> blob data = 'Bc'
+// CHECK-5-NEXT: <DefLocation abbrevid=6 op0=19 op1=4/> blob data = '{{.*}}'
+// CHECK-5-NEXT: <Scoped abbrevid=9 op0=1/>
+// CHECK-5-NEXT: <Member abbrevid=8 op0=1/> blob data = 'A'
+// CHECK-5-NEXT: <Member abbrevid=8 op0=1/> blob data = 'B'
+// CHECK-5-NEXT: </EnumBlock>
+// CHECK-5-NEXT: </NamespaceBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/0921737541208B8FA9BB42B60F78AC1D779AA054.bc | FileCheck %s --check-prefix CHECK-6
+// CHECK-6: <BLOCKINFO_BLOCK/>
+// CHECK-6-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-6-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-6-NEXT: </VersionBlock>
+// CHECK-6-NEXT: <RecordBlock NumWords=12 BlockCodeSize=4>
+// CHECK-6-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-6-NEXT: <Name abbrevid=5 op0=1/> blob data = 'D'
+// CHECK-6-NEXT: <DefLocation abbrevid=6 op0=23 op1=4/> blob data = '{{.*}}'
+// CHECK-6-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-6-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/E3B54702FABFF4037025BA194FC27C47006330B5.bc | FileCheck %s --check-prefix CHECK-7
+// CHECK-7: <BLOCKINFO_BLOCK/>
+// CHECK-7-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-7-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-7-NEXT: </VersionBlock>
+// CHECK-7-NEXT: <RecordBlock NumWords=37 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=1/> blob data = 'F'
+// CHECK-7-NEXT: <DefLocation abbrevid=6 op0=36 op1=4/> blob data = '{{.*}}'
+// CHECK-7-NEXT: <TagType abbrevid=8 op0=3/>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=1/> blob data = 'E'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=2/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: <ReferenceBlock NumWords=10 BlockCodeSize=4>
+// CHECK-7-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-7-NEXT: <Name abbrevid=5 op0=1/> blob data = 'D'
+// CHECK-7-NEXT: <RefType abbrevid=6 op0=2/>
+// CHECK-7-NEXT: <Field abbrevid=7 op0=3/>
+// CHECK-7-NEXT: </ReferenceBlock>
+// CHECK-7-NEXT: </RecordBlock>
+
+// RUN: llvm-bcanalyzer --dump %t/docs/bc/ACE81AFA6627B4CEF2B456FB6E1252925674AF7E.bc | FileCheck %s --check-prefix CHECK-8
+// CHECK-8: <BLOCKINFO_BLOCK/>
+// CHECK-8-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
+// CHECK-8-NEXT: <Version abbrevid=4 op0=2/>
+// CHECK-8-NEXT: </VersionBlock>
+// CHECK-8-NEXT: <RecordBlock NumWords=33 BlockCodeSize=4>
+// CHECK-8-NEXT: <USR abbrevid=4 op0=20 op1={{[0-9]+}} op2={{[0-9]+}} op3={{[0-9]+}} op4={{[0-9]+}} op5={{[0-9]+}} op6={{[0-9]+}} op7={{[0-9]+}} op8={{[0-9]+}} op9={{[0-9]+}} op10={{[0-9]+}} op11={{[0-9]+}} op12={{[0-9]+}} op13={{[0-9]+}} op14={{[0-9]+}} op15={{[0-9]+}} op16={{[0-9]+}} op17={{[0-9]+}} op18={{[0-9]+}} op19={{[0-9]+}} op20={{[0-9]+}}/>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=1/> blob data = 'A'
+// CHECK-8-NEXT: <DefLocation abbrevid=6 op0=15 op1=4/> blob data = '{{.*}}'
+// CHECK-8-NEXT: <TagType abbrevid=8 op0=2/>
+// CHECK-8-NEXT: <MemberTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-8-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-8-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: <Name abbrevid=4 op0=1/> blob data = 'X'
+// CHECK-8-NEXT: </MemberTypeBlock>
+// CHECK-8-NEXT: <MemberTypeBlock NumWords=8 BlockCodeSize=4>
+// CHECK-8-NEXT: <ReferenceBlock NumWords=3 BlockCodeSize=4>
+// CHECK-8-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
+// CHECK-8-NEXT: <Field abbrevid=7 op0=4/>
+// CHECK-8-NEXT: </ReferenceBlock>
+// CHECK-8-NEXT: <Name abbrevid=4 op0=1/> blob data = 'Y'
+// CHECK-8-NEXT: </MemberTypeBlock>
+// CHECK-8-NEXT: </RecordBlock>
diff --git a/test/clang-doc/mapper-struct.cpp b/test/clang-doc/mapper-struct.cpp
deleted file mode 100644
index 62138106..00000000
--- a/test/clang-doc/mapper-struct.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-// RUN: rm -rf %t
-// RUN: mkdir %t
-// RUN: echo "" > %t/compile_flags.txt
-// RUN: cp "%s" "%t/test.cpp"
-// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
-// RUN: llvm-bcanalyzer %t/docs/bc/06B5F6A19BA9F6A832E127C9968282B94619B210.bc --dump | FileCheck %s
-
-struct C { int i; };
-
-// CHECK: <BLOCKINFO_BLOCK/>
-// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-NEXT: </VersionBlock>
-// CHECK-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=6 op2=181 op3=246 op4=161 op5=155 op6=169 op7=246 op8=168 op9=50 op10=225 op11=39 op12=201 op13=150 op14=130 op15=130 op16=185 op17=70 op18=25 op19=178 op20=16/>
- // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'C'
- // CHECK-NEXT: <DefLocation abbrevid=6 op0=8 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-NEXT: <MemberTypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
- // CHECK-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-NEXT: </ReferenceBlock>
- // CHECK-NEXT: <Name abbrevid=4 op0=1/> blob data = 'i'
- // CHECK-NEXT: </MemberTypeBlock>
-// CHECK-NEXT: </RecordBlock>
diff --git a/test/clang-doc/mapper-union.cpp b/test/clang-doc/mapper-union.cpp
deleted file mode 100644
index 3226ff1b..00000000
--- a/test/clang-doc/mapper-union.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-// RUN: rm -rf %t
-// RUN: mkdir %t
-// RUN: echo "" > %t/compile_flags.txt
-// RUN: cp "%s" "%t/test.cpp"
-// RUN: clang-doc --dump-mapper -doxygen -p %t %t/test.cpp -output=%t/docs
-// RUN: llvm-bcanalyzer %t/docs/bc/0B8A6B938B939B77C6325CCCC8AA3E938BF9E2E8.bc --dump | FileCheck %s
-
-union D { int X; int Y; };
-
-// CHECK: <BLOCKINFO_BLOCK/>
-// CHECK-NEXT: <VersionBlock NumWords=1 BlockCodeSize=4>
- // CHECK-NEXT: <Version abbrevid=4 op0=2/>
-// CHECK-NEXT: </VersionBlock>
-// CHECK-NEXT: <RecordBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <USR abbrevid=4 op0=20 op1=11 op2=138 op3=107 op4=147 op5=139 op6=147 op7=155 op8=119 op9=198 op10=50 op11=92 op12=204 op13=200 op14=170 op15=62 op16=147 op17=139 op18=249 op19=226 op20=232/>
- // CHECK-NEXT: <Name abbrevid=5 op0=1/> blob data = 'D'
- // CHECK-NEXT: <DefLocation abbrevid=6 op0=8 op1={{[0-9]*}}/> blob data = '{{.*}}'
- // CHECK-NEXT: <TagType abbrevid=8 op0=2/>
- // CHECK-NEXT: <MemberTypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
- // CHECK-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-NEXT: </ReferenceBlock>
- // CHECK-NEXT: <Name abbrevid=4 op0=1/> blob data = 'X'
- // CHECK-NEXT: </MemberTypeBlock>
- // CHECK-NEXT: <MemberTypeBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <ReferenceBlock NumWords={{[0-9]*}} BlockCodeSize=4>
- // CHECK-NEXT: <Name abbrevid=5 op0=3/> blob data = 'int'
- // CHECK-NEXT: <Field abbrevid=7 op0=4/>
- // CHECK-NEXT: </ReferenceBlock>
- // CHECK-NEXT: <Name abbrevid=4 op0=1/> blob data = 'Y'
- // CHECK-NEXT: </MemberTypeBlock>
-// CHECK-NEXT: </RecordBlock>
diff --git a/test/clang-doc/public-comment.cpp b/test/clang-doc/public-comment.cpp
new file mode 100644
index 00000000..6c5545e8
--- /dev/null
+++ b/test/clang-doc/public-comment.cpp
@@ -0,0 +1,138 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+/// \brief Brief description.
+///
+/// Extended description that
+/// continues onto the next line.
+///
+/// <ul class="test">
+/// <li> Testing.
+/// </ul>
+///
+/// \verbatim
+/// The description continues.
+/// \endverbatim
+/// --
+/// \param [out] I is a parameter.
+/// \param J is a parameter.
+/// \return void
+void F(int I, int J);
+
+/// Bonus comment on definition
+void F(int I, int J) {}
+
+// RUN: clang-doc --format=yaml --doxygen --public --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: cat %t/docs/./GlobalNamespace.yaml | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: ---
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: ChildFunctions:
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'F'
+// CHECK-0-NEXT: Description:
+// CHECK-0-NEXT: - Kind: 'FullComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'BlockCommandComment'
+// CHECK-0-NEXT: Name: 'brief'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' Brief description.'
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' Extended description that'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' continues onto the next line.'
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'HTMLStartTagComment'
+// CHECK-0-NEXT: Name: 'ul'
+// CHECK-0-NEXT: AttrKeys:
+// CHECK-0-NEXT: - 'class'
+// CHECK-0-NEXT: AttrValues:
+// CHECK-0-NEXT: - 'test'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'HTMLStartTagComment'
+// CHECK-0-NEXT: Name: 'li'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' Testing.'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'HTMLEndTagComment'
+// CHECK-0-NEXT: Name: 'ul'
+// CHECK-0-NEXT: SelfClosing: true
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'VerbatimBlockComment'
+// CHECK-0-NEXT: Name: 'verbatim'
+// CHECK-0-NEXT: CloseName: 'endverbatim'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'VerbatimBlockLineComment'
+// CHECK-0-NEXT: Text: ' The description continues.'
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' --'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'ParamCommandComment'
+// CHECK-0-NEXT: Direction: '[out]'
+// CHECK-0-NEXT: ParamName: 'I'
+// CHECK-0-NEXT: Explicit: true
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' is a parameter.'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'ParamCommandComment'
+// CHECK-0-NEXT: Direction: '[in]'
+// CHECK-0-NEXT: ParamName: 'J'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' is a parameter.'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'BlockCommandComment'
+// CHECK-0-NEXT: Name: 'return'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' void'
+// CHECK-0-NEXT: - Kind: 'FullComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' Bonus comment on definition'
+// CHECK-0-NEXT: DefLocation:
+// CHECK-0-NEXT: LineNumber: 28
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 25
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Params:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'I'
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'J'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'void'
+// CHECK-0-NEXT: ...
diff --git a/test/clang-doc/public-linkage.cpp b/test/clang-doc/public-linkage.cpp
new file mode 100644
index 00000000..c33e08ce
--- /dev/null
+++ b/test/clang-doc/public-linkage.cpp
@@ -0,0 +1,299 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+void function(int x);
+
+inline int inlinedFunction(int x);
+
+int functionWithInnerClass(int x) {
+ class InnerClass { //NoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+inline int inlinedFunctionWithInnerClass(int x) {
+ class InnerClass { //VisibleNoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+class Class {
+public:
+ void publicMethod();
+ int publicField;
+
+protected:
+ void protectedMethod();
+ int protectedField;
+
+private:
+ void privateMethod();
+ int privateField;
+};
+
+namespace named {
+class NamedClass {
+public:
+ void namedPublicMethod();
+ int namedPublicField;
+
+protected:
+ void namedProtectedMethod();
+ int namedProtectedField;
+
+private:
+ void namedPrivateMethod();
+ int namedPrivateField;
+};
+
+void namedFunction();
+static void namedStaticFunction();
+inline void namedInlineFunction();
+} // namespace named
+
+static void staticFunction(int x); //Internal Linkage
+
+static int staticFunctionWithInnerClass(int x) {
+ class InnerClass { //NoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+namespace {
+class AnonClass {
+public:
+ void anonPublicMethod();
+ int anonPublicField;
+
+protected:
+ void anonProtectedMethod();
+ int anonProtectedField;
+
+private:
+ void anonPrivateMethod();
+ int anonPrivateField;
+};
+
+void anonFunction();
+static void anonStaticFunction();
+inline void anonInlineFunction();
+} // namespace
+
+// RUN: clang-doc --format=yaml --doxygen --public --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: cat %t/docs/./Class.yaml | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: ---
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: DefLocation:
+// CHECK-0-NEXT: LineNumber: 32
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: TagType: Class
+// CHECK-0-NEXT: Members:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'publicField'
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'protectedField'
+// CHECK-0-NEXT: Access: Protected
+// CHECK-0-NEXT: ChildFunctions:
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'publicMethod'
+// CHECK-0-NEXT: Namespace:
+// CHECK-0-NEXT: - Type: Record
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 34
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: IsMethod: true
+// CHECK-0-NEXT: Parent:
+// CHECK-0-NEXT: Type: Record
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'void'
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'protectedMethod'
+// CHECK-0-NEXT: Namespace:
+// CHECK-0-NEXT: - Type: Record
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 38
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: IsMethod: true
+// CHECK-0-NEXT: Parent:
+// CHECK-0-NEXT: Type: Record
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'void'
+// CHECK-0-NEXT: ...
+
+// RUN: cat %t/docs/./named.yaml | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: ---
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'named'
+// CHECK-1-NEXT: ChildFunctions:
+// CHECK-1-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'namedFunction'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'named'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Location:
+// CHECK-1-NEXT: - LineNumber: 61
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: ReturnType:
+// CHECK-1-NEXT: Type:
+// CHECK-1-NEXT: Name: 'void'
+// CHECK-1-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'namedInlineFunction'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'named'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Location:
+// CHECK-1-NEXT: - LineNumber: 63
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: ReturnType:
+// CHECK-1-NEXT: Type:
+// CHECK-1-NEXT: Name: 'void'
+// CHECK-1-NEXT: ...
+
+// RUN: cat %t/docs/./GlobalNamespace.yaml | FileCheck %s --check-prefix CHECK-2
+// CHECK-2: ---
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: ChildFunctions:
+// CHECK-2-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Name: 'function'
+// CHECK-2-NEXT: Location:
+// CHECK-2-NEXT: - LineNumber: 10
+// CHECK-2-NEXT: Filename: 'test'
+// CHECK-2-NEXT: Params:
+// CHECK-2-NEXT: - Type:
+// CHECK-2-NEXT: Name: 'int'
+// CHECK-2-NEXT: Name: 'x'
+// CHECK-2-NEXT: ReturnType:
+// CHECK-2-NEXT: Type:
+// CHECK-2-NEXT: Name: 'void'
+// CHECK-2-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Name: 'inlinedFunction'
+// CHECK-2-NEXT: Location:
+// CHECK-2-NEXT: - LineNumber: 12
+// CHECK-2-NEXT: Filename: 'test'
+// CHECK-2-NEXT: Params:
+// CHECK-2-NEXT: - Type:
+// CHECK-2-NEXT: Name: 'int'
+// CHECK-2-NEXT: Name: 'x'
+// CHECK-2-NEXT: ReturnType:
+// CHECK-2-NEXT: Type:
+// CHECK-2-NEXT: Name: 'int'
+// CHECK-2-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Name: 'functionWithInnerClass'
+// CHECK-2-NEXT: DefLocation:
+// CHECK-2-NEXT: LineNumber: 14
+// CHECK-2-NEXT: Filename: 'test'
+// CHECK-2-NEXT: Params:
+// CHECK-2-NEXT: - Type:
+// CHECK-2-NEXT: Name: 'int'
+// CHECK-2-NEXT: Name: 'x'
+// CHECK-2-NEXT: ReturnType:
+// CHECK-2-NEXT: Type:
+// CHECK-2-NEXT: Name: 'int'
+// CHECK-2-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Name: 'inlinedFunctionWithInnerClass'
+// CHECK-2-NEXT: DefLocation:
+// CHECK-2-NEXT: LineNumber: 23
+// CHECK-2-NEXT: Filename: 'test'
+// CHECK-2-NEXT: Params:
+// CHECK-2-NEXT: - Type:
+// CHECK-2-NEXT: Name: 'int'
+// CHECK-2-NEXT: Name: 'x'
+// CHECK-2-NEXT: ReturnType:
+// CHECK-2-NEXT: Type:
+// CHECK-2-NEXT: Name: 'int'
+// CHECK-2-NEXT: ...
+
+// RUN: cat %t/docs/named/NamedClass.yaml | FileCheck %s --check-prefix CHECK-3
+// CHECK-3: ---
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'NamedClass'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Namespace
+// CHECK-3-NEXT: Name: 'named'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: DefLocation:
+// CHECK-3-NEXT: LineNumber: 47
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: TagType: Class
+// CHECK-3-NEXT: Members:
+// CHECK-3-NEXT: - Type:
+// CHECK-3-NEXT: Name: 'int'
+// CHECK-3-NEXT: Name: 'namedPublicField'
+// CHECK-3-NEXT: - Type:
+// CHECK-3-NEXT: Name: 'int'
+// CHECK-3-NEXT: Name: 'namedProtectedField'
+// CHECK-3-NEXT: Access: Protected
+// CHECK-3-NEXT: ChildFunctions:
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'namedPublicMethod'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Record
+// CHECK-3-NEXT: Name: 'NamedClass'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: - Type: Namespace
+// CHECK-3-NEXT: Name: 'named'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Location:
+// CHECK-3-NEXT: - LineNumber: 49
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: IsMethod: true
+// CHECK-3-NEXT: Parent:
+// CHECK-3-NEXT: Type: Record
+// CHECK-3-NEXT: Name: 'NamedClass'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'namedProtectedMethod'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Record
+// CHECK-3-NEXT: Name: 'NamedClass'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: - Type: Namespace
+// CHECK-3-NEXT: Name: 'named'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Location:
+// CHECK-3-NEXT: - LineNumber: 53
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: IsMethod: true
+// CHECK-3-NEXT: Parent:
+// CHECK-3-NEXT: Type: Record
+// CHECK-3-NEXT: Name: 'NamedClass'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: ...
diff --git a/test/clang-doc/public-module.cpp b/test/clang-doc/public-module.cpp
new file mode 100644
index 00000000..0c93d688
--- /dev/null
+++ b/test/clang-doc/public-module.cpp
@@ -0,0 +1,51 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+export module M;
+
+int moduleFunction(int x); // ModuleLinkage
+
+static int staticModuleFunction(int x); // ModuleInternalLinkage
+
+export double exportedModuleFunction(double y, int z); // ExternalLinkage
+
+// RUN: clang-doc --format=yaml --doxygen --public --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: cat %t/docs/./GlobalNamespace.yaml | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: ---
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: ChildFunctions:
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'moduleFunction'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 11
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Params:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'x'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'exportedModuleFunction'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 15
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Params:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'double'
+// CHECK-0-NEXT: Name: 'y'
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'z'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'double'
+// CHECK-0-NEXT: ...
diff --git a/test/clang-doc/public-namespace.cpp b/test/clang-doc/public-namespace.cpp
new file mode 100644
index 00000000..d104ff2c
--- /dev/null
+++ b/test/clang-doc/public-namespace.cpp
@@ -0,0 +1,96 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+namespace A {
+
+void f();
+
+} // namespace A
+
+namespace A {
+
+void f(){};
+
+namespace B {
+
+enum E { X };
+
+E func(int i) { return X; }
+
+} // namespace B
+} // namespace A
+
+// RUN: clang-doc --format=yaml --doxygen --public --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: cat %t/docs/./A.yaml | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: ---
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'A'
+// CHECK-0-NEXT: ChildFunctions:
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'f'
+// CHECK-0-NEXT: Namespace:
+// CHECK-0-NEXT: - Type: Namespace
+// CHECK-0-NEXT: Name: 'A'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: DefLocation:
+// CHECK-0-NEXT: LineNumber: 17
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 11
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'void'
+// CHECK-0-NEXT: ...
+
+// RUN: cat %t/docs/A/B.yaml | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: ---
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'B'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'A'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: ChildFunctions:
+// CHECK-1-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'func'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'B'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'A'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: DefLocation:
+// CHECK-1-NEXT: LineNumber: 23
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: Params:
+// CHECK-1-NEXT: - Type:
+// CHECK-1-NEXT: Name: 'int'
+// CHECK-1-NEXT: Name: 'i'
+// CHECK-1-NEXT: ReturnType:
+// CHECK-1-NEXT: Type:
+// CHECK-1-NEXT: Name: 'enum A::B::E'
+// CHECK-1-NEXT: ChildEnums:
+// CHECK-1-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'E'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'B'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'A'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: DefLocation:
+// CHECK-1-NEXT: LineNumber: 21
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: Members:
+// CHECK-1-NEXT: - 'X'
+// CHECK-1-NEXT: ...
diff --git a/test/clang-doc/public-record.cpp b/test/clang-doc/public-record.cpp
new file mode 100644
index 00000000..d3302193
--- /dev/null
+++ b/test/clang-doc/public-record.cpp
@@ -0,0 +1,208 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// This test requires Linux due to system-dependent USR for the inner class.
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+void H() {
+ class I {};
+}
+
+union A { int X; int Y; };
+
+enum B { X, Y };
+
+enum class Bc { A, B };
+
+struct C { int i; };
+
+class D {};
+
+class E {
+public:
+ E() {}
+ ~E() {}
+
+protected:
+ void ProtectedMethod();
+};
+
+void E::ProtectedMethod() {}
+
+class F : virtual private D, public E {};
+
+class X {
+ class Y {};
+};
+
+// RUN: clang-doc --format=yaml --doxygen --public --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: cat %t/docs/./C.yaml | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: ---
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'C'
+// CHECK-0-NEXT: DefLocation:
+// CHECK-0-NEXT: LineNumber: 21
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Members:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'i'
+// CHECK-0-NEXT: ...
+
+// RUN: cat %t/docs/./A.yaml | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: ---
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'A'
+// CHECK-1-NEXT: DefLocation:
+// CHECK-1-NEXT: LineNumber: 15
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: TagType: Union
+// CHECK-1-NEXT: Members:
+// CHECK-1-NEXT: - Type:
+// CHECK-1-NEXT: Name: 'int'
+// CHECK-1-NEXT: Name: 'X'
+// CHECK-1-NEXT: - Type:
+// CHECK-1-NEXT: Name: 'int'
+// CHECK-1-NEXT: Name: 'Y'
+// CHECK-1-NEXT: ...
+
+// RUN: cat %t/docs/./F.yaml | FileCheck %s --check-prefix CHECK-2
+// CHECK-2: ---
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Name: 'F'
+// CHECK-2-NEXT: DefLocation:
+// CHECK-2-NEXT: LineNumber: 36
+// CHECK-2-NEXT: Filename: 'test'
+// CHECK-2-NEXT: TagType: Class
+// CHECK-2-NEXT: Parents:
+// CHECK-2-NEXT: - Type: Record
+// CHECK-2-NEXT: Name: 'E'
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: VirtualParents:
+// CHECK-2-NEXT: - Type: Record
+// CHECK-2-NEXT: Name: 'D'
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: ...
+
+// RUN: cat %t/docs/./E.yaml | FileCheck %s --check-prefix CHECK-3
+// CHECK-3: ---
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: DefLocation:
+// CHECK-3-NEXT: LineNumber: 25
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: TagType: Class
+// CHECK-3-NEXT: ChildFunctions:
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: DefLocation:
+// CHECK-3-NEXT: LineNumber: 27
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: IsMethod: true
+// CHECK-3-NEXT: Parent:
+// CHECK-3-NEXT: Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: '~E'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: DefLocation:
+// CHECK-3-NEXT: LineNumber: 28
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: IsMethod: true
+// CHECK-3-NEXT: Parent:
+// CHECK-3-NEXT: Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'ProtectedMethod'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: DefLocation:
+// CHECK-3-NEXT: LineNumber: 34
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: Location:
+// CHECK-3-NEXT: - LineNumber: 31
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: IsMethod: true
+// CHECK-3-NEXT: Parent:
+// CHECK-3-NEXT: Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: ...
+
+// RUN: cat %t/docs/./D.yaml | FileCheck %s --check-prefix CHECK-4
+// CHECK-4: ---
+// CHECK-4-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-4-NEXT: Name: 'D'
+// CHECK-4-NEXT: DefLocation:
+// CHECK-4-NEXT: LineNumber: 23
+// CHECK-4-NEXT: Filename: 'test'
+// CHECK-4-NEXT: TagType: Class
+// CHECK-4-NEXT: ...
+
+// RUN: cat %t/docs/./X.yaml | FileCheck %s --check-prefix CHECK-5
+// CHECK-5: ---
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: Name: 'X'
+// CHECK-5-NEXT: DefLocation:
+// CHECK-5-NEXT: LineNumber: 38
+// CHECK-5-NEXT: Filename: 'test'
+// CHECK-5-NEXT: TagType: Class
+// CHECK-5-NEXT: ...
+
+// RUN: cat %t/docs/./GlobalNamespace.yaml | FileCheck %s --check-prefix CHECK-6
+// CHECK-6: ---
+// CHECK-6-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: ChildFunctions:
+// CHECK-6-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: Name: 'H'
+// CHECK-6-NEXT: DefLocation:
+// CHECK-6-NEXT: LineNumber: 11
+// CHECK-6-NEXT: Filename: 'test'
+// CHECK-6-NEXT: ReturnType:
+// CHECK-6-NEXT: Type:
+// CHECK-6-NEXT: Name: 'void'
+// CHECK-6-NEXT: ChildEnums:
+// CHECK-6-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: Name: 'B'
+// CHECK-6-NEXT: DefLocation:
+// CHECK-6-NEXT: LineNumber: 17
+// CHECK-6-NEXT: Filename: 'test'
+// CHECK-6-NEXT: Members:
+// CHECK-6-NEXT: - 'X'
+// CHECK-6-NEXT: - 'Y'
+// CHECK-6-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: Name: 'Bc'
+// CHECK-6-NEXT: DefLocation:
+// CHECK-6-NEXT: LineNumber: 19
+// CHECK-6-NEXT: Filename: 'test'
+// CHECK-6-NEXT: Scoped: true
+// CHECK-6-NEXT: Members:
+// CHECK-6-NEXT: - 'A'
+// CHECK-6-NEXT: - 'B'
+// CHECK-6-NEXT: ...
diff --git a/test/clang-doc/test_cases/comment.cpp b/test/clang-doc/test_cases/comment.cpp
new file mode 100644
index 00000000..9a4ae1b0
--- /dev/null
+++ b/test/clang-doc/test_cases/comment.cpp
@@ -0,0 +1,28 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+/// \brief Brief description.
+///
+/// Extended description that
+/// continues onto the next line.
+///
+/// <ul class="test">
+/// <li> Testing.
+/// </ul>
+///
+/// \verbatim
+/// The description continues.
+/// \endverbatim
+/// --
+/// \param [out] I is a parameter.
+/// \param J is a parameter.
+/// \return void
+void F(int I, int J);
+
+/// Bonus comment on definition
+void F(int I, int J) {}
diff --git a/test/clang-doc/test_cases/compile_flags.txt b/test/clang-doc/test_cases/compile_flags.txt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/test/clang-doc/test_cases/compile_flags.txt
diff --git a/test/clang-doc/test_cases/linkage.cpp b/test/clang-doc/test_cases/linkage.cpp
new file mode 100644
index 00000000..ed4b4a30
--- /dev/null
+++ b/test/clang-doc/test_cases/linkage.cpp
@@ -0,0 +1,95 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+void function(int x);
+
+inline int inlinedFunction(int x);
+
+int functionWithInnerClass(int x) {
+ class InnerClass { //NoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+inline int inlinedFunctionWithInnerClass(int x) {
+ class InnerClass { //VisibleNoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+class Class {
+public:
+ void publicMethod();
+ int publicField;
+
+protected:
+ void protectedMethod();
+ int protectedField;
+
+private:
+ void privateMethod();
+ int privateField;
+};
+
+namespace named {
+class NamedClass {
+public:
+ void namedPublicMethod();
+ int namedPublicField;
+
+protected:
+ void namedProtectedMethod();
+ int namedProtectedField;
+
+private:
+ void namedPrivateMethod();
+ int namedPrivateField;
+};
+
+void namedFunction();
+static void namedStaticFunction();
+inline void namedInlineFunction();
+} // namespace named
+
+static void staticFunction(int x); //Internal Linkage
+
+static int staticFunctionWithInnerClass(int x) {
+ class InnerClass { //NoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+namespace {
+class AnonClass {
+public:
+ void anonPublicMethod();
+ int anonPublicField;
+
+protected:
+ void anonProtectedMethod();
+ int anonProtectedField;
+
+private:
+ void anonPrivateMethod();
+ int anonPrivateField;
+};
+
+void anonFunction();
+static void anonStaticFunction();
+inline void anonInlineFunction();
+} // namespace
diff --git a/test/clang-doc/test_cases/module.cpp b/test/clang-doc/test_cases/module.cpp
new file mode 100644
index 00000000..3c30a547
--- /dev/null
+++ b/test/clang-doc/test_cases/module.cpp
@@ -0,0 +1,15 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+export module M;
+
+int moduleFunction(int x); // ModuleLinkage
+
+static int staticModuleFunction(int x); // ModuleInternalLinkage
+
+export double exportedModuleFunction(double y, int z); // ExternalLinkage
diff --git a/test/clang-doc/test_cases/namespace.cpp b/test/clang-doc/test_cases/namespace.cpp
new file mode 100644
index 00000000..7fa6f8f9
--- /dev/null
+++ b/test/clang-doc/test_cases/namespace.cpp
@@ -0,0 +1,26 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+namespace A {
+
+void f();
+
+} // namespace A
+
+namespace A {
+
+void f(){};
+
+namespace B {
+
+enum E { X };
+
+E func(int i) { return X; }
+
+} // namespace B
+} // namespace A
diff --git a/test/clang-doc/test_cases/record.cpp b/test/clang-doc/test_cases/record.cpp
new file mode 100644
index 00000000..03419a9e
--- /dev/null
+++ b/test/clang-doc/test_cases/record.cpp
@@ -0,0 +1,40 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// This test requires Linux due to system-dependent USR for the inner class.
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+void H() {
+ class I {};
+}
+
+union A { int X; int Y; };
+
+enum B { X, Y };
+
+enum class Bc { A, B };
+
+struct C { int i; };
+
+class D {};
+
+class E {
+public:
+ E() {}
+ ~E() {}
+
+protected:
+ void ProtectedMethod();
+};
+
+void E::ProtectedMethod() {}
+
+class F : virtual private D, public E {};
+
+class X {
+ class Y {};
+};
diff --git a/test/clang-doc/yaml-comment.cpp b/test/clang-doc/yaml-comment.cpp
new file mode 100644
index 00000000..7aa8e64d
--- /dev/null
+++ b/test/clang-doc/yaml-comment.cpp
@@ -0,0 +1,138 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+/// \brief Brief description.
+///
+/// Extended description that
+/// continues onto the next line.
+///
+/// <ul class="test">
+/// <li> Testing.
+/// </ul>
+///
+/// \verbatim
+/// The description continues.
+/// \endverbatim
+/// --
+/// \param [out] I is a parameter.
+/// \param J is a parameter.
+/// \return void
+void F(int I, int J);
+
+/// Bonus comment on definition
+void F(int I, int J) {}
+
+// RUN: clang-doc --format=yaml --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: cat %t/docs/./GlobalNamespace.yaml | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: ---
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: ChildFunctions:
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'F'
+// CHECK-0-NEXT: Description:
+// CHECK-0-NEXT: - Kind: 'FullComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'BlockCommandComment'
+// CHECK-0-NEXT: Name: 'brief'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' Brief description.'
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' Extended description that'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' continues onto the next line.'
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'HTMLStartTagComment'
+// CHECK-0-NEXT: Name: 'ul'
+// CHECK-0-NEXT: AttrKeys:
+// CHECK-0-NEXT: - 'class'
+// CHECK-0-NEXT: AttrValues:
+// CHECK-0-NEXT: - 'test'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'HTMLStartTagComment'
+// CHECK-0-NEXT: Name: 'li'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' Testing.'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'HTMLEndTagComment'
+// CHECK-0-NEXT: Name: 'ul'
+// CHECK-0-NEXT: SelfClosing: true
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'VerbatimBlockComment'
+// CHECK-0-NEXT: Name: 'verbatim'
+// CHECK-0-NEXT: CloseName: 'endverbatim'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'VerbatimBlockLineComment'
+// CHECK-0-NEXT: Text: ' The description continues.'
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' --'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'ParamCommandComment'
+// CHECK-0-NEXT: Direction: '[out]'
+// CHECK-0-NEXT: ParamName: 'I'
+// CHECK-0-NEXT: Explicit: true
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' is a parameter.'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'ParamCommandComment'
+// CHECK-0-NEXT: Direction: '[in]'
+// CHECK-0-NEXT: ParamName: 'J'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' is a parameter.'
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: - Kind: 'BlockCommandComment'
+// CHECK-0-NEXT: Name: 'return'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' void'
+// CHECK-0-NEXT: - Kind: 'FullComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'ParagraphComment'
+// CHECK-0-NEXT: Children:
+// CHECK-0-NEXT: - Kind: 'TextComment'
+// CHECK-0-NEXT: Text: ' Bonus comment on definition'
+// CHECK-0-NEXT: DefLocation:
+// CHECK-0-NEXT: LineNumber: 28
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 25
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Params:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'I'
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'J'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'void'
+// CHECK-0-NEXT: ...
diff --git a/test/clang-doc/yaml-linkage.cpp b/test/clang-doc/yaml-linkage.cpp
new file mode 100644
index 00000000..3a0aa5bf
--- /dev/null
+++ b/test/clang-doc/yaml-linkage.cpp
@@ -0,0 +1,529 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+void function(int x);
+
+inline int inlinedFunction(int x);
+
+int functionWithInnerClass(int x) {
+ class InnerClass { //NoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+inline int inlinedFunctionWithInnerClass(int x) {
+ class InnerClass { //VisibleNoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+class Class {
+public:
+ void publicMethod();
+ int publicField;
+
+protected:
+ void protectedMethod();
+ int protectedField;
+
+private:
+ void privateMethod();
+ int privateField;
+};
+
+namespace named {
+class NamedClass {
+public:
+ void namedPublicMethod();
+ int namedPublicField;
+
+protected:
+ void namedProtectedMethod();
+ int namedProtectedField;
+
+private:
+ void namedPrivateMethod();
+ int namedPrivateField;
+};
+
+void namedFunction();
+static void namedStaticFunction();
+inline void namedInlineFunction();
+} // namespace named
+
+static void staticFunction(int x); //Internal Linkage
+
+static int staticFunctionWithInnerClass(int x) {
+ class InnerClass { //NoLinkage
+ public:
+ int innerPublicMethod() { return 2; };
+ }; //end class
+ InnerClass temp;
+ return temp.innerPublicMethod();
+};
+
+namespace {
+class AnonClass {
+public:
+ void anonPublicMethod();
+ int anonPublicField;
+
+protected:
+ void anonProtectedMethod();
+ int anonProtectedField;
+
+private:
+ void anonPrivateMethod();
+ int anonPrivateField;
+};
+
+void anonFunction();
+static void anonStaticFunction();
+inline void anonInlineFunction();
+} // namespace
+
+// RUN: clang-doc --format=yaml --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: cat %t/docs/./Class.yaml | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: ---
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: DefLocation:
+// CHECK-0-NEXT: LineNumber: 32
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: TagType: Class
+// CHECK-0-NEXT: Members:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'publicField'
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'protectedField'
+// CHECK-0-NEXT: Access: Protected
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'privateField'
+// CHECK-0-NEXT: Access: Private
+// CHECK-0-NEXT: ChildFunctions:
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'publicMethod'
+// CHECK-0-NEXT: Namespace:
+// CHECK-0-NEXT: - Type: Record
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 34
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: IsMethod: true
+// CHECK-0-NEXT: Parent:
+// CHECK-0-NEXT: Type: Record
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'void'
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'protectedMethod'
+// CHECK-0-NEXT: Namespace:
+// CHECK-0-NEXT: - Type: Record
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 38
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: IsMethod: true
+// CHECK-0-NEXT: Parent:
+// CHECK-0-NEXT: Type: Record
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'void'
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'privateMethod'
+// CHECK-0-NEXT: Namespace:
+// CHECK-0-NEXT: - Type: Record
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 42
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: IsMethod: true
+// CHECK-0-NEXT: Parent:
+// CHECK-0-NEXT: Type: Record
+// CHECK-0-NEXT: Name: 'Class'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'void'
+// CHECK-0-NEXT: ...
+
+// RUN: cat %t/docs/./named.yaml | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: ---
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'named'
+// CHECK-1-NEXT: ChildFunctions:
+// CHECK-1-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'namedFunction'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'named'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Location:
+// CHECK-1-NEXT: - LineNumber: 61
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: ReturnType:
+// CHECK-1-NEXT: Type:
+// CHECK-1-NEXT: Name: 'void'
+// CHECK-1-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'namedStaticFunction'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'named'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Location:
+// CHECK-1-NEXT: - LineNumber: 62
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: ReturnType:
+// CHECK-1-NEXT: Type:
+// CHECK-1-NEXT: Name: 'void'
+// CHECK-1-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'namedInlineFunction'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'named'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Location:
+// CHECK-1-NEXT: - LineNumber: 63
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: ReturnType:
+// CHECK-1-NEXT: Type:
+// CHECK-1-NEXT: Name: 'void'
+// CHECK-1-NEXT: ...
+
+// RUN: cat %t/docs/./AnonClass.yaml | FileCheck %s --check-prefix CHECK-2
+// CHECK-2: ---
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Name: 'AnonClass'
+// CHECK-2-NEXT: Namespace:
+// CHECK-2-NEXT: - Type: Namespace
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: DefLocation:
+// CHECK-2-NEXT: LineNumber: 78
+// CHECK-2-NEXT: Filename: 'test'
+// CHECK-2-NEXT: TagType: Class
+// CHECK-2-NEXT: Members:
+// CHECK-2-NEXT: - Type:
+// CHECK-2-NEXT: Name: 'int'
+// CHECK-2-NEXT: Name: 'anonPublicField'
+// CHECK-2-NEXT: - Type:
+// CHECK-2-NEXT: Name: 'int'
+// CHECK-2-NEXT: Name: 'anonProtectedField'
+// CHECK-2-NEXT: Access: Protected
+// CHECK-2-NEXT: - Type:
+// CHECK-2-NEXT: Name: 'int'
+// CHECK-2-NEXT: Name: 'anonPrivateField'
+// CHECK-2-NEXT: Access: Private
+// CHECK-2-NEXT: ChildFunctions:
+// CHECK-2-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Name: 'anonPublicMethod'
+// CHECK-2-NEXT: Namespace:
+// CHECK-2-NEXT: - Type: Record
+// CHECK-2-NEXT: Name: 'AnonClass'
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: - Type: Namespace
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Location:
+// CHECK-2-NEXT: - LineNumber: 80
+// CHECK-2-NEXT: Filename: 'test'
+// CHECK-2-NEXT: IsMethod: true
+// CHECK-2-NEXT: Parent:
+// CHECK-2-NEXT: Type: Record
+// CHECK-2-NEXT: Name: 'AnonClass'
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: ReturnType:
+// CHECK-2-NEXT: Type:
+// CHECK-2-NEXT: Name: 'void'
+// CHECK-2-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Name: 'anonProtectedMethod'
+// CHECK-2-NEXT: Namespace:
+// CHECK-2-NEXT: - Type: Record
+// CHECK-2-NEXT: Name: 'AnonClass'
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: - Type: Namespace
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Location:
+// CHECK-2-NEXT: - LineNumber: 84
+// CHECK-2-NEXT: Filename: 'test'
+// CHECK-2-NEXT: IsMethod: true
+// CHECK-2-NEXT: Parent:
+// CHECK-2-NEXT: Type: Record
+// CHECK-2-NEXT: Name: 'AnonClass'
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: ReturnType:
+// CHECK-2-NEXT: Type:
+// CHECK-2-NEXT: Name: 'void'
+// CHECK-2-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Name: 'anonPrivateMethod'
+// CHECK-2-NEXT: Namespace:
+// CHECK-2-NEXT: - Type: Record
+// CHECK-2-NEXT: Name: 'AnonClass'
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: - Type: Namespace
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Location:
+// CHECK-2-NEXT: - LineNumber: 88
+// CHECK-2-NEXT: Filename: 'test'
+// CHECK-2-NEXT: IsMethod: true
+// CHECK-2-NEXT: Parent:
+// CHECK-2-NEXT: Type: Record
+// CHECK-2-NEXT: Name: 'AnonClass'
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: ReturnType:
+// CHECK-2-NEXT: Type:
+// CHECK-2-NEXT: Name: 'void'
+// CHECK-2-NEXT: ...
+
+// RUN: cat %t/docs/./GlobalNamespace.yaml | FileCheck %s --check-prefix CHECK-3
+// CHECK-3: ---
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: ChildFunctions:
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'anonFunction'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Namespace
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Location:
+// CHECK-3-NEXT: - LineNumber: 92
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'anonStaticFunction'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Namespace
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Location:
+// CHECK-3-NEXT: - LineNumber: 93
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'anonInlineFunction'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Namespace
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Location:
+// CHECK-3-NEXT: - LineNumber: 94
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: ...
+
+// RUN: cat %t/docs/staticFunctionWithInnerClass/InnerClass.yaml | FileCheck %s --check-prefix CHECK-4
+// CHECK-4: ---
+// CHECK-4-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-4-NEXT: Name: 'InnerClass'
+// CHECK-4-NEXT: Namespace:
+// CHECK-4-NEXT: - Type: Function
+// CHECK-4-NEXT: Name: 'staticFunctionWithInnerClass'
+// CHECK-4-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-4-NEXT: DefLocation:
+// CHECK-4-NEXT: LineNumber: 69
+// CHECK-4-NEXT: Filename: 'test'
+// CHECK-4-NEXT: TagType: Class
+// CHECK-4-NEXT: ChildFunctions:
+// CHECK-4-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-4-NEXT: Name: 'innerPublicMethod'
+// CHECK-4-NEXT: Namespace:
+// CHECK-4-NEXT: - Type: Record
+// CHECK-4-NEXT: Name: 'InnerClass'
+// CHECK-4-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-4-NEXT: - Type: Function
+// CHECK-4-NEXT: Name: 'staticFunctionWithInnerClass'
+// CHECK-4-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-4-NEXT: DefLocation:
+// CHECK-4-NEXT: LineNumber: 71
+// CHECK-4-NEXT: Filename: 'test'
+// CHECK-4-NEXT: IsMethod: true
+// CHECK-4-NEXT: Parent:
+// CHECK-4-NEXT: Type: Record
+// CHECK-4-NEXT: Name: 'InnerClass'
+// CHECK-4-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-4-NEXT: ReturnType:
+// CHECK-4-NEXT: Type:
+// CHECK-4-NEXT: Name: 'int'
+// CHECK-4-NEXT: ...
+
+// RUN: cat %t/docs/named/NamedClass.yaml | FileCheck %s --check-prefix CHECK-5
+// CHECK-5: ---
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: Name: 'NamedClass'
+// CHECK-5-NEXT: Namespace:
+// CHECK-5-NEXT: - Type: Namespace
+// CHECK-5-NEXT: Name: 'named'
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: DefLocation:
+// CHECK-5-NEXT: LineNumber: 47
+// CHECK-5-NEXT: Filename: 'test'
+// CHECK-5-NEXT: TagType: Class
+// CHECK-5-NEXT: Members:
+// CHECK-5-NEXT: - Type:
+// CHECK-5-NEXT: Name: 'int'
+// CHECK-5-NEXT: Name: 'namedPublicField'
+// CHECK-5-NEXT: - Type:
+// CHECK-5-NEXT: Name: 'int'
+// CHECK-5-NEXT: Name: 'namedProtectedField'
+// CHECK-5-NEXT: Access: Protected
+// CHECK-5-NEXT: - Type:
+// CHECK-5-NEXT: Name: 'int'
+// CHECK-5-NEXT: Name: 'namedPrivateField'
+// CHECK-5-NEXT: Access: Private
+// CHECK-5-NEXT: ChildFunctions:
+// CHECK-5-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: Name: 'namedPublicMethod'
+// CHECK-5-NEXT: Namespace:
+// CHECK-5-NEXT: - Type: Record
+// CHECK-5-NEXT: Name: 'NamedClass'
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: - Type: Namespace
+// CHECK-5-NEXT: Name: 'named'
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: Location:
+// CHECK-5-NEXT: - LineNumber: 49
+// CHECK-5-NEXT: Filename: 'test'
+// CHECK-5-NEXT: IsMethod: true
+// CHECK-5-NEXT: Parent:
+// CHECK-5-NEXT: Type: Record
+// CHECK-5-NEXT: Name: 'NamedClass'
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: ReturnType:
+// CHECK-5-NEXT: Type:
+// CHECK-5-NEXT: Name: 'void'
+// CHECK-5-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: Name: 'namedProtectedMethod'
+// CHECK-5-NEXT: Namespace:
+// CHECK-5-NEXT: - Type: Record
+// CHECK-5-NEXT: Name: 'NamedClass'
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: - Type: Namespace
+// CHECK-5-NEXT: Name: 'named'
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: Location:
+// CHECK-5-NEXT: - LineNumber: 53
+// CHECK-5-NEXT: Filename: 'test'
+// CHECK-5-NEXT: IsMethod: true
+// CHECK-5-NEXT: Parent:
+// CHECK-5-NEXT: Type: Record
+// CHECK-5-NEXT: Name: 'NamedClass'
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: ReturnType:
+// CHECK-5-NEXT: Type:
+// CHECK-5-NEXT: Name: 'void'
+// CHECK-5-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: Name: 'namedPrivateMethod'
+// CHECK-5-NEXT: Namespace:
+// CHECK-5-NEXT: - Type: Record
+// CHECK-5-NEXT: Name: 'NamedClass'
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: - Type: Namespace
+// CHECK-5-NEXT: Name: 'named'
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: Location:
+// CHECK-5-NEXT: - LineNumber: 57
+// CHECK-5-NEXT: Filename: 'test'
+// CHECK-5-NEXT: IsMethod: true
+// CHECK-5-NEXT: Parent:
+// CHECK-5-NEXT: Type: Record
+// CHECK-5-NEXT: Name: 'NamedClass'
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: ReturnType:
+// CHECK-5-NEXT: Type:
+// CHECK-5-NEXT: Name: 'void'
+// CHECK-5-NEXT: ...
+
+// RUN: cat %t/docs/functionWithInnerClass/InnerClass.yaml | FileCheck %s --check-prefix CHECK-6
+// CHECK-6: ---
+// CHECK-6-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: Name: 'InnerClass'
+// CHECK-6-NEXT: Namespace:
+// CHECK-6-NEXT: - Type: Function
+// CHECK-6-NEXT: Name: 'functionWithInnerClass'
+// CHECK-6-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: DefLocation:
+// CHECK-6-NEXT: LineNumber: 15
+// CHECK-6-NEXT: Filename: 'test'
+// CHECK-6-NEXT: TagType: Class
+// CHECK-6-NEXT: ChildFunctions:
+// CHECK-6-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: Name: 'innerPublicMethod'
+// CHECK-6-NEXT: Namespace:
+// CHECK-6-NEXT: - Type: Record
+// CHECK-6-NEXT: Name: 'InnerClass'
+// CHECK-6-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: - Type: Function
+// CHECK-6-NEXT: Name: 'functionWithInnerClass'
+// CHECK-6-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: DefLocation:
+// CHECK-6-NEXT: LineNumber: 17
+// CHECK-6-NEXT: Filename: 'test'
+// CHECK-6-NEXT: IsMethod: true
+// CHECK-6-NEXT: Parent:
+// CHECK-6-NEXT: Type: Record
+// CHECK-6-NEXT: Name: 'InnerClass'
+// CHECK-6-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: ReturnType:
+// CHECK-6-NEXT: Type:
+// CHECK-6-NEXT: Name: 'int'
+// CHECK-6-NEXT: ...
+
+// RUN: cat %t/docs/inlinedFunctionWithInnerClass/InnerClass.yaml | FileCheck %s --check-prefix CHECK-7
+// CHECK-7: ---
+// CHECK-7-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-7-NEXT: Name: 'InnerClass'
+// CHECK-7-NEXT: Namespace:
+// CHECK-7-NEXT: - Type: Function
+// CHECK-7-NEXT: Name: 'inlinedFunctionWithInnerClass'
+// CHECK-7-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-7-NEXT: DefLocation:
+// CHECK-7-NEXT: LineNumber: 24
+// CHECK-7-NEXT: Filename: 'test'
+// CHECK-7-NEXT: TagType: Class
+// CHECK-7-NEXT: ChildFunctions:
+// CHECK-7-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-7-NEXT: Name: 'innerPublicMethod'
+// CHECK-7-NEXT: Namespace:
+// CHECK-7-NEXT: - Type: Record
+// CHECK-7-NEXT: Name: 'InnerClass'
+// CHECK-7-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-7-NEXT: - Type: Function
+// CHECK-7-NEXT: Name: 'inlinedFunctionWithInnerClass'
+// CHECK-7-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-7-NEXT: DefLocation:
+// CHECK-7-NEXT: LineNumber: 26
+// CHECK-7-NEXT: Filename: 'test'
+// CHECK-7-NEXT: IsMethod: true
+// CHECK-7-NEXT: Parent:
+// CHECK-7-NEXT: Type: Record
+// CHECK-7-NEXT: Name: 'InnerClass'
+// CHECK-7-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-7-NEXT: ReturnType:
+// CHECK-7-NEXT: Type:
+// CHECK-7-NEXT: Name: 'int'
+// CHECK-7-NEXT: ...
diff --git a/test/clang-doc/yaml-module.cpp b/test/clang-doc/yaml-module.cpp
new file mode 100644
index 00000000..80602aca
--- /dev/null
+++ b/test/clang-doc/yaml-module.cpp
@@ -0,0 +1,63 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+export module M;
+
+int moduleFunction(int x); // ModuleLinkage
+
+static int staticModuleFunction(int x); // ModuleInternalLinkage
+
+export double exportedModuleFunction(double y, int z); // ExternalLinkage
+
+// RUN: clang-doc --format=yaml --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: cat %t/docs/./GlobalNamespace.yaml | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: ---
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: ChildFunctions:
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'moduleFunction'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 11
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Params:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'x'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'staticModuleFunction'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 13
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Params:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'x'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'exportedModuleFunction'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 15
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Params:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'double'
+// CHECK-0-NEXT: Name: 'y'
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'z'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'double'
+// CHECK-0-NEXT: ...
diff --git a/test/clang-doc/yaml-namespace.cpp b/test/clang-doc/yaml-namespace.cpp
new file mode 100644
index 00000000..d187f7e3
--- /dev/null
+++ b/test/clang-doc/yaml-namespace.cpp
@@ -0,0 +1,96 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+namespace A {
+
+void f();
+
+} // namespace A
+
+namespace A {
+
+void f(){};
+
+namespace B {
+
+enum E { X };
+
+E func(int i) { return X; }
+
+} // namespace B
+} // namespace A
+
+// RUN: clang-doc --format=yaml --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: cat %t/docs/./A.yaml | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: ---
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'A'
+// CHECK-0-NEXT: ChildFunctions:
+// CHECK-0-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'f'
+// CHECK-0-NEXT: Namespace:
+// CHECK-0-NEXT: - Type: Namespace
+// CHECK-0-NEXT: Name: 'A'
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: DefLocation:
+// CHECK-0-NEXT: LineNumber: 17
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Location:
+// CHECK-0-NEXT: - LineNumber: 11
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: ReturnType:
+// CHECK-0-NEXT: Type:
+// CHECK-0-NEXT: Name: 'void'
+// CHECK-0-NEXT: ...
+
+// RUN: cat %t/docs/A/B.yaml | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: ---
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'B'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'A'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: ChildFunctions:
+// CHECK-1-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'func'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'B'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'A'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: DefLocation:
+// CHECK-1-NEXT: LineNumber: 23
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: Params:
+// CHECK-1-NEXT: - Type:
+// CHECK-1-NEXT: Name: 'int'
+// CHECK-1-NEXT: Name: 'i'
+// CHECK-1-NEXT: ReturnType:
+// CHECK-1-NEXT: Type:
+// CHECK-1-NEXT: Name: 'enum A::B::E'
+// CHECK-1-NEXT: ChildEnums:
+// CHECK-1-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'E'
+// CHECK-1-NEXT: Namespace:
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'B'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: - Type: Namespace
+// CHECK-1-NEXT: Name: 'A'
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: DefLocation:
+// CHECK-1-NEXT: LineNumber: 21
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: Members:
+// CHECK-1-NEXT: - 'X'
+// CHECK-1-NEXT: ...
diff --git a/test/clang-doc/yaml-record.cpp b/test/clang-doc/yaml-record.cpp
new file mode 100644
index 00000000..8fad2201
--- /dev/null
+++ b/test/clang-doc/yaml-record.cpp
@@ -0,0 +1,236 @@
+// THIS IS A GENERATED TEST. DO NOT EDIT.
+// To regenerate, see clang-doc/gen_test.py docstring.
+//
+// This test requires Linux due to system-dependent USR for the inner class.
+// REQUIRES: system-linux
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: echo "" > %t/compile_flags.txt
+// RUN: cp "%s" "%t/test.cpp"
+
+void H() {
+ class I {};
+}
+
+union A { int X; int Y; };
+
+enum B { X, Y };
+
+enum class Bc { A, B };
+
+struct C { int i; };
+
+class D {};
+
+class E {
+public:
+ E() {}
+ ~E() {}
+
+protected:
+ void ProtectedMethod();
+};
+
+void E::ProtectedMethod() {}
+
+class F : virtual private D, public E {};
+
+class X {
+ class Y {};
+};
+
+// RUN: clang-doc --format=yaml --doxygen --extra-arg=-fmodules-ts -p %t %t/test.cpp -output=%t/docs
+
+
+// RUN: cat %t/docs/./C.yaml | FileCheck %s --check-prefix CHECK-0
+// CHECK-0: ---
+// CHECK-0-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-0-NEXT: Name: 'C'
+// CHECK-0-NEXT: DefLocation:
+// CHECK-0-NEXT: LineNumber: 21
+// CHECK-0-NEXT: Filename: 'test'
+// CHECK-0-NEXT: Members:
+// CHECK-0-NEXT: - Type:
+// CHECK-0-NEXT: Name: 'int'
+// CHECK-0-NEXT: Name: 'i'
+// CHECK-0-NEXT: ...
+
+// RUN: cat %t/docs/./A.yaml | FileCheck %s --check-prefix CHECK-1
+// CHECK-1: ---
+// CHECK-1-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-1-NEXT: Name: 'A'
+// CHECK-1-NEXT: DefLocation:
+// CHECK-1-NEXT: LineNumber: 15
+// CHECK-1-NEXT: Filename: 'test'
+// CHECK-1-NEXT: TagType: Union
+// CHECK-1-NEXT: Members:
+// CHECK-1-NEXT: - Type:
+// CHECK-1-NEXT: Name: 'int'
+// CHECK-1-NEXT: Name: 'X'
+// CHECK-1-NEXT: - Type:
+// CHECK-1-NEXT: Name: 'int'
+// CHECK-1-NEXT: Name: 'Y'
+// CHECK-1-NEXT: ...
+
+// RUN: cat %t/docs/./F.yaml | FileCheck %s --check-prefix CHECK-2
+// CHECK-2: ---
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: Name: 'F'
+// CHECK-2-NEXT: DefLocation:
+// CHECK-2-NEXT: LineNumber: 36
+// CHECK-2-NEXT: Filename: 'test'
+// CHECK-2-NEXT: TagType: Class
+// CHECK-2-NEXT: Parents:
+// CHECK-2-NEXT: - Type: Record
+// CHECK-2-NEXT: Name: 'E'
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: VirtualParents:
+// CHECK-2-NEXT: - Type: Record
+// CHECK-2-NEXT: Name: 'D'
+// CHECK-2-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-2-NEXT: ...
+
+// RUN: cat %t/docs/./E.yaml | FileCheck %s --check-prefix CHECK-3
+// CHECK-3: ---
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: DefLocation:
+// CHECK-3-NEXT: LineNumber: 25
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: TagType: Class
+// CHECK-3-NEXT: ChildFunctions:
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: DefLocation:
+// CHECK-3-NEXT: LineNumber: 27
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: IsMethod: true
+// CHECK-3-NEXT: Parent:
+// CHECK-3-NEXT: Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: '~E'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: DefLocation:
+// CHECK-3-NEXT: LineNumber: 28
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: IsMethod: true
+// CHECK-3-NEXT: Parent:
+// CHECK-3-NEXT: Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: Name: 'ProtectedMethod'
+// CHECK-3-NEXT: Namespace:
+// CHECK-3-NEXT: - Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: DefLocation:
+// CHECK-3-NEXT: LineNumber: 34
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: Location:
+// CHECK-3-NEXT: - LineNumber: 31
+// CHECK-3-NEXT: Filename: 'test'
+// CHECK-3-NEXT: IsMethod: true
+// CHECK-3-NEXT: Parent:
+// CHECK-3-NEXT: Type: Record
+// CHECK-3-NEXT: Name: 'E'
+// CHECK-3-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-3-NEXT: ReturnType:
+// CHECK-3-NEXT: Type:
+// CHECK-3-NEXT: Name: 'void'
+// CHECK-3-NEXT: ...
+
+// RUN: cat %t/docs/./D.yaml | FileCheck %s --check-prefix CHECK-4
+// CHECK-4: ---
+// CHECK-4-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-4-NEXT: Name: 'D'
+// CHECK-4-NEXT: DefLocation:
+// CHECK-4-NEXT: LineNumber: 23
+// CHECK-4-NEXT: Filename: 'test'
+// CHECK-4-NEXT: TagType: Class
+// CHECK-4-NEXT: ...
+
+// RUN: cat %t/docs/./X.yaml | FileCheck %s --check-prefix CHECK-5
+// CHECK-5: ---
+// CHECK-5-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-5-NEXT: Name: 'X'
+// CHECK-5-NEXT: DefLocation:
+// CHECK-5-NEXT: LineNumber: 38
+// CHECK-5-NEXT: Filename: 'test'
+// CHECK-5-NEXT: TagType: Class
+// CHECK-5-NEXT: ...
+
+// RUN: cat %t/docs/./GlobalNamespace.yaml | FileCheck %s --check-prefix CHECK-6
+// CHECK-6: ---
+// CHECK-6-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: ChildFunctions:
+// CHECK-6-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: Name: 'H'
+// CHECK-6-NEXT: DefLocation:
+// CHECK-6-NEXT: LineNumber: 11
+// CHECK-6-NEXT: Filename: 'test'
+// CHECK-6-NEXT: ReturnType:
+// CHECK-6-NEXT: Type:
+// CHECK-6-NEXT: Name: 'void'
+// CHECK-6-NEXT: ChildEnums:
+// CHECK-6-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: Name: 'B'
+// CHECK-6-NEXT: DefLocation:
+// CHECK-6-NEXT: LineNumber: 17
+// CHECK-6-NEXT: Filename: 'test'
+// CHECK-6-NEXT: Members:
+// CHECK-6-NEXT: - 'X'
+// CHECK-6-NEXT: - 'Y'
+// CHECK-6-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-6-NEXT: Name: 'Bc'
+// CHECK-6-NEXT: DefLocation:
+// CHECK-6-NEXT: LineNumber: 19
+// CHECK-6-NEXT: Filename: 'test'
+// CHECK-6-NEXT: Scoped: true
+// CHECK-6-NEXT: Members:
+// CHECK-6-NEXT: - 'A'
+// CHECK-6-NEXT: - 'B'
+// CHECK-6-NEXT: ...
+
+// RUN: cat %t/docs/H/I.yaml | FileCheck %s --check-prefix CHECK-7
+// CHECK-7: ---
+// CHECK-7-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-7-NEXT: Name: 'I'
+// CHECK-7-NEXT: Namespace:
+// CHECK-7-NEXT: - Type: Function
+// CHECK-7-NEXT: Name: 'H'
+// CHECK-7-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-7-NEXT: DefLocation:
+// CHECK-7-NEXT: LineNumber: 12
+// CHECK-7-NEXT: Filename: 'test'
+// CHECK-7-NEXT: TagType: Class
+// CHECK-7-NEXT: ...
+
+// RUN: cat %t/docs/X/Y.yaml | FileCheck %s --check-prefix CHECK-8
+// CHECK-8: ---
+// CHECK-8-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-8-NEXT: Name: 'Y'
+// CHECK-8-NEXT: Namespace:
+// CHECK-8-NEXT: - Type: Record
+// CHECK-8-NEXT: Name: 'X'
+// CHECK-8-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}'
+// CHECK-8-NEXT: DefLocation:
+// CHECK-8-NEXT: LineNumber: 39
+// CHECK-8-NEXT: Filename: 'test'
+// CHECK-8-NEXT: TagType: Class
+// CHECK-8-NEXT: ...
diff --git a/test/clang-tidy/abseil-string-find-startswith.cpp b/test/clang-tidy/abseil-string-find-startswith.cpp
index 194e795b..7d5fd73c 100644
--- a/test/clang-tidy/abseil-string-find-startswith.cpp
+++ b/test/clang-tidy/abseil-string-find-startswith.cpp
@@ -1,4 +1,6 @@
-// RUN: %check_clang_tidy %s abseil-string-find-startswith %t
+// RUN: %check_clang_tidy %s abseil-string-find-startswith %t -- \
+// RUN: -config="{CheckOptions: [{key: 'abseil-string-find-startswith.StringLikeClasses', value: '::std::basic_string;::basic_string'}]}" \
+// RUN: -- -std=c++11
namespace std {
template <typename T> class allocator {};
@@ -15,14 +17,23 @@ struct basic_string {
};
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
+
+struct cxx_string {
+ int find(const char *s, int pos = 0);
+};
} // namespace std
+struct basic_string : public std::cxx_string {
+ basic_string();
+};
+typedef basic_string global_string;
+
std::string foo(std::string);
std::string bar();
#define A_MACRO(x, y) ((x) == (y))
-void tests(std::string s) {
+void tests(std::string s, global_string s2) {
s.find("a") == 0;
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use absl::StartsWith instead of find() == 0 [abseil-string-find-startswith]
// CHECK-FIXES: {{^[[:space:]]*}}absl::StartsWith(s, "a");{{$}}
@@ -47,6 +58,10 @@ void tests(std::string s) {
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use !absl::StartsWith
// CHECK-FIXES: {{^[[:space:]]*}}!absl::StartsWith(s, "a");{{$}}
+ s2.find("a") == 0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use absl::StartsWith
+ // CHECK-FIXES: {{^[[:space:]]*}}absl::StartsWith(s2, "a");{{$}}
+
// expressions that don't trigger the check are here.
A_MACRO(s.find("a"), 0);
s.find("a", 1) == 0;
diff --git a/test/clang-tidy/bugprone-exception-escape.cpp b/test/clang-tidy/bugprone-exception-escape.cpp
new file mode 100644
index 00000000..af2c23d4
--- /dev/null
+++ b/test/clang-tidy/bugprone-exception-escape.cpp
@@ -0,0 +1,265 @@
+// RUN: %check_clang_tidy %s bugprone-exception-escape %t -- -extra-arg=-std=c++11 -extra-arg=-fexceptions -config="{CheckOptions: [{key: bugprone-exception-escape.IgnoredExceptions, value: 'ignored1,ignored2'}, {key: bugprone-exception-escape.FunctionsThatShouldNotThrow, value: 'enabled1,enabled2,enabled3'}]}" --
+
+struct throwing_destructor {
+ ~throwing_destructor() {
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: an exception may be thrown in function '~throwing_destructor' which should not throw exceptions
+ throw 1;
+ }
+};
+
+struct throwing_move_constructor {
+ throwing_move_constructor(throwing_move_constructor&&) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: an exception may be thrown in function 'throwing_move_constructor' which should not throw exceptions
+ throw 1;
+ }
+};
+
+struct throwing_move_assignment {
+ throwing_move_assignment& operator=(throwing_move_assignment&&) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: an exception may be thrown in function 'operator=' which should not throw exceptions
+ throw 1;
+ }
+};
+
+void throwing_noexcept() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throwing_noexcept' which should not throw exceptions
+ throw 1;
+}
+
+void throwing_throw_nothing() throw() {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throwing_throw_nothing' which should not throw exceptions
+ throw 1;
+}
+
+void throw_and_catch() noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_and_catch' which should not throw exceptions
+ try {
+ throw 1;
+ } catch(int &) {
+ }
+}
+
+void throw_and_catch_some(int n) noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_and_catch_some' which should not throw exceptions
+ try {
+ if (n) throw 1;
+ throw 1.1;
+ } catch(int &) {
+ }
+}
+
+void throw_and_catch_each(int n) noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_and_catch_each' which should not throw exceptions
+ try {
+ if (n) throw 1;
+ throw 1.1;
+ } catch(int &) {
+ } catch(double &) {
+ }
+}
+
+void throw_and_catch_all(int n) noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_and_catch_all' which should not throw exceptions
+ try {
+ if (n) throw 1;
+ throw 1.1;
+ } catch(...) {
+ }
+}
+
+void throw_and_rethrow() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_and_rethrow' which should not throw exceptions
+ try {
+ throw 1;
+ } catch(int &) {
+ throw;
+ }
+}
+
+void throw_catch_throw() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_catch_throw' which should not throw exceptions
+ try {
+ throw 1;
+ } catch(int &) {
+ throw 2;
+ }
+}
+
+void throw_catch_rethrow_the_rest(int n) noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_catch_rethrow_the_rest' which should not throw exceptions
+ try {
+ if (n) throw 1;
+ throw 1.1;
+ } catch(int &) {
+ } catch(...) {
+ throw;
+ }
+}
+
+class base {};
+class derived: public base {};
+
+void throw_derived_catch_base() noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_derived_catch_base' which should not throw exceptions
+ try {
+ throw derived();
+ } catch(base &) {
+ }
+}
+
+void try_nested_try(int n) noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'try_nested_try' which should not throw exceptions
+ try {
+ try {
+ if (n) throw 1;
+ throw 1.1;
+ } catch(int &) {
+ }
+ } catch(double &) {
+ }
+}
+
+void bad_try_nested_try(int n) noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'bad_try_nested_try' which should not throw exceptions
+ try {
+ if (n) throw 1;
+ try {
+ throw 1.1;
+ } catch(int &) {
+ }
+ } catch(double &) {
+ }
+}
+
+void try_nested_catch() noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'try_nested_catch' which should not throw exceptions
+ try {
+ try {
+ throw 1;
+ } catch(int &) {
+ throw 1.1;
+ }
+ } catch(double &) {
+ }
+}
+
+void catch_nested_try() noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'catch_nested_try' which should not throw exceptions
+ try {
+ throw 1;
+ } catch(int &) {
+ try {
+ throw 1;
+ } catch(int &) {
+ }
+ }
+}
+
+void bad_catch_nested_try() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'bad_catch_nested_try' which should not throw exceptions
+ try {
+ throw 1;
+ } catch(int &) {
+ try {
+ throw 1.1;
+ } catch(int &) {
+ }
+ } catch(double &) {
+ }
+}
+
+void implicit_int_thrower() {
+ throw 1;
+}
+
+void explicit_int_thrower() throw(int);
+
+void indirect_implicit() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'indirect_implicit' which should not throw exceptions
+ implicit_int_thrower();
+}
+
+void indirect_explicit() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'indirect_explicit' which should not throw exceptions
+ explicit_int_thrower();
+}
+
+void indirect_catch() noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'indirect_catch' which should not throw exceptions
+ try {
+ implicit_int_thrower();
+ } catch(int&) {
+ }
+}
+
+template<typename T>
+void dependent_throw() noexcept(sizeof(T)<4) {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'dependent_throw' which should not throw exceptions
+ if (sizeof(T) > 4)
+ throw 1;
+}
+
+void swap(int&, int&) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'swap' which should not throw exceptions
+ throw 1;
+}
+
+namespace std {
+class bad_alloc {};
+}
+
+void alloc() {
+ throw std::bad_alloc();
+}
+
+void allocator() noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'allocator' which should not throw exceptions
+ alloc();
+}
+
+void enabled1() {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'enabled1' which should not throw exceptions
+ throw 1;
+}
+
+void enabled2() {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'enabled2' which should not throw exceptions
+ enabled1();
+}
+
+void enabled3() {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'enabled3' which should not throw exceptions
+ try {
+ enabled1();
+ } catch(...) {
+ }
+}
+
+class ignored1 {};
+class ignored2 {};
+
+void this_does_not_count() noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'this_does_not_count' which should not throw exceptions
+ throw ignored1();
+}
+
+void this_does_not_count_either(int n) noexcept {
+ // CHECK-MESSAGES-NOT: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'this_does_not_count_either' which should not throw exceptions
+ try {
+ throw 1;
+ if (n) throw ignored2();
+ } catch(int &) {
+ }
+}
+
+void this_counts(int n) noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'this_counts' which should not throw exceptions
+ if (n) throw 1;
+ throw ignored1();
+}
+
+int main() {
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: an exception may be thrown in function 'main' which should not throw exceptions
+ throw 1;
+ return 0;
+}
diff --git a/test/clang-tidy/bugprone-use-after-move.cpp b/test/clang-tidy/bugprone-use-after-move.cpp
index 43e63221..9307f17f 100644
--- a/test/clang-tidy/bugprone-use-after-move.cpp
+++ b/test/clang-tidy/bugprone-use-after-move.cpp
@@ -1,4 +1,4 @@
-// RUN: %check_clang_tidy %s bugprone-use-after-move %t -- -- -std=c++11 -fno-delayed-template-parsing
+// RUN: %check_clang_tidy %s bugprone-use-after-move %t -- -- -std=c++17 -fno-delayed-template-parsing
typedef decltype(nullptr) nullptr_t;
@@ -1132,13 +1132,30 @@ void forRangeSequences() {
}
}
-// If a variable is declared in an if statement, the declaration of the variable
-// (which is treated like a reinitialization by the check) is sequenced before
-// the evaluation of the condition (which constitutes a use).
-void ifStmtSequencesDeclAndCondition() {
+// If a variable is declared in an if, while or switch statement, the init
+// statement (for if and switch) is sequenced before the variable declaration,
+// which in turn is sequenced before the evaluation of the condition.
+void ifWhileAndSwitchSequenceInitDeclAndCondition() {
for (int i = 0; i < 10; ++i) {
- if (A a = A()) {
- std::move(a);
+ A a1;
+ if (A a2 = std::move(a1)) {
+ std::move(a2);
+ }
+ }
+ for (int i = 0; i < 10; ++i) {
+ A a1;
+ if (A a2 = std::move(a1); A a3 = std::move(a2)) {
+ std::move(a3);
+ }
+ }
+ while (A a = A()) {
+ std::move(a);
+ }
+ for (int i = 0; i < 10; ++i) {
+ A a1;
+ switch (A a2 = a1; A a3 = std::move(a2)) {
+ case true:
+ std::move(a3);
}
}
}
diff --git a/test/clang-tidy/cert-msc32-c.c b/test/clang-tidy/cert-msc32-c.c
new file mode 100644
index 00000000..6cc40fa2
--- /dev/null
+++ b/test/clang-tidy/cert-msc32-c.c
@@ -0,0 +1,27 @@
+// RUN: %check_clang_tidy %s cert-msc32-c %t -- -config="{CheckOptions: [{key: cert-msc32-c.DisallowedSeedTypes, value: 'some_type,time_t'}]}" -- -std=c99
+
+void srand(int seed);
+typedef int time_t;
+time_t time(time_t *t);
+
+void f() {
+ srand(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc32-c]
+
+ const int a = 1;
+ srand(a);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc32-c]
+
+ time_t t;
+ srand(time(&t)); // Disallowed seed type
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc32-c]
+}
+
+void g() {
+ typedef int user_t;
+ user_t a = 1;
+ srand(a);
+
+ int b = 1;
+ srand(b); // Can not evaluate as int
+}
diff --git a/test/clang-tidy/cert-msc51-cpp.cpp b/test/clang-tidy/cert-msc51-cpp.cpp
new file mode 100644
index 00000000..8a8d778e
--- /dev/null
+++ b/test/clang-tidy/cert-msc51-cpp.cpp
@@ -0,0 +1,210 @@
+// RUN: %check_clang_tidy %s cert-msc51-cpp %t -- -config="{CheckOptions: [{key: cert-msc51-cpp.DisallowedSeedTypes, value: 'some_type,time_t'}]}" -- -std=c++11
+
+namespace std {
+
+void srand(int seed);
+
+template <class UIntType, UIntType a, UIntType c, UIntType m>
+struct linear_congruential_engine {
+ linear_congruential_engine(int _ = 0);
+ void seed(int _ = 0);
+};
+using default_random_engine = linear_congruential_engine<unsigned int, 1, 2, 3>;
+
+using size_t = int;
+template <class UIntType, size_t w, size_t n, size_t m, size_t r,
+ UIntType a, size_t u, UIntType d, size_t s,
+ UIntType b, size_t t,
+ UIntType c, size_t l, UIntType f>
+struct mersenne_twister_engine {
+ mersenne_twister_engine(int _ = 0);
+ void seed(int _ = 0);
+};
+using mt19937 = mersenne_twister_engine<unsigned int, 32, 624, 397, 21, 0x9908b0df, 11, 0xffffffff, 7, 0x9d2c5680, 15, 0xefc60000, 18, 1812433253>;
+
+template <class UIntType, size_t w, size_t s, size_t r>
+struct subtract_with_carry_engine {
+ subtract_with_carry_engine(int _ = 0);
+ void seed(int _ = 0);
+};
+using ranlux24_base = subtract_with_carry_engine<unsigned int, 24, 10, 24>;
+
+template <class Engine, size_t p, size_t r>
+struct discard_block_engine {
+ discard_block_engine();
+ discard_block_engine(int _);
+ void seed();
+ void seed(int _);
+};
+using ranlux24 = discard_block_engine<ranlux24_base, 223, 23>;
+
+template <class Engine, size_t w, class UIntType>
+struct independent_bits_engine {
+ independent_bits_engine();
+ independent_bits_engine(int _);
+ void seed();
+ void seed(int _);
+};
+using independent_bits = independent_bits_engine<ranlux24_base, 223, int>;
+
+template <class Engine, size_t k>
+struct shuffle_order_engine {
+ shuffle_order_engine();
+ shuffle_order_engine(int _);
+ void seed();
+ void seed(int _);
+};
+using shuffle_order = shuffle_order_engine<ranlux24_base, 223>;
+
+struct random_device {
+ random_device();
+ int operator()();
+};
+} // namespace std
+
+using time_t = unsigned int;
+time_t time(time_t *t);
+
+void f() {
+ const int seed = 2;
+ time_t t;
+
+ std::srand(0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::srand(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::srand(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+
+ // One instantiation for every engine
+ std::default_random_engine engine1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:30: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ std::default_random_engine engine2(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:30: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::default_random_engine engine3(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:30: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::default_random_engine engine4(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:30: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine1.seed();
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ engine1.seed(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine1.seed(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine1.seed(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+
+ std::mt19937 engine5;
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ std::mt19937 engine6(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::mt19937 engine7(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::mt19937 engine8(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine5.seed();
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ engine5.seed(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine5.seed(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine5.seed(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+
+ std::ranlux24_base engine9;
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ std::ranlux24_base engine10(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::ranlux24_base engine11(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::ranlux24_base engine12(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine9.seed();
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ engine9.seed(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine9.seed(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine9.seed(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+
+ std::ranlux24 engine13;
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ std::ranlux24 engine14(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::ranlux24 engine15(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::ranlux24 engine16(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine13.seed();
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ engine13.seed(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine13.seed(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine13.seed(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+
+ std::independent_bits engine17;
+ // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ std::independent_bits engine18(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::independent_bits engine19(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::independent_bits engine20(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine17.seed();
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ engine17.seed(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine17.seed(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine17.seed(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+
+ std::shuffle_order engine21;
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ std::shuffle_order engine22(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::shuffle_order engine23(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ std::shuffle_order engine24(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine21.seed();
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a default argument will generate a predictable sequence of values [cert-msc51-cpp]
+ engine21.seed(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine21.seed(seed);
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a constant value will generate a predictable sequence of values [cert-msc51-cpp]
+ engine21.seed(time(&t));
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: random number generator seeded with a disallowed source of seed value will generate a predictable sequence of values [cert-msc51-cpp]
+}
+
+struct A {
+ A(int _ = 0);
+ void seed(int _ = 0);
+};
+
+void g() {
+ int n = 1;
+ std::default_random_engine engine1(n);
+ std::mt19937 engine2(n);
+ std::ranlux24_base engine3(n);
+ std::ranlux24 engine4(n);
+ std::independent_bits engine5(n);
+ std::shuffle_order engine6(n);
+
+ std::random_device dev;
+ std::default_random_engine engine7(dev());
+ std::mt19937 engine8(dev());
+ std::ranlux24_base engine9(dev());
+ std::ranlux24 engine10(dev());
+ std::independent_bits engine11(dev());
+ std::shuffle_order engine12(dev());
+
+ A a1;
+ A a2(1);
+ a1.seed();
+ a1.seed(1);
+ a1.seed(n);
+}
diff --git a/test/clang-tidy/clang-tidy-enable-check-profile-one-tu.cpp b/test/clang-tidy/clang-tidy-enable-check-profile-one-tu.cpp
index 523b6eb0..df4cb93f 100644
--- a/test/clang-tidy/clang-tidy-enable-check-profile-one-tu.cpp
+++ b/test/clang-tidy/clang-tidy-enable-check-profile-one-tu.cpp
@@ -1,16 +1,22 @@
-// RUN: clang-tidy -enable-check-profile -checks='-*,readability-function-size' %s 2>&1 | FileCheck --match-full-lines -implicit-check-not='{{warning:|error:}}' %s
+// RUN: clang-tidy -enable-check-profile -checks='-*,readability-function-size' %s -- 2>&1 | FileCheck --match-full-lines -implicit-check-not='{{warning:|error:}}' %s
// CHECK: ===-------------------------------------------------------------------------===
-// CHECK-NEXT: {{.*}} --- Name ---
+// CHECK-NEXT: clang-tidy checks profiling
+// CHECK-NEXT: ===-------------------------------------------------------------------------===
+// CHECK-NEXT: Total Execution Time: {{.*}} seconds ({{.*}} wall clock)
+
+// CHECK: {{.*}} --- Name ---
// CHECK-NEXT: {{.*}} readability-function-size
// CHECK-NEXT: {{.*}} Total
-// CHECK-NEXT: ===-------------------------------------------------------------------------===
// CHECK-NOT: ===-------------------------------------------------------------------------===
+// CHECK-NOT: clang-tidy checks profiling
+// CHECK-NOT: ===-------------------------------------------------------------------------===
+// CHECK-NOT: Total Execution Time: {{.*}} seconds ({{.*}} wall clock)
+
// CHECK-NOT: {{.*}} --- Name ---
// CHECK-NOT: {{.*}} readability-function-size
// CHECK-NOT: {{.*}} Total
-// CHECK-NOT: ===-------------------------------------------------------------------------===
class A {
A() {}
diff --git a/test/clang-tidy/clang-tidy-enable-check-profile-two-tu.cpp b/test/clang-tidy/clang-tidy-enable-check-profile-two-tu.cpp
index 9777dc90..30603a2a 100644
--- a/test/clang-tidy/clang-tidy-enable-check-profile-two-tu.cpp
+++ b/test/clang-tidy/clang-tidy-enable-check-profile-two-tu.cpp
@@ -1,22 +1,31 @@
-// RUN: clang-tidy -enable-check-profile -checks='-*,readability-function-size' %s %s 2>&1 | FileCheck --match-full-lines -implicit-check-not='{{warning:|error:}}' %s
+// RUN: clang-tidy -enable-check-profile -checks='-*,readability-function-size' %s %s -- 2>&1 | FileCheck --match-full-lines -implicit-check-not='{{warning:|error:}}' %s
// CHECK: ===-------------------------------------------------------------------------===
-// CHECK-NEXT: {{.*}} --- Name ---
+// CHECK-NEXT: clang-tidy checks profiling
+// CHECK-NEXT: ===-------------------------------------------------------------------------===
+// CHECK-NEXT: Total Execution Time: {{.*}} seconds ({{.*}} wall clock)
+
+// CHECK: {{.*}} --- Name ---
// CHECK-NEXT: {{.*}} readability-function-size
// CHECK-NEXT: {{.*}} Total
-// CHECK-NEXT: ===-------------------------------------------------------------------------===
// CHECK: ===-------------------------------------------------------------------------===
-// CHECK-NEXT: {{.*}} --- Name ---
+// CHECK-NEXT: clang-tidy checks profiling
+// CHECK-NEXT: ===-------------------------------------------------------------------------===
+// CHECK-NEXT: Total Execution Time: {{.*}} seconds ({{.*}} wall clock)
+
+// CHECK: {{.*}} --- Name ---
// CHECK-NEXT: {{.*}} readability-function-size
// CHECK-NEXT: {{.*}} Total
-// CHECK-NEXT: ===-------------------------------------------------------------------------===
// CHECK-NOT: ===-------------------------------------------------------------------------===
+// CHECK-NOT: clang-tidy checks profiling
+// CHECK-NOT: ===-------------------------------------------------------------------------===
+// CHECK-NOT: Total Execution Time: {{.*}} seconds ({{.*}} wall clock)
+
// CHECK-NOT: {{.*}} --- Name ---
// CHECK-NOT: {{.*}} readability-function-size
// CHECK-NOT: {{.*}} Total
-// CHECK-NOT: ===-------------------------------------------------------------------------===
class A {
A() {}
diff --git a/test/clang-tidy/clang-tidy-store-check-profile-one-tu.cpp b/test/clang-tidy/clang-tidy-store-check-profile-one-tu.cpp
new file mode 100644
index 00000000..832723ba
--- /dev/null
+++ b/test/clang-tidy/clang-tidy-store-check-profile-one-tu.cpp
@@ -0,0 +1,37 @@
+// RUN: rm -rf %T/out
+// RUN: clang-tidy -enable-check-profile -checks='-*,readability-function-size' -store-check-profile=%T/out %s -- 2>&1 | not FileCheck --match-full-lines -implicit-check-not='{{warning:|error:}}' -check-prefix=CHECK-CONSOLE %s
+// RUN: cat %T/out/*-clang-tidy-store-check-profile-one-tu.cpp.json | FileCheck --match-full-lines -implicit-check-not='{{warning:|error:}}' -check-prefix=CHECK-FILE %s
+// RUN: rm -rf %T/out
+// RUN: clang-tidy -enable-check-profile -checks='-*,readability-function-size' -store-check-profile=%T/out %s -- 2>&1
+// RUN: cat %T/out/*-clang-tidy-store-check-profile-one-tu.cpp.json | FileCheck --match-full-lines -implicit-check-not='{{warning:|error:}}' -check-prefix=CHECK-FILE %s
+
+// CHECK-CONSOLE-NOT: ===-------------------------------------------------------------------------===
+// CHECK-CONSOLE-NOT: {{.*}} --- Name ---
+// CHECK-CONSOLE-NOT: {{.*}} readability-function-size
+// CHECK-CONSOLE-NOT: {{.*}} Total
+// CHECK-CONSOLE-NOT: ===-------------------------------------------------------------------------===
+
+// CHECK-FILE: {
+// CHECK-FILE-NEXT:"file": "{{.*}}clang-tidy-store-check-profile-one-tu.cpp",
+// CHECK-FILE-NEXT:"timestamp": "{{[0-9]+}}-{{[0-9]+}}-{{[0-9]+}} {{[0-9]+}}:{{[0-9]+}}:{{[0-9]+}}.{{[0-9]+}}",
+// CHECK-FILE-NEXT:"profile": {
+// CHECK-FILE-NEXT: "time.clang-tidy.readability-function-size.wall": {{.*}}{{[0-9]}}.{{[0-9]+}}e{{[-+]}}{{[0-9]}}{{[0-9]}},
+// CHECK-FILE-NEXT: "time.clang-tidy.readability-function-size.user": {{.*}}{{[0-9]}}.{{[0-9]+}}e{{[-+]}}{{[0-9]}}{{[0-9]}},
+// CHECK-FILE-NEXT: "time.clang-tidy.readability-function-size.sys": {{.*}}{{[0-9]}}.{{[0-9]+}}e{{[-+]}}{{[0-9]}}{{[0-9]}}
+// CHECK-FILE-NEXT: }
+// CHECK-FILE-NEXT: }
+
+// CHECK-FILE-NOT: {
+// CHECK-FILE-NOT: "file": {{.*}}clang-tidy-store-check-profile-one-tu.cpp{{.*}},
+// CHECK-FILE-NOT: "timestamp": "{{[0-9]+}}-{{[0-9]+}}-{{[0-9]+}} {{[0-9]+}}:{{[0-9]+}}:{{[0-9]+}}.{{[0-9]+}}",
+// CHECK-FILE-NOT: "profile": {
+// CHECK-FILE-NOT: "time.clang-tidy.readability-function-size.wall": {{.*}}{{[0-9]}}.{{[0-9]+}}e{{[-+]}}{{[0-9]}}{{[0-9]}},
+// CHECK-FILE-NOT: "time.clang-tidy.readability-function-size.user": {{.*}}{{[0-9]}}.{{[0-9]+}}e{{[-+]}}{{[0-9]}}{{[0-9]}},
+// CHECK-FILE-NOT: "time.clang-tidy.readability-function-size.sys": {{.*}}{{[0-9]}}.{{[0-9]+}}e{{[-+]}}{{[0-9]}}{{[0-9]}}
+// CHECK-FILE-NOT: }
+// CHECK-FILE-NOT: }
+
+class A {
+ A() {}
+ ~A() {}
+};
diff --git a/test/clang-tidy/cppcoreguidelines-pro-bounds-pointer-arithmetic-pr36489.cpp b/test/clang-tidy/cppcoreguidelines-pro-bounds-pointer-arithmetic-pr36489.cpp
new file mode 100644
index 00000000..47109522
--- /dev/null
+++ b/test/clang-tidy/cppcoreguidelines-pro-bounds-pointer-arithmetic-pr36489.cpp
@@ -0,0 +1,53 @@
+// RUN: %check_clang_tidy %s cppcoreguidelines-pro-bounds-pointer-arithmetic %t -- -- -std=c++14
+
+// Fix PR36489 and detect auto-deduced value correctly.
+char *getPtr();
+auto getPtrAuto() { return getPtr(); }
+decltype(getPtr()) getPtrDeclType();
+decltype(auto) getPtrDeclTypeAuto() { return getPtr(); }
+auto getPtrWithTrailingReturnType() -> char *;
+
+void auto_deduction_binary() {
+ auto p1 = getPtr() + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: do not use pointer arithmetic
+ auto p2 = getPtrAuto() + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: do not use pointer arithmetic
+ auto p3 = getPtrWithTrailingReturnType() + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:44: warning: do not use pointer arithmetic
+ auto p4 = getPtr();
+ auto *p5 = getPtr();
+ p4 = p4 + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: do not use pointer arithmetic
+ p5 = p5 + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: do not use pointer arithmetic
+ auto p6 = getPtrDeclType() + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:30: warning: do not use pointer arithmetic
+ auto p7 = getPtrDeclTypeAuto() + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:34: warning: do not use pointer arithmetic
+ auto *p8 = getPtrDeclType() + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: do not use pointer arithmetic
+ auto *p9 = getPtrDeclTypeAuto() + 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:35: warning: do not use pointer arithmetic
+}
+
+void auto_deduction_subscript() {
+ char p1 = getPtr()[2];
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: do not use pointer arithmetic
+ auto p2 = getPtr()[3];
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: do not use pointer arithmetic
+
+ char p3 = getPtrAuto()[4];
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: do not use pointer arithmetic
+ auto p4 = getPtrAuto()[5];
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: do not use pointer arithmetic
+
+ char p5 = getPtrWithTrailingReturnType()[6];
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: do not use pointer arithmetic
+ auto p6 = getPtrWithTrailingReturnType()[7];
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: do not use pointer arithmetic
+
+ auto p7 = getPtrDeclType()[8];
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: do not use pointer arithmetic
+ auto p8 = getPtrDeclTypeAuto()[9];
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: do not use pointer arithmetic
+}
diff --git a/test/clang-tidy/fuchsia-multiple-inheritance.cpp b/test/clang-tidy/fuchsia-multiple-inheritance.cpp
index e61e1299..fd2ed145 100644
--- a/test/clang-tidy/fuchsia-multiple-inheritance.cpp
+++ b/test/clang-tidy/fuchsia-multiple-inheritance.cpp
@@ -134,3 +134,14 @@ template<typename T> struct B : virtual T {};
template<typename> struct C {};
template<typename T> struct D : C<T> {};
+
+// Check clang_tidy does not crash on this code.
+template <class T>
+struct WithTemplBase : T {
+ WithTemplBase();
+};
+
+int test_no_crash() {
+ auto foo = []() {};
+ WithTemplBase<decltype(foo)>();
+}
diff --git a/test/clang-tidy/misc-unused-parameters-strict.cpp b/test/clang-tidy/misc-unused-parameters-strict.cpp
new file mode 100644
index 00000000..a334b459
--- /dev/null
+++ b/test/clang-tidy/misc-unused-parameters-strict.cpp
@@ -0,0 +1,25 @@
+// RUN: %check_clang_tidy %s misc-unused-parameters %t -- \
+// RUN: -config="{CheckOptions: [{key: StrictMode, value: 1}]}" --
+
+// Warn on empty function bodies in StrictMode.
+namespace strict_mode {
+void f(int foo) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: parameter 'foo' is unused [misc-unused-parameters]
+// CHECK-FIXES: {{^}}void f(int /*foo*/) {}{{$}}
+class E {
+ int i;
+
+public:
+ E(int j) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: parameter 'j' is unused
+// CHECK-FIXES: {{^}} E(int /*j*/) {}{{$}}
+};
+class F {
+ int i;
+
+public:
+ F(int j) : i() {}
+// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: parameter 'j' is unused
+// CHECK-FIXES: {{^}} F(int /*j*/) : i() {}{{$}}
+};
+}
diff --git a/test/clang-tidy/misc-unused-parameters.cpp b/test/clang-tidy/misc-unused-parameters.cpp
index 42554b95..ec1ee2d0 100644
--- a/test/clang-tidy/misc-unused-parameters.cpp
+++ b/test/clang-tidy/misc-unused-parameters.cpp
@@ -222,5 +222,56 @@ static Function<void(int, int i)> dontGetConfusedByFunctionReturnTypes() {
return Function<void(int, int)>();
}
+namespace PR38055 {
+namespace {
+struct a {
+ void b(int c) {;}
+// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: parameter 'c' is unused
+// CHECK-FIXES: {{^}} void b() {;}{{$}}
+};
+template <class>
+class d {
+ a e;
+ void f() { e.b(); }
+};
+} // namespace
+} // namespace PR38055
+
+namespace strict_mode_off {
// Do not warn on empty function bodies.
-void f(int foo) {}
+void f1(int foo1) {}
+void f2(int foo2) {
+ // "empty" in the AST sense, not in textual sense.
+}
+void f3(int foo3) {;}
+// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: parameter 'foo3' is unused
+// CHECK-FIXES: {{^}}void f3(int /*foo3*/) {;}{{$}}
+
+class E {
+ int i;
+
+public:
+ E(int j) {}
+};
+class F {
+ int i;
+
+public:
+ // Constructor initializer counts as a non-empty body.
+ F(int j) : i() {}
+// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: parameter 'j' is unused
+// CHECK-FIXES: {{^}} F(int /*j*/) : i() {}{{$}}
+};
+
+class A {
+public:
+ A();
+ A(int);
+};
+class B : public A {
+public:
+ B(int i) : A() {}
+// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: parameter 'i' is unused
+// CHECK-FIXES: {{^}} B(int /*i*/) : A() {}{{$}}
+};
+} // namespace strict_mode_off
diff --git a/test/clang-tidy/modernize-shrink-to-fit.cpp b/test/clang-tidy/modernize-shrink-to-fit.cpp
index f4f3388a..6993d300 100644
--- a/test/clang-tidy/modernize-shrink-to-fit.cpp
+++ b/test/clang-tidy/modernize-shrink-to-fit.cpp
@@ -72,3 +72,16 @@ void h() {
// CHECK-FIXES: {{^ }}COPY_AND_SWAP_INT_VEC(v);{{$}}
}
+void PR38315() {
+ typedef std::vector<int> Vector;
+ Vector v;
+ Vector(v).swap(v);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the shrink_to_fit method should
+ // CHECK-FIXES: {{^ }}v.shrink_to_fit();{{$}}
+
+ using Vector2 = std::vector<int>;
+ Vector2 v2;
+ Vector2(v2).swap(v2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: the shrink_to_fit method should
+ // CHECK-FIXES: {{^ }}v2.shrink_to_fit();{{$}}
+}
diff --git a/test/clang-tidy/modernize-use-auto-min-type-name-length.cpp b/test/clang-tidy/modernize-use-auto-min-type-name-length.cpp
index 2d978289..1cd91582 100644
--- a/test/clang-tidy/modernize-use-auto-min-type-name-length.cpp
+++ b/test/clang-tidy/modernize-use-auto-min-type-name-length.cpp
@@ -1,30 +1,85 @@
-// RUN: %check_clang_tidy %s modernize-use-auto %t -- \
-// RUN: -config="{CheckOptions: [{key: modernize-use-auto.MinTypeNameLength, value: '5'}]}" \
-// RUN: -- -std=c++11 -frtti
+// RUN: %check_clang_tidy -check-suffix=0-0 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 0}, {key: modernize-use-auto.MinTypeNameLength, value: 0}]}" -- --std=c++11 -frtti
+// RUN: %check_clang_tidy -check-suffix=0-8 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 0}, {key: modernize-use-auto.MinTypeNameLength, value: 8}]}" -- --std=c++11 -frtti
+// RUN: %check_clang_tidy -check-suffix=1-0 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 1}, {key: modernize-use-auto.MinTypeNameLength, value: 0}]}" -- --std=c++11 -frtti
+// RUN: %check_clang_tidy -check-suffix=1-8 %s modernize-use-auto %t -- -config="{CheckOptions: [{key: modernize-use-auto.RemoveStars, value: 1}, {key: modernize-use-auto.MinTypeNameLength, value: 8}]}" -- --std=c++11 -frtti
-extern int foo();
-
-using VeryVeryVeryLongTypeName = int;
+template <class T> extern T foo();
+template <class T> struct P { explicit P(T t) : t_(t) {} T t_;};
+template <class T> P<T> *foo_ptr();
+template <class T> P<T> &foo_ref();
int bar() {
- int a = static_cast<VeryVeryVeryLongTypeName>(foo());
- // strlen('int') = 4 < 5, so skip it,
- // even strlen('VeryVeryVeryLongTypeName') > 5.
+ {
+ // Lenth(long) = 4
+ long i = static_cast<long>(foo<long>());
+ // CHECK-FIXES-0-0: auto i = {{.*}}
+ // CHECK-FIXES-0-8: long i = {{.*}}
+ // CHECK-FIXES-1-0: auto i = {{.*}}
+ // CHECK-FIXES-1-8: long i = {{.*}}
+ const long ci = static_cast<long>(foo<const long>());
+ // CHECK-FIXES-0-0: auto ci = {{.*}}
+ // CHECK-FIXES-0-8: long ci = {{.*}}
+ // CHECK-FIXES-1-0: auto ci = {{.*}}
+ // CHECK-FIXES-1-8: long ci = {{.*}}
+ long *pi = static_cast<long *>(foo<long *>());
+ // CHECK-FIXES-0-0: auto *pi = {{.*}}
+ // CHECK-FIXES-0-8: long *pi = {{.*}}
+ // CHECK-FIXES-1-0: auto pi = {{.*}}
+ // CHECK-FIXES-1-8: long *pi = {{.*}}
+
+ // Length(long *) is still 5
+ long * pi2 = static_cast<long *>(foo<long *>());
+ // CHECK-FIXES-0-0: auto * pi2 = {{.*}}
+ // CHECK-FIXES-0-8: long * pi2 = {{.*}}
+ // CHECK-FIXES-1-0: auto pi2 = {{.*}}
+ // CHECK-FIXES-1-8: long * pi2 = {{.*}}
+
+ // Length(long **) = 6
+ long **ppi = static_cast<long **>(foo<long **>());
+ // CHECK-FIXES-0-0: auto **ppi = {{.*}}
+ // CHECK-FIXES-0-8: long **ppi = {{.*}}
+ // CHECK-FIXES-1-0: auto ppi = {{.*}}
+ // CHECK-FIXES-1-8: long **ppi = {{.*}}
+ }
- unsigned b = static_cast<unsigned>(foo());
- // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use auto when initializing with a cast to avoid duplicating the type name [modernize-use-auto]
- // CHECK-FIXES: auto b = static_cast<unsigned>(foo());
+ {
+ // Lenth(long int) = 4 + 1 + 3 = 8
+ // Lenth(long int) is still 8
+ long int i = static_cast<long int>(foo<long int>());
+ // CHECK-FIXES-0-0: auto i = {{.*}}
+ // CHECK-FIXES-0-8: auto i = {{.*}}
+ // CHECK-FIXES-1-0: auto i = {{.*}}
+ // CHECK-FIXES-1-8: auto i = {{.*}}
- bool c = static_cast<bool>(foo());
- // strlen('bool') = 4 < 5, so skip it.
+ long int *pi = static_cast<long int *>(foo<long int *>());
+ // CHECK-FIXES-0-0: auto *pi = {{.*}}
+ // CHECK-FIXES-0-8: auto *pi = {{.*}}
+ // CHECK-FIXES-1-0: auto pi = {{.*}}
+ // CHECK-FIXES-1-8: auto pi = {{.*}}
+ }
- const bool c1 = static_cast<const bool>(foo());
- // strlen('bool') = 4 < 5, so skip it, even there's a 'const'.
+ // Templates
+ {
+ // Length(P<long>) = 7
+ P<long>& i = static_cast<P<long>&>(foo_ref<long>());
+ // CHECK-FIXES-0-0: auto& i = {{.*}}
+ // CHECK-FIXES-0-8: P<long>& i = {{.*}}
+ // CHECK-FIXES-1-0: auto & i = {{.*}}
+ // CHECK-FIXES-1-8: P<long>& i = {{.*}}
- unsigned long long ull = static_cast<unsigned long long>(foo());
- // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use auto when initializing with a cast to avoid duplicating the type name [modernize-use-auto]
- // CHECK-FIXES: auto ull = static_cast<unsigned long long>(foo());
+ // Length(P<long*>) = 8
+ P<long*>& pi = static_cast<P<long*> &>(foo_ref<long*>());
+ // CHECK-FIXES-0-0: auto& pi = {{.*}}
+ // CHECK-FIXES-0-8: auto& pi = {{.*}}
+ // CHECK-FIXES-1-0: auto & pi = {{.*}}
+ // CHECK-FIXES-1-8: auto & pi = {{.*}}
+
+ P<long>* pi2 = static_cast<P<long>*>(foo_ptr<long>());
+ // CHECK-FIXES-0-0: auto* pi2 = {{.*}}
+ // CHECK-FIXES-0-8: P<long>* pi2 = {{.*}}
+ // CHECK-FIXES-1-0: auto pi2 = {{.*}}
+ // CHECK-FIXES-1-8: auto pi2 = {{.*}}
+ }
return 1;
}
-
diff --git a/test/clang-tidy/modernize-use-equals-default-copy.cpp b/test/clang-tidy/modernize-use-equals-default-copy.cpp
index 26f35af3..39a864d6 100644
--- a/test/clang-tidy/modernize-use-equals-default-copy.cpp
+++ b/test/clang-tidy/modernize-use-equals-default-copy.cpp
@@ -497,3 +497,11 @@ STRUCT_WITH_COPY_CONSTRUCT(unsigned char, Hex8CopyConstruct)
STRUCT_WITH_COPY_ASSIGN(unsigned char, Hex8CopyAssign)
// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: use '= default' to define a trivial copy-assignment operator
// CHECK-MESSAGES: :[[@LINE-9]]:40: note:
+
+// Use of braces
+struct UOB{
+ UOB(const UOB &Other):j{Other.j}{}
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' to define a trivial copy constructor [modernize-use-equals-default]
+ // CHECK-FIXES: UOB(const UOB &Other)= default;
+ int j;
+};
diff --git a/test/clang-tidy/objc-property-declaration.m b/test/clang-tidy/objc-property-declaration.m
index ede14426..7f70784d 100644
--- a/test/clang-tidy/objc-property-declaration.m
+++ b/test/clang-tidy/objc-property-declaration.m
@@ -22,6 +22,7 @@
@property(assign, nonatomic) int shouldUseCFPreferences;
@property(assign, nonatomic) int enableGLAcceleration;
@property(assign, nonatomic) int ID;
+@property(assign, nonatomic) int hasADog;
@end
@interface Foo (Bar)
diff --git a/test/clang-tidy/performance-for-range-copy.cpp b/test/clang-tidy/performance-for-range-copy.cpp
index 88ec45b2..1c77996c 100644
--- a/test/clang-tidy/performance-for-range-copy.cpp
+++ b/test/clang-tidy/performance-for-range-copy.cpp
@@ -117,6 +117,11 @@ struct Mutable {
~Mutable() {}
};
+struct Point {
+ ~Point() {}
+ int x, y;
+};
+
Mutable& operator<<(Mutable &Out, bool B) {
Out.setBool(B);
return Out;
@@ -214,6 +219,15 @@ void positiveOnlyUsedAsConstArguments() {
}
}
+void positiveOnlyAccessedFieldAsConst() {
+ for (auto UsedAsConst : View<Iterator<Point>>()) {
+ // CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [performance-for-range-copy]
+ // CHECK-FIXES: for (const auto& UsedAsConst : View<Iterator<Point>>()) {
+ use(UsedAsConst.x);
+ use(UsedAsConst.y);
+ }
+}
+
void positiveOnlyUsedInCopyConstructor() {
for (auto A : View<Iterator<Mutable>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [performance-for-range-copy]
diff --git a/test/clang-tidy/performance-implicit-conversion-in-loop.cpp b/test/clang-tidy/performance-implicit-conversion-in-loop.cpp
index 7b1ad396..72f9e526 100644
--- a/test/clang-tidy/performance-implicit-conversion-in-loop.cpp
+++ b/test/clang-tidy/performance-implicit-conversion-in-loop.cpp
@@ -40,7 +40,7 @@ class ImplicitWrapper {
template <typename T>
class OperatorWrapper {
public:
- explicit OperatorWrapper(const T& t);
+ OperatorWrapper() = delete;
};
struct SimpleClass {
@@ -101,7 +101,7 @@ void ImplicitSimpleClassIterator() {
// CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the loop variable 'foo' is different from the one returned by the iterator and generates an implicit conversion; you can either change the type to the matching one ('const SimpleClass &' but 'const auto&' is always a valid option) or remove the reference to make it explicit that you are creating a new value [performance-implicit-conversion-in-loop]
// for (ImplicitWrapper<SimpleClass>& foo : SimpleView()) {}
for (const ImplicitWrapper<SimpleClass> foo : SimpleView()) {}
- for (ImplicitWrapper<SimpleClass>foo : SimpleView()) {}
+ for (ImplicitWrapper<SimpleClass> foo : SimpleView()) {}
}
void ImplicitSimpleClassRefIterator() {
@@ -109,7 +109,16 @@ void ImplicitSimpleClassRefIterator() {
// CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const SimpleClass &'.*}}
// for (ImplicitWrapper<SimpleClass>& foo : SimpleRefView()) {}
for (const ImplicitWrapper<SimpleClass> foo : SimpleRefView()) {}
- for (ImplicitWrapper<SimpleClass>foo : SimpleRefView()) {}
+ for (ImplicitWrapper<SimpleClass> foo : SimpleRefView()) {}
+}
+
+void ImplicitSimpleClassArray() {
+ SimpleClass array[5];
+ for (const ImplicitWrapper<SimpleClass>& foo : array) {}
+ // CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const SimpleClass &'.*}}
+ // for (ImplicitWrapper<SimpleClass>& foo : array) {}
+ for (const ImplicitWrapper<SimpleClass> foo : array) {}
+ for (ImplicitWrapper<SimpleClass> foo : array) {}
}
void ImplicitComplexClassIterator() {
@@ -117,15 +126,24 @@ void ImplicitComplexClassIterator() {
// CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const ComplexClass &'.*}}
// for (ImplicitWrapper<ComplexClass>& foo : ComplexView()) {}
for (const ImplicitWrapper<ComplexClass> foo : ComplexView()) {}
- for (ImplicitWrapper<ComplexClass>foo : ComplexView()) {}
+ for (ImplicitWrapper<ComplexClass> foo : ComplexView()) {}
}
void ImplicitComplexClassRefIterator() {
+ ComplexClass array[5];
+ for (const ImplicitWrapper<ComplexClass>& foo : array) {}
+ // CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const ComplexClass &'.*}}
+ // for (ImplicitWrapper<ComplexClass>& foo : array) {}
+ for (const ImplicitWrapper<ComplexClass> foo : array) {}
+ for (ImplicitWrapper<ComplexClass> foo : array) {}
+}
+
+void ImplicitComplexClassArray() {
for (const ImplicitWrapper<ComplexClass>& foo : ComplexRefView()) {}
// CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const ComplexClass &'.*}}
// for (ImplicitWrapper<ComplexClass>& foo : ComplexRefView()) {}
for (const ImplicitWrapper<ComplexClass> foo : ComplexRefView()) {}
- for (ImplicitWrapper<ComplexClass>foo : ComplexRefView()) {}
+ for (ImplicitWrapper<ComplexClass> foo : ComplexRefView()) {}
}
void OperatorSimpleClassIterator() {
@@ -133,7 +151,7 @@ void OperatorSimpleClassIterator() {
// CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const SimpleClass &'.*}}
// for (OperatorWrapper<SimpleClass>& foo : SimpleView()) {}
for (const OperatorWrapper<SimpleClass> foo : SimpleView()) {}
- for (OperatorWrapper<SimpleClass>foo : SimpleView()) {}
+ for (OperatorWrapper<SimpleClass> foo : SimpleView()) {}
}
void OperatorSimpleClassRefIterator() {
@@ -141,7 +159,16 @@ void OperatorSimpleClassRefIterator() {
// CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const SimpleClass &'.*}}
// for (OperatorWrapper<SimpleClass>& foo : SimpleRefView()) {}
for (const OperatorWrapper<SimpleClass> foo : SimpleRefView()) {}
- for (OperatorWrapper<SimpleClass>foo : SimpleRefView()) {}
+ for (OperatorWrapper<SimpleClass> foo : SimpleRefView()) {}
+}
+
+void OperatorSimpleClassArray() {
+ SimpleClass array[5];
+ for (const OperatorWrapper<SimpleClass>& foo : array) {}
+ // CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const SimpleClass &'.*}}
+ // for (OperatorWrapper<SimpleClass>& foo : array) {}
+ for (const OperatorWrapper<SimpleClass> foo : array) {}
+ for (OperatorWrapper<SimpleClass> foo : array) {}
}
void OperatorComplexClassIterator() {
@@ -149,7 +176,7 @@ void OperatorComplexClassIterator() {
// CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const ComplexClass &'.*}}
// for (OperatorWrapper<ComplexClass>& foo : ComplexView()) {}
for (const OperatorWrapper<ComplexClass> foo : ComplexView()) {}
- for (OperatorWrapper<ComplexClass>foo : ComplexView()) {}
+ for (OperatorWrapper<ComplexClass> foo : ComplexView()) {}
}
void OperatorComplexClassRefIterator() {
@@ -157,5 +184,14 @@ void OperatorComplexClassRefIterator() {
// CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const ComplexClass &'.*}}
// for (OperatorWrapper<ComplexClass>& foo : ComplexRefView()) {}
for (const OperatorWrapper<ComplexClass> foo : ComplexRefView()) {}
- for (OperatorWrapper<ComplexClass>foo : ComplexRefView()) {}
+ for (OperatorWrapper<ComplexClass> foo : ComplexRefView()) {}
+}
+
+void OperatorComplexClassArray() {
+ ComplexClass array[5];
+ for (const OperatorWrapper<ComplexClass>& foo : array) {}
+ // CHECK-MESSAGES: [[@LINE-1]]:{{[0-9]*}}: warning: the type of the{{.*'const ComplexClass &'.*}}
+ // for (OperatorWrapper<ComplexClass>& foo : array) {}
+ for (const OperatorWrapper<ComplexClass> foo : array) {}
+ for (OperatorWrapper<ComplexClass> foo : array) {}
}
diff --git a/test/clang-tidy/performance-unnecessary-value-param.cpp b/test/clang-tidy/performance-unnecessary-value-param.cpp
index dcf82df2..f801494c 100644
--- a/test/clang-tidy/performance-unnecessary-value-param.cpp
+++ b/test/clang-tidy/performance-unnecessary-value-param.cpp
@@ -15,6 +15,20 @@ void mutate(ExpensiveToCopyType *);
void useAsConstReference(const ExpensiveToCopyType &);
void useByValue(ExpensiveToCopyType);
+template <class T> class Vector {
+ public:
+ using iterator = T*;
+ using const_iterator = const T*;
+
+ Vector(const Vector&);
+ Vector& operator=(const Vector&);
+
+ iterator begin();
+ iterator end();
+ const_iterator begin() const;
+ const_iterator end() const;
+};
+
// This class simulates std::pair<>. It is trivially copy constructible
// and trivially destructible, but not trivially copy assignable.
class SomewhatTrivial {
@@ -59,6 +73,14 @@ void positiveExpensiveValue(ExpensiveToCopyType Obj) {
useByValue(Obj);
}
+void positiveVector(Vector<ExpensiveToCopyType> V) {
+ // CHECK-MESSAGES: [[@LINE-1]]:49: warning: the parameter 'V' is copied for each invocation but only used as a const reference; consider making it a const reference [performance-unnecessary-value-param]
+ // CHECK-FIXES: void positiveVector(const Vector<ExpensiveToCopyType>& V) {
+ for (const auto& Obj : V) {
+ useByValue(Obj);
+ }
+}
+
void positiveWithComment(const ExpensiveToCopyType /* important */ S);
// CHECK-FIXES: void positiveWithComment(const ExpensiveToCopyType& /* important */ S);
void positiveWithComment(const ExpensiveToCopyType /* important */ S) {
diff --git a/test/clang-tidy/readability-inconsistent-declaration-parameter-name-strict.cpp b/test/clang-tidy/readability-inconsistent-declaration-parameter-name-strict.cpp
new file mode 100644
index 00000000..90ac45fd
--- /dev/null
+++ b/test/clang-tidy/readability-inconsistent-declaration-parameter-name-strict.cpp
@@ -0,0 +1,11 @@
+// RUN: %check_clang_tidy %s readability-inconsistent-declaration-parameter-name %t -- \
+// RUN: -config="{CheckOptions: [{key: readability-inconsistent-declaration-parameter-name.Strict, value: 1}]}" \
+// RUN: -- -std=c++11
+
+void inconsistentFunction(int a, int b, int c);
+// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'inconsistentFunction' has 1 other declaration with different parameter names
+void inconsistentFunction(int prefixA, int b, int cSuffix);
+// CHECK-MESSAGES: :[[@LINE-1]]:6: note: the 1st inconsistent declaration seen here
+// CHECK-MESSAGES: :[[@LINE-2]]:6: note: differing parameters are named here: ('prefixA', 'cSuffix'), in the other declaration: ('a', 'c')
+void inconsistentFunction(int a, int b, int c);
+void inconsistentFunction(int /*c*/, int /*c*/, int /*c*/);
diff --git a/test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp b/test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp
index 43412f52..c98258bd 100644
--- a/test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp
+++ b/test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp
@@ -2,6 +2,8 @@
void consistentFunction(int a, int b, int c);
void consistentFunction(int a, int b, int c);
+void consistentFunction(int prefixA, int b, int cSuffix);
+void consistentFunction(int a, int b, int c);
void consistentFunction(int a, int b, int /*c*/);
void consistentFunction(int /*c*/, int /*c*/, int /*c*/);
diff --git a/test/clangd/compile-commands-path-in-initialize.test b/test/clangd/compile-commands-path-in-initialize.test
new file mode 100644
index 00000000..b34c5952
--- /dev/null
+++ b/test/clangd/compile-commands-path-in-initialize.test
@@ -0,0 +1,35 @@
+# Test that we can set choose a configuration/build directly in the initialize
+# request.
+
+# RUN: rm -rf %t.dir/* && mkdir -p %t.dir
+# RUN: mkdir %t.dir/build-1
+# RUN: mkdir %t.dir/build-2
+# RUN: echo '[{"directory": "%/t.dir", "command": "c++ the-file.cpp", "file": "the-file.cpp"}]' > %t.dir/compile_commands.json
+# RUN: echo '[{"directory": "%/t.dir/build-1", "command": "c++ -DMACRO=1 the-file.cpp", "file": "../the-file.cpp"}]' > %t.dir/build-1/compile_commands.json
+# RUN: echo '[{"directory": "%/t.dir/build-2", "command": "c++ -DMACRO=2 the-file.cpp", "file": "../the-file.cpp"}]' > %t.dir/build-2/compile_commands.json
+
+# RUN: sed -e "s|INPUT_DIR|%/t.dir|g" %s > %t.test.1
+
+# On Windows, we need the URI in didOpen to look like "uri":"file:///C:/..."
+# (with the extra slash in the front), so we add it here.
+# RUN: sed -e "s|file://\([A-Z]\):/|file:///\1:/|g" %t.test.1 > %t.test
+
+# RUN: clangd -lit-test < %t.test | FileCheck -strict-whitespace %t.test
+
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"initializationOptions":{"compilationDatabasePath":"INPUT_DIR/build-1"}}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file://INPUT_DIR/the-file.cpp","languageId":"cpp","version":1,"text":"#if !defined(MACRO)\n#pragma message (\"MACRO is not defined\")\n#elif MACRO == 1\n#pragma message (\"MACRO is one\")\n#elif MACRO == 2\n#pragma message (\"MACRO is two\")\n#else\n#pragma message (\"woops\")\n#endif\nint main() {}\n"}}}
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "MACRO is one",
+---
+{"jsonrpc":"2.0","id":0,"method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabasePath":"INPUT_DIR/build-2"}}}
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "MACRO is two",
+---
+{"jsonrpc":"2.0","id":10000,"method":"shutdown"}
diff --git a/test/clangd/compile-commands-path.test b/test/clangd/compile-commands-path.test
new file mode 100644
index 00000000..f25d002f
--- /dev/null
+++ b/test/clangd/compile-commands-path.test
@@ -0,0 +1,42 @@
+# Test that we can switch between configuration/build using the
+# workspace/didChangeConfiguration notification.
+
+# RUN: rm -rf %t.dir/* && mkdir -p %t.dir
+# RUN: mkdir %t.dir/build-1
+# RUN: mkdir %t.dir/build-2
+# RUN: echo '[{"directory": "%/t.dir", "command": "c++ the-file.cpp", "file": "the-file.cpp"}]' > %t.dir/compile_commands.json
+# RUN: echo '[{"directory": "%/t.dir/build-1", "command": "c++ -DMACRO=1 the-file.cpp", "file": "../the-file.cpp"}]' > %t.dir/build-1/compile_commands.json
+# RUN: echo '[{"directory": "%/t.dir/build-2", "command": "c++ -DMACRO=2 the-file.cpp", "file": "../the-file.cpp"}]' > %t.dir/build-2/compile_commands.json
+
+# RUN: sed -e "s|INPUT_DIR|%/t.dir|g" %s > %t.test.1
+
+# On Windows, we need the URI in didOpen to look like "uri":"file:///C:/..."
+# (with the extra slash in the front), so we add it here.
+# RUN: sed -e "s|file://\([A-Z]\):/|file:///\1:/|g" %t.test.1 > %t.test
+
+# RUN: clangd -lit-test < %t.test | FileCheck -strict-whitespace %t.test
+
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file://INPUT_DIR/the-file.cpp","languageId":"cpp","version":1,"text":"#if !defined(MACRO)\n#pragma message (\"MACRO is not defined\")\n#elif MACRO == 1\n#pragma message (\"MACRO is one\")\n#elif MACRO == 2\n#pragma message (\"MACRO is two\")\n#else\n#pragma message (\"woops\")\n#endif\nint main() {}\n"}}}
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "MACRO is not defined",
+---
+{"jsonrpc":"2.0","id":0,"method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabasePath":"INPUT_DIR/build-1"}}}
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "MACRO is one",
+---
+{"jsonrpc":"2.0","id":0,"method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabasePath":"INPUT_DIR/build-2"}}}
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "MACRO is two",
+---
+{"jsonrpc":"2.0","id":10000,"method":"shutdown"}
diff --git a/test/clangd/completion-snippets.test b/test/clangd/completion-snippets.test
index 1b9a24e0..06896319 100644
--- a/test/clangd/completion-snippets.test
+++ b/test/clangd/completion-snippets.test
@@ -32,7 +32,7 @@
# CHECK-NEXT: "insertText": "func_with_args(${1:int a}, ${2:int b})",
# CHECK-NEXT: "insertTextFormat": 2,
# CHECK-NEXT: "kind": 3,
-# CHECK-NEXT: "label": "func_with_args(int a, int b)",
+# CHECK-NEXT: "label": " func_with_args(int a, int b)",
# CHECK-NEXT: "sortText": "{{.*}}func_with_args"
# CHECK-NEXT: }
# CHECK-NEXT: ]
diff --git a/test/clangd/completion.test b/test/clangd/completion.test
index da00b566..c679e328 100644
--- a/test/clangd/completion.test
+++ b/test/clangd/completion.test
@@ -16,10 +16,10 @@
# CHECK-NEXT: "insertText": "a",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 5,
-# CHECK-NEXT: "label": "a",
+# CHECK-NEXT: "label": " a",
# CHECK-NEXT: "sortText": "{{.*}}a"
-# CHECK-NEXT: },
-# CHECK: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
---
# Update the source file and check for completions again.
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp","version":2},"contentChanges":[{"text":"struct S { int b; };\nint main() {\nS().\n}"}]}}
@@ -36,9 +36,9 @@
# CHECK-NEXT: "insertText": "b",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 5,
-# CHECK-NEXT: "label": "b",
+# CHECK-NEXT: "label": " b",
# CHECK-NEXT: "sortText": "{{.*}}b"
-# CHECK-NEXT: },
-# CHECK: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
---
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
diff --git a/test/clangd/diagnostics.test b/test/clangd/diagnostics.test
index afd1277b..a191c082 100644
--- a/test/clangd/diagnostics.test
+++ b/test/clangd/diagnostics.test
@@ -6,7 +6,7 @@
# CHECK-NEXT: "params": {
# CHECK-NEXT: "diagnostics": [
# CHECK-NEXT: {
-# CHECK-NEXT: "message": "return type of 'main' is not 'int'",
+# CHECK-NEXT: "message": "Return type of 'main' is not 'int'",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 4,
diff --git a/test/clangd/did-change-configuration-params.test b/test/clangd/did-change-configuration-params.test
new file mode 100644
index 00000000..51b4a874
--- /dev/null
+++ b/test/clangd/did-change-configuration-params.test
@@ -0,0 +1,52 @@
+# RUN: clangd -compile_args_from=lsp -lit-test < %s 2> %t | FileCheck -strict-whitespace %s
+# RUN: cat %t | FileCheck --check-prefix=ERR %s
+# UNSUPPORTED: windows-gnu,windows-msvc
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
+---
+{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabaseChanges":{"/clangd-test/foo.c": {"workingDirectory":"/clangd-test", "compilationCommand": ["clang", "-c", "foo.c"]}}}}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main() { int i; return i; }"}}}
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [],
+# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
+# CHECK-NEXT: }
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///bar.c","languageId":"c","version":1,"text":"int main() { int i; return i; }"}}}
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [],
+# CHECK-NEXT: "uri": "file://{{.*}}/bar.c"
+# CHECK-NEXT: }
+---
+{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabaseChanges":{"/clangd-test/foo.c": {"workingDirectory":"/clangd-test2", "compilationCommand": ["clang", "-c", "foo.c", "-Wall", "-Werror"]}}}}}
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "Variable 'i' is uninitialized when used here",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 28,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 27,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
+# CHECK-NEXT: }
+#
+# ERR: Updating file {{.*}}foo.c with command [{{.*}}clangd-test2] clang -c foo.c -Wall -Werror
+# Don't reparse the second file:
+# ERR: Skipping rebuild of the AST for {{.*}}bar.c
+---
+{"jsonrpc":"2.0","id":5,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}
+
+
diff --git a/test/clangd/execute-command.test b/test/clangd/execute-command.test
index 9686d040..492006fd 100644
--- a/test/clangd/execute-command.test
+++ b/test/clangd/execute-command.test
@@ -6,7 +6,7 @@
# CHECK-NEXT: "params": {
# CHECK-NEXT: "diagnostics": [
# CHECK-NEXT: {
-# CHECK-NEXT: "message": "using the result of an assignment as a condition without parentheses",
+# CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 37,
diff --git a/test/clangd/extra-flags.test b/test/clangd/extra-flags.test
index 23b2c652..f2460eb1 100644
--- a/test/clangd/extra-flags.test
+++ b/test/clangd/extra-flags.test
@@ -6,7 +6,7 @@
# CHECK-NEXT: "params": {
# CHECK-NEXT: "diagnostics": [
# CHECK-NEXT: {
-# CHECK-NEXT: "message": "variable 'i' is uninitialized when used here",
+# CHECK-NEXT: "message": "Variable 'i' is uninitialized when used here",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 28,
@@ -23,12 +23,12 @@
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
# CHECK-NEXT: }
---
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c","version":2},"contentChanges":[{"text":"int main() { int i; return i; }"}]}}
+{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c","version":2},"contentChanges":[{"text":"int main() { int i; return i+1; }"}]}}
# CHECK: "method": "textDocument/publishDiagnostics",
# CHECK-NEXT: "params": {
# CHECK-NEXT: "diagnostics": [
# CHECK-NEXT: {
-# CHECK-NEXT: "message": "variable 'i' is uninitialized when used here",
+# CHECK-NEXT: "message": "Variable 'i' is uninitialized when used here",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 28,
diff --git a/test/clangd/fixits.test b/test/clangd/fixits.test
index f8eb20ca..ce74d1c8 100644
--- a/test/clangd/fixits.test
+++ b/test/clangd/fixits.test
@@ -6,7 +6,7 @@
# CHECK-NEXT: "params": {
# CHECK-NEXT: "diagnostics": [
# CHECK-NEXT: {
-# CHECK-NEXT: "message": "using the result of an assignment as a condition without parentheses",
+# CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 37,
@@ -23,7 +23,7 @@
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c"
# CHECK-NEXT: }
---
-{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"}]}}}
+{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses"}]}}}
# CHECK: "id": 2,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
@@ -92,7 +92,7 @@
# CHECK-NEXT: }
# CHECK-NEXT: ]
---
-{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"}]}}}
+{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses"}]}}}
# Make sure unused "code" and "source" fields ignored gracefully
# CHECK: "id": 3,
# CHECK-NEXT: "jsonrpc": "2.0",
diff --git a/test/clangd/hover.test b/test/clangd/hover.test
index 9db8f9c9..8f1ead05 100644
--- a/test/clangd/hover.test
+++ b/test/clangd/hover.test
@@ -14,6 +14,11 @@
# CHECK-NEXT: }
# CHECK-NEXT:}
---
+{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":0,"character":10}}}
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": null
+---
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}
diff --git a/test/clangd/initialize-params-invalid.test b/test/clangd/initialize-params-invalid.test
index e8df566c..99c04c62 100644
--- a/test/clangd/initialize-params-invalid.test
+++ b/test/clangd/initialize-params-invalid.test
@@ -22,6 +22,7 @@
# CHECK-NEXT: "moreTriggerCharacter": []
# CHECK-NEXT: },
# CHECK-NEXT: "documentRangeFormattingProvider": true,
+# CHECK-NEXT: "documentSymbolProvider": true,
# CHECK-NEXT: "executeCommandProvider": {
# CHECK-NEXT: "commands": [
# CHECK-NEXT: "clangd.applyFix"
diff --git a/test/clangd/initialize-params.test b/test/clangd/initialize-params.test
index 298dd2cd..a04d70ad 100644
--- a/test/clangd/initialize-params.test
+++ b/test/clangd/initialize-params.test
@@ -22,6 +22,7 @@
# CHECK-NEXT: "moreTriggerCharacter": []
# CHECK-NEXT: },
# CHECK-NEXT: "documentRangeFormattingProvider": true,
+# CHECK-NEXT: "documentSymbolProvider": true,
# CHECK-NEXT: "executeCommandProvider": {
# CHECK-NEXT: "commands": [
# CHECK-NEXT: "clangd.applyFix"
diff --git a/test/clangd/protocol.test b/test/clangd/protocol.test
index 46af4edc..7295ca57 100644
--- a/test/clangd/protocol.test
+++ b/test/clangd/protocol.test
@@ -34,11 +34,11 @@ Content-Length: 146
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
-# CHECK: "filterText": "fake",
-# CHECK-NEXT: "insertText": "fake",
+# CHECK: "filterText": "a",
+# CHECK-NEXT: "insertText": "a",
# CHECK-NEXT: "insertTextFormat": 1,
-# CHECK-NEXT: "kind": 7,
-# CHECK-NEXT: "label": "fake",
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": " a",
# CHECK-NEXT: "sortText": "{{.*}}"
# CHECK: ]
# CHECK-NEXT: }
@@ -63,11 +63,11 @@ Content-Length: 146
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
-# CHECK: "filterText": "fake",
-# CHECK-NEXT: "insertText": "fake",
+# CHECK: "filterText": "a",
+# CHECK-NEXT: "insertText": "a",
# CHECK-NEXT: "insertTextFormat": 1,
-# CHECK-NEXT: "kind": 7,
-# CHECK-NEXT: "label": "fake",
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": " a",
# CHECK-NEXT: "sortText": "{{.*}}"
# CHECK: ]
# CHECK-NEXT: }
@@ -92,11 +92,11 @@ Content-Length: 146
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
-# CHECK: "filterText": "fake",
-# CHECK-NEXT: "insertText": "fake",
+# CHECK: "filterText": "a",
+# CHECK-NEXT: "insertText": "a",
# CHECK-NEXT: "insertTextFormat": 1,
-# CHECK-NEXT: "kind": 7,
-# CHECK-NEXT: "label": "fake",
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": " a",
# CHECK-NEXT: "sortText": "{{.*}}"
# CHECK: ]
# CHECK-NEXT: }
diff --git a/test/clangd/symbols.test b/test/clangd/symbols.test
index 015c8d50..5b07f8f5 100644
--- a/test/clangd/symbols.test
+++ b/test/clangd/symbols.test
@@ -28,6 +28,57 @@
# CHECK-NEXT: ]
# CHECK-NEXT:}
---
+{"jsonrpc":"2.0","id":2,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"test:///main.cpp"}}}
+# CHECK: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "containerName": "",
+# CHECK-NEXT: "kind": 12,
+# CHECK-NEXT: "location": {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": {{.*}},
+# CHECK-NEXT: "line": {{.*}}
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": {{.*}},
+# CHECK-NEXT: "line": {{.*}}
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file://{{.*}}/main.cpp"
+# CHECK-NEXT: },
+# CHECK-NEXT: "name": "foo"
+# CHECK-NEXT: }
+# CHECK-NEXT: {
+# CHECK-NEXT: "containerName": "",
+# CHECK-NEXT: "kind": 12,
+# CHECK-NEXT: "location": {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": {{.*}},
+# CHECK-NEXT: "line": {{.*}}
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": {{.*}},
+# CHECK-NEXT: "line": {{.*}}
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file://{{.*}}/main.cpp"
+# CHECK-NEXT: },
+# CHECK-NEXT: "name": "main"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT:}
+---
+{"jsonrpc":"2.0","id":3,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"test:///foo.cpp"}}}
+# CHECK: "error": {
+# CHECK-NEXT: "code": -32602,
+# CHECK-NEXT: "message": "trying to get AST for non-added document"
+# CHECK-NEXT: },
+# CHECK-NEXT: "id": 3,
+# CHECK-NEXT: "jsonrpc": "2.0"
+---
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}
diff --git a/test/clangd/test-uri-posix.test b/test/clangd/test-uri-posix.test
index 15cc09f6..2b67fa03 100644
--- a/test/clangd/test-uri-posix.test
+++ b/test/clangd/test-uri-posix.test
@@ -1,5 +1,5 @@
# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
-# UNSUPPORTED: mingw32,win32
+# UNSUPPORTED: windows-gnu,windows-msvc
# Test authority-less URI
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
---
diff --git a/test/clangd/test-uri-windows.test b/test/clangd/test-uri-windows.test
index 7ec290e7..381c48fa 100644
--- a/test/clangd/test-uri-windows.test
+++ b/test/clangd/test-uri-windows.test
@@ -1,5 +1,5 @@
# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
-# REQUIRES: mingw32 || win32
+# REQUIRES: windows-gnu || windows-msvc
# Test authority-less URI
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
---
diff --git a/test/clangd/too_large.test b/test/clangd/too_large.test
index 60de8009..7b846c37 100644
--- a/test/clangd/too_large.test
+++ b/test/clangd/too_large.test
@@ -4,4 +4,4 @@
#
Content-Length: 2147483648
-# STDERR: Skipped overly large message
+# STDERR: Refusing to read message
diff --git a/tool-template/ToolTemplate.cpp b/tool-template/ToolTemplate.cpp
index 5345d77f..66ec2e84 100644
--- a/tool-template/ToolTemplate.cpp
+++ b/tool-template/ToolTemplate.cpp
@@ -66,8 +66,8 @@ public:
auto *D = Result.Nodes.getNodeAs<NamedDecl>("decl");
assert(D);
// Use AtomicChange to get a key.
- if (D->getLocStart().isValid()) {
- AtomicChange Change(*Result.SourceManager, D->getLocStart());
+ if (D->getBeginLoc().isValid()) {
+ AtomicChange Change(*Result.SourceManager, D->getBeginLoc());
Context.reportResult(Change.getKey(), D->getQualifiedNameAsString());
}
}
diff --git a/unittests/clang-move/ClangMoveTests.cpp b/unittests/clang-move/ClangMoveTests.cpp
index e8b7ba35..45e3f682 100644
--- a/unittests/clang-move/ClangMoveTests.cpp
+++ b/unittests/clang-move/ClangMoveTests.cpp
@@ -239,8 +239,10 @@ runClangMoveOnCode(const move::MoveDefinitionSpec &Spec,
// The Key is file name, value is the new code after moving the class.
std::map<std::string, std::string> Results;
for (const auto &It : FileToReplacements) {
- StringRef FilePath = It.first;
- Results[FilePath] = Context.getRewrittenText(FileToFileID[FilePath]);
+ // The path may come out as "./foo.h", normalize to "foo.h".
+ SmallString<32> FilePath (It.first);
+ llvm::sys::path::remove_dots(FilePath);
+ Results[FilePath.str().str()] = Context.getRewrittenText(FileToFileID[FilePath]);
}
return Results;
}
diff --git a/unittests/clang-tidy/CMakeLists.txt b/unittests/clang-tidy/CMakeLists.txt
index c5644537..a678e59c 100644
--- a/unittests/clang-tidy/CMakeLists.txt
+++ b/unittests/clang-tidy/CMakeLists.txt
@@ -9,6 +9,7 @@ include_directories(${CLANG_LINT_SOURCE_DIR})
add_extra_unittest(ClangTidyTests
ClangTidyDiagnosticConsumerTest.cpp
ClangTidyOptionsTest.cpp
+ ExprMutationAnalyzerTest.cpp
IncludeInserterTest.cpp
GoogleModuleTest.cpp
LLVMModuleTest.cpp
diff --git a/unittests/clang-tidy/ExprMutationAnalyzerTest.cpp b/unittests/clang-tidy/ExprMutationAnalyzerTest.cpp
new file mode 100644
index 00000000..042d4cbe
--- /dev/null
+++ b/unittests/clang-tidy/ExprMutationAnalyzerTest.cpp
@@ -0,0 +1,611 @@
+//===---------- ExprMutationAnalyzerTest.cpp - clang-tidy -----------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../clang-tidy/utils/ExprMutationAnalyzer.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Tooling/Tooling.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <cctype>
+
+namespace clang {
+namespace tidy {
+namespace test {
+
+using namespace clang::ast_matchers;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::ResultOf;
+using ::testing::StartsWith;
+using ::testing::Values;
+
+namespace {
+
+using ExprMatcher = internal::Matcher<Expr>;
+using StmtMatcher = internal::Matcher<Stmt>;
+
+ExprMatcher declRefTo(StringRef Name) {
+ return declRefExpr(to(namedDecl(hasName(Name))));
+}
+
+StmtMatcher withEnclosingCompound(ExprMatcher Matcher) {
+ return expr(Matcher, hasAncestor(compoundStmt().bind("stmt"))).bind("expr");
+}
+
+bool isMutated(const SmallVectorImpl<BoundNodes> &Results, ASTUnit *AST) {
+ const auto *const S = selectFirst<Stmt>("stmt", Results);
+ const auto *const E = selectFirst<Expr>("expr", Results);
+ return utils::ExprMutationAnalyzer(S, &AST->getASTContext()).isMutated(E);
+}
+
+SmallVector<std::string, 1>
+mutatedBy(const SmallVectorImpl<BoundNodes> &Results, ASTUnit *AST) {
+ const auto *const S = selectFirst<Stmt>("stmt", Results);
+ SmallVector<std::string, 1> Chain;
+ utils::ExprMutationAnalyzer Analyzer(S, &AST->getASTContext());
+ for (const auto *E = selectFirst<Expr>("expr", Results); E != nullptr;) {
+ const Stmt *By = Analyzer.findMutation(E);
+ std::string buffer;
+ llvm::raw_string_ostream stream(buffer);
+ By->printPretty(stream, nullptr, AST->getASTContext().getPrintingPolicy());
+ Chain.push_back(StringRef(stream.str()).trim().str());
+ E = dyn_cast<DeclRefExpr>(By);
+ }
+ return Chain;
+}
+
+std::string removeSpace(std::string s) {
+ s.erase(std::remove_if(s.begin(), s.end(),
+ [](char c) { return std::isspace(c); }),
+ s.end());
+ return s;
+}
+
+} // namespace
+
+TEST(ExprMutationAnalyzerTest, Trivial) {
+ const auto AST = tooling::buildASTFromCode("void f() { int x; x; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+class AssignmentTest : public ::testing::TestWithParam<std::string> {};
+
+TEST_P(AssignmentTest, AssignmentModifies) {
+ const std::string ModExpr = "x " + GetParam() + " 10";
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; " + ModExpr + "; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre(ModExpr));
+}
+
+INSTANTIATE_TEST_CASE_P(AllAssignmentOperators, AssignmentTest,
+ Values("=", "+=", "-=", "*=", "/=", "%=", "&=", "|=",
+ "^=", "<<=", ">>="), );
+
+class IncDecTest : public ::testing::TestWithParam<std::string> {};
+
+TEST_P(IncDecTest, IncDecModifies) {
+ const std::string ModExpr = GetParam();
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; " + ModExpr + "; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre(ModExpr));
+}
+
+INSTANTIATE_TEST_CASE_P(AllIncDecOperators, IncDecTest,
+ Values("++x", "--x", "x++", "x--"), );
+
+TEST(ExprMutationAnalyzerTest, NonConstMemberFunc) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct Foo { void mf(); }; Foo x; x.mf(); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.mf()"));
+}
+
+TEST(ExprMutationAnalyzerTest, ConstMemberFunc) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct Foo { void mf() const; }; Foo x; x.mf(); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, NonConstOperator) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct Foo { Foo& operator=(int); }; Foo x; x = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, ConstOperator) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct Foo { int operator()() const; }; Foo x; x(); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ByValueArgument) {
+ auto AST =
+ tooling::buildASTFromCode("void g(int); void f() { int x; g(x); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A {}; A operator+(A, int); A x; x + 1; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A { A(int); }; int x; A y(x); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A { A(); A(A); }; A x; A y(x); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ByNonConstRefArgument) {
+ auto AST =
+ tooling::buildASTFromCode("void g(int&); void f() { int x; g(x); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("g(x)"));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A {}; A operator+(A&, int); A x; x + 1; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x + 1"));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A { A(int&); }; int x; A y(x); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A { A(); A(A&); }; A x; A y(x); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
+}
+
+TEST(ExprMutationAnalyzerTest, ByConstRefArgument) {
+ auto AST = tooling::buildASTFromCode(
+ "void g(const int&); void f() { int x; g(x); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A {}; A operator+(const A&, int); A x; x + 1; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A { A(const int&); }; int x; A y(x); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A { A(); A(const A&); }; A x; A y(x); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ByNonConstRRefArgument) {
+ auto AST = tooling::buildASTFromCode(
+ "void g(int&&); void f() { int x; g(static_cast<int &&>(x)); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("g(static_cast<int &&>(x))"));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A {}; A operator+(A&&, int); "
+ "A x; static_cast<A &&>(x) + 1; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("static_cast<A &&>(x) + 1"));
+
+ AST = tooling::buildASTFromCode("void f() { struct A { A(int&&); }; "
+ "int x; A y(static_cast<int &&>(x)); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("static_cast<int &&>(x)"));
+
+ AST = tooling::buildASTFromCode("void f() { struct A { A(); A(A&&); }; "
+ "A x; A y(static_cast<A &&>(x)); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("static_cast<A &&>(x)"));
+}
+
+TEST(ExprMutationAnalyzerTest, ByConstRRefArgument) {
+ auto AST = tooling::buildASTFromCode(
+ "void g(const int&&); void f() { int x; g(static_cast<int&&>(x)); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A {}; A operator+(const A&&, int); "
+ "A x; static_cast<A&&>(x) + 1; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode("void f() { struct A { A(const int&&); }; "
+ "int x; A y(static_cast<int&&>(x)); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode("void f() { struct A { A(); A(const A&&); }; "
+ "A x; A y(static_cast<A&&>(x)); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, Move) {
+ // Technically almost the same as ByNonConstRRefArgument, just double checking
+ const auto AST = tooling::buildASTFromCode(
+ "namespace std {"
+ "template<class T> struct remove_reference { typedef T type; };"
+ "template<class T> struct remove_reference<T&> { typedef T type; };"
+ "template<class T> struct remove_reference<T&&> { typedef T type; };"
+ "template<class T> typename std::remove_reference<T>::type&& "
+ "move(T&& t) noexcept; }"
+ "void f() { struct A {}; A x; std::move(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("std::move(x)"));
+}
+
+TEST(ExprMutationAnalyzerTest, Forward) {
+ // Technically almost the same as ByNonConstRefArgument, just double checking
+ const auto AST = tooling::buildASTFromCode(
+ "namespace std {"
+ "template<class T> struct remove_reference { typedef T type; };"
+ "template<class T> struct remove_reference<T&> { typedef T type; };"
+ "template<class T> struct remove_reference<T&&> { typedef T type; };"
+ "template<class T> T&& "
+ "forward(typename std::remove_reference<T>::type&) noexcept;"
+ "template<class T> T&& "
+ "forward(typename std::remove_reference<T>::type&&) noexcept;"
+ "void f() { struct A {}; A x; std::forward<A &>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("std::forward<A &>(x)"));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsValue) {
+ const auto AST = tooling::buildASTFromCode("int f() { int x; return x; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsNonConstRef) {
+ const auto AST = tooling::buildASTFromCode("int& f() { int x; return x; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("return x;"));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsConstRef) {
+ const auto AST =
+ tooling::buildASTFromCode("const int& f() { int x; return x; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsNonConstRRef) {
+ const auto AST = tooling::buildASTFromCode(
+ "int&& f() { int x; return static_cast<int &&>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("return static_cast<int &&>(x);"));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsConstRRef) {
+ const auto AST = tooling::buildASTFromCode(
+ "const int&& f() { int x; return static_cast<int&&>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, TakeAddress) {
+ const auto AST =
+ tooling::buildASTFromCode("void g(int*); void f() { int x; g(&x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("&x"));
+}
+
+TEST(ExprMutationAnalyzerTest, ArrayToPointerDecay) {
+ const auto AST =
+ tooling::buildASTFromCode("void g(int*); void f() { int x[2]; g(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowRefModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x; int& r0 = x; int& r1 = r0; int& r2 = r1; "
+ "int& r3 = r2; r3 = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("r0", "r1", "r2", "r3", "r3 = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowRefNotModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x; int& r0 = x; int& r1 = r0; int& r2 = r1; "
+ "int& r3 = r2; int& r4 = r3; int& r5 = r4;}");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowConditionalRefModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x, y; bool b; int &r = b ? x : y; r = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("r", "r = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowConditionalRefNotModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x, y; bool b; int& r = b ? x : y; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ArrayElementModified) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x[2]; x[0] = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x[0] = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, ArrayElementNotModified) {
+ const auto AST = tooling::buildASTFromCode("void f() { int x[2]; x[0]; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, NestedMemberModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct A { int vi; }; struct B { A va; }; "
+ "struct C { B vb; }; C x; x.vb.va.vi = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.vb.va.vi = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, NestedMemberNotModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct A { int vi; }; struct B { A va; }; "
+ "struct C { B vb; }; C x; x.vb.va.vi; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToValue) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; static_cast<double>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToRefModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x; static_cast<int &>(x) = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("static_cast<int &>(x) = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToRefNotModified) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; static_cast<int&>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToConstRef) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x; static_cast<const int&>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaDefaultCaptureByValue) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; [=]() { x = 10; }; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaExplicitCaptureByValue) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; [x]() { x = 10; }; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaDefaultCaptureByRef) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; [&]() { x = 10; }; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre(ResultOf(removeSpace, "[&](){x=10;}")));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaExplicitCaptureByRef) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; [&x]() { x = 10; }; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre(ResultOf(removeSpace, "[&x](){x=10;}")));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByRefModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x[2]; for (int& e : x) e = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("e", "e = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByRefNotModified) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x[2]; for (int& e : x) e; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByValue) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x[2]; for (int e : x) e = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByConstRef) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x[2]; for (const int& e : x) e; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByRefModified) {
+ const auto AST =
+ tooling::buildASTFromCode("struct V { int* begin(); int* end(); };"
+ "void f() { V x; for (int& e : x) e = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("e", "e = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByRefNotModified) {
+ const auto AST =
+ tooling::buildASTFromCode("struct V { int* begin(); int* end(); };"
+ "void f() { V x; for (int& e : x) e; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByValue) {
+ const auto AST = tooling::buildASTFromCode(
+ "struct V { const int* begin() const; const int* end() const; };"
+ "void f() { V x; for (int e : x) e; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByConstRef) {
+ const auto AST = tooling::buildASTFromCode(
+ "struct V { const int* begin() const; const int* end() const; };"
+ "void f() { V x; for (const int& e : x) e; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, UnevaluatedExpressions) {
+ auto AST = tooling::buildASTFromCode(
+ "void f() { int x, y; decltype(x = 10) z = y; }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { int x, y; __typeof(x = 10) z = y; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { int x, y; __typeof__(x = 10) z = y; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode("void f() { int x; sizeof(x = 10); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode("void f() { int x; alignof(x = 10); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode("void f() { int x; noexcept(x = 10); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCodeWithArgs("namespace std { class type_info; }"
+ "void f() { int x; typeid(x = 10); }",
+ {"-frtti"});
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { int x; _Generic(x = 10, int: 0, default: 1); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, NotUnevaluatedExpressions) {
+ auto AST = tooling::buildASTFromCode("void f() { int x; sizeof(int[x++]); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x++"));
+
+ AST = tooling::buildASTFromCodeWithArgs(
+ "namespace std { class type_info; }"
+ "struct A { virtual ~A(); }; struct B : A {};"
+ "struct X { A& f(); }; void f() { X x; typeid(x.f()); }",
+ {"-frtti"});
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.f()"));
+}
+
+} // namespace test
+} // namespace tidy
+} // namespace clang
diff --git a/unittests/clang-tidy/IncludeInserterTest.cpp b/unittests/clang-tidy/IncludeInserterTest.cpp
index a5f5898b..7a70f66a 100644
--- a/unittests/clang-tidy/IncludeInserterTest.cpp
+++ b/unittests/clang-tidy/IncludeInserterTest.cpp
@@ -45,7 +45,7 @@ public:
}
void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
- auto Diag = diag(Result.Nodes.getNodeAs<DeclStmt>("stmt")->getLocStart(),
+ auto Diag = diag(Result.Nodes.getNodeAs<DeclStmt>("stmt")->getBeginLoc(),
"foo, bar");
for (StringRef header : HeadersToInclude()) {
auto Fixit = Inserter->CreateIncludeInsertion(
diff --git a/unittests/clang-tidy/NamespaceAliaserTest.cpp b/unittests/clang-tidy/NamespaceAliaserTest.cpp
index 71286e29..e4f8ebce 100644
--- a/unittests/clang-tidy/NamespaceAliaserTest.cpp
+++ b/unittests/clang-tidy/NamespaceAliaserTest.cpp
@@ -36,13 +36,11 @@ public:
auto Hint = Aliaser->createAlias(*Result.Context, *Call, "::foo::bar",
{"b", "some_alias"});
if (Hint.hasValue())
- diag(Call->getLocStart(), "Fix for testing") << Hint.getValue();
+ diag(Call->getBeginLoc(), "Fix for testing") << Hint.getValue();
- diag(Call->getLocStart(), "insert call")
- << FixItHint::CreateInsertion(
- Call->getLocStart(),
- Aliaser->getNamespaceName(*Result.Context, *Call, "::foo::bar") +
- "::");
+ diag(Call->getBeginLoc(), "insert call") << FixItHint::CreateInsertion(
+ Call->getBeginLoc(),
+ Aliaser->getNamespaceName(*Result.Context, *Call, "::foo::bar") + "::");
}
private:
diff --git a/unittests/clang-tidy/OverlappingReplacementsTest.cpp b/unittests/clang-tidy/OverlappingReplacementsTest.cpp
index b7c4805c..87213b17 100644
--- a/unittests/clang-tidy/OverlappingReplacementsTest.cpp
+++ b/unittests/clang-tidy/OverlappingReplacementsTest.cpp
@@ -33,8 +33,8 @@ public:
}
void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
- diag(VD->getLocStart(), "use char") << FixItHint::CreateReplacement(
- CharSourceRange::getTokenRange(VD->getLocStart(), VD->getLocStart()),
+ diag(VD->getBeginLoc(), "use char") << FixItHint::CreateReplacement(
+ CharSourceRange::getTokenRange(VD->getBeginLoc(), VD->getBeginLoc()),
"char");
}
};
@@ -52,7 +52,7 @@ public:
auto *Cond = If->getCond();
SourceRange Range = Cond->getSourceRange();
if (auto *D = If->getConditionVariable()) {
- Range = SourceRange(D->getLocStart(), D->getLocEnd());
+ Range = SourceRange(D->getBeginLoc(), D->getEndLoc());
}
diag(Range.getBegin(), "the cake is a lie") << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(Range), "false");
diff --git a/unittests/clang-tidy/UsingInserterTest.cpp b/unittests/clang-tidy/UsingInserterTest.cpp
index 9e465a0c..16d25192 100644
--- a/unittests/clang-tidy/UsingInserterTest.cpp
+++ b/unittests/clang-tidy/UsingInserterTest.cpp
@@ -39,9 +39,9 @@ public:
Inserter->createUsingDeclaration(*Result.Context, *Call, "::foo::func");
if (Hint.hasValue())
- diag(Call->getLocStart(), "Fix for testing") << Hint.getValue();
+ diag(Call->getBeginLoc(), "Fix for testing") << Hint.getValue();
- diag(Call->getLocStart(), "insert call")
+ diag(Call->getBeginLoc(), "insert call")
<< clang::FixItHint::CreateReplacement(
Call->getCallee()->getSourceRange(),
Inserter->getShortName(*Result.Context, *Call, "::foo::func"));
diff --git a/unittests/clangd/CMakeLists.txt b/unittests/clangd/CMakeLists.txt
index 0629ab89..516060ee 100644
--- a/unittests/clangd/CMakeLists.txt
+++ b/unittests/clangd/CMakeLists.txt
@@ -15,23 +15,24 @@ add_extra_unittest(ClangdTests
CodeCompleteTests.cpp
CodeCompletionStringsTests.cpp
ContextTests.cpp
+ DexIndexTests.cpp
DraftStoreTests.cpp
+ FileDistanceTests.cpp
FileIndexTests.cpp
FindSymbolsTests.cpp
FuzzyMatchTests.cpp
GlobalCompilationDatabaseTests.cpp
HeadersTests.cpp
IndexTests.cpp
- JSONExprTests.cpp
QualityTests.cpp
SourceCodeTests.cpp
SymbolCollectorTests.cpp
SyncAPI.cpp
+ TUSchedulerTests.cpp
TestFS.cpp
TestTU.cpp
ThreadingTests.cpp
TraceTests.cpp
- TUSchedulerTests.cpp
URITests.cpp
XRefsTests.cpp
)
@@ -48,6 +49,7 @@ target_link_libraries(ClangdTests
clangSema
clangTooling
clangToolingCore
+ clangToolingInclusions
LLVMSupport
LLVMTestingSupport
)
diff --git a/unittests/clangd/ClangdTests.cpp b/unittests/clangd/ClangdTests.cpp
index a91b65ff..ace2cef4 100644
--- a/unittests/clangd/ClangdTests.cpp
+++ b/unittests/clangd/ClangdTests.cpp
@@ -35,6 +35,7 @@ namespace clangd {
using ::testing::ElementsAre;
using ::testing::Eq;
+using ::testing::Field;
using ::testing::Gt;
using ::testing::IsEmpty;
using ::testing::Pair;
@@ -152,28 +153,6 @@ protected:
}
};
-constexpr const char* ClangdTestScheme = "ClangdTests";
-class TestURIScheme : public URIScheme {
-public:
- llvm::Expected<std::string>
- getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
- llvm::StringRef /*HintPath*/) const override {
- llvm_unreachable("ClangdTests never makes absolute path.");
- }
-
- llvm::Expected<URI>
- uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
- llvm_unreachable("ClangdTest never creates a test URI.");
- }
-
- llvm::Expected<std::string> getIncludeSpelling(const URI &U) const override {
- return ("\"" + U.body().trim("/") + "\"").str();
- }
-};
-
-static URISchemeRegistry::Add<TestURIScheme>
- X(ClangdTestScheme, "Test scheme for ClangdTests.");
-
TEST_F(ClangdVFSTest, Parse) {
// FIXME: figure out a stable format for AST dumps, so that we can check the
// output of the dump itself is equal to the expected one, not just that it's
@@ -390,16 +369,7 @@ struct bar { T x; };
// Now switch to C++ mode.
CDB.ExtraClangFlags = {"-xc++"};
- // By default addDocument does not check if CompileCommand has changed, so we
- // expect to see the errors.
- runAddDocument(Server, FooCpp, SourceContents1);
- EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
- runAddDocument(Server, FooCpp, SourceContents2);
- EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
- // Passing SkipCache=true will force addDocument to reparse the file with
- // proper flags.
- runAddDocument(Server, FooCpp, SourceContents2, WantDiagnostics::Auto,
- /*SkipCache=*/true);
+ runAddDocument(Server, FooCpp, SourceContents2, WantDiagnostics::Auto);
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
// Subsequent addDocument calls should finish without errors too.
runAddDocument(Server, FooCpp, SourceContents1);
@@ -431,14 +401,7 @@ int main() { return 0; }
// Parse without the define, no errors should be produced.
CDB.ExtraClangFlags = {};
- // By default addDocument does not check if CompileCommand has changed, so we
- // expect to see the errors.
- runAddDocument(Server, FooCpp, SourceContents);
- EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
- // Passing SkipCache=true will force addDocument to reparse the file with
- // proper flags.
- runAddDocument(Server, FooCpp, SourceContents, WantDiagnostics::Auto,
- /*SkipCache=*/true);
+ runAddDocument(Server, FooCpp, SourceContents, WantDiagnostics::Auto);
ASSERT_TRUE(Server.blockUntilIdleForTest());
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
// Subsequent addDocument call should finish without errors too.
@@ -500,10 +463,8 @@ int hello;
CDB.ExtraClangFlags.clear();
DiagConsumer.clear();
Server.removeDocument(BazCpp);
- Server.addDocument(FooCpp, FooSource.code(), WantDiagnostics::Auto,
- /*SkipCache=*/true);
- Server.addDocument(BarCpp, BarSource.code(), WantDiagnostics::Auto,
- /*SkipCache=*/true);
+ Server.addDocument(FooCpp, FooSource.code(), WantDiagnostics::Auto);
+ Server.addDocument(BarCpp, BarSource.code(), WantDiagnostics::Auto);
ASSERT_TRUE(Server.blockUntilIdleForTest());
EXPECT_THAT(DiagConsumer.filesWithDiags(),
@@ -574,7 +535,7 @@ TEST_F(ClangdVFSTest, InvalidCompileCommand) {
// can't parse the file.
EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Position(),
clangd::CodeCompleteOptions()))
- .items,
+ .Completions,
IsEmpty());
auto SigHelp = runSignatureHelp(Server, FooCpp, Position());
ASSERT_TRUE(bool(SigHelp)) << "signatureHelp returned an error";
@@ -708,7 +669,7 @@ int d;
Server.addDocument(FilePaths[FileIndex],
ShouldHaveErrors ? SourceContentsWithErrors
: SourceContentsWithoutErrors,
- WantDiagnostics::Auto, SkipCache);
+ WantDiagnostics::Auto);
UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
};
@@ -967,6 +928,41 @@ void f() {}
EXPECT_EQ(Expected, *Changed);
}
+TEST_F(ClangdVFSTest, ChangedHeaderFromISystem) {
+ MockFSProvider FS;
+ ErrorCheckingDiagConsumer DiagConsumer;
+ MockCompilationDatabase CDB;
+ ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
+
+ auto SourcePath = testPath("source/foo.cpp");
+ auto HeaderPath = testPath("headers/foo.h");
+ FS.Files[HeaderPath] = "struct X { int bar; };";
+ Annotations Code(R"cpp(
+ #include "foo.h"
+
+ int main() {
+ X().ba^
+ })cpp");
+ CDB.ExtraClangFlags.push_back("-xc++");
+ CDB.ExtraClangFlags.push_back("-isystem" + testPath("headers"));
+
+ runAddDocument(Server, SourcePath, Code.code());
+ auto Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(),
+ clangd::CodeCompleteOptions()))
+ .Completions;
+ EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar")));
+ // Update the header and rerun addDocument to make sure we get the updated
+ // files.
+ FS.Files[HeaderPath] = "struct X { int bar; int baz; };";
+ runAddDocument(Server, SourcePath, Code.code());
+ Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(),
+ clangd::CodeCompleteOptions()))
+ .Completions;
+ // We want to make sure we see the updated version.
+ EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar"),
+ Field(&CodeCompletion::Name, "baz")));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/unittests/clangd/ClangdUnitTests.cpp b/unittests/clangd/ClangdUnitTests.cpp
index 6843c9e0..5e662cb8 100644
--- a/unittests/clangd/ClangdUnitTests.cpp
+++ b/unittests/clangd/ClangdUnitTests.cpp
@@ -172,7 +172,7 @@ TEST(DiagnosticsTest, ToLSP) {
};
// Diagnostics should turn into these:
- clangd::Diagnostic MainLSP = MatchingLSP(D, R"(something terrible happened
+ clangd::Diagnostic MainLSP = MatchingLSP(D, R"(Something terrible happened
main.cpp:6:7: remark: declared somewhere in the main file
@@ -180,7 +180,7 @@ main.cpp:6:7: remark: declared somewhere in the main file
note: declared somewhere in the header file)");
clangd::Diagnostic NoteInMainLSP =
- MatchingLSP(NoteInMain, R"(declared somewhere in the main file
+ MatchingLSP(NoteInMain, R"(Declared somewhere in the main file
main.cpp:2:3: error: something terrible happened)");
diff --git a/unittests/clangd/CodeCompleteTests.cpp b/unittests/clangd/CodeCompleteTests.cpp
index 2fd85885..5fd17d0a 100644
--- a/unittests/clangd/CodeCompleteTests.cpp
+++ b/unittests/clangd/CodeCompleteTests.cpp
@@ -13,10 +13,12 @@
#include "Compiler.h"
#include "Matchers.h"
#include "Protocol.h"
+#include "Quality.h"
#include "SourceCode.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "index/MemIndex.h"
+#include "clang/Sema/CodeCompleteConsumer.h"
#include "llvm/Support/Error.h"
#include "llvm/Testing/Support/Error.h"
#include "gmock/gmock.h"
@@ -32,6 +34,8 @@ using ::testing::Contains;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::Field;
+using ::testing::HasSubstr;
+using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::UnorderedElementsAre;
@@ -41,46 +45,32 @@ class IgnoreDiagnostics : public DiagnosticsConsumer {
};
// GMock helpers for matching completion items.
-MATCHER_P(Named, Name, "") { return arg.insertText == Name; }
-MATCHER_P(Labeled, Label, "") { return arg.label == Label; }
-MATCHER_P(Kind, K, "") { return arg.kind == K; }
-MATCHER_P(Filter, F, "") { return arg.filterText == F; }
-MATCHER_P(Doc, D, "") { return arg.documentation == D; }
-MATCHER_P(Detail, D, "") { return arg.detail == D; }
+MATCHER_P(Named, Name, "") { return arg.Name == Name; }
+MATCHER_P(Scope, S, "") { return arg.Scope == S; }
+MATCHER_P(Qualifier, Q, "") { return arg.RequiredQualifier == Q; }
+MATCHER_P(Labeled, Label, "") {
+ return arg.RequiredQualifier + arg.Name + arg.Signature == Label;
+}
+MATCHER_P(SigHelpLabeled, Label, "") { return arg.label == Label; }
+MATCHER_P(Kind, K, "") { return arg.Kind == K; }
+MATCHER_P(Doc, D, "") { return arg.Documentation == D; }
+MATCHER_P(ReturnType, D, "") { return arg.ReturnType == D; }
MATCHER_P(InsertInclude, IncludeHeader, "") {
- if (arg.additionalTextEdits.size() != 1)
- return false;
- const auto &Edit = arg.additionalTextEdits[0];
- if (Edit.range.start != Edit.range.end)
- return false;
- SmallVector<StringRef, 2> Matches;
- llvm::Regex RE(R"(#include[ ]*(["<][^">]*[">]))");
- return RE.match(Edit.newText, &Matches) && Matches[1] == IncludeHeader;
-}
-MATCHER_P(PlainText, Text, "") {
- return arg.insertTextFormat == clangd::InsertTextFormat::PlainText &&
- arg.insertText == Text;
-}
-MATCHER_P(Snippet, Text, "") {
- return arg.insertTextFormat == clangd::InsertTextFormat::Snippet &&
- arg.insertText == Text;
+ return arg.Header == IncludeHeader && bool(arg.HeaderInsertion);
}
-MATCHER(NameContainsFilter, "") {
- if (arg.filterText.empty())
- return true;
- return llvm::StringRef(arg.insertText).contains(arg.filterText);
-}
-MATCHER(HasAdditionalEdits, "") { return !arg.additionalTextEdits.empty(); }
+MATCHER(InsertInclude, "") { return bool(arg.HeaderInsertion); }
+MATCHER_P(SnippetSuffix, Text, "") { return arg.SnippetSuffix == Text; }
+MATCHER_P(Origin, OriginSet, "") { return arg.Origin == OriginSet; }
// Shorthand for Contains(Named(Name)).
-Matcher<const std::vector<CompletionItem> &> Has(std::string Name) {
+Matcher<const std::vector<CodeCompletion> &> Has(std::string Name) {
return Contains(Named(std::move(Name)));
}
-Matcher<const std::vector<CompletionItem> &> Has(std::string Name,
+Matcher<const std::vector<CodeCompletion> &> Has(std::string Name,
CompletionItemKind K) {
return Contains(AllOf(Named(std::move(Name)), Kind(K)));
}
-MATCHER(IsDocumented, "") { return !arg.documentation.empty(); }
+MATCHER(IsDocumented, "") { return !arg.Documentation.empty(); }
std::unique_ptr<SymbolIndex> memIndex(std::vector<Symbol> Symbols) {
SymbolSlab::Builder Slab;
@@ -89,9 +79,26 @@ std::unique_ptr<SymbolIndex> memIndex(std::vector<Symbol> Symbols) {
return MemIndex::build(std::move(Slab).build());
}
-CompletionList completions(ClangdServer &Server, StringRef Text,
- std::vector<Symbol> IndexSymbols = {},
- clangd::CodeCompleteOptions Opts = {}) {
+CodeCompleteResult completions(ClangdServer &Server, StringRef TestCode,
+ Position point,
+ std::vector<Symbol> IndexSymbols = {},
+ clangd::CodeCompleteOptions Opts = {}) {
+ std::unique_ptr<SymbolIndex> OverrideIndex;
+ if (!IndexSymbols.empty()) {
+ assert(!Opts.Index && "both Index and IndexSymbols given!");
+ OverrideIndex = memIndex(std::move(IndexSymbols));
+ Opts.Index = OverrideIndex.get();
+ }
+
+ auto File = testPath("foo.cpp");
+ runAddDocument(Server, File, TestCode);
+ auto CompletionList = cantFail(runCodeComplete(Server, File, point, Opts));
+ return CompletionList;
+}
+
+CodeCompleteResult completions(ClangdServer &Server, StringRef Text,
+ std::vector<Symbol> IndexSymbols = {},
+ clangd::CodeCompleteOptions Opts = {}) {
std::unique_ptr<SymbolIndex> OverrideIndex;
if (!IndexSymbols.empty()) {
assert(!Opts.Index && "both Index and IndexSymbols given!");
@@ -104,16 +111,14 @@ CompletionList completions(ClangdServer &Server, StringRef Text,
runAddDocument(Server, File, Test.code());
auto CompletionList =
cantFail(runCodeComplete(Server, File, Test.point(), Opts));
- // Sanity-check that filterText is valid.
- EXPECT_THAT(CompletionList.items, Each(NameContainsFilter()));
return CompletionList;
}
// Builds a server and runs code completion.
// If IndexSymbols is non-empty, an index will be built and passed to opts.
-CompletionList completions(StringRef Text,
- std::vector<Symbol> IndexSymbols = {},
- clangd::CodeCompleteOptions Opts = {}) {
+CodeCompleteResult completions(StringRef Text,
+ std::vector<Symbol> IndexSymbols = {},
+ clangd::CodeCompleteOptions Opts = {}) {
MockFSProvider FS;
MockCompilationDatabase CDB;
IgnoreDiagnostics DiagConsumer;
@@ -149,10 +154,9 @@ Symbol sym(StringRef QName, index::SymbolKind Kind, StringRef USRFormat) {
}
USR += Regex("^.*$").sub(USRFormat, Sym.Name); // e.g. func -> @F@func#
Sym.ID = SymbolID(USR);
- Sym.CompletionPlainInsertText = Sym.Name;
- Sym.CompletionSnippetInsertText = Sym.Name;
- Sym.CompletionLabel = Sym.Name;
Sym.SymInfo.Kind = Kind;
+ Sym.IsIndexedForCodeCompletion = true;
+ Sym.Origin = SymbolOrigin::Static;
return Sym;
}
Symbol func(StringRef Name) { // Assumes the function has no args.
@@ -185,34 +189,28 @@ int main() { ClassWithMembers().^ }
)cpp",
/*IndexSymbols=*/{}, Opts);
- EXPECT_TRUE(Results.isIncomplete);
- EXPECT_THAT(Results.items, ElementsAre(Named("AAA"), Named("BBB")));
+ EXPECT_TRUE(Results.HasMore);
+ EXPECT_THAT(Results.Completions, ElementsAre(Named("AAA"), Named("BBB")));
}
TEST(CompletionTest, Filter) {
std::string Body = R"cpp(
- int Abracadabra;
- int Alakazam;
+ #define MotorCar
+ int Car;
struct S {
int FooBar;
int FooBaz;
int Qux;
};
)cpp";
- EXPECT_THAT(completions(Body + "int main() { S().Foba^ }").items,
- AllOf(Has("FooBar"), Has("FooBaz"), Not(Has("Qux"))));
-
- EXPECT_THAT(completions(Body + "int main() { S().FR^ }").items,
- AllOf(Has("FooBar"), Not(Has("FooBaz")), Not(Has("Qux"))));
-
- EXPECT_THAT(completions(Body + "int main() { S().opr^ }").items,
- Has("operator="));
- EXPECT_THAT(completions(Body + "int main() { aaa^ }").items,
- AllOf(Has("Abracadabra"), Has("Alakazam")));
+ // Only items matching the fuzzy query are returned.
+ EXPECT_THAT(completions(Body + "int main() { S().Foba^ }").Completions,
+ AllOf(Has("FooBar"), Has("FooBaz"), Not(Has("Qux"))));
- EXPECT_THAT(completions(Body + "int main() { _a^ }").items,
- AllOf(Has("static_cast"), Not(Has("Abracadabra"))));
+ // Macros require prefix match.
+ EXPECT_THAT(completions(Body + "int main() { C^ }").Completions,
+ AllOf(Has("Car"), Not(Has("MotorCar"))));
}
void TestAfterDotCompletion(clangd::CodeCompleteOptions Opts) {
@@ -248,22 +246,24 @@ void TestAfterDotCompletion(clangd::CodeCompleteOptions Opts) {
// Class members. The only items that must be present in after-dot
// completion.
- EXPECT_THAT(
- Results.items,
- AllOf(Has(Opts.EnableSnippets ? "method()" : "method"), Has("field")));
- EXPECT_IFF(Opts.IncludeIneligibleResults, Results.items,
+ EXPECT_THAT(Results.Completions,
+ AllOf(Has("method"), Has("field"), Not(Has("ClassWithMembers")),
+ Not(Has("operator=")), Not(Has("~ClassWithMembers"))));
+ EXPECT_IFF(Opts.IncludeIneligibleResults, Results.Completions,
Has("private_field"));
// Global items.
EXPECT_THAT(
- Results.items,
+ Results.Completions,
Not(AnyOf(Has("global_var"), Has("index_var"), Has("global_func"),
Has("global_func()"), Has("index_func"), Has("GlobalClass"),
Has("IndexClass"), Has("MACRO"), Has("LocalClass"))));
// There should be no code patterns (aka snippets) in after-dot
// completion. At least there aren't any we're aware of.
- EXPECT_THAT(Results.items, Not(Contains(Kind(CompletionItemKind::Snippet))));
+ EXPECT_THAT(Results.Completions,
+ Not(Contains(Kind(CompletionItemKind::Snippet))));
// Check documentation.
- EXPECT_IFF(Opts.IncludeComments, Results.items, Contains(IsDocumented()));
+ EXPECT_IFF(Opts.IncludeComments, Results.Completions,
+ Contains(IsDocumented()));
}
void TestGlobalScopeCompletion(clangd::CodeCompleteOptions Opts) {
@@ -293,22 +293,22 @@ void TestGlobalScopeCompletion(clangd::CodeCompleteOptions Opts) {
{cls("IndexClass"), var("index_var"), func("index_func")}, Opts);
// Class members. Should never be present in global completions.
- EXPECT_THAT(Results.items,
+ EXPECT_THAT(Results.Completions,
Not(AnyOf(Has("method"), Has("method()"), Has("field"))));
// Global items.
- EXPECT_THAT(Results.items,
- AllOf(Has("global_var"), Has("index_var"),
- Has(Opts.EnableSnippets ? "global_func()" : "global_func"),
+ EXPECT_THAT(Results.Completions,
+ AllOf(Has("global_var"), Has("index_var"), Has("global_func"),
Has("index_func" /* our fake symbol doesn't include () */),
Has("GlobalClass"), Has("IndexClass")));
// A macro.
- EXPECT_IFF(Opts.IncludeMacros, Results.items, Has("MACRO"));
+ EXPECT_IFF(Opts.IncludeMacros, Results.Completions, Has("MACRO"));
// Local items. Must be present always.
- EXPECT_THAT(Results.items,
+ EXPECT_THAT(Results.Completions,
AllOf(Has("local_var"), Has("LocalClass"),
Contains(Kind(CompletionItemKind::Snippet))));
// Check documentation.
- EXPECT_IFF(Opts.IncludeComments, Results.items, Contains(IsDocumented()));
+ EXPECT_IFF(Opts.IncludeComments, Results.Completions,
+ Contains(IsDocumented()));
}
TEST(CompletionTest, CompletionOptions) {
@@ -320,7 +320,6 @@ TEST(CompletionTest, CompletionOptions) {
auto Flags = {
&clangd::CodeCompleteOptions::IncludeMacros,
&clangd::CodeCompleteOptions::IncludeComments,
- &clangd::CodeCompleteOptions::EnableSnippets,
&clangd::CodeCompleteOptions::IncludeCodePatterns,
&clangd::CodeCompleteOptions::IncludeIneligibleResults,
};
@@ -343,7 +342,7 @@ TEST(CompletionTest, Priorities) {
};
void Foo::pub() { this->^ }
)cpp");
- EXPECT_THAT(Internal.items,
+ EXPECT_THAT(Internal.Completions,
HasSubsequence(Named("priv"), Named("prot"), Named("pub")));
auto External = completions(R"cpp(
@@ -357,7 +356,7 @@ TEST(CompletionTest, Priorities) {
F.^
}
)cpp");
- EXPECT_THAT(External.items,
+ EXPECT_THAT(External.Completions,
AllOf(Has("pub"), Not(Has("prot")), Not(Has("priv"))));
}
@@ -372,14 +371,37 @@ TEST(CompletionTest, Qualifiers) {
};
void test() { Bar().^ }
)cpp");
- EXPECT_THAT(Results.items, HasSubsequence(Labeled("bar() const"),
- Labeled("Foo::foo() const")));
- EXPECT_THAT(Results.items, Not(Contains(Labeled("foo() const")))); // private
+ EXPECT_THAT(Results.Completions,
+ HasSubsequence(AllOf(Qualifier(""), Named("bar")),
+ AllOf(Qualifier("Foo::"), Named("foo"))));
+ EXPECT_THAT(Results.Completions,
+ Not(Contains(AllOf(Qualifier(""), Named("foo"))))); // private
+}
+
+TEST(CompletionTest, InjectedTypename) {
+ // These are suppressed when accessed as a member...
+ EXPECT_THAT(completions("struct X{}; void foo(){ X().^ }").Completions,
+ Not(Has("X")));
+ EXPECT_THAT(completions("struct X{ void foo(){ this->^ } };").Completions,
+ Not(Has("X")));
+ // ...but accessible in other, more useful cases.
+ EXPECT_THAT(completions("struct X{ void foo(){ ^ } };").Completions,
+ Has("X"));
+ EXPECT_THAT(
+ completions("struct Y{}; struct X:Y{ void foo(){ ^ } };").Completions,
+ Has("Y"));
+ EXPECT_THAT(
+ completions(
+ "template<class> struct Y{}; struct X:Y<int>{ void foo(){ ^ } };")
+ .Completions,
+ Has("Y"));
+ // This case is marginal (`using X::X` is useful), we allow it for now.
+ EXPECT_THAT(completions("struct X{}; void foo(){ X::^ }").Completions,
+ Has("X"));
}
TEST(CompletionTest, Snippets) {
clangd::CodeCompleteOptions Opts;
- Opts.EnableSnippets = true;
auto Results = completions(
R"cpp(
struct fake {
@@ -392,9 +414,10 @@ TEST(CompletionTest, Snippets) {
}
)cpp",
/*IndexSymbols=*/{}, Opts);
- EXPECT_THAT(Results.items,
- HasSubsequence(Snippet("a"),
- Snippet("f(${1:int i}, ${2:const float f})")));
+ EXPECT_THAT(
+ Results.Completions,
+ HasSubsequence(Named("a"),
+ SnippetSuffix("(${1:int i}, ${2:const float f})")));
}
TEST(CompletionTest, Kinds) {
@@ -407,7 +430,7 @@ TEST(CompletionTest, Kinds) {
int X = ^
)cpp",
{func("indexFunction"), var("indexVariable"), cls("indexClass")});
- EXPECT_THAT(Results.items,
+ EXPECT_THAT(Results.Completions,
AllOf(Has("function", CompletionItemKind::Function),
Has("variable", CompletionItemKind::Variable),
Has("int", CompletionItemKind::Keyword),
@@ -418,7 +441,8 @@ TEST(CompletionTest, Kinds) {
Has("indexClass", CompletionItemKind::Class)));
Results = completions("nam^");
- EXPECT_THAT(Results.items, Has("namespace", CompletionItemKind::Snippet));
+ EXPECT_THAT(Results.Completions,
+ Has("namespace", CompletionItemKind::Snippet));
}
TEST(CompletionTest, NoDuplicates) {
@@ -434,27 +458,29 @@ TEST(CompletionTest, NoDuplicates) {
{cls("Adapter")});
// Make sure there are no duplicate entries of 'Adapter'.
- EXPECT_THAT(Results.items, ElementsAre(Named("Adapter")));
+ EXPECT_THAT(Results.Completions, ElementsAre(Named("Adapter")));
}
TEST(CompletionTest, ScopedNoIndex) {
auto Results = completions(
R"cpp(
- namespace fake { int BigBang, Babble, Ball; };
- int main() { fake::bb^ }
+ namespace fake { int BigBang, Babble, Box; };
+ int main() { fake::ba^ }
")cpp");
- // BigBang is a better match than Babble. Ball doesn't match at all.
- EXPECT_THAT(Results.items, ElementsAre(Named("BigBang"), Named("Babble")));
+ // Babble is a better match than BigBang. Box doesn't match at all.
+ EXPECT_THAT(Results.Completions,
+ ElementsAre(Named("Babble"), Named("BigBang")));
}
TEST(CompletionTest, Scoped) {
auto Results = completions(
R"cpp(
- namespace fake { int Babble, Ball; };
- int main() { fake::bb^ }
+ namespace fake { int Babble, Box; };
+ int main() { fake::ba^ }
")cpp",
{var("fake::BigBang")});
- EXPECT_THAT(Results.items, ElementsAre(Named("BigBang"), Named("Babble")));
+ EXPECT_THAT(Results.Completions,
+ ElementsAre(Named("Babble"), Named("BigBang")));
}
TEST(CompletionTest, ScopedWithFilter) {
@@ -463,16 +489,16 @@ TEST(CompletionTest, ScopedWithFilter) {
void f() { ns::x^ }
)cpp",
{cls("ns::XYZ"), func("ns::foo")});
- EXPECT_THAT(Results.items,
- UnorderedElementsAre(AllOf(Named("XYZ"), Filter("XYZ"))));
+ EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("XYZ")));
}
TEST(CompletionTest, ReferencesAffectRanking) {
- auto Results = completions("int main() { abs^ }", {ns("absl"), func("abs")});
- EXPECT_THAT(Results.items, HasSubsequence(Named("abs"), Named("absl")));
+ auto Results = completions("int main() { abs^ }", {ns("absl"), func("absb")});
+ EXPECT_THAT(Results.Completions, HasSubsequence(Named("absb"), Named("absl")));
Results = completions("int main() { abs^ }",
- {withReferences(10000, ns("absl")), func("abs")});
- EXPECT_THAT(Results.items, HasSubsequence(Named("absl"), Named("abs")));
+ {withReferences(10000, ns("absl")), func("absb")});
+ EXPECT_THAT(Results.Completions,
+ HasSubsequence(Named("absl"), Named("absb")));
}
TEST(CompletionTest, GlobalQualified) {
@@ -481,8 +507,9 @@ TEST(CompletionTest, GlobalQualified) {
void f() { ::^ }
)cpp",
{cls("XYZ")});
- EXPECT_THAT(Results.items, AllOf(Has("XYZ", CompletionItemKind::Class),
- Has("f", CompletionItemKind::Function)));
+ EXPECT_THAT(Results.Completions,
+ AllOf(Has("XYZ", CompletionItemKind::Class),
+ Has("f", CompletionItemKind::Function)));
}
TEST(CompletionTest, FullyQualified) {
@@ -492,8 +519,9 @@ TEST(CompletionTest, FullyQualified) {
void f() { ::ns::^ }
)cpp",
{cls("ns::XYZ")});
- EXPECT_THAT(Results.items, AllOf(Has("XYZ", CompletionItemKind::Class),
- Has("bar", CompletionItemKind::Function)));
+ EXPECT_THAT(Results.Completions,
+ AllOf(Has("XYZ", CompletionItemKind::Class),
+ Has("bar", CompletionItemKind::Function)));
}
TEST(CompletionTest, SemaIndexMerge) {
@@ -504,9 +532,12 @@ TEST(CompletionTest, SemaIndexMerge) {
)cpp",
{func("ns::both"), cls("ns::Index")});
// We get results from both index and sema, with no duplicates.
- EXPECT_THAT(
- Results.items,
- UnorderedElementsAre(Named("local"), Named("Index"), Named("both")));
+ EXPECT_THAT(Results.Completions,
+ UnorderedElementsAre(
+ AllOf(Named("local"), Origin(SymbolOrigin::AST)),
+ AllOf(Named("Index"), Origin(SymbolOrigin::Static)),
+ AllOf(Named("both"),
+ Origin(SymbolOrigin::AST | SymbolOrigin::Static))));
}
TEST(CompletionTest, SemaIndexMergeWithLimit) {
@@ -518,8 +549,8 @@ TEST(CompletionTest, SemaIndexMergeWithLimit) {
void f() { ::ns::^ }
)cpp",
{func("ns::both"), cls("ns::Index")}, Opts);
- EXPECT_EQ(Results.items.size(), Opts.Limit);
- EXPECT_TRUE(Results.isIncomplete);
+ EXPECT_EQ(Results.Completions.size(), Opts.Limit);
+ EXPECT_TRUE(Results.HasMore);
}
TEST(CompletionTest, IncludeInsertionPreprocessorIntegrationTests) {
@@ -545,7 +576,7 @@ TEST(CompletionTest, IncludeInsertionPreprocessorIntegrationTests) {
int main() { ns::^ }
)cpp",
{Sym});
- EXPECT_THAT(Results.items,
+ EXPECT_THAT(Results.Completions,
ElementsAre(AllOf(Named("X"), InsertInclude("\"bar.h\""))));
// Duplicate based on inclusions in preamble.
Results = completions(Server,
@@ -554,8 +585,8 @@ TEST(CompletionTest, IncludeInsertionPreprocessorIntegrationTests) {
int main() { ns::^ }
)cpp",
{Sym});
- EXPECT_THAT(Results.items,
- ElementsAre(AllOf(Named("X"), Not(HasAdditionalEdits()))));
+ EXPECT_THAT(Results.Completions, ElementsAre(AllOf(Named("X"), Labeled("X"),
+ Not(InsertInclude()))));
}
TEST(CompletionTest, NoIncludeInsertionWhenDeclFoundInFile) {
@@ -584,9 +615,9 @@ TEST(CompletionTest, NoIncludeInsertionWhenDeclFoundInFile) {
int main() { ns::^ }
)cpp",
{SymX, SymY});
- EXPECT_THAT(Results.items,
- ElementsAre(AllOf(Named("X"), Not(HasAdditionalEdits())),
- AllOf(Named("Y"), Not(HasAdditionalEdits()))));
+ EXPECT_THAT(Results.Completions,
+ ElementsAre(AllOf(Named("X"), Not(InsertInclude())),
+ AllOf(Named("Y"), Not(InsertInclude()))));
}
TEST(CompletionTest, IndexSuppressesPreambleCompletions) {
@@ -610,16 +641,16 @@ TEST(CompletionTest, IndexSuppressesPreambleCompletions) {
auto I = memIndex({var("ns::index")});
Opts.Index = I.get();
auto WithIndex = cantFail(runCodeComplete(Server, File, Test.point(), Opts));
- EXPECT_THAT(WithIndex.items,
+ EXPECT_THAT(WithIndex.Completions,
UnorderedElementsAre(Named("local"), Named("index")));
auto ClassFromPreamble =
cantFail(runCodeComplete(Server, File, Test.point("2"), Opts));
- EXPECT_THAT(ClassFromPreamble.items, Contains(Named("member")));
+ EXPECT_THAT(ClassFromPreamble.Completions, Contains(Named("member")));
Opts.Index = nullptr;
auto WithoutIndex =
cantFail(runCodeComplete(Server, File, Test.point(), Opts));
- EXPECT_THAT(WithoutIndex.items,
+ EXPECT_THAT(WithoutIndex.Completions,
UnorderedElementsAre(Named("local"), Named("preamble")));
}
@@ -652,12 +683,12 @@ TEST(CompletionTest, DynamicIndexMultiFile) {
auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {}));
// "XYZ" and "foo" are not included in the file being completed but are still
// visible through the index.
- EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class));
- EXPECT_THAT(Results.items, Has("foo", CompletionItemKind::Function));
- EXPECT_THAT(Results.items, Has("XXX", CompletionItemKind::Class));
- EXPECT_THAT(Results.items, Contains(AllOf(Named("fooooo"), Filter("fooooo"),
- Kind(CompletionItemKind::Function),
- Doc("Doooc"), Detail("void"))));
+ EXPECT_THAT(Results.Completions, Has("XYZ", CompletionItemKind::Class));
+ EXPECT_THAT(Results.Completions, Has("foo", CompletionItemKind::Function));
+ EXPECT_THAT(Results.Completions, Has("XXX", CompletionItemKind::Class));
+ EXPECT_THAT(Results.Completions,
+ Contains((Named("fooooo"), Kind(CompletionItemKind::Function),
+ Doc("Doooc"), ReturnType("void"))));
}
TEST(CompletionTest, Documentation) {
@@ -675,21 +706,35 @@ TEST(CompletionTest, Documentation) {
int x = ^
)cpp");
- EXPECT_THAT(Results.items,
+ EXPECT_THAT(Results.Completions,
Contains(AllOf(Named("foo"), Doc("Non-doxygen comment."))));
EXPECT_THAT(
- Results.items,
+ Results.Completions,
Contains(AllOf(Named("bar"), Doc("Doxygen comment.\n\\param int a"))));
- EXPECT_THAT(Results.items,
+ EXPECT_THAT(Results.Completions,
Contains(AllOf(Named("baz"), Doc("Multi-line\nblock comment"))));
}
+TEST(CompletionTest, GlobalCompletionFiltering) {
+
+ Symbol Class = cls("XYZ");
+ Class.IsIndexedForCodeCompletion = false;
+ Symbol Func = func("XYZ::foooo");
+ Func.IsIndexedForCodeCompletion = false;
+
+ auto Results = completions(R"(// void f() {
+ XYZ::foooo^
+ })",
+ {Class, Func});
+ EXPECT_THAT(Results.Completions, IsEmpty());
+}
+
TEST(CodeCompleteTest, DisableTypoCorrection) {
auto Results = completions(R"cpp(
namespace clang { int v; }
void f() { clangd::^
)cpp");
- EXPECT_TRUE(Results.items.empty());
+ EXPECT_TRUE(Results.Completions.empty());
}
TEST(CodeCompleteTest, NoColonColonAtTheEnd) {
@@ -700,8 +745,8 @@ TEST(CodeCompleteTest, NoColonColonAtTheEnd) {
}
)cpp");
- EXPECT_THAT(Results.items, Contains(Labeled("clang")));
- EXPECT_THAT(Results.items, Not(Contains(Labeled("clang::"))));
+ EXPECT_THAT(Results.Completions, Contains(Labeled("clang")));
+ EXPECT_THAT(Results.Completions, Not(Contains(Labeled("clang::"))));
}
TEST(CompletionTest, BacktrackCrashes) {
@@ -714,7 +759,7 @@ TEST(CompletionTest, BacktrackCrashes) {
int foo(ns::FooBar^
)cpp");
- EXPECT_THAT(Results.items, ElementsAre(Labeled("FooBarBaz")));
+ EXPECT_THAT(Results.Completions, ElementsAre(Labeled("FooBarBaz")));
// Check we don't crash in that case too.
completions(R"cpp(
@@ -740,7 +785,7 @@ int f(int input_num) {
}
)cpp");
- EXPECT_THAT(Results.items,
+ EXPECT_THAT(Results.Completions,
UnorderedElementsAre(Named("X"), Named("Y")));
}
@@ -758,23 +803,27 @@ int f(int input_num) {
} // namespace ns
)cpp");
- EXPECT_THAT(Results.items, Contains(Named("X")));
+ EXPECT_THAT(Results.Completions, Contains(Named("X")));
}
-TEST(CompletionTest, CompleteInExcludedPPBranch) {
+TEST(CompletionTest, IgnoreCompleteInExcludedPPBranchWithRecoveryContext) {
auto Results = completions(R"cpp(
int bar(int param_in_bar) {
}
int foo(int param_in_foo) {
#if 0
+ // In recorvery mode, "param_in_foo" will also be suggested among many other
+ // unrelated symbols; however, this is really a special case where this works.
+ // If the #if block is outside of the function, "param_in_foo" is still
+ // suggested, but "bar" and "foo" are missing. So the recovery mode doesn't
+ // really provide useful results in excluded branches.
par^
#endif
}
)cpp");
- EXPECT_THAT(Results.items, Contains(Labeled("param_in_foo")));
- EXPECT_THAT(Results.items, Not(Contains(Labeled("param_in_bar"))));
+ EXPECT_TRUE(Results.Completions.empty());
}
SignatureHelp signatures(StringRef Text) {
@@ -799,7 +848,7 @@ MATCHER_P(ParamsAre, P, "") {
Matcher<SignatureInformation> Sig(std::string Label,
std::vector<std::string> Params) {
- return AllOf(Labeled(Label), ParamsAre(Params));
+ return AllOf(SigHelpLabeled(Label), ParamsAre(Params));
}
TEST(SignatureHelpTest, Overloads) {
@@ -861,6 +910,10 @@ public:
void lookup(const LookupRequest &,
llvm::function_ref<void(const Symbol &)>) const override {}
+ void findOccurrences(const OccurrencesRequest &Req,
+ llvm::function_ref<void(const SymbolOccurrence &)>
+ Callback) const override {}
+
const std::vector<FuzzyFindRequest> allRequests() const { return Requests; }
private:
@@ -980,7 +1033,7 @@ TEST(CompletionTest, NoIndexCompletionsInsideClasses) {
Foo::^)cpp",
{func("::SomeNameInTheIndex"), func("::Foo::SomeNameInTheIndex")});
- EXPECT_THAT(Completions.items,
+ EXPECT_THAT(Completions.Completions,
AllOf(Contains(Labeled("SomeNameOfField")),
Contains(Labeled("SomeNameOfTypedefField")),
Not(Contains(Labeled("SomeNameInTheIndex")))));
@@ -997,7 +1050,7 @@ TEST(CompletionTest, NoIndexCompletionsInsideDependentCode) {
)cpp",
{func("::SomeNameInTheIndex")});
- EXPECT_THAT(Completions.items,
+ EXPECT_THAT(Completions.Completions,
Not(Contains(Labeled("SomeNameInTheIndex"))));
}
@@ -1011,7 +1064,7 @@ TEST(CompletionTest, NoIndexCompletionsInsideDependentCode) {
)cpp",
{func("::SomeNameInTheIndex")});
- EXPECT_THAT(Completions.items,
+ EXPECT_THAT(Completions.Completions,
Not(Contains(Labeled("SomeNameInTheIndex"))));
}
@@ -1025,11 +1078,62 @@ TEST(CompletionTest, NoIndexCompletionsInsideDependentCode) {
)cpp",
{func("::SomeNameInTheIndex")});
- EXPECT_THAT(Completions.items,
+ EXPECT_THAT(Completions.Completions,
Not(Contains(Labeled("SomeNameInTheIndex"))));
}
}
+TEST(CompletionTest, OverloadBundling) {
+ clangd::CodeCompleteOptions Opts;
+ Opts.BundleOverloads = true;
+
+ std::string Context = R"cpp(
+ struct X {
+ // Overload with int
+ int a(int);
+ // Overload with bool
+ int a(bool);
+ int b(float);
+ };
+ int GFuncC(int);
+ int GFuncD(int);
+ )cpp";
+
+ // Member completions are bundled.
+ EXPECT_THAT(completions(Context + "int y = X().^", {}, Opts).Completions,
+ UnorderedElementsAre(Labeled("a(…)"), Labeled("b(float)")));
+
+ // Non-member completions are bundled, including index+sema.
+ Symbol NoArgsGFunc = func("GFuncC");
+ EXPECT_THAT(
+ completions(Context + "int y = GFunc^", {NoArgsGFunc}, Opts).Completions,
+ UnorderedElementsAre(Labeled("GFuncC(…)"), Labeled("GFuncD(int)")));
+
+ // Differences in header-to-insert suppress bundling.
+ Symbol::Details Detail;
+ std::string DeclFile = URI::createFile(testPath("foo")).toString();
+ NoArgsGFunc.CanonicalDeclaration.FileURI = DeclFile;
+ Detail.IncludeHeader = "<foo>";
+ NoArgsGFunc.Detail = &Detail;
+ EXPECT_THAT(
+ completions(Context + "int y = GFunc^", {NoArgsGFunc}, Opts).Completions,
+ UnorderedElementsAre(AllOf(Named("GFuncC"), InsertInclude("<foo>")),
+ Labeled("GFuncC(int)"), Labeled("GFuncD(int)")));
+
+ // Examine a bundled completion in detail.
+ auto A =
+ completions(Context + "int y = X().a^", {}, Opts).Completions.front();
+ EXPECT_EQ(A.Name, "a");
+ EXPECT_EQ(A.Signature, "(…)");
+ EXPECT_EQ(A.BundleSize, 2u);
+ EXPECT_EQ(A.Kind, CompletionItemKind::Method);
+ EXPECT_EQ(A.ReturnType, "int"); // All overloads return int.
+ // For now we just return one of the doc strings arbitrarily.
+ EXPECT_THAT(A.Documentation, AnyOf(HasSubstr("Overload with int"),
+ HasSubstr("Overload with bool")));
+ EXPECT_EQ(A.SnippetSuffix, "(${0})");
+}
+
TEST(CompletionTest, DocumentationFromChangedFileCrash) {
MockFSProvider FS;
auto FooH = testPath("foo.h");
@@ -1062,14 +1166,81 @@ TEST(CompletionTest, DocumentationFromChangedFileCrash) {
clangd::CodeCompleteOptions Opts;
Opts.IncludeComments = true;
- CompletionList Completions =
+ CodeCompleteResult Completions =
cantFail(runCodeComplete(Server, FooCpp, Source.point(), Opts));
// We shouldn't crash. Unfortunately, current workaround is to not produce
// comments for symbols from headers.
- EXPECT_THAT(Completions.items,
+ EXPECT_THAT(Completions.Completions,
Contains(AllOf(Not(IsDocumented()), Named("func"))));
}
+TEST(CompletionTest, NonDocComments) {
+ MockFSProvider FS;
+ auto FooCpp = testPath("foo.cpp");
+ FS.Files[FooCpp] = "";
+
+ MockCompilationDatabase CDB;
+ IgnoreDiagnostics DiagConsumer;
+ ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
+
+ Annotations Source(R"cpp(
+ // We ignore namespace comments, for rationale see CodeCompletionStrings.h.
+ namespace comments_ns {
+ }
+
+ // ------------------
+ int comments_foo();
+
+ // A comment and a decl are separated by newlines.
+ // Therefore, the comment shouldn't show up as doc comment.
+
+ int comments_bar();
+
+ // this comment should be in the results.
+ int comments_baz();
+
+
+ template <class T>
+ struct Struct {
+ int comments_qux();
+ int comments_quux();
+ };
+
+
+ // This comment should not be there.
+
+ template <class T>
+ int Struct<T>::comments_qux() {
+ }
+
+ // This comment **should** be in results.
+ template <class T>
+ int Struct<T>::comments_quux() {
+ int a = comments^;
+ }
+ )cpp");
+ // FIXME: Auto-completion in a template requires disabling delayed template
+ // parsing.
+ CDB.ExtraClangFlags.push_back("-fno-delayed-template-parsing");
+ Server.addDocument(FooCpp, Source.code(), WantDiagnostics::Yes);
+ CodeCompleteResult Completions = cantFail(runCodeComplete(
+ Server, FooCpp, Source.point(), clangd::CodeCompleteOptions()));
+
+ // We should not get any of those comments in completion.
+ EXPECT_THAT(
+ Completions.Completions,
+ UnorderedElementsAre(AllOf(Not(IsDocumented()), Named("comments_foo")),
+ AllOf(IsDocumented(), Named("comments_baz")),
+ AllOf(IsDocumented(), Named("comments_quux")),
+ AllOf(Not(IsDocumented()), Named("comments_ns")),
+ // FIXME(ibiryukov): the following items should have
+ // empty documentation, since they are separated from
+ // a comment with an empty line. Unfortunately, I
+ // couldn't make Sema tests pass if we ignore those.
+ AllOf(IsDocumented(), Named("comments_bar")),
+ AllOf(IsDocumented(), Named("comments_qux"))));
+}
+
TEST(CompletionTest, CompleteOnInvalidLine) {
auto FooCpp = testPath("foo.cpp");
@@ -1088,6 +1259,184 @@ TEST(CompletionTest, CompleteOnInvalidLine) {
Failed());
}
+TEST(CompletionTest, QualifiedNames) {
+ auto Results = completions(
+ R"cpp(
+ namespace ns { int local; void both(); }
+ void f() { ::ns::^ }
+ )cpp",
+ {func("ns::both"), cls("ns::Index")});
+ // We get results from both index and sema, with no duplicates.
+ EXPECT_THAT(
+ Results.Completions,
+ UnorderedElementsAre(Scope("ns::"), Scope("ns::"), Scope("ns::")));
+}
+
+TEST(CompletionTest, Render) {
+ CodeCompletion C;
+ C.Name = "x";
+ C.Signature = "(bool) const";
+ C.SnippetSuffix = "(${0:bool})";
+ C.ReturnType = "int";
+ C.RequiredQualifier = "Foo::";
+ C.Scope = "ns::Foo::";
+ C.Documentation = "This is x().";
+ C.Header = "\"foo.h\"";
+ C.Kind = CompletionItemKind::Method;
+ C.Score.Total = 1.0;
+ C.Origin = SymbolOrigin::AST | SymbolOrigin::Static;
+
+ CodeCompleteOptions Opts;
+ Opts.IncludeIndicator.Insert = "^";
+ Opts.IncludeIndicator.NoInsert = "";
+ Opts.EnableSnippets = false;
+
+ auto R = C.render(Opts);
+ EXPECT_EQ(R.label, "Foo::x(bool) const");
+ EXPECT_EQ(R.insertText, "Foo::x");
+ EXPECT_EQ(R.insertTextFormat, InsertTextFormat::PlainText);
+ EXPECT_EQ(R.filterText, "x");
+ EXPECT_EQ(R.detail, "int\n\"foo.h\"");
+ EXPECT_EQ(R.documentation, "This is x().");
+ EXPECT_THAT(R.additionalTextEdits, IsEmpty());
+ EXPECT_EQ(R.sortText, sortText(1.0, "x"));
+
+ Opts.EnableSnippets = true;
+ R = C.render(Opts);
+ EXPECT_EQ(R.insertText, "Foo::x(${0:bool})");
+ EXPECT_EQ(R.insertTextFormat, InsertTextFormat::Snippet);
+
+ C.HeaderInsertion.emplace();
+ R = C.render(Opts);
+ EXPECT_EQ(R.label, "^Foo::x(bool) const");
+ EXPECT_THAT(R.additionalTextEdits, Not(IsEmpty()));
+
+ Opts.ShowOrigins = true;
+ R = C.render(Opts);
+ EXPECT_EQ(R.label, "^[AS]Foo::x(bool) const");
+
+ C.BundleSize = 2;
+ R = C.render(Opts);
+ EXPECT_EQ(R.detail, "[2 overloads]\n\"foo.h\"");
+}
+
+TEST(CompletionTest, IgnoreRecoveryResults) {
+ auto Results = completions(
+ R"cpp(
+ namespace ns { int NotRecovered() { return 0; } }
+ void f() {
+ // Sema enters recovery mode first and then normal mode.
+ if (auto x = ns::NotRecover^)
+ }
+ )cpp");
+ EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("NotRecovered")));
+}
+
+TEST(CompletionTest, ScopeOfClassFieldInConstructorInitializer) {
+ auto Results = completions(
+ R"cpp(
+ namespace ns {
+ class X { public: X(); int x_; };
+ X::X() : x_^(0) {}
+ }
+ )cpp");
+ EXPECT_THAT(Results.Completions,
+ UnorderedElementsAre(AllOf(Scope("ns::X::"), Named("x_"))));
+}
+
+TEST(CompletionTest, CodeCompletionContext) {
+ auto Results = completions(
+ R"cpp(
+ namespace ns {
+ class X { public: X(); int x_; };
+ void f() {
+ X x;
+ x.^;
+ }
+ }
+ )cpp");
+
+ EXPECT_THAT(Results.Context, CodeCompletionContext::CCC_DotMemberAccess);
+}
+
+TEST(CompletionTest, FixItForArrowToDot) {
+ MockFSProvider FS;
+ MockCompilationDatabase CDB;
+ IgnoreDiagnostics DiagConsumer;
+ ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
+
+ CodeCompleteOptions Opts;
+ Opts.IncludeFixIts = true;
+ Annotations TestCode(
+ R"cpp(
+ class Auxilary {
+ public:
+ void AuxFunction();
+ };
+ class ClassWithPtr {
+ public:
+ void MemberFunction();
+ Auxilary* operator->() const;
+ Auxilary* Aux;
+ };
+ void f() {
+ ClassWithPtr x;
+ x[[->]]^;
+ }
+ )cpp");
+ auto Results =
+ completions(Server, TestCode.code(), TestCode.point(), {}, Opts);
+ EXPECT_EQ(Results.Completions.size(), 3u);
+
+ TextEdit ReplacementEdit;
+ ReplacementEdit.range = TestCode.range();
+ ReplacementEdit.newText = ".";
+ for (const auto &C : Results.Completions) {
+ EXPECT_TRUE(C.FixIts.size() == 1u || C.Name == "AuxFunction");
+ if (!C.FixIts.empty())
+ EXPECT_THAT(C.FixIts, ElementsAre(ReplacementEdit));
+ }
+}
+
+TEST(CompletionTest, FixItForDotToArrow) {
+ MockFSProvider FS;
+ MockCompilationDatabase CDB;
+ IgnoreDiagnostics DiagConsumer;
+ ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
+
+ CodeCompleteOptions Opts;
+ Opts.IncludeFixIts = true;
+ Annotations TestCode(
+ R"cpp(
+ class Auxilary {
+ public:
+ void AuxFunction();
+ };
+ class ClassWithPtr {
+ public:
+ void MemberFunction();
+ Auxilary* operator->() const;
+ Auxilary* Aux;
+ };
+ void f() {
+ ClassWithPtr x;
+ x[[.]]^;
+ }
+ )cpp");
+ auto Results =
+ completions(Server, TestCode.code(), TestCode.point(), {}, Opts);
+ EXPECT_EQ(Results.Completions.size(), 3u);
+
+ TextEdit ReplacementEdit;
+ ReplacementEdit.range = TestCode.range();
+ ReplacementEdit.newText = "->";
+ for (const auto &C : Results.Completions) {
+ EXPECT_TRUE(C.FixIts.empty() || C.Name == "AuxFunction");
+ if (!C.FixIts.empty()) {
+ EXPECT_THAT(C.FixIts, ElementsAre(ReplacementEdit));
+ }
+ }
+}
} // namespace
} // namespace clangd
diff --git a/unittests/clangd/CodeCompletionStringsTests.cpp b/unittests/clangd/CodeCompletionStringsTests.cpp
index 77126862..a6038a73 100644
--- a/unittests/clangd/CodeCompletionStringsTests.cpp
+++ b/unittests/clangd/CodeCompletionStringsTests.cpp
@@ -23,31 +23,23 @@ public:
CCTUInfo(Allocator), Builder(*Allocator, CCTUInfo) {}
protected:
- void labelAndInsertText(const CodeCompletionString &CCS,
- bool EnableSnippets = false) {
- Label.clear();
- InsertText.clear();
- getLabelAndInsertText(CCS, &Label, &InsertText, EnableSnippets);
+ void computeSignature(const CodeCompletionString &CCS) {
+ Signature.clear();
+ Snippet.clear();
+ getSignature(CCS, &Signature, &Snippet);
}
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
CodeCompletionTUInfo CCTUInfo;
CodeCompletionBuilder Builder;
- std::string Label;
- std::string InsertText;
+ std::string Signature;
+ std::string Snippet;
};
-TEST_F(CompletionStringTest, Detail) {
+TEST_F(CompletionStringTest, ReturnType) {
Builder.AddResultTypeChunk("result");
Builder.AddResultTypeChunk("redundant result no no");
- EXPECT_EQ(getDetail(*Builder.TakeString()), "result");
-}
-
-TEST_F(CompletionStringTest, FilterText) {
- Builder.AddTypedTextChunk("typed");
- Builder.AddTypedTextChunk("redundant typed no no");
- auto *S = Builder.TakeString();
- EXPECT_EQ(getFilterText(*S), "typed");
+ EXPECT_EQ(getReturnType(*Builder.TakeString()), "result");
}
TEST_F(CompletionStringTest, Documentation) {
@@ -72,31 +64,15 @@ TEST_F(CompletionStringTest, MultipleAnnotations) {
"Annotations: Ano1 Ano2 Ano3\n");
}
-TEST_F(CompletionStringTest, SimpleLabelAndInsert) {
+TEST_F(CompletionStringTest, EmptySignature) {
Builder.AddTypedTextChunk("X");
Builder.AddResultTypeChunk("result no no");
- labelAndInsertText(*Builder.TakeString());
- EXPECT_EQ(Label, "X");
- EXPECT_EQ(InsertText, "X");
+ computeSignature(*Builder.TakeString());
+ EXPECT_EQ(Signature, "");
+ EXPECT_EQ(Snippet, "");
}
-TEST_F(CompletionStringTest, FunctionPlainText) {
- Builder.AddResultTypeChunk("result no no");
- Builder.AddTypedTextChunk("Foo");
- Builder.AddChunk(CodeCompletionString::CK_LeftParen);
- Builder.AddPlaceholderChunk("p1");
- Builder.AddChunk(CodeCompletionString::CK_Comma);
- Builder.AddPlaceholderChunk("p2");
- Builder.AddChunk(CodeCompletionString::CK_RightParen);
- Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
- Builder.AddInformativeChunk("const");
-
- labelAndInsertText(*Builder.TakeString());
- EXPECT_EQ(Label, "Foo(p1, p2) const");
- EXPECT_EQ(InsertText, "Foo");
-}
-
-TEST_F(CompletionStringTest, FunctionSnippet) {
+TEST_F(CompletionStringTest, Function) {
Builder.AddResultTypeChunk("result no no");
Builder.addBriefComment("This comment is ignored");
Builder.AddTypedTextChunk("Foo");
@@ -107,15 +83,10 @@ TEST_F(CompletionStringTest, FunctionSnippet) {
Builder.AddChunk(CodeCompletionString::CK_RightParen);
auto *CCS = Builder.TakeString();
- labelAndInsertText(*CCS);
- EXPECT_EQ(Label, "Foo(p1, p2)");
- EXPECT_EQ(InsertText, "Foo");
-
- labelAndInsertText(*CCS, /*EnableSnippets=*/true);
- EXPECT_EQ(Label, "Foo(p1, p2)");
- EXPECT_EQ(InsertText, "Foo(${1:p1}, ${2:p2})");
+ computeSignature(*CCS);
+ EXPECT_EQ(Signature, "(p1, p2)");
+ EXPECT_EQ(Snippet, "(${1:p1}, ${2:p2})");
EXPECT_EQ(formatDocumentation(*CCS, "Foo's comment"), "Foo's comment");
- EXPECT_EQ(getFilterText(*CCS), "Foo");
}
TEST_F(CompletionStringTest, EscapeSnippet) {
@@ -124,18 +95,18 @@ TEST_F(CompletionStringTest, EscapeSnippet) {
Builder.AddPlaceholderChunk("$p}1\\");
Builder.AddChunk(CodeCompletionString::CK_RightParen);
- labelAndInsertText(*Builder.TakeString(), /*EnableSnippets=*/true);
- EXPECT_EQ(Label, "Foo($p}1\\)");
- EXPECT_EQ(InsertText, "Foo(${1:\\$p\\}1\\\\})");
+ computeSignature(*Builder.TakeString());
+ EXPECT_EQ(Signature, "($p}1\\)");
+ EXPECT_EQ(Snippet, "(${1:\\$p\\}1\\\\})");
}
TEST_F(CompletionStringTest, IgnoreInformativeQualifier) {
Builder.AddTypedTextChunk("X");
Builder.AddInformativeChunk("info ok");
Builder.AddInformativeChunk("info no no::");
- labelAndInsertText(*Builder.TakeString());
- EXPECT_EQ(Label, "Xinfo ok");
- EXPECT_EQ(InsertText, "X");
+ computeSignature(*Builder.TakeString());
+ EXPECT_EQ(Signature, "info ok");
+ EXPECT_EQ(Snippet, "");
}
} // namespace
diff --git a/unittests/clangd/DexIndexTests.cpp b/unittests/clangd/DexIndexTests.cpp
new file mode 100644
index 00000000..906d62ab
--- /dev/null
+++ b/unittests/clangd/DexIndexTests.cpp
@@ -0,0 +1,317 @@
+//===-- DexIndexTests.cpp ----------------------------*- C++ -*-----------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "index/dex/Iterator.h"
+#include "index/dex/Token.h"
+#include "index/dex/Trigram.h"
+#include "llvm/Support/ScopedPrinter.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace clangd {
+namespace dex {
+
+using ::testing::ElementsAre;
+
+TEST(DexIndexIterators, DocumentIterator) {
+ const PostingList L = {4, 7, 8, 20, 42, 100};
+ auto DocIterator = create(L);
+
+ EXPECT_EQ(DocIterator->peek(), 4U);
+ EXPECT_EQ(DocIterator->reachedEnd(), false);
+
+ DocIterator->advance();
+ EXPECT_EQ(DocIterator->peek(), 7U);
+ EXPECT_EQ(DocIterator->reachedEnd(), false);
+
+ DocIterator->advanceTo(20);
+ EXPECT_EQ(DocIterator->peek(), 20U);
+ EXPECT_EQ(DocIterator->reachedEnd(), false);
+
+ DocIterator->advanceTo(65);
+ EXPECT_EQ(DocIterator->peek(), 100U);
+ EXPECT_EQ(DocIterator->reachedEnd(), false);
+
+ DocIterator->advanceTo(420);
+ EXPECT_EQ(DocIterator->reachedEnd(), true);
+}
+
+TEST(DexIndexIterators, AndWithEmpty) {
+ const PostingList L0;
+ const PostingList L1 = {0, 5, 7, 10, 42, 320, 9000};
+
+ auto AndEmpty = createAnd(create(L0));
+ EXPECT_EQ(AndEmpty->reachedEnd(), true);
+
+ auto AndWithEmpty = createAnd(create(L0), create(L1));
+ EXPECT_EQ(AndWithEmpty->reachedEnd(), true);
+
+ EXPECT_THAT(consume(*AndWithEmpty), ElementsAre());
+}
+
+TEST(DexIndexIterators, AndTwoLists) {
+ const PostingList L0 = {0, 5, 7, 10, 42, 320, 9000};
+ const PostingList L1 = {0, 4, 7, 10, 30, 60, 320, 9000};
+
+ auto And = createAnd(create(L1), create(L0));
+
+ EXPECT_EQ(And->reachedEnd(), false);
+ EXPECT_THAT(consume(*And), ElementsAre(0U, 7U, 10U, 320U, 9000U));
+
+ And = createAnd(create(L0), create(L1));
+
+ And->advanceTo(0);
+ EXPECT_EQ(And->peek(), 0U);
+ And->advanceTo(5);
+ EXPECT_EQ(And->peek(), 7U);
+ And->advanceTo(10);
+ EXPECT_EQ(And->peek(), 10U);
+ And->advanceTo(42);
+ EXPECT_EQ(And->peek(), 320U);
+ And->advanceTo(8999);
+ EXPECT_EQ(And->peek(), 9000U);
+ And->advanceTo(9001);
+}
+
+TEST(DexIndexIterators, AndThreeLists) {
+ const PostingList L0 = {0, 5, 7, 10, 42, 320, 9000};
+ const PostingList L1 = {0, 4, 7, 10, 30, 60, 320, 9000};
+ const PostingList L2 = {1, 4, 7, 11, 30, 60, 320, 9000};
+
+ auto And = createAnd(create(L0), create(L1), create(L2));
+ EXPECT_EQ(And->peek(), 7U);
+ And->advanceTo(300);
+ EXPECT_EQ(And->peek(), 320U);
+ And->advanceTo(100000);
+
+ EXPECT_EQ(And->reachedEnd(), true);
+}
+
+TEST(DexIndexIterators, OrWithEmpty) {
+ const PostingList L0;
+ const PostingList L1 = {0, 5, 7, 10, 42, 320, 9000};
+
+ auto OrEmpty = createOr(create(L0));
+ EXPECT_EQ(OrEmpty->reachedEnd(), true);
+
+ auto OrWithEmpty = createOr(create(L0), create(L1));
+ EXPECT_EQ(OrWithEmpty->reachedEnd(), false);
+
+ EXPECT_THAT(consume(*OrWithEmpty),
+ ElementsAre(0U, 5U, 7U, 10U, 42U, 320U, 9000U));
+}
+
+TEST(DexIndexIterators, OrTwoLists) {
+ const PostingList L0 = {0, 5, 7, 10, 42, 320, 9000};
+ const PostingList L1 = {0, 4, 7, 10, 30, 60, 320, 9000};
+
+ auto Or = createOr(create(L0), create(L1));
+
+ EXPECT_EQ(Or->reachedEnd(), false);
+ EXPECT_EQ(Or->peek(), 0U);
+ Or->advance();
+ EXPECT_EQ(Or->peek(), 4U);
+ Or->advance();
+ EXPECT_EQ(Or->peek(), 5U);
+ Or->advance();
+ EXPECT_EQ(Or->peek(), 7U);
+ Or->advance();
+ EXPECT_EQ(Or->peek(), 10U);
+ Or->advance();
+ EXPECT_EQ(Or->peek(), 30U);
+ Or->advanceTo(42);
+ EXPECT_EQ(Or->peek(), 42U);
+ Or->advanceTo(300);
+ EXPECT_EQ(Or->peek(), 320U);
+ Or->advanceTo(9000);
+ EXPECT_EQ(Or->peek(), 9000U);
+ Or->advanceTo(9001);
+ EXPECT_EQ(Or->reachedEnd(), true);
+
+ Or = createOr(create(L0), create(L1));
+
+ EXPECT_THAT(consume(*Or),
+ ElementsAre(0U, 4U, 5U, 7U, 10U, 30U, 42U, 60U, 320U, 9000U));
+}
+
+TEST(DexIndexIterators, OrThreeLists) {
+ const PostingList L0 = {0, 5, 7, 10, 42, 320, 9000};
+ const PostingList L1 = {0, 4, 7, 10, 30, 60, 320, 9000};
+ const PostingList L2 = {1, 4, 7, 11, 30, 60, 320, 9000};
+
+ auto Or = createOr(create(L0), create(L1), create(L2));
+
+ EXPECT_EQ(Or->reachedEnd(), false);
+ EXPECT_EQ(Or->peek(), 0U);
+
+ Or->advance();
+ EXPECT_EQ(Or->peek(), 1U);
+
+ Or->advance();
+ EXPECT_EQ(Or->peek(), 4U);
+
+ Or->advanceTo(7);
+
+ Or->advanceTo(59);
+ EXPECT_EQ(Or->peek(), 60U);
+
+ Or->advanceTo(9001);
+ EXPECT_EQ(Or->reachedEnd(), true);
+}
+
+// FIXME(kbobyrev): The testcase below is similar to what is expected in real
+// queries. It should be updated once new iterators (such as boosting, limiting,
+// etc iterators) appear. However, it is not exhaustive and it would be
+// beneficial to implement automatic generation of query trees for more
+// comprehensive testing.
+TEST(DexIndexIterators, QueryTree) {
+ // An example of more complicated query
+ //
+ // +-----------------+
+ // |And Iterator:1, 5|
+ // +--------+--------+
+ // |
+ // |
+ // +------------------------------------+
+ // | |
+ // | |
+ // +----------v----------+ +----------v---------+
+ // |And Iterator: 1, 5, 9| |Or Iterator: 0, 1, 5|
+ // +----------+----------+ +----------+---------+
+ // | |
+ // +------+-----+ +---------+-----------+
+ // | | | | |
+ // +-------v-----+ +----v-----+ +--v--+ +-V--+ +---v---+
+ // |1, 3, 5, 8, 9| |1, 5, 7, 9| |Empty| |0, 5| |0, 1, 5|
+ // +-------------+ +----------+ +-----+ +----+ +-------+
+
+ const PostingList L0 = {1, 3, 5, 8, 9};
+ const PostingList L1 = {1, 5, 7, 9};
+ const PostingList L2 = {0, 5};
+ const PostingList L3 = {0, 1, 5};
+ const PostingList L4;
+
+ // Root of the query tree: [1, 5]
+ auto Root = createAnd(
+ // Lower And Iterator: [1, 5, 9]
+ createAnd(create(L0), create(L1)),
+ // Lower Or Iterator: [0, 1, 5]
+ createOr(create(L2), create(L3), create(L4)));
+
+ EXPECT_EQ(Root->reachedEnd(), false);
+ EXPECT_EQ(Root->peek(), 1U);
+ Root->advanceTo(0);
+ // Advance multiple times. Shouldn't do anything.
+ Root->advanceTo(1);
+ Root->advanceTo(0);
+ EXPECT_EQ(Root->peek(), 1U);
+ Root->advance();
+ EXPECT_EQ(Root->peek(), 5U);
+ Root->advanceTo(5);
+ EXPECT_EQ(Root->peek(), 5U);
+ Root->advanceTo(9000);
+ EXPECT_EQ(Root->reachedEnd(), true);
+}
+
+TEST(DexIndexIterators, StringRepresentation) {
+ const PostingList L0 = {4, 7, 8, 20, 42, 100};
+ const PostingList L1 = {1, 3, 5, 8, 9};
+ const PostingList L2 = {1, 5, 7, 9};
+ const PostingList L3 = {0, 5};
+ const PostingList L4 = {0, 1, 5};
+ const PostingList L5;
+
+ EXPECT_EQ(llvm::to_string(*(create(L0))), "[4, 7, 8, 20, 42, 100]");
+
+ auto Nested = createAnd(createAnd(create(L1), create(L2)),
+ createOr(create(L3), create(L4), create(L5)));
+
+ EXPECT_EQ(llvm::to_string(*Nested),
+ "(& (& [1, 3, 5, 8, 9] [1, 5, 7, 9]) (| [0, 5] [0, 1, 5] []))");
+}
+
+testing::Matcher<std::vector<Token>>
+trigramsAre(std::initializer_list<std::string> Trigrams) {
+ std::vector<Token> Tokens;
+ for (const auto &Symbols : Trigrams) {
+ Tokens.push_back(Token(Token::Kind::Trigram, Symbols));
+ }
+ return testing::UnorderedElementsAreArray(Tokens);
+}
+
+TEST(DexIndexTrigrams, IdentifierTrigrams) {
+ EXPECT_THAT(generateIdentifierTrigrams("X86"), trigramsAre({"x86"}));
+
+ EXPECT_THAT(generateIdentifierTrigrams("nl"), trigramsAre({}));
+
+ EXPECT_THAT(generateIdentifierTrigrams("clangd"),
+ trigramsAre({"cla", "lan", "ang", "ngd"}));
+
+ EXPECT_THAT(generateIdentifierTrigrams("abc_def"),
+ trigramsAre({"abc", "abd", "ade", "bcd", "bde", "cde", "def"}));
+
+ EXPECT_THAT(
+ generateIdentifierTrigrams("a_b_c_d_e_"),
+ trigramsAre({"abc", "abd", "acd", "ace", "bcd", "bce", "bde", "cde"}));
+
+ EXPECT_THAT(
+ generateIdentifierTrigrams("unique_ptr"),
+ trigramsAre({"uni", "unp", "upt", "niq", "nip", "npt", "iqu", "iqp",
+ "ipt", "que", "qup", "qpt", "uep", "ept", "ptr"}));
+
+ EXPECT_THAT(generateIdentifierTrigrams("TUDecl"),
+ trigramsAre({"tud", "tde", "ude", "dec", "ecl"}));
+
+ EXPECT_THAT(generateIdentifierTrigrams("IsOK"),
+ trigramsAre({"iso", "iok", "sok"}));
+
+ EXPECT_THAT(generateIdentifierTrigrams("abc_defGhij__klm"),
+ trigramsAre({
+ "abc", "abd", "abg", "ade", "adg", "adk", "agh", "agk", "bcd",
+ "bcg", "bde", "bdg", "bdk", "bgh", "bgk", "cde", "cdg", "cdk",
+ "cgh", "cgk", "def", "deg", "dek", "dgh", "dgk", "dkl", "efg",
+ "efk", "egh", "egk", "ekl", "fgh", "fgk", "fkl", "ghi", "ghk",
+ "gkl", "hij", "hik", "hkl", "ijk", "ikl", "jkl", "klm",
+ }));
+}
+
+TEST(DexIndexTrigrams, QueryTrigrams) {
+ EXPECT_THAT(generateQueryTrigrams("X86"), trigramsAre({"x86"}));
+
+ EXPECT_THAT(generateQueryTrigrams("nl"), trigramsAre({}));
+
+ EXPECT_THAT(generateQueryTrigrams("clangd"),
+ trigramsAre({"cla", "lan", "ang", "ngd"}));
+
+ EXPECT_THAT(generateQueryTrigrams("abc_def"),
+ trigramsAre({"abc", "bcd", "cde", "def"}));
+
+ EXPECT_THAT(generateQueryTrigrams("a_b_c_d_e_"),
+ trigramsAre({"abc", "bcd", "cde"}));
+
+ EXPECT_THAT(generateQueryTrigrams("unique_ptr"),
+ trigramsAre({"uni", "niq", "iqu", "que", "uep", "ept", "ptr"}));
+
+ EXPECT_THAT(generateQueryTrigrams("TUDecl"),
+ trigramsAre({"tud", "ude", "dec", "ecl"}));
+
+ EXPECT_THAT(generateQueryTrigrams("IsOK"), trigramsAre({"iso", "sok"}));
+
+ EXPECT_THAT(generateQueryTrigrams("abc_defGhij__klm"),
+ trigramsAre({"abc", "bcd", "cde", "def", "efg", "fgh", "ghi",
+ "hij", "ijk", "jkl", "klm"}));
+}
+
+} // namespace dex
+} // namespace clangd
+} // namespace clang
diff --git a/unittests/clangd/FileDistanceTests.cpp b/unittests/clangd/FileDistanceTests.cpp
new file mode 100644
index 00000000..0d5afc9a
--- /dev/null
+++ b/unittests/clangd/FileDistanceTests.cpp
@@ -0,0 +1,99 @@
+//===-- FileDistanceTests.cpp ------------------------*- C++ -*-----------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "FileDistance.h"
+#include "TestFS.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+TEST(FileDistanceTests, Distance) {
+ FileDistanceOptions Opts;
+ Opts.UpCost = 5;
+ Opts.DownCost = 3;
+ SourceParams CostTwo;
+ CostTwo.Cost = 2;
+ FileDistance D(
+ {{"tools/clang/lib/Format/FormatToken.cpp", SourceParams()},
+ {"tools/clang/include/clang/Format/FormatToken.h", SourceParams()},
+ {"include/llvm/ADT/StringRef.h", CostTwo}},
+ Opts);
+
+ // Source
+ EXPECT_EQ(D.distance("tools/clang/lib/Format/FormatToken.cpp"), 0u);
+ EXPECT_EQ(D.distance("include/llvm/ADT/StringRef.h"), 2u);
+ // Parent
+ EXPECT_EQ(D.distance("tools/clang/lib/Format/"), 5u);
+ // Child
+ EXPECT_EQ(D.distance("tools/clang/lib/Format/FormatToken.cpp/Oops"), 3u);
+ // Ancestor (up+up+up+up)
+ EXPECT_EQ(D.distance("/"), 22u);
+ // Sibling (up+down)
+ EXPECT_EQ(D.distance("tools/clang/lib/Format/AnotherFile.cpp"), 8u);
+ // Cousin (up+up+down+down)
+ EXPECT_EQ(D.distance("include/llvm/Support/Allocator.h"), 18u);
+ // First cousin, once removed (up+up+up+down+down)
+ EXPECT_EQ(D.distance("include/llvm-c/Core.h"), 23u);
+}
+
+TEST(FileDistanceTests, BadSource) {
+ // We mustn't assume that paths above sources are best reached via them.
+ FileDistanceOptions Opts;
+ Opts.UpCost = 5;
+ Opts.DownCost = 3;
+ SourceParams CostLots;
+ CostLots.Cost = 100;
+ FileDistance D({{"a", SourceParams()}, {"b/b/b", CostLots}}, Opts);
+ EXPECT_EQ(D.distance("b"), 8u); // a+up+down, not b+up+up
+ EXPECT_EQ(D.distance("b/b/b"), 14u); // a+up+down+down+down, not b
+ EXPECT_EQ(D.distance("b/b/b/c"), 17u); // a+up+down+down+down+down, not b+down
+}
+
+auto UseUnittestScheme = UnittestSchemeAnchorSource;
+
+TEST(FileDistanceTests, URI) {
+ FileDistanceOptions Opts;
+ Opts.UpCost = 5;
+ Opts.DownCost = 3;
+ SourceParams CostLots;
+ CostLots.Cost = 1000;
+
+ URIDistance D({{testPath("foo"), CostLots},
+ {"/not/a/testpath", SourceParams()},
+ {"C:\\not\\a\\testpath", SourceParams()}},
+ Opts);
+#ifdef _WIN32
+ EXPECT_EQ(D.distance("file:///C%3a/not/a/testpath/either"), 3u);
+#else
+ EXPECT_EQ(D.distance("file:///not/a/testpath/either"), 3u);
+#endif
+ EXPECT_EQ(D.distance("unittest:///foo"), 1000u);
+ EXPECT_EQ(D.distance("unittest:///bar"), 1008u);
+}
+
+TEST(FileDistance, LimitUpTraversals) {
+ FileDistanceOptions Opts;
+ Opts.UpCost = Opts.DownCost = 1;
+ SourceParams CheapButLimited, CostLots;
+ CheapButLimited.MaxUpTraversals = 1;
+ CostLots.Cost = 100;
+
+ FileDistance D({{"/", CostLots}, {"/a/b/c", CheapButLimited}}, Opts);
+ EXPECT_EQ(D.distance("/a"), 101u);
+ EXPECT_EQ(D.distance("/a/z"), 102u);
+ EXPECT_EQ(D.distance("/a/b"), 1u);
+ EXPECT_EQ(D.distance("/a/b/z"), 2u);
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
diff --git a/unittests/clangd/FileIndexTests.cpp b/unittests/clangd/FileIndexTests.cpp
index 7cb6a97a..08e85689 100644
--- a/unittests/clangd/FileIndexTests.cpp
+++ b/unittests/clangd/FileIndexTests.cpp
@@ -96,6 +96,20 @@ void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) {
M.update(File.Filename, &AST.getASTContext(), AST.getPreprocessorPtr());
}
+TEST(FileIndexTest, CustomizedURIScheme) {
+ FileIndex M({"unittest"});
+ update(M, "f", "class string {};");
+
+ FuzzyFindRequest Req;
+ Req.Query = "";
+ bool SeenSymbol = false;
+ M.fuzzyFind(Req, [&](const Symbol &Sym) {
+ EXPECT_EQ(Sym.CanonicalDeclaration.FileURI, "unittest:///f.h");
+ SeenSymbol = true;
+ });
+ EXPECT_TRUE(SeenSymbol);
+}
+
TEST(FileIndexTest, IndexAST) {
FileIndex M;
update(M, "f1", "namespace ns { void f() {} class X {}; }");
@@ -145,13 +159,14 @@ TEST(FileIndexTest, RemoveNonExisting) {
EXPECT_THAT(match(M, FuzzyFindRequest()), UnorderedElementsAre());
}
-TEST(FileIndexTest, IgnoreClassMembers) {
+TEST(FileIndexTest, ClassMembers) {
FileIndex M;
update(M, "f1", "class X { static int m1; int m2; static void f(); };");
FuzzyFindRequest Req;
Req.Query = "";
- EXPECT_THAT(match(M, Req), UnorderedElementsAre("X"));
+ EXPECT_THAT(match(M, Req),
+ UnorderedElementsAre("X", "X::m1", "X::m2", "X::f"));
}
TEST(FileIndexTest, NoIncludeCollected) {
@@ -187,18 +202,15 @@ vector<Ty> make_vector(Arg A) {}
bool SeenMakeVector = false;
M.fuzzyFind(Req, [&](const Symbol &Sym) {
if (Sym.Name == "vector") {
- EXPECT_EQ(Sym.CompletionLabel, "vector<class Ty>");
- EXPECT_EQ(Sym.CompletionSnippetInsertText, "vector<${1:class Ty}>");
- EXPECT_EQ(Sym.CompletionPlainInsertText, "vector");
+ EXPECT_EQ(Sym.Signature, "<class Ty>");
+ EXPECT_EQ(Sym.CompletionSnippetSuffix, "<${1:class Ty}>");
SeenVector = true;
return;
}
if (Sym.Name == "make_vector") {
- EXPECT_EQ(Sym.CompletionLabel, "make_vector<class Ty>(Arg A)");
- EXPECT_EQ(Sym.CompletionSnippetInsertText,
- "make_vector<${1:class Ty}>(${2:Arg A})");
- EXPECT_EQ(Sym.CompletionPlainInsertText, "make_vector");
+ EXPECT_EQ(Sym.Signature, "<class Ty>(Arg A)");
+ EXPECT_EQ(Sym.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})");
SeenMakeVector = true;
}
});
diff --git a/unittests/clangd/FindSymbolsTests.cpp b/unittests/clangd/FindSymbolsTests.cpp
index 6092b2d1..068660d7 100644
--- a/unittests/clangd/FindSymbolsTests.cpp
+++ b/unittests/clangd/FindSymbolsTests.cpp
@@ -22,6 +22,7 @@ namespace {
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::ElementsAre;
+using ::testing::ElementsAreArray;
using ::testing::IsEmpty;
using ::testing::UnorderedElementsAre;
@@ -31,22 +32,29 @@ class IgnoreDiagnostics : public DiagnosticsConsumer {
};
// GMock helpers for matching SymbolInfos items.
-MATCHER_P(Named, Name, "") { return arg.name == Name; }
-MATCHER_P(InContainer, ContainerName, "") {
- return arg.containerName == ContainerName;
+MATCHER_P(QName, Name, "") {
+ if (arg.containerName.empty())
+ return arg.name == Name;
+ return (arg.containerName + "::" + arg.name) == Name;
}
MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; }
+MATCHER_P(SymRange, Range, "") { return arg.location.range == Range; }
ClangdServer::Options optsForTests() {
auto ServerOpts = ClangdServer::optsForTest();
ServerOpts.BuildDynamicSymbolIndex = true;
+ ServerOpts.URISchemes = {"unittest", "file"};
return ServerOpts;
}
class WorkspaceSymbolsTest : public ::testing::Test {
public:
WorkspaceSymbolsTest()
- : Server(CDB, FSProvider, DiagConsumer, optsForTests()) {}
+ : Server(CDB, FSProvider, DiagConsumer, optsForTests()) {
+ // Make sure the test root directory is created.
+ FSProvider.Files[testPath("unused")] = "";
+ Server.setRootPath(testRoot());
+ }
protected:
MockFSProvider FSProvider;
@@ -101,12 +109,10 @@ TEST_F(WorkspaceSymbolsTest, Globals) {
#include "foo.h"
)cpp");
EXPECT_THAT(getSymbols("global"),
- UnorderedElementsAre(AllOf(Named("GlobalStruct"), InContainer(""),
- WithKind(SymbolKind::Struct)),
- AllOf(Named("global_func"), InContainer(""),
- WithKind(SymbolKind::Function)),
- AllOf(Named("global_var"), InContainer(""),
- WithKind(SymbolKind::Variable))));
+ UnorderedElementsAre(
+ AllOf(QName("GlobalStruct"), WithKind(SymbolKind::Struct)),
+ AllOf(QName("global_func"), WithKind(SymbolKind::Function)),
+ AllOf(QName("global_var"), WithKind(SymbolKind::Variable))));
}
TEST_F(WorkspaceSymbolsTest, Unnamed) {
@@ -118,9 +124,11 @@ TEST_F(WorkspaceSymbolsTest, Unnamed) {
#include "foo.h"
)cpp");
EXPECT_THAT(getSymbols("UnnamedStruct"),
- ElementsAre(AllOf(Named("UnnamedStruct"),
+ ElementsAre(AllOf(QName("UnnamedStruct"),
WithKind(SymbolKind::Variable))));
- EXPECT_THAT(getSymbols("InUnnamed"), IsEmpty());
+ EXPECT_THAT(getSymbols("InUnnamed"),
+ ElementsAre(AllOf(QName("(anonymous struct)::InUnnamed"),
+ WithKind(SymbolKind::Field))));
}
TEST_F(WorkspaceSymbolsTest, InMainFile) {
@@ -143,28 +151,20 @@ TEST_F(WorkspaceSymbolsTest, Namespaces) {
addFile("foo.cpp", R"cpp(
#include "foo.h"
)cpp");
- EXPECT_THAT(
- getSymbols("a"),
- UnorderedElementsAre(AllOf(Named("ans1"), InContainer("")),
- AllOf(Named("ai1"), InContainer("ans1")),
- AllOf(Named("ans2"), InContainer("ans1")),
- AllOf(Named("ai2"), InContainer("ans1::ans2"))));
- EXPECT_THAT(getSymbols("::"),
- ElementsAre(AllOf(Named("ans1"), InContainer(""))));
- EXPECT_THAT(getSymbols("::a"),
- ElementsAre(AllOf(Named("ans1"), InContainer(""))));
+ EXPECT_THAT(getSymbols("a"),
+ UnorderedElementsAre(QName("ans1"), QName("ans1::ai1"),
+ QName("ans1::ans2"),
+ QName("ans1::ans2::ai2")));
+ EXPECT_THAT(getSymbols("::"), ElementsAre(QName("ans1")));
+ EXPECT_THAT(getSymbols("::a"), ElementsAre(QName("ans1")));
EXPECT_THAT(getSymbols("ans1::"),
- UnorderedElementsAre(AllOf(Named("ai1"), InContainer("ans1")),
- AllOf(Named("ans2"), InContainer("ans1"))));
- EXPECT_THAT(getSymbols("::ans1"),
- ElementsAre(AllOf(Named("ans1"), InContainer(""))));
+ UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2")));
+ EXPECT_THAT(getSymbols("::ans1"), ElementsAre(QName("ans1")));
EXPECT_THAT(getSymbols("::ans1::"),
- UnorderedElementsAre(AllOf(Named("ai1"), InContainer("ans1")),
- AllOf(Named("ans2"), InContainer("ans1"))));
- EXPECT_THAT(getSymbols("::ans1::ans2"),
- ElementsAre(AllOf(Named("ans2"), InContainer("ans1"))));
+ UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2")));
+ EXPECT_THAT(getSymbols("::ans1::ans2"), ElementsAre(QName("ans1::ans2")));
EXPECT_THAT(getSymbols("::ans1::ans2::"),
- ElementsAre(AllOf(Named("ai2"), InContainer("ans1::ans2"))));
+ ElementsAre(QName("ans1::ans2::ai2")));
}
TEST_F(WorkspaceSymbolsTest, AnonymousNamespace) {
@@ -193,8 +193,7 @@ TEST_F(WorkspaceSymbolsTest, MultiFile) {
#include "foo2.h"
)cpp");
EXPECT_THAT(getSymbols("foo"),
- UnorderedElementsAre(AllOf(Named("foo"), InContainer("")),
- AllOf(Named("foo2"), InContainer(""))));
+ UnorderedElementsAre(QName("foo"), QName("foo2")));
}
TEST_F(WorkspaceSymbolsTest, GlobalNamespaceQueries) {
@@ -212,17 +211,66 @@ TEST_F(WorkspaceSymbolsTest, GlobalNamespaceQueries) {
addFile("foo.cpp", R"cpp(
#include "foo.h"
)cpp");
- EXPECT_THAT(
- getSymbols("::"),
- UnorderedElementsAre(
- AllOf(Named("Foo"), InContainer(""), WithKind(SymbolKind::Class)),
- AllOf(Named("foo"), InContainer(""), WithKind(SymbolKind::Function)),
- AllOf(Named("ns"), InContainer(""),
- WithKind(SymbolKind::Namespace))));
+ EXPECT_THAT(getSymbols("::"),
+ UnorderedElementsAre(
+ AllOf(QName("Foo"), WithKind(SymbolKind::Class)),
+ AllOf(QName("foo"), WithKind(SymbolKind::Function)),
+ AllOf(QName("ns"), WithKind(SymbolKind::Namespace))));
EXPECT_THAT(getSymbols(":"), IsEmpty());
EXPECT_THAT(getSymbols(""), IsEmpty());
}
+TEST_F(WorkspaceSymbolsTest, Enums) {
+ addFile("foo.h", R"cpp(
+ enum {
+ Red
+ };
+ enum Color {
+ Green
+ };
+ enum class Color2 {
+ Yellow
+ };
+ namespace ns {
+ enum {
+ Black
+ };
+ enum Color3 {
+ Blue
+ };
+ enum class Color4 {
+ White
+ };
+ }
+ )cpp");
+ addFile("foo.cpp", R"cpp(
+ #include "foo.h"
+ )cpp");
+ EXPECT_THAT(getSymbols("Red"), ElementsAre(QName("Red")));
+ EXPECT_THAT(getSymbols("::Red"), ElementsAre(QName("Red")));
+ EXPECT_THAT(getSymbols("Green"), ElementsAre(QName("Green")));
+ EXPECT_THAT(getSymbols("Green"), ElementsAre(QName("Green")));
+ EXPECT_THAT(getSymbols("Color2::Yellow"),
+ ElementsAre(QName("Color2::Yellow")));
+ EXPECT_THAT(getSymbols("Yellow"), ElementsAre(QName("Color2::Yellow")));
+
+ EXPECT_THAT(getSymbols("ns::Black"), ElementsAre(QName("ns::Black")));
+ EXPECT_THAT(getSymbols("ns::Blue"), ElementsAre(QName("ns::Blue")));
+ EXPECT_THAT(getSymbols("ns::Color4::White"),
+ ElementsAre(QName("ns::Color4::White")));
+}
+
+TEST_F(WorkspaceSymbolsTest, Ranking) {
+ addFile("foo.h", R"cpp(
+ namespace ns{}
+ function func();
+ )cpp");
+ addFile("foo.cpp", R"cpp(
+ #include "foo.h"
+ )cpp");
+ EXPECT_THAT(getSymbols("::"), ElementsAre(QName("func"), QName("ns")));
+}
+
TEST_F(WorkspaceSymbolsTest, WithLimit) {
addFile("foo.h", R"cpp(
int foo;
@@ -231,16 +279,283 @@ TEST_F(WorkspaceSymbolsTest, WithLimit) {
addFile("foo.cpp", R"cpp(
#include "foo.h"
)cpp");
+ // Foo is higher ranked because of exact name match.
EXPECT_THAT(getSymbols("foo"),
- ElementsAre(AllOf(Named("foo"), InContainer(""),
- WithKind(SymbolKind::Variable)),
- AllOf(Named("foo2"), InContainer(""),
- WithKind(SymbolKind::Variable))));
+ UnorderedElementsAre(
+ AllOf(QName("foo"), WithKind(SymbolKind::Variable)),
+ AllOf(QName("foo2"), WithKind(SymbolKind::Variable))));
Limit = 1;
- EXPECT_THAT(getSymbols("foo"),
- ElementsAre(AnyOf((Named("foo"), InContainer("")),
- AllOf(Named("foo2"), InContainer("")))));
+ EXPECT_THAT(getSymbols("foo"), ElementsAre(QName("foo")));
+}
+
+namespace {
+class DocumentSymbolsTest : public ::testing::Test {
+public:
+ DocumentSymbolsTest()
+ : Server(CDB, FSProvider, DiagConsumer, optsForTests()) {}
+
+protected:
+ MockFSProvider FSProvider;
+ MockCompilationDatabase CDB;
+ IgnoreDiagnostics DiagConsumer;
+ ClangdServer Server;
+
+ std::vector<SymbolInformation> getSymbols(PathRef File) {
+ EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble";
+ auto SymbolInfos = runDocumentSymbols(Server, File);
+ EXPECT_TRUE(bool(SymbolInfos)) << "documentSymbols returned an error";
+ return *SymbolInfos;
+ }
+
+ void addFile(StringRef FilePath, StringRef Contents) {
+ FSProvider.Files[FilePath] = Contents;
+ Server.addDocument(FilePath, Contents);
+ }
+};
+} // namespace
+
+TEST_F(DocumentSymbolsTest, BasicSymbols) {
+ std::string FilePath = testPath("foo.cpp");
+ Annotations Main(R"(
+ class Foo;
+ class Foo {
+ Foo() {}
+ Foo(int a) {}
+ void $decl[[f]]();
+ friend void f1();
+ friend class Friend;
+ Foo& operator=(const Foo&);
+ ~Foo();
+ class Nested {
+ void f();
+ };
+ };
+ class Friend {
+ };
+
+ void f1();
+ inline void f2() {}
+ static const int KInt = 2;
+ const char* kStr = "123";
+
+ void f1() {}
+
+ namespace foo {
+ // Type alias
+ typedef int int32;
+ using int32_t = int32;
+
+ // Variable
+ int v1;
+
+ // Namespace
+ namespace bar {
+ int v2;
+ }
+ // Namespace alias
+ namespace baz = bar;
+
+ // FIXME: using declaration is not supported as the IndexAction will ignore
+ // implicit declarations (the implicit using shadow declaration) by default,
+ // and there is no way to customize this behavior at the moment.
+ using bar::v2;
+ } // namespace foo
+ )");
+
+ addFile(FilePath, Main.code());
+ EXPECT_THAT(getSymbols(FilePath),
+ ElementsAreArray(
+ {AllOf(QName("Foo"), WithKind(SymbolKind::Class)),
+ AllOf(QName("Foo"), WithKind(SymbolKind::Class)),
+ AllOf(QName("Foo::Foo"), WithKind(SymbolKind::Method)),
+ AllOf(QName("Foo::Foo"), WithKind(SymbolKind::Method)),
+ AllOf(QName("Foo::f"), WithKind(SymbolKind::Method)),
+ AllOf(QName("f1"), WithKind(SymbolKind::Function)),
+ AllOf(QName("Foo::operator="), WithKind(SymbolKind::Method)),
+ AllOf(QName("Foo::~Foo"), WithKind(SymbolKind::Method)),
+ AllOf(QName("Foo::Nested"), WithKind(SymbolKind::Class)),
+ AllOf(QName("Foo::Nested::f"), WithKind(SymbolKind::Method)),
+ AllOf(QName("Friend"), WithKind(SymbolKind::Class)),
+ AllOf(QName("f1"), WithKind(SymbolKind::Function)),
+ AllOf(QName("f2"), WithKind(SymbolKind::Function)),
+ AllOf(QName("KInt"), WithKind(SymbolKind::Variable)),
+ AllOf(QName("kStr"), WithKind(SymbolKind::Variable)),
+ AllOf(QName("f1"), WithKind(SymbolKind::Function)),
+ AllOf(QName("foo"), WithKind(SymbolKind::Namespace)),
+ AllOf(QName("foo::int32"), WithKind(SymbolKind::Class)),
+ AllOf(QName("foo::int32_t"), WithKind(SymbolKind::Class)),
+ AllOf(QName("foo::v1"), WithKind(SymbolKind::Variable)),
+ AllOf(QName("foo::bar"), WithKind(SymbolKind::Namespace)),
+ AllOf(QName("foo::bar::v2"), WithKind(SymbolKind::Variable)),
+ AllOf(QName("foo::baz"), WithKind(SymbolKind::Namespace))}));
+}
+
+TEST_F(DocumentSymbolsTest, DeclarationDefinition) {
+ std::string FilePath = testPath("foo.cpp");
+ Annotations Main(R"(
+ class Foo {
+ void $decl[[f]]();
+ };
+ void Foo::$def[[f]]() {
+ }
+ )");
+
+ addFile(FilePath, Main.code());
+ EXPECT_THAT(getSymbols(FilePath),
+ ElementsAre(AllOf(QName("Foo"), WithKind(SymbolKind::Class)),
+ AllOf(QName("Foo::f"), WithKind(SymbolKind::Method),
+ SymRange(Main.range("decl"))),
+ AllOf(QName("Foo::f"), WithKind(SymbolKind::Method),
+ SymRange(Main.range("def")))));
+}
+
+TEST_F(DocumentSymbolsTest, ExternSymbol) {
+ std::string FilePath = testPath("foo.cpp");
+ addFile(testPath("foo.h"), R"cpp(
+ extern int var = 2;
+ )cpp");
+ addFile(FilePath, R"cpp(
+ #include "foo.h"
+ )cpp");
+
+ EXPECT_THAT(getSymbols(FilePath), IsEmpty());
+}
+
+TEST_F(DocumentSymbolsTest, NoLocals) {
+ std::string FilePath = testPath("foo.cpp");
+ addFile(FilePath,
+ R"cpp(
+ void test(int FirstParam, int SecondParam) {
+ struct LocalClass {};
+ int local_var;
+ })cpp");
+ EXPECT_THAT(getSymbols(FilePath), ElementsAre(QName("test")));
+}
+
+TEST_F(DocumentSymbolsTest, Unnamed) {
+ std::string FilePath = testPath("foo.h");
+ addFile(FilePath,
+ R"cpp(
+ struct {
+ int InUnnamed;
+ } UnnamedStruct;
+ )cpp");
+ EXPECT_THAT(
+ getSymbols(FilePath),
+ ElementsAre(AllOf(QName("UnnamedStruct"), WithKind(SymbolKind::Variable)),
+ AllOf(QName("(anonymous struct)::InUnnamed"),
+ WithKind(SymbolKind::Field))));
+}
+
+TEST_F(DocumentSymbolsTest, InHeaderFile) {
+ addFile("bar.h", R"cpp(
+ int foo() {
+ }
+ )cpp");
+ std::string FilePath = testPath("foo.h");
+ addFile(FilePath, R"cpp(
+ #include "bar.h"
+ int test() {
+ }
+ )cpp");
+ addFile("foo.cpp", R"cpp(
+ #include "foo.h"
+ )cpp");
+ EXPECT_THAT(getSymbols(FilePath), ElementsAre(QName("test")));
+}
+
+TEST_F(DocumentSymbolsTest, Template) {
+ std::string FilePath = testPath("foo.cpp");
+ addFile(FilePath, R"(
+ // Primary templates and specializations are included but instantiations
+ // are not.
+ template <class T> struct Tmpl {T x = 0;};
+ template <> struct Tmpl<int> {};
+ extern template struct Tmpl<float>;
+ template struct Tmpl<double>;
+ )");
+ EXPECT_THAT(getSymbols(FilePath),
+ ElementsAre(AllOf(QName("Tmpl"), WithKind(SymbolKind::Struct)),
+ AllOf(QName("Tmpl::x"), WithKind(SymbolKind::Field)),
+ AllOf(QName("Tmpl"), WithKind(SymbolKind::Struct))));
+}
+
+TEST_F(DocumentSymbolsTest, Namespaces) {
+ std::string FilePath = testPath("foo.cpp");
+ addFile(FilePath, R"cpp(
+ namespace ans1 {
+ int ai1;
+ namespace ans2 {
+ int ai2;
+ }
+ }
+ namespace {
+ void test() {}
+ }
+
+ namespace na {
+ inline namespace nb {
+ class Foo {};
+ }
+ }
+ namespace na {
+ // This is still inlined.
+ namespace nb {
+ class Bar {};
+ }
+ }
+ )cpp");
+ EXPECT_THAT(
+ getSymbols(FilePath),
+ ElementsAreArray({QName("ans1"), QName("ans1::ai1"), QName("ans1::ans2"),
+ QName("ans1::ans2::ai2"), QName("test"), QName("na"),
+ QName("na::nb"), QName("na::Foo"), QName("na"),
+ QName("na::nb"), QName("na::Bar")}));
+}
+
+TEST_F(DocumentSymbolsTest, Enums) {
+ std::string FilePath = testPath("foo.cpp");
+ addFile(FilePath, R"(
+ enum {
+ Red
+ };
+ enum Color {
+ Green
+ };
+ enum class Color2 {
+ Yellow
+ };
+ namespace ns {
+ enum {
+ Black
+ };
+ }
+ )");
+ EXPECT_THAT(getSymbols(FilePath),
+ ElementsAre(QName("Red"), QName("Color"), QName("Green"),
+ QName("Color2"), QName("Color2::Yellow"), QName("ns"),
+ QName("ns::Black")));
+}
+
+TEST_F(DocumentSymbolsTest, FromMacro) {
+ std::string FilePath = testPath("foo.cpp");
+ Annotations Main(R"(
+ #define FF(name) \
+ class name##_Test {};
+
+ $expansion[[FF]](abc);
+
+ #define FF2() \
+ class $spelling[[Test]] {};
+
+ FF2();
+ )");
+ addFile(FilePath, Main.code());
+ EXPECT_THAT(
+ getSymbols(FilePath),
+ ElementsAre(AllOf(QName("abc_Test"), SymRange(Main.range("expansion"))),
+ AllOf(QName("Test"), SymRange(Main.range("spelling")))));
}
} // namespace clangd
diff --git a/unittests/clangd/FuzzyMatchTests.cpp b/unittests/clangd/FuzzyMatchTests.cpp
index 3de53494..c03f8f3d 100644
--- a/unittests/clangd/FuzzyMatchTests.cpp
+++ b/unittests/clangd/FuzzyMatchTests.cpp
@@ -46,10 +46,14 @@ private:
struct MatchesMatcher : public testing::MatcherInterface<StringRef> {
ExpectedMatch Candidate;
- MatchesMatcher(ExpectedMatch Candidate) : Candidate(std::move(Candidate)) {}
+ Optional<float> Score;
+ MatchesMatcher(ExpectedMatch Candidate, Optional<float> Score)
+ : Candidate(std::move(Candidate)), Score(Score) {}
void DescribeTo(::std::ostream *OS) const override {
raw_os_ostream(*OS) << "Matches " << Candidate;
+ if (Score)
+ *OS << " with score " << *Score;
}
bool MatchAndExplain(StringRef Pattern,
@@ -60,21 +64,22 @@ struct MatchesMatcher : public testing::MatcherInterface<StringRef> {
FuzzyMatcher Matcher(Pattern);
auto Result = Matcher.match(Candidate.Word);
auto AnnotatedMatch = Matcher.dumpLast(*OS << "\n");
- return Result && Candidate.accepts(AnnotatedMatch);
+ return Result && Candidate.accepts(AnnotatedMatch) &&
+ (!Score || testing::Value(*Result, testing::FloatEq(*Score)));
}
};
-// Accepts patterns that match a given word.
+// Accepts patterns that match a given word, optionally requiring a score.
// Dumps the debug tables on match failure.
-testing::Matcher<StringRef> matches(StringRef M) {
- return testing::MakeMatcher<StringRef>(new MatchesMatcher(M));
+testing::Matcher<StringRef> matches(StringRef M, Optional<float> Score = {}) {
+ return testing::MakeMatcher<StringRef>(new MatchesMatcher(M, Score));
}
TEST(FuzzyMatch, Matches) {
EXPECT_THAT("", matches("unique_ptr"));
EXPECT_THAT("u_p", matches("[u]nique[_p]tr"));
EXPECT_THAT("up", matches("[u]nique_[p]tr"));
- EXPECT_THAT("uq", matches("[u]ni[q]ue_ptr"));
+ EXPECT_THAT("uq", Not(matches("unique_ptr")));
EXPECT_THAT("qp", Not(matches("unique_ptr")));
EXPECT_THAT("log", Not(matches("SVGFEMorphologyElement")));
@@ -94,7 +99,7 @@ TEST(FuzzyMatch, Matches) {
EXPECT_THAT("moza", matches("-[moz]-[a]nimation"));
EXPECT_THAT("ab", matches("[ab]A"));
- EXPECT_THAT("ccm", matches("[c]a[cm]elCase"));
+ EXPECT_THAT("ccm", Not(matches("cacmelCase")));
EXPECT_THAT("bti", Not(matches("the_black_knight")));
EXPECT_THAT("ccm", Not(matches("camelCase")));
EXPECT_THAT("cmcm", Not(matches("camelCase")));
@@ -105,17 +110,17 @@ TEST(FuzzyMatch, Matches) {
EXPECT_THAT("LLLL", Not(matches("SVisualLoggerLogsList")));
EXPECT_THAT("TEdit", matches("[T]ext[Edit]"));
EXPECT_THAT("TEdit", matches("[T]ext[Edit]or"));
- EXPECT_THAT("TEdit", matches("[Te]xte[dit]"));
+ EXPECT_THAT("TEdit", Not(matches("[T]ext[edit]")));
EXPECT_THAT("TEdit", matches("[t]ext_[edit]"));
- EXPECT_THAT("TEditDit", matches("[T]ext[Edit]or[D]ecorat[i]on[T]ype"));
+ EXPECT_THAT("TEditDt", matches("[T]ext[Edit]or[D]ecoration[T]ype"));
EXPECT_THAT("TEdit", matches("[T]ext[Edit]orDecorationType"));
EXPECT_THAT("Tedit", matches("[T]ext[Edit]"));
EXPECT_THAT("ba", Not(matches("?AB?")));
EXPECT_THAT("bkn", matches("the_[b]lack_[kn]ight"));
- EXPECT_THAT("bt", matches("the_[b]lack_knigh[t]"));
- EXPECT_THAT("ccm", matches("[c]amelCase[cm]"));
- EXPECT_THAT("fdm", matches("[f]in[dM]odel"));
- EXPECT_THAT("fob", matches("[fo]o[b]ar"));
+ EXPECT_THAT("bt", Not(matches("the_[b]lack_knigh[t]")));
+ EXPECT_THAT("ccm", Not(matches("[c]amelCase[cm]")));
+ EXPECT_THAT("fdm", Not(matches("[f]in[dM]odel")));
+ EXPECT_THAT("fob", Not(matches("[fo]o[b]ar")));
EXPECT_THAT("fobz", Not(matches("foobar")));
EXPECT_THAT("foobar", matches("[foobar]"));
EXPECT_THAT("form", matches("editor.[form]atOnSave"));
@@ -127,14 +132,14 @@ TEST(FuzzyMatch, Matches) {
EXPECT_THAT("gp", matches("[G]it_Git_[P]ull"));
EXPECT_THAT("is", matches("[I]mport[S]tatement"));
EXPECT_THAT("is", matches("[is]Valid"));
- EXPECT_THAT("lowrd", matches("[low]Wo[rd]"));
- EXPECT_THAT("myvable", matches("[myva]ria[ble]"));
+ EXPECT_THAT("lowrd", Not(matches("[low]Wo[rd]")));
+ EXPECT_THAT("myvable", Not(matches("[myva]ria[ble]")));
EXPECT_THAT("no", Not(matches("")));
EXPECT_THAT("no", Not(matches("match")));
EXPECT_THAT("ob", Not(matches("foobar")));
EXPECT_THAT("sl", matches("[S]Visual[L]oggerLogsList"));
- EXPECT_THAT("sllll", matches("[S]Visua[lL]ogger[L]ogs[L]ist"));
- EXPECT_THAT("Three", matches("H[T]ML[HRE]l[e]ment"));
+ EXPECT_THAT("sllll", matches("[S]Visua[L]ogger[Ll]ama[L]ist"));
+ EXPECT_THAT("THRE", matches("H[T]ML[HRE]lement"));
EXPECT_THAT("b", Not(matches("NDEBUG")));
EXPECT_THAT("Three", matches("[Three]"));
EXPECT_THAT("fo", Not(matches("barfoo")));
@@ -168,6 +173,9 @@ TEST(FuzzyMatch, Matches) {
// These would ideally match, but would need special segmentation rules.
EXPECT_THAT("printf", Not(matches("s[printf]")));
EXPECT_THAT("str", Not(matches("o[str]eam")));
+ EXPECT_THAT("strcpy", Not(matches("strncpy")));
+ EXPECT_THAT("std", Not(matches("PTHREAD_MUTEX_STALLED")));
+ EXPECT_THAT("std", Not(matches("pthread_condattr_setpshared")));
}
struct RankMatcher : public testing::MatcherInterface<StringRef> {
@@ -231,15 +239,14 @@ template <typename... T> testing::Matcher<StringRef> ranks(T... RankedStrings) {
}
TEST(FuzzyMatch, Ranking) {
- EXPECT_THAT("eb", ranks("[e]mplace_[b]ack", "[e]m[b]ed"));
EXPECT_THAT("cons",
ranks("[cons]ole", "[Cons]ole", "ArrayBuffer[Cons]tructor"));
EXPECT_THAT("foo", ranks("[foo]", "[Foo]"));
- EXPECT_THAT("onMess",
- ranks("[onMess]age", "[onmess]age", "[on]This[M]ega[Es]cape[s]"));
+ EXPECT_THAT("onMes",
+ ranks("[onMes]sage", "[onmes]sage", "[on]This[M]ega[Es]capes"));
EXPECT_THAT("CC", ranks("[C]amel[C]ase", "[c]amel[C]ase"));
EXPECT_THAT("cC", ranks("[c]amel[C]ase", "[C]amel[C]ase"));
- EXPECT_THAT("p", ranks("[p]arse", "[p]osix", "[p]afdsa", "[p]ath", "[p]"));
+ EXPECT_THAT("p", ranks("[p]", "[p]arse", "[p]osix", "[p]afdsa", "[p]ath"));
EXPECT_THAT("pa", ranks("[pa]rse", "[pa]th", "[pa]fdsa"));
EXPECT_THAT("log", ranks("[log]", "Scroll[Log]icalPosition"));
EXPECT_THAT("e", ranks("[e]lse", "Abstract[E]lement"));
@@ -254,12 +261,39 @@ TEST(FuzzyMatch, Ranking) {
ranks("[convertModelPosition]ToViewPosition",
"[convert]ViewTo[ModelPosition]"));
EXPECT_THAT("is", ranks("[is]ValidViewletId", "[i]mport [s]tatement"));
- EXPECT_THAT("title", ranks("window.[title]",
- "files.[t]r[i]m[T]rai[l]ingWhit[e]space"));
- EXPECT_THAT("strcpy", ranks("[strcpy]", "[strcpy]_s", "[str]n[cpy]"));
- EXPECT_THAT("close", ranks("workbench.quickOpen.[close]OnFocusOut",
- "[c]ss.[l]int.imp[o]rt[S]tat[e]ment",
- "[c]ss.co[lo]rDecorator[s].[e]nable"));
+ EXPECT_THAT("strcpy", ranks("[strcpy]", "[strcpy]_s"));
+}
+
+// Verify some bounds so we know scores fall in the right range.
+// Testing exact scores is fragile, so we prefer Ranking tests.
+TEST(FuzzyMatch, Scoring) {
+ EXPECT_THAT("abs", matches("[a]w[B]xYz[S]", 0.f));
+ EXPECT_THAT("abs", matches("[abs]l", 1.f));
+ EXPECT_THAT("abs", matches("[abs]", 2.f));
+ EXPECT_THAT("Abs", matches("[abs]", 2.f));
+}
+
+// Returns pretty-printed segmentation of Text.
+// e.g. std::basic_string --> +-- +---- +-----
+std::string segment(StringRef Text) {
+ std::vector<CharRole> Roles(Text.size());
+ calculateRoles(Text, Roles);
+ std::string Printed;
+ for (unsigned I = 0; I < Text.size(); ++I)
+ Printed.push_back("?-+ "[static_cast<unsigned>(Roles[I])]);
+ return Printed;
+}
+
+// this is a no-op hack so clang-format will vertically align our testcases.
+StringRef returns(StringRef Text) { return Text; }
+
+TEST(FuzzyMatch, Segmentation) {
+ EXPECT_THAT(segment("std::basic_string"), //
+ returns("+-- +---- +-----"));
+ EXPECT_THAT(segment("XMLHttpRequest"), //
+ returns("+--+---+------"));
+ EXPECT_THAT(segment("t3h PeNgU1N oF d00m!!!!!!!!"), //
+ returns("+-- +-+-+-+ ++ +--- "));
}
} // namespace
diff --git a/unittests/clangd/HeadersTests.cpp b/unittests/clangd/HeadersTests.cpp
index 94dcd317..21046c0c 100644
--- a/unittests/clangd/HeadersTests.cpp
+++ b/unittests/clangd/HeadersTests.cpp
@@ -64,24 +64,23 @@ private:
}
protected:
- std::vector<Inclusion> collectIncludes() {
+ IncludeStructure collectIncludes() {
auto Clang = setupClang();
PreprocessOnlyAction Action;
EXPECT_TRUE(
Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));
- std::vector<Inclusion> Inclusions;
- Clang->getPreprocessor().addPPCallbacks(collectInclusionsInMainFileCallback(
- Clang->getSourceManager(),
- [&](Inclusion Inc) { Inclusions.push_back(std::move(Inc)); }));
+ IncludeStructure Includes;
+ Clang->getPreprocessor().addPPCallbacks(
+ collectIncludeStructureCallback(Clang->getSourceManager(), &Includes));
EXPECT_TRUE(Action.Execute());
Action.EndSourceFile();
- return Inclusions;
+ return Includes;
}
- // Calculates the include path, or returns "" on error.
+ // Calculates the include path, or returns "" on error or header should not be
+ // inserted.
std::string calculate(PathRef Original, PathRef Preferred = "",
- const std::vector<Inclusion> &Inclusions = {},
- bool ExpectError = false) {
+ const std::vector<Inclusion> &Inclusions = {}) {
auto Clang = setupClang();
PreprocessOnlyAction Action;
EXPECT_TRUE(
@@ -94,24 +93,21 @@ protected:
/*Verbatim=*/!llvm::sys::path::is_absolute(Header)};
};
- auto Path = calculateIncludePath(
- MainFile, CDB.getCompileCommand(MainFile)->Directory,
- Clang->getPreprocessor().getHeaderSearchInfo(), Inclusions,
- ToHeaderFile(Original), ToHeaderFile(Preferred));
+ IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),
+ CDB.getCompileCommand(MainFile)->Directory,
+ Clang->getPreprocessor().getHeaderSearchInfo());
+ for (const auto &Inc : Inclusions)
+ Inserter.addExisting(Inc);
+ auto Declaring = ToHeaderFile(Original);
+ auto Inserted = ToHeaderFile(Preferred);
+ if (!Inserter.shouldInsertInclude(Declaring, Inserted))
+ return "";
+ std::string Path = Inserter.calculateIncludePath(Declaring, Inserted);
Action.EndSourceFile();
- if (!Path) {
- llvm::consumeError(Path.takeError());
- EXPECT_TRUE(ExpectError);
- return std::string();
- } else {
- EXPECT_FALSE(ExpectError);
- }
- return std::move(*Path);
+ return Path;
}
- Expected<Optional<TextEdit>>
- insert(const HeaderFile &DeclaringHeader, const HeaderFile &InsertedHeader,
- const std::vector<Inclusion> &ExistingInclusions = {}) {
+ Optional<TextEdit> insert(StringRef VerbatimHeader) {
auto Clang = setupClang();
PreprocessOnlyAction Action;
EXPECT_TRUE(
@@ -120,10 +116,7 @@ protected:
IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),
CDB.getCompileCommand(MainFile)->Directory,
Clang->getPreprocessor().getHeaderSearchInfo());
- for (const auto &Inc : ExistingInclusions)
- Inserter.addExisting(Inc);
-
- auto Edit = Inserter.insert(DeclaringHeader, InsertedHeader);
+ auto Edit = Inserter.insert(VerbatimHeader);
Action.EndSourceFile();
return Edit;
}
@@ -139,6 +132,14 @@ protected:
MATCHER_P(Written, Name, "") { return arg.Written == Name; }
MATCHER_P(Resolved, Name, "") { return arg.Resolved == Name; }
+MATCHER_P2(Distance, File, D, "") {
+ if (arg.getKey() != File)
+ *result_listener << "file =" << arg.getKey().str();
+ if (arg.getValue() != D)
+ *result_listener << "distance =" << arg.getValue();
+ return arg.getKey() == File && arg.getValue() == D;
+}
+
TEST_F(HeadersTest, CollectRewrittenAndResolved) {
FS.Files[MainFile] = R"cpp(
#include "sub/bar.h" // not shortest
@@ -146,9 +147,12 @@ TEST_F(HeadersTest, CollectRewrittenAndResolved) {
std::string BarHeader = testPath("sub/bar.h");
FS.Files[BarHeader] = "";
- EXPECT_THAT(collectIncludes(),
+ EXPECT_THAT(collectIncludes().MainFileIncludes,
UnorderedElementsAre(
AllOf(Written("\"sub/bar.h\""), Resolved(BarHeader))));
+ EXPECT_THAT(collectIncludes().includeDepth(MainFile),
+ UnorderedElementsAre(Distance(MainFile, 0u),
+ Distance(testPath("sub/bar.h"), 1u)));
}
TEST_F(HeadersTest, OnlyCollectInclusionsInMain) {
@@ -162,8 +166,16 @@ TEST_F(HeadersTest, OnlyCollectInclusionsInMain) {
#include "bar.h"
)cpp";
EXPECT_THAT(
- collectIncludes(),
+ collectIncludes().MainFileIncludes,
UnorderedElementsAre(AllOf(Written("\"bar.h\""), Resolved(BarHeader))));
+ EXPECT_THAT(collectIncludes().includeDepth(MainFile),
+ UnorderedElementsAre(Distance(MainFile, 0u),
+ Distance(testPath("sub/bar.h"), 1u),
+ Distance(testPath("sub/baz.h"), 2u)));
+ // includeDepth() also works for non-main files.
+ EXPECT_THAT(collectIncludes().includeDepth(testPath("sub/bar.h")),
+ UnorderedElementsAre(Distance(testPath("sub/bar.h"), 0u),
+ Distance(testPath("sub/baz.h"), 1u)));
}
TEST_F(HeadersTest, UnResolvedInclusion) {
@@ -171,8 +183,10 @@ TEST_F(HeadersTest, UnResolvedInclusion) {
#include "foo.h"
)cpp";
- EXPECT_THAT(collectIncludes(),
+ EXPECT_THAT(collectIncludes().MainFileIncludes,
UnorderedElementsAre(AllOf(Written("\"foo.h\""), Resolved(""))));
+ EXPECT_THAT(collectIncludes().includeDepth(MainFile),
+ UnorderedElementsAre(Distance(MainFile, 0u)));
}
TEST_F(HeadersTest, InsertInclude) {
@@ -220,31 +234,10 @@ TEST_F(HeadersTest, DontInsertDuplicateResolved) {
EXPECT_EQ(calculate(BarHeader, "\"BAR.h\"", Inclusions), "");
}
-HeaderFile literal(StringRef Include) {
- HeaderFile H{Include, /*Verbatim=*/true};
- assert(H.valid());
- return H;
-}
-
TEST_F(HeadersTest, PreferInserted) {
- auto Edit = insert(literal("<x>"), literal("<y>"));
- EXPECT_TRUE(static_cast<bool>(Edit));
- EXPECT_TRUE(llvm::StringRef((*Edit)->newText).contains("<y>"));
-}
-
-TEST_F(HeadersTest, ExistingInclusion) {
- Inclusion Existing{Range(), /*Written=*/"<c.h>", /*Resolved=*/""};
- auto Edit = insert(literal("<c.h>"), literal("<c.h>"), {Existing});
- EXPECT_TRUE(static_cast<bool>(Edit));
- EXPECT_FALSE(static_cast<bool>(*Edit));
-}
-
-TEST_F(HeadersTest, ValidateHeaders) {
- HeaderFile InvalidHeader{"a.h", /*Verbatim=*/true};
- assert(!InvalidHeader.valid());
- auto Edit = insert(InvalidHeader, literal("\"c.h\""));
- EXPECT_FALSE(static_cast<bool>(Edit));
- llvm::consumeError(Edit.takeError());
+ auto Edit = insert("<y>");
+ EXPECT_TRUE(Edit.hasValue());
+ EXPECT_TRUE(llvm::StringRef(Edit->newText).contains("<y>"));
}
} // namespace
diff --git a/unittests/clangd/IndexTests.cpp b/unittests/clangd/IndexTests.cpp
index 4d7c4302..3ddf2d26 100644
--- a/unittests/clangd/IndexTests.cpp
+++ b/unittests/clangd/IndexTests.cpp
@@ -274,25 +274,29 @@ TEST(MergeTest, Merge) {
R.CanonicalDeclaration.FileURI = "file:///right.h";
L.References = 1;
R.References = 2;
- L.CompletionPlainInsertText = "f00"; // present in left only
- R.CompletionSnippetInsertText = "f0{$1:0}"; // present in right only
+ L.Signature = "()"; // present in left only
+ R.CompletionSnippetSuffix = "{$1:0}"; // present in right only
Symbol::Details DetL, DetR;
- DetL.CompletionDetail = "DetL";
- DetR.CompletionDetail = "DetR";
+ DetL.ReturnType = "DetL";
+ DetR.ReturnType = "DetR";
DetR.Documentation = "--doc--";
L.Detail = &DetL;
R.Detail = &DetR;
+ L.Origin = SymbolOrigin::Dynamic;
+ R.Origin = SymbolOrigin::Static;
Symbol::Details Scratch;
Symbol M = mergeSymbol(L, R, &Scratch);
EXPECT_EQ(M.Name, "Foo");
EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:///left.h");
EXPECT_EQ(M.References, 3u);
- EXPECT_EQ(M.CompletionPlainInsertText, "f00");
- EXPECT_EQ(M.CompletionSnippetInsertText, "f0{$1:0}");
+ EXPECT_EQ(M.Signature, "()");
+ EXPECT_EQ(M.CompletionSnippetSuffix, "{$1:0}");
ASSERT_TRUE(M.Detail);
- EXPECT_EQ(M.Detail->CompletionDetail, "DetL");
+ EXPECT_EQ(M.Detail->ReturnType, "DetL");
EXPECT_EQ(M.Detail->Documentation, "--doc--");
+ EXPECT_EQ(M.Origin,
+ SymbolOrigin::Dynamic | SymbolOrigin::Static | SymbolOrigin::Merge);
}
TEST(MergeTest, PreferSymbolWithDefn) {
@@ -302,19 +306,19 @@ TEST(MergeTest, PreferSymbolWithDefn) {
L.ID = R.ID = SymbolID("hello");
L.CanonicalDeclaration.FileURI = "file:/left.h";
R.CanonicalDeclaration.FileURI = "file:/right.h";
- L.CompletionPlainInsertText = "left-insert";
- R.CompletionPlainInsertText = "right-insert";
+ L.Name = "left";
+ R.Name = "right";
Symbol M = mergeSymbol(L, R, &Scratch);
EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:/left.h");
EXPECT_EQ(M.Definition.FileURI, "");
- EXPECT_EQ(M.CompletionPlainInsertText, "left-insert");
+ EXPECT_EQ(M.Name, "left");
R.Definition.FileURI = "file:/right.cpp"; // Now right will be favored.
M = mergeSymbol(L, R, &Scratch);
EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:/right.h");
EXPECT_EQ(M.Definition.FileURI, "file:/right.cpp");
- EXPECT_EQ(M.CompletionPlainInsertText, "right-insert");
+ EXPECT_EQ(M.Name, "right");
}
} // namespace
diff --git a/unittests/clangd/JSONExprTests.cpp b/unittests/clangd/JSONExprTests.cpp
deleted file mode 100644
index 1e24e098..00000000
--- a/unittests/clangd/JSONExprTests.cpp
+++ /dev/null
@@ -1,291 +0,0 @@
-//===-- JSONExprTests.cpp - JSON expression unit tests ----------*- C++ -*-===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "JSONExpr.h"
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-
-namespace clang {
-namespace clangd {
-namespace json {
-
-namespace {
-
-std::string s(const Expr &E) { return llvm::formatv("{0}", E).str(); }
-std::string sp(const Expr &E) { return llvm::formatv("{0:2}", E).str(); }
-
-TEST(JSONExprTests, Types) {
- EXPECT_EQ("true", s(true));
- EXPECT_EQ("null", s(nullptr));
- EXPECT_EQ("2.5", s(2.5));
- EXPECT_EQ(R"("foo")", s("foo"));
- EXPECT_EQ("[1,2,3]", s({1, 2, 3}));
- EXPECT_EQ(R"({"x":10,"y":20})", s(obj{{"x", 10}, {"y", 20}}));
-}
-
-TEST(JSONExprTests, Constructors) {
- // Lots of edge cases around empty and singleton init lists.
- EXPECT_EQ("[[[3]]]", s({{{3}}}));
- EXPECT_EQ("[[[]]]", s({{{}}}));
- EXPECT_EQ("[[{}]]", s({{obj{}}}));
- EXPECT_EQ(R"({"A":{"B":{}}})", s(obj{{"A", obj{{"B", obj{}}}}}));
- EXPECT_EQ(R"({"A":{"B":{"X":"Y"}}})",
- s(obj{{"A", obj{{"B", obj{{"X", "Y"}}}}}}));
-}
-
-TEST(JSONExprTests, StringOwnership) {
- char X[] = "Hello";
- Expr Alias = static_cast<const char *>(X);
- X[1] = 'a';
- EXPECT_EQ(R"("Hallo")", s(Alias));
-
- std::string Y = "Hello";
- Expr Copy = Y;
- Y[1] = 'a';
- EXPECT_EQ(R"("Hello")", s(Copy));
-}
-
-TEST(JSONExprTests, CanonicalOutput) {
- // Objects are sorted (but arrays aren't)!
- EXPECT_EQ(R"({"a":1,"b":2,"c":3})", s(obj{{"a", 1}, {"c", 3}, {"b", 2}}));
- EXPECT_EQ(R"(["a","c","b"])", s({"a", "c", "b"}));
- EXPECT_EQ("3", s(3.0));
-}
-
-TEST(JSONExprTests, Escaping) {
- std::string test = {
- 0, // Strings may contain nulls.
- '\b', '\f', // Have mnemonics, but we escape numerically.
- '\r', '\n', '\t', // Escaped with mnemonics.
- 'S', '\"', '\\', // Printable ASCII characters.
- '\x7f', // Delete is not escaped.
- '\xce', '\x94', // Non-ASCII UTF-8 is not escaped.
- };
-
- std::string teststring = R"("\u0000\u0008\u000c\r\n\tS\"\\)"
- "\x7f\xCE\x94\"";
-
- EXPECT_EQ(teststring, s(test));
-
- EXPECT_EQ(R"({"object keys are\nescaped":true})",
- s(obj{{"object keys are\nescaped", true}}));
-}
-
-TEST(JSONExprTests, PrettyPrinting) {
- const char str[] = R"({
- "empty_array": [],
- "empty_object": {},
- "full_array": [
- 1,
- null
- ],
- "full_object": {
- "nested_array": [
- {
- "property": "value"
- }
- ]
- }
-})";
-
- EXPECT_EQ(str, sp(obj{
- {"empty_object", obj{}},
- {"empty_array", {}},
- {"full_array", {1, nullptr}},
- {"full_object",
- obj{
- {"nested_array",
- {obj{
- {"property", "value"},
- }}},
- }},
- }));
-}
-
-TEST(JSONTest, Parse) {
- auto Compare = [](llvm::StringRef S, Expr Expected) {
- if (auto E = parse(S)) {
- // Compare both string forms and with operator==, in case we have bugs.
- EXPECT_EQ(*E, Expected);
- EXPECT_EQ(sp(*E), sp(Expected));
- } else {
- handleAllErrors(E.takeError(), [S](const llvm::ErrorInfoBase &E) {
- FAIL() << "Failed to parse JSON >>> " << S << " <<<: " << E.message();
- });
- }
- };
-
- Compare(R"(true)", true);
- Compare(R"(false)", false);
- Compare(R"(null)", nullptr);
-
- Compare(R"(42)", 42);
- Compare(R"(2.5)", 2.5);
- Compare(R"(2e50)", 2e50);
- Compare(R"(1.2e3456789)", std::numeric_limits<double>::infinity());
-
- Compare(R"("foo")", "foo");
- Compare(R"("\"\\\b\f\n\r\t")", "\"\\\b\f\n\r\t");
- Compare(R"("\u0000")", llvm::StringRef("\0", 1));
- Compare("\"\x7f\"", "\x7f");
- Compare(R"("\ud801\udc37")", u8"\U00010437"); // UTF16 surrogate pair escape.
- Compare("\"\xE2\x82\xAC\xF0\x9D\x84\x9E\"", u8"\u20ac\U0001d11e"); // UTF8
- Compare(
- R"("LoneLeading=\ud801, LoneTrailing=\udc01, LeadingLeadingTrailing=\ud801\ud801\udc37")",
- u8"LoneLeading=\ufffd, LoneTrailing=\ufffd, "
- u8"LeadingLeadingTrailing=\ufffd\U00010437"); // Invalid unicode.
-
- Compare(R"({"":0,"":0})", obj{{"", 0}});
- Compare(R"({"obj":{},"arr":[]})", obj{{"obj", obj{}}, {"arr", {}}});
- Compare(R"({"\n":{"\u0000":[[[[]]]]}})",
- obj{{"\n", obj{
- {llvm::StringRef("\0", 1), {{{{}}}}},
- }}});
- Compare("\r[\n\t] ", {});
-}
-
-TEST(JSONTest, ParseErrors) {
- auto ExpectErr = [](llvm::StringRef Msg, llvm::StringRef S) {
- if (auto E = parse(S)) {
- // Compare both string forms and with operator==, in case we have bugs.
- FAIL() << "Parsed JSON >>> " << S << " <<< but wanted error: " << Msg;
- } else {
- handleAllErrors(E.takeError(), [S, Msg](const llvm::ErrorInfoBase &E) {
- EXPECT_THAT(E.message(), testing::HasSubstr(Msg)) << S;
- });
- }
- };
- ExpectErr("Unexpected EOF", "");
- ExpectErr("Unexpected EOF", "[");
- ExpectErr("Text after end of document", "[][]");
- ExpectErr("Invalid bareword", "fuzzy");
- ExpectErr("Expected , or ]", "[2?]");
- ExpectErr("Expected object key", "{a:2}");
- ExpectErr("Expected : after object key", R"({"a",2})");
- ExpectErr("Expected , or } after object property", R"({"a":2 "b":3})");
- ExpectErr("Expected JSON value", R"([&%!])");
- ExpectErr("Invalid number", "1e1.0");
- ExpectErr("Unterminated string", R"("abc\"def)");
- ExpectErr("Control character in string", "\"abc\ndef\"");
- ExpectErr("Invalid escape sequence", R"("\030")");
- ExpectErr("Invalid \\u escape sequence", R"("\usuck")");
- ExpectErr("[3:3, byte=19]", R"({
- "valid": 1,
- invalid: 2
-})");
-}
-
-TEST(JSONTest, Inspection) {
- llvm::Expected<Expr> Doc = parse(R"(
- {
- "null": null,
- "boolean": false,
- "number": 2.78,
- "string": "json",
- "array": [null, true, 3.14, "hello", [1,2,3], {"time": "arrow"}],
- "object": {"fruit": "banana"}
- }
- )");
- EXPECT_TRUE(!!Doc);
-
- obj *O = Doc->asObject();
- ASSERT_TRUE(O);
-
- EXPECT_FALSE(O->getNull("missing"));
- EXPECT_FALSE(O->getNull("boolean"));
- EXPECT_TRUE(O->getNull("null"));
-
- EXPECT_EQ(O->getNumber("number"), llvm::Optional<double>(2.78));
- EXPECT_FALSE(O->getInteger("number"));
- EXPECT_EQ(O->getString("string"), llvm::Optional<llvm::StringRef>("json"));
- ASSERT_FALSE(O->getObject("missing"));
- ASSERT_FALSE(O->getObject("array"));
- ASSERT_TRUE(O->getObject("object"));
- EXPECT_EQ(*O->getObject("object"), (obj{{"fruit", "banana"}}));
-
- ary *A = O->getArray("array");
- ASSERT_TRUE(A);
- EXPECT_EQ(A->getBoolean(1), llvm::Optional<bool>(true));
- ASSERT_TRUE(A->getArray(4));
- EXPECT_EQ(*A->getArray(4), (ary{1, 2, 3}));
- EXPECT_EQ(A->getArray(4)->getInteger(1), llvm::Optional<int64_t>(2));
- int I = 0;
- for (Expr &E : *A) {
- if (I++ == 5) {
- ASSERT_TRUE(E.asObject());
- EXPECT_EQ(E.asObject()->getString("time"),
- llvm::Optional<llvm::StringRef>("arrow"));
- } else
- EXPECT_FALSE(E.asObject());
- }
-}
-
-// Sample struct with typical JSON-mapping rules.
-struct CustomStruct {
- CustomStruct() : B(false) {}
- CustomStruct(std::string S, llvm::Optional<int> I, bool B)
- : S(S), I(I), B(B) {}
- std::string S;
- llvm::Optional<int> I;
- bool B;
-};
-inline bool operator==(const CustomStruct &L, const CustomStruct &R) {
- return L.S == R.S && L.I == R.I && L.B == R.B;
-}
-inline std::ostream &operator<<(std::ostream &OS, const CustomStruct &S) {
- return OS << "(" << S.S << ", " << (S.I ? std::to_string(*S.I) : "None")
- << ", " << S.B << ")";
-}
-bool fromJSON(const json::Expr &E, CustomStruct &R) {
- ObjectMapper O(E);
- if (!O || !O.map("str", R.S) || !O.map("int", R.I))
- return false;
- O.map("bool", R.B);
- return true;
-}
-
-TEST(JSONTest, Deserialize) {
- std::map<std::string, std::vector<CustomStruct>> R;
- CustomStruct ExpectedStruct = {"foo", 42, true};
- std::map<std::string, std::vector<CustomStruct>> Expected;
- Expr J = obj{{"foo", ary{
- obj{
- {"str", "foo"},
- {"int", 42},
- {"bool", true},
- {"unknown", "ignored"},
- },
- obj{{"str", "bar"}},
- obj{
- {"str", "baz"},
- {"bool", "string"}, // OK, deserialize ignores.
- },
- }}};
- Expected["foo"] = {
- CustomStruct("foo", 42, true),
- CustomStruct("bar", llvm::None, false),
- CustomStruct("baz", llvm::None, false),
- };
- ASSERT_TRUE(fromJSON(J, R));
- EXPECT_EQ(R, Expected);
-
- CustomStruct V;
- EXPECT_FALSE(fromJSON(nullptr, V)) << "Not an object " << V;
- EXPECT_FALSE(fromJSON(obj{}, V)) << "Missing required field " << V;
- EXPECT_FALSE(fromJSON(obj{{"str", 1}}, V)) << "Wrong type " << V;
- // Optional<T> must parse as the correct type if present.
- EXPECT_FALSE(fromJSON(obj{{"str", 1}, {"int", "string"}}, V))
- << "Wrong type for Optional<T> " << V;
-}
-
-} // namespace
-} // namespace json
-} // namespace clangd
-} // namespace clang
diff --git a/unittests/clangd/QualityTests.cpp b/unittests/clangd/QualityTests.cpp
index aa194ced..1dcb3911 100644
--- a/unittests/clangd/QualityTests.cpp
+++ b/unittests/clangd/QualityTests.cpp
@@ -17,58 +17,154 @@
//
//===----------------------------------------------------------------------===//
+#include "FileDistance.h"
#include "Quality.h"
+#include "TestFS.h"
#include "TestTU.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Type.h"
+#include "clang/Sema/CodeCompleteConsumer.h"
+#include "llvm/Support/Casting.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
+
+// Force the unittest URI scheme to be linked,
+static int LLVM_ATTRIBUTE_UNUSED UnittestSchemeAnchorDest =
+ UnittestSchemeAnchorSource;
+
namespace {
TEST(QualityTests, SymbolQualitySignalExtraction) {
auto Header = TestTU::withHeaderCode(R"cpp(
- int x;
+ int _X;
[[deprecated]]
- int f() { return x; }
+ int _f() { return _X; }
)cpp");
auto Symbols = Header.headerSymbols();
auto AST = Header.build();
SymbolQualitySignals Quality;
- Quality.merge(findSymbol(Symbols, "x"));
+ Quality.merge(findSymbol(Symbols, "_X"));
EXPECT_FALSE(Quality.Deprecated);
- EXPECT_EQ(Quality.SemaCCPriority, SymbolQualitySignals().SemaCCPriority);
+ EXPECT_TRUE(Quality.ReservedName);
EXPECT_EQ(Quality.References, SymbolQualitySignals().References);
+ EXPECT_EQ(Quality.Category, SymbolQualitySignals::Variable);
- Symbol F = findSymbol(Symbols, "f");
+ Symbol F = findSymbol(Symbols, "_f");
F.References = 24; // TestTU doesn't count references, so fake it.
Quality = {};
Quality.merge(F);
EXPECT_FALSE(Quality.Deprecated); // FIXME: Include deprecated bit in index.
- EXPECT_EQ(Quality.SemaCCPriority, SymbolQualitySignals().SemaCCPriority);
+ EXPECT_FALSE(Quality.ReservedName);
EXPECT_EQ(Quality.References, 24u);
+ EXPECT_EQ(Quality.Category, SymbolQualitySignals::Function);
Quality = {};
- Quality.merge(CodeCompletionResult(&findDecl(AST, "f"), /*Priority=*/42));
+ Quality.merge(CodeCompletionResult(&findDecl(AST, "_f"), /*Priority=*/42));
EXPECT_TRUE(Quality.Deprecated);
- EXPECT_EQ(Quality.SemaCCPriority, 42u);
+ EXPECT_FALSE(Quality.ReservedName);
EXPECT_EQ(Quality.References, SymbolQualitySignals().References);
+ EXPECT_EQ(Quality.Category, SymbolQualitySignals::Function);
+
+ Quality = {};
+ Quality.merge(CodeCompletionResult("if"));
+ EXPECT_EQ(Quality.Category, SymbolQualitySignals::Keyword);
}
TEST(QualityTests, SymbolRelevanceSignalExtraction) {
- auto AST = TestTU::withHeaderCode(R"cpp(
- [[deprecated]]
- int f() { return 0; }
- )cpp")
- .build();
+ TestTU Test;
+ Test.HeaderCode = R"cpp(
+ int header();
+ int header_main();
+
+ namespace hdr { class Bar {}; } // namespace hdr
+
+ #define DEFINE_FLAG(X) \
+ namespace flags { \
+ int FLAGS_##X; \
+ } \
+
+ DEFINE_FLAG(FOO)
+ )cpp";
+ Test.Code = R"cpp(
+ using hdr::Bar;
+
+ using flags::FLAGS_FOO;
+
+ int ::header_main() {}
+ int main();
+
+ [[deprecated]]
+ int deprecated() { return 0; }
+
+ namespace { struct X { void y() { int z; } }; }
+ struct S{}
+ )cpp";
+ auto AST = Test.build();
SymbolRelevanceSignals Relevance;
- Relevance.merge(CodeCompletionResult(&findDecl(AST, "f"), /*Priority=*/42,
- nullptr, false, /*Accessible=*/false));
+ Relevance.merge(CodeCompletionResult(&findDecl(AST, "deprecated"),
+ /*Priority=*/42, nullptr, false,
+ /*Accessible=*/false));
EXPECT_EQ(Relevance.NameMatch, SymbolRelevanceSignals().NameMatch);
EXPECT_TRUE(Relevance.Forbidden);
+ EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::GlobalScope);
+
+ Relevance = {};
+ Relevance.merge(CodeCompletionResult(&findDecl(AST, "main"), 42));
+ EXPECT_FLOAT_EQ(Relevance.SemaProximityScore, 1.0f) << "Decl in current file";
+ Relevance = {};
+ Relevance.merge(CodeCompletionResult(&findDecl(AST, "header"), 42));
+ EXPECT_FLOAT_EQ(Relevance.SemaProximityScore, 0.6f) << "Decl from header";
+ Relevance = {};
+ Relevance.merge(CodeCompletionResult(&findDecl(AST, "header_main"), 42));
+ EXPECT_FLOAT_EQ(Relevance.SemaProximityScore, 1.0f)
+ << "Current file and header";
+
+ auto constructShadowDeclCompletionResult = [&](const std::string DeclName) {
+ auto *Shadow =
+ *dyn_cast<UsingDecl>(
+ &findAnyDecl(AST,
+ [&](const NamedDecl &ND) {
+ if (const UsingDecl *Using =
+ dyn_cast<UsingDecl>(&ND))
+ if (Using->shadow_size() &&
+ Using->getQualifiedNameAsString() == DeclName)
+ return true;
+ return false;
+ }))
+ ->shadow_begin();
+ CodeCompletionResult Result(Shadow->getTargetDecl(), 42);
+ Result.ShadowDecl = Shadow;
+ return Result;
+ };
+
+ Relevance = {};
+ Relevance.merge(constructShadowDeclCompletionResult("Bar"));
+ EXPECT_FLOAT_EQ(Relevance.SemaProximityScore, 1.0f)
+ << "Using declaration in main file";
+ Relevance.merge(constructShadowDeclCompletionResult("FLAGS_FOO"));
+ EXPECT_FLOAT_EQ(Relevance.SemaProximityScore, 1.0f)
+ << "Using declaration in main file";
+
+ Relevance = {};
+ Relevance.merge(CodeCompletionResult(&findAnyDecl(AST, "X"), 42));
+ EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::FileScope);
+ Relevance = {};
+ Relevance.merge(CodeCompletionResult(&findAnyDecl(AST, "y"), 42));
+ EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::ClassScope);
+ Relevance = {};
+ Relevance.merge(CodeCompletionResult(&findAnyDecl(AST, "z"), 42));
+ EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::FunctionScope);
+ // The injected class name is treated as the outer class name.
+ Relevance = {};
+ Relevance.merge(CodeCompletionResult(&findDecl(AST, "S::S"), 42));
+ EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::GlobalScope);
}
// Do the signals move the scores in the direction we expect?
@@ -80,17 +176,26 @@ TEST(QualityTests, SymbolQualitySignalsSanity) {
Deprecated.Deprecated = true;
EXPECT_LT(Deprecated.evaluate(), Default.evaluate());
+ SymbolQualitySignals ReservedName;
+ ReservedName.ReservedName = true;
+ EXPECT_LT(ReservedName.evaluate(), Default.evaluate());
+
SymbolQualitySignals WithReferences, ManyReferences;
- WithReferences.References = 10;
+ WithReferences.References = 20;
ManyReferences.References = 1000;
EXPECT_GT(WithReferences.evaluate(), Default.evaluate());
EXPECT_GT(ManyReferences.evaluate(), WithReferences.evaluate());
- SymbolQualitySignals LowPriority, HighPriority;
- LowPriority.SemaCCPriority = 60;
- HighPriority.SemaCCPriority = 20;
- EXPECT_GT(HighPriority.evaluate(), Default.evaluate());
- EXPECT_LT(LowPriority.evaluate(), Default.evaluate());
+ SymbolQualitySignals Keyword, Variable, Macro, Constructor, Function;
+ Keyword.Category = SymbolQualitySignals::Keyword;
+ Variable.Category = SymbolQualitySignals::Variable;
+ Macro.Category = SymbolQualitySignals::Macro;
+ Constructor.Category = SymbolQualitySignals::Constructor;
+ Function.Category = SymbolQualitySignals::Function;
+ EXPECT_GT(Variable.evaluate(), Default.evaluate());
+ EXPECT_GT(Keyword.evaluate(), Variable.evaluate());
+ EXPECT_LT(Macro.evaluate(), Default.evaluate());
+ EXPECT_LT(Constructor.evaluate(), Function.evaluate());
}
TEST(QualityTests, SymbolRelevanceSignalsSanity) {
@@ -104,10 +209,42 @@ TEST(QualityTests, SymbolRelevanceSignalsSanity) {
SymbolRelevanceSignals PoorNameMatch;
PoorNameMatch.NameMatch = 0.2f;
EXPECT_LT(PoorNameMatch.evaluate(), Default.evaluate());
+
+ SymbolRelevanceSignals WithSemaProximity;
+ WithSemaProximity.SemaProximityScore = 0.2f;
+ EXPECT_GT(WithSemaProximity.evaluate(), Default.evaluate());
+
+ SymbolRelevanceSignals IndexProximate;
+ IndexProximate.SymbolURI = "unittest:/foo/bar.h";
+ llvm::StringMap<SourceParams> ProxSources;
+ ProxSources.try_emplace(testPath("foo/baz.h"));
+ URIDistance Distance(ProxSources);
+ IndexProximate.FileProximityMatch = &Distance;
+ EXPECT_GT(IndexProximate.evaluate(), Default.evaluate());
+ SymbolRelevanceSignals IndexDistant = IndexProximate;
+ IndexDistant.SymbolURI = "unittest:/elsewhere/path.h";
+ EXPECT_GT(IndexProximate.evaluate(), IndexDistant.evaluate())
+ << IndexProximate << IndexDistant;
+ EXPECT_GT(IndexDistant.evaluate(), Default.evaluate());
+
+ SymbolRelevanceSignals Scoped;
+ Scoped.Scope = SymbolRelevanceSignals::FileScope;
+ EXPECT_EQ(Scoped.evaluate(), Default.evaluate());
+ Scoped.Query = SymbolRelevanceSignals::CodeComplete;
+ EXPECT_GT(Scoped.evaluate(), Default.evaluate());
+
+ SymbolRelevanceSignals Instance;
+ Instance.IsInstanceMember = false;
+ EXPECT_EQ(Instance.evaluate(), Default.evaluate());
+ Instance.Context = CodeCompletionContext::CCC_DotMemberAccess;
+ EXPECT_LT(Instance.evaluate(), Default.evaluate());
+ Instance.IsInstanceMember = true;
+ EXPECT_EQ(Instance.evaluate(), Default.evaluate());
}
TEST(QualityTests, SortText) {
- EXPECT_LT(sortText(std::numeric_limits<float>::infinity()), sortText(1000.2f));
+ EXPECT_LT(sortText(std::numeric_limits<float>::infinity()),
+ sortText(1000.2f));
EXPECT_LT(sortText(1000.2f), sortText(1));
EXPECT_LT(sortText(1), sortText(0.3f));
EXPECT_LT(sortText(0.3f), sortText(0));
@@ -118,6 +255,119 @@ TEST(QualityTests, SortText) {
EXPECT_LT(sortText(0, "a"), sortText(0, "z"));
}
+TEST(QualityTests, NoBoostForClassConstructor) {
+ auto Header = TestTU::withHeaderCode(R"cpp(
+ class Foo {
+ public:
+ Foo(int);
+ };
+ )cpp");
+ auto Symbols = Header.headerSymbols();
+ auto AST = Header.build();
+
+ const NamedDecl *Foo = &findDecl(AST, "Foo");
+ SymbolRelevanceSignals Cls;
+ Cls.merge(CodeCompletionResult(Foo, /*Priority=*/0));
+
+ const NamedDecl *CtorDecl = &findAnyDecl(AST, [](const NamedDecl &ND) {
+ return (ND.getQualifiedNameAsString() == "Foo::Foo") &&
+ llvm::isa<CXXConstructorDecl>(&ND);
+ });
+ SymbolRelevanceSignals Ctor;
+ Ctor.merge(CodeCompletionResult(CtorDecl, /*Priority=*/0));
+
+ EXPECT_EQ(Cls.Scope, SymbolRelevanceSignals::GlobalScope);
+ EXPECT_EQ(Ctor.Scope, SymbolRelevanceSignals::GlobalScope);
+}
+
+TEST(QualityTests, IsInstanceMember) {
+ auto Header = TestTU::withHeaderCode(R"cpp(
+ class Foo {
+ public:
+ static void foo() {}
+
+ template <typename T> void tpl(T *t) {}
+
+ void bar() {}
+ };
+ )cpp");
+ auto Symbols = Header.headerSymbols();
+
+ SymbolRelevanceSignals Rel;
+ const Symbol &FooSym = findSymbol(Symbols, "Foo::foo");
+ Rel.merge(FooSym);
+ EXPECT_FALSE(Rel.IsInstanceMember);
+ const Symbol &BarSym = findSymbol(Symbols, "Foo::bar");
+ Rel.merge(BarSym);
+ EXPECT_TRUE(Rel.IsInstanceMember);
+
+ Rel.IsInstanceMember =false;
+ const Symbol &TplSym = findSymbol(Symbols, "Foo::tpl");
+ Rel.merge(TplSym);
+ EXPECT_TRUE(Rel.IsInstanceMember);
+
+ auto AST = Header.build();
+ const NamedDecl *Foo = &findDecl(AST, "Foo::foo");
+ const NamedDecl *Bar = &findDecl(AST, "Foo::bar");
+ const NamedDecl *Tpl = &findDecl(AST, "Foo::tpl");
+
+ Rel.IsInstanceMember = false;
+ Rel.merge(CodeCompletionResult(Foo, /*Priority=*/0));
+ EXPECT_FALSE(Rel.IsInstanceMember);
+ Rel.merge(CodeCompletionResult(Bar, /*Priority=*/0));
+ EXPECT_TRUE(Rel.IsInstanceMember);
+ Rel.IsInstanceMember = false;
+ Rel.merge(CodeCompletionResult(Tpl, /*Priority=*/0));
+ EXPECT_TRUE(Rel.IsInstanceMember);
+}
+
+TEST(QualityTests, ConstructorQuality) {
+ auto Header = TestTU::withHeaderCode(R"cpp(
+ class Foo {
+ public:
+ Foo(int);
+ };
+ )cpp");
+ auto Symbols = Header.headerSymbols();
+ auto AST = Header.build();
+
+ const NamedDecl *CtorDecl = &findAnyDecl(AST, [](const NamedDecl &ND) {
+ return (ND.getQualifiedNameAsString() == "Foo::Foo") &&
+ llvm::isa<CXXConstructorDecl>(&ND);
+ });
+
+ SymbolQualitySignals Q;
+ Q.merge(CodeCompletionResult(CtorDecl, /*Priority=*/0));
+ EXPECT_EQ(Q.Category, SymbolQualitySignals::Constructor);
+
+ Q.Category = SymbolQualitySignals::Unknown;
+ const Symbol &CtorSym = findSymbol(Symbols, "Foo::Foo");
+ Q.merge(CtorSym);
+ EXPECT_EQ(Q.Category, SymbolQualitySignals::Constructor);
+}
+
+TEST(QualityTests, ItemWithFixItsRankedDown) {
+ CodeCompleteOptions Opts;
+ Opts.IncludeFixIts = true;
+
+ auto Header = TestTU::withHeaderCode(R"cpp(
+ int x;
+ )cpp");
+ auto AST = Header.build();
+
+ SymbolRelevanceSignals RelevanceWithFixIt;
+ RelevanceWithFixIt.merge(CodeCompletionResult(&findDecl(AST, "x"), 0, nullptr,
+ false, true, {FixItHint{}}));
+ EXPECT_TRUE(RelevanceWithFixIt.NeedsFixIts);
+
+ SymbolRelevanceSignals RelevanceWithoutFixIt;
+ RelevanceWithoutFixIt.merge(
+ CodeCompletionResult(&findDecl(AST, "x"), 0, nullptr, false, true, {}));
+ EXPECT_FALSE(RelevanceWithoutFixIt.NeedsFixIts);
+
+ EXPECT_LT(RelevanceWithFixIt.evaluate(), RelevanceWithoutFixIt.evaluate());
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/unittests/clangd/SymbolCollectorTests.cpp b/unittests/clangd/SymbolCollectorTests.cpp
index 06a087cb..666d0bb0 100644
--- a/unittests/clangd/SymbolCollectorTests.cpp
+++ b/unittests/clangd/SymbolCollectorTests.cpp
@@ -9,6 +9,7 @@
#include "Annotations.h"
#include "TestFS.h"
+#include "TestTU.h"
#include "index/SymbolCollector.h"
#include "index/SymbolYAML.h"
#include "clang/Basic/FileManager.h"
@@ -35,15 +36,18 @@ using testing::UnorderedElementsAre;
using testing::UnorderedElementsAreArray;
// GMock helpers for matching Symbol.
-MATCHER_P(Labeled, Label, "") { return arg.CompletionLabel == Label; }
-MATCHER(HasDetail, "") { return arg.Detail; }
-MATCHER_P(Detail, D, "") {
- return arg.Detail && arg.Detail->CompletionDetail == D;
+MATCHER_P(Labeled, Label, "") {
+ return (arg.Name + arg.Signature).str() == Label;
+}
+MATCHER(HasReturnType, "") {
+ return arg.Detail && !arg.Detail->ReturnType.empty();
+}
+MATCHER_P(ReturnType, D, "") {
+ return arg.Detail && arg.Detail->ReturnType == D;
}
MATCHER_P(Doc, D, "") { return arg.Detail && arg.Detail->Documentation == D; }
-MATCHER_P(Plain, Text, "") { return arg.CompletionPlainInsertText == Text; }
MATCHER_P(Snippet, S, "") {
- return arg.CompletionSnippetInsertText == S;
+ return (arg.Name + arg.CompletionSnippetSuffix).str() == S;
}
MATCHER_P(QName, Name, "") { return (arg.Scope + arg.Name).str() == Name; }
MATCHER_P(DeclURI, P, "") { return arg.CanonicalDeclaration.FileURI == P; }
@@ -67,11 +71,94 @@ MATCHER_P(DefRange, Pos, "") {
Pos.end.character);
}
MATCHER_P(Refs, R, "") { return int(arg.References) == R; }
+MATCHER_P(ForCodeCompletion, IsIndexedForCodeCompletion, "") {
+ return arg.IsIndexedForCodeCompletion == IsIndexedForCodeCompletion;
+}
namespace clang {
namespace clangd {
namespace {
+
+class ShouldCollectSymbolTest : public ::testing::Test {
+public:
+ void build(StringRef HeaderCode, StringRef Code = "") {
+ File.HeaderFilename = HeaderName;
+ File.Filename = FileName;
+ File.HeaderCode = HeaderCode;
+ File.Code = Code;
+ AST = File.build();
+ }
+
+ // build() must have been called.
+ bool shouldCollect(StringRef Name, bool Qualified = true) {
+ assert(AST.hasValue());
+ return SymbolCollector::shouldCollectSymbol(
+ Qualified ? findDecl(*AST, Name) : findAnyDecl(*AST, Name),
+ AST->getASTContext(), SymbolCollector::Options());
+ }
+
+protected:
+ std::string HeaderName = "f.h";
+ std::string FileName = "f.cpp";
+ TestTU File;
+ Optional<ParsedAST> AST; // Initialized after build.
+};
+
+TEST_F(ShouldCollectSymbolTest, ShouldCollectSymbol) {
+ build(R"(
+ namespace nx {
+ class X{}
+ void f() { int Local; }
+ struct { int x } var;
+ namespace { class InAnonymous {}; }
+ }
+ )",
+ "class InMain {};");
+ auto AST = File.build();
+ EXPECT_TRUE(shouldCollect("nx"));
+ EXPECT_TRUE(shouldCollect("nx::X"));
+ EXPECT_TRUE(shouldCollect("nx::f"));
+
+ EXPECT_FALSE(shouldCollect("InMain"));
+ EXPECT_FALSE(shouldCollect("Local", /*Qualified=*/false));
+ EXPECT_FALSE(shouldCollect("InAnonymous", /*Qualified=*/false));
+}
+
+TEST_F(ShouldCollectSymbolTest, NoPrivateProtoSymbol) {
+ HeaderName = "f.proto.h";
+ build(
+ R"(// Generated by the protocol buffer compiler. DO NOT EDIT!
+ namespace nx {
+ class Top_Level {};
+ class TopLevel {};
+ enum Kind {
+ KIND_OK,
+ Kind_Not_Ok,
+ };
+ })");
+ EXPECT_TRUE(shouldCollect("nx::TopLevel"));
+ EXPECT_TRUE(shouldCollect("nx::Kind::KIND_OK"));
+ EXPECT_TRUE(shouldCollect("nx::Kind"));
+
+ EXPECT_FALSE(shouldCollect("nx::Top_Level"));
+ EXPECT_FALSE(shouldCollect("nx::Kind::Kind_Not_Ok"));
+}
+
+TEST_F(ShouldCollectSymbolTest, DoubleCheckProtoHeaderComment) {
+ HeaderName = "f.proto.h";
+ build(R"(
+ namespace nx {
+ class Top_Level {};
+ enum Kind {
+ Kind_Fine
+ };
+ }
+ )");
+ EXPECT_TRUE(shouldCollect("nx::Top_Level"));
+ EXPECT_TRUE(shouldCollect("nx::Kind_Fine"));
+}
+
class SymbolIndexActionFactory : public tooling::FrontendActionFactory {
public:
SymbolIndexActionFactory(SymbolCollector::Options COpts,
@@ -132,9 +219,13 @@ public:
CollectorOpts, PragmaHandler.get());
std::vector<std::string> Args = {
- "symbol_collector", "-fsyntax-only", "-xc++", "-std=c++11",
- "-include", TestHeaderName, TestFileName};
+ "symbol_collector", "-fsyntax-only", "-xc++",
+ "-std=c++11", "-include", TestHeaderName};
Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end());
+ // This allows to override the "-xc++" with something else, i.e.
+ // -xobjective-c++.
+ Args.push_back(TestFileName);
+
tooling::ToolInvocation Invocation(
Args,
Factory->create(), Files.get(),
@@ -163,8 +254,20 @@ protected:
TEST_F(SymbolCollectorTest, CollectSymbols) {
const std::string Header = R"(
class Foo {
+ Foo() {}
+ Foo(int a) {}
+ void f();
+ friend void f1();
+ friend class Friend;
+ Foo& operator=(const Foo&);
+ ~Foo();
+ class Nested {
void f();
+ };
};
+ class Friend {
+ };
+
void f1();
inline void f2() {}
static const int KInt = 2;
@@ -200,23 +303,78 @@ TEST_F(SymbolCollectorTest, CollectSymbols) {
runSymbolCollector(Header, /*Main=*/"");
EXPECT_THAT(Symbols,
UnorderedElementsAreArray(
- {QName("Foo"), QName("f1"), QName("f2"), QName("KInt"),
- QName("kStr"), QName("foo"), QName("foo::bar"),
- QName("foo::int32"), QName("foo::int32_t"), QName("foo::v1"),
- QName("foo::bar::v2"), QName("foo::baz")}));
+ {AllOf(QName("Foo"), ForCodeCompletion(true)),
+ AllOf(QName("Foo::Foo"), ForCodeCompletion(false)),
+ AllOf(QName("Foo::Foo"), ForCodeCompletion(false)),
+ AllOf(QName("Foo::f"), ForCodeCompletion(false)),
+ AllOf(QName("Foo::~Foo"), ForCodeCompletion(false)),
+ AllOf(QName("Foo::operator="), ForCodeCompletion(false)),
+ AllOf(QName("Foo::Nested"), ForCodeCompletion(false)),
+ AllOf(QName("Foo::Nested::f"), ForCodeCompletion(false)),
+
+ AllOf(QName("Friend"), ForCodeCompletion(true)),
+ AllOf(QName("f1"), ForCodeCompletion(true)),
+ AllOf(QName("f2"), ForCodeCompletion(true)),
+ AllOf(QName("KInt"), ForCodeCompletion(true)),
+ AllOf(QName("kStr"), ForCodeCompletion(true)),
+ AllOf(QName("foo"), ForCodeCompletion(true)),
+ AllOf(QName("foo::bar"), ForCodeCompletion(true)),
+ AllOf(QName("foo::int32"), ForCodeCompletion(true)),
+ AllOf(QName("foo::int32_t"), ForCodeCompletion(true)),
+ AllOf(QName("foo::v1"), ForCodeCompletion(true)),
+ AllOf(QName("foo::bar::v2"), ForCodeCompletion(true)),
+ AllOf(QName("foo::baz"), ForCodeCompletion(true))}));
}
TEST_F(SymbolCollectorTest, Template) {
Annotations Header(R"(
// Template is indexed, specialization and instantiation is not.
- template <class T> struct [[Tmpl]] {T x = 0;};
+ template <class T> struct [[Tmpl]] {T $xdecl[[x]] = 0;};
template <> struct Tmpl<int> {};
extern template struct Tmpl<float>;
template struct Tmpl<double>;
)");
runSymbolCollector(Header.code(), /*Main=*/"");
- EXPECT_THAT(Symbols, UnorderedElementsAreArray({AllOf(
- QName("Tmpl"), DeclRange(Header.range()))}));
+ EXPECT_THAT(Symbols,
+ UnorderedElementsAreArray(
+ {AllOf(QName("Tmpl"), DeclRange(Header.range())),
+ AllOf(QName("Tmpl::x"), DeclRange(Header.range("xdecl")))}));
+}
+
+TEST_F(SymbolCollectorTest, ObjCSymbols) {
+ const std::string Header = R"(
+ @interface Person
+ - (void)someMethodName:(void*)name1 lastName:(void*)lName;
+ @end
+
+ @implementation Person
+ - (void)someMethodName:(void*)name1 lastName:(void*)lName{
+ int foo;
+ ^(int param){ int bar; };
+ }
+ @end
+
+ @interface Person (MyCategory)
+ - (void)someMethodName2:(void*)name2;
+ @end
+
+ @implementation Person (MyCategory)
+ - (void)someMethodName2:(void*)name2 {
+ int foo2;
+ }
+ @end
+
+ @protocol MyProtocol
+ - (void)someMethodName3:(void*)name3;
+ @end
+ )";
+ TestFileName = "test.m";
+ runSymbolCollector(Header, /*Main=*/"", {"-fblocks", "-xobjective-c++"});
+ EXPECT_THAT(Symbols,
+ UnorderedElementsAre(
+ QName("Person"), QName("Person::someMethodName:lastName:"),
+ QName("MyCategory"), QName("Person::someMethodName2:"),
+ QName("MyProtocol"), QName("MyProtocol::someMethodName3:")));
}
TEST_F(SymbolCollectorTest, Locations) {
@@ -298,17 +456,15 @@ TEST_F(SymbolCollectorTest, SymbolRelativeWithFallback) {
UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI))));
}
-#ifndef _WIN32
TEST_F(SymbolCollectorTest, CustomURIScheme) {
// Use test URI scheme from URITests.cpp
CollectorOpts.URISchemes.insert(CollectorOpts.URISchemes.begin(), "unittest");
- TestHeaderName = testPath("test-root/x.h");
- TestFileName = testPath("test-root/x.cpp");
+ TestHeaderName = testPath("x.h");
+ TestFileName = testPath("x.cpp");
runSymbolCollector("class Foo {};", /*Main=*/"");
- EXPECT_THAT(Symbols,
- UnorderedElementsAre(AllOf(QName("Foo"), DeclURI("unittest:x.h"))));
+ EXPECT_THAT(Symbols, UnorderedElementsAre(
+ AllOf(QName("Foo"), DeclURI("unittest:///x.h"))));
}
-#endif
TEST_F(SymbolCollectorTest, InvalidURIScheme) {
// Use test URI scheme from URITests.cpp
@@ -334,7 +490,7 @@ TEST_F(SymbolCollectorTest, IncludeEnums) {
Green
};
enum class Color2 {
- Yellow // ignore
+ Yellow
};
namespace ns {
enum {
@@ -343,20 +499,26 @@ TEST_F(SymbolCollectorTest, IncludeEnums) {
}
)";
runSymbolCollector(Header, /*Main=*/"");
- EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Red"), QName("Color"),
- QName("Green"), QName("Color2"),
- QName("ns"), QName("ns::Black")));
+ EXPECT_THAT(Symbols,
+ UnorderedElementsAre(
+ AllOf(QName("Red"), ForCodeCompletion(true)),
+ AllOf(QName("Color"), ForCodeCompletion(true)),
+ AllOf(QName("Green"), ForCodeCompletion(true)),
+ AllOf(QName("Color2"), ForCodeCompletion(true)),
+ AllOf(QName("Color2::Yellow"), ForCodeCompletion(false)),
+ AllOf(QName("ns"), ForCodeCompletion(true)),
+ AllOf(QName("ns::Black"), ForCodeCompletion(true))));
}
-TEST_F(SymbolCollectorTest, IgnoreNamelessSymbols) {
+TEST_F(SymbolCollectorTest, NamelessSymbols) {
const std::string Header = R"(
struct {
int a;
} Foo;
)";
runSymbolCollector(Header, /*Main=*/"");
- EXPECT_THAT(Symbols,
- UnorderedElementsAre(QName("Foo")));
+ EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"),
+ QName("(anonymous struct)::a")));
}
TEST_F(SymbolCollectorTest, SymbolFormedFromMacro) {
@@ -417,7 +579,7 @@ TEST_F(SymbolCollectorTest, IgnoreSymbolsInMainFile) {
UnorderedElementsAre(QName("Foo"), QName("f1"), QName("f2")));
}
-TEST_F(SymbolCollectorTest, IgnoreClassMembers) {
+TEST_F(SymbolCollectorTest, ClassMembers) {
const std::string Header = R"(
class Foo {
void f() {}
@@ -432,7 +594,10 @@ TEST_F(SymbolCollectorTest, IgnoreClassMembers) {
void Foo::ssf() {}
)";
runSymbolCollector(Header, Main);
- EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo")));
+ EXPECT_THAT(Symbols,
+ UnorderedElementsAre(QName("Foo"), QName("Foo::f"),
+ QName("Foo::g"), QName("Foo::sf"),
+ QName("Foo::ssf"), QName("Foo::x")));
}
TEST_F(SymbolCollectorTest, Scopes) {
@@ -494,10 +659,10 @@ TEST_F(SymbolCollectorTest, SymbolWithDocumentation) {
Symbols,
UnorderedElementsAre(
QName("nx"), AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"),
- Detail("int"), Doc("Foo comment."))));
+ ReturnType("int"), Doc("Foo comment."))));
}
-TEST_F(SymbolCollectorTest, PlainAndSnippet) {
+TEST_F(SymbolCollectorTest, Snippet) {
const std::string Header = R"(
namespace nx {
void f() {}
@@ -505,13 +670,12 @@ TEST_F(SymbolCollectorTest, PlainAndSnippet) {
}
)";
runSymbolCollector(Header, /*Main=*/"");
- EXPECT_THAT(
- Symbols,
- UnorderedElementsAre(
- QName("nx"),
- AllOf(QName("nx::f"), Labeled("f()"), Plain("f"), Snippet("f()")),
- AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"), Plain("ff"),
- Snippet("ff(${1:int x}, ${2:double y})"))));
+ EXPECT_THAT(Symbols,
+ UnorderedElementsAre(
+ QName("nx"),
+ AllOf(QName("nx::f"), Labeled("f()"), Snippet("f()")),
+ AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"),
+ Snippet("ff(${1:int x}, ${2:double y})"))));
}
TEST_F(SymbolCollectorTest, YAMLConversions) {
@@ -531,12 +695,10 @@ CanonicalDeclaration:
End:
Line: 1
Column: 1
-CompletionLabel: 'Foo1-label'
-CompletionFilterText: 'filter'
-CompletionPlainInsertText: 'plain'
+IsIndexedForCodeCompletion: true
Detail:
Documentation: 'Foo doc'
- CompletionDetail: 'int'
+ ReturnType: 'int'
...
)";
const std::string YAML2 = R"(
@@ -555,23 +717,24 @@ CanonicalDeclaration:
End:
Line: 1
Column: 1
-CompletionLabel: 'Foo2-label'
-CompletionFilterText: 'filter'
-CompletionPlainInsertText: 'plain'
-CompletionSnippetInsertText: 'snippet'
+IsIndexedForCodeCompletion: false
+Signature: '-sig'
+CompletionSnippetSuffix: '-snippet'
...
)";
- auto Symbols1 = SymbolsFromYAML(YAML1);
+ auto Symbols1 = symbolsFromYAML(YAML1);
EXPECT_THAT(Symbols1,
- UnorderedElementsAre(AllOf(
- QName("clang::Foo1"), Labeled("Foo1-label"), Doc("Foo doc"),
- Detail("int"), DeclURI("file:///path/foo.h"))));
- auto Symbols2 = SymbolsFromYAML(YAML2);
+ UnorderedElementsAre(AllOf(QName("clang::Foo1"), Labeled("Foo1"),
+ Doc("Foo doc"), ReturnType("int"),
+ DeclURI("file:///path/foo.h"),
+ ForCodeCompletion(true))));
+ auto Symbols2 = symbolsFromYAML(YAML2);
EXPECT_THAT(Symbols2, UnorderedElementsAre(AllOf(
- QName("clang::Foo2"), Labeled("Foo2-label"),
- Not(HasDetail()), DeclURI("file:///path/bar.h"))));
+ QName("clang::Foo2"), Labeled("Foo2-sig"),
+ Not(HasReturnType()), DeclURI("file:///path/bar.h"),
+ ForCodeCompletion(false))));
std::string ConcatenatedYAML;
{
@@ -579,7 +742,7 @@ CompletionSnippetInsertText: 'snippet'
SymbolsToYAML(Symbols1, OS);
SymbolsToYAML(Symbols2, OS);
}
- auto ConcatenatedSymbols = SymbolsFromYAML(ConcatenatedYAML);
+ auto ConcatenatedSymbols = symbolsFromYAML(ConcatenatedYAML);
EXPECT_THAT(ConcatenatedSymbols,
UnorderedElementsAre(QName("clang::Foo1"),
QName("clang::Foo2")));
@@ -741,23 +904,27 @@ TEST_F(SymbolCollectorTest, AvoidUsingFwdDeclsAsCanonicalDecls) {
// Canonical declarations.
class $cdecl[[C]] {};
struct $sdecl[[S]] {};
- union $udecl[[U]] {int x; bool y;};
+ union $udecl[[U]] {int $xdecl[[x]]; bool $ydecl[[y]];};
)");
runSymbolCollector(Header.code(), /*Main=*/"");
- EXPECT_THAT(Symbols,
- UnorderedElementsAre(
- AllOf(QName("C"), DeclURI(TestHeaderURI),
- DeclRange(Header.range("cdecl")),
- IncludeHeader(TestHeaderURI), DefURI(TestHeaderURI),
- DefRange(Header.range("cdecl"))),
- AllOf(QName("S"), DeclURI(TestHeaderURI),
- DeclRange(Header.range("sdecl")),
- IncludeHeader(TestHeaderURI), DefURI(TestHeaderURI),
- DefRange(Header.range("sdecl"))),
- AllOf(QName("U"), DeclURI(TestHeaderURI),
- DeclRange(Header.range("udecl")),
- IncludeHeader(TestHeaderURI), DefURI(TestHeaderURI),
- DefRange(Header.range("udecl")))));
+ EXPECT_THAT(
+ Symbols,
+ UnorderedElementsAre(
+ AllOf(QName("C"), DeclURI(TestHeaderURI),
+ DeclRange(Header.range("cdecl")), IncludeHeader(TestHeaderURI),
+ DefURI(TestHeaderURI), DefRange(Header.range("cdecl"))),
+ AllOf(QName("S"), DeclURI(TestHeaderURI),
+ DeclRange(Header.range("sdecl")), IncludeHeader(TestHeaderURI),
+ DefURI(TestHeaderURI), DefRange(Header.range("sdecl"))),
+ AllOf(QName("U"), DeclURI(TestHeaderURI),
+ DeclRange(Header.range("udecl")), IncludeHeader(TestHeaderURI),
+ DefURI(TestHeaderURI), DefRange(Header.range("udecl"))),
+ AllOf(QName("U::x"), DeclURI(TestHeaderURI),
+ DeclRange(Header.range("xdecl")), DefURI(TestHeaderURI),
+ DefRange(Header.range("xdecl"))),
+ AllOf(QName("U::y"), DeclURI(TestHeaderURI),
+ DeclRange(Header.range("ydecl")), DefURI(TestHeaderURI),
+ DefRange(Header.range("ydecl")))));
}
TEST_F(SymbolCollectorTest, ClassForwardDeclarationIsCanonical) {
@@ -776,40 +943,79 @@ TEST_F(SymbolCollectorTest, UTF16Character) {
AllOf(QName("pörk"), DeclRange(Header.range()))));
}
-TEST_F(SymbolCollectorTest, FilterPrivateProtoSymbols) {
- TestHeaderName = testPath("x.proto.h");
- const std::string Header =
- R"(// Generated by the protocol buffer compiler. DO NOT EDIT!
- namespace nx {
- class Top_Level {};
- class TopLevel {};
- enum Kind {
- KIND_OK,
- Kind_Not_Ok,
- };
- bool operator<(const TopLevel &, const TopLevel &);
- })";
- runSymbolCollector(Header, /*Main=*/"");
+TEST_F(SymbolCollectorTest, DoNotIndexSymbolsInFriendDecl) {
+ Annotations Header(R"(
+ namespace nx {
+ class $z[[Z]] {};
+ class X {
+ friend class Y;
+ friend class Z;
+ friend void foo();
+ friend void $bar[[bar]]() {}
+ };
+ class $y[[Y]] {};
+ void $foo[[foo]]();
+ }
+ )");
+ runSymbolCollector(Header.code(), /*Main=*/"");
+
EXPECT_THAT(Symbols,
- UnorderedElementsAre(QName("nx"), QName("nx::TopLevel"),
- QName("nx::Kind"), QName("nx::KIND_OK"),
- QName("nx::operator<")));
+ UnorderedElementsAre(
+ QName("nx"), QName("nx::X"),
+ AllOf(QName("nx::Y"), DeclRange(Header.range("y"))),
+ AllOf(QName("nx::Z"), DeclRange(Header.range("z"))),
+ AllOf(QName("nx::foo"), DeclRange(Header.range("foo"))),
+ AllOf(QName("nx::bar"), DeclRange(Header.range("bar")))));
}
-TEST_F(SymbolCollectorTest, DoubleCheckProtoHeaderComment) {
- TestHeaderName = testPath("x.proto.h");
+TEST_F(SymbolCollectorTest, ReferencesInFriendDecl) {
const std::string Header = R"(
- namespace nx {
- class Top_Level {};
- enum Kind {
- Kind_Fine
+ class X;
+ class Y;
+ )";
+ const std::string Main = R"(
+ class C {
+ friend ::X;
+ friend class Y;
};
- }
)";
- runSymbolCollector(Header, /*Main=*/"");
- EXPECT_THAT(Symbols,
- UnorderedElementsAre(QName("nx"), QName("nx::Top_Level"),
- QName("nx::Kind"), QName("nx::Kind_Fine")));
+ CollectorOpts.CountReferences = true;
+ runSymbolCollector(Header, Main);
+ EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), Refs(1)),
+ AllOf(QName("Y"), Refs(1))));
+}
+
+TEST_F(SymbolCollectorTest, Origin) {
+ CollectorOpts.Origin = SymbolOrigin::Static;
+ runSymbolCollector("class Foo {};", /*Main=*/"");
+ EXPECT_THAT(Symbols, UnorderedElementsAre(
+ Field(&Symbol::Origin, SymbolOrigin::Static)));
+}
+
+TEST_F(SymbolCollectorTest, CollectMacros) {
+ CollectorOpts.CollectIncludePath = true;
+ Annotations Header(R"(
+ #define X 1
+ #define $mac[[MAC]](x) int x
+ #define $used[[USED]](y) float y;
+
+ MAC(p);
+ )");
+ const std::string Main = R"(
+ #define MAIN 1 // not indexed
+ USED(t);
+ )";
+ CollectorOpts.CountReferences = true;
+ CollectorOpts.CollectMacro = true;
+ runSymbolCollector(Header.code(), Main);
+ EXPECT_THAT(
+ Symbols,
+ UnorderedElementsAre(
+ QName("p"),
+ AllOf(QName("X"), DeclURI(TestHeaderURI),
+ IncludeHeader(TestHeaderURI)),
+ AllOf(Labeled("MAC(x)"), Refs(0), DeclRange(Header.range("mac"))),
+ AllOf(Labeled("USED(y)"), Refs(1), DeclRange(Header.range("used")))));
}
} // namespace
diff --git a/unittests/clangd/SyncAPI.cpp b/unittests/clangd/SyncAPI.cpp
index 0a1c5988..ae9ece59 100644
--- a/unittests/clangd/SyncAPI.cpp
+++ b/unittests/clangd/SyncAPI.cpp
@@ -12,8 +12,8 @@ namespace clang {
namespace clangd {
void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents,
- WantDiagnostics WantDiags, bool SkipCache) {
- Server.addDocument(File, Contents, WantDiags, SkipCache);
+ WantDiagnostics WantDiags) {
+ Server.addDocument(File, Contents, WantDiags);
if (!Server.blockUntilIdleForTest())
llvm_unreachable("not idle after addDocument");
}
@@ -36,7 +36,7 @@ template <typename T> struct CaptureProxy {
}
CaptureProxy &operator=(CaptureProxy &&) = delete;
- operator UniqueFunction<void(T)>() && {
+ operator llvm::unique_function<void(T)>() && {
assert(!Future.valid() && "conversion to callback called multiple times");
Future = Promise.get_future();
return Bind(
@@ -68,10 +68,10 @@ template <typename T> CaptureProxy<T> capture(llvm::Optional<T> &Target) {
}
} // namespace
-llvm::Expected<CompletionList>
+llvm::Expected<CodeCompleteResult>
runCodeComplete(ClangdServer &Server, PathRef File, Position Pos,
clangd::CodeCompleteOptions Opts) {
- llvm::Optional<llvm::Expected<CompletionList>> Result;
+ llvm::Optional<llvm::Expected<CodeCompleteResult>> Result;
Server.codeComplete(File, Pos, Opts, capture(Result));
return std::move(*Result);
}
@@ -117,5 +117,12 @@ runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit) {
return std::move(*Result);
}
+llvm::Expected<std::vector<SymbolInformation>>
+runDocumentSymbols(ClangdServer &Server, PathRef File) {
+ llvm::Optional<llvm::Expected<std::vector<SymbolInformation>>> Result;
+ Server.documentSymbols(File, capture(Result));
+ return std::move(*Result);
+}
+
} // namespace clangd
} // namespace clang
diff --git a/unittests/clangd/SyncAPI.h b/unittests/clangd/SyncAPI.h
index d4d2ac8f..ddf90d81 100644
--- a/unittests/clangd/SyncAPI.h
+++ b/unittests/clangd/SyncAPI.h
@@ -20,10 +20,9 @@ namespace clangd {
// Calls addDocument and then blockUntilIdleForTest.
void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents,
- WantDiagnostics WantDiags = WantDiagnostics::Auto,
- bool SkipCache = false);
+ WantDiagnostics WantDiags = WantDiagnostics::Auto);
-llvm::Expected<CompletionList>
+llvm::Expected<CodeCompleteResult>
runCodeComplete(ClangdServer &Server, PathRef File, Position Pos,
clangd::CodeCompleteOptions Opts);
@@ -44,6 +43,9 @@ std::string runDumpAST(ClangdServer &Server, PathRef File);
llvm::Expected<std::vector<SymbolInformation>>
runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit);
+llvm::Expected<std::vector<SymbolInformation>>
+runDocumentSymbols(ClangdServer &Server, PathRef File);
+
} // namespace clangd
} // namespace clang
diff --git a/unittests/clangd/TUSchedulerTests.cpp b/unittests/clangd/TUSchedulerTests.cpp
index fbf623fb..f485b0b8 100644
--- a/unittests/clangd/TUSchedulerTests.cpp
+++ b/unittests/clangd/TUSchedulerTests.cpp
@@ -19,6 +19,7 @@ namespace clang {
namespace clangd {
using ::testing::_;
+using ::testing::Each;
using ::testing::AnyOf;
using ::testing::Pair;
using ::testing::Pointee;
@@ -32,13 +33,12 @@ void ignoreError(llvm::Error Err) {
class TUSchedulerTests : public ::testing::Test {
protected:
ParseInputs getInputs(PathRef File, std::string Contents) {
- return ParseInputs{*CDB.getCompileCommand(File), buildTestFS(Files),
- std::move(Contents)};
+ return ParseInputs{*CDB.getCompileCommand(File),
+ buildTestFS(Files, Timestamps), std::move(Contents)};
}
llvm::StringMap<std::string> Files;
-
-private:
+ llvm::StringMap<time_t> Timestamps;
MockCompilationDatabase CDB;
};
@@ -197,20 +197,22 @@ TEST_F(TUSchedulerTests, ManyUpdates) {
{
WithContextValue WithNonce(NonceKey, ++Nonce);
S.update(File, Inputs, WantDiagnostics::Auto,
- [Nonce, &Mut,
+ [File, Nonce, &Mut,
&TotalUpdates](llvm::Optional<std::vector<Diag>> Diags) {
EXPECT_THAT(Context::current().get(NonceKey),
Pointee(Nonce));
std::lock_guard<std::mutex> Lock(Mut);
++TotalUpdates;
+ EXPECT_EQ(File,
+ *TUScheduler::getFileBeingProcessedInContext());
});
}
{
WithContextValue WithNonce(NonceKey, ++Nonce);
S.runWithAST("CheckAST", File,
- [Inputs, Nonce, &Mut,
+ [File, Inputs, Nonce, &Mut,
&TotalASTReads](llvm::Expected<InputsAndAST> AST) {
EXPECT_THAT(Context::current().get(NonceKey),
Pointee(Nonce));
@@ -221,23 +223,27 @@ TEST_F(TUSchedulerTests, ManyUpdates) {
std::lock_guard<std::mutex> Lock(Mut);
++TotalASTReads;
+ EXPECT_EQ(
+ File,
+ *TUScheduler::getFileBeingProcessedInContext());
});
}
{
WithContextValue WithNonce(NonceKey, ++Nonce);
- S.runWithPreamble("CheckPreamble", File,
- [Inputs, Nonce, &Mut, &TotalPreambleReads](
- llvm::Expected<InputsAndPreamble> Preamble) {
- EXPECT_THAT(Context::current().get(NonceKey),
- Pointee(Nonce));
-
- ASSERT_TRUE((bool)Preamble);
- EXPECT_EQ(Preamble->Contents, Inputs.Contents);
-
- std::lock_guard<std::mutex> Lock(Mut);
- ++TotalPreambleReads;
- });
+ S.runWithPreamble(
+ "CheckPreamble", File,
+ [File, Inputs, Nonce, &Mut, &TotalPreambleReads](
+ llvm::Expected<InputsAndPreamble> Preamble) {
+ EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce));
+
+ ASSERT_TRUE((bool)Preamble);
+ EXPECT_EQ(Preamble->Contents, Inputs.Contents);
+
+ std::lock_guard<std::mutex> Lock(Mut);
+ ++TotalPreambleReads;
+ EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext());
+ });
}
}
}
@@ -250,6 +256,7 @@ TEST_F(TUSchedulerTests, ManyUpdates) {
}
TEST_F(TUSchedulerTests, EvictedAST) {
+ std::atomic<int> BuiltASTCounter(0);
ASTRetentionPolicy Policy;
Policy.MaxRetainedASTs = 2;
TUScheduler S(
@@ -261,13 +268,15 @@ TEST_F(TUSchedulerTests, EvictedAST) {
int* a;
double* b = a;
)cpp";
+ llvm::StringLiteral OtherSourceContents = R"cpp(
+ int* a;
+ double* b = a + 0;
+ )cpp";
auto Foo = testPath("foo.cpp");
auto Bar = testPath("bar.cpp");
auto Baz = testPath("baz.cpp");
- std::atomic<int> BuiltASTCounter;
- BuiltASTCounter = false;
// Build one file in advance. We will not access it later, so it will be the
// one that the cache will evict.
S.update(Foo, getInputs(Foo, SourceContents), WantDiagnostics::Yes,
@@ -288,7 +297,7 @@ TEST_F(TUSchedulerTests, EvictedAST) {
ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz));
// Access the old file again.
- S.update(Foo, getInputs(Foo, SourceContents), WantDiagnostics::Yes,
+ S.update(Foo, getInputs(Foo, OtherSourceContents), WantDiagnostics::Yes,
[&BuiltASTCounter](std::vector<Diag> Diags) { ++BuiltASTCounter; });
ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1)));
ASSERT_EQ(BuiltASTCounter.load(), 4);
@@ -299,5 +308,127 @@ TEST_F(TUSchedulerTests, EvictedAST) {
UnorderedElementsAre(Foo, AnyOf(Bar, Baz)));
}
+TEST_F(TUSchedulerTests, RunWaitsForPreamble) {
+ // Testing strategy: we update the file and schedule a few preamble reads at
+ // the same time. All reads should get the same non-null preamble.
+ TUScheduler S(
+ /*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true,
+ PreambleParsedCallback(),
+ /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
+ ASTRetentionPolicy());
+ auto Foo = testPath("foo.cpp");
+ auto NonEmptyPreamble = R"cpp(
+ #define FOO 1
+ #define BAR 2
+
+ int main() {}
+ )cpp";
+ constexpr int ReadsToSchedule = 10;
+ std::mutex PreamblesMut;
+ std::vector<const void *> Preambles(ReadsToSchedule, nullptr);
+ S.update(Foo, getInputs(Foo, NonEmptyPreamble), WantDiagnostics::Auto,
+ [](std::vector<Diag>) {});
+ for (int I = 0; I < ReadsToSchedule; ++I) {
+ S.runWithPreamble(
+ "test", Foo,
+ [I, &PreamblesMut, &Preambles](llvm::Expected<InputsAndPreamble> IP) {
+ std::lock_guard<std::mutex> Lock(PreamblesMut);
+ Preambles[I] = cantFail(std::move(IP)).Preamble;
+ });
+ }
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
+ // Check all actions got the same non-null preamble.
+ std::lock_guard<std::mutex> Lock(PreamblesMut);
+ ASSERT_NE(Preambles[0], nullptr);
+ ASSERT_THAT(Preambles, Each(Preambles[0]));
+}
+
+TEST_F(TUSchedulerTests, NoopOnEmptyChanges) {
+ TUScheduler S(
+ /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
+ /*StorePreambleInMemory=*/true, PreambleParsedCallback(),
+ /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
+ ASTRetentionPolicy());
+
+ auto Source = testPath("foo.cpp");
+ auto Header = testPath("foo.h");
+
+ Files[Header] = "int a;";
+ Timestamps[Header] = time_t(0);
+
+ auto SourceContents = R"cpp(
+ #include "foo.h"
+ int b = a;
+ )cpp";
+
+ // Return value indicates if the updated callback was received.
+ auto DoUpdate = [&](ParseInputs Inputs) -> bool {
+ std::atomic<bool> Updated(false);
+ Updated = false;
+ S.update(Source, std::move(Inputs), WantDiagnostics::Yes,
+ [&Updated](std::vector<Diag>) { Updated = true; });
+ bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(1));
+ if (!UpdateFinished)
+ ADD_FAILURE() << "Updated has not finished in one second. Threading bug?";
+ return Updated;
+ };
+
+ // Test that subsequent updates with the same inputs do not cause rebuilds.
+ ASSERT_TRUE(DoUpdate(getInputs(Source, SourceContents)));
+ ASSERT_FALSE(DoUpdate(getInputs(Source, SourceContents)));
+
+ // Update to a header should cause a rebuild, though.
+ Files[Header] = time_t(1);
+ ASSERT_TRUE(DoUpdate(getInputs(Source, SourceContents)));
+ ASSERT_FALSE(DoUpdate(getInputs(Source, SourceContents)));
+
+ // Update to the contents should cause a rebuild.
+ auto OtherSourceContents = R"cpp(
+ #include "foo.h"
+ int c = d;
+ )cpp";
+ ASSERT_TRUE(DoUpdate(getInputs(Source, OtherSourceContents)));
+ ASSERT_FALSE(DoUpdate(getInputs(Source, OtherSourceContents)));
+
+ // Update to the compile commands should also cause a rebuild.
+ CDB.ExtraClangFlags.push_back("-DSOMETHING");
+ ASSERT_TRUE(DoUpdate(getInputs(Source, OtherSourceContents)));
+ ASSERT_FALSE(DoUpdate(getInputs(Source, OtherSourceContents)));
+}
+
+TEST_F(TUSchedulerTests, NoChangeDiags) {
+ TUScheduler S(
+ /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(),
+ /*StorePreambleInMemory=*/true, PreambleParsedCallback(),
+ /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(),
+ ASTRetentionPolicy());
+
+ auto FooCpp = testPath("foo.cpp");
+ auto Contents = "int a; int b;";
+
+ S.update(FooCpp, getInputs(FooCpp, Contents), WantDiagnostics::No,
+ [](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
+ S.runWithAST("touchAST", FooCpp, [](llvm::Expected<InputsAndAST> IA) {
+ // Make sure the AST was actually built.
+ cantFail(std::move(IA));
+ });
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1)));
+
+ // Even though the inputs didn't change and AST can be reused, we need to
+ // report the diagnostics, as they were not reported previously.
+ std::atomic<bool> SeenDiags(false);
+ S.update(FooCpp, getInputs(FooCpp, Contents), WantDiagnostics::Auto,
+ [&](std::vector<Diag>) { SeenDiags = true; });
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1)));
+ ASSERT_TRUE(SeenDiags);
+
+ // Subsequent request does not get any diagnostics callback because the same
+ // diags have previously been reported and the inputs didn't change.
+ S.update(
+ FooCpp, getInputs(FooCpp, Contents), WantDiagnostics::Auto,
+ [&](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; });
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(1)));
+}
+
} // namespace clangd
} // namespace clang
diff --git a/unittests/clangd/TestFS.cpp b/unittests/clangd/TestFS.cpp
index 9020c6da..b3081d64 100644
--- a/unittests/clangd/TestFS.cpp
+++ b/unittests/clangd/TestFS.cpp
@@ -7,7 +7,11 @@
//
//===----------------------------------------------------------------------===//
#include "TestFS.h"
+#include "URI.h"
+#include "clang/AST/DeclCXX.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Errc.h"
+#include "llvm/Support/Path.h"
#include "gtest/gtest.h"
namespace clang {
@@ -15,13 +19,15 @@ namespace clangd {
using namespace llvm;
IntrusiveRefCntPtr<vfs::FileSystem>
-buildTestFS(StringMap<std::string> const &Files) {
+buildTestFS(llvm::StringMap<std::string> const &Files,
+ llvm::StringMap<time_t> const &Timestamps) {
IntrusiveRefCntPtr<vfs::InMemoryFileSystem> MemFS(
new vfs::InMemoryFileSystem);
for (auto &FileAndContents : Files) {
- MemFS->addFile(FileAndContents.first(), time_t(),
- MemoryBuffer::getMemBufferCopy(FileAndContents.second,
- FileAndContents.first()));
+ StringRef File = FileAndContents.first();
+ MemFS->addFile(
+ File, Timestamps.lookup(File),
+ MemoryBuffer::getMemBufferCopy(FileAndContents.second, File));
}
return MemFS;
}
@@ -62,5 +68,44 @@ std::string testPath(PathRef File) {
return Path.str();
}
+/// unittest: is a scheme that refers to files relative to testRoot().
+/// URI body is a path relative to testRoot() e.g. unittest:///x.h for
+/// /clangd-test/x.h.
+class TestScheme : public URIScheme {
+public:
+ static const char *Scheme;
+
+ llvm::Expected<std::string>
+ getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
+ llvm::StringRef HintPath) const override {
+ assert(HintPath.startswith(testRoot()));
+ if (!Body.consume_front("/"))
+ return llvm::make_error<llvm::StringError>(
+ "Body of an unittest: URI must start with '/'",
+ llvm::inconvertibleErrorCode());
+ llvm::SmallString<16> Path(Body.begin(), Body.end());
+ llvm::sys::path::native(Path);
+ return testPath(Path);
+ }
+
+ llvm::Expected<URI>
+ uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
+ llvm::StringRef Body = AbsolutePath;
+ if (!Body.consume_front(testRoot()))
+ return llvm::make_error<llvm::StringError>(
+ AbsolutePath + "does not start with " + testRoot(),
+ llvm::inconvertibleErrorCode());
+
+ return URI(Scheme, /*Authority=*/"",
+ llvm::sys::path::convert_to_slash(Body));
+ }
+};
+
+const char *TestScheme::Scheme = "unittest";
+
+static URISchemeRegistry::Add<TestScheme> X(TestScheme::Scheme, "Test schema");
+
+volatile int UnittestSchemeAnchorSource = 0;
+
} // namespace clangd
} // namespace clang
diff --git a/unittests/clangd/TestFS.h b/unittests/clangd/TestFS.h
index 06a1d8ac..9c2a15e6 100644
--- a/unittests/clangd/TestFS.h
+++ b/unittests/clangd/TestFS.h
@@ -23,7 +23,8 @@ namespace clangd {
// Builds a VFS that provides access to the provided files, plus temporary
// directories.
llvm::IntrusiveRefCntPtr<vfs::FileSystem>
-buildTestFS(llvm::StringMap<std::string> const &Files);
+buildTestFS(llvm::StringMap<std::string> const &Files,
+ llvm::StringMap<time_t> const &Timestamps = {});
// A VFS provider that returns TestFSes containing a provided set of files.
class MockFSProvider : public FileSystemProvider {
@@ -56,6 +57,11 @@ const char *testRoot();
// Returns a suitable absolute path for this OS.
std::string testPath(PathRef File);
+// unittest: is a scheme that refers to files relative to testRoot()
+// This anchor is used to force the linker to link in the generated object file
+// and thus register unittest: URI scheme plugin.
+extern volatile int UnittestSchemeAnchorSource;
+
} // namespace clangd
} // namespace clang
#endif
diff --git a/unittests/clangd/TestTU.cpp b/unittests/clangd/TestTU.cpp
index 259f36f8..b47d9448 100644
--- a/unittests/clangd/TestTU.cpp
+++ b/unittests/clangd/TestTU.cpp
@@ -10,6 +10,7 @@
#include "TestFS.h"
#include "index/FileIndex.h"
#include "index/MemIndex.h"
+#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/PCHContainerOperations.h"
#include "clang/Frontend/Utils.h"
@@ -28,7 +29,8 @@ ParsedAST TestTU::build() const {
Cmd.push_back("-include");
Cmd.push_back(FullHeaderName.c_str());
}
- auto AST = ParsedAST::Build(
+ Cmd.insert(Cmd.end(), ExtraArgs.begin(), ExtraArgs.end());
+ auto AST = ParsedAST::build(
createInvocationFromCommandLine(Cmd), nullptr,
MemoryBuffer::getMemBufferCopy(Code),
std::make_shared<PCHContainerOperations>(),
@@ -49,7 +51,6 @@ std::unique_ptr<SymbolIndex> TestTU::index() const {
return MemIndex::build(headerSymbols());
}
-// Look up a symbol by qualified name, which must be unique.
const Symbol &findSymbol(const SymbolSlab &Slab, llvm::StringRef QName) {
const Symbol *Result = nullptr;
for (const Symbol &S : Slab) {
@@ -92,5 +93,35 @@ const NamedDecl &findDecl(ParsedAST &AST, llvm::StringRef QName) {
return LookupDecl(*Scope, Components.back());
}
+const NamedDecl &findAnyDecl(ParsedAST &AST,
+ std::function<bool(const NamedDecl &)> Callback) {
+ struct Visitor : RecursiveASTVisitor<Visitor> {
+ decltype(Callback) CB;
+ llvm::SmallVector<const NamedDecl *, 1> Decls;
+ bool VisitNamedDecl(const NamedDecl *ND) {
+ if (CB(*ND))
+ Decls.push_back(ND);
+ return true;
+ }
+ } Visitor;
+ Visitor.CB = Callback;
+ for (Decl *D : AST.getLocalTopLevelDecls())
+ Visitor.TraverseDecl(D);
+ if (Visitor.Decls.size() != 1) {
+ ADD_FAILURE() << Visitor.Decls.size() << " symbols matched.";
+ assert(Visitor.Decls.size() == 1);
+ }
+ return *Visitor.Decls.front();
+}
+
+const NamedDecl &findAnyDecl(ParsedAST &AST, llvm::StringRef Name) {
+ return findAnyDecl(AST, [Name](const NamedDecl &ND) {
+ if (auto *ID = ND.getIdentifier())
+ if (ID->getName() == Name)
+ return true;
+ return false;
+ });
+}
+
} // namespace clangd
} // namespace clang
diff --git a/unittests/clangd/TestTU.h b/unittests/clangd/TestTU.h
index f89a7f30..b66159e3 100644
--- a/unittests/clangd/TestTU.h
+++ b/unittests/clangd/TestTU.h
@@ -40,10 +40,13 @@ struct TestTU {
std::string Code;
std::string Filename = "TestTU.cpp";
- // Define contents of a header to be included by TestTU.cpp.
+ // Define contents of a header which will be implicitly included by Code.
std::string HeaderCode;
std::string HeaderFilename = "TestTU.h";
+ // Extra arguments for the compiler invocation.
+ std::vector<const char *> ExtraArgs;
+
ParsedAST build() const;
SymbolSlab headerSymbols() const;
std::unique_ptr<SymbolIndex> index() const;
@@ -53,6 +56,11 @@ struct TestTU {
const Symbol &findSymbol(const SymbolSlab &, llvm::StringRef QName);
// Look up an AST symbol by qualified name, which must be unique and top-level.
const NamedDecl &findDecl(ParsedAST &AST, llvm::StringRef QName);
+// Look up a main-file AST symbol that satisfies \p Filter.
+const NamedDecl &findAnyDecl(ParsedAST &AST,
+ std::function<bool(const NamedDecl &)> Filter);
+// Look up a main-file AST symbol by unqualified name, which must be unique.
+const NamedDecl &findAnyDecl(ParsedAST &AST, llvm::StringRef Name);
} // namespace clangd
} // namespace clang
diff --git a/unittests/clangd/URITests.cpp b/unittests/clangd/URITests.cpp
index 4ddc1645..84d5ca10 100644
--- a/unittests/clangd/URITests.cpp
+++ b/unittests/clangd/URITests.cpp
@@ -14,6 +14,11 @@
namespace clang {
namespace clangd {
+
+// Force the unittest URI scheme to be linked,
+static int LLVM_ATTRIBUTE_UNUSED UnittestSchemeAnchorDest =
+ UnittestSchemeAnchorSource;
+
namespace {
using ::testing::AllOf;
@@ -22,38 +27,6 @@ MATCHER_P(Scheme, S, "") { return arg.scheme() == S; }
MATCHER_P(Authority, A, "") { return arg.authority() == A; }
MATCHER_P(Body, B, "") { return arg.body() == B; }
-// Assume all files in the schema have a "test-root/" root directory, and the
-// schema path is the relative path to the root directory.
-// So the schema of "/some-dir/test-root/x/y/z" is "test:x/y/z".
-class TestScheme : public URIScheme {
-public:
- static const char *Scheme;
-
- static const char *TestRoot;
-
- llvm::Expected<std::string>
- getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
- llvm::StringRef HintPath) const override {
- auto Pos = HintPath.find(TestRoot);
- assert(Pos != llvm::StringRef::npos);
- return (HintPath.substr(0, Pos + llvm::StringRef(TestRoot).size()) + Body)
- .str();
- }
-
- llvm::Expected<URI>
- uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
- auto Pos = AbsolutePath.find(TestRoot);
- assert(Pos != llvm::StringRef::npos);
- return URI(Scheme, /*Authority=*/"",
- AbsolutePath.substr(Pos + llvm::StringRef(TestRoot).size()));
- }
-};
-
-const char *TestScheme::Scheme = "unittest";
-const char *TestScheme::TestRoot = "/test-root/";
-
-static URISchemeRegistry::Add<TestScheme> X(TestScheme::Scheme, "Test schema");
-
std::string createOrDie(llvm::StringRef AbsolutePath,
llvm::StringRef Scheme = "file") {
auto Uri = URI::create(AbsolutePath, Scheme);
@@ -167,12 +140,12 @@ TEST(URITest, Resolve) {
#else
EXPECT_EQ(resolveOrDie(parseOrDie("file:/a/b/c")), "/a/b/c");
EXPECT_EQ(resolveOrDie(parseOrDie("file://auth/a/b/c")), "/a/b/c");
- EXPECT_EQ(resolveOrDie(parseOrDie("unittest:a/b/c"), "/dir/test-root/x/y/z"),
- "/dir/test-root/a/b/c");
EXPECT_THAT(resolveOrDie(parseOrDie("file://au%3dth/%28x%29/y/%20z")),
"/(x)/y/ z");
EXPECT_THAT(resolveOrDie(parseOrDie("file:///c:/x/y/z")), "c:/x/y/z");
#endif
+ EXPECT_EQ(resolveOrDie(parseOrDie("unittest:///a"), testPath("x")),
+ testPath("a"));
}
TEST(URITest, Platform) {
diff --git a/unittests/clangd/XRefsTests.cpp b/unittests/clangd/XRefsTests.cpp
index 2ef68f58..3383dd54 100644
--- a/unittests/clangd/XRefsTests.cpp
+++ b/unittests/clangd/XRefsTests.cpp
@@ -343,6 +343,13 @@ TEST(Hover, All) {
OneTest Tests[] = {
{
+ R"cpp(// No hover
+ ^int main() {
+ }
+ )cpp",
+ "",
+ },
+ {
R"cpp(// Local variable
int main() {
int bonjour;
@@ -629,14 +636,283 @@ TEST(Hover, All) {
)cpp",
"Declared in union outer::(anonymous)\n\nint def",
},
+ {
+ R"cpp(// Nothing
+ void foo() {
+ ^
+ }
+ )cpp",
+ "",
+ },
+ {
+ R"cpp(// Simple initialization with auto
+ void foo() {
+ ^auto i = 1;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Simple initialization with const auto
+ void foo() {
+ const ^auto i = 1;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Simple initialization with const auto&
+ void foo() {
+ const ^auto& i = 1;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Simple initialization with auto&
+ void foo() {
+ ^auto& i = 1;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Auto with initializer list.
+ namespace std
+ {
+ template<class _E>
+ class initializer_list {};
+ }
+ void foo() {
+ ^auto i = {1,2};
+ }
+ )cpp",
+ "class std::initializer_list<int>",
+ },
+ {
+ R"cpp(// User defined conversion to auto
+ struct Bar {
+ operator ^auto() const { return 10; }
+ };
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Simple initialization with decltype(auto)
+ void foo() {
+ ^decltype(auto) i = 1;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// Simple initialization with const decltype(auto)
+ void foo() {
+ const int j = 0;
+ ^decltype(auto) i = j;
+ }
+ )cpp",
+ "const int",
+ },
+ {
+ R"cpp(// Simple initialization with const& decltype(auto)
+ void foo() {
+ int k = 0;
+ const int& j = k;
+ ^decltype(auto) i = j;
+ }
+ )cpp",
+ "const int &",
+ },
+ {
+ R"cpp(// Simple initialization with & decltype(auto)
+ void foo() {
+ int k = 0;
+ int& j = k;
+ ^decltype(auto) i = j;
+ }
+ )cpp",
+ "int &",
+ },
+ {
+ R"cpp(// decltype with initializer list: nothing
+ namespace std
+ {
+ template<class _E>
+ class initializer_list {};
+ }
+ void foo() {
+ ^decltype(auto) i = {1,2};
+ }
+ )cpp",
+ "",
+ },
+ {
+ R"cpp(// auto function return with trailing type
+ struct Bar {};
+ ^auto test() -> decltype(Bar()) {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// trailing return type
+ struct Bar {};
+ auto test() -> ^decltype(Bar()) {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// auto in function return
+ struct Bar {};
+ ^auto test() {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// auto& in function return
+ struct Bar {};
+ ^auto& test() {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// const auto& in function return
+ struct Bar {};
+ const ^auto& test() {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// decltype(auto) in function return
+ struct Bar {};
+ ^decltype(auto) test() {
+ return Bar();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// decltype(auto) reference in function return
+ struct Bar {};
+ ^decltype(auto) test() {
+ int a;
+ return (a);
+ }
+ )cpp",
+ "int &",
+ },
+ {
+ R"cpp(// decltype lvalue reference
+ void foo() {
+ int I = 0;
+ ^decltype(I) J = I;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// decltype lvalue reference
+ void foo() {
+ int I= 0;
+ int &K = I;
+ ^decltype(K) J = I;
+ }
+ )cpp",
+ "int &",
+ },
+ {
+ R"cpp(// decltype lvalue reference parenthesis
+ void foo() {
+ int I = 0;
+ ^decltype((I)) J = I;
+ }
+ )cpp",
+ "int &",
+ },
+ {
+ R"cpp(// decltype rvalue reference
+ void foo() {
+ int I = 0;
+ ^decltype(static_cast<int&&>(I)) J = static_cast<int&&>(I);
+ }
+ )cpp",
+ "int &&",
+ },
+ {
+ R"cpp(// decltype rvalue reference function call
+ int && bar();
+ void foo() {
+ int I = 0;
+ ^decltype(bar()) J = bar();
+ }
+ )cpp",
+ "int &&",
+ },
+ {
+ R"cpp(// decltype of function with trailing return type.
+ struct Bar {};
+ auto test() -> decltype(Bar()) {
+ return Bar();
+ }
+ void foo() {
+ ^decltype(test()) i = test();
+ }
+ )cpp",
+ "struct Bar",
+ },
+ {
+ R"cpp(// decltype of var with decltype.
+ void foo() {
+ int I = 0;
+ decltype(I) J = I;
+ ^decltype(J) K = J;
+ }
+ )cpp",
+ "int",
+ },
+ {
+ R"cpp(// structured binding. Not supported yet
+ struct Bar {};
+ void foo() {
+ Bar a[2];
+ ^auto [x,y] = a;
+ }
+ )cpp",
+ "",
+ },
+ {
+ R"cpp(// Template auto parameter. Nothing (Not useful).
+ template<^auto T>
+ void func() {
+ }
+ void foo() {
+ func<1>();
+ }
+ )cpp",
+ "",
+ },
};
for (const OneTest &Test : Tests) {
Annotations T(Test.Input);
- auto AST = TestTU::withCode(T.code()).build();
- Hover H = getHover(AST, T.point());
-
- EXPECT_EQ(H.contents.value, Test.ExpectedHover) << Test.Input;
+ TestTU TU = TestTU::withCode(T.code());
+ TU.ExtraArgs.push_back("-std=c++17");
+ auto AST = TU.build();
+ if (auto H = getHover(AST, T.point())) {
+ EXPECT_NE("", Test.ExpectedHover) << Test.Input;
+ EXPECT_EQ(H->contents.value, Test.ExpectedHover.str()) << Test.Input;
+ } else
+ EXPECT_EQ("", Test.ExpectedHover.str()) << Test.Input;
}
}