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 2008 Sun Microsystems, Inc.
038: */
039:
040: package org.netbeans.modules.ruby.hints;
041:
042: import java.util.ArrayList;
043: import java.util.Collections;
044: import java.util.List;
045: import java.util.Set;
046: import java.util.prefs.Preferences;
047: import javax.swing.JComponent;
048: import javax.swing.text.BadLocationException;
049: import org.jruby.ast.Node;
050: import org.jruby.ast.NodeTypes;
051: import org.jruby.ast.WhenNode;
052: import org.netbeans.modules.gsf.api.CompilationInfo;
053: import org.netbeans.modules.gsf.api.OffsetRange;
054: import org.netbeans.api.lexer.Token;
055: import org.netbeans.api.lexer.TokenId;
056: import org.netbeans.api.lexer.TokenSequence;
057: import org.netbeans.editor.BaseDocument;
058: import org.netbeans.editor.Utilities;
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.hints.spi.PreviewableFix;
065: import org.netbeans.modules.ruby.hints.spi.RuleContext;
066: import org.netbeans.modules.ruby.lexer.LexUtilities;
067: import org.netbeans.modules.ruby.lexer.RubyTokenId;
068: import org.openide.util.Exceptions;
069: import org.openide.util.NbBundle;
070:
071: /**
072: * Convert "when foo : bar" to "when foo then bar" as required by Ruby 1.9
073: * @todo This is not particular to when/case - the following is also illegal in 1.9
074: * if true: puts "True!"
075: * end
076: * @todo Add a reference link to the wiki page with more information about the quickfix and
077: * references
078: *
079: * @author Tor Norbye
080: */
081: public class ColonToThen implements AstRule {
082:
083: public Set<Integer> getKinds() {
084: return Collections.singleton(NodeTypes.WHENNODE);
085: }
086:
087: public void run(RuleContext context, List<Description> result) {
088: Node node = context.node;
089: CompilationInfo info = context.compilationInfo;
090:
091: WhenNode when = (WhenNode) node;
092: Node body = when.getBodyNode();
093: if (body == null) {
094: return;
095: }
096:
097: // See if the child contains
098: BaseDocument doc = context.doc;
099: try {
100: // (1) make sure the body is on the same line as the when, and
101: // (2) the separator is ":", not "then" or something else
102: int whenStart = when.getPosition().getStartOffset();
103: int bodyStart = body.getPosition().getStartOffset();
104: if (Utilities.getRowEnd(doc, bodyStart) != Utilities
105: .getRowEnd(doc, whenStart)) {
106: return;
107: }
108:
109: int offset = -1;
110: try {
111: // Check tokens - look for ":" as opposed to then
112: doc.readLock();
113: TokenSequence<? extends RubyTokenId> ts = LexUtilities
114: .getRubyTokenSequence(doc, bodyStart);
115: if (ts == null) {
116: return;
117: }
118: ts.move(bodyStart);
119: while (ts.movePrevious()) {
120: Token<? extends RubyTokenId> token = ts.token();
121: TokenId id = token.id();
122: if (id == RubyTokenId.WHITESPACE) {
123: continue;
124: } else if (id == RubyTokenId.NONUNARY_OP) {
125: String s = token.text().toString();
126: if (":".equals(s)) {
127: offset = ts.offset();
128: break;
129: } else {
130: return;
131: }
132: } else {
133: return;
134: }
135: }
136: } finally {
137: doc.readUnlock();
138: }
139:
140: OffsetRange range = new OffsetRange(offset, offset + 1);
141: String displayName = NbBundle.getMessage(ColonToThen.class,
142: "ColonToThenGutter");
143: List<Fix> fixes = new ArrayList<Fix>(2);
144: fixes.add(new ColonFix(doc, offset, INSERT_THEN));
145: fixes.add(new ColonFix(doc, offset, INSERT_SEMICOLON));
146: fixes.add(new ColonFix(doc, offset, INSERT_NEWLINE));
147: Description desc = new Description(this , displayName, info
148: .getFileObject(), range, fixes, 150);
149: result.add(desc);
150: } catch (BadLocationException ex) {
151: Exceptions.printStackTrace(ex);
152: }
153: }
154:
155: public String getId() {
156: return "ColonToThen"; // NOI18N
157: }
158:
159: public String getDescription() {
160: return NbBundle
161: .getMessage(ColonToThen.class, "ColonToThenDesc");
162: }
163:
164: public boolean getDefaultEnabled() {
165: return true;
166: }
167:
168: public JComponent getCustomizer(Preferences node) {
169: return null;
170: }
171:
172: public boolean appliesTo(CompilationInfo info) {
173: return true;
174: }
175:
176: public String getDisplayName() {
177: return NbBundle.getMessage(ColonToThen.class, "ColonToThen");
178: }
179:
180: public boolean showInTasklist() {
181: return true;
182: }
183:
184: public HintSeverity getDefaultSeverity() {
185: return HintSeverity.WARNING;
186: }
187:
188: // ColonFix possibilities
189: private static final int INSERT_THEN = 1;
190: private static final int INSERT_NEWLINE = 2;
191: private static final int INSERT_SEMICOLON = 3;
192:
193: private static class ColonFix implements PreviewableFix {
194: private BaseDocument doc;
195: private int offset;
196: private int mode;
197:
198: public ColonFix(BaseDocument doc, int offset, int mode) {
199: this .doc = doc;
200: this .offset = offset;
201: this .mode = mode;
202: }
203:
204: public String getDescription() {
205: switch (mode) {
206: case INSERT_SEMICOLON:
207: return NbBundle.getMessage(Deprecations.class,
208: "ColonToThenFixSemi");
209: case INSERT_THEN:
210: return NbBundle.getMessage(Deprecations.class,
211: "ColonToThenFix");
212: case INSERT_NEWLINE:
213: default:
214: return NbBundle.getMessage(Deprecations.class,
215: "ColonToThenFixNewline");
216: }
217: }
218:
219: public void implement() throws Exception {
220: getEditList().apply();
221: }
222:
223: public EditList getEditList() throws Exception {
224: EditList list = new EditList(doc);
225: switch (mode) {
226: case INSERT_NEWLINE:
227: list.replace(offset, 1, "\n", true, 0); // NOI18N
228: break;
229: case INSERT_THEN: {
230: String s = doc.getText(offset, 3);
231: StringBuilder sb = new StringBuilder();
232: if (!Character.isWhitespace(doc.getText(offset - 1, 1)
233: .charAt(0))) {
234: sb.append(' ');
235: }
236: sb.append("then"); // NOI18N
237: if (offset < doc.getLength() - 2) {
238: if (!Character.isWhitespace(doc.getText(offset + 1,
239: 1).charAt(0))) {
240: sb.append(' ');
241: }
242: }
243: list.replace(offset, 1, sb.toString(), false, 0);
244: break;
245: }
246: case INSERT_SEMICOLON:
247: list.replace(offset, 1, ";", false, 0); // NOI18N
248: break;
249: }
250:
251: return list;
252: }
253:
254: public boolean isSafe() {
255: return true;
256: }
257:
258: public boolean isInteractive() {
259: return false;
260: }
261:
262: public boolean canPreview() {
263: return true;
264: }
265: }
266: }
|