18 #include "llvm/ADT/ScopeExit.h"
19 #include "llvm/ADT/SmallVector.h"
20 #include "llvm/ADT/StringRef.h"
21 #include "llvm/LineEditor/LineEditor.h"
22 #include "llvm/Support/CommandLine.h"
23 #include "llvm/Support/Signals.h"
29 llvm::cl::opt<std::string> IndexLocation(
30 llvm::cl::desc(
"<path to index file | remote:server.address>"),
31 llvm::cl::Positional);
33 llvm::cl::opt<std::string>
34 ExecCommand(
"c", llvm::cl::desc(
"Command to execute and then exit."));
36 llvm::cl::opt<std::string> ProjectRoot(
39 "Path to the project. Required when connecting using remote index."));
41 static constexpr
char Overview[] = R
"(
42 This is an **experimental** interactive tool to process user-provided search
43 queries over given symbol collection obtained via clangd-indexer. The
44 tool can be used to evaluate search quality of existing index implementations
45 and manually construct non-trivial test cases.
47 You can connect to remote index by passing remote:address to dexp. Example:
49 $ dexp remote:0.0.0.0:9000
51 Type use "help" request to get information about the details.
54 void reportTime(llvm::StringRef Name, llvm::function_ref<
void()> F) {
55 const auto TimerStart = std::chrono::high_resolution_clock::now();
57 const auto TimerStop = std::chrono::high_resolution_clock::now();
58 const auto Duration = std::chrono::duration_cast<std::chrono::milliseconds>(
59 TimerStop - TimerStart);
60 llvm::outs() << llvm::formatv(
"{0} took {1:ms+n}.\n", Name, Duration);
63 std::vector<SymbolID> getSymbolIDsFromIndex(llvm::StringRef QualifiedName,
64 const SymbolIndex *
Index) {
65 FuzzyFindRequest Request;
68 bool IsGlobalScope = QualifiedName.consume_front(
"::");
70 if (IsGlobalScope || !Names.first.empty())
71 Request.Scopes = {std::string(Names.first)};
75 Request.Scopes = {
""};
77 Request.Query = std::string(Names.second);
78 std::vector<SymbolID> SymIDs;
80 std::string SymQualifiedName = (Sym.Scope + Sym.Name).str();
81 if (QualifiedName == SymQualifiedName)
82 SymIDs.push_back(Sym.ID);
91 llvm::cl::opt<bool, false, llvm::cl::parser<bool>> Help{
92 "help", llvm::cl::desc(
"Display available options"),
93 llvm::cl::ValueDisallowed, llvm::cl::cat(llvm::cl::getGeneralCategory())};
95 virtual void run() = 0;
101 virtual ~Command() =
default;
102 bool parseAndRun(llvm::ArrayRef<const char *> Argv,
const char *Overview,
103 const SymbolIndex &
Index) {
104 std::string ParseErrs;
105 llvm::raw_string_ostream
OS(ParseErrs);
106 bool Ok = llvm::cl::ParseCommandLineOptions(Argv.size(), Argv.data(),
109 auto Cleanup = llvm::make_scope_exit(llvm::cl::ResetCommandLineParser);
110 if (Help.getNumOccurrences() > 0) {
113 llvm::cl::PrintHelpMessage();
117 llvm::outs() <<
OS.str();
119 this->Index = &
Index;
120 reportTime(Argv[0], [&] { run(); });
135 class FuzzyFind :
public Command {
136 llvm::cl::opt<std::string> Query{
138 llvm::cl::Positional,
140 llvm::cl::desc(
"Query string to be fuzzy-matched"),
142 llvm::cl::opt<std::string> Scopes{
144 llvm::cl::desc(
"Allowed symbol scopes (comma-separated list)"),
146 llvm::cl::opt<unsigned> Limit{
149 llvm::cl::desc(
"Max results to display"),
152 void run()
override {
153 FuzzyFindRequest Request;
154 Request.Limit = Limit;
155 Request.Query = Query;
156 if (Scopes.getNumOccurrences() > 0) {
157 llvm::SmallVector<llvm::StringRef> Scopes;
158 llvm::StringRef(this->Scopes).split(Scopes,
',');
159 Request.Scopes = {Scopes.begin(), Scopes.end()};
161 Request.AnyScope = Request.Scopes.empty();
163 static const auto *OutputFormat =
"{0,-4} | {1,-40} | {2,-25}\n";
164 llvm::outs() << llvm::formatv(OutputFormat,
"Rank",
"Symbol ID",
167 Index->fuzzyFind(Request, [&](
const Symbol &Sym) {
168 llvm::outs() << llvm::formatv(OutputFormat, Rank++, Sym.ID.str(),
169 Sym.Scope + Sym.Name);
174 class Lookup :
public Command {
175 llvm::cl::opt<std::string>
ID{
177 llvm::cl::Positional,
178 llvm::cl::desc(
"Symbol ID to look up (hex)"),
180 llvm::cl::opt<std::string>
Name{
182 llvm::cl::desc(
"Qualified name to look up."),
185 void run()
override {
186 if (
ID.getNumOccurrences() == 0 &&
Name.getNumOccurrences() == 0) {
188 <<
"Missing required argument: please provide id or -name.\n";
191 std::vector<SymbolID> IDs;
192 if (
ID.getNumOccurrences()) {
200 IDs = getSymbolIDsFromIndex(Name,
Index);
203 LookupRequest Request;
204 Request.IDs.insert(IDs.begin(), IDs.end());
205 bool FoundSymbol =
false;
206 Index->lookup(Request, [&](
const Symbol &Sym) {
208 llvm::outs() <<
toYAML(Sym);
211 llvm::errs() <<
"not found\n";
215 class Refs :
public Command {
216 llvm::cl::opt<std::string>
ID{
218 llvm::cl::Positional,
219 llvm::cl::desc(
"Symbol ID of the symbol being queried (hex)."),
221 llvm::cl::opt<std::string>
Name{
223 llvm::cl::desc(
"Qualified name of the symbol being queried."),
225 llvm::cl::opt<std::string> Filter{
227 llvm::cl::init(
".*"),
229 "Print all results from files matching this regular expression."),
232 void run()
override {
233 if (
ID.getNumOccurrences() == 0 &&
Name.getNumOccurrences() == 0) {
235 <<
"Missing required argument: please provide id or -name.\n";
238 std::vector<SymbolID> IDs;
239 if (
ID.getNumOccurrences()) {
247 IDs = getSymbolIDsFromIndex(Name,
Index);
248 if (IDs.size() > 1) {
249 llvm::errs() << llvm::formatv(
250 "The name {0} is ambiguous, found {1} different "
251 "symbols. Please use id flag to disambiguate.\n",
256 RefsRequest RefRequest;
257 RefRequest.IDs.insert(IDs.begin(), IDs.end());
258 llvm::Regex RegexFilter(Filter);
259 Index->refs(RefRequest, [&RegexFilter](
const Ref &R) {
262 llvm::errs() << U.takeError();
265 if (RegexFilter.match(U->body()))
266 llvm::outs() << R <<
"\n";
271 class Relations :
public Command {
272 llvm::cl::opt<std::string>
ID{
274 llvm::cl::Positional,
275 llvm::cl::desc(
"Symbol ID of the symbol being queried (hex)."),
277 llvm::cl::opt<RelationKind> Relation{
279 llvm::cl::desc(
"Relation kind for the predicate."),
281 "Find subclasses of a class."),
283 "Find methods that overrides a virtual method.")),
286 void run()
override {
287 if (
ID.getNumOccurrences() == 0 || Relation.getNumOccurrences() == 0) {
289 <<
"Missing required argument: please provide id and -relation.\n";
292 RelationsRequest Req;
293 if (
ID.getNumOccurrences()) {
299 Req.Subjects.insert(*SID);
301 Req.Predicate = Relation.getValue();
302 Index->relations(Req, [](
const SymbolID &SID,
const Symbol &S) {
303 llvm::outs() <<
toYAML(S);
308 class Export :
public Command {
309 llvm::cl::opt<IndexFileFormat> Format{
311 llvm::cl::desc(
"Format of index export"),
314 "human-readable YAML format"),
318 llvm::cl::opt<std::string> OutputFile{
320 llvm::cl::Positional,
322 llvm::cl::desc(
"Output file for export"),
326 void run()
override {
329 auto Buffer = llvm::MemoryBuffer::getFile(IndexLocation);
331 llvm::errs() << llvm::formatv(
"Can't open {0}", IndexLocation) <<
"\n";
345 llvm::raw_fd_ostream OutputStream(OutputFile, EC);
347 llvm::errs() << llvm::formatv(
"Can't open {0} for writing", OutputFile)
354 IndexOut.Format = Format;
355 OutputStream << IndexOut;
364 {
"find",
"Search for symbols with fuzzyFind", std::make_unique<FuzzyFind>},
365 {
"lookup",
"Dump symbol details by ID or qualified name",
366 std::make_unique<Lookup>},
367 {
"refs",
"Find references by ID or qualified name", std::make_unique<Refs>},
368 {
"relations",
"Find relations by ID and relation kind",
369 std::make_unique<Relations>},
370 {
"export",
"Export index", std::make_unique<Export>},
373 std::unique_ptr<SymbolIndex> openIndex(llvm::StringRef
Index) {
374 return Index.startswith(
"remote:")
382 std::replace(Request.begin(), Request.end(),
' ',
'\0');
383 llvm::SmallVector<llvm::StringRef>
Args;
384 llvm::StringRef(Request).split(
Args,
'\0', -1,
388 if (
Args.front() ==
"help") {
389 llvm::outs() <<
"dexp - Index explorer\nCommands:\n";
390 for (
const auto &
C : CommandInfo)
391 llvm::outs() << llvm::formatv(
"{0,16} - {1}\n",
C.Name,
C.Description);
392 llvm::outs() <<
"Get detailed command help with e.g. `find -help`.\n";
395 llvm::SmallVector<const char *> FakeArgv;
396 for (llvm::StringRef S :
Args)
397 FakeArgv.push_back(S.data());
399 for (
const auto &Cmd : CommandInfo) {
400 if (Cmd.Name ==
Args.front())
401 return Cmd.Implementation()->parseAndRun(FakeArgv, Cmd.Description,
404 llvm::errs() <<
"Unknown command. Try 'help'.\n";
412 int main(
int argc,
const char *argv[]) {
415 llvm::cl::ParseCommandLineOptions(argc, argv, Overview);
419 IndexLocation.setValue(IndexLocation,
true);
420 ExecCommand.setValue(ExecCommand,
true);
421 ProjectRoot.setValue(ProjectRoot,
true);
423 llvm::cl::ResetCommandLineParser();
424 llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
426 bool RemoteMode = llvm::StringRef(IndexLocation).startswith(
"remote:");
427 if (RemoteMode && ProjectRoot.empty()) {
428 llvm::errs() <<
"--project-root is required in remote mode\n";
432 std::unique_ptr<SymbolIndex>
Index;
433 reportTime(RemoteMode ?
"Remote index client creation" :
"Dex build",
434 [&]() {
Index = openIndex(IndexLocation); });
437 llvm::errs() <<
"Failed to open the index.\n";
441 if (!ExecCommand.empty())
442 return runCommand(ExecCommand, *
Index) ? 0 : 1;
444 llvm::LineEditor LE(
"dexp");
445 while (llvm::Optional<std::string> Request = LE.readLine())
446 runCommand(std::move(*Request), *
Index);