Commits

Jan Lahoda committed 9fab0d5

Support for multistatement variables.

Comments (0)

Files changed (13)

api/src/org/netbeans/modules/jackpot30/impl/Utilities.java

 import com.sun.source.tree.MethodTree;
 import com.sun.source.tree.NewArrayTree;
 import com.sun.source.tree.Scope;
+import com.sun.source.tree.StatementTree;
 import com.sun.source.tree.Tree;
 import com.sun.source.tree.Tree.Kind;
 import com.sun.source.util.SourcePositions;
         return null;
     }
 
+    public static boolean isMultistatementWildcard(@NonNull CharSequence name) {
+        return name.charAt(name.length() - 1) == '$';
+    }
+
+    public static boolean isMultistatementWildcardTree(StatementTree tree) {
+        CharSequence name = Utilities.getWildcardTreeName(tree);
+
+        return name != null && Utilities.isMultistatementWildcard(name);
+    }
+
     private static long inc;
 
     public static Scope constructScope(CompilationInfo info, Map<String, TypeMirror> constraints) {

api/src/org/netbeans/modules/jackpot30/impl/hints/HintsInvoker.java

 import org.netbeans.modules.jackpot30.impl.pm.BulkSearch;
 import org.netbeans.modules.jackpot30.impl.pm.BulkSearch.BulkPattern;
 import org.netbeans.modules.jackpot30.impl.pm.CopyFinder;
-import org.netbeans.modules.jackpot30.impl.pm.CopyFinder.Pair;
+import org.netbeans.modules.jackpot30.impl.pm.CopyFinder.VariableAssignments;
 import org.netbeans.modules.jackpot30.impl.pm.Pattern;
 import org.netbeans.modules.jackpot30.spi.HintContext;
 import org.netbeans.modules.jackpot30.spi.HintDescription;
                 TreePath patt = new TreePath(toplevel, p.getPattern());
 
                 for (TreePath candidate : occ.getValue()) {
-                    Pair<Map<String, TreePath>, Map<String, String>> verified = CopyFinder.computeVariables(info, patt, candidate, cancel, p.getConstraints());
+                    VariableAssignments verified = CopyFinder.computeVariables(info, patt, candidate, cancel, p.getConstraints());
 
                     if (verified == null) {
                         continue;
                     }
                     
-                    HintContext c = new HintContext(info, AbstractHint.HintSeverity.WARNING, candidate, verified.getA(), verified.getB());
+                    HintContext c = new HintContext(info, AbstractHint.HintSeverity.WARNING, candidate, verified.variables, verified.multiVariables, verified.variables2Names);
 
                     for (HintDescription hd : patternHints.get(d)) {
                         Collection<? extends ErrorDescription> workerErrors = hd.getWorker().createErrors(c);
                     }
 
                     if (enabled) {
-                        HintContext c = new HintContext(info, AbstractHint.HintSeverity.WARNING, path, Collections.<String, TreePath>emptyMap(), Collections.<String, String>emptyMap());
+                        HintContext c = new HintContext(info, AbstractHint.HintSeverity.WARNING, path, Collections.<String, TreePath>emptyMap(), Collections.<String, Collection<? extends TreePath>>emptyMap(), Collections.<String, String>emptyMap());
                         Collection<? extends ErrorDescription> errors = hd.getWorker().createErrors(c);
 
                         if (errors != null) {

api/src/org/netbeans/modules/jackpot30/impl/pm/CopyFinder.java

 import com.sun.source.tree.VariableTree;
 import com.sun.source.util.TreePath;
 import com.sun.source.util.TreePathScanner;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
     private final TreePath searchingFor;
     private final CompilationInfo info;
-    private final Map<TreePath, Pair<Map<String, TreePath>, Map<String, String>>> result = new LinkedHashMap<TreePath, Pair<Map<String, TreePath>, Map<String, String>>>();
+    private final Map<TreePath, VariableAssignments> result = new LinkedHashMap<TreePath, VariableAssignments>();
     private boolean allowGoDeeper = true;
     private Map<String, TreePath> variables = new HashMap<String, TreePath>(); //XXX
+    private Map<String, Collection<? extends TreePath>> multiVariables = new HashMap<String, Collection<? extends TreePath>>(); //XXX
     private Map<String, String> variables2Names = new HashMap<String, String>(); //XXX
     private AtomicBoolean cancel;
 
     }
 
     //XXX: should probably also include designedTypeHack:
-    public static Map<TreePath, Pair<Map<String, TreePath>, Map<String, String>>> computeDuplicates(CompilationInfo info, TreePath searchingFor, TreePath scope, AtomicBoolean cancel, Map<String, TypeMirror> designedTypeHack) {
+    public static Map<TreePath, VariableAssignments> computeDuplicates(CompilationInfo info, TreePath searchingFor, TreePath scope, AtomicBoolean cancel, Map<String, TypeMirror> designedTypeHack) {
         CopyFinder f = new CopyFinder(searchingFor, info, cancel);
         
         f.designedTypeHack = designedTypeHack;
         return f.result;
     }
 
-    public static Pair<Map<String, TreePath>, Map<String, String>> computeVariables(CompilationInfo info, TreePath searchingFor, TreePath scope, AtomicBoolean cancel, Map<String, TypeMirror> designedTypeHack) {
+    public static VariableAssignments computeVariables(CompilationInfo info, TreePath searchingFor, TreePath scope, AtomicBoolean cancel, Map<String, TypeMirror> designedTypeHack) {
         if (!sameKind(searchingFor.getLeaf(), scope.getLeaf())) {
             return null;
         }
         f.designedTypeHack = designedTypeHack;
 
         if (f.scan(scope, searchingFor)) {
-            return new Pair(f.variables, f.variables2Names);
+            return new VariableAssignments(f.variables, f.multiVariables, f.variables2Names);
         }
 
         return null;
 
             if (result) {
                 if (p == searchingFor && node != searchingFor) {
-                    this.result.put(new TreePath(getCurrentPath(), node), new Pair(variables, variables2Names));
+                    this.result.put(new TreePath(getCurrentPath(), node), new VariableAssignments(variables, multiVariables, variables2Names));
                     variables = new HashMap<String, TreePath>();
                     variables2Names = new HashMap<String, String>();
                 }
             
             if (result) {
                 if (node != searchingFor.getLeaf()) {
-                    this.result.put(new TreePath(getCurrentPath(), node), new Pair(variables, variables2Names));
+                    this.result.put(new TreePath(getCurrentPath(), node), new VariableAssignments(variables, multiVariables, variables2Names));
                     variables = new HashMap<String, TreePath>();
                     variables2Names = new HashMap<String, String>();
                 }
         if (one == null || other == null) {
             return one == other;
         }
-        
+
         if (one.size() != other.size())
             return false;
         
         return result && scan(node.getRightOperand(), bt.getRightOperand(), p);
     }
 
+    private static boolean containsMultistatementTrees(List<? extends StatementTree> statements) {
+        for (StatementTree t : statements) {
+            if (Utilities.isMultistatementWildcardTree(t)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    //TODO: currently, only the first matching combination is found:
+    private boolean checkListsWithMultistatementTrees(List<? extends StatementTree> real, int realOffset, List<? extends StatementTree> pattern, int patternOffset, TreePath p) {
+        while (realOffset < real.size() && patternOffset < pattern.size() && !Utilities.isMultistatementWildcardTree(pattern.get(patternOffset))) {
+            if (!scan(real.get(realOffset), pattern.get(patternOffset), p)) {
+                return false;
+            }
+
+            realOffset++;
+            patternOffset++;
+        }
+
+        if (realOffset == real.size() && patternOffset == pattern.size()) {
+            return true;
+        }
+
+        if (Utilities.isMultistatementWildcardTree(pattern.get(patternOffset))) {
+            if (patternOffset + 1 == pattern.size()) {
+                List<TreePath> tps = new LinkedList<TreePath>();
+
+                for (StatementTree t : real.subList(realOffset, real.size())) {
+                    tps.add(new TreePath(getCurrentPath(), t));
+                }
+
+                multiVariables.put(Utilities.getWildcardTreeName(pattern.get(patternOffset)).toString(), tps);
+                return true;
+            }
+            
+            List<TreePath> tps = new LinkedList<TreePath>();
+            
+            while (realOffset < real.size()) {
+                Map<String, TreePath> variables = this.variables;
+                Map<String, Collection<? extends TreePath>> multiVariables = this.multiVariables;
+                Map<String, String> variables2Names = this.variables2Names;
+
+                this.variables = new HashMap<String, TreePath>(variables);
+                this.multiVariables = new HashMap<String, Collection<? extends TreePath>>(multiVariables);
+                this.variables2Names = new HashMap<String, String>(variables2Names);
+
+                if (checkListsWithMultistatementTrees(real, realOffset, pattern, patternOffset + 1, p)) {
+                    this.multiVariables.put(Utilities.getWildcardTreeName(pattern.get(patternOffset)).toString(), tps);
+                    return true;
+                }
+
+                this.variables = variables;
+                this.multiVariables = multiVariables;
+                this.variables2Names = variables2Names;
+
+                tps.add(new TreePath(getCurrentPath(), real.get(realOffset)));
+
+                realOffset++;
+            }
+
+            return false;
+        }
+
+        return false;
+    }
+    
     public Boolean visitBlock(BlockTree node, TreePath p) {
         if (p == null) {
             super.visitBlock(node, p);
             return false;
         }
 
+        if (containsMultistatementTrees(at.getStatements())) {
+            return checkListsWithMultistatementTrees(node.getStatements(), 0, at.getStatements(), 0, p);
+        }
+        
         return checkLists(node.getStatements(), at.getStatements(), p);
     }
 
 //    }
     
 
-    public static final class Pair<A, B> {
-        private final A a;
-        private final B b;
+    public static final class VariableAssignments {
+        public final Map<String, TreePath> variables;
+        public final Map<String, Collection<? extends TreePath>> multiVariables;
+        public final Map<String, String> variables2Names;
 
-        public Pair(A a, B b) {
-            this.a = a;
-            this.b = b;
-        }
-
-        public A getA() {
-            return a;
-        }
-
-        public B getB() {
-            return b;
+        VariableAssignments(Map<String, TreePath> variables, Map<String, Collection<? extends TreePath>> multiVariables, Map<String, String> variables2Names) {
+            this.variables = variables;
+            this.multiVariables = multiVariables;
+            this.variables2Names = variables2Names;
         }
 
     }

api/src/org/netbeans/modules/jackpot30/impl/pm/Pattern.java

 import javax.lang.model.type.TypeMirror;
 import org.netbeans.api.java.source.CompilationInfo;
 import org.netbeans.modules.jackpot30.impl.Utilities;
-import org.netbeans.modules.jackpot30.impl.pm.CopyFinder.Pair;
+import org.netbeans.modules.jackpot30.impl.pm.CopyFinder.VariableAssignments;
 
 /**XXX: cancelability!
  *
     }
 
     public Map<String, TreePath> match(TreePath toCheck) {
-        Pair<Map<String, TreePath>, Map<String, String>> variables = CopyFinder.computeVariables(info, new TreePath(new TreePath(info.getCompilationUnit()), patternTree), toCheck, new AtomicBoolean(), constraintsHack);
+        VariableAssignments variables = CopyFinder.computeVariables(info, new TreePath(new TreePath(info.getCompilationUnit()), patternTree), toCheck, new AtomicBoolean(), constraintsHack);
 
         if (variables == null) {
             return null;
         }
 
-        return variables.getA();
+        return variables.variables;
     }
 
     public boolean checkAntipatterns(TreePath tp) {

api/src/org/netbeans/modules/jackpot30/impl/pm/TreeSerializer.java

             return null;
         }
 
-        if (Utilities.getWildcardTreeName(tree) != null) {
+        CharSequence wildcardTreeName = Utilities.getWildcardTreeName(tree);
+
+        if (wildcardTreeName != null) {
+            boolean isMultistatementWildcard = Utilities.isMultistatementWildcard(wildcardTreeName);
+
+            if (isMultistatementWildcard) {
+                append(p, "(?:");
+            }
+
             append(p, "<([0-9a-z]+)>.*?</\\" + (group++) + ">");
+
+            if (isMultistatementWildcard) {
+                append(p, ")*");
+            }
+
             return null;
         }
 

api/src/org/netbeans/modules/jackpot30/spi/HintContext.java

 package org.netbeans.modules.jackpot30.spi;
 
 import com.sun.source.util.TreePath;
+import java.util.Collection;
 import java.util.Map;
 import org.netbeans.api.java.source.CompilationInfo;
 import org.netbeans.modules.java.hints.spi.AbstractHint.HintSeverity;
     private final HintSeverity severity;
     private final TreePath path;
     private final Map<String, TreePath> variables;
+    private final Map<String, Collection<? extends TreePath>> multiVariables;
     private final Map<String, String> variableNames;
 
-    public HintContext(CompilationInfo info, HintSeverity severity, TreePath path, Map<String, TreePath> variables, Map<String, String> variableNames) {
+    public HintContext(CompilationInfo info, HintSeverity severity, TreePath path, Map<String, TreePath> variables, Map<String, Collection<? extends TreePath>> multiVariables, Map<String, String> variableNames) {
         this.info = info;
         this.severity = severity;
         this.path = path;
         this.variables = variables;
+        this.multiVariables = multiVariables;
         this.variableNames = variableNames;
     }
 
         return variables;
     }
 
+    public Map<String, Collection<? extends TreePath>> getMultiVariables() {
+        return multiVariables;
+    }
+
     public Map<String, String> getVariableNames() {
         return variableNames;
     }
 
     //XXX: probably should not be visible to clients:
-    public static HintContext create(CompilationInfo info, HintSeverity severity, TreePath path, Map<String, TreePath> variables, Map<String, String> variableNames) {
-        return new HintContext(info, severity, path, variables, variableNames);
+    public static HintContext create(CompilationInfo info, HintSeverity severity, TreePath path, Map<String, TreePath> variables, Map<String, Collection<? extends TreePath>> multiVariables, Map<String, String> variableNames) {
+        return new HintContext(info, severity, path, variables, multiVariables, variableNames);
     }
 }

api/src/org/netbeans/modules/jackpot30/spi/JavaFix.java

 package org.netbeans.modules.jackpot30.spi;
 
 import com.sun.javadoc.Tag;
+import com.sun.org.apache.xpath.internal.SourceTree;
+import com.sun.source.tree.BlockTree;
 import com.sun.source.tree.ExpressionStatementTree;
 import com.sun.source.tree.VariableTree;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.regex.Matcher;
 import com.sun.source.tree.IdentifierTree;
 import com.sun.source.tree.MemberSelectTree;
 import com.sun.source.tree.Scope;
+import com.sun.source.tree.StatementTree;
 import com.sun.source.tree.Tree;
 import com.sun.source.tree.Tree.Kind;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.TreePath;
 import com.sun.source.util.TreePathScanner;
 import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.logging.Level;
         return handle.getFileObject();
     }
 
-    public static Fix rewriteFix(CompilationInfo info, String displayName, TreePath what, final String to, Map<String, TreePath> parameters, final Map<String, String> parameterNames, Map<String, TypeMirror> constraints) {
+    public static Fix rewriteFix(CompilationInfo info, String displayName, TreePath what, final String to, Map<String, TreePath> parameters, Map<String, Collection<? extends TreePath>> parametersMulti, final Map<String, String> parameterNames, Map<String, TypeMirror> constraints) {
         final Map<String, TreePathHandle> params = new HashMap<String, TreePathHandle>();
 
         for (Entry<String, TreePath> e : parameters.entrySet()) {
             params.put(e.getKey(), TreePathHandle.create(e.getValue(), info));
         }
 
+        final Map<String, Collection<TreePathHandle>> paramsMulti = new HashMap<String, Collection<TreePathHandle>>();
+
+        for (Entry<String, Collection<? extends TreePath>> e : parametersMulti.entrySet()) {
+            Collection<TreePathHandle> tph = new LinkedList<TreePathHandle>();
+
+            for (TreePath tp : e.getValue()) {
+                tph.add(TreePathHandle.create(tp, info));
+            }
+
+            paramsMulti.put(e.getKey(), tph);
+        }
+
         final Map<String, TypeMirrorHandle> constraintsHandles = new HashMap<String, TypeMirrorHandle>();
 
         for (Entry<String, TypeMirror> c : constraints.entrySet()) {
                 for (Entry<String, TreePathHandle> e : params.entrySet()) {
                     TreePath p = e.getValue().resolve(wc);
 
-                    if (tp == null) {
+                    if (p == null) {
                         Logger.getLogger(JavaFix.class.getName()).log(Level.SEVERE, "Cannot resolve handle={0}", e.getValue());
                     }
 
                     parameters.put(e.getKey(), p);
                 }
 
+                final Map<String, Collection<TreePath>> parametersMulti = new HashMap<String, Collection<TreePath>>();
+
+                for (Entry<String, Collection<TreePathHandle>> e : paramsMulti.entrySet()) {
+                    Collection<TreePath> tps = new LinkedList<TreePath>();
+
+                    for (TreePathHandle tph : e.getValue()) {
+                        TreePath p = tph.resolve(wc);
+
+                        if (p == null) {
+                            Logger.getLogger(JavaFix.class.getName()).log(Level.SEVERE, "Cannot resolve handle={0}", e.getValue());
+                        }
+
+                        tps.add(p);
+                    }
+
+                    parametersMulti.put(e.getKey(), tps);
+                }
+
                 Map<String, TypeMirror> constraints = new HashMap<String, TypeMirror>();
 
                 for (Entry<String, TypeMirrorHandle> c : constraintsHandles.entrySet()) {
                         
                         return super.visitExpressionStatement(node, p);
                     }
+                    @Override
+                    public Void visitBlock(BlockTree node, Void p) {
+                        List<StatementTree> nueStatement = new LinkedList<StatementTree>();
 
+                        for (StatementTree t : node.getStatements()) {
+                            if (Utilities.isMultistatementWildcardTree(t)) {
+                                for (TreePath tp : parametersMulti.get(Utilities.getWildcardTreeName(t).toString())) {
+                                    nueStatement.add((StatementTree) tp.getLeaf());
+                                }
+                            } else {
+                                nueStatement.add(t);
+                            }
+                        }
+
+                        BlockTree nue = wc.getTreeMaker().Block(nueStatement, node.isStatic());
+
+                        wc.rewrite(node, nue);
+
+                        return super.visitBlock(nue, p);
+                    }
                 }.scan(new TreePath(new TreePath(tp.getCompilationUnit()), parsed), null);
 
                 wc.rewrite(tp.getLeaf(), parsed);

api/test/unit/src/org/netbeans/modules/jackpot30/impl/hints/HintsInvokerTest.java

                        "}\n").replaceAll("[ \t\n]+", " "));
     }
 
+    public void testMultiStatementVariables1() throws Exception {
+        performFixTest("test/Test.java",
+                       "|package test;\n" +
+                       "\n" +
+                       "public class Test {\n" +
+                       "     private int test(int j) {\n" +
+                       "         j++;\n" +
+                       "         j++;\n" +
+                       "         int i = 3;\n" +
+                       "         j++;\n" +
+                       "         j++;\n" +
+                       "         return i;\n" +
+                       "     }\n" +
+                       "}\n",
+                       "3:29-10:6:verifier:HINT",
+                       "FixImpl",
+                       ("package test;\n" +
+                       "\n" +
+                       "public class Test {\n" +
+                       "     private int test(int j) {\n" +
+                       "         j++;\n" +
+                       "         j++;\n" +
+                       "         float i = 3;\n" +
+                       "         j++;\n" +
+                       "         j++;\n" +
+                       "         return i;\n" +
+                       "     }\n" +
+                       "}\n").replaceAll("[ \t\n]+", " "));
+    }
+
+    public void testMultiStatementVariables2() throws Exception {
+        performFixTest("test/Test.java",
+                       "|package test;\n" +
+                       "\n" +
+                       "public class Test {\n" +
+                       "     private int test(int j) {\n" +
+                       "         int i = 3;\n" +
+                       "         return i;\n" +
+                       "     }\n" +
+                       "}\n",
+                       "3:29-6:6:verifier:HINT",
+                       "FixImpl",
+                       ("package test;\n" +
+                       "\n" +
+                       "public class Test {\n" +
+                       "     private int test(int j) {\n" +
+                       "         float i = 3;\n" +
+                       "         return i;\n" +
+                       "     }\n" +
+                       "}\n").replaceAll("[ \t\n]+", " "));
+    }
+
     private static final Map<String, HintDescription> test2Hint;
 
     static {
         test2Hint.put("testPatternFalseOccurrence", HintDescriptionFactory.create().setTriggerPattern(PatternDescription.create("$1.toURL()", Collections.singletonMap("$1", "java.io.File"))).setWorker(new WorkerImpl()).produce());
         test2Hint.put("testStatementVariables1", HintDescriptionFactory.create().setTriggerPattern(PatternDescription.create("if ($1) $2; else $3;", constraints)).setWorker(new WorkerImpl("if (!$1) $3; else $2;")).produce());
         test2Hint.put("testStatementVariables2", test2Hint.get("testStatementVariables1"));
+        test2Hint.put("testMultiStatementVariables1", HintDescriptionFactory.create().setTriggerPattern(PatternDescription.create("{ $pref$; int $i = 3; $inf$; return $i; }", Collections.<String, String>emptyMap())).setWorker(new WorkerImpl("{ $pref$; float $i = 3; $inf$; return $i; }")).produce());
+        test2Hint.put("testMultiStatementVariables2", test2Hint.get("testMultiStatementVariables1"));
     }
 
     @Override
             List<Fix> fixes = new LinkedList<Fix>();
 
             if (fix != null) {
-                fixes.add(JavaFix.rewriteFix(ctx.getInfo(), "Rewrite", ctx.getPath(), fix, ctx.getVariables(), ctx.getVariableNames(), /*XXX*/Collections.<String, TypeMirror>emptyMap()));
+                fixes.add(JavaFix.rewriteFix(ctx.getInfo(), "Rewrite", ctx.getPath(), fix, ctx.getVariables(), ctx.getMultiVariables(), ctx.getVariableNames(), /*XXX*/Collections.<String, TypeMirror>emptyMap()));
             }
             
             return Collections.singletonList(ErrorDescriptionFactory.forName(ctx, ctx.getPath(), "HINT", fixes.toArray(new Fix[0])));

api/test/unit/src/org/netbeans/modules/jackpot30/impl/pm/BulkSearchTest.java

                     Collections.<String>emptyList());
     }
 
+    public void testMultiStatementVariables1() throws Exception {
+        performTest("package test; public class Test { public int test1(int i) { System.err.println(i); System.err.println(i); i = 3; System.err.println(i); System.err.println(i); return i; } }",
+                    Collections.singletonMap("{ $s1$; i = 3; $s2$; return i; }", Arrays.asList("{ System.err.println(i); System.err.println(i); i = 3; System.err.println(i); System.err.println(i); return i; }")),
+                    Collections.<String>emptyList());
+    }
+
+    public void testMultiStatementVariables2() throws Exception {
+        performTest("package test; public class Test { public int test1(int i) { i = 3; return i; } }",
+                    Collections.singletonMap("{ $s1$; i = 3; $s2$; return i; }", Arrays.asList("{ i = 3; return i; }")),
+                    Collections.<String>emptyList());
+    }
+
     public void XtestMeasureTime() throws Exception {
         String code = TestUtilities.copyFileToString(new File("/usr/local/home/lahvac/src/nb//outgoing/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionProvider.java"));
         List<String> patterns = new LinkedList<String>();

api/test/unit/src/org/netbeans/modules/jackpot30/impl/pm/CopyFinderTest.java

 import org.netbeans.api.java.source.TestUtilities;
 import org.netbeans.api.lexer.Language;
 import org.netbeans.junit.NbTestCase;
-import org.netbeans.modules.jackpot30.impl.pm.CopyFinder.Pair;
+import org.netbeans.modules.jackpot30.impl.pm.CopyFinder.VariableAssignments;
 import org.openide.cookies.EditorCookie;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
                              new Pair[0]);
     }
 
+    public void testMultiStatementVariables1() throws Exception {
+        performVariablesTest("package test; public class Test { public int test1() { System.err.println(); System.err.println(); int i = 3; System.err.println(i); System.err.println(i); return i; } }",
+                             "{ $s1$; int $i = 3; $s2$; return $i; }",
+                             new Pair[0],
+                             new Pair[] {
+                                  new Pair<String, int[]>("$s1$", new int[] {55, 76, 77, 98}),
+                                  new Pair<String, int[]>("$s2$", new int[] {110, 132, 133, 155})
+                             },
+                             new Pair[] {new Pair<String, String>("$i", "i")});
+    }
+
+    public void testMultiStatementVariables2() throws Exception {
+        performVariablesTest("package test; public class Test { public int test1() { int i = 3; return i; } }",
+                             "{ $s1$; int $i = 3; $s2$; return $i; }",
+                             new Pair[0],
+                             new Pair[] {
+                                  new Pair<String, int[]>("$s1$", new int[] {}),
+                                  new Pair<String, int[]>("$s2$", new int[] {}),
+                             },
+                             new Pair[] {new Pair<String, String>("$i", "i")});
+    }
+    
     protected void performVariablesTest(String code, String pattern, Pair<String, int[]>[] duplicatesPos, Pair<String, String>[] duplicatesNames) throws Exception {
+        performVariablesTest(code, pattern, duplicatesPos, new Pair[0], duplicatesNames);
+    }
+
+    protected void performVariablesTest(String code, String pattern, Pair<String, int[]>[] duplicatesPos, Pair<String, int[]>[] multiStatementPos, Pair<String, String>[] duplicatesNames) throws Exception {
         prepareTest(code, -1);
 
         Tree patternTree = Pattern.parseAndAttribute(info, pattern, Collections.<String, TypeMirror>emptyMap(), new Scope[1]);
-        Map<TreePath, Pair<Map<String, TreePath>, Map<String, String>>> result = CopyFinder.computeDuplicates(info, new TreePath(new TreePath(info.getCompilationUnit()), patternTree), new TreePath(info.getCompilationUnit()), new AtomicBoolean(), Collections.<String, TypeMirror>emptyMap());
+        Map<TreePath, VariableAssignments> result = CopyFinder.computeDuplicates(info, new TreePath(new TreePath(info.getCompilationUnit()), patternTree), new TreePath(info.getCompilationUnit()), new AtomicBoolean(), Collections.<String, TypeMirror>emptyMap());
 
         assertSame(1, result.size());
 
         Map<String, int[]> actual = new HashMap<String, int[]>();
 
-        for (Entry<String, TreePath> e : result.values().iterator().next().getA().entrySet()) {
+        for (Entry<String, TreePath> e : result.values().iterator().next().variables.entrySet()) {
             int[] span = new int[] {
                 (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), e.getValue().getLeaf()),
                 (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), e.getValue().getLeaf())
             assertTrue(dup.getA() + ":" + Arrays.toString(span), Arrays.equals(span, dup.getB()));
         }
 
+        Map<String, int[]> actualMulti = new HashMap<String, int[]>();
+
+        for (Entry<String, Collection<? extends TreePath>> e : result.values().iterator().next().multiVariables.entrySet()) {
+            int[] span = new int[2 * e.getValue().size()];
+            int i = 0;
+            
+            for (TreePath tp : e.getValue()) {
+                span[i++] = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), tp.getLeaf());
+                span[i++] = (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), tp.getLeaf());
+            }
+
+            actualMulti.put(e.getKey(), span);
+        }
+
+        for (Pair<String, int[]> dup : multiStatementPos) {
+            int[] span = actualMulti.remove(dup.getA());
+
+            if (span == null) {
+                fail(dup.getA());
+            }
+            assertTrue(dup.getA() + ":" + Arrays.toString(span), Arrays.equals(span, dup.getB()));
+        }
+
         Map<String, String> golden = new HashMap<String, String>();
 
         for ( Pair<String, String> e : duplicatesNames) {
             golden.put(e.getA(), e.getB());
         }
 
-        assertEquals(golden, result.values().iterator().next().getB());
+        assertEquals(golden, result.values().iterator().next().variables2Names);
     }
 
 //    @Override
     protected CompilationInfo info;
     private Document doc;
 
+    public static final class Pair<A, B> {
+        private final A a;
+        private final B b;
+
+        public Pair(A a, B b) {
+            this.a = a;
+            this.b = b;
+        }
+
+        public A getA() {
+            return a;
+        }
+
+        public B getB() {
+            return b;
+        }
+
+    }
 }

file/src/org/netbeans/modules/jackpot30/file/DeclarativeHintsWorker.java

         Fix[] editorFixes = new Fix[fixes.size()];
 
         for (int cntr = 0; cntr < fixes.size(); cntr++) {
-            editorFixes[cntr] = JavaFix.rewriteFix(ctx.getInfo(), fixes.get(cntr).getDisplayName(), ctx.getPath(), fixes.get(cntr).getPattern(), ctx.getVariables(), ctx.getVariableNames(), Collections.<String, TypeMirror>emptyMap()/*XXX*/);
+            editorFixes[cntr] = JavaFix.rewriteFix(ctx.getInfo(), fixes.get(cntr).getDisplayName(), ctx.getPath(), fixes.get(cntr).getPattern(), ctx.getVariables(), ctx.getMultiVariables(), ctx.getVariableNames(), Collections.<String, TypeMirror>emptyMap()/*XXX*/);
         }
 
         ErrorDescription ed = ErrorDescriptionFactory.forName(ctx, ctx.getPath(), displayName, editorFixes);

jackpot/test/unit/src/org/netbeans/modules/jackpot30/jackpot/AddConditionals.test

 package test;
 public class Test {
     public void test(boolean b) {
-        int q;
+        int q = 3;
         if (b)
             q = 1;
         else
 package test;
 public class Test {
     public void test(boolean b) {
-        int q;
+        int q = 3;
         q = b ? 1 : 2;
     }
 }
         String s = (String) (b ? o1 : o2);
     }
 }
+%%TestCase simplify-with-declaration
+package test;
+public class Test {
+    public void test(boolean b) {
+        int q;
+        if (b)
+            q = 1;
+        else
+            q = 2;
+    }
+}
+%%=>
+package test;
+public class Test {
+    public void test(boolean b) {
+        int q = b ? 1 : 2;
+    }
+}
+%%=>
+package test;
+public class Test {
+    public void test(boolean b) {
+        int q;
+        q = b ? 1 : 2;
+    }
+}

transformer/src/org/netbeans/modules/jackpot30/transformers/TransformationHintProviderImpl.java

                     dn = defaultFixDisplayName(ctx.getInfo(), ctx.getVariables(), d);
                 }
 
-                fixes.add(JavaFix.rewriteFix(ctx.getInfo(), dn, ctx.getPath(), d.getPattern(), ctx.getVariables(), ctx.getVariableNames(), Collections.<String, TypeMirror>emptyMap()/*XXX: pd.pattern.getConstraints()*/));
+                fixes.add(JavaFix.rewriteFix(ctx.getInfo(), dn, ctx.getPath(), d.getPattern(), ctx.getVariables(), ctx.getMultiVariables(), ctx.getVariableNames(), Collections.<String, TypeMirror>emptyMap()/*XXX: pd.pattern.getConstraints()*/));
             }
 
             return Collections.singletonList(ErrorDescriptionFactory.forName(ctx, ctx.getPath(), displayName, fixes.toArray(new Fix[0])));