namespace llvm {
namespace yaml {
+template <>
+struct ScalarEnumerationTraits<clang::format::FormatStyle::LanguageKind> {
+ static void enumeration(IO &IO,
+ clang::format::FormatStyle::LanguageKind &Value) {
+ IO.enumCase(Value, "Cpp", clang::format::FormatStyle::LK_Cpp);
+ IO.enumCase(Value, "JavaScript", clang::format::FormatStyle::LK_JavaScript);
+ }
+};
+
template <>
struct ScalarEnumerationTraits<clang::format::FormatStyle::LanguageStandard> {
static void enumeration(IO &IO,
} else {
StringRef BasedOnStyle;
IO.mapOptional("BasedOnStyle", BasedOnStyle);
- if (!BasedOnStyle.empty())
+ if (!BasedOnStyle.empty()) {
+ clang::format::FormatStyle::LanguageKind Language = Style.Language;
if (!clang::format::getPredefinedStyle(BasedOnStyle, &Style)) {
IO.setError(Twine("Unknown value for BasedOnStyle: ", BasedOnStyle));
return;
}
+ Style.Language = Language;
+ }
}
+ IO.mapOptional("Language", Style.Language);
IO.mapOptional("AccessModifierOffset", Style.AccessModifierOffset);
IO.mapOptional("ConstructorInitializerIndentWidth",
Style.ConstructorInitializerIndentWidth);
IO.mapOptional("ContinuationIndentWidth", Style.ContinuationIndentWidth);
}
};
+
+// Allows to read vector<FormatStyle> while keeping default values.
+// Elements will be written or read starting from the 1st element.
+// When writing, the 0th element is ignored.
+// When reading, keys that are not present in the serialized form will be
+// copied from the 0th element of the vector. If the first element had no
+// Language specified, it will be treated as the default one for the following
+// elements.
+template <>
+struct DocumentListTraits<std::vector<clang::format::FormatStyle> > {
+ static size_t size(IO &io, std::vector<clang::format::FormatStyle> &Seq) {
+ return Seq.size() - 1;
+ }
+ static clang::format::FormatStyle &
+ element(IO &io, std::vector<clang::format::FormatStyle> &Seq, size_t Index) {
+ if (Index + 2 > Seq.size()) {
+ assert(Index + 2 == Seq.size() + 1);
+ clang::format::FormatStyle Template;
+ if (Seq.size() > 1 &&
+ Seq[1].Language == clang::format::FormatStyle::LK_None) {
+ Template = Seq[1];
+ } else {
+ Template = Seq[0];
+ Template.Language = clang::format::FormatStyle::LK_None;
+ }
+ Seq.resize(Index + 2, Template);
+ }
+ return Seq[Index + 1];
+ }
+};
}
}
FormatStyle getLLVMStyle() {
FormatStyle LLVMStyle;
+ LLVMStyle.Language = FormatStyle::LK_Cpp;
LLVMStyle.AccessModifierOffset = -2;
LLVMStyle.AlignEscapedNewlinesLeft = false;
LLVMStyle.AlignTrailingComments = true;
FormatStyle getGoogleStyle() {
FormatStyle GoogleStyle;
+ GoogleStyle.Language = FormatStyle::LK_Cpp;
GoogleStyle.AccessModifierOffset = -1;
GoogleStyle.AlignEscapedNewlinesLeft = true;
GoogleStyle.AlignTrailingComments = true;
}
llvm::error_code parseConfiguration(StringRef Text, FormatStyle *Style) {
+ assert(Style);
+ assert(Style->Language != FormatStyle::LK_None);
if (Text.trim().empty())
return llvm::make_error_code(llvm::errc::invalid_argument);
+
+ std::vector<FormatStyle> Styles;
+ // DocumentListTraits<vector<FormatStyle>> uses 0th element as the default one
+ // for the fields, keys for which are missing from the configuration.
+ Styles.push_back(*Style);
llvm::yaml::Input Input(Text);
- Input >> *Style;
- return Input.error();
+ Input >> Styles;
+ if (Input.error())
+ return Input.error();
+
+ for (unsigned i = 1; i < Styles.size(); ++i) {
+ // Ensures that only the first configuration can skip the Language option.
+ if (Styles[i].Language == FormatStyle::LK_None && i != 1)
+ return llvm::make_error_code(llvm::errc::invalid_argument);
+ // Ensure that each language is configured at most once.
+ for (unsigned j = 1; j < i; ++j) {
+ if (Styles[i].Language == Styles[j].Language)
+ return llvm::make_error_code(llvm::errc::invalid_argument);
+ }
+ }
+ // Look for a suitable configuration starting from the end, so we can
+ // find the configuration for the specific language first, and the default
+ // configuration (which can only be at slot 1) after it.
+ for (unsigned i = Styles.size() - 1; i > 0; --i) {
+ if (Styles[i].Language == Styles[0].Language ||
+ Styles[i].Language == FormatStyle::LK_None) {
+ *Style = Styles[i];
+ Style->Language = Styles[0].Language;
+ return llvm::make_error_code(llvm::errc::success);
+ }
+ }
+ return llvm::make_error_code(llvm::errc::not_supported);
}
std::string configurationAsText(const FormatStyle &Style) {
private:
void tryMergePreviousTokens() {
- tryMerge_TMacro() || tryMergeJavaScriptIdentityOperators();
+ if (tryMerge_TMacro())
+ return;
+
+ if (Style.Language == FormatStyle::LK_JavaScript) {
+ static tok::TokenKind JSIdentity[] = { tok::equalequal, tok::equal };
+ static tok::TokenKind JSNotIdentity[] = { tok::exclaimequal, tok::equal };
+ static tok::TokenKind JSShiftEqual[] = { tok::greater, tok::greater,
+ tok::greaterequal };
+ // FIXME: We probably need to change token type to mimic operator with the
+ // correct priority.
+ if (tryMergeTokens(JSIdentity))
+ return;
+ if (tryMergeTokens(JSNotIdentity))
+ return;
+ if (tryMergeTokens(JSShiftEqual))
+ return;
+ }
}
- bool tryMergeJavaScriptIdentityOperators() {
- if (Tokens.size() < 2)
- return false;
- FormatToken &First = *Tokens[Tokens.size() - 2];
- if (!First.isOneOf(tok::exclaimequal, tok::equalequal))
- return false;
- FormatToken &Second = *Tokens.back();
- if (!Second.is(tok::equal))
+ bool tryMergeTokens(ArrayRef<tok::TokenKind> Kinds) {
+ if (Tokens.size() < Kinds.size())
return false;
- if (Second.WhitespaceRange.getBegin() != Second.WhitespaceRange.getEnd())
+
+ SmallVectorImpl<FormatToken *>::const_iterator First =
+ Tokens.end() - Kinds.size();
+ if (!First[0]->is(Kinds[0]))
return false;
- First.TokenText =
- StringRef(First.TokenText.data(), First.TokenText.size() + 1);
- First.ColumnWidth += 1;
- Tokens.pop_back();
+ unsigned AddLength = 0;
+ for (unsigned i = 1; i < Kinds.size(); ++i) {
+ if (!First[i]->is(Kinds[i]) || First[i]->WhitespaceRange.getBegin() !=
+ First[i]->WhitespaceRange.getEnd())
+ return false;
+ AddLength += First[i]->TokenText.size();
+ }
+ Tokens.resize(Tokens.size() - Kinds.size() + 1);
+ First[0]->TokenText = StringRef(First[0]->TokenText.data(),
+ First[0]->TokenText.size() + AddLength);
+ First[0]->ColumnWidth += AddLength;
return true;
}
}
};
+static StringRef getLanguageName(FormatStyle::LanguageKind Language) {
+ switch (Language) {
+ case FormatStyle::LK_Cpp:
+ return "C++";
+ case FormatStyle::LK_JavaScript:
+ return "JavaScript";
+ default:
+ return "Unknown";
+ }
+}
+
class Formatter : public UnwrappedLineConsumer {
public:
Formatter(const FormatStyle &Style, Lexer &Lex, SourceManager &SourceMgr,
<< (Encoding == encoding::Encoding_UTF8 ? "UTF8"
: "unknown")
<< "\n");
+ DEBUG(llvm::dbgs() << "Language: " << getLanguageName(Style.Language)
+ << "\n");
}
tooling::Replacements format() {
"parameters, e.g.:\n"
" -style=\"{BasedOnStyle: llvm, IndentWidth: 8}\"";
+static void fillLanguageByFileName(StringRef FileName, FormatStyle *Style) {
+ if (FileName.endswith_lower(".c") || FileName.endswith_lower(".h") ||
+ FileName.endswith_lower(".cpp") || FileName.endswith_lower(".hpp") ||
+ FileName.endswith_lower(".cc") || FileName.endswith_lower(".hh") ||
+ FileName.endswith_lower(".cxx") || FileName.endswith_lower(".hxx") ||
+ FileName.endswith_lower(".m") || FileName.endswith_lower(".mm")) {
+ Style->Language = FormatStyle::LK_Cpp;
+ }
+ if (FileName.endswith_lower(".js")) {
+ Style->Language = FormatStyle::LK_JavaScript;
+ }
+}
+
FormatStyle getStyle(StringRef StyleName, StringRef FileName) {
+ // FIXME: Configure fallback style from outside (add a command line option).
// Fallback style in case the rest of this function can't determine a style.
StringRef FallbackStyle = "LLVM";
FormatStyle Style;
getPredefinedStyle(FallbackStyle, &Style);
+ fillLanguageByFileName(FileName, &Style);
if (StyleName.startswith("{")) {
// Parse YAML/JSON style from the command line.
if (!getPredefinedStyle(StyleName, &Style))
llvm::errs() << "Invalid value for -style, using " << FallbackStyle
<< " style\n";
+ fillLanguageByFileName(FileName, &Style);
return Style;
}
+ SmallString<128> UnsuitableConfigFiles;
SmallString<128> Path(FileName);
llvm::sys::fs::make_absolute(Path);
for (StringRef Directory = Path; !Directory.empty();
continue;
}
if (llvm::error_code ec = parseConfiguration(Text->getBuffer(), &Style)) {
- llvm::errs() << "Error reading " << ConfigFile << ": " << ec.message()
- << "\n";
+ if (ec == llvm::errc::not_supported) {
+ if (!UnsuitableConfigFiles.empty())
+ UnsuitableConfigFiles.append(", ");
+ UnsuitableConfigFiles.append(ConfigFile);
+ } else {
+ llvm::errs() << "Error reading " << ConfigFile << ": " << ec.message()
+ << "\n";
+ }
continue;
}
DEBUG(llvm::dbgs() << "Using configuration file " << ConfigFile << "\n");
}
llvm::errs() << "Can't find usable .clang-format, using " << FallbackStyle
<< " style\n";
+ if (!UnsuitableConfigFiles.empty()) {
+ llvm::errs() << "Configuration file(s) do(es) not support "
+ << getLanguageName(Style.Language) << ": "
+ << UnsuitableConfigFiles << "\n";
+ }
return Style;
}
EXPECT_FALSE(getPredefinedStyle("qwerty", &Styles[0]));
}
-TEST_F(FormatTest, ParsesConfiguration) {
- FormatStyle Style = {};
#define CHECK_PARSE(TEXT, FIELD, VALUE) \
EXPECT_NE(VALUE, Style.FIELD); \
EXPECT_EQ(0, parseConfiguration(TEXT, &Style).value()); \
EXPECT_EQ(0, parseConfiguration(#FIELD ": false", &Style).value()); \
EXPECT_FALSE(Style.FIELD);
+TEST_F(FormatTest, ParsesConfiguration) {
+ FormatStyle Style = {};
+ Style.Language = FormatStyle::LK_Cpp;
CHECK_PARSE_BOOL(AlignEscapedNewlinesLeft);
CHECK_PARSE_BOOL(AlignTrailingComments);
CHECK_PARSE_BOOL(AllowAllParametersOfDeclarationOnNextLine);
FormatStyle::NI_Inner);
CHECK_PARSE("NamespaceIndentation: All", NamespaceIndentation,
FormatStyle::NI_All);
+}
+
+TEST_F(FormatTest, ParsesConfigurationWithLanguages) {
+ FormatStyle Style = {};
+ Style.Language = FormatStyle::LK_Cpp;
+ CHECK_PARSE("Language: Cpp\n"
+ "IndentWidth: 12",
+ IndentWidth, 12u);
+ EXPECT_EQ(llvm::errc::not_supported,
+ parseConfiguration("Language: JavaScript\n"
+ "IndentWidth: 34",
+ &Style));
+ EXPECT_EQ(12u, Style.IndentWidth);
+ CHECK_PARSE("IndentWidth: 56", IndentWidth, 56u);
+ EXPECT_EQ(FormatStyle::LK_Cpp, Style.Language);
+
+ Style.Language = FormatStyle::LK_JavaScript;
+ CHECK_PARSE("Language: JavaScript\n"
+ "IndentWidth: 12",
+ IndentWidth, 12u);
+ CHECK_PARSE("IndentWidth: 23", IndentWidth, 23u);
+ EXPECT_EQ(llvm::errc::not_supported, parseConfiguration("Language: Cpp\n"
+ "IndentWidth: 34",
+ &Style));
+ EXPECT_EQ(23u, Style.IndentWidth);
+ CHECK_PARSE("IndentWidth: 56", IndentWidth, 56u);
+ EXPECT_EQ(FormatStyle::LK_JavaScript, Style.Language);
+
+ CHECK_PARSE("BasedOnStyle: LLVM\n"
+ "IndentWidth: 67",
+ IndentWidth, 67u);
+
+ CHECK_PARSE("---\n"
+ "Language: JavaScript\n"
+ "IndentWidth: 12\n"
+ "---\n"
+ "Language: Cpp\n"
+ "IndentWidth: 34\n"
+ "...\n",
+ IndentWidth, 12u);
+
+ Style.Language = FormatStyle::LK_Cpp;
+ CHECK_PARSE("---\n"
+ "Language: JavaScript\n"
+ "IndentWidth: 12\n"
+ "---\n"
+ "Language: Cpp\n"
+ "IndentWidth: 34\n"
+ "...\n",
+ IndentWidth, 34u);
+ CHECK_PARSE("---\n"
+ "IndentWidth: 78\n"
+ "---\n"
+ "Language: JavaScript\n"
+ "IndentWidth: 56\n"
+ "...\n",
+ IndentWidth, 78u);
+
+ Style.ColumnLimit = 123;
+ Style.IndentWidth = 234;
+ Style.BreakBeforeBraces = FormatStyle::BS_Linux;
+ Style.TabWidth = 345;
+ EXPECT_EQ(llvm::errc::success,
+ parseConfiguration("---\n"
+ "IndentWidth: 456\n"
+ "BreakBeforeBraces: Allman\n"
+ "---\n"
+ "Language: JavaScript\n"
+ "IndentWidth: 111\n"
+ "TabWidth: 111\n"
+ "---\n"
+ "Language: Cpp\n"
+ "BreakBeforeBraces: Stroustrup\n"
+ "TabWidth: 789\n"
+ "...\n",
+ &Style));
+ EXPECT_EQ(123u, Style.ColumnLimit);
+ EXPECT_EQ(456u, Style.IndentWidth);
+ EXPECT_EQ(FormatStyle::BS_Stroustrup, Style.BreakBeforeBraces);
+ EXPECT_EQ(789u, Style.TabWidth);
+
+
+ EXPECT_EQ(llvm::errc::invalid_argument,
+ parseConfiguration("---\n"
+ "Language: JavaScript\n"
+ "IndentWidth: 56\n"
+ "---\n"
+ "IndentWidth: 78\n"
+ "...\n",
+ &Style));
+ EXPECT_EQ(llvm::errc::invalid_argument,
+ parseConfiguration("---\n"
+ "Language: JavaScript\n"
+ "IndentWidth: 56\n"
+ "---\n"
+ "Language: JavaScript\n"
+ "IndentWidth: 78\n"
+ "...\n",
+ &Style));
+
+ EXPECT_EQ(FormatStyle::LK_Cpp, Style.Language);
+}
#undef CHECK_PARSE
#undef CHECK_PARSE_BOOL
-}
TEST_F(FormatTest, ConfigurationRoundTripTest) {
FormatStyle Style = getLLVMStyle();
std::string YAML = configurationAsText(Style);
FormatStyle ParsedStyle = {};
+ ParsedStyle.Language = FormatStyle::LK_Cpp;
EXPECT_EQ(0, parseConfiguration(YAML, &ParsedStyle).value());
EXPECT_EQ(Style, ParsedStyle);
}
}
TEST_F(FormatTest, UnderstandsJavaScript) {
- verifyFormat("a == = b;");
- verifyFormat("a != = b;");
-
- verifyFormat("a === b;");
- verifyFormat("aaaaaaa ===\n b;", getLLVMStyleWithColumns(10));
- verifyFormat("a !== b;");
- verifyFormat("aaaaaaa !==\n b;", getLLVMStyleWithColumns(10));
+ FormatStyle JS = getLLVMStyle();
+ FormatStyle JS10Columns = getLLVMStyleWithColumns(10);
+ FormatStyle JS20Columns = getLLVMStyleWithColumns(20);
+ JS.Language = JS10Columns.Language = JS20Columns.Language =
+ FormatStyle::LK_JavaScript;
+
+ verifyFormat("a == = b;", JS);
+ verifyFormat("a != = b;", JS);
+
+ verifyFormat("a === b;", JS);
+ verifyFormat("aaaaaaa ===\n b;", JS10Columns);
+ verifyFormat("a !== b;", JS);
+ verifyFormat("aaaaaaa !==\n b;", JS10Columns);
verifyFormat("if (a + b + c +\n"
" d !==\n"
" e + f + g)\n"
" q();",
- getLLVMStyleWithColumns(20));
+ JS20Columns);
+
+ verifyFormat("a >> >= b;", JS);
+
+ verifyFormat("a >>> b;", JS);
+ verifyFormat("aaaaaaa >>>\n b;", JS10Columns);
+ verifyFormat("a >>>= b;", JS);
+ verifyFormat("aaaaaaa >>>=\n b;", JS10Columns);
+ verifyFormat("if (a + b + c +\n"
+ " d >>>\n"
+ " e + f + g)\n"
+ " q();",
+ JS20Columns);
}
} // end namespace tooling