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.javascript.hints;
029:
030: import java.util.Collections;
031: import org.netbeans.modules.gsf.api.OffsetRange;
032: import org.netbeans.editor.BaseDocument;
033: import org.netbeans.editor.Utilities;
034: import java.util.Map;
035: import org.netbeans.modules.gsf.api.CompilationInfo;
036: import java.util.ArrayList;
037: import java.util.Comparator;
038: import java.util.HashMap;
039: import java.util.HashSet;
040: import java.util.List;
041: import java.util.Map;
042: import java.util.Set;
043: import java.util.prefs.Preferences;
044: import javax.swing.text.Document;
045: import org.mozilla.javascript.Node;
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.junit.NbTestCase;
051: import org.netbeans.modules.javascript.editing.AstUtilities;
052: import org.netbeans.modules.javascript.editing.JsTestBase;
053: import org.netbeans.modules.javascript.hints.options.HintsSettings;
054: import org.netbeans.modules.javascript.hints.spi.AstRule;
055: import org.netbeans.modules.javascript.hints.infrastructure.JsHintsProvider;
056: import org.netbeans.modules.javascript.hints.infrastructure.RulesManager;
057: import org.netbeans.modules.javascript.hints.spi.ErrorRule;
058: import org.netbeans.modules.javascript.hints.spi.HintSeverity;
059: import org.netbeans.modules.javascript.hints.spi.Rule;
060: import org.netbeans.modules.javascript.hints.spi.SelectionRule;
061: import org.netbeans.modules.javascript.hints.spi.UserConfigurableRule;
062: import org.netbeans.spi.editor.hints.ErrorDescription;
063: import org.netbeans.spi.editor.hints.Fix;
064: import org.netbeans.spi.editor.hints.LazyFixList;
065: import org.openide.filesystems.FileObject;
066: import org.openide.filesystems.FileUtil;
067:
068: /**
069: * Common utility methods for testing a hint
070: *
071: * @author Tor Norbye
072: */
073: public abstract class HintTestBase extends JsTestBase {
074:
075: public HintTestBase(String testName) {
076: super (testName);
077: }
078:
079: private static final String[] JRUBY_BIG_FILES = {
080: // Biggest files in the standard library
081: "lib/ruby/1.8/drb/drb.rb",
082: "lib/ruby/1.8/rdoc/parsers/parse_rb.rb",
083: "lib/ruby/1.8/rdoc/parsers/parse_f95.rb",
084: "lib/ruby/1.8/net/http.rb",
085: "lib/ruby/1.8/cgi.rb",
086: "lib/ruby/1.8/net/imap.rb",
087: // Biggest files in Rails
088: "lib/ruby/gems/1.8/gems/activerecord-1.15.5/test/associations_test.rb",
089: "lib/ruby/gems/1.8/gems/actionmailer-1.3.5/lib/action_mailer/vendor/text/format.rb",
090: "lib/ruby/gems/1.8/gems/actionpack-1.13.5/test/controller/routing_test.rb",
091: "lib/ruby/gems/1.8/gems/activerecord-1.15.5/lib/active_record/associations.rb",
092: "lib/ruby/gems/1.8/gems/activerecord-1.15.5/lib/active_record/base.rb",
093: "lib/ruby/gems/1.8/gems/actionpack-1.13.5/test/template/date_helper_test.rb", };
094:
095: // protected List<FileObject> getBigSourceFiles() {
096: // FileObject jruby = TestUtil.getXTestJRubyHomeFO();
097: //
098: // List<FileObject> files = new ArrayList<FileObject>();
099: // for (String relative : JRUBY_BIG_FILES) {
100: // FileObject f = jruby.getFileObject(relative);
101: // assertNotNull(relative, f);
102: // files.add(f);
103: // }
104: //
105: // return files;
106: // }
107:
108: private String annotate(BaseDocument doc,
109: List<ErrorDescription> result, int caretOffset)
110: throws Exception {
111: Map<OffsetRange, List<ErrorDescription>> posToDesc = new HashMap<OffsetRange, List<ErrorDescription>>();
112: Set<OffsetRange> ranges = new HashSet<OffsetRange>();
113: for (ErrorDescription desc : result) {
114: int start = desc.getRange().getBegin().getOffset();
115: int end = desc.getRange().getEnd().getOffset();
116: OffsetRange range = new OffsetRange(start, end);
117: List<ErrorDescription> l = posToDesc.get(range);
118: if (l == null) {
119: l = new ArrayList<ErrorDescription>();
120: posToDesc.put(range, l);
121: }
122: l.add(desc);
123: ranges.add(range);
124: }
125: StringBuilder sb = new StringBuilder();
126: String text = doc.getText(0, doc.getLength());
127: Map<Integer, OffsetRange> starts = new HashMap<Integer, OffsetRange>(
128: 100);
129: Map<Integer, OffsetRange> ends = new HashMap<Integer, OffsetRange>(
130: 100);
131: for (OffsetRange range : ranges) {
132: starts.put(range.getStart(), range);
133: ends.put(range.getEnd(), range);
134: }
135:
136: int index = 0;
137: int length = text.length();
138: while (index < length) {
139: int lineStart = Utilities.getRowStart(doc, index);
140: int lineEnd = Utilities.getRowEnd(doc, index);
141: OffsetRange lineRange = new OffsetRange(lineStart, lineEnd);
142: boolean skipLine = true;
143: for (OffsetRange range : ranges) {
144: if (lineRange.containsInclusive(range.getStart())
145: || lineRange.containsInclusive(range.getEnd())) {
146: skipLine = false;
147: }
148: }
149: if (!skipLine) {
150: List<ErrorDescription> descsOnLine = null;
151: int underlineStart = -1;
152: int underlineEnd = -1;
153: for (int i = lineStart; i <= lineEnd; i++) {
154: if (i == caretOffset) {
155: sb.append("^");
156: }
157: if (starts.containsKey(i)) {
158: if (descsOnLine == null) {
159: descsOnLine = new ArrayList<ErrorDescription>();
160: }
161: underlineStart = i - lineStart;
162: OffsetRange range = starts.get(i);
163: if (posToDesc.get(range) != null) {
164: for (ErrorDescription desc : posToDesc
165: .get(range)) {
166: descsOnLine.add(desc);
167: }
168: }
169: }
170: if (ends.containsKey(i)) {
171: underlineEnd = i - lineStart;
172: }
173: sb.append(text.charAt(i));
174: }
175: if (underlineStart != -1) {
176: for (int i = 0; i < underlineStart; i++) {
177: sb.append(" ");
178: }
179: for (int i = underlineStart; i < underlineEnd; i++) {
180: sb.append("-");
181: }
182: sb.append("\n");
183: }
184: if (descsOnLine != null) {
185: Collections.sort(descsOnLine,
186: new Comparator<ErrorDescription>() {
187: public int compare(
188: ErrorDescription arg0,
189: ErrorDescription arg1) {
190: return arg0
191: .getDescription()
192: .compareTo(
193: arg1
194: .getDescription());
195: }
196: });
197: for (ErrorDescription desc : descsOnLine) {
198: sb.append("HINT:");
199: sb.append(desc.getDescription());
200: sb.append("\n");
201: LazyFixList list = desc.getFixes();
202: if (list != null) {
203: List<Fix> fixes = list.getFixes();
204: if (fixes != null) {
205: for (Fix fix : fixes) {
206: sb.append("FIX:");
207: sb.append(fix.getText());
208: sb.append("\n");
209: }
210: }
211: }
212: }
213: }
214: }
215: index = lineEnd + 1;
216: }
217:
218: return sb.toString();
219: }
220:
221: protected boolean parseErrorsOk;
222:
223: protected ComputedHints getHints(NbTestCase test, Rule hint,
224: String relFilePath, FileObject fileObject, String caretLine)
225: throws Exception {
226: assert relFilePath == null || fileObject == null;
227: UserConfigurableRule ucr = null;
228: if (hint instanceof UserConfigurableRule) {
229: ucr = (UserConfigurableRule) hint;
230: }
231:
232: // Make sure the hint is enabled
233: if (ucr != null && !HintsSettings.isEnabled(ucr)) {
234: Preferences p = RulesManager.getInstance().getPreferences(
235: ucr, HintsSettings.getCurrentProfileId());
236: HintsSettings.setEnabled(p, true);
237: }
238:
239: CompilationInfo info = fileObject != null ? getInfo(fileObject)
240: : getInfo(relFilePath);
241: Node root = AstUtilities.getRoot(info);
242: if (root == null && !(hint instanceof ErrorRule)) { // only expect testcase source errors in error tests
243: if (parseErrorsOk) {
244: List<ErrorDescription> result = new ArrayList<ErrorDescription>();
245: int caretOffset = 0;
246: return new ComputedHints(info, result, caretOffset);
247: }
248: assertNotNull("Unexpected parse error in test case "
249: + FileUtil.getFileDisplayName(info.getFileObject())
250: + "\nErrors = " + info.getErrors(), root);
251: }
252:
253: String text = info.getText();
254:
255: int caretOffset = -1;
256: if (caretLine != null) {
257: int caretDelta = caretLine.indexOf("^");
258: assertTrue(caretDelta != -1);
259: caretLine = caretLine.substring(0, caretDelta)
260: + caretLine.substring(caretDelta + 1);
261: int lineOffset = text.indexOf(caretLine);
262: assertTrue("NOT FOUND: " + info.getFileObject().getName()
263: + ":" + caretLine, lineOffset != -1);
264:
265: caretOffset = lineOffset + caretDelta;
266: }
267:
268: JsHintsProvider provider = new JsHintsProvider();
269:
270: List<ErrorDescription> result = new ArrayList<ErrorDescription>();
271: if (hint instanceof ErrorRule) {
272: // It's an error!
273: // Create a hint registry which contains ONLY our hint (so other registered
274: // hints don't interfere with the test)
275: Map<String, List<ErrorRule>> testHints = new HashMap<String, List<ErrorRule>>();
276: if (hint.appliesTo(info)) {
277: ErrorRule errorRule = (ErrorRule) hint;
278: for (String key : errorRule.getCodes()) {
279: testHints.put(key, Collections
280: .singletonList(errorRule));
281: }
282: }
283: provider.setTestingHints(null, null, testHints, null);
284: provider.computeErrors(info, result);
285: } else if (hint instanceof SelectionRule) {
286: SelectionRule rule = (SelectionRule) hint;
287: List<SelectionRule> testHints = new ArrayList<SelectionRule>();
288: testHints.add(rule);
289:
290: provider.setTestingHints(null, null, null, testHints);
291:
292: if (caretLine != null) {
293: int start = text.indexOf(caretLine);
294: int end = start + caretLine.length();
295: provider
296: .computeSelectionHints(info, result, start, end);
297: }
298: } else {
299: assert hint instanceof AstRule && ucr != null;
300: AstRule astRule = (AstRule) hint;
301: // Create a hint registry which contains ONLY our hint (so other registered
302: // hints don't interfere with the test)
303: Map<Integer, List<AstRule>> testHints = new HashMap<Integer, List<AstRule>>();
304: if (hint.appliesTo(info)) {
305: for (int nodeId : astRule.getKinds()) {
306: testHints.put(nodeId, Collections
307: .singletonList(astRule));
308: }
309: }
310: if (RulesManager.getInstance().getSeverity(ucr) == HintSeverity.CURRENT_LINE_WARNING) {
311: provider.setTestingHints(null, testHints, null, null);
312: provider.computeSuggestions(info, result, caretOffset);
313: } else {
314: provider.setTestingHints(testHints, null, null, null);
315: provider.computeHints(info, result);
316: }
317: }
318:
319: return new ComputedHints(info, result, caretOffset);
320: }
321:
322: // protected void assertNoJRubyMatches(Rule hint, Set<String> exceptions) throws Exception {
323: // List<FileObject> files = findJRubyRubyFiles();
324: // assertTrue(files.size() > 0);
325: //
326: // Set<String> fails = new HashSet<String>();
327: // for (FileObject fileObject : files) {
328: // ComputedHints r = getHints(this, hint, null, fileObject, null);
329: // CompilationInfo info = r.info;
330: // List<ErrorDescription> result = r.hints;
331: // int caretOffset = r.caretOffset;
332: // if (hint.getDefaultSeverity() == HintSeverity.CURRENT_LINE_WARNING && hint instanceof AstRule) {
333: // result = new ArrayList<ErrorDescription>(result);
334: // Set<Integer> nodeTypes = ((AstRule)hint).getKinds();
335: // Node root = AstUtilities.getRoot(info);
336: // List<Node> nodes = new ArrayList<Node>();
337: // int[] nodeIds = new int[nodeTypes.size()];
338: // int index = 0;
339: // for (int id : nodeTypes) {
340: // nodeIds[index++] = id;
341: // }
342: // AstUtilities.addNodesByType(root, nodeIds, nodes);
343: // BaseDocument doc = (BaseDocument) info.getDocument();
344: // for (Node n : nodes) {
345: // int start = AstUtilities.getRange(n).getStart();
346: // int lineStart = Utilities.getRowFirstNonWhite(doc, start);
347: // int lineEnd = Utilities.getRowEnd(doc, start);
348: // String first = doc.getText(lineStart, start-lineStart);
349: // String last = doc.getText(start, lineEnd-start);
350: // if (first.indexOf("^") == -1 && last.indexOf("^") == -1) {
351: // String caretLine = first + "^" + last;
352: // ComputedHints r2 = getHints(this, hint, null, fileObject, caretLine);
353: // result.addAll(r.hints);
354: // }
355: // }
356: // }
357: //
358: // String annotatedSource = annotate((BaseDocument)info.getDocument(), result, caretOffset);
359: //
360: // if (annotatedSource.length() > 0) {
361: // // Check if there's an exception
362: // String name = fileObject.getNameExt();
363: // if (exceptions.contains(name)) {
364: // continue;
365: // }
366: //
367: // fails.add(fileObject.getNameExt());
368: // }
369: // }
370: //
371: // assertTrue(fails.toString(), fails.size() == 0);
372: // }
373:
374: // TODO - rename to "checkHints"
375: protected void findHints(NbTestCase test, Rule hint,
376: String relFilePath, String caretLine) throws Exception {
377: findHints(test, hint, relFilePath, null, caretLine);
378: }
379:
380: protected void findHints(Rule hint, String relFilePath,
381: String selStartLine, String selEndLine) throws Exception {
382: FileObject fo = getTestFile(relFilePath);
383: String text = read(fo);
384:
385: assert selStartLine != null;
386: assert selEndLine != null;
387:
388: int selStartOffset = -1;
389: int lineDelta = selStartLine.indexOf("^");
390: assertTrue(lineDelta != -1);
391: selStartLine = selStartLine.substring(0, lineDelta)
392: + selStartLine.substring(lineDelta + 1);
393: int lineOffset = text.indexOf(selStartLine);
394: assertTrue(lineOffset != -1);
395:
396: selStartOffset = lineOffset + lineDelta;
397:
398: int selEndOffset = -1;
399: lineDelta = selEndLine.indexOf("^");
400: assertTrue(lineDelta != -1);
401: selEndLine = selEndLine.substring(0, lineDelta)
402: + selEndLine.substring(lineDelta + 1);
403: lineOffset = text.indexOf(selEndLine);
404: assertTrue(lineOffset != -1);
405:
406: selEndOffset = lineOffset + lineDelta;
407:
408: String caretLine = text.substring(selStartOffset, selEndOffset)
409: + "^";
410:
411: findHints(this , hint, relFilePath, caretLine);
412: }
413:
414: // TODO - rename to "checkHints"
415: protected void findHints(NbTestCase test, Rule hint,
416: FileObject fileObject, String caretLine) throws Exception {
417: findHints(test, hint, null, fileObject, caretLine);
418: }
419:
420: protected String getGoldenFileSuffix() {
421: return "";
422: }
423:
424: // TODO - rename to "checkHints"
425: protected void findHints(NbTestCase test, Rule hint,
426: String relFilePath, FileObject fileObject, String caretLine)
427: throws Exception {
428: ComputedHints r = getHints(test, hint, relFilePath, fileObject,
429: caretLine);
430: CompilationInfo info = r.info;
431: List<ErrorDescription> result = r.hints;
432: int caretOffset = r.caretOffset;
433:
434: String annotatedSource = annotate((BaseDocument) info
435: .getDocument(), result, caretOffset);
436:
437: if (fileObject != null) {
438: assertDescriptionMatches(fileObject, annotatedSource, true,
439: getGoldenFileSuffix() + ".hints");
440: } else {
441: assertDescriptionMatches(relFilePath, annotatedSource,
442: true, getGoldenFileSuffix() + ".hints");
443: }
444: }
445:
446: protected void applyHint(NbTestCase test, Rule hint,
447: String relFilePath, String selStartLine, String selEndLine,
448: String fixDesc) throws Exception {
449: FileObject fo = getTestFile(relFilePath);
450: String text = read(fo);
451:
452: assert selStartLine != null;
453: assert selEndLine != null;
454:
455: int selStartOffset = -1;
456: int lineDelta = selStartLine.indexOf("^");
457: assertTrue(lineDelta != -1);
458: selStartLine = selStartLine.substring(0, lineDelta)
459: + selStartLine.substring(lineDelta + 1);
460: int lineOffset = text.indexOf(selStartLine);
461: assertTrue(lineOffset != -1);
462:
463: selStartOffset = lineOffset + lineDelta;
464:
465: int selEndOffset = -1;
466: lineDelta = selEndLine.indexOf("^");
467: assertTrue(lineDelta != -1);
468: selEndLine = selEndLine.substring(0, lineDelta)
469: + selEndLine.substring(lineDelta + 1);
470: lineOffset = text.indexOf(selEndLine);
471: assertTrue(lineOffset != -1);
472:
473: selEndOffset = lineOffset + lineDelta;
474:
475: String caretLine = text.substring(selStartOffset, selEndOffset)
476: + "^";
477:
478: applyHint(test, hint, relFilePath, caretLine, fixDesc);
479: }
480:
481: protected void applyHint(NbTestCase test, Rule hint,
482: String relFilePath, String caretLine, String fixDesc)
483: throws Exception {
484: ComputedHints r = getHints(test, hint, relFilePath, null,
485: caretLine);
486: CompilationInfo info = r.info;
487:
488: Fix fix = findApplicableFix(r, fixDesc);
489: assertNotNull(fix);
490:
491: fix.implement();
492:
493: Document doc = info.getDocument();
494: String fixed = doc.getText(0, doc.getLength());
495:
496: assertDescriptionMatches(relFilePath, fixed, true, ".fixed");
497: }
498:
499: // public void ensureRegistered(AstRule hint) throws Exception {
500: // Map<Integer, List<AstRule>> hints = RulesManager.getInstance().getHints();
501: // Set<Integer> kinds = hint.getKinds();
502: // for (int nodeType : kinds) {
503: // List<AstRule> rules = hints.get(nodeType);
504: // assertNotNull(rules);
505: // boolean found = false;
506: // for (AstRule rule : rules) {
507: // if (rule instanceof BlockVarReuse) {
508: // found = true;
509: // break;
510: // }
511: // }
512: //
513: // assertTrue(found);
514: // }
515: // }
516:
517: private Fix findApplicableFix(ComputedHints r, String text) {
518: boolean substringMatch = true;
519: if (text.endsWith("\n")) {
520: text = text.substring(0, text.length() - 1);
521: substringMatch = false;
522: }
523: int caretOffset = r.caretOffset;
524: for (ErrorDescription desc : r.hints) {
525: int start = desc.getRange().getBegin().getOffset();
526: int end = desc.getRange().getEnd().getOffset();
527: OffsetRange range = new OffsetRange(start, end);
528: if (range.containsInclusive(caretOffset)
529: || caretOffset == range.getEnd() + 1) { // special case for wrong JRuby offsets
530: // Optionally make sure the text is the one we're after such that
531: // tests can disambiguate among multiple fixes
532: LazyFixList list = desc.getFixes();
533: assertNotNull(list);
534: for (Fix fix : list.getFixes()) {
535: if (text == null
536: || (substringMatch && fix.getText()
537: .indexOf(text) != -1)
538: || (!substringMatch && fix.getText()
539: .equals(text))) {
540: return fix;
541: }
542: }
543: }
544: }
545:
546: return null;
547: }
548:
549: private static class ComputedHints {
550: ComputedHints(CompilationInfo info,
551: List<ErrorDescription> hints, int caretOffset) {
552: this .info = info;
553: this .hints = hints;
554: this .caretOffset = caretOffset;
555: }
556:
557: CompilationInfo info;
558: List<ErrorDescription> hints;
559: int caretOffset;
560: }
561: }
|