ruby.refactoring/src/org/netbeans/modules/refactoring/ruby/plugins/RenameRefactoringPlugin.java
author enebo@netbeans.org
Tue, 22 Apr 2014 15:36:21 -0500
changeset 4559 7a0a8afa3e90
parent 4542 be97a5e85907
permissions -rw-r--r--
Bump jruby-parser and hopefully see green (commented out tests pass individually -- some state surviving to kill them later -- workaround for now)
tor@3
     1
/*
phrebejk@559
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
tor@3
     3
 *
jglick@4117
     4
 * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
jglick@4117
     5
 *
jglick@4117
     6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
jglick@4117
     7
 * Other names may be trademarks of their respective owners.
tor@3
     8
 *
phrebejk@559
     9
 * The contents of this file are subject to the terms of either the GNU
phrebejk@559
    10
 * General Public License Version 2 only ("GPL") or the Common
phrebejk@559
    11
 * Development and Distribution License("CDDL") (collectively, the
phrebejk@559
    12
 * "License"). You may not use this file except in compliance with the
phrebejk@559
    13
 * License. You can obtain a copy of the License at
phrebejk@559
    14
 * http://www.netbeans.org/cddl-gplv2.html
phrebejk@559
    15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
phrebejk@559
    16
 * specific language governing permissions and limitations under the
phrebejk@559
    17
 * License.  When distributing the software, include this License Header
phrebejk@559
    18
 * Notice in each file and include the License file at
jglick@4117
    19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
phrebejk@559
    20
 * particular file as subject to the "Classpath" exception as provided
jglick@4117
    21
 * by Oracle in the GPL Version 2 section of the License file that
phrebejk@559
    22
 * accompanied this code. If applicable, add the following below the
phrebejk@559
    23
 * License Header, with the fields enclosed by brackets [] replaced by
phrebejk@559
    24
 * your own identifying information:
tor@3
    25
 * "Portions Copyrighted [year] [name of copyright owner]"
tor@3
    26
 *
phrebejk@559
    27
 * Contributor(s):
phrebejk@559
    28
 *
tor@3
    29
 * The Original Software is NetBeans. The Initial Developer of the Original
mkrauskopf@2737
    30
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
tor@3
    31
 * Microsystems, Inc. All Rights Reserved.
phrebejk@559
    32
 *
phrebejk@559
    33
 * If you wish your version of this file to be governed by only the CDDL
phrebejk@559
    34
 * or only the GPL Version 2, indicate your decision by adding
phrebejk@559
    35
 * "[Contributor] elects to include this software in this distribution
phrebejk@559
    36
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
phrebejk@559
    37
 * single choice of license, a recipient has the option to distribute
phrebejk@559
    38
 * your version of this file under either the CDDL, the GPL Version 2 or
phrebejk@559
    39
 * to extend the choice of license to its licensees as provided above.
phrebejk@559
    40
 * However, if you add GPL Version 2 code and therefore, elected the GPL
phrebejk@559
    41
 * Version 2 license, then the option applies only if the new code is
phrebejk@559
    42
 * made subject to such option by the copyright holder.
tor@3
    43
 */
tor@3
    44
package org.netbeans.modules.refactoring.ruby.plugins;
tor@3
    45
tor@3
    46
import java.io.IOException;
tor@3
    47
import java.text.MessageFormat;
tor@3
    48
import java.util.*;
mkrauskopf@3030
    49
import java.util.logging.Level;
mkrauskopf@3030
    50
import java.util.logging.Logger;
tor@3
    51
import javax.swing.text.BadLocationException;
tor@3
    52
import javax.swing.text.Document;
tor@3
    53
import javax.swing.text.Position.Bias;
emononen@3259
    54
import org.jrubyparser.ast.ArgumentNode;
emononen@3259
    55
import org.jrubyparser.ast.ClassNode;
emononen@3259
    56
import org.jrubyparser.ast.ClassVarAsgnNode;
emononen@3259
    57
import org.jrubyparser.ast.ClassVarDeclNode;
emononen@3259
    58
import org.jrubyparser.ast.ClassVarNode;
emononen@3259
    59
import org.jrubyparser.ast.Colon2Node;
emononen@3259
    60
import org.jrubyparser.ast.DAsgnNode;
emononen@3259
    61
import org.jrubyparser.ast.DVarNode;
emononen@3259
    62
import org.jrubyparser.ast.GlobalAsgnNode;
emononen@3259
    63
import org.jrubyparser.ast.GlobalVarNode;
emononen@3259
    64
import org.jrubyparser.ast.InstAsgnNode;
emononen@3259
    65
import org.jrubyparser.ast.InstVarNode;
emononen@3259
    66
import org.jrubyparser.ast.LocalAsgnNode;
emononen@3259
    67
import org.jrubyparser.ast.LocalVarNode;
emononen@3259
    68
import org.jrubyparser.ast.MethodDefNode;
emononen@3259
    69
import org.jrubyparser.ast.ModuleNode;
emononen@3259
    70
import org.jrubyparser.ast.Node;
emononen@3259
    71
import org.jrubyparser.ast.SClassNode;
emononen@3259
    72
import org.jrubyparser.ast.SymbolNode;
emononen@3259
    73
import org.jrubyparser.ast.INameNode;
tor@3
    74
import org.netbeans.api.lexer.Token;
tor@3
    75
import org.netbeans.api.lexer.TokenHierarchy;
tor@3
    76
import org.netbeans.api.lexer.TokenId;
tor@3
    77
import org.netbeans.api.lexer.TokenSequence;
tor@2113
    78
import org.netbeans.api.lexer.TokenUtilities;
emononen@3643
    79
import org.netbeans.api.project.FileOwnerQuery;
emononen@3643
    80
import org.netbeans.api.project.Project;
tor@3
    81
import org.netbeans.editor.BaseDocument;
mkrauskopf@3030
    82
import org.netbeans.editor.Utilities;
mkrauskopf@3030
    83
import org.netbeans.modules.csl.api.ElementKind;
mkrauskopf@3030
    84
import org.netbeans.modules.csl.api.Error;
mkrauskopf@3030
    85
import org.netbeans.modules.csl.api.OffsetRange;
mkrauskopf@3030
    86
import org.netbeans.modules.csl.api.Severity;
mkrauskopf@3030
    87
import org.netbeans.modules.csl.spi.ParserResult;
mkrauskopf@3030
    88
import org.netbeans.modules.csl.spi.support.ModificationResult;
mkrauskopf@3030
    89
import org.netbeans.modules.csl.spi.support.ModificationResult.Difference;
mkrauskopf@3030
    90
import org.netbeans.modules.parsing.api.ParserManager;
mkrauskopf@3030
    91
import org.netbeans.modules.parsing.api.ResultIterator;
mkrauskopf@3030
    92
import org.netbeans.modules.parsing.api.Source;
mkrauskopf@3030
    93
import org.netbeans.modules.parsing.api.UserTask;
mkrauskopf@3030
    94
import org.netbeans.modules.parsing.spi.ParseException;
emononen@3643
    95
import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport.Kind;
mkrauskopf@3030
    96
import org.netbeans.modules.refactoring.api.*;
tor@3
    97
import org.netbeans.modules.refactoring.ruby.DiffElement;
tor@3
    98
import org.netbeans.modules.refactoring.ruby.RetoucheUtils;
tor@3
    99
