Skip to content

Commit 39b6212

Browse files
java-team-github-botgoogle-java-format Team
authored andcommitted
Add support for AOSP formatting in the Eclipse plugin
PiperOrigin-RevId: 823204805
1 parent 8c30268 commit 39b6212

File tree

4 files changed

+181
-127
lines changed

4 files changed

+181
-127
lines changed

eclipse_plugin/plugin.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,10 @@
2424
id="com.google.googlejavaformat.java.GoogleJavaFormatter"
2525
name="google-java-format">
2626
</javaFormatter>
27+
<javaFormatter
28+
class="com.google.googlejavaformat.java.AospJavaFormatter"
29+
id="com.google.googlejavaformat.java.AospJavaFormatter"
30+
name="aosp-java-format">
31+
</javaFormatter>
2732
</extension>
2833
</plugin>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2025 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.googlejavaformat.java;
16+
17+
/** Runs the Google Java formatter on the given code. */
18+
public class AospJavaFormatter extends JavaFormatterBase {
19+
public AospJavaFormatter() {
20+
super(JavaFormatterOptions.builder().style(JavaFormatterOptions.Style.AOSP).build());
21+
}
22+
}

eclipse_plugin/src/com/google/googlejavaformat/java/GoogleJavaFormatter.java

Lines changed: 3 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -14,133 +14,9 @@
1414

1515
package com.google.googlejavaformat.java;
1616

