Index: clang/docs/ClangFormat.rst =================================================================== --- clang/docs/ClangFormat.rst +++ clang/docs/ClangFormat.rst @@ -75,6 +75,10 @@ .clang-format file located in one of the parent directories of the source file (or current directory for stdin). + Use -style=file: to load style + configuration from a format file located at + . This path can be absolute or + relative to the working directory. Use -style="{key: value, ...}" to set specific parameters, e.g.: -style="{BasedOnStyle: llvm, IndentWidth: 8}" Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -24,6 +24,10 @@ of the input file. When the standard input is used, the search is started from the current directory. +When using ``-style=file:, :program:`clang-format` for +each input file will use the format file located at ``. +The path may be absolute or relative to the working directory. + The ``.clang-format`` file uses YAML format: .. code-block:: yaml Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -269,6 +269,10 @@ bar(); }); +- The command line argument `-style=` has been extended so that a specific + format file at location can be selected. This is supported + via the syntax: `-style=file:`. + libclang -------- Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -2480,6 +2480,7 @@ /// * "file" - Load style configuration from a file called ``.clang-format`` /// located in one of the parent directories of ``FileName`` or the current /// directory if ``FileName`` is empty. +/// * "file:" to explicitly specify the configuration file to use. /// /// \param[in] StyleName Style name to interpret according to the description /// above. Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ clang/lib/Format/Format.cpp @@ -2640,6 +2640,8 @@ ".clang-format file located in one of the parent\n" "directories of the source file (or current\n" "directory for stdin).\n" + "Use -style=file: to explicitly specify" + "the configuration file.\n" "Use -style=\"{key: value, ...}\" to set specific\n" "parameters, e.g.:\n" " -style=\"{BasedOnStyle: llvm, IndentWidth: 8}\""; @@ -2685,6 +2687,29 @@ return GuessedLanguage; } +/// Attempts to load a format file +llvm::Expected LoadConfigFile(StringRef ConfigFile, + llvm::vfs::FileSystem *FS, + bool *IsSuitable) { + FormatStyle Style = getLLVMStyle(); + *IsSuitable = false; + + llvm::ErrorOr> Text = + FS->getBufferForFile(ConfigFile.str()); + if (std::error_code EC = Text.getError()) + return make_string_error(EC.message()); + std::error_code ParserErrorCode = + parseConfiguration(Text.get()->getBuffer(), &Style); + if (ParserErrorCode == ParseError::Unsuitable) { + return Style; + } else if (ParserErrorCode != ParseError::Success) { + return make_string_error("Error reading " + ConfigFile + ": " + + ParserErrorCode.message()); + } + *IsSuitable = true; + return Style; +} + const char *DefaultFormatStyle = "file"; const char *DefaultFallbackStyle = "LLVM"; @@ -2709,6 +2734,21 @@ return Style; } + llvm::SmallVector FilesToLookFor; + // User provided clang-format file using -style=file:/path/to/format/file + // Check for explicit config filename + if (StyleName.startswith_lower("file:")) { + auto StyleNameFile = StyleName.substr(5); + bool IsSuitable = true; + auto Style = LoadConfigFile(StyleNameFile, FS, &IsSuitable); + if (Style && !IsSuitable) { + return make_string_error("Configuration file(s) do(es) not support " + + getLanguageName((*Style).Language) + ": " + + StyleNameFile); + } + return Style; + } + if (!StyleName.equals_lower("file")) { if (!getPredefinedStyle(StyleName, Style.Language, &Style)) return make_string_error("Invalid value for -style"); @@ -2721,7 +2761,6 @@ if (std::error_code EC = FS->makeAbsolute(Path)) return make_string_error(EC.message()); - llvm::SmallVector FilesToLookFor; FilesToLookFor.push_back(".clang-format"); FilesToLookFor.push_back("_clang-format"); @@ -2739,29 +2778,22 @@ llvm::sys::path::append(ConfigFile, F); LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n"); - Status = FS->status(ConfigFile.str()); - if (Status && (Status->getType() == llvm::sys::fs::file_type::regular_file)) { - llvm::ErrorOr> Text = - 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 (ec == ParseError::Unsuitable) { + bool IsSuitable; + if (auto ConfigStyle = LoadConfigFile(ConfigFile, FS, &IsSuitable)) { + if (!IsSuitable) { if (!UnsuitableConfigFiles.empty()) UnsuitableConfigFiles.append(", "); UnsuitableConfigFiles.append(ConfigFile); continue; + } else { + return *ConfigStyle; } - return make_string_error("Error reading " + ConfigFile + ": " + - ec.message()); + } else { + return ConfigStyle.takeError(); } - LLVM_DEBUG(llvm::dbgs() - << "Using configuration file " << ConfigFile << "\n"); - return Style; } } } Index: clang/unittests/Format/FormatTest.cpp =================================================================== --- clang/unittests/Format/FormatTest.cpp +++ clang/unittests/Format/FormatTest.cpp @@ -15204,6 +15204,61 @@ auto StyleTd = getStyle("file", "x.td", "llvm", "", &FS); ASSERT_TRUE((bool)StyleTd); ASSERT_EQ(*StyleTd, getLLVMStyle(FormatStyle::LK_TableGen)); + + // Test 9: explicit format file in parent directory. + ASSERT_TRUE( + FS.addFile("/e/.clang-format", 0, + llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: LLVM"))); + ASSERT_TRUE( + FS.addFile("/e/explicit.clang-format", 0, + llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google"))); + ASSERT_TRUE(FS.addFile("/e/sub/sub/sub/test.cpp", 0, + llvm::MemoryBuffer::getMemBuffer("int i;"))); + auto Style8 = getStyle("file:/e/explicit.clang-format", + "/e/sub/sub/sub/test.cpp", "LLVM", "", &FS); + ASSERT_TRUE((bool)Style8); + ASSERT_EQ(*Style8, getGoogleStyle()); + + // Test 10: relative pah to a format file + ASSERT_TRUE( + FS.addFile("../../e/explicit.clang-format", 0, + llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google"))); + auto Style9 = getStyle("file:../../e/explicit.clang-format", + "/e/sub/sub/sub/test.cpp", "LLVM", "", &FS); + ASSERT_TRUE((bool)Style9); + ASSERT_EQ(*Style9, getGoogleStyle()); + + // Test 11: missing explicit format file + auto Style10 = getStyle("file:/e/missing.clang-format", + "/e/sub/sub/sub/test.cpp", "LLVM", "", &FS); + ASSERT_FALSE((bool)Style10); + llvm::consumeError(Style10.takeError()); + + // Test 12: format file from the filesystem + SmallString<128> FormatFilePath; + std::error_code ECF = llvm::sys::fs::createTemporaryFile( + "FormatFileTest", "tpl", FormatFilePath); + EXPECT_FALSE((bool)ECF); + llvm::raw_fd_ostream FormatFileTest(FormatFilePath, ECF); + EXPECT_FALSE((bool)ECF); + FormatFileTest << "BasedOnStyle: Google\n"; + FormatFileTest.close(); + + SmallString<128> TestFilePath; + std::error_code ECT = + llvm::sys::fs::createTemporaryFile("CodeFileTest", "cc", TestFilePath); + EXPECT_FALSE((bool)ECT); + llvm::raw_fd_ostream CodeFileTest(TestFilePath, ECT); + CodeFileTest << "int i;\n"; + CodeFileTest.close(); + + std::string format_file_arg = std::string("file:") + FormatFilePath.c_str(); + auto Style11 = getStyle(format_file_arg, TestFilePath, "LLVM", "", nullptr); + + llvm::sys::fs::remove(FormatFilePath.c_str()); + llvm::sys::fs::remove(TestFilePath.c_str()); + ASSERT_TRUE((bool)Style11); + ASSERT_EQ(*Style11, getGoogleStyle()); } TEST_F(ReplacementTest, FormatCodeAfterReplacements) {