import org.netbeans.modules.refactoring.ruby.RubyElementCtx;
mkrauskopf@3030
   100
import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
mkrauskopf@3030
   101
import org.netbeans.modules.ruby.AstPath;
mkrauskopf@3030
   102
import org.netbeans.modules.ruby.AstUtilities;
mkrauskopf@3030
   103
import org.netbeans.modules.ruby.RubyIndex;
mkrauskopf@3030
   104
import org.netbeans.modules.ruby.RubyParseResult;
mkrauskopf@3030
   105
import org.netbeans.modules.ruby.RubyStructureAnalyzer.AnalysisResult;
tor@3
   106
import org.netbeans.modules.ruby.RubyUtils;
mkrauskopf@3030
   107
import org.netbeans.modules.ruby.elements.AstElement;
tor@1198
   108
import org.netbeans.modules.ruby.elements.Element;
emononen@3645
   109
import org.netbeans.modules.ruby.elements.IndexedClass;
emononen@3645
   110
import org.netbeans.modules.ruby.elements.IndexedElement;
emononen@3643
   111
import org.netbeans.modules.ruby.elements.IndexedMethod;
tor@544
   112
import org.netbeans.modules.ruby.lexer.LexUtilities;
emononen@3643
   113
import org.netbeans.modules.ruby.rubyproject.RubyBaseProject;
mkrauskopf@3030
   114
import org.openide.filesystems.FileObject;
mkrauskopf@3030
   115
import org.openide.filesystems.FileUtil;
emononen@3643
   116
import org.openide.loaders.OperationEvent.Rename;
mkrauskopf@3030
   117
import org.openide.text.CloneableEditorSupport;
tor@3
   118
import org.openide.text.PositionRef;
mkrauskopf@3030
   119
import org.openide.util.Exceptions;
tor@3
   120
import org.openide.util.NbBundle;
tor@3
   121
tor@3
   122
/**
tor@3
   123
 * The actual Renaming refactoring work for Ruby. The skeleton (name checks etc.) based
tor@3
   124
 * on the Java refactoring module by Jan Becicka, Martin Matula, Pavel Flaska and Daniel Prusa.
tor@3
   125
 * 
tor@3
   126
 * @author Jan Becicka
tor@3
   127
 * @author Martin Matula
tor@3
   128
 * @author Pavel Flaska
tor@3
   129
 * @author Daniel Prusa
tor@3
   130
 * @author Tor Norbye
tor@3
   131
 * 
tor@3
   132
 * @todo Perform index lookups to determine the set of files to be checked!
tor@3
   133
 * @todo Check that the new name doesn't conflict with an existing name
tor@3
   134
 * @todo Check unknown files!
tor@3
   135
 * @todo More prechecks
tor@171
   136
 * @todo When invoking refactoring on a file object, I also rename the file. I should (a) list the
tor@171
   137
 *   name it's going to change the file to, and (b) definitely "filenamize" it - e.g. for class FooBar the
tor@171
   138
 *   filename should be foo_bar.
tor@582
   139
 * @todo If you rename a Model, I should add a corresponding rename_table entry in the migrations...
tor@3
   140
 *
tor@3
   141
 * @todo Complete this. Most of the prechecks are not implemented - and the refactorings themselves need a lot of work.
tor@3
   142
 */
tor@3
   143
