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.util.Collections;
031: import java.util.HashMap;
032: import java.util.HashSet;
033: import java.util.List;
034: import java.util.Map;
035: import java.util.Set;
036: import java.util.prefs.Preferences;
037: import javax.swing.JComponent;
038: import org.jruby.ast.CallNode;
039: import org.jruby.ast.Node;
040: import org.jruby.ast.NodeTypes;
041: import org.jruby.ast.types.INameNode;
042: import org.netbeans.modules.gsf.api.CompilationInfo;
043: import org.netbeans.modules.gsf.api.OffsetRange;
044: import org.netbeans.api.project.FileOwnerQuery;
045: import org.netbeans.api.project.Project;
046: import org.netbeans.modules.ruby.AstPath;
047: import org.netbeans.modules.ruby.AstUtilities;
048: import org.netbeans.modules.ruby.hints.spi.AstRule;
049: import org.netbeans.modules.ruby.hints.spi.Description;
050: import org.netbeans.modules.ruby.hints.spi.Fix;
051: import org.netbeans.modules.ruby.hints.spi.HintSeverity;
052: import org.netbeans.modules.ruby.hints.spi.RuleContext;
053: import org.netbeans.modules.ruby.lexer.LexUtilities;
054: import org.openide.util.NbBundle;
055:
056: /**
057: * A hint which looks at Rails files and scans for usages of deprecated
058: * Rails constructs; it adds warnings (and in some cases fixes) for these.
059: * <p>
060: * Source:
061: * <a href="http://www.rubyonrails.org/deprecation">
062: * http://www.rubyonrails.org/deprecation
063: * </a>
064: * The above deprecations are (mostly) covered. However, my googling also found
065: * the following lists which need to be evaluated and covered:
066: * <ul>
067: * <li> http://rubythis.blogspot.com/2006/12/ruby-on-rails-deprecations-part-1.html
068: * <li> http://rubythis.blogspot.com/2006/12/ruby-on-rails-deprecations-part-2.html
069: * <li> http://rubythis.blogspot.com/2006/12/ruby-on-rails-deprecations-part-3-of-3.html
070: * <li> http://i.nfectio.us/articles/2006/11/02/deprecations-in-rails-1-2
071: * </ul>
072: *
073: * @todo Infer deprecations dynamically; Rails seems to annotate them - see
074: * for example has_many_association.rb
075: * <pre>
076: * deprecate :find_all => "use find(:all, ...) instead"
077: * </pre>
078: *
079: * @author Tor Norbye
080: */
081: public class RailsDeprecations implements AstRule {
082: static Set<String> deprecatedFields = new HashSet<String>();
083: static Map<String, String> deprecatedMethods = new HashMap<String, String>();
084: static {
085: deprecatedFields.add("@params"); // NOI18N
086: deprecatedFields.add("@session"); // NOI18N
087: deprecatedFields.add("@flash"); // NOI18N
088: deprecatedFields.add("@request"); // NOI18N
089: deprecatedFields.add("@cookies"); // NOI18N
090: deprecatedFields.add("@headers"); // NOI18N
091: deprecatedFields.add("@response"); // NOI18N
092:
093: deprecatedMethods.put("find_first", "find :first"); // NOI18N
094: deprecatedMethods.put("find_all", "find :all"); // NOI18N
095: deprecatedMethods.put("push_with_attributes",
096: "has_many :through"); // NOI18N
097: deprecatedMethods.put("redirect_to_path", "redirect_to"); // NOI18N
098: deprecatedMethods.put("redirect_to_url", "redirect_to"); // NOI18N
099: deprecatedMethods
100: .put("start_form_tag", "form_tag with a block"); // TODO - I18n?
101: deprecatedMethods.put("end_form_tag", "form_tag with a block");
102: deprecatedMethods.put("update_element_function", "RJS"); // NOI18N
103: deprecatedMethods.put("link_to_image",
104: "link_to(image_tag(..), url)"); // NOI18N
105: deprecatedMethods.put("link_image_to",
106: "link_to(image_tag(..), url)"); // NOI18N
107: deprecatedMethods.put("human_size", "number_to_human_size"); // NOI18N
108: deprecatedMethods.put("post_format",
109: "respond_to or request.format");
110: deprecatedMethods.put("formatted_post?",
111: "respond_to or request.format");
112: deprecatedMethods.put("xml_post?",
113: "respond_to or request.format");
114: deprecatedMethods.put("yaml_post?",
115: "respond_to or request.format");
116: deprecatedMethods.put("render_text", "render :text => ..."); // NOI18N
117: deprecatedMethods.put("render_template",
118: "render :template => ..."); // NOI18N
119: // TODO - the above list for render_X was not exhaustive - look up the API and complete it!
120: // TODO url_for(:symbol, *args), redirect_to(:symbol, *args)
121: // TODO components
122: // TODO *association*_count
123: }
124:
125: public RailsDeprecations() {
126: }
127:
128: public boolean appliesTo(CompilationInfo info) {
129: // Only perform these checks in Rails projects
130: Project project = FileOwnerQuery.getOwner(info.getFileObject());
131: // Ugly!!
132: if (project == null
133: || project.getClass().getName().indexOf("RailsProject") == -1) { // NOI18N
134: return false;
135: }
136:
137: return true;
138: }
139:
140: public Set<Integer> getKinds() {
141: return Collections.singleton(NodeTypes.ROOTNODE);
142: }
143:
144: public void run(RuleContext context, List<Description> result) {
145: Node root = context.node;
146: CompilationInfo info = context.compilationInfo;
147: AstPath path = context.path;
148:
149: if (root == null) {
150: return;
151: }
152:
153: // This rule should only be called on the root node itself
154: assert path.leaf() == root;
155:
156: scan(info, root, result);
157: }
158:
159: public void cancel() {
160: // Does nothing
161: }
162:
163: public String getId() {
164: return "Rails_Deprecations"; // NOI18N
165: }
166:
167: public String getDisplayName() {
168: return NbBundle.getMessage(RailsDeprecations.class,
169: "RailsDeprecation");
170: }
171:
172: public String getDescription() {
173: return NbBundle.getMessage(RailsDeprecations.class,
174: "RailsDeprecationDesc");
175: }
176:
177: private void scan(CompilationInfo info, Node node,
178: List<Description> result) {
179: // Look for use of deprecated fields
180: if (node.nodeId == NodeTypes.INSTVARNODE
181: || node.nodeId == NodeTypes.INSTASGNNODE) {
182: String name = ((INameNode) node).getName();
183:
184: // Skip matches in _test files, since the standard code generator still
185: // spits out code which violates the deprecations
186: // (such as @request = ActionController::TestRequest.new )
187: if (deprecatedFields.contains(name)
188: && !info.getFileObject().getName()
189: .endsWith("_test")) { // NOI18N
190: // Add a warning - you're using a deprecated field. Use the
191: // method/attribute instead!
192: String message = NbBundle
193: .getMessage(RailsDeprecations.class,
194: "DeprecatedRailsField", name, name
195: .substring(1));
196: addFix(info, node, result, message);
197: }
198: } else if (AstUtilities.isCall(node)) {
199: String name = ((INameNode) node).getName();
200:
201: if (deprecatedMethods.containsKey(name)) {
202: // #121418: render_template is not just a deprecated Rails method,
203: // it's also an RSpec method
204: if ("render_template".equals(name)) { // NOI18N
205: // Filter from RSpec modules
206: if (info.getFileObject().getName()
207: .endsWith("_spec")) { // NOI18N
208: return;
209: }
210: }
211:
212: // find_all is not only a deprecated Rails active record method,
213: // it's also a common method on Enumerable! Only warn about
214: // this when you're calling it as a static method!
215: // (It would be better to check the actual types here and
216: // make sure that the class on the left is actually a model,
217: // but that's costly and much less likely to be a problem
218: if (name.startsWith("find_")) { // NOI18N
219: if (node.nodeId == NodeTypes.CALLNODE) {
220: Node receiver = ((CallNode) node)
221: .getReceiverNode();
222: if (receiver.nodeId != NodeTypes.CONSTNODE
223: && receiver.nodeId != NodeTypes.COLON2NODE) {
224: return;
225: }
226: }
227: }
228:
229: // Add a warning - you're using a deprecated field. Use the
230: // method/attribute instead!
231: String message = NbBundle.getMessage(
232: RailsDeprecations.class,
233: "DeprecatedRailsMethodUse", name,
234: deprecatedMethods.get(name));
235: addFix(info, node, result, message);
236: }
237: }
238:
239: @SuppressWarnings(value="unchecked")
240: List<Node> list = node.childNodes();
241:
242: for (Node child : list) {
243: scan(info, child, result);
244: }
245: }
246:
247: private void addFix(CompilationInfo info, Node node,
248: List<Description> result, String displayName) {
249: OffsetRange range = AstUtilities.getNameRange(node);
250:
251: range = LexUtilities.getLexerOffsets(info, range);
252: if (range != OffsetRange.NONE) {
253: Description desc = new Description(this , displayName, info
254: .getFileObject(), range, Collections
255: .<Fix> emptyList(), 100);
256: result.add(desc);
257: }
258: }
259:
260: public boolean getDefaultEnabled() {
261: return true;
262: }
263:
264: public HintSeverity getDefaultSeverity() {
265: return HintSeverity.WARNING;
266: }
267:
268: public boolean showInTasklist() {
269: return true;
270: }
271:
272: public JComponent getCustomizer(Preferences node) {
273: return null;
274: }
275: }
|