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.List;
032: import java.util.Set;
033: import org.jruby.ast.Node;
034: import org.jruby.lexer.yacc.ISourcePosition;
035: import org.netbeans.modules.gsf.api.CompilationInfo;
036: import org.netbeans.modules.gsf.api.Error;
037: import org.netbeans.modules.gsf.api.OffsetRange;
038: import org.netbeans.editor.BaseDocument;
039: import org.netbeans.modules.ruby.AstPath;
040: import org.netbeans.modules.ruby.AstUtilities;
041: import org.netbeans.modules.ruby.hints.spi.Description;
042: import org.netbeans.modules.ruby.hints.spi.EditList;
043: import org.netbeans.modules.ruby.hints.spi.ErrorRule;
044: import org.netbeans.modules.ruby.hints.spi.Fix;
045: import org.netbeans.modules.ruby.hints.spi.HintSeverity;
046: import org.netbeans.modules.ruby.hints.spi.PreviewableFix;
047: import org.netbeans.modules.ruby.hints.spi.RuleContext;
048: import org.netbeans.modules.ruby.lexer.LexUtilities;
049: import org.openide.util.NbBundle;
050:
051: /**
052: * Offer to insert missing parentheses for ambiguous parenthesis errors
053: *
054: * @todo Check http://eigenclass.org/hiki/ruby-warnings-SEX-and-stds
055: * In particular, handle the '&' interpreted as arg prefix warning which occurs
056: * a lot in Mephisto code (and probably elsewhere - I googled for an explanation
057: * and instead found -tons- of ruby output logs spitting out this warning - this
058: * and '*' interpreted as argument prefix
059: *
060: * @author Tor Norbye
061: */
062: public class InsertParens implements ErrorRule {
063:
064: public Set<String> getCodes() {
065: // Set<String> s = new HashSet<String>();
066: // s.add("`*' interpreted as argument prefix");
067: // s.add("`&' interpreted as argument prefix");
068: // s.add("parenthesize argument(s) for future version");
069: // return s;
070: return Collections
071: .singleton("parenthesize argument(s) for future version");
072: }
073:
074: public void run(RuleContext context, Error error,
075: List<Description> result) {
076: CompilationInfo info = context.compilationInfo;
077:
078: Node root = AstUtilities.getRoot(info);
079: if (root != null) {
080: int astOffset = error.getStartPosition();
081: AstPath path = new AstPath(root, astOffset);
082: Node node = path.leaf();
083: if (node != null) {
084: OffsetRange range = AstUtilities.getRange(node);
085: Node callNode = null;
086: if (AstUtilities.isCall(path.leafParent())) {
087: callNode = path.leafParent();
088: } else if (path.leafGrandParent() != null
089: && AstUtilities.isCall(path.leafGrandParent())) {
090: callNode = path.leafGrandParent();
091: } else if (AstUtilities.isCall(node)) {
092: callNode = node;
093: }
094: if (callNode != null) {
095: Fix fix = new InsertParenFix(info, callNode);
096: List<Fix> fixList = Collections.singletonList(fix);
097: range = LexUtilities.getLexerOffsets(info, range);
098: if (range != OffsetRange.NONE) {
099: Description desc = new Description(this ,
100: getDisplayName(), info.getFileObject(),
101: range, fixList, 500);
102: result.add(desc);
103: }
104: }
105: }
106: }
107: }
108:
109: public boolean appliesTo(CompilationInfo info) {
110: // Skip for RHTML files for now - isn't implemented properly
111: return info.getFileObject().getMIMEType().equals("text/x-ruby");
112: }
113:
114: public String getDisplayName() {
115: return NbBundle.getMessage(InsertParens.class, "InsertParens");
116: }
117:
118: public HintSeverity getDefaultSeverity() {
119: return HintSeverity.WARNING;
120: }
121:
122: public boolean showInTasklist() {
123: return false;
124: }
125:
126: private static class InsertParenFix implements PreviewableFix {
127:
128: private final CompilationInfo info;
129: private final Node node;
130:
131: InsertParenFix(CompilationInfo info, Node node) {
132: this .info = info;
133: this .node = node;
134: }
135:
136: public String getDescription() {
137: return NbBundle.getMessage(InsertParens.class,
138: "InsertParenFix");
139: }
140:
141: public void implement() throws Exception {
142: getEditList().apply();
143: }
144:
145: public EditList getEditList() throws Exception {
146: ISourcePosition pos = node.getPosition();
147: int endOffset = pos.getEndOffset();
148: BaseDocument doc = (BaseDocument) info.getDocument();
149: EditList edits = new EditList(doc);
150: if (endOffset > doc.getLength()) {
151: return edits;
152: }
153:
154: // Insert parentheses
155: assert AstUtilities.isCall(node);
156: OffsetRange astRange = AstUtilities.getCallRange(node);
157: OffsetRange range = LexUtilities.getLexerOffsets(info,
158: astRange);
159: if (range == OffsetRange.NONE) {
160: return edits;
161: }
162: int insertPos = range.getEnd();
163: // Check if I should remove a space; e.g. replace "foo arg" with "foo(arg"
164: if (Character.isWhitespace(doc.getText(insertPos, 1)
165: .charAt(0))) {
166: edits.replace(insertPos, 1, "(", false, 0); // NOI18N
167: } else {
168: edits.replace(insertPos, 0, "(", false, 0); // NOI18N
169: endOffset++;
170: }
171:
172: // Insert )
173: // Look backwards from endOffset to see the first nonspace char and insert
174: // after that
175: for (int i = endOffset - 1; i >= 0; i--) {
176: // TODO - find more efficient doc iterator!
177: char c = doc.getText(i, 1).charAt(0);
178: if (Character.isWhitespace(c)) {
179: continue;
180: } else {
181: endOffset = i + 1;
182: break;
183: }
184: }
185: edits.replace(endOffset, 0, ")", false, 1); // NOI18N
186:
187: return edits;
188: }
189:
190: public boolean isSafe() {
191: return true;
192: }
193:
194: public boolean isInteractive() {
195: return false;
196: }
197:
198: public boolean canPreview() {
199: return true;
200: }
201: }
202: }
|