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: * Contributor(s):
025: *
026: * Portions Copyrighted 2007 Sun Microsystems, Inc.
027: */
028: package org.netbeans.modules.ruby.hints;
029:
030: import java.net.URL;
031: import java.util.ArrayList;
032: import java.util.HashMap;
033: import java.util.HashSet;
034: import java.util.List;
035: import java.util.Map;
036: import java.util.Set;
037: import java.util.prefs.Preferences;
038: import javax.swing.JComponent;
039: import org.jruby.ast.FCallNode;
040: import org.jruby.ast.ListNode;
041: import org.jruby.ast.Node;
042: import org.jruby.ast.NodeTypes;
043: import org.jruby.ast.StrNode;
044: import org.jruby.ast.types.INameNode;
045: import org.jruby.util.ByteList;
046: import org.netbeans.modules.gsf.api.CompilationInfo;
047: import org.netbeans.modules.gsf.api.OffsetRange;
048: import org.netbeans.editor.BaseDocument;
049: import org.netbeans.editor.Utilities;
050: import org.netbeans.modules.ruby.AstUtilities;
051: import org.netbeans.modules.ruby.hints.spi.AstRule;
052: import org.netbeans.modules.ruby.hints.spi.Description;
053: import org.netbeans.modules.ruby.hints.spi.EditList;
054: import org.netbeans.modules.ruby.hints.spi.Fix;
055: import org.netbeans.modules.ruby.hints.spi.HintSeverity;
056: import org.netbeans.modules.ruby.hints.spi.PreviewableFix;
057: import org.netbeans.modules.ruby.hints.spi.RuleContext;
058: import org.netbeans.modules.ruby.lexer.LexUtilities;
059: import org.openide.awt.HtmlBrowser;
060: import org.openide.util.NbBundle;
061:
062: /**
063: * <p>A hint which looks at all files and scans for usages of deprecated
064: * constructs; it adds warnings (and in some cases fixes) for these.
065: * </p>
066: *
067: * @todo Offer customized messages per suggested fix, e.g. explaining why a particular
068: * fix is necessary, which versions it applies to, and perhaps a link to more information.
069: * @todo See http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/2397
070: * Looks like deprecations are marked like this:
071: * +warn "Warning: getopts.rb is deprecated after Ruby 1.8.1"
072: *
073: * @author Tor Norbye
074: */
075: public class Deprecations implements AstRule {
076:
077: private static class Deprecation {
078: private String oldName;
079: private String newName;
080: /** Key: {0} is the old name, {1} is the new name */
081: private String descriptionKey;
082: private String helpUrl;
083:
084: public Deprecation(String oldName, String newName,
085: String descriptionKey, String helpUrl) {
086: this .oldName = oldName;
087: this .newName = newName;
088: this .descriptionKey = descriptionKey;
089: this .helpUrl = helpUrl;
090: }
091: }
092:
093: static Set<Integer> kinds = new HashSet<Integer>();
094: private static Map<String, Deprecation> deprecatedMethods = new HashMap<String, Deprecation>();
095: private static Map<String, Deprecation> deprecatedRequires = new HashMap<String, Deprecation>();
096: static {
097: kinds.add(NodeTypes.FCALLNODE);
098: kinds.add(NodeTypes.VCALLNODE);
099: kinds.add(NodeTypes.CALLNODE);
100:
101: Deprecation require_gem = new Deprecation("require_gem", "gem",
102: "HELP_require_gem",
103: "http://www.ruby-forum.com/topic/136010"); // NOI18N
104: deprecatedMethods.put(require_gem.oldName, require_gem);
105:
106: Deprecation assert_raises = new Deprecation("assert_raises",
107: "assert_raise", "HELP_assert_raises",
108: "http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155815"); // NOI18N
109: deprecatedMethods.put(assert_raises.oldName, assert_raises);
110:
111: // Deprecated requires
112: Deprecation d = new Deprecation("getopts", "optparse", null,
113: null); // NOI18N
114: deprecatedRequires.put(d.oldName, d);
115:
116: d = new Deprecation("cgi-lib", "cgi", null, null); // NOI18N
117: deprecatedRequires.put(d.oldName, d);
118:
119: d = new Deprecation("importenv", "(no replacement)", null, null); // NOI18N
120: deprecatedRequires.put(d.oldName, d);
121:
122: d = new Deprecation("parsearg", "optparse", null, null); // NOI18N
123: deprecatedRequires.put(d.oldName, d);
124:
125: d = new Deprecation("ftools", "fileutils", "HELP_ftools", null); // NOI18N
126: deprecatedRequires.put(d.oldName, d);
127: }
128:
129: public Deprecations() {
130: }
131:
132: public boolean appliesTo(CompilationInfo info) {
133: return true;
134: }
135:
136: public Set<Integer> getKinds() {
137: return kinds;
138: }
139:
140: public void run(RuleContext context, List<Description> result) {
141: Node node = context.node;
142: CompilationInfo info = context.compilationInfo;
143:
144: // Look for use of deprecated fields
145: String name = ((INameNode) node).getName();
146:
147: Deprecation deprecation = null;
148: final boolean isRequire;
149: if ("require".equals(name)) { // NOI18N
150: isRequire = true;
151: // It's a require-completion.
152: String require = getStringArg(node);
153: if (require != null) {
154: deprecation = deprecatedRequires.get(require);
155: }
156: } else if (deprecatedMethods.containsKey(name)) {
157: isRequire = false;
158: deprecation = deprecatedMethods.get(name);
159: } else {
160: return;
161: }
162:
163: if (deprecation != null) {
164: // Add a warning - you're using a deprecated field. Use the
165: // method/attribute instead!
166: OffsetRange range = AstUtilities.getNameRange(node);
167:
168: range = LexUtilities.getLexerOffsets(info, range);
169: if (range != OffsetRange.NONE) {
170: String defaultKey = isRequire ? "DeprecatedRequire"
171: : "DeprecatedMethodUse"; // NOI18N
172: String message = NbBundle
173: .getMessage(
174: Deprecations.class,
175: deprecation.descriptionKey != null ? deprecation.descriptionKey
176: : defaultKey,
177: deprecation.oldName,
178: deprecation.newName);
179:
180: List<Fix> fixes = new ArrayList<Fix>();
181: if (!isRequire) {
182: fixes.add(new DeprecationCallFix(info, node,
183: deprecation, false));
184: }
185: if (deprecation.helpUrl != null) {
186: fixes.add(new DeprecationCallFix(info, node,
187: deprecation, true));
188: }
189:
190: Description desc = new Description(this , message, info
191: .getFileObject(), range, fixes, 100);
192: result.add(desc);
193: }
194: }
195: }
196:
197: private static String getStringArg(Node node) {
198: if (node.nodeId == NodeTypes.FCALLNODE) {
199: Node argsNode = ((FCallNode) node).getArgsNode();
200:
201: if (argsNode instanceof ListNode) {
202: ListNode args = (ListNode) argsNode;
203:
204: if (args.size() > 0) {
205: Node n = args.get(0);
206:
207: // For dynamically computed strings, we have n instanceof DStrNode
208: // but I can't handle these anyway
209: if (n instanceof StrNode) {
210: ByteList require = ((StrNode) n).getValue();
211:
212: if ((require != null) && (require.length() > 0)) {
213: return require.toString();
214: }
215: }
216: }
217: }
218: }
219:
220: return null;
221: }
222:
223: public void cancel() {
224: // Does nothing
225: }
226:
227: public String getId() {
228: return "Deprecations"; // NOI18N
229: }
230:
231: public String getDisplayName() {
232: return NbBundle.getMessage(Deprecations.class, "Deprecation");
233: }
234:
235: public String getDescription() {
236: return NbBundle.getMessage(Deprecations.class,
237: "DeprecationDesc");
238: }
239:
240: public boolean getDefaultEnabled() {
241: return true;
242: }
243:
244: public HintSeverity getDefaultSeverity() {
245: return HintSeverity.WARNING;
246: }
247:
248: public boolean showInTasklist() {
249: return true;
250: }
251:
252: public JComponent getCustomizer(Preferences node) {
253: return null;
254: }
255:
256: private static class DeprecationCallFix implements PreviewableFix {
257:
258: private CompilationInfo info;
259: private Node node;
260: private Deprecation deprecation;
261: private boolean help;
262:
263: public DeprecationCallFix(CompilationInfo info, Node node,
264: Deprecation deprecation, boolean help) {
265: this .info = info;
266: this .node = node;
267: this .deprecation = deprecation;
268: this .help = help;
269: }
270:
271: public String getDescription() {
272: if (help) {
273: return NbBundle.getMessage(Deprecations.class,
274: "ShowDeprecationHelp");
275: } else {
276: return NbBundle.getMessage(Deprecations.class,
277: "DeprecationFix", deprecation.oldName,
278: deprecation.newName);
279: }
280: }
281:
282: public void implement() throws Exception {
283: if (help) {
284: URL url = new URL(deprecation.helpUrl);
285: HtmlBrowser.URLDisplayer.getDefault().showURL(url);
286: } else {
287: EditList edits = getEditList();
288: if (edits != null) {
289: edits.apply();
290: }
291: }
292: }
293:
294: public EditList getEditList() throws Exception {
295: BaseDocument doc = (BaseDocument) info.getDocument();
296: OffsetRange range = AstUtilities.getCallRange(node);
297:
298: EditList list = new EditList(doc);
299: if (range != OffsetRange.NONE) {
300: if ("require_gem".equals(deprecation.oldName)) { // NOI18N
301: // Special case; see Dr. Nic's advice in my blog entry's comments
302: // http://blogs.sun.com/tor/entry/require_gem
303: String gemName = getStringArg(node);
304: int rowEnd = Utilities.getRowEnd(doc, range
305: .getStart());
306: list.replace(range.getStart(), range.getLength(),
307: deprecation.newName, false, 0);
308: if (gemName != null) {
309: list.replace(rowEnd, 0, "\nrequire \""
310: + gemName + "\"", false, 1); // NOI18N
311: }
312: list.format();
313: } else {
314: list.replace(range.getStart(), range.getLength(),
315: deprecation.newName, false, 0);
316: }
317: }
318:
319: return list;
320: }
321:
322: public boolean isSafe() {
323: return true;
324: }
325:
326: public boolean isInteractive() {
327: return false;
328: }
329:
330: public boolean canPreview() {
331: return !help;
332: }
333: }
334: }
|