Skip to content

Commit 8b2f871

Browse files
authored
Add JVM-based JSON formatter (#853)
2 parents 8e570ca + 965d2ff commit 8b2f871

30 files changed

+1675
-1
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1111

1212
## [Unreleased]
1313

14+
### Added
15+
* Added formatter for [JVM-based JSON formatting](https://github.com/diffplug/spotless/issues/850)
16+
* Added Gradle configuration JVM-based JSON formatting
17+
1418
## [2.14.0] - 2021-06-10
1519
### Added
1620
* Added support for `eclipse-cdt` at `4.19.0`. Note that version requires Java 11 or higher.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2021 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.json;
17+
18+
import java.io.IOException;
19+
import java.io.Serializable;
20+
import java.lang.reflect.Constructor;
21+
import java.lang.reflect.InvocationTargetException;
22+
import java.lang.reflect.Method;
23+
import java.util.Objects;
24+
25+
import com.diffplug.spotless.FormatterFunc;
26+
import com.diffplug.spotless.FormatterStep;
27+
import com.diffplug.spotless.JarState;
28+
import com.diffplug.spotless.Provisioner;
29+
30+
/**
31+
* Simple JSON formatter which reformats the file according to the org.json library's default pretty-printing, but has no ability to customise more than the indentation size.
32+
*/
33+
public final class JsonSimpleStep {
34+
private static final String MAVEN_COORDINATE = "org.json:json:";
35+
private static final String DEFAULT_VERSION = "20210307";
36+
37+
public static FormatterStep create(int indent, Provisioner provisioner) {
38+
Objects.requireNonNull(provisioner, "provisioner cannot be null");
39+
return FormatterStep.createLazy("json", () -> new State(indent, provisioner), State::toFormatter);
40+
}
41+
42+
private static final class State implements Serializable {
43+
private static final long serialVersionUID = 1L;
44+
45+
private final int indentSpaces;
46+
private final JarState jarState;
47+
48+
private State(int indent, Provisioner provisioner) throws IOException {
49+
this.indentSpaces = indent;
50+
this.jarState = JarState.from(MAVEN_COORDINATE + DEFAULT_VERSION, provisioner);
51+
}
52+
53+
FormatterFunc toFormatter() {
54+
Method objectToString;
55+
Method arrayToString;
56+
Constructor<?> objectConstructor;
57+
Constructor<?> arrayConstructor;
58+
try {
59+
ClassLoader classLoader = jarState.getClassLoader();
60+
Class<?> jsonObject = classLoader.loadClass("org.json.JSONObject");
61+
Class<?>[] constructorArguments = new Class[]{String.class};
62+
objectConstructor = jsonObject.getConstructor(constructorArguments);
63+
objectToString = jsonObject.getMethod("toString", int.class);
64+
65+
Class<?> jsonArray = classLoader.loadClass("org.json.JSONArray");
66+
arrayConstructor = jsonArray.getConstructor(constructorArguments);
67+
arrayToString = jsonArray.getMethod("toString", int.class);
68+
} catch (ClassNotFoundException | NoSuchMethodException e) {
69+
throw new IllegalStateException("There was a problem preparing org.json dependencies", e);
70+
}
71+
72+
return s -> {
73+
String prettyPrinted = null;
74+
if (s.isEmpty()) {
75+
prettyPrinted = s;
76+
}
77+
if (s.startsWith("{")) {
78+
try {
79+
Object parsed = objectConstructor.newInstance(s);
80+
prettyPrinted = objectToString.invoke(parsed, indentSpaces) + "\n";
81+
} catch (InvocationTargetException ignored) {
82+
// ignore if we cannot convert to JSON string
83+
}
84+
}
85+
if (s.startsWith("[")) {
86+
try {
87+
Object parsed = arrayConstructor.newInstance(s);
88+
prettyPrinted = arrayToString.invoke(parsed, indentSpaces) + "\n";
89+
} catch (InvocationTargetException ignored) {
90+
// ignore if we cannot convert to JSON string
91+
}
92+
}
93+
94+
if (prettyPrinted == null) {
95+
throw new AssertionError("Invalid JSON file provided");
96+
}
97+
98+
return prettyPrinted;
99+
};
100+
}
101+
}
102+
103+
private JsonSimpleStep() {
104+
// cannot be directly instantiated
105+
}
106+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@ParametersAreNonnullByDefault
2+
@ReturnValuesAreNonnullByDefault
3+
package com.diffplug.spotless.extra.json.java;
4+
5+
import javax.annotation.ParametersAreNonnullByDefault;
6+
7+
import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault;

plugin-gradle/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
44

55
## [Unreleased]
66

7+
### Added
8+
* Added Gradle configuration [JVM-based JSON formatting](https://github.com/diffplug/spotless/issues/850)
79
### Fixed
810
* Fixed IndexOutOfBoundsException in parallel execution of `eclipse-groovy` formatter ([#877](https://github.com/diffplug/spotless/issues/877))
911

plugin-gradle/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui
6868
- [Antlr4](#antlr4) ([antlr4formatter](#antlr4formatter))
6969
- [SQL](#sql) ([dbeaver](#dbeaver), [prettier](#prettier))
7070
- [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier))
71+
- [JSON](#json)
7172
- Multiple languages
7273
- [Prettier](#prettier) ([plugins](#prettier-plugins), [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection))
7374
- javascript, jsx, angular, vue, flow, typescript, css, less, scss, html, json, graphql, markdown, ymaml
@@ -527,6 +528,36 @@ spotless {
527528
528529
For details, see the [npm detection](#npm-detection) and [`.npmrc` detection](#npmrc-detection) sections of prettier, which apply also to tsfmt.
529530
531+
## JSON
532+
533+
- `com.diffplug.gradle.spotless.JsonExtension` [javadoc](https://javadoc.io/static/com.diffplug.spotless/spotless-plugin-gradle/5.13.0/com/diffplug/gradle/spotless/JsonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java)
534+
535+
```gradle
536+
spotless {
537+
json {
538+
target 'src/**/*.json' // you have to set the target manually
539+
simple() // has its own section below
540+
prettier().config(['parser': 'json']) // see Prettier section below
541+
eclipseWtp('json') // see Eclipse web tools platform section
542+
}
543+
}
544+
```
545+
546+
### simple
547+
548+
Uses a JSON pretty-printer that optionally allows configuring the number of spaces that are used to pretty print objects:
549+
550+
```gradle
551+
spotless {
552+
json {
553+
target 'src/**/*.json'
554+
simple()
555+
// optional: specify the number of spaces to use
556+
simple().indentWithSpaces(6)
557+
}
558+
}
559+
```
560+
530561
<a name="applying-prettier-to-javascript--flow--typescript--css--scss--less--jsx--graphql--yaml--etc"></a>
531562
532563
## Prettier
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2016-2021 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.gradle.spotless;
17+
18+
import javax.inject.Inject;
19+
20+
import com.diffplug.spotless.FormatterStep;
21+
import com.diffplug.spotless.json.JsonSimpleStep;
22+
23+
public class JsonExtension extends FormatExtension {
24+
private static final int DEFAULT_INDENTATION = 4;
25+
static final String NAME = "json";
26+
27+
@Inject
28+
public JsonExtension(SpotlessExtension spotless) {
29+
super(spotless);
30+
}
31+
32+
@Override
33+
protected void setupTask(SpotlessTask task) {
34+
if (target == null) {
35+
throw noDefaultTargetException();
36+
}
37+
super.setupTask(task);
38+
}
39+
40+
public SimpleConfig simple() {
41+
return new SimpleConfig(DEFAULT_INDENTATION);
42+
}
43+
44+
public class SimpleConfig {
45+
private int indent;
46+
47+
public SimpleConfig(int indent) {
48+
this.indent = indent;
49+
addStep(createStep());
50+
}
51+
52+
public void indentWithSpaces(int indent) {
53+
this.indent = indent;
54+
replaceStep(createStep());
55+
}
56+
57+
private FormatterStep createStep() {
58+
return JsonSimpleStep.create(indent, provisioner());
59+
}
60+
}
61+
62+
}

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 DiffPlug
2+
* Copyright 2016-2021 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -169,6 +169,12 @@ public void python(Action<PythonExtension> closure) {
169169
format(PythonExtension.NAME, PythonExtension.class, closure);
170170
}
171171

172+
/** Configures the special JSON-specific extension. */
173+
public void json(Action<JsonExtension> closure) {
174+
requireNonNull(closure);
175+
format(JsonExtension.NAME, JsonExtension.class, closure);
176+
}
177+
172178
/** Configures a custom extension. */
173179
public void format(String name, Action<FormatExtension> closure) {
174180
requireNonNull(name, "name");
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2021 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.gradle.spotless;
17+
18+
import java.io.IOException;
19+
20+
import org.junit.Test;
21+
22+
public class JsonExtensionTest extends GradleIntegrationHarness {
23+
@Test
24+
public void defaultFormatting() throws IOException {
25+
setFile("build.gradle").toLines(
26+
"buildscript { repositories { mavenCentral() } }",
27+
"plugins {",
28+
" id 'java'",
29+
" id 'com.diffplug.spotless'",
30+
"}",
31+
"spotless {",
32+
" json {",
33+
" target 'examples/**/*.json'",
34+
" simple()",
35+
"}",
36+
"}");
37+
setFile("src/main/resources/example.json").toResource("json/nestedObjectBefore.json");
38+
setFile("examples/main/resources/example.json").toResource("json/nestedObjectBefore.json");
39+
gradleRunner().withArguments("spotlessApply").build();
40+
assertFile("src/main/resources/example.json").sameAsResource("json/nestedObjectBefore.json");
41+
assertFile("examples/main/resources/example.json").sameAsResource("json/nestedObjectAfter.json");
42+
}
43+
44+
@Test
45+
public void formattingWithCustomNumberOfSpaces() throws IOException {
46+
setFile("build.gradle").toLines(
47+
"buildscript { repositories { mavenCentral() } }",
48+
"plugins {",
49+
" id 'java'",
50+
" id 'com.diffplug.spotless'",
51+
"}",
52+
"spotless {",
53+
" json {",
54+
" target 'src/**/*.json'",
55+
" simple().indentWithSpaces(6)",
56+
"}",
57+
"}");
58+
setFile("src/main/resources/example.json").toResource("json/singletonArrayBefore.json");
59+
gradleRunner().withArguments("spotlessApply").build();
60+
assertFile("src/main/resources/example.json").sameAsResource("json/singletonArrayAfter6Spaces.json");
61+
}
62+
}

0 commit comments

Comments
 (0)