some update.
This commit is contained in:
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@@ -8,18 +8,18 @@
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "clice_socket",
|
||||
"program": "./build/bin/clice"
|
||||
"program": "${workspaceFolder}/build/bin/clice"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "attach",
|
||||
"name": "clice",
|
||||
"name": "clice_attach",
|
||||
"program": "clice"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "DependentNameResolver",
|
||||
"name": "clice_test",
|
||||
"program": "${workspaceFolder}/build/bin/clice_test",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
|
||||
16
docs/design.md
Normal file
16
docs/design.md
Normal file
@@ -0,0 +1,16 @@
|
||||
clice 充分吸取了 clangd 教训,用一些全新的设计来解决问题。
|
||||
|
||||
|
||||
- 默认索引所有文件
|
||||
- 对于只读文件,使用 LSIF 返回请求结果,speed speed speed !!!!
|
||||
- 对于可变文件,为它们构建 preamble,以加速后续的构建,然后跑代码补全之类的行为
|
||||
|
||||
- clice 如何区分只读文件和可变文件?一般来说,当您第一次尝试编辑这个文件的时候,clice 就会认为这是一个可变的文件,并为其构建 preamble。但是很多情况下可能只是不小心碰到了键盘,并不是真的想要修改,可以在设置里面配置选项,使得第一次更改并报错之后才会让 clice 认为这是一个活跃文件。另外,使用 code action 也会触发。
|
||||
|
||||
# non self contained
|
||||
|
||||
clice 解决了一个 clangd 里面长期存在的问题,non self contained 的头文件,这种技巧常见于 glibc 或者 libstdc++ 这样的代码库中。non self-contained file 指的是那些不能单独编译的源文件。只有把他们一同拼接到一个文件中,才能编译通过,而且对于这种文件,往往 include 顺序也十分重要,建议关闭头文件顺序重排。
|
||||
|
||||
对于 non self contained 的头文件来说,只要生成的编译数据库中,有包含它的源文件,并且能正常编译,那么 clice 就可以正确为它生成索引,从而支持转到定义等等等操作。
|
||||
|
||||
那么 non self contained 的文件对于可变操作如何呢?TODO: ... 初步设想,手动通过字符串拼接的方式来补全缺少的头文件 ...
|
||||
@@ -20,3 +20,5 @@
|
||||
|
||||
- Go to implementation
|
||||
常用于跳转到虚函数实现,部分情况下。特别的,如果 clice 能发现你这个类型是一个 CRTP 类型,那么它也可以进行对应的跳转。
|
||||
|
||||
#
|
||||
|
||||
19
include/AST/Compiler.h
Normal file
19
include/AST/Compiler.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <Support/ADT.h>
|
||||
#include <clang/Frontend/CompilerInstance.h>
|
||||
|
||||
namespace clice {
|
||||
|
||||
// TODO:
|
||||
|
||||
class Preamble;
|
||||
|
||||
std::unique_ptr<clang::CompilerInvocation> createInvocation(StringRef filename,
|
||||
StringRef content,
|
||||
std::vector<const char*>& args,
|
||||
Preamble* preamble = nullptr);
|
||||
|
||||
std::unique_ptr<clang::CompilerInstance> createInstance(std::shared_ptr<clang::CompilerInvocation> invocation);
|
||||
|
||||
} // namespace clice
|
||||
@@ -36,6 +36,7 @@ struct Directive {
|
||||
};
|
||||
|
||||
struct Directives {
|
||||
clang::Preprocessor& preproc;
|
||||
clang::SourceManager& sourceManager;
|
||||
llvm::DenseMap<clang::FileID, Directive> x;
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ namespace clice {
|
||||
struct Preamble {
|
||||
clang::PrecompiledPreamble data;
|
||||
|
||||
static std::unique_ptr<Preamble>
|
||||
build(llvm::StringRef filename, llvm::StringRef content, std::vector<const char*>& args);
|
||||
static std::unique_ptr<Preamble> build(llvm::StringRef filename,
|
||||
llvm::StringRef content,
|
||||
std::vector<const char*>& args);
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
#include "ParsedAST.h"
|
||||
#include <clang/AST/ASTTypeTraits.h>
|
||||
|
||||
namespace clice {
|
||||
|
||||
class Selection {};
|
||||
class SelectionTree {
|
||||
public:
|
||||
enum Selection {
|
||||
Unselected,
|
||||
Partial,
|
||||
Complete,
|
||||
};
|
||||
|
||||
clang::Decl* test(clang::syntax::Token* token, ParsedAST& AST, llvm::StringRef filename);
|
||||
struct Node {
|
||||
Node* parent;
|
||||
llvm::SmallVector<const Node*> children;
|
||||
clang::DynTypedNode ASTNode;
|
||||
};
|
||||
|
||||
const Node* commonAncestor() const;
|
||||
|
||||
const Node& root() const;
|
||||
|
||||
SelectionTree(clang::ASTContext& context, const clang::syntax::TokenBuffer& tokens, unsigned start, unsigned end);
|
||||
|
||||
private:
|
||||
std::deque<Node> m_Nodes;
|
||||
const Node* m_Root;
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
|
||||
22
include/Index/Index.h
Normal file
22
include/Index/Index.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <clang/Index/IndexDataConsumer.h>
|
||||
|
||||
namespace clice {
|
||||
|
||||
class IndexConsumer : public clang::index::IndexDataConsumer {
|
||||
public:
|
||||
void initialize(clang::ASTContext& Ctx) override {}
|
||||
|
||||
void setPreprocessor(std::shared_ptr<clang::Preprocessor> PP) override {}
|
||||
|
||||
bool handleDeclOccurrence(const clang::Decl* D,
|
||||
clang::index::SymbolRoleSet Roles,
|
||||
llvm::ArrayRef<clang::index::SymbolRelation> Relations,
|
||||
clang::SourceLocation Loc,
|
||||
clang::index::IndexDataConsumer::ASTNodeInfo ASTNode) override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
52
include/Index/Protocol.h
Normal file
52
include/Index/Protocol.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <Support/ADT.h>
|
||||
|
||||
namespace clice {
|
||||
|
||||
struct SymbolID {};
|
||||
|
||||
struct Range {};
|
||||
|
||||
enum class SymbolKind : uint8_t {
|
||||
|
||||
};
|
||||
|
||||
struct Relation {
|
||||
SymbolID target;
|
||||
bool isReference = false;
|
||||
bool isImplementation = false;
|
||||
bool isTypeDefinition = false;
|
||||
bool isBase = false;
|
||||
bool isOverride = false;
|
||||
};
|
||||
|
||||
struct Symbol {
|
||||
SymbolID symbol;
|
||||
SymbolKind kind;
|
||||
StringRef displayName;
|
||||
StringRef document;
|
||||
std::vector<Relation> relations;
|
||||
};
|
||||
|
||||
enum class SymbolRole : uint8_t {
|
||||
Definition,
|
||||
Import,
|
||||
WriteAccess,
|
||||
ReadAccess,
|
||||
ForwardDeclaration,
|
||||
};
|
||||
|
||||
struct Occurrence {
|
||||
SymbolID symbol;
|
||||
Range range;
|
||||
SymbolRole role;
|
||||
};
|
||||
|
||||
struct Document {
|
||||
StringRef uri;
|
||||
std::vector<Symbol> symbols;
|
||||
std::vector<Occurrence> occurrences;
|
||||
};
|
||||
|
||||
}; // namespace clice
|
||||
17
include/Support/ADT.h
Normal file
17
include/Support/ADT.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <llvm/ADT/ArrayRef.h>
|
||||
#include <llvm/ADT/StringRef.h>
|
||||
#include <llvm/ADT/SmallVector.h>
|
||||
#include <llvm/ADT/SmallString.h>
|
||||
|
||||
namespace clice {
|
||||
using llvm::ArrayRef;
|
||||
using llvm::StringRef;
|
||||
using llvm::SmallVector;
|
||||
using llvm::SmallString;
|
||||
} // namespace clice
|
||||
56
src/AST/Compiler.cpp
Normal file
56
src/AST/Compiler.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include <AST/Compiler.h>
|
||||
#include <AST/Preamble.h>
|
||||
|
||||
#include <clang/Lex/PreprocessorOptions.h>
|
||||
|
||||
namespace clice {
|
||||
|
||||
static void setInvocation(clang::CompilerInvocation& invocation) {
|
||||
clang::LangOptions& langOpts = invocation.getLangOpts();
|
||||
langOpts.CommentOpts.ParseAllComments = true;
|
||||
langOpts.RetainCommentsFromSystemHeaders = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<clang::CompilerInvocation>
|
||||
createInvocation(StringRef filename, StringRef content, std::vector<const char*>& args, Preamble* preamble) {
|
||||
clang::CreateInvocationOptions options;
|
||||
// FIXME: explore VFS
|
||||
auto vfs = llvm::vfs::getRealFileSystem();
|
||||
|
||||
auto invocation = clang::createInvocation(args, options);
|
||||
|
||||
setInvocation(*invocation);
|
||||
|
||||
auto buffer = llvm::MemoryBuffer::getMemBufferCopy(content, filename);
|
||||
if(preamble) {
|
||||
auto bounds = clang::ComputePreambleBounds(invocation->getLangOpts(), *buffer, false);
|
||||
// check if the preamble can be reused
|
||||
if(preamble->data.CanReuse(*invocation, *buffer, bounds, *vfs)) {
|
||||
llvm::outs() << "Resued preamble\n";
|
||||
// reuse preamble
|
||||
preamble->data.AddImplicitPreamble(*invocation, vfs, buffer.release());
|
||||
}
|
||||
} else {
|
||||
invocation->getPreprocessorOpts().addRemappedFile(invocation->getFrontendOpts().Inputs[0].getFile(),
|
||||
buffer.release());
|
||||
}
|
||||
|
||||
return invocation;
|
||||
}
|
||||
|
||||
std::unique_ptr<clang::CompilerInstance> createInstance(std::shared_ptr<clang::CompilerInvocation> invocation) {
|
||||
auto instance = std::make_unique<clang::CompilerInstance>();
|
||||
|
||||
instance->setInvocation(std::move(invocation));
|
||||
// FIXME: resolve diagnostics
|
||||
instance->createDiagnostics(new clang::TextDiagnosticPrinter(llvm::outs(), new clang::DiagnosticOptions()), true);
|
||||
|
||||
if(!instance->createTarget()) {
|
||||
llvm::errs() << "Failed to create target\n";
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace clice
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <AST/Directive.h>
|
||||
#include <Support/Reflection.h>
|
||||
#include <clang/Lex/MacroArgs.h>
|
||||
|
||||
namespace clice {
|
||||
|
||||
@@ -12,7 +13,7 @@ struct CommentHandler : public clang::CommentHandler {
|
||||
// directive.comments.push_back(Comment);
|
||||
auto start = directive.sourceManager.getCharacterData(Comment.getBegin());
|
||||
auto end = directive.sourceManager.getCharacterData(Comment.getEnd());
|
||||
llvm::outs() << "Comment: " << llvm::StringRef(start, end - start) << "\n";
|
||||
// llvm::outs() << "Comment: " << llvm::StringRef(start, end - start) << "\n";
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -58,7 +59,7 @@ struct PPCallback : clang::PPCallbacks {
|
||||
void If(clang::SourceLocation Loc,
|
||||
clang::SourceRange ConditionRange,
|
||||
clang::PPCallbacks::ConditionValueKind ConditionValue) override {
|
||||
llvm::outs() << "If\n";
|
||||
// llvm::outs() << "If\n";
|
||||
}
|
||||
|
||||
void Elif(clang::SourceLocation loc,
|
||||
@@ -92,6 +93,37 @@ struct PPCallback : clang::PPCallbacks {
|
||||
void Endif(clang::SourceLocation loc, clang::SourceLocation ifLoc) override {}
|
||||
|
||||
void MacroDefined(const clang::Token& MacroNameTok, const clang::MacroDirective* MD) override {}
|
||||
|
||||
void MacroExpands(const clang::Token& MacroNameTok,
|
||||
const clang::MacroDefinition& MD,
|
||||
clang::SourceRange Range,
|
||||
const clang::MacroArgs* Args) override {
|
||||
// llvm::outs() << "------------------------\n";
|
||||
// auto info = MD.getMacroInfo();
|
||||
// if(info->isObjectLike()) {
|
||||
// Range = clang::SourceRange(MacroNameTok.getLocation(), MacroNameTok.getEndLoc());
|
||||
// } else if(info->isFunctionLike()) {
|
||||
// // MacroNameTok.getLocation().dump(directive.sourceManager);
|
||||
// }
|
||||
// Range.getBegin().dump(directive.sourceManager);
|
||||
// Range.getEnd().dump(directive.sourceManager);
|
||||
// auto s = directive.sourceManager.getCharacterData(Range.getBegin());
|
||||
// auto e = directive.sourceManager.getCharacterData(Range.getEnd());
|
||||
//// llvm::outs() << llvm::StringRef(s, e - s) << "\n";
|
||||
//
|
||||
// llvm::outs() << directive.preproc.getSpelling(MacroNameTok) << " ";
|
||||
// if(Args) {
|
||||
// auto size = Args->getNumMacroArguments();
|
||||
// for(int i = 0; i < size; i++) {
|
||||
// auto arg = Args->getUnexpArgument(i);
|
||||
// auto len = Args->getArgLength(arg);
|
||||
// for(int j = 0; j < len; j++) {
|
||||
// llvm::outs() << directive.preproc.getSpelling(arg[j]) << " ";
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
// llvm::outs() << "\n";
|
||||
}
|
||||
};
|
||||
|
||||
clang::CommentHandler* Directives::handler() { return new CommentHandler(*this); }
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
#include <AST/ParsedAST.h>
|
||||
#include "clang/Lex/PreprocessorOptions.h"
|
||||
#include <AST/Compiler.h>
|
||||
|
||||
namespace clice {
|
||||
|
||||
static void setInvocation(clang::CompilerInvocation& invocation) {
|
||||
clang::LangOptions& langOpts = invocation.getLangOpts();
|
||||
langOpts.CommentOpts.ParseAllComments = true;
|
||||
langOpts.RetainCommentsFromSystemHeaders = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<ParsedAST> ParsedAST::build(llvm::StringRef filename,
|
||||
llvm::StringRef content,
|
||||
std::vector<const char*>& args,
|
||||
@@ -18,43 +12,8 @@ std::unique_ptr<ParsedAST> ParsedAST::build(llvm::StringRef filename,
|
||||
clang::CreateInvocationOptions options;
|
||||
// options.VFS = vfs;
|
||||
|
||||
auto invocation = std::make_shared<clang::CompilerInvocation>();
|
||||
invocation = clang::createInvocation(args, options);
|
||||
|
||||
auto buffer = llvm::MemoryBuffer::getMemBufferCopy(content, filename);
|
||||
if(preamble) {
|
||||
|
||||
auto bounds = clang::ComputePreambleBounds(invocation->getLangOpts(), *buffer, false);
|
||||
|
||||
// check if the preamble can be reused
|
||||
if(preamble->data.CanReuse(*invocation, *buffer, bounds, *vfs)) {
|
||||
llvm::outs() << "Resued preamble\n";
|
||||
// reuse preamble
|
||||
preamble->data.AddImplicitPreamble(*invocation, vfs, buffer.release());
|
||||
}
|
||||
} else {
|
||||
invocation->getPreprocessorOpts().addRemappedFile(invocation->getFrontendOpts().Inputs[0].getFile(),
|
||||
buffer.release());
|
||||
}
|
||||
|
||||
invocation->getLangOpts().CommentOpts.ParseAllComments = true;
|
||||
|
||||
auto instance = std::make_unique<clang::CompilerInstance>();
|
||||
instance->setInvocation(std::move(invocation));
|
||||
|
||||
instance->createDiagnostics(new clang::TextDiagnosticPrinter(llvm::outs(), new clang::DiagnosticOptions()), true);
|
||||
|
||||
// if(auto remappingVSF =
|
||||
// createVFSFromCompilerInvocation(instance->getInvocation(), instance->getDiagnostics(), vfs)) {
|
||||
// vfs = remappingVSF;
|
||||
// }
|
||||
// instance->createFileManager(vfs);
|
||||
|
||||
if(!instance->createTarget()) {
|
||||
llvm::errs() << "Failed to create target\n";
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
auto invocation = createInvocation(filename, content, args, preamble);
|
||||
auto instance = createInstance(std::move(invocation));
|
||||
auto action = std::make_unique<clang::SyntaxOnlyAction>();
|
||||
|
||||
if(!action->BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) {
|
||||
@@ -65,7 +24,7 @@ std::unique_ptr<ParsedAST> ParsedAST::build(llvm::StringRef filename,
|
||||
auto& preproc = instance->getPreprocessor();
|
||||
clang::syntax::TokenCollector collector(preproc);
|
||||
|
||||
auto directive = std::make_unique<Directives>(instance->getSourceManager());
|
||||
auto directive = std::make_unique<Directives>(instance->getPreprocessor(), instance->getSourceManager());
|
||||
preproc.addCommentHandler(directive->handler());
|
||||
preproc.addPPCallbacks(directive->callback());
|
||||
|
||||
|
||||
@@ -2,10 +2,26 @@
|
||||
|
||||
namespace clice {
|
||||
|
||||
std::unique_ptr<Preamble>
|
||||
Preamble::build(llvm::StringRef filename, llvm::StringRef content, std::vector<const char*>& args) {
|
||||
class PreambleCallback : public clang::PreambleCallbacks {
|
||||
public:
|
||||
std::optional<clang::syntax::TokenCollector> collector;
|
||||
|
||||
public:
|
||||
void BeforeExecute(clang::CompilerInstance& CI) override { collector.emplace(CI.getPreprocessor()); }
|
||||
|
||||
void AfterExecute(clang::CompilerInstance& CI) override {
|
||||
auto tokens = std::move(collector.value()).consume();
|
||||
for(auto& token: tokens.expandedTokens()) {
|
||||
llvm::outs() << token.text(CI.getSourceManager()) << "\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Preamble> Preamble::build(llvm::StringRef filename,
|
||||
llvm::StringRef content,
|
||||
std::vector<const char*>& args) {
|
||||
auto invocation = clang::createInvocation(args, {});
|
||||
auto buffer = llvm::MemoryBuffer::getMemBuffer(content, filename);
|
||||
auto buffer = llvm::MemoryBuffer::getMemBufferCopy(content, filename);
|
||||
|
||||
// compute preamble bounds, i.e. the range of the preamble.
|
||||
// if MaxLines set to false(0), i.e. limit the number of lines.
|
||||
@@ -19,7 +35,7 @@ std::unique_ptr<Preamble>
|
||||
|
||||
// use to collect information in the process of building preamble, such as include files and macros
|
||||
// TODO: inherit from clang::PreambleCallbacks and collect the information
|
||||
clang::PreambleCallbacks callbacks = {};
|
||||
PreambleCallback callbacks = {};
|
||||
|
||||
auto preamble = clang::PrecompiledPreamble::Build(*invocation,
|
||||
buffer.get(),
|
||||
|
||||
@@ -23,14 +23,21 @@ TEST(test, test) {
|
||||
#include <cstdio>
|
||||
|
||||
const char* code = R"(
|
||||
// 123
|
||||
#if x
|
||||
#endif
|
||||
// 333
|
||||
void f();
|
||||
|
||||
void f() {}
|
||||
)";
|
||||
|
||||
auto AST = clice::ParsedAST::build("main.cpp", code, compileArgs);
|
||||
auto preamble = clice::Preamble::build("main.cpp", code, compileArgs);
|
||||
auto AST = clice::ParsedAST::build("main.cpp", code, compileArgs, preamble.get());
|
||||
auto fileID = AST->getFileID("main.cpp");
|
||||
auto tokens = AST->tokenBuffer.spelledTokens(fileID);
|
||||
AST->context.getTranslationUnitDecl()->dump();
|
||||
|
||||
// for(auto& token: tokens) {
|
||||
// llvm::outs() << token.text(AST->sourceManager) << "\n";
|
||||
// }
|
||||
|
||||
// AST->context.getTranslationUnitDecl()->dump();
|
||||
// auto semanticTokens = clice::feature::semanticTokens(*AST, "main.cpp");
|
||||
}
|
||||
|
||||
3
tests/Support/TokenBuffer.cpp
Normal file
3
tests/Support/TokenBuffer.cpp
Normal file
@@ -0,0 +1,3 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(clang, TokenBuffer) {}
|
||||
@@ -6,25 +6,25 @@ namespace {
|
||||
using namespace clice;
|
||||
|
||||
TEST(URITest, ConstructorAndAccessors) {
|
||||
URI uri("https", "reviews.lvm.org", "/D41946");
|
||||
URI uri("https", "reviews.llvm.org", "/D41946");
|
||||
|
||||
EXPECT_EQ(uri.scheme(), "https");
|
||||
EXPECT_EQ(uri.authority(), "reviews.lvm.org");
|
||||
EXPECT_EQ(uri.authority(), "reviews.llvm.org");
|
||||
EXPECT_EQ(uri.body(), "/D41946");
|
||||
}
|
||||
|
||||
TEST(URITest, CopyConstructor) {
|
||||
URI uri1("https", "reviews.lvm.org", "/D41946");
|
||||
URI uri1("https", "reviews.llvm.org", "/D41946");
|
||||
URI uri2(uri1);
|
||||
|
||||
EXPECT_EQ(uri2.scheme(), "https");
|
||||
EXPECT_EQ(uri2.authority(), "reviews.lvm.org");
|
||||
EXPECT_EQ(uri2.authority(), "reviews.llvm.org");
|
||||
EXPECT_EQ(uri2.body(), "/D41946");
|
||||
}
|
||||
|
||||
TEST(URITest, EqualityOperator) {
|
||||
URI uri1("https", "reviews.lvm.org", "/D41946");
|
||||
URI uri2("https", "reviews.lvm.org", "/D41946");
|
||||
URI uri1("https", "reviews.llvm.org", "/D41946");
|
||||
URI uri2("https", "reviews.llvm.org", "/D41946");
|
||||
URI uri3("http", "example.com", "/index.html");
|
||||
|
||||
EXPECT_TRUE(uri1 == uri2);
|
||||
@@ -32,12 +32,12 @@ TEST(URITest, EqualityOperator) {
|
||||
}
|
||||
|
||||
TEST(URITest, ParseFunction) {
|
||||
auto expectedUri = URI::parse("https://reviews.lvm.org/D41946");
|
||||
auto expectedUri = URI::parse("https://reviews.llvm.org/D41946");
|
||||
ASSERT_TRUE(static_cast<bool>(expectedUri));
|
||||
|
||||
URI uri = expectedUri.get();
|
||||
EXPECT_EQ(uri.scheme(), "https");
|
||||
EXPECT_EQ(uri.authority(), "reviews.lvm.org");
|
||||
EXPECT_EQ(uri.authority(), "reviews.llvm.org");
|
||||
EXPECT_EQ(uri.body(), "/D41946");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user