[clang-format] Add option to explicitly specify a config file

This diff extends the -style=file option to allow a config file to be specified explicitly. This is useful (for instance) when adding IDE commands to reformat code to a personal style.

Usage: `clang-format -style=file:<path/to/config/file> ...`

Reviewed By: HazardyKnusperkeks, curdeius, MyDeveloperDay, zwliew

Differential Revision: https://reviews.llvm.org/D72326
This commit is contained in:
Zhao Wei Liew
2022-01-03 11:37:20 +01:00
committed by Marek Kurdej
parent 0090cd4e7a
commit b9e173fcd4
6 changed files with 118 additions and 7 deletions

View File

@@ -82,6 +82,10 @@ to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# code.
.clang-format file located in one of the parent
directories of the source file (or current
directory for stdin).
Use -style=file:<format_file_path> to load style
configuration from a format file located at
<format_file_path>. 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}"

View File

@@ -32,6 +32,10 @@ try to find the ``.clang-format`` file located in the closest parent directory
of the input file. When the standard input is used, the search is started from
the current directory.
When using ``-style=file:<format_file_path>``, :program:`clang-format` for
each input file will use the format file located at `<format_file_path>`.
The path may be absolute or relative to the working directory.
The ``.clang-format`` file uses YAML format:
.. code-block:: yaml

View File

@@ -301,6 +301,10 @@ clang-format
space before parentheses. The custom options can be set using
``SpaceBeforeParensOptions``.
- The command line argument `-style=<string>` has been extended so that a specific
format file at location <format_file_path> can be selected. This is supported
via the syntax: `-style=file:<format_file_path>`.
- Improved C++20 Modules and Coroutines support.
libclang

View File

@@ -4066,6 +4066,8 @@ extern const char *DefaultFallbackStyle;
/// * "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:<format_file_path>" to explicitly specify the configuration file to
/// use.
///
/// \param[in] StyleName Style name to interpret according to the description
/// above.

View File

@@ -3181,6 +3181,8 @@ const char *StyleOptionHelpDescription =
".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:<format_file_path> to explicitly specify"
"the configuration file.\n"
"Use -style=\"{key: value, ...}\" to set specific\n"
"parameters, e.g.:\n"
" -style=\"{BasedOnStyle: llvm, IndentWidth: 8}\"";
@@ -3233,6 +3235,18 @@ const char *DefaultFormatStyle = "file";
const char *DefaultFallbackStyle = "LLVM";
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
loadAndParseConfigFile(StringRef ConfigFile, llvm::vfs::FileSystem *FS,
FormatStyle *Style, bool AllowUnknownOptions) {
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
FS->getBufferForFile(ConfigFile.str());
if (auto EC = Text.getError())
return EC;
if (auto EC = parseConfiguration(*Text.get(), Style, AllowUnknownOptions))
return EC;
return Text;
}
llvm::Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName,
StringRef FallbackStyleName,
StringRef Code, llvm::vfs::FileSystem *FS,
@@ -3263,6 +3277,28 @@ llvm::Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName,
return Style;
}
// User provided clang-format file using -style=file:path/to/format/file.
if (!Style.InheritsParentConfig &&
StyleName.startswith_insensitive("file:")) {
auto ConfigFile = StyleName.substr(5);
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
loadAndParseConfigFile(ConfigFile, FS, &Style, AllowUnknownOptions);
if (auto EC = Text.getError())
return make_string_error("Error reading " + ConfigFile + ": " +
EC.message());
LLVM_DEBUG(llvm::dbgs()
<< "Using configuration file " << ConfigFile << "\n");
if (!Style.InheritsParentConfig)
return Style;
// Search for parent configs starting from the parent directory of
// ConfigFile.
FileName = ConfigFile;
ChildFormatTextToApply.emplace_back(std::move(*Text));
}
// If the style inherits the parent configuration it is a command line
// configuration, which wants to inherit, so we have to skip the check of the
// StyleName.
@@ -3318,19 +3354,16 @@ llvm::Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName,
if (Status &&
(Status->getType() == llvm::sys::fs::file_type::regular_file)) {
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> 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(), &Style, AllowUnknownOptions)) {
if (ec == ParseError::Unsuitable) {
loadAndParseConfigFile(ConfigFile, FS, &Style, AllowUnknownOptions);
if (auto EC = Text.getError()) {
if (EC == ParseError::Unsuitable) {
if (!UnsuitableConfigFiles.empty())
UnsuitableConfigFiles.append(", ");
UnsuitableConfigFiles.append(ConfigFile);
continue;
}
return make_string_error("Error reading " + ConfigFile + ": " +
ec.message());
EC.message());
}
LLVM_DEBUG(llvm::dbgs()
<< "Using configuration file " << ConfigFile << "\n");

View File

@@ -21579,6 +21579,70 @@ TEST(FormatStyle, GetStyleOfFile) {
Style.IndentWidth = 7;
return Style;
}());
// Test 9.9: use inheritance from a specific config file.
Style9 = getStyle("file:/e/sub/sub/.clang-format", "/e/sub/sub/code.cpp",
"none", "", &FS);
ASSERT_TRUE(static_cast<bool>(Style9));
ASSERT_EQ(*Style9, SubSubStyle);
}
TEST(FormatStyle, GetStyleOfSpecificFile) {
llvm::vfs::InMemoryFileSystem FS;
// Specify absolute path to a format file in a 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 Style = getStyle("file:/e/explicit.clang-format",
"/e/sub/sub/sub/test.cpp", "LLVM", "", &FS);
ASSERT_TRUE(static_cast<bool>(Style));
ASSERT_EQ(*Style, getGoogleStyle());
// Specify relative path to a format file.
ASSERT_TRUE(
FS.addFile("../../e/explicit.clang-format", 0,
llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google")));
Style = getStyle("file:../../e/explicit.clang-format",
"/e/sub/sub/sub/test.cpp", "LLVM", "", &FS);
ASSERT_TRUE(static_cast<bool>(Style));
ASSERT_EQ(*Style, getGoogleStyle());
// Specify path to a format file that does not exist.
Style = getStyle("file:/e/missing.clang-format", "/e/sub/sub/sub/test.cpp",
"LLVM", "", &FS);
ASSERT_FALSE(static_cast<bool>(Style));
llvm::consumeError(Style.takeError());
// Specify path to a file on 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();
Style = getStyle(format_file_arg, TestFilePath, "LLVM", "", nullptr);
llvm::sys::fs::remove(FormatFilePath.c_str());
llvm::sys::fs::remove(TestFilePath.c_str());
ASSERT_TRUE(static_cast<bool>(Style));
ASSERT_EQ(*Style, getGoogleStyle());
}
TEST_F(ReplacementTest, FormatCodeAfterReplacements) {