public class RenameRefactoringPlugin extends RubyRefactoringPlugin {
tor@3
   144
    
emononen@3643
   145
    private RubyElementCtx treePathHandle;
emononen@3643
   146
    private final Collection<IndexedMethod> overriddenByMethods = new ArrayList<IndexedMethod>();
emononen@3643
   147
    private final Collection<IndexedMethod> overridesMethods = new ArrayList<IndexedMethod>();; // methods that are overridden by the method to be renamed
mkrauskopf@2738
   148
//    private boolean doCheckName = true;
tor@3
   149
    
tor@3
   150
    private RenameRefactoring refactoring;
emononen@3643
   151
    private RubyBaseProject project;
tor@3
   152
    
tor@3
   153
    /** Creates a new instance of RenameRefactoring */
tor@3
   154
    public RenameRefactoringPlugin(RenameRefactoring rename) {
tor@3
   155
        this.refactoring = rename;
tor@3
   156
        RubyElementCtx tph = rename.getRefactoringSource().lookup(RubyElementCtx.class);
mkrauskopf@3030
   157
        if (tph != null) {
tor@3
   158
            treePathHandle = tph;
tor@3
   159
        } else {
mkrauskopf@3030
   160
            Source source = Source.create(rename.getRefactoringSource().lookup(FileObject.class));
tor@3
   161
            try {
mkrauskopf@3030
   162
                ParserManager.parse(Collections.singleton(source), new UserTask() {
mkrauskopf@3030
   163
mkrauskopf@3030
   164
                    public
mkrauskopf@3030
   165
                    @Override
mkrauskopf@3030
   166
                    void run(ResultIterator co) throws Exception {
mkrauskopf@3030
   167
                        if (co.getSnapshot().getMimeType().equals(RubyUtils.RUBY_MIME_TYPE)) {
mkrauskopf@3030
   168
                            RubyParseResult parserResult = AstUtilities.getParseResult(co.getParserResult());
emononen@3259
   169
                            org.jrubyparser.ast.Node root = parserResult.getRootNode();
mkrauskopf@3030
   170
                            if (root != null) {
mkrauskopf@3030
   171
                                AnalysisResult ar = parserResult.getStructure();
tor@236
   172
                                List<? extends AstElement> els = ar.getElements();
tor@3
   173
                                if (els.size() > 0) {
tor@3
   174
                                    // TODO - try to find the outermost or most "relevant" module/class in the file?
tor@3
   175
                                    // In Java, we look for a class with the name corresponding to the file.
tor@3
   176
                                    // It's not as simple in Ruby.
tor@3
   177
                                    AstElement element = els.get(0);
emononen@3259
   178
                                    org.jrubyparser.ast.Node node = element.getNode();
mkrauskopf@3030
   179
                                    treePathHandle = new RubyElementCtx(root, node,
mkrauskopf@3030
   180
                                            element, RubyUtils.getFileObject(parserResult), parserResult);
tor@3
   181
                                    refactoring.getContext().add(co);
tor@3
   182
                                }
tor@3
   183
                            }
tor@3
   184
                        }
tor@3
   185
                    }
mkrauskopf@3030
   186
                });
mkrauskopf@3030
   187
            } catch (ParseException e) {
mkrauskopf@3030
   188
                Logger.getLogger(RenameRefactoringPlugin.class.getName()).log(Level.WARNING, null, e);
tor@3
   189
            }
tor@3
   190
        }
emononen@3643
   191
        if (treePathHandle != null) {
emononen@3643
   192
            Project p = FileOwnerQuery.getOwner(treePathHandle.getFileObject());
emononen@3643
   193
            if (p instanceof RubyBaseProject) {
emononen@3643
   194
                project = (RubyBaseProject) p;
emononen@3643
   195
            }
emononen@3643
   196
        }
tor@3
   197
    }
mkrauskopf@3030
   198
mkrauskopf@3030
   199
    public Problem fastCheckParameters() {
tor@3
   200
        Problem fastCheckProblem = null;
tor@3
   201
        ElementKind kind = treePathHandle.getKind();
tor@3
   202
        String newName = refactoring.getNewName();
tor@3
   203
        String oldName = treePathHandle.getSimpleName();
tor@2113
   204
        if (oldName == null) {
tor@2353
   205
            return new Problem(true, "Cannot determine target name. Please file a bug with detailed information on how to reproduce (preferably including the current source file and the cursor position)");
tor@2113
   206
        }
tor@3
   207
        
tor@3
   208
        if (oldName.equals(newName)) {
tor@3
   209
            boolean nameNotChanged = true;
tor@3
   210
            //if (kind == ElementKind.CLASS || kind == ElementKind.MODULE) {
tor@3
   211
            //    if (!((TypeElement) element).getNestingKind().isNested()) {
tor@3
   212
            //        nameNotChanged = info.getFileObject().getName().equals(element);
tor@3
   213
            //    }
tor@3
   214
            //}
tor@3
   215
            if (nameNotChanged) {
tor@3
   216
                fastCheckProblem = createProblem(fastCheckProblem, true, getString("ERR_NameNotChanged"));
tor@3
   217
                return fastCheckProblem;
tor@3
   218
            }
tor@3
   219
            
tor@3
   220
        }
tor@3
   221
        
tor@3
   222
        // TODO - get a better ruby name picker - and check for invalid Ruby symbol names etc.
tor@143
   223
        // TODO - call RubyUtils.isValidLocalVariableName if we're renaming a local symbol!
emononen@2990
   224
        if (kind == ElementKind.CLASS && !RubyUtils.isValidConstantFQN(newName)) {
tor@3
   225
            String s = getString("ERR_InvalidClassName"); //NOI18N
tor@3
   226
            String msg = new MessageFormat(s).format(
tor@3
   227
                    new Object[] {newName}
tor@3
   228
            );
tor@3
   229
            fastCheckProblem = createProblem(fastCheckProblem, true, msg);
tor@3
   230
            return fastCheckProblem;
tor@3
   231
        } else if (kind == ElementKind.METHOD && !RubyUtils.isValidRubyMethodName(newName)) {
tor@3
   232
            String s = getString("ERR_InvalidMethodName"); //NOI18N
tor@3
   233
            String msg = new MessageFormat(s).format(
tor@3
   234
                    new Object[] {newName}
tor@3
   235
            );
tor@3
   236
            fastCheckProblem = createProblem(fastCheckProblem, true, msg);
tor@3
   237
            return fastCheckProblem;
tor@3
   238
        } else if (!RubyUtils.isValidRubyIdentifier(newName)) {
tor@3
   239
            String s = getString("ERR_InvalidIdentifier"); //NOI18N
tor@3
   240
            String msg = new MessageFormat(s).format(
tor@3
   241
                    new Object[] {newName}
tor@3
   242
            );
tor@3
   243
            fastCheckProblem = createProblem(fastCheckProblem, true, msg);
tor@3
   244
            return fastCheckProblem;
tor@3
   245
        }
emononen@2990
   246
        String msg = getWarningMsg(kind, newName);
tor@171
   247
        if (msg != null) {
tor@171
   248
            fastCheckProblem = createProblem(fastCheckProblem, false, msg);
tor@171
   249
        }
tor@171
   250
        
tor@3
   251
        return fastCheckProblem;
tor@3
   252
    }
emononen@3645
   253
emononen@3645
   254
    private Set<String> asNames(Collection<? extends IndexedElement> elems) {
emononen@3645
   255
        Set<String> names = new HashSet<String>(elems.size());
emononen@3645
   256
        for (IndexedElement each : elems) {
emononen@3645
   257
            names.add(each.getName());
emononen@3645
   258
        }
emononen@3645
   259
        return names;
emononen@3645
   260
    }
emononen@3645
   261
mkrauskopf@3030
   262
    public Problem checkParameters() {
tor@3
   263
        
tor@3
   264
        Problem checkProblem = null;
tor@3
   265
        int steps = 0;
emononen@3643
   266
        if (AstUtilities.isCall(treePathHandle.getNode()) || treePathHandle.getKind() == ElementKind.METHOD) {
emononen@3643
   267
            RubyIndex index = RubyIndex.get(treePathHandle.getInfo());
emononen@3643
   268
            String className = treePathHandle.getDefClass();
emononen@3643
   269
            String methodName = AstUtilities.getName(treePathHandle.getNode());
emononen@3645
   270
            Set<IndexedMethod> methodsInSameTree = index.getAllOverridingMethodsInHierachy(methodName, className);
emononen@3645
   271
            overridesMethods.addAll(methodsInSameTree);
emononen@3645
   272
emononen@3643
   273
            // inherited contains also the method itself
emononen@3643
   274
            if (overridesMethods.size() > 1) {
emononen@3645
   275
                Set<String> superClassNames = asNames(index.getSuperClasses(className));
emononen@3645
   276
                // does the method override a super method that is defined in a class in the project sources
emononen@3643
   277
                boolean overridesFromSources = false;
emononen@3645
   278
                // does the method overrided a super method that is also overridden in a class in a 
emononen@3645
   279
                // different branch of the class hierarhcy
emononen@3645
   280
                boolean classesInOtherBranch = false;
emononen@3645
   281
emononen@3643
   282
                for (IndexedMethod method : overridesMethods) {
emononen@3645
   283
                    // warn about matches under non-source roots (we don't rename them)
emononen@3643
   284
                    if (!isUnderSourceRoot(method.getFileObject())) {
emononen@3643
   285
                        checkProblem =
emononen@3643
   286
                                createProblem(checkProblem,
emononen@3643
   287
                                false, NbBundle.getMessage(RenameRefactoringPlugin.class, "ERR_Overrides_Method",
emononen@3643
   288
                                method.getIn() + "#" + method.getName(), method.getFileObject().getPath()));
emononen@3643
   289
                    } else if (!method.getFileObject().equals(treePathHandle.getFileObject())){
emononen@3643
   290
                        overridesFromSources = true;
emononen@3643
   291
                    }
emononen@3645
   292
                    if (!classesInOtherBranch 
emononen@3645
   293
                            && !className.equals(method.getIn())
emononen@3645
   294
                            && !superClassNames.contains(method.getIn())) {
emononen@3645
   295
                        classesInOtherBranch = true;
emononen@3645
   296
                    }
emononen@3643
   297
                }
emononen@3643
   298
                if (overridesFromSources) {
emononen@3643
   299
                    checkProblem = createProblem(checkProblem, false, NbBundle.getMessage(RenameRefactoringPlugin.class, "ERR_Overrides"));
emononen@3643
   300
                }
emononen@3645
   301
                if (classesInOtherBranch) {
emononen@3645
   302
                    checkProblem = createProblem(checkProblem, false, NbBundle.getMessage(RenameRefactoringPlugin.class, "ERR_Overrides_tree"));
emononen@3645
   303
                }
emononen@3643
   304
            }
tor@565
   305
        }
emononen@3643
   306
emononen@3643
   307
        steps += overriddenByMethods.size();
emononen@3643
   308
        steps += overridesMethods.size();
emononen@3643
   309
mkrauskopf@2738
   310
        fireProgressListenerStart(RenameRefactoring.PARAMETERS_CHECK, 8 + 3*steps);
tor@3
   311
        
tor@3
   312
        fireProgressListenerStep();
tor@3
   313
        fireProgressListenerStep();
tor@3
   314
        fireProgressListenerStop();
tor@3
   315
        return checkProblem;
tor@3
   316
    }
emononen@3643
   317
emononen@3643
   318
    private boolean isUnderSourceRoot(FileObject fo) {
emononen@3643
   319
        if (project == null) {
emononen@3643
   320
            return false;
emononen@3643
   321
        }
emononen@3643
   322
        for (FileObject root : project.getSourceRootFiles()) {
emononen@3643
   323
            if (FileUtil.isParentOf(root, fo)) {
emononen@3643
   324
                return true;
emononen@3643
   325
            }
emononen@3643
   326
        }
emononen@3643
   327
        for (FileObject root : project.getTestSourceRootFiles()) {
emononen@3643
   328
            if (FileUtil.isParentOf(root, fo)) {
emononen@3643
   329
                return true;
emononen@3643
   330
            }
emononen@3643
   331
        }
emononen@3643
   332
        return false;
emononen@3643
   333
    }
tor@3
   334
    
tor@381
   335
    @Override
tor@381
   336
    public Problem preCheck() {
tor@1161
   337
        if (treePathHandle == null || treePathHandle.getFileObject() == null || !treePathHandle.getFileObject().isValid()) {
tor@381
   338
            return new Problem(true, NbBundle.getMessage(RenameRefactoringPlugin.class, "DSC_ElNotAvail")); // NOI18N
tor@381
   339
        }
tor@381
   340
        return null;
tor@381
   341
    }
tor@381
   342
tor@3
   343
    private Set<FileObject> getRelevantFiles() {
mkrauskopf@3030
   344
        if (treePathHandle.getKind() == ElementKind.VARIABLE || treePathHandle.getKind() == ElementKind.PARAMETER) {
mkrauskopf@3030
   345
            // For local variables, only look in the current file!
mkrauskopf@3030
   346
            return Collections.singleton(treePathHandle.getFileObject());
mkrauskopf@3030
   347
        } else {
mkrauskopf@3030
   348
            return RetoucheUtils.getRubyFilesInProject(treePathHandle.getFileObject());
mkrauskopf@3030
   349
        }
mkrauskopf@3030
   350
//        }
tor@3
   351
    }
mkrauskopf@3030
   352
tor@3
   353
    private Set<RubyElementCtx> allMethods;
enebo@4559
   354
enebo@4559
   355
    private static final Comparator<Difference> COMPARATOR = new Comparator<Difference>() {
enebo@4559
   356
        public int compare(Difference d1, Difference d2) {
enebo@4559
   357
            return d1.getStartPosition().getOffset() - d2.getStartPosition().getOffset();
enebo@4559
   358
enebo@4559
   359
        };
enebo@4559
   360
    };
tor@3
   361
    
tor@3
   362
    public Problem prepare(RefactoringElementsBag elements) {
tor@565
   363
        if (treePathHandle == null) {
tor@3
   364
            return null;
tor@565
   365
        }
emononen@3643
   366
        Problem problem  = null;
mkrauskopf@3030
   367
        Set<FileObject> files = getRelevantFiles();
mkrauskopf@3030
   368
        fireProgressListenerStart(ProgressEvent.START, files.size());
mkrauskopf@3030
   369
        if (!files.isEmpty()) {
mkrauskopf@3030
   370
            TransformTask transform = new TransformTask() {
mkrauskopf@3030
   371
                @Override
mkrauskopf@3030
   372
                protected Collection<ModificationResult> process(ParserResult parserResult) {
mkrauskopf@3030
   373
                    RenameTransformer rt = new RenameTransformer(refactoring.getNewName(), allMethods);
mkrauskopf@3030
   374
                    rt.setWorkingCopy(parserResult);
mkrauskopf@3030
   375
                    rt.scan();
mkrauskopf@3030
   376
                    ModificationResult mr = new ModificationResult();
enebo@4559
   377
enebo@4559
   378
                    mr.addDifferences(parserResult.getSnapshot().getSource().getFileObject(), cullDifferences(rt.diffs));
enebo@4559
   379
mkrauskopf@3030
   380
                    return Collections.singleton(mr);
mkrauskopf@3030
   381
                }
mkrauskopf@3030
   382
            };
mkrauskopf@3030
   383
mkrauskopf@3030
   384
            final Collection<ModificationResult> results = processFiles(files, transform);
enebo@4559
   385
enebo@4559
   386
            // We don't want retouche to look at all results since we are finding the same nodes
enebo@4559
   387
            // repeated in our tree (e.g. @a += 1 has two nodes for @a for the assign and the references).
enebo@4559
   388
            for (ModificationResult result: results) {
tor@3
   389
                for (FileObject jfo : result.getModifiedFileObjects()) {
enebo@4559
   390
tor@3
   391
                    for (Difference diff: result.getDifferences(jfo)) {
tor@3
   392
                        String old = diff.getOldText();
enebo@4559
   393
                        if (old!=null) {  //TODO: workaround. generator issue?
tor@3
   394
                            elements.add(refactoring,DiffElement.create(diff, jfo, result));
tor@3
   395
                        }
tor@3
   396
                    }
tor@3
   397
                }
tor@3
   398
            }
enebo@4559
   399
enebo@4559
   400
            elements.registerTransaction(new RetoucheCommit(results));
tor@3
   401
        }
emononen@1352
   402
        // see #126733. need to set a correct new name for the file rename plugin
emononen@1353
   403
        // that gets invoked after this plugin when the refactoring is invoked on a file.
emononen@1352
   404
        if (refactoring.getRefactoringSource().lookup(FileObject.class) != null) {
emononen@1352
   405
            String newName = RubyUtils.camelToUnderlinedName(refactoring.getNewName());
emononen@1352
   406
            refactoring.setNewName(newName);
emononen@1352
   407
        }
emononen@1352
   408
tor@3
   409
        fireProgressListenerStop();
emononen@1352
   410
                
emononen@3643
   411
        return problem;
tor@3
   412
    }
tor@3
   413
enebo@4559
   414
    // At NB 8.0 it gets really unhappy if we submit duplicate Differences which overlap.
enebo@4559
   415
    // This method sorts and then removes any which happen to have the same start offset.
enebo@4559
   416
    // Start offset might not be perfect but in the cases on improper overlaps it should get
enebo@4559
   417
    // repaired by the thing supplying the differences.
enebo@4559
   418
    private static List<Difference> cullDifferences(List<Difference> oldDiffs) {
enebo@4559
   419
        List<Difference> diffs = new ArrayList<Difference>();
enebo@4559
   420
        Difference lastDiff = null;
enebo@4559
   421
enebo@4559
   422
        if (oldDiffs.size() > 0) Collections.sort(oldDiffs, COMPARATOR);
enebo@4559
   423
enebo@4559
   424
        for (Difference diff: oldDiffs) {
enebo@4559
   425
            if (lastDiff == null ||
enebo@4559
   426
                    (diff.getStartPosition().getOffset() != lastDiff.getStartPosition().getOffset() &&
enebo@4559
   427
                    diff.getEndPosition().getOffset() != lastDiff.getEndPosition().getOffset())) {
enebo@4559
   428
                diffs.add(diff);
enebo@4559
   429
            }
enebo@4559
   430
enebo@4559
   431
            lastDiff = diff;
enebo@4559
   432
        }
enebo@4559
   433
enebo@4559
   434
        return diffs;
enebo@4559
   435
    }
enebo@4559
   436
tor@3
   437
    private static final String getString(String key) {
tor@3
   438
        return NbBundle.getMessage(RenameRefactoringPlugin.class, key);
tor@3
   439
    }
emononen@2990
   440
emononen@2990
   441
    private String getWarningMsg(ElementKind kind, String newName) {
emononen@2990
   442
        String msg = null;
emononen@2990
   443
        if (ElementKind.CLASS == kind) {
emononen@2990
   444
            for (String each : newName.split("::")) {
emononen@2990
   445
                //NOI18N
emononen@2990
   446
                msg = RubyUtils.getIdentifierWarning(each, 0);
emononen@2990
   447
                if (msg != null) {
emononen@2990
   448
                    break;
emononen@2990
   449
                }
emononen@2990
   450
            }
emononen@2990
   451
        } else {
emononen@2990
   452
            msg = RubyUtils.getIdentifierWarning(newName, 0);
emononen@2990
   453
        }
emononen@2990
   454
        return msg;
emononen@2990
   455
    }
tor@3
   456
    
tor@3
   457
    /**
tor@3
   458
     *
tor@3
   459
     * @author Jan Becicka
tor@3
   460
     */
tor@3
   461
    public class RenameTransformer extends SearchVisitor {
tor@3
   462
mkrauskopf@3030
   463
        private final Set<RubyElementCtx> allMethods;
mkrauskopf@3030
   464
        private final String newName;
mkrauskopf@3030
   465
        private final String oldName;
tor@3
   466
        private CloneableEditorSupport ces;
tor@3
   467
        private List<Difference> diffs;
tor@3
   468
tor@3
   469
        @Override
mkrauskopf@3030
   470
        public void setWorkingCopy(ParserResult workingCopy) {
tor@3
   471
            // Cached per working copy
tor@3
   472
            this.ces = null;
mkrauskopf@3030
   473
            this.diffs = null;
tor@3
   474
            super.setWorkingCopy(workingCopy);
tor@3
   475
        }
tor@3
   476
        
tor@3
   477
        public RenameTransformer(String newName, Set<RubyElementCtx> am) {
tor@3
   478
            this.newName = newName;
tor@3
   479
            this.oldName = treePathHandle.getSimpleName();
tor@3
   480
            this.allMethods = am;
tor@3
   481
        }
tor@3
   482
        
tor@3
   483
        @Override
tor@3
   484
        public void scan() {
tor@3
   485
            // TODO - do I need to force state to resolved?
tor@738
   486
            //compiler.toPhase(org.netbeans.napi.gsfret.source.Phase.RESOLVED);
tor@3
   487
tor@3
   488
            diffs = new ArrayList<Difference>();
tor@3
   489
            RubyElementCtx searchCtx = treePathHandle;
tor@3
   490
            Error error = null;
tor@3
   491
            Node root = AstUtilities.getRoot(workingCopy);
mkrauskopf@3030
   492
            FileObject workingCopyFileObject = RubyUtils.getFileObject(workingCopy);
tor@3
   493
            if (root != null) {
tor@3
   494
                
tor@1205
   495
                Element element = AstElement.create(workingCopy, root);
tor@3
   496
                Node node = searchCtx.getNode();
mkrauskopf@3030
   497
                RubyElementCtx fileCtx = new RubyElementCtx(root, node, element, workingCopyFileObject, workingCopy);
tor@3
   498
                Node method = null;
tor@3
   499
                if (node instanceof ArgumentNode) {
tor@3
   500
                    AstPath path = searchCtx.getPath();
tor@3
   501
                    assert path.leaf() == node;
tor@3
   502
                    Node parent = path.leafParent();
tor@3
   503
tor@3
   504
                    if (!(parent instanceof MethodDefNode)) {
tor@3
   505
                        method = AstUtilities.findLocalScope(node, path);
tor@3
   506
                    }
tor@3
   507
                } else if (node instanceof LocalVarNode || node instanceof LocalAsgnNode || node instanceof DAsgnNode || 
tor@3
   508
                        node instanceof DVarNode) {
tor@3
   509
                    // A local variable read or a parameter read, or an assignment to one of these
tor@3
   510
                    AstPath path = searchCtx.getPath();
tor@3
   511
                    method = AstUtilities.findLocalScope(node, path);
tor@3
   512
                }
tor@3
   513
tor@3
   514
                if (method != null) {
tor@3
   515
                    findLocal(searchCtx, fileCtx, method, oldName);
tor@3
   516
                } else {
tor@3
   517
                    // Full AST search
tor@3
   518
                    AstPath path = new AstPath();
tor@3
   519
                    path.descend(root);
tor@3
   520
                    find(path, searchCtx, fileCtx, root, oldName, Character.isUpperCase(oldName.charAt(0)));
tor@3
   521
                    path.ascend();
tor@3
   522
                }
tor@3
   523
            } else {
tor@3
   524
                // See if the document contains references to this symbol and if so, put a warning in
mkrauskopf@3030
   525
                String workingCopyText = workingCopy.getSnapshot().getText().toString();
mkrauskopf@3030
   526
mkrauskopf@3030
   527
                if (workingCopyText.indexOf(oldName) != -1) {
tor@3
   528
                    // TODO - icon??
tor@3
   529
                    if (ces == null) {
tor@3
   530
                        ces = RetoucheUtils.findCloneableEditorSupport(workingCopy);
tor@3
   531
                    }
tor@3
   532
                    int start = 0;
tor@3
   533
                    int end = 0;
tor@565
   534
                    String desc = NbBundle.getMessage(RenameRefactoringPlugin.class, "ParseErrorFile", oldName);
mkrauskopf@3030
   535
                    List<? extends Error> errors = workingCopy.getDiagnostics();
tor@3
   536
                    if (errors.size() > 0) {
tor@3
   537
                        for (Error e : errors) {
tor@3
   538
                            if (e.getSeverity() == Severity.ERROR) {
tor@3
   539
                                error = e;
tor@3
   540
                                break;
tor@3
   541
                            }
tor@3
   542
                        }
tor@3
   543
                        if (error == null) {
tor@3
   544
                            error = errors.get(0);
tor@3
   545
                        }
tor@3
   546
                        
tor@3
   547
                        String errorMsg = error.getDisplayName();
tor@3
   548
                        
tor@3
   549
                        if (errorMsg.length() > 80) {
tor@3
   550
                            errorMsg = errorMsg.substring(0, 77) + "..."; // NOI18N
tor@3
   551
                        }
tor@3
   552
tor@3
   553
                        desc = desc + "; " + errorMsg;
tor@1198
   554
                        start = error.getStartPosition();
tor@544
   555
                        start = LexUtilities.getLexerOffset(workingCopy, start);
tor@544
   556
                        if (start == -1) {
tor@544
   557
                            start = 0;
tor@3
   558
                        }
tor@544
   559
                        end = start;
tor@3
   560
                    }
tor@3
   561
                    PositionRef startPos = ces.createPositionRef(start, Bias.Forward);
tor@3
   562
                    PositionRef endPos = ces.createPositionRef(end, Bias.Forward);
tor@3
   563
                    Difference diff = new Difference(Difference.Kind.CHANGE, startPos, endPos, "", "", desc); // NOI18N
tor@3
   564
                    diffs.add(diff);
tor@3
   565
                }
tor@3
   566
            }
tor@3
   567
tor@3
   568
            if (error == null && refactoring.isSearchInComments()) {
mkrauskopf@3030
   569
                Document doc = RetoucheUtils.getDocument(workingCopy, RubyUtils.getFileObject(workingCopy));
tor@3
   570
                if (doc != null) {
tor@3
   571
                    //force open
tor@3
   572
                    TokenHierarchy<Document> th = TokenHierarchy.get(doc);
mmetelka@806
   573
                    TokenSequence<?> ts = th.tokenSequence();
tor@3
   574
tor@3
   575
                    ts.move(0);
tor@3
   576
tor@3
   577
                    searchTokenSequence(ts);
tor@3
   578
                }
tor@3
   579
            }
tor@3
   580
tor@3
   581
            ces = null;
tor@3
   582
        }
tor@3
   583
        
mmetelka@806
   584
        private void searchTokenSequence(TokenSequence<?> ts) {
tor@3
   585
            if (ts.moveNext()) {
tor@3
   586
                do {
mmetelka@806
   587
                    Token<?> token = ts.token();
tor@3
   588
                    TokenId id = token.id();
tor@3
   589
tor@3
   590
                    String primaryCategory = id.primaryCategory();
tor@3
   591
                    if ("comment".equals(primaryCategory) || "block-comment".equals(primaryCategory)) { // NOI18N
tor@3
   592
                        // search this comment
tor@2113
   593
                        CharSequence tokenText = token.text();
tor@2113
   594
                        if (tokenText == null || oldName == null) {
tor@2113
   595
                            continue;
tor@2113
   596
                        }
tor@2113
   597
                        int index = TokenUtilities.indexOf(tokenText, oldName);
tor@3
   598
                        if (index != -1) {
tor@2113
   599
                            String text = tokenText.toString();
tor@3
   600
                            // TODO make sure it's its own word. Technically I could
tor@3
   601
                            // look at identifier chars like "_" here but since they are
tor@3
   602
                            // used for other purposes in comments, consider letters
tor@3
   603
                            // and numbers as enough
tor@3
   604
                            if ((index == 0 || !Character.isLetterOrDigit(text.charAt(index-1))) &&
tor@3
   605
                                    (index+oldName.length() >= text.length() || 
tor@3
   606
                                    !Character.isLetterOrDigit(text.charAt(index+oldName.length())))) {
tor@3
   607
                                int start = ts.offset() + index;
tor@3
   608
                                int end = start + oldName.length();
tor@3
   609
                                if (ces == null) {
tor@3
   610
                                    ces = RetoucheUtils.findCloneableEditorSupport(workingCopy);
tor@3
   611
                                }
tor@3
   612
                                PositionRef startPos = ces.createPositionRef(start, Bias.Forward);
tor@3
   613
                                PositionRef endPos = ces.createPositionRef(end, Bias.Forward);
tor@565
   614
                                String desc = getString("ChangeComment");
tor@3
   615
                                Difference diff = new Difference(Difference.Kind.CHANGE, startPos, endPos, oldName, newName, desc);
tor@3
   616
                                diffs.add(diff);
tor@3
   617
                            }
tor@3
   618
                        }
tor@3
   619
                    } else {
mmetelka@806
   620
                        TokenSequence<?> embedded = ts.embedded();
tor@3
   621
                        if (embedded != null) {
tor@3
   622
                            searchTokenSequence(embedded);
tor@3
   623
                        }                                    
tor@3
   624
                    }
tor@3
   625
                } while (ts.moveNext());
tor@3
   626
            }
tor@3
   627
        }
tor@3
   628
tor@3
   629
        private void rename(Node node, String oldCode, String newCode, String desc) {
tor@3
   630
            OffsetRange range = AstUtilities.getNameRange(node);
tor@3
   631
            assert range != OffsetRange.NONE;
tor@3
   632
            int pos = range.getStart();
tor@3
   633
tor@3
   634
            if (desc == null) {
tor@3
   635
                // TODO - insert "method call", "method definition", "class definition", "symbol", "attribute" etc. and from and too?
tor@3
   636
                if (node instanceof MethodDefNode) {
tor@565
   637
                    desc = getString("UpdateMethodDef");
tor@3
   638
                } else if (AstUtilities.isCall(node)) {
tor@565
   639
                    desc = getString("UpdateCall");
tor@3
   640
                } else if (node instanceof SymbolNode) {
tor@565
   641
                    desc = getString("UpdateSymbol");
tor@3
   642
                } else if (node instanceof ClassNode || node instanceof SClassNode) {
tor@565
   643
                    desc = getString("UpdateClassDef");
tor@3
   644
                } else if (node instanceof ModuleNode) {
tor@565
   645
                    desc = getString("UpdateModule");
tor@3
   646
                } else if (node instanceof LocalVarNode || node instanceof LocalAsgnNode || node instanceof DVarNode || node instanceof DAsgnNode) {
tor@565
   647
                    desc = getString("UpdateLocalvar");
tor@3
   648
                } else if (node instanceof GlobalVarNode || node instanceof GlobalAsgnNode) {
tor@565
   649
                    desc = getString("UpdateGlobal");
tor@3
   650
                } else if (node instanceof InstVarNode || node instanceof InstAsgnNode) {
tor@565
   651
                    desc = getString("UpdateInstance");
tor@3
   652
                } else if (node instanceof ClassVarNode || node instanceof ClassVarDeclNode || node instanceof ClassVarAsgnNode) {
tor@565
   653
                    desc = getString("UpdateClassvar");
tor@3
   654
                } else {
tor@565
   655
                    desc = NbBundle.getMessage(RenameRefactoringPlugin.class, "UpdateRef", oldCode);
tor@3
   656
                }
tor@3
   657
            }
tor@3
   658
tor@3
   659
            if (ces == null) {
tor@3
   660
                ces = RetoucheUtils.findCloneableEditorSupport(workingCopy);
tor@3
   661
            }
tor@3
   662
            
tor@544
   663
            // Convert from AST to lexer offsets if necessary
tor@544
   664
            pos = LexUtilities.getLexerOffset(workingCopy, pos);
tor@544
   665
            if (pos == -1) {
tor@544
   666
                // Translation failed
tor@544
   667
                return;
tor@3
   668
            }
tor@3
   669
            
tor@3
   670
            int start = pos;
tor@3
   671
            int end = pos+oldCode.length();
tor@3
   672
            // TODO if a SymbolNode, +=1 since the symbolnode includes the ":"
tor@2818
   673
            BaseDocument doc = null;
tor@3
   674
            try {
tor@2818
   675
                doc = (BaseDocument)ces.openDocument();
tor@2818
   676
                doc.readLock();
tor@3
   677
tor@3
   678
                if (start > doc.getLength()) {
tor@3
   679
                    start = end = doc.getLength();
tor@3
   680
                }
tor@3
   681
tor@3
   682
                if (end > doc.getLength()) {
tor@3
   683
                    end = doc.getLength();
tor@3
   684
                }
tor@3
   685
tor@3
   686
                // Look in the document and search around a bit to detect the exact method reference
tor@3
   687
                // (and adjust position accordingly). Thus, if I have off by one errors in the AST (which
tor@3
   688
                // occasionally happens) the user's source won't get munged
tor@3
   689
                if (!oldCode.equals(doc.getText(start, end-start))) {
tor@3
   690
                    // Look back and forwards by 1 at first
tor@3
   691
                    int lineStart = Utilities.getRowFirstNonWhite(doc, start);
tor@3
   692
                    int lineEnd = Utilities.getRowLastNonWhite(doc, start)+1; // +1: after last char
tor@3
   693
                    if (lineStart == -1 || lineEnd == -1) { // We're really on the wrong line!
mkrauskopf@3030
   694
                        System.out.println("Empty line entry in " + FileUtil.getFileDisplayName(RubyUtils.getFileObject(workingCopy)) +
tor@3
   695
                                "; no match for " + oldCode + " in line " + start + " referenced by node " + 
tor@3
   696
                                node + " of type " + node.getClass().getName());
tor@3
   697
                        return;
tor@3
   698
                    }
tor@3
   699
tor@3
   700
                    if (lineStart < 0 || lineEnd-lineStart < 0) {
tor@3
   701
                        return; // Can't process this one
tor@3
   702
                    }
tor@3
   703
tor@3
   704
                    String line = doc.getText(lineStart, lineEnd-lineStart);
tor@3
   705
                    if (line.indexOf(oldCode) == -1) {
mkrauskopf@3030
   706
                        System.out.println("Skipping entry in " + FileUtil.getFileDisplayName(RubyUtils.getFileObject(workingCopy)) +
tor@3
   707
                                "; no match for " + oldCode + " in line " + line + " referenced by node " + 
tor@3
   708
                                node + " of type " + node.getClass().getName());
tor@3
   709
                    } else {
tor@3
   710
                        int lineOffset = start-lineStart;
tor@3
   711
                        int newOffset = -1;
tor@3
   712
                        // Search up and down by one
tor@3
   713
                        for (int distance = 1; distance < line.length(); distance++) {
tor@3
   714
                            // Ahead first
tor@3
   715
                            if (lineOffset+distance+oldCode.length() <= line.length() &&
tor@3
   716
                                    oldCode.equals(line.substring(lineOffset+distance, lineOffset+distance+oldCode.length()))) {
tor@3
   717
                                newOffset = lineOffset+distance;
tor@3
   718
                                break;
tor@3
   719
                            }
tor@3
   720
                            if (lineOffset-distance >= 0 && lineOffset-distance+oldCode.length() <= line.length() &&
tor@3
   721
                                    oldCode.equals(line.substring(lineOffset-distance, lineOffset-distance+oldCode.length()))) {
tor@3
   722
                                newOffset = lineOffset-distance;
tor@3
   723
                                break;
tor@3
   724
                            }
tor@3
   725
                        }
tor@3
   726
tor@3
   727
                        if (newOffset != -1) {
tor@3
   728
                            start = newOffset+lineStart;
tor@3
   729
                            end = start+oldCode.length();
tor@3
   730
                        }
tor@3
   731
                    }
tor@3
   732
                }
tor@3
   733
            } catch (IOException ie) {
tor@3
   734
                Exceptions.printStackTrace(ie);
tor@3
   735
            } catch (BadLocationException ble) {
tor@3
   736
                Exceptions.printStackTrace(ble);
tor@2818
   737
            } finally {
tor@2818
   738
                if (doc != null) {
tor@2818
   739
                    doc.readUnlock();
tor@2818
   740
                }
tor@3
   741
            }
tor@3
   742
            
tor@3
   743
            if (newCode == null) {
tor@3
   744
                // Usually it's the new name so allow client code to refer to it as just null
tor@3
   745
                newCode = refactoring.getNewName(); // XXX isn't this == our field "newName"?
tor@3
   746
            }
tor@3
   747
tor@3
   748
            PositionRef startPos = ces.createPositionRef(start, Bias.Forward);
tor@3
   749
            PositionRef endPos = ces.createPositionRef(end, Bias.Forward);
tor@3
   750
            Difference diff = new Difference(Difference.Kind.CHANGE, startPos, endPos, oldCode, newCode, desc);
tor@3
   751
            diffs.add(diff);
tor@3
   752
        }
tor@3
   753
    
tor@3
   754
        /** Search for local variables in local scope */
tor@3
   755
        private void findLocal(RubyElementCtx searchCtx, RubyElementCtx fileCtx, Node node, String name) {
emononen@3259
   756
            switch (node.getNodeType()) {
tor@1291
   757
            case ARGUMENTNODE:
tor@3
   758
                // TODO - check parent and make sure it's not a method of the same name?
tor@3
   759
                // e.g. if I have "def foo(foo)" and I'm searching for "foo" (the parameter),
tor@3
   760
                // I don't want to pick up the ArgumentNode under def foo that corresponds to the
tor@3
   761
                // "foo" method name!
tor@3
   762
                if (((ArgumentNode)node).getName().equals(name)) {
tor@565
   763
                    rename(node, name, null, getString("RenameParam"));
tor@3
   764
                }
tor@662
   765
                break;
tor@3
   766
// I don't have alias nodes within a method, do I?                
tor@3
   767
//            } else if (node instanceof AliasNode) { 
tor@3
   768
//                AliasNode an = (AliasNode)node;
tor@3
   769
//                if (an.getNewName().equals(name) || an.getOldName().equals(name)) {
tor@3
   770
//                    elements.add(refactoring, WhereUsedElement.create(matchCtx));
tor@3
   771
//                }
tor@662
   772
//                break;
tor@1291
   773
            case LOCALVARNODE:
tor@1291
   774
            case LOCALASGNNODE:
tor@3
   775
                if (((INameNode)node).getName().equals(name)) {
tor@565
   776
                    rename(node, name, null, getString("UpdateLocalvar"));
tor@3
   777
                }
tor@662
   778
                break;
tor@1291
   779
            case DVARNODE:
tor@1291
   780
            case DASGNNODE:
tor@3
   781
                 if (((INameNode)node).getName().equals(name)) {
tor@3
   782
                    // Found a method call match
tor@3
   783
                    // TODO - make a node on the same line
tor@3
   784
                    // TODO - check arity - see OccurrencesFinder
tor@565
   785
                    rename(node, name, null, getString("UpdateDynvar"));
tor@3
   786
                 }                 
tor@662
   787
                 break;
tor@1291
   788
            case SYMBOLNODE:
tor@3
   789
                // XXX Can I have symbols to local variables? Try it!!!
tor@3
   790
                if (((SymbolNode)node).getName().equals(name)) {
tor@565
   791
                    rename(node, name, null, getString("UpdateSymbol"));
tor@3
   792
                }
tor@662
   793
                break;
tor@3
   794
            }
tor@3
   795
enebo@4542
   796
            for (Node child : node.childNodes()) {
tor@3
   797
                findLocal(searchCtx, fileCtx, child, name);
tor@3
   798
            }
tor@3
   799
        }
tor@3
   800
        
tor@3
   801
        /**
tor@3
   802
         * @todo P1: This is matching method names on classes that have nothing to do with the class we're searching for
tor@3
   803
         *   - I've gotta filter fields, methods etc. that are not in the current class
tor@3
   804
         *  (but I also have to search for methods that are OVERRIDING the class... so I've gotta work a little harder!)
tor@3
   805
         * @todo Arity matching on the methods to preclude methods that aren't overriding or aliasing!
tor@3
   806
         */
tor@2503
   807
        @SuppressWarnings("fallthrough")
tor@3
   808
        private void find(AstPath path, RubyElementCtx searchCtx, RubyElementCtx fileCtx, Node node, String name, boolean upperCase) {
tor@3
   809
            /* TODO look for both old and new and attempt to fix
tor@3
   810
             if (node instanceof AliasNode) {
tor@3
   811
                AliasNode an = (AliasNode)node;
tor@3
   812
                if (an.getNewName().equals(name) || an.getOldName().equals(name)) {
tor@3
   813
                    RubyElementCtx matchCtx = new RubyElementCtx(fileCtx, node);
tor@3
   814
                    elements.add(refactoring, WhereUsedElement.create(matchCtx));
tor@3
   815
                }
tor@3
   816
            } else*/ if (!upperCase) {
tor@3
   817
                // Local variables - I can be smarter about context searches here!
tor@3
   818
                
tor@3
   819
                // Methods, attributes, etc.
tor@3
   820
                // TODO - be more discriminating on the filetype
emononen@3259
   821
                switch (node.getNodeType()) {
tor@1291
   822
                case DEFNNODE:
tor@1291
   823
                case DEFSNODE: {
tor@3
   824
                    if (((MethodDefNode)node).getName().equals(name)) {
tor@3
   825
                                                
tor@3
   826
                        boolean skip = false;
tor@3
   827
tor@3
   828
                        // Check that we're in a class or module we're interested in
tor@3
   829
                        String fqn = AstUtilities.getFqnName(path);
tor@3
   830
                        if (fqn == null || fqn.length() == 0) {
tor@3
   831
                            fqn = RubyIndex.OBJECT;
tor@3
   832
                        }
tor@3
   833
                        
tor@3
   834
                        if (!fqn.equals(searchCtx.getDefClass())) {
emononen@3643
   835
                            boolean inherited = false;
emononen@3643
   836
                            for (IndexedMethod method : overridesMethods) {
emononen@3643
   837
                                if (method.getIn().equals(fqn)) {
emononen@3643
   838
                                    inherited = true;
emononen@3643
   839
                                    break;
emononen@3643
   840
                                }
emononen@3643
   841
                            }
tor@3
   842
                            // XXX THE ABOVE IS NOT RIGHT - I shouldn't
tor@3
   843
                            // use equals on the class names, I should use the
tor@3
   844
                            // index and see if one derives fromor includes the other
emononen@3643
   845
                            skip = !inherited;
tor@3
   846
                        }
tor@3
   847
tor@3
   848
                        // Check arity
tor@3
   849
                        if (!skip && AstUtilities.isCall(searchCtx.getNode())) {
tor@3
   850
                            // The reference is a call and this is a definition; see if
tor@3
   851
                            // this looks like a match
tor@3
   852
                            // TODO - enforce that this method is also in the desired
tor@3
   853
                            // target class!!!
tor@3
   854
                            if (!AstUtilities.isCallFor(searchCtx.getNode(), searchCtx.getArity(), node)) {
tor@3
   855
                                skip = true;
tor@3
   856
                            }
tor@3
   857
                        } else {
tor@3
   858
                            // The search handle is a method def, as is this, with the same name.
tor@3
   859
                            // Now I need to go and see if this is an override (e.g. compatible
tor@3
   860
                            // arglist...)
tor@3
   861
                            // XXX TODO
tor@3
   862
                        }
tor@3
   863
                        
tor@3
   864
                        if (!skip) {
tor@3
   865
                            // Found a method match
tor@3
   866
                            // TODO - check arity - see OccurrencesFinder
enebo@4518
   867
                            node = ((MethodDefNode)node).getNameNode();
tor@565
   868
                            rename(node, name, null, getString("UpdateMethodDef"));
tor@3
   869
                        }
tor@3
   870
                    }
tor@662
   871
                    break;
tor@662
   872
                }
tor@1291
   873
                case FCALLNODE:
tor@662
   874
                    if (AstUtilities.isAttr(node)) {
tor@662
   875
                        SymbolNode[] symbols = AstUtilities.getAttrSymbols(node);
tor@662
   876
                        for (SymbolNode symbol : symbols) {
tor@662
   877
                            if (symbol.getName().equals(name)) {
tor@662
   878
                                // TODO - can't replace the whole node here - I need to replace only the text!
tor@662
   879
                                rename(node, name, null, null);
tor@662
   880
                            }
tor@662
   881
                        }
tor@662
   882
                    }
tor@662
   883
                    // Fall through for other call checking
tor@1291
   884
                case VCALLNODE:
tor@1291
   885
                case CALLNODE:
tor@3
   886
                     if (((INameNode)node).getName().equals(name)) {
tor@3
   887
                         // TODO - if it's a call without a lhs (e.g. Call.LOCAL),
tor@3
   888
                         // make sure that we're referring to the same method call
tor@3
   889
                        // Found a method call match
tor@3
   890
                        // TODO - make a node on the same line
tor@3
   891
                        // TODO - check arity - see OccurrencesFinder
tor@3
   892
                        rename(node, name, null, null);
tor@3
   893
                     }
tor@662
   894
                     break;
tor@1291
   895
                case SYMBOLNODE:
tor@3
   896
                    if (((SymbolNode)node).getName().equals(name)) {
tor@3
   897
                        // TODO do something about the colon?
tor@3
   898
                        rename(node, name, null, null);
tor@3
   899
                    }
tor@662
   900
                    break;
tor@1291
   901
                case GLOBALVARNODE:
tor@1291
   902
                case GLOBALASGNNODE:
tor@1291
   903
                case INSTVARNODE:
tor@1291
   904
                case INSTASGNNODE:
tor@1291
   905
                case CLASSVARNODE:
tor@1291
   906
                case CLASSVARASGNNODE:
tor@1291
   907
                case CLASSVARDECLNODE:
tor@3
   908
                    if (((INameNode)node).getName().equals(name)) {
enebo@4559
   909
                        rename(node, ((INameNode)node).getLexicalName(), null, null);
tor@3
   910
                    }
tor@662
   911
                    break;
tor@3
   912
                }
tor@3
   913
            } else {
tor@3
   914
                // Classes, modules, constants, etc.
emononen@3259
   915
                switch (node.getNodeType()) {
tor@1291
   916
                case COLON2NODE: {
tor@3
   917
                    Colon2Node c2n = (Colon2Node)node;
tor@3
   918
                    if (c2n.getName().equals(name)) {
tor@3
   919
                        rename(node, name, null, null);
tor@3
   920
                    }
tor@3
   921
                    
tor@662
   922
                    break;
tor@662
   923
                }
tor@1291
   924
                case CONSTNODE:
tor@1291
   925
                case CONSTDECLNODE:
tor@3
   926
                    if (((INameNode)node).getName().equals(name)) {
tor@3
   927
                        rename(node, name, null, null);
tor@3
   928
                    }
tor@662
   929
                    break;
tor@3
   930
                }
tor@3
   931
            }
tor@3
   932
            
enebo@4542
   933
            for (Node child : node.childNodes()) {
tor@3
   934
                path.descend(child);
tor@3
   935
                find(path, searchCtx, fileCtx, child, name, upperCase);
tor@3
   936
                path.ascend();
tor@3
   937
            }
tor@3
   938
        }
tor@3
   939
    
tor@3
   940
    }
tor@3
   941
    
tor@3
   942
}