diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java index 0727cbe743be..0e44783168e2 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java @@ -947,7 +947,7 @@ public Boolean visitClass(ClassTree node, Void p) { } List perms = node.getPermitsClause(); if (perms != null && !perms.isEmpty()) { - wrapToken(cs.wrapExtendsImplementsKeyword(), 1, EXTENDS); + wrapToken(cs.wrapExtendsImplementsKeyword(), 1, EXTENDS); wrapList(cs.wrapExtendsImplementsList(), cs.alignMultilineImplements(), true, COMMA, perms); } } finally { @@ -1352,6 +1352,7 @@ private Boolean scanRecord(ClassTree node, Void p) { if (!recParams.isEmpty()) { spaces(cs.spaceWithinMethodDeclParens() ? 1 : 0, true); wrapList(cs.wrapMethodParams(), cs.alignMultilineMethodParams(), false, COMMA, recParams); + spaces(cs.spaceWithinMethodDeclParens() ? 1 : 0, true); // solves #7403 } accept(RPAREN); List impls = node.getImplementsClause(); @@ -1398,8 +1399,6 @@ private Boolean scanRecord(ClassTree node, Void p) { isFirstMember = false; } } - - spaces(cs.spaceWithinMethodDeclParens() ? 1 : 0, true); } } finally { indent = oldIndent; @@ -5425,7 +5424,7 @@ private void reformatComment() { } /** - * + * * @see for more info on inline tags check documentation here. * @return returns true if has inline tag prefix like "{+@tagname" */ diff --git a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/RecordFormattingTest.java b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/RecordFormattingTest.java new file mode 100644 index 000000000000..385328784609 --- /dev/null +++ b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/RecordFormattingTest.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.netbeans.modules.java.source.save; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.prefs.Preferences; +import javax.lang.model.SourceVersion; +import javax.swing.JEditorPane; +import javax.swing.text.Document; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.fail; +import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.source.SourceUtilsTestUtil; +import org.netbeans.api.java.source.TestUtilities; +import org.netbeans.api.lexer.Language; +import org.netbeans.junit.NbTestCase; +import org.netbeans.junit.NbTestSuite; +import org.netbeans.modules.editor.indent.api.Reformat; +import org.netbeans.modules.java.source.BootClassPathUtil; +import org.netbeans.modules.java.source.usages.IndexUtil; +import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.netbeans.spi.java.queries.SourceLevelQueryImplementation; +import org.openide.cookies.EditorCookie; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.loaders.DataObject; +import org.openide.util.SharedClassObject; + +/** + * Test to show the problem mentioned in #7043. + * + * For some strange reason, the setting spaceWithinMethodDeclParens affects the + * formatting (indentation) of the trailing brace (}) of a record definition, + * both as inner record and as top level record. + * + * @author homberghp + */ +public class RecordFormattingTest extends NbTestCase { + + File testFile = null; + private String sourceLevel = "1.8"; + private static final List EXTRA_OPTIONS = new ArrayList<>(); + + /** + * Creates a new instance of FormatingTest + */ + public RecordFormattingTest(String name) { + super(name); + } + + public static NbTestSuite suite() { + NbTestSuite suite = new NbTestSuite(); + suite.addTestSuite(RecordFormattingTest.class); + return suite; + } + + String content + = """ + record Student(int id,String lastname,String firstname) implements Serializable { + int m(int x){ + return id+x; + } + } // should stay flush to left margin + """; + + public void test7043NoSpaces() throws Exception { + run7043(""" + record Student(int id, String lastname, String firstname) implements Serializable { + int m(int x) { + return id + x; + } + } // should stay flush to left margin + """, false); + } + + public void test7043WithSpaces() throws Exception { + run7043(""" + record Student( int id, String lastname, String firstname ) implements Serializable { + int m( int x ) { + return id + x; + } + } // should stay flush to left margin + """, true); + } + + /** + * The java formatter indents the final brace '}' of a record to far to the + * right, when the setting spaceWithinMethodDeclParens is True AND the + * record header defines parameters. This + * + * @throws Exception for some reason + */ + // copied from testSealed + public void run7043(String golden, boolean spacesInMethodDecl) throws Exception { + try { + SourceVersion.valueOf("RELEASE_17"); //NOI18N + } catch (IllegalArgumentException ex) { + //OK, no RELEASE_15, skip test + return; + } + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, ""); + FileObject testSourceFO = FileUtil.toFileObject(testFile); + DataObject testSourceDO = DataObject.find(testSourceFO); + EditorCookie ec = (EditorCookie)testSourceDO.getCookie(EditorCookie.class); + Preferences preferences = MimeLookup.getLookup(JavaTokenId.language().mimeType()).lookup(Preferences.class); + preferences.putBoolean("spaceWithinMethodDeclParens", spacesInMethodDecl); + preferences.putInt("blankLinesAfterClassHeader", 0); + + final Document doc = ec.openDocument(); + doc.putProperty(Language.class, JavaTokenId.language()); + reformat(doc, content, golden); + } + + private void reformat(Document doc, String content, String golden) throws Exception { + reformat(doc, content, golden, 0, content.length()); + } + + private void reformat(Document doc, String content, String golden, int startOffset, int endOffset) throws Exception { + doc.remove(0, doc.getLength()); + doc.insertString(0, content, null); + + Reformat reformat = Reformat.get(doc); + reformat.lock(); + try { + reformat.reformat(startOffset, endOffset); + } finally { + reformat.unlock(); + } + String res = doc.getText(0, doc.getLength()); + assertNoDiff(golden, res); + } + + static void assertNoDiff(String expected, String actual) { + var expectedLines = expected.trim().split("\n"); + var actualLines = actual.trim().split("\n"); + // fail if not of equal length + String assertResult = ""; + if (expectedLines.length != actualLines.length) { + assertResult + = "number of lines differ :\n expected nr of lines = " + expectedLines.length + + " actual nr of lines= " + actualLines.length; + } + + for (int i = 0; i < expectedLines.length && i < actualLines.length; i++) { + String actualLine = actualLines[i]; + String expLine = expectedLines[i]; + if (expLine.equals(actualLine)) { + continue; + } + assertResult += "\n expected at line " + (i + 1) + ": \n'" + expLine + "'\n not equal to actual \n'" + actualLine + "'"; + } + if (assertResult.equals("")) { + return; + } + // output generated only when test fails, to help finding the difference. + for (int i = 1; i <= expectedLines.length; i++) { + String line = expectedLines[i-1]; + System.err.format("%3d: %s\n", i,line); + + } + System.err.println("actual"); + for (int i = 1; i <= actualLines.length; i++) { + String line = actualLines[i-1]; + System.err.format("%3d: %s\n", i,line); + } + fail("not golden, see stderr for more info"); + + } + +} diff --git a/java/java.source/src/org/netbeans/modules/java/ui/Bundle.properties b/java/java.source/src/org/netbeans/modules/java/ui/Bundle.properties index 7589b9380dfe..eb2edc7a97b6 100644 --- a/java/java.source/src/org/netbeans/modules/java/ui/Bundle.properties +++ b/java/java.source/src/org/netbeans/modules/java/ui/Bundle.properties @@ -497,6 +497,10 @@ default\:\ public int add(int a, int b) {\ return a + b;\ }\ +public record ARecord(String name, int age){\ +}\ +}\ +record TopLevelRecord(String name, double value){\ } #do not translate