Skip to content

Commit 8b7881a

Browse files
[clang-format] Add basic support for formatting JSON
I find as I develop I'm moving between many different languages C++,C#,JavaScript all the time. As I move between the file types I like to keep `clang-format` as my formatting tool of choice. (hence why I initially added C# support in {D58404}) I know those other languages have their own tools but I have to learn them all, and I have to work out how to configure them, and they may or may not have integration into my IDE or my source code integration. I am increasingly finding that I'm editing additional JSON files as part of my daily work and my editor and git commit hooks are just not setup to go and run [[ https://stedolan.github.io/jq/ | jq ]], So I tend to go to [[ https://jsonformatter.curiousconcept.com/ | JSON Formatter ]] and copy and paste back and forth. To get nicely formatted JSON. This is a painful process and I'd like a new one that causes me much less friction. This has come up from time to time: {D10543} https://stackoverflow.com/questions/35856565/clang-format-a-json-file https://bugs.llvm.org/show_bug.cgi?id=18699 I would like to stop having to do that and have formatting JSON as a first class clang-format support `Language` (even if it has minimal style settings at present). This revision adds support for formatting JSON using the inbuilt JSON serialization library of LLVM, With limited control at present only over the indentation level This adds an additional Language into the .clang-format file to separate the settings from your other supported languages. Reviewed By: HazardyKnusperkeks Differential Revision: https://reviews.llvm.org/D93528
1 parent 37c2233 commit 8b7881a

File tree

12 files changed

+279
-8
lines changed

12 files changed

+279
-8
lines changed

clang/docs/ClangFormat.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ Standalone Tool
1111
===============
1212

1313
:program:`clang-format` is located in `clang/tools/clang-format` and can be used
14-
to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.
14+
to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# code.
1515

1616
.. code-block:: console
1717
1818
$ clang-format -help
19-
OVERVIEW: A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.
19+
OVERVIEW: A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# code.
2020
2121
If no arguments are specified, it formats the code from standard input
2222
and writes the result to the standard output.

clang/docs/ClangFormatStyleOptions.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2919,6 +2919,9 @@ the configuration (without a prefix: ``Auto``).
29192919
* ``LK_JavaScript`` (in configuration: ``JavaScript``)
29202920
Should be used for JavaScript.
29212921

2922+
* ``LK_Json`` (in configuration: ``Json``)
2923+
Should be used for JSON.
2924+
29222925
* ``LK_ObjC`` (in configuration: ``ObjC``)
29232926
Should be used for Objective-C, Objective-C++.
29242927

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ clang-format
278278
- Option ``AlignArrayOfStructure`` has been added to allow for ordering array-like
279279
initializers.
280280

281+
- Support for formatting JSON file (\*.json) has been added to clang-format.
282+
281283
libclang
282284
--------
283285

clang/include/clang/Format/Format.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2493,6 +2493,8 @@ struct FormatStyle {
24932493
LK_Java,
24942494
/// Should be used for JavaScript.
24952495
LK_JavaScript,
2496+
/// Should be used for JSON.
2497+
LK_Json,
24962498
/// Should be used for Objective-C, Objective-C++.
24972499
LK_ObjC,
24982500
/// Should be used for Protocol Buffers
@@ -2506,6 +2508,7 @@ struct FormatStyle {
25062508
};
25072509
bool isCpp() const { return Language == LK_Cpp || Language == LK_ObjC; }
25082510
bool isCSharp() const { return Language == LK_CSharp; }
2511+
bool isJson() const { return Language == LK_Json; }
25092512

25102513
/// Language, this format style is targeted at.
25112514
LanguageKind Language;
@@ -3796,6 +3799,8 @@ inline StringRef getLanguageName(FormatStyle::LanguageKind Language) {
37963799
return "Java";
37973800
case FormatStyle::LK_JavaScript:
37983801
return "JavaScript";
3802+
case FormatStyle::LK_Json:
3803+
return "Json";
37993804
case FormatStyle::LK_Proto:
38003805
return "Proto";
38013806
case FormatStyle::LK_TableGen:

clang/lib/Format/ContinuationIndenter.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,12 +1906,12 @@ ContinuationIndenter::createBreakableToken(const FormatToken &Current,
19061906
LineState &State, bool AllowBreak) {
19071907
unsigned StartColumn = State.Column - Current.ColumnWidth;
19081908
if (Current.isStringLiteral()) {
1909-
// FIXME: String literal breaking is currently disabled for C#, Java and
1910-
// JavaScript, as it requires strings to be merged using "+" which we
1909+
// FIXME: String literal breaking is currently disabled for C#, Java, Json
1910+
// and JavaScript, as it requires strings to be merged using "+" which we
19111911
// don't support.
19121912
if (Style.Language == FormatStyle::LK_Java ||
19131913
Style.Language == FormatStyle::LK_JavaScript || Style.isCSharp() ||
1914-
!Style.BreakStringLiterals || !AllowBreak)
1914+
Style.isJson() || !Style.BreakStringLiterals || !AllowBreak)
19151915
return nullptr;
19161916

19171917
// Don't break string literals inside preprocessor directives (except for

clang/lib/Format/Format.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ template <> struct ScalarEnumerationTraits<FormatStyle::LanguageKind> {
6363
IO.enumCase(Value, "TableGen", FormatStyle::LK_TableGen);
6464
IO.enumCase(Value, "TextProto", FormatStyle::LK_TextProto);
6565
IO.enumCase(Value, "CSharp", FormatStyle::LK_CSharp);
66+
IO.enumCase(Value, "Json", FormatStyle::LK_Json);
6667
}
6768
};
6869

@@ -1133,6 +1134,9 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
11331134
if (Language == FormatStyle::LK_TableGen) {
11341135
LLVMStyle.SpacesInContainerLiterals = false;
11351136
}
1137+
if (LLVMStyle.isJson()) {
1138+
LLVMStyle.ColumnLimit = 0;
1139+
}
11361140

11371141
return LLVMStyle;
11381142
}
@@ -2825,6 +2829,25 @@ reformat(const FormatStyle &Style, StringRef Code,
28252829
if (Expanded.Language == FormatStyle::LK_JavaScript && isMpegTS(Code))
28262830
return {tooling::Replacements(), 0};
28272831

2832+
// JSON only needs the formatting passing.
2833+
if (Style.isJson()) {
2834+
std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size()));
2835+
auto Env =
2836+
std::make_unique<Environment>(Code, FileName, Ranges, FirstStartColumn,
2837+
NextStartColumn, LastStartColumn);
2838+
// Perform the actual formatting pass.
2839+
tooling::Replacements Replaces =
2840+
Formatter(*Env, Style, Status).process().first;
2841+
// add a replacement to remove the "x = " from the result.
2842+
if (!Replaces.add(tooling::Replacement(FileName, 0, 4, ""))) {
2843+
// apply the reformatting changes and the removal of "x = ".
2844+
if (applyAllReplacements(Code, Replaces)) {
2845+
return {Replaces, 0};
2846+
}
2847+
}
2848+
return {tooling::Replacements(), 0};
2849+
}
2850+
28282851
typedef std::function<std::pair<tooling::Replacements, unsigned>(
28292852
const Environment &)>
28302853
AnalyzerPass;
@@ -2991,6 +3014,8 @@ static FormatStyle::LanguageKind getLanguageByFileName(StringRef FileName) {
29913014
return FormatStyle::LK_TableGen;
29923015
if (FileName.endswith_insensitive(".cs"))
29933016
return FormatStyle::LK_CSharp;
3017+
if (FileName.endswith_insensitive(".json"))
3018+
return FormatStyle::LK_Json;
29943019
return FormatStyle::LK_Cpp;
29953020
}
29963021

@@ -3163,4 +3188,4 @@ llvm::Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName,
31633188
}
31643189

31653190
} // namespace format
3166-
} // namespace clang
3191+
} // namespace clang