17-
import com.google.common.base.Preconditions;
18-
import com.google.common.collect.Range;
19-
import com.google.googlejavaformat.java.SnippetFormatter.SnippetKind;
20-
import java.util.ArrayList;
21-
import java.util.List;
22-
import org.eclipse.jdt.core.dom.ASTParser;
23-
import org.eclipse.jdt.core.formatter.CodeFormatter;
24-
import org.eclipse.jface.text.IRegion;
25-
import org.eclipse.jface.text.Region;
26-
import org.eclipse.text.edits.MultiTextEdit;
27-
import org.eclipse.text.edits.ReplaceEdit;
28-
import org.eclipse.text.edits.TextEdit;
29-
3017
/** Runs the Google Java formatter on the given code. */
31-
public class GoogleJavaFormatter extends CodeFormatter {
32-
33-
private static final int INDENTATION_SIZE = 2;
34-
35-
@Override
36-
public TextEdit format(
37-
int kind, String source, int offset, int length, int indentationLevel, String lineSeparator) {
38-
IRegion[] regions = new IRegion[] {new Region(offset, length)};
39-
return formatInternal(kind, source, regions, indentationLevel);
40-
}
41-
42-
@Override
43-
public TextEdit format(
44-
int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator) {
45-
return formatInternal(kind, source, regions, indentationLevel);
46-
}
47-
48-
@Override
49-
public String createIndentationString(int indentationLevel) {
50-
Preconditions.checkArgument(
51-
indentationLevel >= 0,
52-
"Indentation level cannot be less than zero. Given: %s",
53-
indentationLevel);
54-
int spaces = indentationLevel * INDENTATION_SIZE;
55-
StringBuilder buf = new StringBuilder(spaces);
56-
for (int i = 0; i < spaces; i++) {
57-
buf.append(' ');
58-
}
59-
return buf.toString();
60-
}
61-
62-
/** Runs the Google Java formatter on the given source, with only the given ranges specified. */
63-
private TextEdit formatInternal(int kind, String source, IRegion[] regions, int initialIndent) {
64-
try {
65-
boolean includeComments =
66-
(kind & CodeFormatter.F_INCLUDE_COMMENTS) == CodeFormatter.F_INCLUDE_COMMENTS;
67-
kind &= ~CodeFormatter.F_INCLUDE_COMMENTS;
68-
SnippetKind snippetKind;
69-
switch (kind) {
70-
case ASTParser.K_EXPRESSION:
71-
snippetKind = SnippetKind.EXPRESSION;
72-
break;
73-
case ASTParser.K_STATEMENTS:
74-
snippetKind = SnippetKind.STATEMENTS;
75-
break;
76-
case ASTParser.K_CLASS_BODY_DECLARATIONS:
77-
snippetKind = SnippetKind.CLASS_BODY_DECLARATIONS;
78-
break;
79-
case ASTParser.K_COMPILATION_UNIT:
80-
snippetKind = SnippetKind.COMPILATION_UNIT;
81-
break;
82-
default:
83-
throw new IllegalArgumentException(String.format("Unknown snippet kind: %d", kind));
84-
}
85-
List<Replacement> replacements =
86-
new SnippetFormatter()
87-
.format(
88-
snippetKind, source, rangesFromRegions(regions), initialIndent, includeComments);
89-
if (idempotent(source, regions, replacements)) {
90-
// Do not create edits if there's no diff.
91-
return null;
92-
}
93-
// Convert replacements to text edits.
94-
return editFromReplacements(replacements);
95-
} catch (IllegalArgumentException | FormatterException exception) {
96-
// Do not format on errors.
97-
return null;
98-
}
99-
}
100-
101-
private List<Range<Integer>> rangesFromRegions(IRegion[] regions) {
102-
List<Range<Integer>> ranges = new ArrayList<>();
103-
for (IRegion region : regions) {
104-
ranges.add(Range.closedOpen(region.getOffset(), region.getOffset() + region.getLength()));
105-
}
106-
return ranges;
107-
}
108-
109-
/**
110-
* @return {@code true} if input and output texts are equal, else {@code false}.
111-
*/
112-
private boolean idempotent(String source, IRegion[] regions, List<Replacement> replacements) {
113-
// This implementation only checks for single replacement.
114-
if (replacements.size() == 1) {
115-
Replacement replacement = replacements.get(0);
116-
String output = replacement.getReplacementString();
117-
// Entire source case: input = output, nothing changed.
118-
if (output.equals(source)) {
119-
return true;
120-
}
121-
// Single region and single replacement case: if they are equal, nothing changed.
122-
if (regions.length == 1) {
123-
Range<Integer> range = replacement.getReplaceRange();
124-
String snippet = source.substring(range.lowerEndpoint(), range.upperEndpoint());
125-
if (output.equals(snippet)) {
126-
return true;
127-
}
128-
}
129-
}
130-
return false;
131-
}
132-
133-
private TextEdit editFromReplacements(List<Replacement> replacements) {
134-
// Split the replacements that cross line boundaries.
135-
TextEdit edit = new MultiTextEdit();
136-
for (Replacement replacement : replacements) {
137-
Range<Integer> replaceRange = replacement.getReplaceRange();
138-
edit.addChild(
139-
new ReplaceEdit(
140-
replaceRange.lowerEndpoint(),
141-
replaceRange.upperEndpoint() - replaceRange.lowerEndpoint(),
142-
replacement.getReplacementString()));
143-
}
144-
return edit;
18+
public class GoogleJavaFormatter extends JavaFormatterBase {
19+
public GoogleJavaFormatter() {
20+
super(JavaFormatterOptions.defaultOptions());
14521
}
14622
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright 2025 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.googlejavaformat.java;
16+
17+
import com.google.common.base.Preconditions;
18+
import com.google.common.collect.Range;
19+
import com.google.googlejavaformat.java.SnippetFormatter.SnippetKind;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import org.eclipse.jdt.core.dom.ASTParser;
23+
import org.eclipse.jdt.core.formatter.CodeFormatter;
24+
import org.eclipse.jface.text.IRegion;
25+
import org.eclipse.jface.text.Region;
26+
import org.eclipse.text.edits.MultiTextEdit;
27+
import org.eclipse.text.edits.ReplaceEdit;
28+
import org.eclipse.text.edits.TextEdit;
29+
30+
/** Runs the Google Java formatter on the given code. */
31+
public class JavaFormatterBase extends CodeFormatter {
32+
33+
private static final int INDENTATION_SIZE = 2;
34+
private final JavaFormatterOptions formatterOptions;
35+
36+
JavaFormatterBase(JavaFormatterOptions formatterOptions) {
37+
this.formatterOptions = formatterOptions;
38+
}
39+
40+
@Override
41+
public TextEdit format(
42+
int kind, String source, int offset, int length, int indentationLevel, String lineSeparator) {
43+
IRegion[] regions = new IRegion[] {new Region(offset, length)};
44+
return formatInternal(kind, source, regions, indentationLevel);
45+
}
46+
47+
@Override
48+
public TextEdit format(
49+
int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator) {
50+
return formatInternal(kind, source, regions, indentationLevel);
51+
}
52+
53+
@Override
54+
public String createIndentationString(int indentationLevel) {
55+
Preconditions.checkArgument(
56+
indentationLevel >= 0,
57+
"Indentation level cannot be less than zero. Given: %s",
58+
indentationLevel);
59+
int spaces = indentationLevel * INDENTATION_SIZE;
60+
StringBuilder buf = new StringBuilder(spaces);
61+
for (int i = 0; i < spaces; i++) {
62+
buf.append(' ');
63+
}
64+
return buf.toString();
65+
}
66+
67+
/** Runs the Google Java formatter on the given source, with only the given ranges specified. */
68+
private TextEdit formatInternal(int kind, String source, IRegion[] regions, int initialIndent) {
69+
try {
70+
boolean includeComments =
71+
(kind & CodeFormatter.F_INCLUDE_COMMENTS) == CodeFormatter.F_INCLUDE_COMMENTS;
72+
kind &= ~CodeFormatter.F_INCLUDE_COMMENTS;
73+
SnippetKind snippetKind;
74+
switch (kind) {
75+
case ASTParser.K_EXPRESSION:
76+
snippetKind = SnippetKind.EXPRESSION;
77+
break;
78+
case ASTParser.K_STATEMENTS:
79+
snippetKind = SnippetKind.STATEMENTS;
80+
break;
81+
case ASTParser.K_CLASS_BODY_DECLARATIONS:
82+
snippetKind = SnippetKind.CLASS_BODY_DECLARATIONS;
83+
break;
84+
case ASTParser.K_COMPILATION_UNIT:
85+
snippetKind = SnippetKind.COMPILATION_UNIT;
86+
break;
87+
default:
88+
throw new IllegalArgumentException(String.format("Unknown snippet kind: %d", kind));
89+
}
90+
List<Replacement> replacements =
91+
new SnippetFormatter(formatterOptions)
92+
.format(
93+
snippetKind, source, rangesFromRegions(regions), initialIndent, includeComments);
94+
if (idempotent(source, regions, replacements)) {
95+
// Do not create edits if there's no diff.
96+
return null;
97+
}
98+
// Convert replacements to text edits.
99+
return editFromReplacements(replacements);
100+
} catch (IllegalArgumentException | FormatterException exception) {
101+
// Do not format on errors.
102+
return null;
103+
}
104+
}
105+
106+
private List<Range<Integer>> rangesFromRegions(IRegion[] regions) {
107+
List<Range<Integer>> ranges = new ArrayList<>();
108+
for (IRegion region : regions) {
109+
ranges.add(Range.closedOpen(region.getOffset(), region.getOffset() + region.getLength()));
110+
}
111+
return ranges;
112+
}
113+
114+
/**
115+
* @return {@code true} if input and output texts are equal, else {@code false}.
116+
*/
117+
private boolean idempotent(String source, IRegion[] regions, List<Replacement> replacements) {
118+
// This implementation only checks for single replacement.
119+
if (replacements.size() == 1) {
120+
Replacement replacement = replacements.get(0);
121+
String output = replacement.getReplacementString();
122+
// Entire source case: input = output, nothing changed.
123+
if (output.equals(source)) {
124+
return true;
125+
}
126+
// Single region and single replacement case: if they are equal, nothing changed.
127+
if (regions.length == 1) {
128+
Range<Integer> range = replacement.getReplaceRange();
129+
String snippet = source.substring(range.lowerEndpoint(), range.upperEndpoint());
130+
if (output.equals(snippet)) {
131+
return true;
132+
}
133+
}
134+
}
135+
return false;
136+
}
137+
138+
private TextEdit editFromReplacements(List<Replacement> replacements) {
139+
// Split the replacements that cross line boundaries.
140+
TextEdit edit = new MultiTextEdit();
141+
for (Replacement replacement : replacements) {
142+
Range<Integer> replaceRange = replacement.getReplaceRange();
143+
edit.addChild(
144+
new ReplaceEdit(
145+
replaceRange.lowerEndpoint(),
146+
replaceRange.upperEndpoint() - replaceRange.lowerEndpoint(),
147+
replacement.getReplacementString()));
148+
}
149+
return edit;
150+
}
151+
}

0 commit comments

Comments
 (0)