001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * If you wish your version of this file to be governed by only the CDDL
025: * or only the GPL Version 2, indicate your decision by adding
026: * "[Contributor] elects to include this software in this distribution
027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
028: * single choice of license, a recipient has the option to distribute
029: * your version of this file under either the CDDL, the GPL Version 2 or
030: * to extend the choice of license to its licensees as provided above.
031: * However, if you add GPL Version 2 code and therefore, elected the GPL
032: * Version 2 license, then the option applies only if the new code is
033: * made subject to such option by the copyright holder.
034: *
035: * Contributor(s):
036: *
037: * Portions Copyrighted 2007 Sun Microsystems, Inc.
038: */
039: package org.netbeans.modules.ruby.hints;
040:
041: import java.util.HashSet;
042: import java.util.Set;
043: import java.util.ArrayList;
044: import java.util.Collections;
045: import java.util.List;
046: import java.util.Set;
047: import java.util.prefs.Preferences;
048: import javax.swing.JComponent;
049: import org.jruby.ast.IfNode;
050: import org.jruby.ast.Node;
051: import org.jruby.ast.NodeTypes;
052: import org.netbeans.modules.gsf.api.CompilationInfo;
053: import org.netbeans.modules.gsf.api.OffsetRange;
054: import org.netbeans.editor.BaseDocument;
055: import org.netbeans.editor.Utilities;
056: import org.netbeans.modules.ruby.AstPath;
057: import org.netbeans.modules.ruby.AstUtilities;
058: import org.netbeans.modules.ruby.ParseTreeWalker;
059: import org.netbeans.modules.ruby.hints.spi.AstRule;
060: import org.netbeans.modules.ruby.hints.spi.Description;
061: import org.netbeans.modules.ruby.hints.spi.EditList;
062: import org.netbeans.modules.ruby.hints.spi.Fix;
063: import org.netbeans.modules.ruby.hints.spi.HintSeverity;
064: import org.netbeans.modules.ruby.lexer.LexUtilities;
065: import org.openide.util.NbBundle;
066: import org.jruby.ast.types.INameNode;
067: import org.netbeans.modules.ruby.ParseTreeVisitor;
068: import org.netbeans.modules.ruby.hints.spi.PreviewableFix;
069: import org.netbeans.modules.ruby.hints.spi.RuleContext;
070:
071: /**
072: * Identify "accidental" assignments of the form "if (a = b)" which should have been "if (a == b)"
073: *
074: * @todo Refine this by only warning about comparisons where the LHS is an existing variable in scope
075: *
076: * @author Tor Norbye
077: */
078: public class AccidentalAssignment implements AstRule {
079:
080: public Set<Integer> getKinds() {
081: return Collections.singleton(NodeTypes.IFNODE);
082: }
083:
084: public void run(RuleContext context, List<Description> result) {
085: Node node = context.node;
086: AstPath path = context.path;
087: CompilationInfo info = context.compilationInfo;
088:
089: IfNode ifNode = (IfNode) node;
090: Node condition = ifNode.getCondition();
091: if (condition != null) {
092: if (condition.nodeId == NodeTypes.NEWLINENODE) {
093: @SuppressWarnings("unchecked")
094: List<Node> children = condition.childNodes();
095: if (children.size() == 0) {
096: return;
097: }
098: condition = children.get(0);
099: }
100: if (((condition.nodeId == NodeTypes.LOCALASGNNODE) && !isFirstUsage(
101: path, condition))
102: || (condition.nodeId == NodeTypes.ATTRASSIGNNODE)) {
103: String displayName = NbBundle.getMessage(
104: AccidentalAssignment.class,
105: "AccidentalAssignment");
106: OffsetRange range = AstUtilities.getRange(condition);
107: range = LexUtilities.getLexerOffsets(info, range);
108: if (range != OffsetRange.NONE) {
109: List<Fix> fixList = new ArrayList<Fix>(2);
110: fixList.add(new ConvertAssignmentFix(info,
111: condition));
112: Description desc = new Description(this ,
113: displayName, info.getFileObject(), range,
114: fixList, 600);
115: result.add(desc);
116: }
117: }
118: }
119: }
120:
121: private boolean isFirstUsage(AstPath path, Node node) {
122: DeclarationFinder finder = new DeclarationFinder(node);
123: Node method = AstUtilities.findLocalScope(node, path);
124: new ParseTreeWalker(finder).walk(method);
125:
126: return finder.isDeclaration();
127: }
128:
129: public String getId() {
130: return "AccidentalAssignment";
131: }
132:
133: public String getDescription() {
134: return NbBundle.getMessage(AccidentalAssignment.class,
135: "AccidentalAssignmentDesc");
136: }
137:
138: public boolean getDefaultEnabled() {
139: return true;
140: }
141:
142: public JComponent getCustomizer(Preferences node) {
143: return null;
144: }
145:
146: public boolean appliesTo(CompilationInfo info) {
147: return true;
148: }
149:
150: public String getDisplayName() {
151: return NbBundle.getMessage(AccidentalAssignment.class,
152: "AccidentalAssignment");
153: }
154:
155: public boolean showInTasklist() {
156: return true;
157: }
158:
159: public HintSeverity getDefaultSeverity() {
160: return HintSeverity.WARNING;
161: }
162:
163: private static class ConvertAssignmentFix implements PreviewableFix {
164:
165: private CompilationInfo info;
166: private Node assignment;
167:
168: public ConvertAssignmentFix(CompilationInfo info,
169: Node assignment) {
170: this .info = info;
171: this .assignment = assignment;
172: }
173:
174: public String getDescription() {
175: return NbBundle.getMessage(AccidentalAssignment.class,
176: "AccidentalAssignmentFix");
177: }
178:
179: public void implement() throws Exception {
180: EditList edits = getEditList();
181: if (edits != null) {
182: edits.apply();
183: }
184: }
185:
186: public EditList getEditList() throws Exception {
187: BaseDocument doc = (BaseDocument) info.getDocument();
188: OffsetRange range = AstUtilities.getNameRange(assignment);
189: int endOffset = range.getEnd();
190: if (assignment.nodeId == NodeTypes.ATTRASSIGNNODE) {
191: // Workaround: the name-range of attr nodes isn't computed
192: // correctly so just use the LHS
193: endOffset = range.getStart();
194: }
195: int lineEnd = Utilities.getRowEnd(doc, endOffset);
196: String line = doc.getText(endOffset, lineEnd - endOffset);
197: int dashIndex = line.indexOf('=');
198: if (dashIndex != -1) {
199: return new EditList(doc).replace(endOffset + dashIndex,
200: 0, "=", false, 0);
201: }
202:
203: return null;
204: }
205:
206: public boolean isSafe() {
207: return false;
208: }
209:
210: public boolean isInteractive() {
211: return false;
212: }
213:
214: public boolean canPreview() {
215: return true;
216: }
217: }
218:
219: public class DeclarationFinder implements ParseTreeVisitor {
220:
221: private Node target;
222: // private boolean inArgs;
223: private Set<String> names = new HashSet<String>();
224: private boolean found;
225:
226: DeclarationFinder(Node target) {
227: this .target = target;
228: }
229:
230: public boolean isDeclaration() {
231: return !found;
232: }
233:
234: public boolean visit(Node node) {
235: if (node == target) {
236: String name = ((INameNode) node).getName();
237: if (names.contains(name)) {
238: found = true;
239: return true;
240: }
241: }
242: switch (node.nodeId) {
243: case NodeTypes.LOCALVARNODE:
244: names.add(((INameNode) node).getName());
245: break;
246: // TODO - handle blocks properly
247: case NodeTypes.DVARNODE:
248: names.add(((INameNode) node).getName());
249: break;
250: }
251: return false;
252: }
253:
254: public boolean unvisit(Node node) {
255: switch (node.nodeId) {
256: case NodeTypes.LOCALASGNNODE:
257: names.add(((INameNode) node).getName());
258: break;
259: // TODO - handle blocks properly
260: case NodeTypes.DASGNNODE:
261: names.add(((INameNode) node).getName());
262: break;
263: }
264:
265: return node == target;
266: }
267: }
268: }
|