===============
:program:`clang-format` is located in `clang/tools/clang-format` and can be used
-to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.
+to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# code.
.. code-block:: console
$ clang-format -help
- OVERVIEW: A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.
+ OVERVIEW: A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# code.
If no arguments are specified, it formats the code from standard input
and writes the result to the standard output.
* ``LK_JavaScript`` (in configuration: ``JavaScript``)
Should be used for JavaScript.
+ * ``LK_Json`` (in configuration: ``Json``)
+ Should be used for JSON.
+
* ``LK_ObjC`` (in configuration: ``ObjC``)
Should be used for Objective-C, Objective-C++.
- Option ``AlignArrayOfStructure`` has been added to allow for ordering array-like
initializers.
+- Support for formatting JSON file (\*.json) has been added to clang-format.
+
libclang
--------
LK_Java,
/// Should be used for JavaScript.
LK_JavaScript,
+ /// Should be used for JSON.
+ LK_Json,
/// Should be used for Objective-C, Objective-C++.
LK_ObjC,
/// Should be used for Protocol Buffers
};
bool isCpp() const { return Language == LK_Cpp || Language == LK_ObjC; }
bool isCSharp() const { return Language == LK_CSharp; }
+ bool isJson() const { return Language == LK_Json; }
/// Language, this format style is targeted at.
LanguageKind Language;
return "Java";
case FormatStyle::LK_JavaScript:
return "JavaScript";
+ case FormatStyle::LK_Json:
+ return "Json";
case FormatStyle::LK_Proto:
return "Proto";
case FormatStyle::LK_TableGen:
LineState &State, bool AllowBreak) {
unsigned StartColumn = State.Column - Current.ColumnWidth;
if (Current.isStringLiteral()) {
- // FIXME: String literal breaking is currently disabled for C#, Java and
- // JavaScript, as it requires strings to be merged using "+" which we
+ // FIXME: String literal breaking is currently disabled for C#, Java, Json
+ // and JavaScript, as it requires strings to be merged using "+" which we
// don't support.
if (Style.Language == FormatStyle::LK_Java ||
Style.Language == FormatStyle::LK_JavaScript || Style.isCSharp() ||
- !Style.BreakStringLiterals || !AllowBreak)
+ Style.isJson() || !Style.BreakStringLiterals || !AllowBreak)
return nullptr;
// Don't break string literals inside preprocessor directives (except for
IO.enumCase(Value, "TableGen", FormatStyle::LK_TableGen);
IO.enumCase(Value, "TextProto", FormatStyle::LK_TextProto);
IO.enumCase(Value, "CSharp", FormatStyle::LK_CSharp);
+ IO.enumCase(Value, "Json", FormatStyle::LK_Json);
}
};
if (Language == FormatStyle::LK_TableGen) {
LLVMStyle.SpacesInContainerLiterals = false;
}
+ if (LLVMStyle.isJson()) {
+ LLVMStyle.ColumnLimit = 0;
+ }
return LLVMStyle;
}
if (Expanded.Language == FormatStyle::LK_JavaScript && isMpegTS(Code))
return {tooling::Replacements(), 0};
+ // JSON only needs the formatting passing.
+ if (Style.isJson()) {
+ std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size()));
+ auto Env =
+ std::make_unique<Environment>(Code, FileName, Ranges, FirstStartColumn,
+ NextStartColumn, LastStartColumn);
+ // Perform the actual formatting pass.
+ tooling::Replacements Replaces =
+ Formatter(*Env, Style, Status).process().first;
+ // add a replacement to remove the "x = " from the result.
+ if (!Replaces.add(tooling::Replacement(FileName, 0, 4, ""))) {
+ // apply the reformatting changes and the removal of "x = ".
+ if (applyAllReplacements(Code, Replaces)) {
+ return {Replaces, 0};
+ }
+ }
+ return {tooling::Replacements(), 0};
+ }
+
typedef std::function<std::pair<tooling::Replacements, unsigned>(
const Environment &)>
AnalyzerPass;
return FormatStyle::LK_TableGen;
if (FileName.endswith_insensitive(".cs"))
return FormatStyle::LK_CSharp;
+ if (FileName.endswith_insensitive(".json"))
+ return FormatStyle::LK_Json;
return FormatStyle::LK_Cpp;
}
}
} // namespace format
-} // namespace clang
\ No newline at end of file
+} // namespace clang
const FormatToken &Right) {
if (Left.is(tok::kw_return) && Right.isNot(tok::semi))
return true;
+ if (Style.isJson() && Left.is(tok::string_literal) && Right.is(tok::colon))
+ return false;
if (Left.is(Keywords.kw_assert) && Style.Language == FormatStyle::LK_Java)
return true;
if (Style.ObjCSpaceAfterProperty && Line.Type == LT_ObjCProperty &&
// and "%d %d"
if (Left.is(tok::numeric_constant) && Right.is(tok::percent))
return HasExistingWhitespace();
+ } else if (Style.isJson()) {
+ if (Right.is(tok::colon))
+ return false;
} else if (Style.isCSharp()) {
// Require spaces around '{' and before '}' unless they appear in
// interpolated strings. Interpolated strings are merged into a single token
return true;
}
+ // Basic JSON newline processing.
+ if (Style.isJson()) {
+ // Always break after a JSON record opener.
+ // {
+ // }
+ if (Left.is(TT_DictLiteral) && Left.is(tok::l_brace))
+ return true;
+ // Always break after a JSON array opener.
+ // [
+ // ]
+ if (Left.is(TT_ArrayInitializerLSquare) && Left.is(tok::l_square) &&
+ !Right.is(tok::r_square))
+ return true;
+ // Always break afer successive entries.
+ // 1,
+ // 2
+ if (Left.is(tok::comma))
+ return true;
+ }
+
// If the last token before a '}', ']', or ')' is a comma or a trailing
// comment, the intention is to insert a line break after it in order to make
// shuffling around entries easier. Import statements, especially in
unsigned CursorPosition = Cursor;
Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
AssumedFileName, &CursorPosition);
+
+ // To format JSON insert a variable to trick the code into thinking its
+ // JavaScript.
+ if (FormatStyle->isJson()) {
+ auto Err = Replaces.add(tooling::Replacement(
+ tooling::Replacement(AssumedFileName, 0, 0, "x = ")));
+ if (Err) {
+ llvm::errs() << "Bad Json variable insertion\n";
+ }
+ }
+
auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
if (!ChangedCode) {
llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
cl::SetVersionPrinter(PrintVersion);
cl::ParseCommandLineOptions(
argc, argv,
- "A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.\n\n"
+ "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
+ "code.\n\n"
"If no arguments are specified, it formats the code from standard input\n"
"and writes the result to the standard output.\n"
"If <file>s are given, it reformats the files. If -i is specified\n"
'(case sensitive, overrides -iregex)')
parser.add_argument('-iregex', metavar='PATTERN', default=
r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hh|hpp|hxx|m|mm|inc|js|ts'
- r'|proto|protodevel|java|cs)',
+ r'|proto|protodevel|java|cs|json)',
help='custom pattern selecting file paths to reformat '
'(case insensitive, overridden by -regex)')
parser.add_argument('-sort-includes', action='store_true', default=False,
'js', # JavaScript
'ts', # TypeScript
'cs', # C Sharp
+ 'json', # Json
])
p = argparse.ArgumentParser(
FormatTestCSharp.cpp
FormatTestJS.cpp
FormatTestJava.cpp
+ FormatTestJson.cpp
FormatTestObjC.cpp
FormatTestProto.cpp
FormatTestRawStrings.cpp
--- /dev/null
+//===- unittest/Format/FormatTestJson.cpp - Formatting tests for Json -===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "FormatTestUtils.h"
+#include "clang/Format/Format.h"
+#include "llvm/Support/Debug.h"
+#include "gtest/gtest.h"
+
+#define DEBUG_TYPE "format-test-json"
+
+namespace clang {
+namespace format {
+
+class FormatTestJson : public ::testing::Test {
+protected:
+ static std::string format(llvm::StringRef Code, unsigned Offset,
+ unsigned Length, const FormatStyle &Style) {
+ LLVM_DEBUG(llvm::errs() << "---\n");
+ LLVM_DEBUG(llvm::errs() << Code << "\n\n");
+
+ tooling::Replacements Replaces;
+
+ // Mock up what ClangFormat.cpp will do for JSON by adding a variable
+ // to trick JSON into being JavaScript
+ if (Style.isJson()) {
+ auto Err = Replaces.add(
+ tooling::Replacement(tooling::Replacement("", 0, 0, "x = ")));
+ if (Err) {
+ llvm::errs() << "Bad Json variable insertion\n";
+ }
+ }
+ auto ChangedCode = applyAllReplacements(Code, Replaces);
+ if (!ChangedCode) {
+ llvm::errs() << "Bad Json varibale replacement\n";
+ }
+ StringRef NewCode = *ChangedCode;
+
+ std::vector<tooling::Range> Ranges(1, tooling::Range(0, NewCode.size()));
+ Replaces = reformat(Style, NewCode, Ranges);
+ auto Result = applyAllReplacements(NewCode, Replaces);
+ EXPECT_TRUE(static_cast<bool>(Result));
+ LLVM_DEBUG(llvm::errs() << "\n" << *Result << "\n\n");
+ return *Result;
+ }
+
+ static std::string
+ format(llvm::StringRef Code,
+ const FormatStyle &Style = getLLVMStyle(FormatStyle::LK_Json)) {
+ return format(Code, 0, Code.size(), Style);
+ }
+
+ static FormatStyle getStyleWithColumns(unsigned ColumnLimit) {
+ FormatStyle Style = getLLVMStyle(FormatStyle::LK_Json);
+ Style.ColumnLimit = ColumnLimit;
+ return Style;
+ }
+
+ static void
+ verifyFormat(llvm::StringRef Code,
+ const FormatStyle &Style = getLLVMStyle(FormatStyle::LK_Json)) {
+ EXPECT_EQ(Code.str(), format(Code, Style)) << "Expected code is not stable";
+ EXPECT_EQ(Code.str(), format(test::messUp(Code), Style));
+ }
+};
+
+TEST_F(FormatTestJson, JsonRecord) {
+ verifyFormat("{}");
+ verifyFormat("{\n"
+ " \"name\": 1\n"
+ "}");
+ verifyFormat("{\n"
+ " \"name\": \"Foo\"\n"
+ "}");
+ verifyFormat("{\n"
+ " \"name\": {\n"
+ " \"value\": 1\n"
+ " }\n"
+ "}");
+ verifyFormat("{\n"
+ " \"name\": {\n"
+ " \"value\": 1\n"
+ " },\n"
+ " \"name\": {\n"
+ " \"value\": 2\n"
+ " }\n"
+ "}");
+ verifyFormat("{\n"
+ " \"name\": {\n"
+ " \"value\": [\n"
+ " 1,\n"
+ " 2,\n"
+ " ]\n"
+ " }\n"
+ "}");
+ verifyFormat("{\n"
+ " \"name\": {\n"
+ " \"value\": [\n"
+ " \"name\": {\n"
+ " \"value\": 1\n"
+ " },\n"
+ " \"name\": {\n"
+ " \"value\": 2\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}");
+ verifyFormat(R"({
+ "firstName": "John",
+ "lastName": "Smith",
+ "isAlive": true,
+ "age": 27,
+ "address": {
+ "streetAddress": "21 2nd Street",
+ "city": "New York",
+ "state": "NY",
+ "postalCode": "10021-3100"
+ },
+ "phoneNumbers": [
+ {
+ "type": "home",
+ "number": "212 555-1234"
+ },
+ {
+ "type": "office",
+ "number": "646 555-4567"
+ }
+ ],
+ "children": [],
+ "spouse": null
+})");
+}
+
+TEST_F(FormatTestJson, JsonArray) {
+ verifyFormat("[]");
+ verifyFormat("[\n"
+ " 1\n"
+ "]");
+ verifyFormat("[\n"
+ " 1,\n"
+ " 2\n"
+ "]");
+ verifyFormat("[\n"
+ " {},\n"
+ " {}\n"
+ "]");
+ verifyFormat("[\n"
+ " {\n"
+ " \"name\": 1\n"
+ " },\n"
+ " {}\n"
+ "]");
+}
+
+TEST_F(FormatTestJson, JsonNoStringSplit) {
+ FormatStyle Style = getLLVMStyle(FormatStyle::LK_Json);
+ Style.IndentWidth = 4;
+ verifyFormat(
+ "[\n"
+ " {\n"
+ " "
+ "\"naaaaaaaa\": \"foooooooooooooooooooooo oooooooooooooooooooooo\"\n"
+ " },\n"
+ " {}\n"
+ "]",
+ Style);
+ verifyFormat("[\n"
+ " {\n"
+ " "
+ "\"naaaaaaaa\": "
+ "\"foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"
+ "oooooooooooooooooooooooooo\"\n"
+ " },\n"
+ " {}\n"
+ "]",
+ Style);
+
+ Style.ColumnLimit = 80;
+ verifyFormat("[\n"
+ " {\n"
+ " "
+ "\"naaaaaaaa\":\n"
+ " "
+ "\"foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"
+ "oooooooooooooooooooooooooo\"\n"
+ " },\n"
+ " {}\n"
+ "]",
+ Style);
+}
+
+} // namespace format
+} // end namespace clang