From 3f9f84107dc6956ea17c26442a69a0d20fa6a0ba Mon Sep 17 00:00:00 2001 From: Lucas Tavares Criscuolo <141073918+Stuaarts@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:57:31 -0400 Subject: [PATCH] Fix sout expansion in unbraced for-loop bodies Preserve loop-header validation while accepting code templates in single-statement loop bodies. Add regression coverage for classic and enhanced for loops and incomplete headers. Assisted-by: GPT-5 Codex GPT-5 --- .../editor/java/JavaCodeTemplateFilter.java | 17 +++++- .../java/JavaCodeTemplateProcessorTest.java | 55 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java index e6291187b20d..9d1e1b5a1334 100644 --- a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java +++ b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java @@ -29,7 +29,9 @@ import javax.swing.text.JTextComponent; import com.sun.source.tree.CaseTree; import com.sun.source.tree.ClassTree; +import com.sun.source.tree.EnhancedForLoopTree; import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.ForLoopTree; import com.sun.source.util.SourcePositions; import com.sun.source.util.TreePath; import java.util.Set; @@ -144,7 +146,8 @@ public void run(ResultIterator resultIterator) throws Exception { } case FOR_LOOP: case ENHANCED_FOR_LOOP: - if (!isRightParenthesisOfLoopPresent(controller, so)) { + if (!isInLoopBody(controller, tree, so) + && !isRightParenthesisOfLoopPresent(controller, so)) { treeKindCtx = null; } break; @@ -177,6 +180,18 @@ private boolean isRightParenthesisOfLoopPresent(CompilationController controller } return false; } + + private boolean isInLoopBody(CompilationController controller, Tree loop, int abbrevStartOffset) { + Tree statement; + if (loop.getKind() == Tree.Kind.FOR_LOOP) { + statement = ((ForLoopTree) loop).getStatement(); + } else { + statement = ((EnhancedForLoopTree) loop).getStatement(); + } + long statementStart = controller.getTrees().getSourcePositions() + .getStartPosition(controller.getCompilationUnit(), statement); + return statementStart >= 0 && abbrevStartOffset >= statementStart; + } private TokenId skipNextWhitespaces(TokenSequence tokenSequence) { TokenId tokenId = null; diff --git a/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java b/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java index 4313b46eb955..77f0201f20a1 100644 --- a/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java +++ b/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java @@ -34,6 +34,7 @@ import org.netbeans.lib.editor.codetemplates.CodeTemplateInsertHandler; import org.netbeans.lib.editor.codetemplates.api.CodeTemplate; import org.netbeans.lib.editor.codetemplates.api.CodeTemplateManager; +import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateFilter; import org.netbeans.lib.editor.codetemplates.storage.CodeTemplateSettingsImpl.OnExpandAction; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; @@ -203,6 +204,60 @@ public void testCodeTemplatesShouldWorkInsideParenthesesOfWhileLoop() throws Exc "}"); } + public void testSoutTemplateIsAcceptedInUnbracedForLoopBody() throws Exception { + assertSoutTemplateAccepted("public class Test {\n" + + " private void test() {\n" + + " for (int i = 0; i < 3; i++)\n" + + " sout\n" + + " }\n" + + "}", true); + } + + public void testSoutTemplateIsAcceptedInUnbracedEnhancedForLoopBody() throws Exception { + assertSoutTemplateAccepted("public class Test {\n" + + " private void test(String[] values) {\n" + + " for (String value : values)\n" + + " sout\n" + + " }\n" + + "}", true); + } + + public void testSoutTemplateIsRejectedInIncompleteForLoopHeader() throws Exception { + assertSoutTemplateAccepted("public class Test {\n" + + " private void test() {\n" + + " for (int i = 0; sout\n" + + " }\n" + + "}", false); + } + + private void assertSoutTemplateAccepted(String text, boolean expected) throws Exception { + clearWorkDir(); + testFile = FileUtil.toFileObject(getWorkDir()).createData("Test.java"); + EditorKit kit = new JavaKit(); + JEditorPane pane = new JEditorPane(); + SwingUtilities.invokeAndWait(() -> { + pane.setEditorKit(kit); + }); + Document doc = pane.getDocument(); + doc.putProperty(Document.StreamDescriptionProperty, testFile); + doc.putProperty(Language.class, JavaTokenId.language()); + doc.putProperty("mimeType", "text/x-java"); + pane.setText(text); + try (OutputStream out = testFile.getOutputStream(); + Writer w = new OutputStreamWriter(out)) { + w.append(text); + } + CodeTemplate sout = CodeTemplateManager.get(doc).getCodeTemplates().stream() + .filter(template -> "sout".equals(template.getAbbreviation())) + .findFirst() + .orElseThrow(); + int abbreviationOffset = text.indexOf("sout"); + CodeTemplateFilter filter = new JavaCodeTemplateFilter.Factory() + .createFilter(doc, abbreviationOffset, -1); + + assertEquals(expected, filter.accept(sout)); + } + public void testInfiteLoop() throws Exception { doTestTemplateInsert("for (${IT_TYPE rightSideType type=\"java.util.Iterator\" default=\"Iterator\" editable=false} ${IT newVarName default=\"it\"} = ${COL instanceof=\"java.util.Collection\" default=\"col\"}.iterator(); ${IT}.hasNext();) {\n" + " ${TYPE rightSideType default=\"Object\"} ${ELEM newVarName default=\"elem\"} = ${TYPE_CAST cast default=\"\" editable=false}${IT}.next();\n" +