diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -46,6 +46,10 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/quality/CompletionModel.cmake) gen_decision_forest(${CMAKE_CURRENT_SOURCE_DIR}/quality/model CompletionModel clang::clangd::Example) +clang_tablegen(ConfigFragment.inc -gen-clangd-config-header SOURCE Config.td TARGET clangd_config_fragment_gen) +clang_tablegen(ConfigYAML.inc -gen-clangd-config-yaml SOURCE Config.td TARGET clangd_config_yaml_gen) +clang_tablegen(ConfigDocs.md -gen-clangd-config-docs SOURCE Config.td TARGET clangd_config_docs_gen) + if(MSVC AND NOT CLANG_CL) set_source_files_properties(CompileCommands.cpp PROPERTIES COMPILE_FLAGS -wd4130) # disables C4130: logical operation on address of string constant endif() @@ -136,6 +140,8 @@ DEPENDS omp_gen + clangd_config_fragment_gen + clangd_config_yaml_gen ) # Include generated CompletionModel headers. diff --git a/clang-tools-extra/clangd/Config.td b/clang-tools-extra/clangd/Config.td new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/Config.td @@ -0,0 +1,272 @@ +class Field; + +// Possible types of fields. +class Type { + string spelling = ?; + bit nullable = 0; // True if the type has an "empty" value. + string field_spelling = !if(nullable, spelling, + "llvm::Optional<" # spelling # ">"); +} +// Primitives. +def String : Type { let spelling = "Located"; } +def Boolean : Type { let spelling = "Located"; } +// Lists (JSON array or YAML sequence) +class List : Type { + Type element = T; + let spelling = "std::vector<" # T.spelling # ">"; + let nullable = 1; +} +// Key-value pairs, keys are arbitrary strings (JSON object or YAML mapping) +class KeyValues : Type { + Type element = T; + let spelling = "std::vector, " # T.spelling # ">>"; + let nullable = 1; +} +// Structure with enumerated fields (JSON object or YAML mapping node) +class Struct Fields, bit Nullable> : Type { + string struct_name = Name # "Block"; + // Retain location info only if presence is significant. + let spelling = !if(nullable, struct_name, "Located<" # struct_name # ">"); + string name = Name; + list fields = Fields; + let nullable = Nullable; +} + +// Fields appear within structs. +class Field { + string doc = Doc; + bit parsed = 1; + bit documented = 1; + string name = Name; + Type type = T; +} +// An unparsed field will not have marshalling code. +// It will be documented only if the docstring is nonempty. +class Unparsed : Field { + let parsed = 0; + let documented = !ne(doc, ""); +} +// A block is a field Foo whose type is a structure named FooBlock. +class Block Fields> + : Field, Doc> { +} +// The presence/absence of an optional block is significant. +class OptionalBlock Fields> + : Field, Doc> { +} + +def Fragment : Struct<"Fragment", [ + Block<"If", [{ + Conditions in the If block restrict when a Fragment applies. + + ```yaml + If: # Apply this config conditionally + PathMatch: .*\.h # to all headers... + PathExclude: include/llvm-c/.* # except those under include/llvm-c/ + ``` + + Each separate condition must match (combined with AND). + When one condition has multiple values, any may match (combined with OR). + e.g. `PathMatch: [foo/.*, bar/.*]` matches files in either directory. + + Conditions based on a file's path use the following form: + + - if the fragment came from a project directory, the path is relative + - if the fragment is global (e.g. user config), the path is absolute + - paths always use forward-slashes (UNIX-style) + + If no file is being processed, these conditions will not match. + }], [ + Field<"PathMatch", List, + [{The file being processed must fully match a regular expression.}]>, + Field<"PathExclude", List, + [{The file being processed must *not* fully match a regular expression.}]>, + Unparsed>, + ]>, + + Block<"CompileFlags", [{ + Affects how a file is parsed. + + ```yaml + CompileFlags: # Tweak the parse settings + Add: [-xc++, -Wall] # treat all files as C++, enable more warnings + Remove: -W* # strip all other warning-related flags + ``` + + clangd emulates how clang would interpret a file. + By default, it behaves roughly like `clang $FILENAME`, but real projects + usually require setting the include path (with the `-I` flag), defining + preprocessor symbols, configuring warnings etc. + + Often, a compilation database specifies these compile commands. clangd + searches for compile_commands.json in parents of the source file. + + This section modifies how the compile command is constructed. + }], [ + Field<"Add", List, + [{List of flags to append to the compile command.}]>, + Field<"Remove", List, [{ + List of flags to remove from the compile command. + + - If the value is a recognized clang flag (like `-I`) then it will be + removed along with any arguments. Synonyms like `--include-dir=` will + also be removed. + - Otherwise, if the value ends in `*` (like `-DFOO=*`) then any argument + with the prefix will be removed. + - Otherwise any argument exactly matching the value is removed. + + In all cases, `-Xclang` is also removed where needed. + + Example: + + - Command: `clang++ --include-directory=/usr/include -DFOO=42 foo.cc` + - Configuration: `Remove: [-I, -DFOO=*]` + - Result: `clang++ foo.cc` + + Flags added by the same CompileFlags entry will not be removed. + }]>, + Field<"CompilationDatabase", String, [{ + Directory to search for compilation database (compile_comands.json etc). + + Valid values are: + + - A single path to a directory (absolute, or relative to the fragment) + - Ancestors: search all parent directories (the default) + - None: do not use a compilation database, just default flags. + }]>, + ]>, + + Block<"Index", [{ + Controls how clangd understands code outside the current file. + + ```yaml + Index: + Background: Skip # Disable slow background indexing of these files. + ``` + + clangd's indexes provide information about symbols that isn't available + to clang's parser, such as incoming references. + }], [ + Field<"Background", String, [{ + Whether files are built in the background to produce a project index. + This is checked for translation units only, not headers they include. + Legal values are `Build` (the default) or `Skip`. + }]>, + Unparsed, + Field<"Server", String, + [{Address of a remote index server}]>, + Field<"MountPoint", String, + [{Specifies the source root for the index, needed for relative path + conversions. Defaults to the location of this config fragment. + In a project config, this can be a relative path}]>, + Unparsed>, + ]>>, + ]>, + + Block<"Diagnostics", [{ + Controls behavior of diagnostics (errors and warnings). + }], [ + Field<"Suppress", List, [{ + Diagnostic codes that should be suppressed. + + Valid values are: + + - `*`, to disable all diagnostics + - diagnostic codes exposed by clangd (e.g `unknown_type`, `-Wunused-result`) + - clang internal diagnostic codes (e.g. `err_unknown_type`) + - warning categories (e.g. `unused-result`) + - clang-tidy check names (e.g. `bugprone-narrowing-conversions`) + + This is a simple filter. Diagnostics can be controlled in other ways + (e.g. by disabling a clang-tidy check, or the `-Wunused` compile flag). + This often has other advantages, such as skipping some analysis. + }]>, + Field<"UnusedIncludes", String>, + Block<"ClangTidy", [{ + Controls how clang-tidy will run over the code base. + + The settings are merged with any settings found in .clang-tidy + configiration files with these ones taking precedence. + }], [ + Field<"Add", List, + [{List of checks to enable, can be globs.}]>, + Field<"Remove", List, [{ + List of checks to disable, can be globs. + + This takes precedence over Add, this supports enabling all checks from a module apart from some specific checks. + + Example to use all modernize module checks apart from use trailing return type: + + ```yaml + Diagnostics: + ClangTidy: + Add: modernize* + Remove: modernize-use-trailing-return-type + ``` + }]>, + Field<"CheckOptions", KeyValues, [{ + Key-value pairs of options for clang-tidy checks. + Available options for all checks can be found [here](https://clang.llvm.org/extra/clang-tidy/checks/list.html). + + Note the format here is slightly different to `.clang-tidy` configuration + files as we don't specify `key: , value: `. Instead just use + `: ` + + ```yaml + Diagnostics: + ClangTidy: + CheckOptions: + readability-identifier-naming.VariableCase: CamelCase + ``` + }]>, + ]>, + ]>, + + Block<"Style", [{ + Describes the style of the codebase, beyond formatting. + }], [ + Field<"FullyQualifiedNamespaces", List, [{ + Namespaces that should always be fully qualified, meaning no "using" + declarations, always spell out the whole name (with or without leading::). + All nested namespaces are affected as well. + Affects availability of the AddUsing tweak. + }]>, + ]>, + + Block<"Completion", [{ + Describes code completion preferences. + }], [ + Field<"AllScopes", Boolean, [{ + Whether code completion should include suggestions from scopes that are + not visible. The required scope prefix will be inserted. + }]>, + ]>, + + Block<"Hover", [{ + Describes hover preferences. + }], [ + Field<"ShowAKA", Boolean, + [{Whether a.k.a. types are shown for type aliases}]>, + ]>, +], /*Nullable=*/1> { + let struct_name = name; // Fragment, not FragmentBlock +} diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -343,7 +343,8 @@ #endif // Make sure exactly one of the Sources is set. unsigned SourceCount = External.File.hasValue() + - External.Server.hasValue() + *External.IsNone; + External.Server.hasValue() + + External.IsNone.hasValue(); if (SourceCount != 1) { diag(Error, "Exactly one of File, Server or None must be set.", BlockRange); @@ -361,7 +362,7 @@ return; Spec.Location = std::move(*AbsPath); } else { - assert(*External.IsNone); + assert(**External.IsNone); Spec.Kind = Config::ExternalIndexSpec::None; } if (Spec.Kind != Config::ExternalIndexSpec::None) { diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -20,12 +20,11 @@ //===----------------------------------------------------------------------===// // // To add a new configuration option, you must: -// - add its syntactic form to Fragment -// - update ConfigYAML.cpp to parse it +// - add its syntactic form to Config.td +// (This is used to generate Fragment, ConfigYAML, and documentation) // - add its semantic form to Config (in Config.h) // - update ConfigCompile.cpp to map Fragment -> Config // - make use of the option inside clangd -// - document the new option (config.md in the llvm/clangd-www repository) // //===----------------------------------------------------------------------===// @@ -34,8 +33,6 @@ #include "ConfigProvider.h" #include "llvm/ADT/Optional.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/Support/Error.h" #include "llvm/Support/SMLoc.h" #include "llvm/Support/SourceMgr.h" #include @@ -100,179 +97,7 @@ }; SourceInfo Source; - /// Conditions in the If block restrict when a Fragment applies. - /// - /// Each separate condition must match (combined with AND). - /// When one condition has multiple values, any may match (combined with OR). - /// e.g. `PathMatch: [foo/.*, bar/.*]` matches files in either directory. - /// - /// Conditions based on a file's path use the following form: - /// - if the fragment came from a project directory, the path is relative - /// - if the fragment is global (e.g. user config), the path is absolute - /// - paths always use forward-slashes (UNIX-style) - /// If no file is being processed, these conditions will not match. - struct IfBlock { - /// The file being processed must fully match a regular expression. - std::vector> PathMatch; - /// The file being processed must *not* fully match a regular expression. - std::vector> PathExclude; - - /// An unrecognized key was found while parsing the condition. - /// The condition will evaluate to false. - bool HasUnrecognizedCondition = false; - }; - IfBlock If; - - /// Conditions in the CompileFlags block affect how a file is parsed. - /// - /// clangd emulates how clang would interpret a file. - /// By default, it behaves roughly like `clang $FILENAME`, but real projects - /// usually require setting the include path (with the `-I` flag), defining - /// preprocessor symbols, configuring warnings etc. - /// Often, a compilation database specifies these compile commands. clangd - /// searches for compile_commands.json in parents of the source file. - /// - /// This section modifies how the compile command is constructed. - struct CompileFlagsBlock { - /// List of flags to append to the compile command. - std::vector> Add; - /// List of flags to remove from the compile command. - /// - /// - If the value is a recognized clang flag (like "-I") then it will be - /// removed along with any arguments. Synonyms like --include-dir= will - /// also be removed. - /// - Otherwise, if the value ends in * (like "-DFOO=*") then any argument - /// with the prefix will be removed. - /// - Otherwise any argument exactly matching the value is removed. - /// - /// In all cases, -Xclang is also removed where needed. - /// - /// Example: - /// Command: clang++ --include-directory=/usr/include -DFOO=42 foo.cc - /// Remove: [-I, -DFOO=*] - /// Result: clang++ foo.cc - /// - /// Flags added by the same CompileFlags entry will not be removed. - std::vector> Remove; - - /// Directory to search for compilation database (compile_comands.json etc). - /// Valid values are: - /// - A single path to a directory (absolute, or relative to the fragment) - /// - Ancestors: search all parent directories (the default) - /// - None: do not use a compilation database, just default flags. - llvm::Optional> CompilationDatabase; - }; - CompileFlagsBlock CompileFlags; - - /// Controls how clangd understands code outside the current file. - /// clangd's indexes provide information about symbols that isn't available - /// to clang's parser, such as incoming references. - struct IndexBlock { - /// Whether files are built in the background to produce a project index. - /// This is checked for translation units only, not headers they include. - /// Legal values are "Build" or "Skip". - llvm::Optional> Background; - /// An external index uses data source outside of clangd itself. This is - /// usually prepared using clangd-indexer. - /// Exactly one source (File/Server) should be configured. - struct ExternalBlock { - /// Whether the block is explicitly set to `None`. Can be used to clear - /// any external index specified before. - Located IsNone = false; - /// Path to an index file generated by clangd-indexer. Relative paths may - /// be used, if config fragment is associated with a directory. - llvm::Optional> File; - /// Address and port number for a clangd-index-server. e.g. - /// `123.1.1.1:13337`. - llvm::Optional> Server; - /// Source root governed by this index. Default is the directory - /// associated with the config fragment. Absolute in case of user config - /// and relative otherwise. Should always use forward-slashes. - llvm::Optional> MountPoint; - }; - llvm::Optional> External; - }; - IndexBlock Index; - - /// Controls behavior of diagnostics (errors and warnings). - struct DiagnosticsBlock { - /// Diagnostic codes that should be suppressed. - /// - /// Valid values are: - /// - *, to disable all diagnostics - /// - diagnostic codes exposed by clangd (e.g unknown_type, -Wunused-result) - /// - clang internal diagnostic codes (e.g. err_unknown_type) - /// - warning categories (e.g. unused-result) - /// - clang-tidy check names (e.g. bugprone-narrowing-conversions) - /// - /// This is a simple filter. Diagnostics can be controlled in other ways - /// (e.g. by disabling a clang-tidy check, or the -Wunused compile flag). - /// This often has other advantages, such as skipping some analysis. - std::vector> Suppress; - - /// Controls how clangd will correct "unnecessary #include directives. - /// clangd can warn if a header is `#include`d but not used, and suggest - /// removing it. - // - /// Strict means a header is unused if it does not *directly* provide any - /// symbol used in the file. Removing it may still break compilation if it - /// transitively includes headers that are used. This should be fixed by - /// including those headers directly. - /// - /// Valid values are: - /// - Strict - /// - None - llvm::Optional> UnusedIncludes; - - /// Controls how clang-tidy will run over the code base. - /// - /// The settings are merged with any settings found in .clang-tidy - /// configiration files with these ones taking precedence. - struct ClangTidyBlock { - std::vector> Add; - /// List of checks to disable. - /// Takes precedence over Add. To enable all llvm checks except include - /// order: - /// Add: llvm-* - /// Remove: llvm-include-onder - std::vector> Remove; - - /// A Key-Value pair list of options to pass to clang-tidy checks - /// These take precedence over options specified in clang-tidy - /// configuration files. Example: - /// CheckOptions: - /// readability-braces-around-statements.ShortStatementLines: 2 - std::vector, Located>> - CheckOptions; - }; - ClangTidyBlock ClangTidy; - }; - DiagnosticsBlock Diagnostics; - - // Describes the style of the codebase, beyond formatting. - struct StyleBlock { - // Namespaces that should always be fully qualified, meaning no "using" - // declarations, always spell out the whole name (with or without leading - // ::). All nested namespaces are affected as well. - // Affects availability of the AddUsing tweak. - std::vector> FullyQualifiedNamespaces; - }; - StyleBlock Style; - - /// Describes code completion preferences. - struct CompletionBlock { - /// Whether code completion should include suggestions from scopes that are - /// not visible. The required scope prefix will be inserted. - llvm::Optional> AllScopes; - }; - CompletionBlock Completion; - - /// Describes hover preferences. - struct HoverBlock { - /// Whether hover show a.k.a type. - llvm::Optional> ShowAKA; - }; - HoverBlock Hover; +#include "ConfigFragment.gen.h" }; } // namespace config diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp --- a/clang-tools-extra/clangd/ConfigYAML.cpp +++ b/clang-tools-extra/clangd/ConfigYAML.cpp @@ -8,6 +8,7 @@ #include "ConfigFragment.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SourceMgr.h" @@ -58,164 +59,124 @@ // Tries to parse N into F, returning false if it failed and we couldn't // meaningfully recover (YAML syntax error, or hard semantic error). bool parse(Fragment &F, Node &N) { - DictParser Dict("Config", this); - Dict.handle("If", [&](Node &N) { parse(F.If, N); }); - Dict.handle("CompileFlags", [&](Node &N) { parse(F.CompileFlags, N); }); - Dict.handle("Index", [&](Node &N) { parse(F.Index, N); }); - Dict.handle("Style", [&](Node &N) { parse(F.Style, N); }); - Dict.handle("Diagnostics", [&](Node &N) { parse(F.Diagnostics, N); }); - Dict.handle("Completion", [&](Node &N) { parse(F.Completion, N); }); - Dict.handle("Hover", [&](Node &N) { parse(F.Hover, N); }); - Dict.parse(N); + parse(F, N, "Config"); return !(N.failed() || HadError); } private: - void parse(Fragment::IfBlock &F, Node &N) { - DictParser Dict("If", this); - Dict.unrecognized([&](Located, Node &) { - F.HasUnrecognizedCondition = true; - return true; // Emit a warning for the unrecognized key. - }); - Dict.handle("PathMatch", [&](Node &N) { - if (auto Values = scalarValues(N)) - F.PathMatch = std::move(*Values); - }); - Dict.handle("PathExclude", [&](Node &N) { - if (auto Values = scalarValues(N)) - F.PathExclude = std::move(*Values); - }); - Dict.parse(N); - } + class DictParser; +#include "ConfigYAML.inc" - void parse(Fragment::CompileFlagsBlock &F, Node &N) { - DictParser Dict("CompileFlags", this); - Dict.handle("Add", [&](Node &N) { - if (auto Values = scalarValues(N)) - F.Add = std::move(*Values); - }); - Dict.handle("Remove", [&](Node &N) { - if (auto Values = scalarValues(N)) - F.Remove = std::move(*Values); - }); - Dict.handle("CompilationDatabase", [&](Node &N) { - F.CompilationDatabase = scalarValue(N, "CompilationDatabase"); - }); - Dict.parse(N); + // Parse Optional + void parse(llvm::Optional &F, Node &N, llvm::StringRef Name) { + if (auto *S = llvm::dyn_cast(&N)) { + llvm::SmallString<256> Buf; + F = S->getValue(Buf).str(); + return; + } + if (auto *BS = llvm::dyn_cast(&N)) { + F = BS->getValue().str(); + return; + } + warning(Name + " should be scalar", N); } - void parse(Fragment::StyleBlock &F, Node &N) { - DictParser Dict("Style", this); - Dict.handle("FullyQualifiedNamespaces", [&](Node &N) { - if (auto Values = scalarValues(N)) - F.FullyQualifiedNamespaces = std::move(*Values); - }); - Dict.parse(N); + // Parse Optional + void parse(llvm::Optional &F, Node &N, llvm::StringRef Name) { + llvm::Optional Scalar; + parse(Scalar, N, Name); + if (Scalar) { + if (auto Bool = llvm::yaml::parseBool(*Scalar)) { + F = *Bool; + } else { + warning(Name + " should be a boolean", N); + } + } } - void parse(Fragment::DiagnosticsBlock &F, Node &N) { - DictParser Dict("Diagnostics", this); - Dict.handle("Suppress", [&](Node &N) { - if (auto Values = scalarValues(N)) - F.Suppress = std::move(*Values); - }); - Dict.handle("UnusedIncludes", [&](Node &N) { - F.UnusedIncludes = scalarValue(N, "UnusedIncludes"); - }); - Dict.handle("ClangTidy", [&](Node &N) { parse(F.ClangTidy, N); }); - Dict.parse(N); + // Parse Located if we can parse T. + template + void parse(llvm::Optional> &F, Node &N, llvm::StringRef Name) { + llvm::Optional Value; + parse(Value, N, Name); + if (Value) + F.emplace(std::move(*Value), N.getSourceRange()); } - void parse(Fragment::DiagnosticsBlock::ClangTidyBlock &F, Node &N) { - DictParser Dict("ClangTidy", this); - Dict.handle("Add", [&](Node &N) { - if (auto Values = scalarValues(N)) - F.Add = std::move(*Values); - }); - Dict.handle("Remove", [&](Node &N) { - if (auto Values = scalarValues(N)) - F.Remove = std::move(*Values); - }); - Dict.handle("CheckOptions", [&](Node &N) { - DictParser CheckOptDict("CheckOptions", this); - CheckOptDict.unrecognized([&](Located &&Key, Node &Val) { - if (auto Value = scalarValue(Val, *Key)) - F.CheckOptions.emplace_back(std::move(Key), std::move(*Value)); - return false; // Don't emit a warning - }); - CheckOptDict.parse(N); - }); - Dict.parse(N); + // Parse Optional if we can parse T. + template + void parse(llvm::Optional &F, Node &N, llvm::StringRef Name) { + F.emplace(); + parse(*F, N, Name); } - void parse(Fragment::IndexBlock &F, Node &N) { - DictParser Dict("Index", this); - Dict.handle("Background", - [&](Node &N) { F.Background = scalarValue(N, "Background"); }); - Dict.handle("External", [&](Node &N) { - Fragment::IndexBlock::ExternalBlock External; - // External block can either be a mapping or a scalar value. Dispatch - // accordingly. - if (N.getType() == Node::NK_Mapping) { - parse(External, N); - } else if (N.getType() == Node::NK_Scalar || - N.getType() == Node::NK_BlockScalar) { - parse(External, scalarValue(N, "External").getValue()); - } else { - error("External must be either a scalar or a mapping.", N); - return; + // Parse vector if we can parse Optional + template + void parse(std::vector &F, Node &N, llvm::StringRef Name) { + llvm::Optional Element; + if (auto *S = llvm::dyn_cast(&N)) { + // We *must* consume all items, even on error, or the parser will assert. + std::string EltName = (Name + " element").str(); + for (auto &Child : *S) { + Element.reset(); + parse(Element, Child, EltName); + if (Element) + F.push_back(std::move(*Element)); } - F.External.emplace(std::move(External)); - F.External->Range = N.getSourceRange(); - }); - Dict.parse(N); - } - - void parse(Fragment::IndexBlock::ExternalBlock &F, - Located ExternalVal) { - if (!llvm::StringRef(*ExternalVal).equals_insensitive("none")) { - error("Only scalar value supported for External is 'None'", - ExternalVal.Range); return; } - F.IsNone = true; - F.IsNone.Range = ExternalVal.Range; + + parse(Element, N, Name); + if (Element) + F.push_back(std::move(*Element)); } - void parse(Fragment::IndexBlock::ExternalBlock &F, Node &N) { - DictParser Dict("External", this); - Dict.handle("File", [&](Node &N) { F.File = scalarValue(N, "File"); }); - Dict.handle("Server", - [&](Node &N) { F.Server = scalarValue(N, "Server"); }); - Dict.handle("MountPoint", - [&](Node &N) { F.MountPoint = scalarValue(N, "MountPoint"); }); - Dict.parse(N); + // Parse vector, T> if we can parse Optional + template + void parse(std::vector, T>> &F, Node &N, + llvm::StringRef Name) { + DictParser CheckOptDict(Name, this); + CheckOptDict.unrecognized([&](Located &&Key, Node &Val) { + llvm::Optional Value; + parse(Value, Val, *Key); + if (Value) + F.emplace_back(std::move(Key), std::move(*Value)); + return false; // Don't emit a warning + }); + CheckOptDict.parse(N); } - void parse(Fragment::CompletionBlock &F, Node &N) { - DictParser Dict("Completion", this); - Dict.handle("AllScopes", [&](Node &N) { - if (auto Value = scalarValue(N, "AllScopes")) { - if (auto AllScopes = llvm::yaml::parseBool(**Value)) - F.AllScopes = *AllScopes; - else - warning("AllScopes should be a boolean", N); - } + // By default, don't customize block parsing. + template void parseCustom(T &, DictParser &) {} + + // For the If block, record whether we see unrecognized conditions. + void parseCustom(Fragment::IfBlock &F, DictParser &Dict) { + Dict.unrecognized([&](Located, Node &) { + F.HasUnrecognizedCondition = true; + return true; // Emit a warning for the unrecognized key. }); - Dict.parse(N); } - void parse(Fragment::HoverBlock &F, Node &N) { - DictParser Dict("Hover", this); - Dict.handle("ShowAKA", [&](Node &N) { - if (auto Value = scalarValue(N, "ShowAKA")) { - if (auto ShowAKA = llvm::yaml::parseBool(**Value)) - F.ShowAKA = *ShowAKA; - else - warning("ShowAKA should be a boolean", N); - } + // For the Index block, External can be a magic scalar "None". + void parseCustom(Fragment::IndexBlock &F, DictParser &Dict) { + Dict.handle("External", [&](Node &N) { + if (auto Scalar = peekScalar(N)) + if (llvm::StringRef(*Scalar).equals_insensitive("none")) { + Fragment::IndexBlock::ExternalBlock External; + External.IsNone = {true, N.getSourceRange()}; + F.External = {External, N.getSourceRange()}; + return; + } + + parse(F.External, N, "External"); }); - Dict.parse(N); + } + + llvm::Optional peekScalar(Node &N) { + llvm::Optional Result; + if (N.getType() == Node::NK_Scalar || N.getType() == Node::NK_BlockScalar) + parse(Result, N, ""); + return Result; } // Helper for parsing mapping nodes (dictionaries). @@ -262,7 +223,8 @@ auto *K = KV.getKey(); if (!K) // YAMLParser emitted an error. continue; - auto Key = Outer->scalarValue(*K, "Dictionary key"); + llvm::Optional> Key; + Outer->parse(Key, *K, "Dictionary key"); if (!Key) continue; if (!Seen.insert(**Key).second) { @@ -315,39 +277,6 @@ } }; - // Try to parse a single scalar value from the node, warn on failure. - llvm::Optional> scalarValue(Node &N, - llvm::StringRef Desc) { - llvm::SmallString<256> Buf; - if (auto *S = llvm::dyn_cast(&N)) - return Located(S->getValue(Buf).str(), N.getSourceRange()); - if (auto *BS = llvm::dyn_cast(&N)) - return Located(BS->getValue().str(), N.getSourceRange()); - warning(Desc + " should be scalar", N); - return llvm::None; - } - - // Try to parse a list of single scalar values, or just a single value. - llvm::Optional>> scalarValues(Node &N) { - std::vector> Result; - if (auto *S = llvm::dyn_cast(&N)) { - llvm::SmallString<256> Buf; - Result.emplace_back(S->getValue(Buf).str(), N.getSourceRange()); - } else if (auto *S = llvm::dyn_cast(&N)) { - Result.emplace_back(S->getValue().str(), N.getSourceRange()); - } else if (auto *S = llvm::dyn_cast(&N)) { - // We *must* consume all items, even on error, or the parser will assert. - for (auto &Child : *S) { - if (auto Value = scalarValue(Child, "List item")) - Result.push_back(std::move(*Value)); - } - } else { - warning("Expected scalar or list of scalars", N); - return llvm::None; - } - return Result; - } - // Report a "hard" error, reflecting a config file that can never be valid. void error(const llvm::Twine &Msg, llvm::SMRange Range) { HadError = true; diff --git a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp --- a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp +++ b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp @@ -164,7 +164,8 @@ EXPECT_FALSE(Results[0].Index.External.getValue()->File.hasValue()); EXPECT_FALSE(Results[0].Index.External.getValue()->MountPoint.hasValue()); EXPECT_FALSE(Results[0].Index.External.getValue()->Server.hasValue()); - EXPECT_THAT(*Results[0].Index.External.getValue()->IsNone, testing::Eq(true)); + EXPECT_THAT(**Results[0].Index.External.getValue()->IsNone, + testing::Eq(true)); } TEST(ParseYAML, ExternalBlock) { @@ -186,48 +187,6 @@ EXPECT_THAT(*Results[0].Index.External.getValue()->Server, Val("bar")); } -TEST(ParseYAML, AllScopes) { - CapturedDiags Diags; - Annotations YAML(R"yaml( -Completion: - AllScopes: True - )yaml"); - auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); - ASSERT_THAT(Diags.Diagnostics, IsEmpty()); - ASSERT_EQ(Results.size(), 1u); - EXPECT_THAT(Results[0].Completion.AllScopes, llvm::ValueIs(Val(true))); -} - -TEST(ParseYAML, AllScopesWarn) { - CapturedDiags Diags; - Annotations YAML(R"yaml( -Completion: - AllScopes: $diagrange[[Truex]] - )yaml"); - auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); - EXPECT_THAT(Diags.Diagnostics, - ElementsAre(AllOf(DiagMessage("AllScopes should be a boolean"), - DiagKind(llvm::SourceMgr::DK_Warning), - DiagPos(YAML.range("diagrange").start), - DiagRange(YAML.range("diagrange"))))); - ASSERT_EQ(Results.size(), 1u); - EXPECT_THAT(Results[0].Completion.AllScopes, testing::Eq(llvm::None)); -} - -TEST(ParseYAML, ShowAKA) { - CapturedDiags Diags; - Annotations YAML(R"yaml( -Hover: - ShowAKA: True - )yaml"); - auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); - ASSERT_THAT(Diags.Diagnostics, IsEmpty()); - ASSERT_EQ(Results.size(), 1u); - EXPECT_THAT(Results[0].Hover.ShowAKA, llvm::ValueIs(Val(true))); -} } // namespace } // namespace config } // namespace clangd diff --git a/clang/utils/TableGen/CMakeLists.txt b/clang/utils/TableGen/CMakeLists.txt --- a/clang/utils/TableGen/CMakeLists.txt +++ b/clang/utils/TableGen/CMakeLists.txt @@ -10,6 +10,7 @@ ClangCommentHTMLTagsEmitter.cpp ClangDataCollectorsEmitter.cpp ClangDiagnosticsEmitter.cpp + ClangdConfigEmitter.cpp ClangOpcodesEmitter.cpp ClangOpenCLBuiltinEmitter.cpp ClangOptionDocEmitter.cpp diff --git a/clang/utils/TableGen/ClangdConfigEmitter.cpp b/clang/utils/TableGen/ClangdConfigEmitter.cpp new file mode 100644 --- /dev/null +++ b/clang/utils/TableGen/ClangdConfigEmitter.cpp @@ -0,0 +1,214 @@ +//=- ClangDiagnosticsEmitter.cpp - Generate clangd tables ----------*- C++ -*-// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// These tablegen backends emit clangd config tables. +// +//===----------------------------------------------------------------------===// + +#include "TableGenBackends.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/TableGen/Record.h" +#include "llvm/TableGen/TableGenBackend.h" + +namespace { +class Emitter { + auto indent() { + Indent += 2; + return llvm::make_scope_exit([this] { Indent -= 2; }); + } + llvm::raw_ostream &line() { + OS << "\n"; + OS.indent(Indent); + return OS; + } + auto sep() { + return [Sep(""), this]() mutable { + OS << Sep; + Sep = "\n"; + }; + } + + static std::string quote(llvm::StringRef S) { + std::string Buf; + llvm::raw_string_ostream OS(Buf); + OS << '"'; + llvm::printEscapedString(S, OS); + OS << '"'; + OS.flush(); + return Buf; + } + + // Returns lines from a docstring: + // - with leading and trailing whitespace-only lines removed + // - with indentation stripped based on the first (non-empty) line + static llvm::SmallVector docLines(llvm::StringRef Doc) { + llvm::Optional Indent; // from first nonempty line + llvm::SmallVector Result; + for (const llvm::StringRef Line : llvm::split(Doc, "\n")) { + if (!Indent.hasValue()) { + llvm::StringRef Trimmed = Line.ltrim(); + if (Trimmed.empty()) + continue; + Indent = Line.size() - Trimmed.size(); + Result.push_back(Trimmed); + } else { + assert(Line.take_front(*Indent).ltrim().empty() && + "Later lines less indented than first!"); + Result.push_back(Line.substr(*Indent)); + } + } + while (!Result.empty() && Result.back().ltrim().empty()) + Result.pop_back(); + return Result; + } + +public: + Emitter(llvm::raw_ostream &OS, llvm::RecordKeeper &Records) + : OS(OS), Records(Records) { + llvm::SmallVector Qual; + fillBlockQualNames(*Fragment, Qual); + } + + void emitFragment() { emitStructBody(*Fragment); } + + void emitYAMLParser() { + auto _ = indent(); + auto Sep = sep(); + for (const llvm::Record *BT : Records.getAllDerivedDefinitions("Struct")) { + Sep(); + emitParseBlock(*BT); + } + } + + void emitDocs() { emitStructDocs(*Fragment, /*Depth=*/2); } + +private: + void emitStructBody(const llvm::Record &Type) { + assert(Type.hasDirectSuperClass(Struct)); + auto _ = indent(); + auto Sep = sep(); + for (const auto *Field : Type.getValueAsListOfDefs("fields")) { + Sep(); + emitField(*Field); + } + } + + void emitField(const llvm::Record &Field) { + llvm::StringRef Doc = Field.getValueAsString("doc"); + if (!Doc.empty()) { + for (llvm::StringRef Line : docLines(Doc)) + line() << "// " << Line; + } + + const llvm::Record &Type = *Field.getValueAsDef("type"); + const llvm::Record &LeafType = leafType(Type); + if (LeafType.hasDirectSuperClass(Struct)) { + line() << "struct " << Type.getValueAsString("struct_name") << " {"; + emitStructBody(LeafType); + line() << "};"; + } + + line() << Type.getValueAsString("field_spelling") << " " + << Field.getValueAsString("name") << ";"; + } + + void emitParseBlock(const llvm::Record &Type) { + line() << "void parse(" << llvm::join(BlockQualNames.lookup(&Type), "::") + << "& F, Node& N, llvm::StringRef Name) {"; + { + auto _ = indent(); + line() << "DictParser Dict(Name, this);"; + line() << "parseCustom(F, Dict);"; + for (const llvm::Record *Field : Type.getValueAsListOfDefs("fields")) { + if (!Field->getValueAsBit("parsed")) + continue; + llvm::StringRef FieldName = Field->getValueAsString("name"); + line() << "Dict.handle(" << quote(FieldName) << ", [&](Node &N) { " + << "parse(F." << FieldName << ", N, " << quote(FieldName) << ");" + << " });"; + } + line() << "Dict.parse(N);"; + } + line() << "}"; + } + + void emitStructDocs(const llvm::Record &Type, unsigned Depth) { + for (const auto *Field : Type.getValueAsListOfDefs("fields")) { + if (!Field->getValueAsBit("documented")) + return; + emitFieldDocs(*Field, Depth); + } + } + + void emitFieldDocs(const llvm::Record &Field, unsigned Depth) { + line() << std::string(Depth, '#') << " " << Field.getValueAsString("name"); + line(); + + llvm::StringRef Doc = Field.getValueAsString("doc"); + if (!Doc.empty()) { + for (llvm::StringRef Line : docLines(Doc)) + line() << Line; + line(); + } + + const llvm::Record &Type = *Field.getValueAsDef("type"); + const llvm::Record &LeafType = leafType(Type); + if (LeafType.hasDirectSuperClass(Struct)) + emitStructDocs(LeafType, Depth + 1); + } + + const llvm::Record &leafType(const llvm::Record &Type) { + if (Type.hasDirectSuperClass(List) || Type.hasDirectSuperClass(KeyValues)) + return leafType(*Type.getValueAsDef("element")); + return Type; + } + + void fillBlockQualNames(const llvm::Record &Type, + llvm::SmallVector &Qual) { + Qual.push_back(Type.getValueAsString("struct_name")); + BlockQualNames.try_emplace(&Type, Qual); + for (const auto *Field : Type.getValueAsListOfDefs("fields")) { + const llvm::Record &LeafType = leafType(*Field->getValueAsDef("type")); + if (LeafType.hasDirectSuperClass(Struct)) + fillBlockQualNames(LeafType, Qual); + } + Qual.pop_back(); + } + + llvm::DenseMap> + BlockQualNames; + llvm::raw_ostream &OS; + llvm::RecordKeeper &Records; + const llvm::Record *Fragment = Records.getDef("Fragment"), + *Struct = Records.getClass("Struct"), + *List = Records.getClass("List"), + *KeyValues = Records.getClass("KeyValues"); + unsigned Indent = 0; +}; +} // namespace + +void clang::EmitClangdConfigHeader(llvm::RecordKeeper &Records, + llvm::raw_ostream &OS) { + llvm::emitSourceFileHeader("Clangd config fragment definition", OS); + + Emitter(OS, Records).emitFragment(); +} + +void clang::EmitClangdConfigYAML(llvm::RecordKeeper &Records, + llvm::raw_ostream &OS) { + llvm::emitSourceFileHeader("Clangd config fragment YAML parser", OS); + + Emitter(OS, Records).emitYAMLParser(); +} + +void clang::EmitClangdConfigDocs(llvm::RecordKeeper &Records, + llvm::raw_ostream &OS) { + Emitter(OS, Records).emitDocs(); +} diff --git a/clang/utils/TableGen/TableGen.cpp b/clang/utils/TableGen/TableGen.cpp --- a/clang/utils/TableGen/TableGen.cpp +++ b/clang/utils/TableGen/TableGen.cpp @@ -92,7 +92,10 @@ GenDiagDocs, GenOptDocs, GenDataCollectors, - GenTestPragmaAttributeSupportedAttributes + GenTestPragmaAttributeSupportedAttributes, + GenClangdConfigHeader, + GenClangdConfigYAML, + GenClangdConfigDocs, }; namespace { @@ -253,7 +256,13 @@ clEnumValN(GenTestPragmaAttributeSupportedAttributes, "gen-clang-test-pragma-attribute-supported-attributes", "Generate a list of attributes supported by #pragma clang " - "attribute for testing purposes"))); + "attribute for testing purposes"), + clEnumValN(GenClangdConfigHeader, "gen-clangd-config-header", + "Generate clangd Config.h struct"), + clEnumValN(GenClangdConfigYAML, "gen-clangd-config-yaml", + "Generate clangd config YAML parser"), + clEnumValN(GenClangdConfigDocs, "gen-clangd-config-docs", + "Generate clangd config documentation"))); cl::opt ClangComponent("clang-component", @@ -473,6 +482,15 @@ case GenTestPragmaAttributeSupportedAttributes: EmitTestPragmaAttributeSupportedAttributes(Records, OS); break; + case GenClangdConfigHeader: + EmitClangdConfigHeader(Records, OS); + break; + case GenClangdConfigYAML: + EmitClangdConfigYAML(Records, OS); + break; + case GenClangdConfigDocs: + EmitClangdConfigDocs(Records, OS); + break; } return false; diff --git a/clang/utils/TableGen/TableGenBackends.h b/clang/utils/TableGen/TableGenBackends.h --- a/clang/utils/TableGen/TableGenBackends.h +++ b/clang/utils/TableGen/TableGenBackends.h @@ -132,6 +132,10 @@ void EmitTestPragmaAttributeSupportedAttributes(llvm::RecordKeeper &Records, llvm::raw_ostream &OS); +void EmitClangdConfigHeader(llvm::RecordKeeper &Records, llvm::raw_ostream &OS); +void EmitClangdConfigYAML(llvm::RecordKeeper &Records, llvm::raw_ostream &OS); +void EmitClangdConfigDocs(llvm::RecordKeeper &Records, llvm::raw_ostream &OS); + } // end namespace clang #endif