Commits

Jan Lahoda committed 9a2a931

Support for variables in patterns.

  • Participants
  • Parent commits 85e5205

Comments (0)

Files changed (17)

File 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.Pattern;
 import org.netbeans.modules.jackpot30.spi.HintContext;
 import org.netbeans.modules.jackpot30.spi.HintDescription;
                 TreePath toplevel = new TreePath(info.getCompilationUnit());
                 TreePath patt = new TreePath(toplevel, p.getPattern());
 
-                for (Entry<TreePath, Map<String, TreePath>> e : CopyFinder.computeDuplicates(info, patt, startAt, cancel).entrySet()) {
-                    HintContext c = new HintContext(info, AbstractHint.HintSeverity.WARNING, e.getKey(), e.getValue());
+                for (Entry<TreePath, Pair<Map<String, TreePath>, Map<String, String>>> e : CopyFinder.computeDuplicates(info, patt, startAt, cancel).entrySet()) {
+                    HintContext c = new HintContext(info, AbstractHint.HintSeverity.WARNING, e.getKey(), e.getValue().getA(), e.getValue().getB());
                     
                     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());
+                        HintContext c = new HintContext(info, AbstractHint.HintSeverity.WARNING, path, Collections.<String, TreePath>emptyMap(), Collections.<String, String>emptyMap());
                         Collection<? extends ErrorDescription> errors = hd.getWorker().createErrors(c);
 
                         if (errors != null) {

File api/src/org/netbeans/modules/jackpot30/impl/pm/BulkSearch.java

 
 package org.netbeans.modules.jackpot30.impl.pm;
 
+import com.sun.source.tree.IdentifierTree;
 import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
 import com.sun.source.util.SourcePositions;
 import java.util.Arrays;
 import java.util.Collection;
         int i = 0;
 
         for (String c : code) {
-            patterns[i++] = info.getTreeUtilities().parseExpression(c, new SourcePositions[1]);
+            Tree t = info.getTreeUtilities().parseExpression(c, new SourcePositions[1]);
+
+            if (t.getKind() == Kind.ERRONEOUS || (t.getKind() == Kind.IDENTIFIER && ((IdentifierTree) t).getName().contentEquals("<error>"))) { //TODO: <error>...
+                t = info.getTreeUtilities().parseStatement(c, new SourcePositions[1]);
+            }
+
+            patterns[i++] = t;
         }
 
         int[] groups = TreeSerializer.serializePatterns(ser, patterns);
-        
+
         return BulkPattern.create(new LinkedList<String>(code), ser.toString(), groups);
     }
 

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

 import com.sun.source.tree.LiteralTree;
 import com.sun.source.tree.MemberSelectTree;
 import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.ModifiersTree;
 import com.sun.source.tree.NewArrayTree;
 import com.sun.source.tree.NewClassTree;
 import com.sun.source.tree.ParameterizedTypeTree;
 import com.sun.source.tree.Tree.Kind;
 import com.sun.source.tree.TypeCastTree;
 import com.sun.source.tree.UnaryTree;
+import com.sun.source.tree.VariableTree;
 import com.sun.source.util.TreePath;
 import com.sun.source.util.TreePathScanner;
 import java.util.HashMap;
 
     private final TreePath searchingFor;
     private final CompilationInfo info;
-    private final Map<TreePath, Map<String, TreePath>> result = new LinkedHashMap<TreePath, Map<String, TreePath>>();
+    private final Map<TreePath, Pair<Map<String, TreePath>, Map<String, String>>> result = new LinkedHashMap<TreePath, Pair<Map<String, TreePath>, Map<String, String>>>();
     private boolean allowGoDeeper = true;
     private Map<String, TreePath> variables = new HashMap<String, 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, Map<String, TreePath>> computeDuplicates(CompilationInfo info, TreePath searchingFor, TreePath scope, AtomicBoolean cancel) {
+    public static Map<TreePath, Pair<Map<String, TreePath>, Map<String, String>>> computeDuplicates(CompilationInfo info, TreePath searchingFor, TreePath scope, AtomicBoolean cancel) {
         CopyFinder f = new CopyFinder(searchingFor, info, cancel);
         
         f.scan(scope, null);
         return f.result;
     }
 
-    public static Map<String, TreePath> computeVariables(CompilationInfo info, TreePath searchingFor, TreePath scope, AtomicBoolean cancel, Map<String, TypeMirror> designedTypeHack) {
+    public static Pair<Map<String, TreePath>, Map<String, String>> 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 f.variables;
+            return new Pair(f.variables, f.variables2Names);
         }
 
         return null;
             String ident = ((IdentifierTree) p.getLeaf()).getName().toString();
 
             if (ident.startsWith("$")) {
+                if (variables2Names.containsKey(ident)) {
+                    return ((IdentifierTree) node).getName().toString().equals(variables2Names.get(ident));
+                }
+                
                 TreePath currentPath = new TreePath(getCurrentPath(), node);
                 TypeMirror designed = designedTypeHack != null ? designedTypeHack.get(ident) : null;//info.getTrees().getTypeMirror(p);
 
 
             if (result) {
                 if (p == searchingFor && node != searchingFor) {
-                    this.result.put(new TreePath(getCurrentPath(), node), variables);
+                    this.result.put(new TreePath(getCurrentPath(), node), new Pair(variables, variables2Names));
                     variables = new HashMap<String, TreePath>();
+                    variables2Names = new HashMap<String, String>();
                 }
                 
                 return true;
             
             if (result) {
                 if (node != searchingFor.getLeaf()) {
-                    this.result.put(new TreePath(getCurrentPath(), node), variables);
+                    this.result.put(new TreePath(getCurrentPath(), node), new Pair(variables, variables2Names));
                     variables = new HashMap<String, TreePath>();
+                    variables2Names = new HashMap<String, String>();
                 }
                 
                 return true;
 //        throw new UnsupportedOperationException("Not supported yet.");
 //    }
 
-//    public Boolean visitModifiers(ModifiersTree node, TreePath p) {
-//        throw new UnsupportedOperationException("Not supported yet.");
-//    }
+    public Boolean visitModifiers(ModifiersTree node, TreePath p) {
+        if (p == null)
+            return super.visitModifiers(node, p);
+
+        ModifiersTree t = (ModifiersTree) p.getLeaf();
+
+        if (!checkLists(node.getAnnotations(), t.getAnnotations(), p))
+            return false;
+
+        return node.getFlags().equals(t.getFlags());
+    }
 
     public Boolean visitNewArray(NewArrayTree node, TreePath p) {
         if (p == null)
         return scan(node.getExpression(), t.getExpression(), p);
     }
 
-//    public Boolean visitVariable(VariableTree node, TreePath p) {
-//        throw new UnsupportedOperationException("Not supported yet.");
-//    }
+    public Boolean visitVariable(VariableTree node, TreePath p) {
+        if (p == null) {
+            return super.visitVariable(node, p);
+        }
+
+        VariableTree t = (VariableTree) p.getLeaf();
+
+        if (!scan(node.getModifiers(), t.getModifiers(), p))
+            return false;
+
+        if (!scan(node.getType(), t.getType(), p))
+            return false;
+
+        String name = t.getName().toString();
+
+        if (name.startsWith("$")) { //XXX: there should be a utility method for this check
+            String existingName = variables2Names.get(name);
+            String currentName = node.getName().toString();
+
+            if (existingName != null) {
+                if (!existingName.equals(name)) {
+                    return false;
+                }
+            } else {
+                variables2Names.put(name, currentName);
+            }
+        }
+
+        return scan(node.getInitializer(), t.getInitializer(), p);
+    }
 
 //    public Boolean visitWhileLoop(WhileLoopTree node, TreePath p) {
 //        throw new UnsupportedOperationException("Not supported yet.");
 //    }
     
 
+    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 api/src/org/netbeans/modules/jackpot30/impl/pm/Pattern.java

 import javax.tools.JavaFileObject;
 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.java.source.JavaSourceAccessor;
 import org.netbeans.modules.java.source.parsing.FileObjects;
 import org.openide.util.Exceptions;
     }
 
     public Map<String, TreePath> match(TreePath toCheck) {
-        return CopyFinder.computeVariables(info, new TreePath(new TreePath(info.getCompilationUnit()), patternTree), toCheck, new AtomicBoolean(), constraintsHack);
+        Pair<Map<String, TreePath>, Map<String, String>> variables = CopyFinder.computeVariables(info, new TreePath(new TreePath(info.getCompilationUnit()), patternTree), toCheck, new AtomicBoolean(), constraintsHack);
+
+        if (variables == null) {
+            return null;
+        }
+
+        return variables.getA();
     }
 
     public boolean checkAntipatterns(TreePath tp) {
     public static Tree parseAndAttribute(CompilationInfo info, String pattern, Map<String, TypeMirror> constraints, Scope[] scope) {
         scope[0] = constructScope(info, constraints);
 
-        if (scope == null) {
+        if (scope[0] == null) {
             return null;
         }
 

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

             return null;
         }
 
+        //shouldn't this be handled by visitIdentifier???
         if (   tree.getKind() == Kind.IDENTIFIER
             && ((IdentifierTree) tree).getName().toString().startsWith("$")) {
             append(p, "<([0-9a-z]+)>.*?</\\" + (group++) + ">");
             append(p, "ID");
 
             //XXX: is this correct??
-            if (m && (tree.getKind() == Kind.IDENTIFIER)) {
-                append(p, ((IdentifierTree) tree).getName());
+            if (/*m && */(tree.getKind() == Kind.IDENTIFIER)) {
+                printName(p, ((IdentifierTree) tree).getName());
             }
-            if (m && (tree.getKind() == Kind.MEMBER_SELECT)) {
-                append(p, ((MemberSelectTree) tree).getIdentifier());
+            if (/*m && */(tree.getKind() == Kind.MEMBER_SELECT)) {
+                printName(p, ((MemberSelectTree) tree).getIdentifier());
             }
 
             if (memberSelectWithVariables) {
         return null;
     }
 
-//    @Override
-//    public Void visitIdentifier(IdentifierTree node, Appendable p) {
-//        append(p, node.getName());
-//        return super.visitIdentifier(node, p);
-//    }
+    private static void printName(Appendable p, CharSequence name) {
+        if (name.length() == 0 || name.charAt(0) != '$') {
+            append(p, name);
+        } else {
+            append(p, "[^<>]+?");
+        }
+    }
+
+    @Override
+    public Void visitIdentifier(IdentifierTree node, Appendable p) {
+        printName(p, node.getName());
+        return super.visitIdentifier(node, p);
+    }
 
     @Override
     public Void visitMemberSelect(MemberSelectTree node, Appendable p) {
-        append(p, node.getIdentifier());
+        printName(p, node.getIdentifier());
         return super.visitMemberSelect(node, p);
     }
 
     @Override
     public Void visitClass(ClassTree node, Appendable p) {
-        append(p, node.getSimpleName());
+        printName(p, node.getSimpleName());
         return super.visitClass(node, p);
     }
 
     @Override
     public Void visitVariable(VariableTree node, Appendable p) {
-        append(p, node.getName());
+        printName(p, node.getName());
         return super.visitVariable(node, p);
     }
 
     @Override
     public Void visitMethod(MethodTree node, Appendable p) {
-        append(p, node.getName());
+        printName(p, node.getName());
         return super.visitMethod(node, p);
     }
 

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

     private final HintSeverity severity;
     private final TreePath path;
     private final Map<String, TreePath> variables;
+    private final Map<String, String> variableNames;
 
-    public HintContext(CompilationInfo info, HintSeverity severity, TreePath path, Map<String, TreePath> variables) {
+    public HintContext(CompilationInfo info, HintSeverity severity, TreePath path, Map<String, TreePath> variables, Map<String, String> variableNames) {
         this.info = info;
         this.severity = severity;
         this.path = path;
         this.variables = variables;
+        this.variableNames = variableNames;
     }
 
     public CompilationInfo getInfo() {
     public Map<String, TreePath> getVariables() {
         return variables;
     }
-    
+
+    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) {
-        return new HintContext(info, severity, path, variables);
+    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);
     }
 }

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

 package org.netbeans.modules.jackpot30.spi;
 
 import com.sun.javadoc.Tag;
+import com.sun.source.tree.VariableTree;
 import java.io.IOException;
 import java.util.regex.Matcher;
 import com.sun.source.tree.IdentifierTree;
         return handle.getFileObject();
     }
 
-    public static Fix rewriteFix(CompilationInfo info, final String displayName, TreePath what, final String to, Map<String, TreePath> parameters, Map<String, TypeMirror> constraints) {
+    public static Fix rewriteFix(CompilationInfo info, final String displayName, TreePath what, final String to, Map<String, TreePath> parameters, 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()) {
                 new TreePathScanner<Void, Void>() {
                     @Override
                     public Void visitIdentifier(IdentifierTree node, Void p) {
-                        TreePath tp = parameters.get(node.getName().toString());
+                        String name = node.getName().toString();
+                        TreePath tp = parameters.get(name);
 
                         if (tp != null) {
                             wc.rewrite(node, tp.getLeaf());
                             return null;
                         }
 
+                        String variableName = parameterNames.get(name);
+
+                        if (variableName != null) {
+                            wc.rewrite(node, wc.getTreeMaker().Identifier(variableName));
+                            return null;
+                        }
+
                         return super.visitIdentifier(node, p);
                     }
                     @Override
                         }
                     }
 
+                    @Override
+                    public Void visitVariable(VariableTree node, Void p) {
+                        String name = node.getName().toString();
+
+                        if (name.startsWith("$")) {
+                            String nueName = parameterNames.get(name);
+
+                            if (nueName != null) {
+                                VariableTree nue = wc.getTreeMaker().Variable(node.getModifiers(), nueName, node.getType(), node.getInitializer());
+
+                                wc.rewrite(node, nue);
+
+                                return super.visitVariable(nue, p);
+                            }
+                        }
+
+                        return super.visitVariable(node, p);
+                    }
+
                 }.scan(new TreePath(new TreePath(tp.getCompilationUnit()), parsed), null);
 
                 wc.rewrite(tp.getLeaf(), parsed);

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

 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.type.TypeMirror;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.netbeans.modules.jackpot30.spi.HintDescription;
 import org.netbeans.modules.jackpot30.spi.HintDescription.PatternDescription;
 import org.netbeans.modules.jackpot30.spi.HintDescription.Worker;
+import org.netbeans.modules.jackpot30.spi.JavaFix;
 import org.netbeans.modules.jackpot30.spi.support.ErrorDescriptionFactory;
 import org.netbeans.modules.java.hints.infrastructure.TreeRuleTestBase;
 import org.netbeans.spi.editor.hints.ErrorDescription;
+import org.netbeans.spi.editor.hints.Fix;
 import org.openide.util.Exceptions;
 
 /**
                             "4:11-4:16:verifier:HINT");
     }
 
+    public void testPatternVariable1() throws Exception {
+        performFixTest("test/Test.java",
+                       "|package test;\n" +
+                       "\n" +
+                       "public class Test {\n" +
+                       "     private void test() {\n" +
+                       "         {\n" +
+                       "             int y;\n" +
+                       "             y = 1;\n" +
+                       "         }\n" +
+                       "         int z;\n" +
+                       "         {\n" +
+                       "             int y;\n" +
+                       "             z = 1;\n" +
+                       "         }\n" +
+                       "     }\n" +
+                       "}\n",
+                       "4:9-7:10:verifier:HINT",
+                       "FixImpl",
+                       "package test; public class Test { private void test() { { int y = 1; } int z; { int y; z = 1; } } } ");
+    }
+
+    public void testPatternAssert1() throws Exception {
+        performAnalysisTest("test/Test.java",
+                            "|package test;\n" +
+                            "\n" +
+                            "public class Test {\n" +
+                            "     private void test() {\n" +
+                            "         assert true : \"\";\n" +
+                            "     }\n" +
+                            "}\n",
+                            "4:9-4:26:verifier:HINT");
+    }
+
     private static final Map<String, HintDescription> test2Hint;
 
     static {
         test2Hint.put("testPattern1", HintDescription.create(HintDescription.PatternDescription.create("$1.toURL()", Collections.singletonMap("$1", "java.io.File")), new WorkerImpl()));
         test2Hint.put("testPattern2", test2Hint.get("testPattern1"));
         test2Hint.put("testKind1", HintDescription.create(Kind.METHOD_INVOCATION, new WorkerImpl()));
+        test2Hint.put("testPatternVariable1", HintDescription.create(HintDescription.PatternDescription.create("{ $1 $2; $2 = $3; }", Collections.<String, String>emptyMap()), new WorkerImpl("{ $1 $2 = $3; }")));
+        Map<String, String> constraints = new HashMap<String, String>();
+
+        constraints.put("$1", "boolean");
+        constraints.put("$2", "java.lang.Object");
+
+        test2Hint.put("testPatternAssert1", HintDescription.create(HintDescription.PatternDescription.create("assert $1 : $2;", constraints), new WorkerImpl()));
     }
 
     @Override
     }
 
     @Override
+    protected String toDebugString(CompilationInfo info, Fix f) {
+        return "FixImpl";
+    }
+
+    @Override
     public void testIssue105979() throws Exception {}
 
     @Override
     public void testNoHintsForSimpleInitialize() throws Exception {}
 
     private static final class WorkerImpl implements Worker {
+
+        private final String fix;
+
+        public WorkerImpl() {
+            this(null);
+        }
+
+        public WorkerImpl(String fix) {
+            this.fix = fix;
+        }
+
         public Collection<? extends ErrorDescription> createErrors(HintContext ctx) {
             if (ctx.getInfo().getTreeUtilities().isSynthetic(ctx.getPath())) {
                 return null;
             }
+
+            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()));
+            }
             
-            return Collections.singletonList(ErrorDescriptionFactory.forName(ctx, ctx.getPath(), "HINT"));
+            return Collections.singletonList(ErrorDescriptionFactory.forName(ctx, ctx.getPath(), "HINT", fixes.toArray(new Fix[0])));
         }
     }
 

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

                     Arrays.asList("$0.append('')"),
                     Collections.<String>emptyList());
     }
-    
+
+    public void testLocalVariables() throws Exception {
+        performTest("package test; public class Test { private void test() { { int y; y = 1; } }}",
+                    Arrays.asList("{ int $1; $1 = 1; }"),
+                    Collections.<String>emptyList());
+    }
+
+    public void testAssert() throws Exception {
+        performTest("package test; public class Test { private void test() { assert true : \"\"; }}",
+                    Arrays.asList("assert $1 : $2;"),
+                    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>();
 
 //        System.err.println("match: " + (e2 - s2));
 
-        assertTrue(result.containsAll(containedPatterns));
+        assertTrue(result.toString(), result.containsAll(containedPatterns));
 
         Set<String> none = new HashSet<String>(result);
 

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

 import java.io.File;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.atomic.AtomicBoolean;
+import javax.lang.model.type.TypeMirror;
 import javax.swing.text.Document;
 import org.netbeans.api.java.lexer.JavaTokenId;
 import org.netbeans.api.java.source.CompilationInfo;
 import org.netbeans.api.java.source.TestUtilities;
 import org.netbeans.api.lexer.Language;
 import org.netbeans.junit.NbTestCase;
-import org.netbeans.modules.java.hints.infrastructure.Pair;
+import org.netbeans.modules.jackpot30.impl.pm.CopyFinder.Pair;
 import org.openide.cookies.EditorCookie;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
     public void testVariables1() throws Exception {
         performVariablesTest("package test; import static java.lang.String.*; public class Test {public void test1() {String.valueOf(2+4);} }",
                              "java.lang.String.valueOf($1)",
-                             new Pair<String, int[]>("$1", new int[] {134 - 31, 137 - 31}));
+                             new Pair[] {new Pair<String, int[]>("$1", new int[] {134 - 31, 137 - 31})},
+                             new Pair[0]);
     }
 
     public void testAssert1() throws Exception {
         performTest("package test; public class Test {public static class T extends Test { public void test() { |Test.test|(); |System.err.println|(); } } }", false);
     }
 
-    protected void performVariablesTest(String code, String pattern, Pair<String, int[]>... duplicates) throws Exception {
+    public void testLocalVariable() throws Exception {
+        performVariablesTest("package test; public class Test {public void test1() { { int y; y = 1; } int z; { int y; z = 1; } } }",
+                             "{ int $1; $1 = 1; }",
+                             new Pair[0],
+                             new Pair[] {new Pair<String, String>("$1", "y")});
+    }
+
+    protected void performVariablesTest(String code, String pattern, Pair<String, int[]>[] duplicatesPos, Pair<String, String>[] duplicatesNames) throws Exception {
         prepareTest(code, -1);
 
-        Tree patternTree = info.getTreeUtilities().parseExpression(pattern, new SourcePositions[1]);
-        Scope scope = info.getTrees().getScope(new TreePath(info.getCompilationUnit()));
-        info.getTreeUtilities().attributeTree(patternTree, scope);
-        Map<TreePath, Map<String, TreePath>> result = CopyFinder.computeDuplicates(info, new TreePath(new TreePath(info.getCompilationUnit()), patternTree), new TreePath(info.getCompilationUnit()), new AtomicBoolean());
+        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());
 
         assertSame(1, result.size());
 
         Map<String, int[]> actual = new HashMap<String, int[]>();
 
-        for (Entry<String, TreePath> e : result.values().iterator().next().entrySet()) {
+        for (Entry<String, TreePath> e : result.values().iterator().next().getA().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())
             actual.put(e.getKey(), span);
         }
 
-        for (Pair<String, int[]> dup : duplicates) {
+        for (Pair<String, int[]> dup : duplicatesPos) {
             int[] span = actual.remove(dup.getA());
 
             if (span == null) {
             }
             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());
     }
 
 //    @Override

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

         }
 
         assertNotNull(tp);
-        
+
+        //XXX:
         Map<String, TreePath> vars = Pattern.compile(info, pattern).match(tp);
 
         if (duplicates == null) {

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

 
         String serialized = sb.toString();
 
-        assertEquals(3, serialized.split("test").length);
+        assertEquals(4, serialized.split("test").length);
         assertEquals(2, serialized.split("println").length);
     }
 

File file/src/org/netbeans/modules/jackpot30/file/DeclarativeHintRegistry.java

             String type = m.group(3);
 
             filtered.append(var);
-            constraints.put(var, type); //XXX: set non-null at most once
+
+            if (type != null) {
+                constraints.put(var, type); //XXX: set non-null at most once
+            }
         }
 
         filtered.append(pattern.substring(i));

File 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(), Collections.<String, TypeMirror>emptyMap()/*XXX*/);
+            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*/);
         }
 
         ErrorDescription ed = ErrorDescriptionFactory.forName(ctx, ctx.getPath(), displayName, editorFixes);

File hintsimpl/src/org/netbeans/modules/jackpot30/hintsimpl/layer.xml

     <folder name="org-netbeans-modules-java-hints">
         <folder name="declarative">
             <file name="assert2if.hint" url="assert2if.hint" />
+            <file name="variable-decl-assignment.hint" url="variable-decl-assignment.hint" />
         </folder>
     </folder>
 

File hintsimpl/src/org/netbeans/modules/jackpot30/hintsimpl/variable-decl-assignment.hint

+"Separated variable declaration and assignment" { $1 $2; $2 = $3; } => "Add initialization to the declaration" { $1 $2 = $3; } ;;

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