diff --git a/clang/docs/ClangFormat.rst b/clang/docs/ClangFormat.rst --- a/clang/docs/ClangFormat.rst +++ b/clang/docs/ClangFormat.rst @@ -31,6 +31,12 @@ Clang-format options: --Werror - If set, changes formatting warnings to errors + --Wno-error=unknown - If set, unknown format options are only warned about. + This can be used to enable formatting, even if the + configuration contains unknown (newer) options. + Use with caution, as this might lead to dramatically + differing format depending on an option being + supported or not. --assume-filename= - Override filename used to determine the language. When reading from stdin, clang-format assumes this filename to determine the language. diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -2478,7 +2478,8 @@ private: FormatStyleSet StyleSet; - friend std::error_code parseConfiguration(StringRef Text, FormatStyle *Style); + friend std::error_code parseConfiguration(StringRef Text, FormatStyle *Style, + bool AllowUnknownOptions); }; /// Returns a format style complying with the LLVM coding standards: @@ -2533,7 +2534,11 @@ /// /// When ``BasedOnStyle`` is not present, options not present in the YAML /// document, are retained in \p Style. -std::error_code parseConfiguration(StringRef Text, FormatStyle *Style); +/// +/// If AllowUnknownOptions is true, no errors are emitted if unknown +/// format options are occured. +std::error_code parseConfiguration(StringRef Text, FormatStyle *Style, + bool AllowUnknownOptions = false); /// Gets configuration in a YAML string. std::string configurationAsText(const FormatStyle &Style); @@ -2670,6 +2675,9 @@ /// language if the filename isn't sufficient. /// \param[in] FS The underlying file system, in which the file resides. By /// default, the file system is the real file system. +/// \param[in] AllowUnknownOptions If true, unknown format options only +/// emit a warning. If false, errors are emitted on unknown format +/// options. /// /// \returns FormatStyle as specified by ``StyleName``. If ``StyleName`` is /// "file" and no file is found, returns ``FallbackStyle``. If no style could be @@ -2677,7 +2685,8 @@ llvm::Expected getStyle(StringRef StyleName, StringRef FileName, StringRef FallbackStyle, StringRef Code = "", - llvm::vfs::FileSystem *FS = nullptr); + llvm::vfs::FileSystem *FS = nullptr, + bool AllowUnknownOptions = false); // Guesses the language from the ``FileName`` and ``Code`` to be formatted. // Defaults to FormatStyle::LK_Cpp. diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -1288,7 +1288,8 @@ return true; } -std::error_code parseConfiguration(StringRef Text, FormatStyle *Style) { +std::error_code parseConfiguration(StringRef Text, FormatStyle *Style, + bool AllowUnknownOptions) { assert(Style); FormatStyle::LanguageKind Language = Style->Language; assert(Language != FormatStyle::LK_None); @@ -1302,6 +1303,7 @@ // Mapping also uses the context to get the language to find the correct // base style. Input.setContext(Style); + Input.setAllowUnknownKeys(AllowUnknownOptions); Input >> Styles; if (Input.error()) return Input.error(); @@ -2800,8 +2802,8 @@ llvm::Expected getStyle(StringRef StyleName, StringRef FileName, StringRef FallbackStyleName, - StringRef Code, - llvm::vfs::FileSystem *FS) { + StringRef Code, llvm::vfs::FileSystem *FS, + bool AllowUnknownOptions) { if (!FS) { FS = llvm::vfs::getRealFileSystem().get(); } @@ -2813,7 +2815,8 @@ if (StyleName.startswith("{")) { // Parse YAML/JSON style from the command line. - if (std::error_code ec = parseConfiguration(StyleName, &Style)) + if (std::error_code ec = + parseConfiguration(StyleName, &Style, AllowUnknownOptions)) return make_string_error("Error parsing -style: " + ec.message()); return Style; } @@ -2857,8 +2860,8 @@ FS->getBufferForFile(ConfigFile.str()); if (std::error_code EC = Text.getError()) return make_string_error(EC.message()); - if (std::error_code ec = - parseConfiguration(Text.get()->getBuffer(), &Style)) { + if (std::error_code ec = parseConfiguration( + Text.get()->getBuffer(), &Style, AllowUnknownOptions)) { if (ec == ParseError::Unsuitable) { if (!UnsuitableConfigFiles.empty()) UnsuitableConfigFiles.append(", "); diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp --- a/clang/tools/clang-format/ClangFormat.cpp +++ b/clang/tools/clang-format/ClangFormat.cpp @@ -104,6 +104,18 @@ "SortIncludes style flag"), cl::cat(ClangFormatCategory)); +// using the full param name as Wno-error probably won't be a common use case in +// clang-format +static cl::opt AllowUnknownOptions( + "Wno-error=unknown", + cl::desc("If set, unknown format options are only warned about.\n" + "This can be used to enable formatting, even if the\n" + "configuration contains unknown (newer) options.\n" + "Use with caution, as this might lead to dramatically\n" + "differing format depending on an option being\n" + "supported or not."), + cl::init(false), cl::cat(ClangFormatCategory)); + static cl::opt Verbose("verbose", cl::desc("If set, shows the list of processed files"), cl::cat(ClangFormatCategory)); @@ -378,7 +390,8 @@ } llvm::Expected FormatStyle = - getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer()); + getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(), + nullptr, AllowUnknownOptions.getValue()); if (!FormatStyle) { llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; return true; diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -16063,6 +16063,9 @@ ASSERT_FALSE((bool)Style7); llvm::consumeError(Style7.takeError()); + auto Style7b = getStyle("file", "/d/.clang-format", "LLVM", "", &FS, true); + ASSERT_TRUE((bool)Style7b); + // Test 8: inferred per-language defaults apply. auto StyleTd = getStyle("file", "x.td", "llvm", "", &FS); ASSERT_TRUE((bool)StyleTd); diff --git a/llvm/include/llvm/Support/YAMLParser.h b/llvm/include/llvm/Support/YAMLParser.h --- a/llvm/include/llvm/Support/YAMLParser.h +++ b/llvm/include/llvm/Support/YAMLParser.h @@ -40,6 +40,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/SMLoc.h" +#include "llvm/Support/SourceMgr.h" #include #include #include @@ -51,7 +52,6 @@ namespace llvm { class MemoryBufferRef; -class SourceMgr; class raw_ostream; class Twine; @@ -100,7 +100,8 @@ return !failed(); } - void printError(Node *N, const Twine &Msg); + void printError(Node *N, const Twine &Msg, + SourceMgr::DiagKind Kind = SourceMgr::DK_Error); private: friend class Document; diff --git a/llvm/include/llvm/Support/YAMLTraits.h b/llvm/include/llvm/Support/YAMLTraits.h --- a/llvm/include/llvm/Support/YAMLTraits.h +++ b/llvm/include/llvm/Support/YAMLTraits.h @@ -789,6 +789,7 @@ virtual NodeKind getNodeKind() = 0; virtual void setError(const Twine &) = 0; + virtual void setAllowUnknownKeys(bool Allow); template void enumCase(T &Val, const char* Str, const T ConstVal) { @@ -1495,6 +1496,9 @@ void setError(HNode *hnode, const Twine &message); void setError(Node *node, const Twine &message); + void reportWarning(HNode *hnode, const Twine &message); + void reportWarning(Node *hnode, const Twine &message); + public: // These are only used by operator>>. They could be private // if those templated things could be made friends. @@ -1504,6 +1508,8 @@ /// Returns the current node that's being parsed by the YAML Parser. const Node *getCurrentNode() const; + void setAllowUnknownKeys(bool Allow) override; + private: SourceMgr SrcMgr; // must be before Strm std::unique_ptr Strm; @@ -1514,6 +1520,7 @@ std::vector BitValuesUsed; HNode *CurrentNode = nullptr; bool ScalarMatchFound = false; + bool AllowUnknownKeys = false; }; /// diff --git a/llvm/lib/Support/YAMLParser.cpp b/llvm/lib/Support/YAMLParser.cpp --- a/llvm/lib/Support/YAMLParser.cpp +++ b/llvm/lib/Support/YAMLParser.cpp @@ -1775,12 +1775,9 @@ bool Stream::failed() { return scanner->failed(); } -void Stream::printError(Node *N, const Twine &Msg) { +void Stream::printError(Node *N, const Twine &Msg, SourceMgr::DiagKind Kind) { SMRange Range = N ? N->getSourceRange() : SMRange(); - scanner->printError( Range.Start - , SourceMgr::DK_Error - , Msg - , Range); + scanner->printError(Range.Start, Kind, Msg, Range); } document_iterator Stream::begin() { diff --git a/llvm/lib/Support/YAMLTraits.cpp b/llvm/lib/Support/YAMLTraits.cpp --- a/llvm/lib/Support/YAMLTraits.cpp +++ b/llvm/lib/Support/YAMLTraits.cpp @@ -48,6 +48,10 @@ Ctxt = Context; } +void IO::setAllowUnknownKeys(bool Allow) { + llvm_unreachable("Only supported for Input"); +} + //===----------------------------------------------------------------------===// // Input //===----------------------------------------------------------------------===// @@ -197,8 +201,12 @@ return; for (const auto &NN : MN->Mapping) { if (!is_contained(MN->ValidKeys, NN.first())) { - setError(NN.second.get(), Twine("unknown key '") + NN.first() + "'"); - break; + HNode *ReportNode = NN.second.get(); + if (!AllowUnknownKeys) { + setError(ReportNode, Twine("unknown key '") + NN.first() + "'"); + break; + } else + reportWarning(ReportNode, Twine("unknown key '") + NN.first() + "'"); } } } @@ -370,6 +378,11 @@ EC = make_error_code(errc::invalid_argument); } +void Input::reportWarning(HNode *hnode, const Twine &message) { + assert(hnode && "HNode must not be NULL"); + Strm->printError(hnode->_node, message, SourceMgr::DK_Warning); +} + std::unique_ptr Input::createHNodes(Node *N) { SmallString<128> StringStorage; if (ScalarNode *SN = dyn_cast(N)) { @@ -428,6 +441,8 @@ setError(CurrentNode, Message); } +void Input::setAllowUnknownKeys(bool Allow) { AllowUnknownKeys = Allow; } + bool Input::canElideEmptySequence() { return false; } diff --git a/llvm/unittests/ObjectYAML/YAMLTest.cpp b/llvm/unittests/ObjectYAML/YAMLTest.cpp --- a/llvm/unittests/ObjectYAML/YAMLTest.cpp +++ b/llvm/unittests/ObjectYAML/YAMLTest.cpp @@ -35,3 +35,21 @@ YOut << BH; EXPECT_NE(OS.str().find("''"), StringRef::npos); } + +TEST(ObjectYAML, UnknownOption) { + StringRef InputYAML = "InvalidKey: InvalidValue\n" + "Binary: AAAA\n"; + BinaryHolder BH; + yaml::Input Input(InputYAML); + // test 1: default in trying to parse invalid key is an error case. + Input >> BH; + EXPECT_EQ(Input.error().value(), 22); + + // test 2: only warn about invalid key if actively set. + yaml::Input Input2(InputYAML); + BinaryHolder BH2; + Input2.setAllowUnknownKeys(true); + Input2 >> BH2; + EXPECT_EQ(BH2.Binary, yaml::BinaryRef("AAAA")); + EXPECT_EQ(Input2.error().value(), 0); +}