clang/lib/Format/TokenAnnotator.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2900,6 +2900,8 @@ bool TokenAnnotator::spaceRequiredBetween(const AnnotatedLine &Line,
29002900
const FormatToken &Right) {
29012901
if (Left.is(tok::kw_return) && Right.isNot(tok::semi))
29022902
return true;
2903+
if (Style.isJson() && Left.is(tok::string_literal) && Right.is(tok::colon))
2904+
return false;
29032905
if (Left.is(Keywords.kw_assert) && Style.Language == FormatStyle::LK_Java)
29042906
return true;
29052907
if (Style.ObjCSpaceAfterProperty && Line.Type == LT_ObjCProperty &&
@@ -3227,6 +3229,9 @@ bool TokenAnnotator::spaceRequiredBefore(const AnnotatedLine &Line,
32273229
// and "%d %d"
32283230
if (Left.is(tok::numeric_constant) && Right.is(tok::percent))
32293231
return HasExistingWhitespace();
3232+
} else if (Style.isJson()) {
3233+
if (Right.is(tok::colon))
3234+
return false;
32303235
} else if (Style.isCSharp()) {
32313236
// Require spaces around '{' and before '}' unless they appear in
32323237
// interpolated strings. Interpolated strings are merged into a single token
@@ -3670,6 +3675,26 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
36703675
return true;
36713676
}
36723677

3678+
// Basic JSON newline processing.
3679+
if (Style.isJson()) {
3680+
// Always break after a JSON record opener.
3681+
// {
3682+
// }
3683+
if (Left.is(TT_DictLiteral) && Left.is(tok::l_brace))
3684+
return true;
3685+
// Always break after a JSON array opener.
3686+
// [
3687+
// ]
3688+
if (Left.is(TT_ArrayInitializerLSquare) && Left.is(tok::l_square) &&
3689+
!Right.is(tok::r_square))
3690+
return true;
3691+
// Always break afer successive entries.
3692+
// 1,
3693+
// 2
3694+
if (Left.is(tok::comma))
3695+
return true;
3696+
}
3697+
36733698
// If the last token before a '}', ']', or ')' is a comma or a trailing
36743699
// comment, the intention is to insert a line break after it in order to make
36753700
// shuffling around entries easier. Import statements, especially in

clang/tools/clang-format/ClangFormat.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,17 @@ static bool format(StringRef FileName) {
411411
unsigned CursorPosition = Cursor;
412412
Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
413413
AssumedFileName, &CursorPosition);
414+
415+
// To format JSON insert a variable to trick the code into thinking its
416+
// JavaScript.
417+
if (FormatStyle->isJson()) {
418+
auto Err = Replaces.add(tooling::Replacement(
419+
tooling::Replacement(AssumedFileName, 0, 0, "x = ")));
420+
if (Err) {
421+
llvm::errs() << "Bad Json variable insertion\n";
422+
}
423+
}
424+
414425
auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
415426
if (!ChangedCode) {
416427
llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
@@ -506,7 +517,8 @@ int main(int argc, const char **argv) {
506517
cl::SetVersionPrinter(PrintVersion);
507518
cl::ParseCommandLineOptions(
508519
argc, argv,
509-
"A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.\n\n"
520+
"A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
521+
"code.\n\n"
510522
"If no arguments are specified, it formats the code from standard input\n"
511523
"and writes the result to the standard output.\n"
512524
"If <file>s are given, it reformats the files. If -i is specified\n"

clang/tools/clang-format/clang-format-diff.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def main():
4848
'(case sensitive, overrides -iregex)')
4949
parser.add_argument('-iregex', metavar='PATTERN', default=
5050
r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hh|hpp|hxx|m|mm|inc|js|ts'
51-
r'|proto|protodevel|java|cs)',
51+
r'|proto|protodevel|java|cs|json)',
5252
help='custom pattern selecting file paths to reformat '
5353
'(case insensitive, overridden by -regex)')
5454
parser.add_argument('-sort-includes', action='store_true', default=False,

clang/tools/clang-format/git-clang-format

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def main():
8585
'js', # JavaScript
8686
'ts', # TypeScript
8787
'cs', # C Sharp
88+
'json', # Json
8889
])
8990

9091
p = argparse.ArgumentParser(

clang/unittests/Format/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_clang_unittest(FormatTests
99
FormatTestCSharp.cpp
1010
FormatTestJS.cpp
1111
FormatTestJava.cpp
12+
FormatTestJson.cpp
1213
FormatTestObjC.cpp
1314
FormatTestProto.cpp
1415
FormatTestRawStrings.cpp

0 commit comments

Comments
 (0)