Skip to content

Commit

Permalink
Add JVM-based JSON formatter (#853)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg authored Jun 17, 2021
2 parents 8e570ca + 965d2ff commit 8b2f871
Show file tree
Hide file tree
Showing 30 changed files with 1,675 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

## [Unreleased]

### Added
* Added formatter for [JVM-based JSON formatting](https://github.com/diffplug/spotless/issues/850)
* Added Gradle configuration JVM-based JSON formatting

## [2.14.0] - 2021-06-10
### Added
* Added support for `eclipse-cdt` at `4.19.0`. Note that version requires Java 11 or higher.
Expand Down
106 changes: 106 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2021 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.spotless.json;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.JarState;
import com.diffplug.spotless.Provisioner;

/**
* 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.
*/
public final class JsonSimpleStep {
private static final String MAVEN_COORDINATE = "org.json:json:";
private static final String DEFAULT_VERSION = "20210307";

public static FormatterStep create(int indent, Provisioner provisioner) {
Objects.requireNonNull(provisioner, "provisioner cannot be null");
return FormatterStep.createLazy("json", () -> new State(indent, provisioner), State::toFormatter);
}

private static final class State implements Serializable {
private static final long serialVersionUID = 1L;

private final int indentSpaces;
private final JarState jarState;

private State(int indent, Provisioner provisioner) throws IOException {
this.indentSpaces = indent;
this.jarState = JarState.from(MAVEN_COORDINATE + DEFAULT_VERSION, provisioner);
}

FormatterFunc toFormatter() {
Method objectToString;
Method arrayToString;
Constructor<?> objectConstructor;
Constructor<?> arrayConstructor;
try {
ClassLoader classLoader = jarState.getClassLoader();
Class<?> jsonObject = classLoader.loadClass("org.json.JSONObject");
Class<?>[] constructorArguments = new Class[]{String.class};
objectConstructor = jsonObject.getConstructor(constructorArguments);
objectToString = jsonObject.getMethod("toString", int.class);

Class<?> jsonArray = classLoader.loadClass("org.json.JSONArray");
arrayConstructor = jsonArray.getConstructor(constructorArguments);
arrayToString = jsonArray.getMethod("toString", int.class);
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new IllegalStateException("There was a problem preparing org.json dependencies", e);
}

return s -> {
String prettyPrinted = null;
if (s.isEmpty()) {
prettyPrinted = s;
}
if (s.startsWith("{")) {
try {
Object parsed = objectConstructor.newInstance(s);
prettyPrinted = objectToString.invoke(parsed, indentSpaces) + "\n";
} catch (InvocationTargetException ignored) {
// ignore if we cannot convert to JSON string
}
}
if (s.startsWith("[")) {
try {
Object parsed = arrayConstructor.newInstance(s);
prettyPrinted = arrayToString.invoke(parsed, indentSpaces) + "\n";
} catch (InvocationTargetException ignored) {
// ignore if we cannot convert to JSON string
}
}

if (prettyPrinted == null) {
throw new AssertionError("Invalid JSON file provided");
}

return prettyPrinted;
};
}
}

private JsonSimpleStep() {
// cannot be directly instantiated
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@ReturnValuesAreNonnullByDefault
package com.diffplug.spotless.extra.json.java;

import javax.annotation.ParametersAreNonnullByDefault;

import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault;
2 changes: 2 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

## [Unreleased]

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

Expand Down
31 changes: 31 additions & 0 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui
- [Antlr4](#antlr4) ([antlr4formatter](#antlr4formatter))
- [SQL](#sql) ([dbeaver](#dbeaver), [prettier](#prettier))
- [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier))
- [JSON](#json)
- Multiple languages
- [Prettier](#prettier) ([plugins](#prettier-plugins), [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection))
- javascript, jsx, angular, vue, flow, typescript, css, less, scss, html, json, graphql, markdown, ymaml
Expand Down Expand Up @@ -527,6 +528,36 @@ spotless {
For details, see the [npm detection](#npm-detection) and [`.npmrc` detection](#npmrc-detection) sections of prettier, which apply also to tsfmt.
## JSON
- `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)
```gradle
spotless {
json {
target 'src/**/*.json' // you have to set the target manually
simple() // has its own section below
prettier().config(['parser': 'json']) // see Prettier section below
eclipseWtp('json') // see Eclipse web tools platform section
}
}
```
### simple
Uses a JSON pretty-printer that optionally allows configuring the number of spaces that are used to pretty print objects:
```gradle
spotless {
json {
target 'src/**/*.json'
simple()
// optional: specify the number of spaces to use
simple().indentWithSpaces(6)
}
}
```
<a name="applying-prettier-to-javascript--flow--typescript--css--scss--less--jsx--graphql--yaml--etc"></a>
## Prettier
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2016-2021 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.gradle.spotless;

import javax.inject.Inject;

import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.json.JsonSimpleStep;

public class JsonExtension extends FormatExtension {
private static final int DEFAULT_INDENTATION = 4;
static final String NAME = "json";

@Inject
public JsonExtension(SpotlessExtension spotless) {
super(spotless);
}

@Override
protected void setupTask(SpotlessTask task) {
if (target == null) {
throw noDefaultTargetException();
}
super.setupTask(task);
}

public SimpleConfig simple() {
return new SimpleConfig(DEFAULT_INDENTATION);
}

public class SimpleConfig {
private int indent;

public SimpleConfig(int indent) {
this.indent = indent;
addStep(createStep());
}

public void indentWithSpaces(int indent) {
this.indent = indent;
replaceStep(createStep());
}

private FormatterStep createStep() {
return JsonSimpleStep.create(indent, provisioner());
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2020 DiffPlug
* Copyright 2016-2021 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -169,6 +169,12 @@ public void python(Action<PythonExtension> closure) {
format(PythonExtension.NAME, PythonExtension.class, closure);
}

/** Configures the special JSON-specific extension. */
public void json(Action<JsonExtension> closure) {
requireNonNull(closure);
format(JsonExtension.NAME, JsonExtension.class, closure);
}

/** Configures a custom extension. */
public void format(String name, Action<FormatExtension> closure) {
requireNonNull(name, "name");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2021 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.gradle.spotless;

import java.io.IOException;

import org.junit.Test;

public class JsonExtensionTest extends GradleIntegrationHarness {
@Test
public void defaultFormatting() throws IOException {
setFile("build.gradle").toLines(
"buildscript { repositories { mavenCentral() } }",
"plugins {",
" id 'java'",
" id 'com.diffplug.spotless'",
"}",
"spotless {",
" json {",
" target 'examples/**/*.json'",
" simple()",
"}",
"}");
setFile("src/main/resources/example.json").toResource("json/nestedObjectBefore.json");
setFile("examples/main/resources/example.json").toResource("json/nestedObjectBefore.json");
gradleRunner().withArguments("spotlessApply").build();
assertFile("src/main/resources/example.json").sameAsResource("json/nestedObjectBefore.json");
assertFile("examples/main/resources/example.json").sameAsResource("json/nestedObjectAfter.json");
}

@Test
public void formattingWithCustomNumberOfSpaces() throws IOException {
setFile("build.gradle").toLines(
"buildscript { repositories { mavenCentral() } }",
"plugins {",
" id 'java'",
" id 'com.diffplug.spotless'",
"}",
"spotless {",
" json {",
" target 'src/**/*.json'",
" simple().indentWithSpaces(6)",
"}",
"}");
setFile("src/main/resources/example.json").toResource("json/singletonArrayBefore.json");
gradleRunner().withArguments("spotlessApply").build();
assertFile("src/main/resources/example.json").sameAsResource("json/singletonArrayAfter6Spaces.json");
}
}
Loading

0 comments on commit 8b2f871

Please sign in to comment.