EMMA Coverage Report (generated Mon Nov 01 16:48:29 PDT 2010)
[all classes][com.google.caja.ancillary.linter]

COVERAGE SUMMARY FOR SOURCE FILE [LinterTest.java]

nameclass, %method, %block, %line, %
LinterTest.java100% (2/2)100% (22/22)100% (1671/1671)100% (83/83)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class LinterTest100% (1/1)100% (17/17)100% (1616/1616)100% (70/70)
LinterTest (): void 100% (1/1)100% (3/3)100% (2/2)
jobs (Linter$LintJob []): List 100% (1/1)100% (3/3)100% (1/1)
runLinterTest (List, String []): void 100% (1/1)100% (54/54)100% (10/10)
testCatchBlocks (): void 100% (1/1)100% (226/226)100% (7/7)
testDeadCode (): void 100% (1/1)100% (73/73)100% (3/3)
testFunctionScopes (): void 100% (1/1)100% (44/44)100% (2/2)
testIEQuirksScoping (): void 100% (1/1)100% (141/141)100% (5/5)
testIgnoredValue (): void 100% (1/1)100% (111/111)100% (2/2)
testLabels (): void 100% (1/1)100% (105/105)100% (5/5)
testLiveness (): void 100% (1/1)100% (45/45)100% (2/2)
testLoops (): void 100% (1/1)100% (196/196)100% (7/7)
testMisplacedExits (): void 100% (1/1)100% (88/88)100% (4/4)
testMultiplyProvidedSymbols (): void 100% (1/1)100% (69/69)100% (2/2)
testOverrides (): void 100% (1/1)100% (155/155)100% (6/6)
testProvides (): void 100% (1/1)100% (171/171)100% (6/6)
testRedefinition (): void 100% (1/1)100% (30/30)100% (2/2)
testRequires (): void 100% (1/1)100% (102/102)100% (4/4)
     
class LinterTest$LintJobMaker100% (1/1)100% (5/5)100% (55/55)100% (13/13)
LinterTest$LintJobMaker (Block): void 100% (1/1)100% (15/15)100% (6/6)
make (): Linter$LintJob 100% (1/1)100% (16/16)100% (1/1)
withOverrides (String []): LinterTest$LintJobMaker 100% (1/1)100% (8/8)100% (2/2)
withProvides (String []): LinterTest$LintJobMaker 100% (1/1)100% (8/8)100% (2/2)
withRequires (String []): LinterTest$LintJobMaker 100% (1/1)100% (8/8)100% (2/2)

1// Copyright (C) 2008 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14 
15package com.google.caja.ancillary.linter;
16 
17import com.google.caja.lexer.InputSource;
18import com.google.caja.parser.js.Block;
19import com.google.caja.reporting.Message;
20import com.google.caja.reporting.MessageQueue;
21import com.google.caja.reporting.SimpleMessageQueue;
22import com.google.caja.util.CajaTestCase;
23import com.google.caja.util.Lists;
24import com.google.caja.util.MoreAsserts;
25import com.google.caja.util.Sets;
26 
27import java.net.URI;
28import java.util.Arrays;
29import java.util.Collections;
30import java.util.List;
31import java.util.Set;
32 
33public class LinterTest extends CajaTestCase {
34 
35  public final void testProvides() throws Exception {
36    runLinterTest(
37        jobs(new LintJobMaker(js(fromString("var x;"))).make()),
38        "LINT: testProvides:1+1 - 6: Undocumented global x");
39    runLinterTest(
40        jobs(new LintJobMaker(js(fromString("var x = 1;")))
41             .withProvides("x")
42             .make())
43        );
44    runLinterTest(
45        jobs(new LintJobMaker(js(fromString("var x = 1;")))
46             .withProvides("x", "y")
47             .make()),
48        "ERROR: testProvides: @provides y not provided"
49        );
50    runLinterTest(
51        jobs(new LintJobMaker(js(fromString(
52             ""
53             + "var x = 1;\n"
54             + "var y;\n"
55             + "if (Math.random()) { y = 1; }"  // Not always assigned.
56             )))
57             .withProvides("x", "y")
58             .make()),
59        "ERROR: testProvides: @provides y not provided"
60        );
61    runLinterTest(
62        jobs(new LintJobMaker(js(fromString("for (var i;;) {}"))).make()),
63        "LINT: testProvides:1+6 - 11: Undocumented global i");
64  }
65 
66  public final void testMultiplyProvidedSymbols() throws Exception {
67    runLinterTest(
68        jobs(new LintJobMaker(js(fromString(
69            "var foo = 0, bar = 1;", new InputSource(URI.create("test:///f1"))
70            )))
71            .withProvides("foo", "bar")
72            .make(),
73            new LintJobMaker(js(fromString(
74            "var bar = 2, baz = 4;", new InputSource(URI.create("test:///f2"))
75            )))
76            .withProvides("bar", "baz")
77            .make()),
78        "ERROR: f2: Another input, f1, already @provides bar");
79  }
80 
81  public final void testRequires() throws Exception {
82    runLinterTest(
83        jobs(new LintJobMaker(js(fromString("x();"))).make()),
84        "ERROR: testRequires:1+1 - 2: Symbol x has not been defined");
85    runLinterTest(
86        jobs(new LintJobMaker(js(fromString("x();")))
87             .withRequires("x")
88             .make())
89        );
90    runLinterTest(
91        jobs(new LintJobMaker(js(fromString("x();")))
92             .withRequires("x", "y")
93             .make()),
94        "ERROR: testRequires: @requires y not used"
95        );
96  }
97 
98  public final void testOverrides() throws Exception {
99    runLinterTest(
100        jobs(new LintJobMaker(js(fromString(
101            "Object = Array;\n")))
102        .make()),
103        "ERROR: testOverrides:1+1 - 7: Invalid assignment to Object");
104    runLinterTest(
105        jobs(new LintJobMaker(js(fromString(
106            "Date.now = function () { return (new Date).getTime(); };\n")))
107        .make()),
108        "ERROR: testOverrides:1+1 - 5: Invalid assignment to Date.now");
109    runLinterTest(
110        jobs(new LintJobMaker(js(fromString(
111            "Date.now = function () { return (new Date).getTime(); };\n")))
112        .withOverrides("Date")
113        .make()));
114    runLinterTest(
115        jobs(new LintJobMaker(js(fromString(
116            "var Date;\n")))
117        .withOverrides("Date")
118        .make()));
119    // No override needed when used as a RHS
120    runLinterTest(
121        jobs(new LintJobMaker(js(fromString(
122            "var data = {}; data[Date] = new Date();\n")))
123        .withProvides("data")
124        .make()));
125  }
126 
127  public final void testFunctionScopes() throws Exception {
128    runLinterTest(
129        jobs(new LintJobMaker(js(fromString(
130            ""
131            + "function p(b, c) {\n"
132            + "  var d;\n"
133            + "  d = r * a - b + c * r;\n"
134            + "  return d;"
135            + "}")))
136            .withProvides("p")
137            .withRequires("r")
138            .make()),
139        "ERROR: testFunctionScopes:3+11 - 12: Symbol a has not been defined");
140  }
141 
142  public final void testRedefinition() throws Exception {
143    runLinterTest(
144        jobs(new LintJobMaker(js(fromString(
145            "(function () { var a = 1; var a = 2; })();"))).make()),
146        ("ERROR: testRedefinition:1+27 - 36: "
147         + "a originally defined at testRedefinition:1+16 - 25"));
148  }
149 
150  public final void testCatchBlocks() throws Exception {
151    runLinterTest(
152        jobs(new LintJobMaker(js(fromString(
153            ""
154            + "try {\n"
155            + "  throw caution();\n"
156            + "} catch (e) {\n"
157            + "  if (e.to === THE_WIND) {\n"
158            + "    panic(e);\n"
159            + "  }\n"
160            + "}")))
161            .withRequires("caution", "THE_WIND")
162            .make()),
163        "ERROR: testCatchBlocks:5+5 - 10: Symbol panic has not been defined");
164    runLinterTest(
165        jobs(new LintJobMaker(js(fromString(
166            ""
167            + "try {\n"
168            + "  throw new Error();\n"
169            + "} catch (e) {\n"
170            + "  // swallow exception\n"
171            + "}")))
172            .withProvides("e")
173            .make()),
174        "ERROR: testCatchBlocks: @provides e not provided");
175    runLinterTest(
176        jobs(new LintJobMaker(js(fromString(
177            ""
178            + "var e;\n"
179            + "try {\n"
180            + "  throw new Error();\n"
181            + "} catch (e) {\n"
182            + "  // swallow exception\n"
183            + "}")))
184            .make()),
185        "LINT: testCatchBlocks:1+1 - 6: Undocumented global e",
186        ("WARNING: testCatchBlocks:4+10 - 11: Declaration of e masks"
187         + " declaration at testCatchBlocks:1+1 - 6"));
188    runLinterTest(
189        jobs(new LintJobMaker(js(fromString(
190            ""
191            + "try {\n"
192            + "  throw new Error();\n"
193            + "} catch (e) {\n"
194            + "  var f = e.stack;\n"
195            + "  print(f);\n"
196            + "}")))
197            .withRequires("print")
198            .make()),
199        "LINT: testCatchBlocks:4+3 - 18: Undocumented global f");
200    runLinterTest(
201        jobs(new LintJobMaker(js(fromString(
202            ""
203            + "function g(e, f) {\n"
204            + "  try {\n"
205            + "    f();\n"
206            + "  } catch (e) {\n"  // does not mask the formal parameter e
207            + "    panic();\n"
208            + "  }\n"
209            + "  return e;\n"
210            + "}")))
211            .withProvides("g")
212            .make()),
213        ("WARNING: testCatchBlocks:4+12 - 13:"
214         + " Declaration of e masks declaration at testCatchBlocks:1+12 - 13"),
215        "ERROR: testCatchBlocks:5+5 - 10: Symbol panic has not been defined");
216    runLinterTest(
217        jobs(new LintJobMaker(js(fromString(
218            ""
219            + "function g(e, f) {\n"
220            + "  try {\n"
221            + "    f();\n"
222            + "  } catch (e) {\n"
223            + "    var e = f(false);\n"
224            + "    return e + 1;\n"
225            + "  }\n"
226            + "  return e;\n"
227            + "}")))
228            .withProvides("g")
229            .make()),
230        ("WARNING: testCatchBlocks:4+12 - 13:"
231         + " Declaration of e masks declaration at testCatchBlocks:1+12 - 13"),
232        ("ERROR: testCatchBlocks:5+5 - 21: e originally defined at"
233         + " testCatchBlocks:1+12 - 13"));
234  }
235 
236  public final void testLoops() throws Exception {
237    runLinterTest(
238        jobs(new LintJobMaker(js(fromString(
239            ""
240            + "(function () {\n"
241            + "  for (var i = 0; i < 10; ++i) { f(i); }\n"
242            + "  for (var i = 10; --i >= 0;) { f(i); }\n"
243            + "})();")))
244            .withRequires("f").make())
245        );
246    runLinterTest(
247        jobs(new LintJobMaker(js(fromString(
248            ""
249            + "(function () {\n"
250            + "  for (var i = 0; i < 10; ++i) {\n"
251            + "    for (var i = 10; --i >= 0;) { f(i); }\n"
252            + "  }\n"
253            + "})();")))
254            .withRequires("f").make()),
255        ("ERROR: testLoops:3+10 - 20:"
256         + " Declaration of i masks declaration at testLoops:2+8 - 17"));
257    runLinterTest(
258        jobs(new LintJobMaker(js(fromString(
259            ""
260            + "(function () {\n"
261            + "  for (var i = 0; i < 10; ++i) { f(i); }\n"
262            + "  return i;"
263            + "})();")))
264            .withRequires("f").make()),
265        ("ERROR: testLoops:3+10 - 11: Usage of i declared at "
266         + "testLoops:2+8 - 17 is out of block scope.")
267        );
268    runLinterTest(
269        jobs(new LintJobMaker(js(fromString(
270            ""
271            + "(function (i) {\n"  // variable i
272            + "  return function (arr) {\n"
273            + "    var sum = 0;\n"
274            + "    for (var i = arr.length; --i >= 0;) {\n"  // i masks formal
275            + "      sum += arr[i];\n"
276            + "    }\n"
277            + "    return sum / i;\n"  // apparent reference to a masked outer
278            + "  };\n"
279            + "})(4);"))).make()),
280        ("ERROR: testLoops:7+18 - 19: Usage of i declared at "
281         + "testLoops:4+10 - 28 is out of block scope.")
282        );
283    runLinterTest(
284        jobs(new LintJobMaker(js(fromString(
285            ""
286            + "(function () {\n"
287            + "  var k;\n"
288            + "  for (k in o);\n"
289            + "})();"))).make()),
290        "ERROR: testLoops:3+13 - 14: Symbol o has not been defined"
291        );
292    runLinterTest(
293        jobs(new LintJobMaker(js(fromString("for (var k in o);"))).make()),
294        "ERROR: testLoops:1+15 - 16: Symbol o has not been defined",
295        "LINT: testLoops:1+6 - 11: Undocumented global k");
296  }
297 
298  public final void testIgnoredValue() throws Exception {
299    runLinterTest(
300        jobs(new LintJobMaker(js(fromString(
301            ""
302            + "var c; f;  \n"  // Line 1
303            + "+n;  \n"  // line 2
304            + "a.b;  \n"  // line 3
305            + "f && g();  f && g;  f() && g;  \n"  // line 4: 1st OK, others not
306            + "a = b, c = d;  \n"  // line 5
307            + "a = b;  \n"  // OK
308            + "m += n;  \n"  // OK
309            + "f();  \n"  // OK
310            + "new Array;  \n"  // line 9
311            + "new Array();  \n"  // line 10
312            + "for (a = b, c = d; !a; ++a, --m, ++c) f;  \n"  // line 11
313            + "++c;  \n"  // OK
314            + "while (1) { 1; }\n"  // line 13.  First allowed, second not
315            + "({ x: 32 });\n"
316            //          1         2         3         4
317            // 1234567890123456789012345678901234567890
318            )))
319            .withRequires("b", "d", "f", "g", "n")
320            .withOverrides("a", "m")
321            .withProvides("c")
322            .make()),
323        "WARNING: testIgnoredValue:1+8 - 9: Operation has no effect",
324        "WARNING: testIgnoredValue:2+1 - 3: Operation has no effect",
325        "WARNING: testIgnoredValue:3+1 - 4: Operation has no effect",
326        "WARNING: testIgnoredValue:4+12 - 18: Operation has no effect",
327        "WARNING: testIgnoredValue:4+21 - 29: Operation has no effect",
328        "WARNING: testIgnoredValue:5+1 - 13: Operation has no effect",
329        "WARNING: testIgnoredValue:9+1 - 10: Operation has no effect",
330        "WARNING: testIgnoredValue:10+1 - 12: Operation has no effect",
331        "WARNING: testIgnoredValue:11+39 - 40: Operation has no effect",
332        "WARNING: testIgnoredValue:13+13 - 14: Operation has no effect",
333        "WARNING: testIgnoredValue:14+1 - 12: Operation has no effect");
334  }
335 
336  public final void testDeadCode() throws Exception {
337    runLinterTest(
338        jobs(new LintJobMaker(js(fromString(
339            ""
340            + "(function () {\n"
341            + "  return\n"
342            + "      foo();\n"
343            + "})();")))
344            .withRequires("foo")
345            .make()),
346        // runLinterTest makes its own message queue so the lint message about
347        // semicolons does not show here.
348        // Linter's main method does use the same message queue though, so
349        // parsing messages will be reported.
350        "WARNING: testDeadCode:3+7 - 12: Code is not reachable");
351    runLinterTest(
352        jobs(new LintJobMaker(js(fromString(
353            ""
354            + "with (o) {\n"
355            // Is reachable even though code within a with block is not
356            // analyzable.  Ignore reachability in with blocks.
357            + "  foo();\n"
358            + "}\n")))
359            .withRequires("o", "foo")
360            .make())
361        );
362  }
363 
364  public final void testLiveness() throws Exception {
365    runLinterTest(
366        jobs(new LintJobMaker(js(fromString(
367            ""
368            + "var a;\n"
369            + "if (Math.random()) {\n"
370            + "  a = new Error();\n"
371            + "}\n"
372            + "throw a;\n"
373            )))
374            .withProvides("a")
375            .make()),
376        ("WARNING: testLiveness:5+7 - 8: Symbol a may be used before"
377         + " being initialized"),
378        "ERROR: testLiveness: @provides a not provided",
379        ("WARNING: testLiveness:5+1 - 8:"
380         + " Uncaught exception thrown during initialization")
381        );
382  }
383 
384  public final void testLabels() throws Exception {
385    runLinterTest(
386        jobs(new LintJobMaker(js(fromString(
387            "foo: for (;;) { foo: for (;;); }"))).make()),
388        ("ERROR: testLabels:1+17 - 31:"
389         + " Label foo nested inside testLabels:1+1 - 33"));
390    // Side-by-side is fine.
391    runLinterTest(
392        jobs(new LintJobMaker(js(fromString(
393            "foo: for (;;); foo: for (;;);"))).make()));
394    // And it's ok if they have the default label
395    runLinterTest(
396        jobs(new LintJobMaker(js(fromString(
397            "for (;;) { for (;;); }"))).make()));
398    // or different labels
399    runLinterTest(
400        jobs(new LintJobMaker(js(fromString(
401            "foo: for (;;) { bar: for (;;); }"))).make()));
402  }
403 
404  public final void testMisplacedExits() throws Exception {
405    runLinterTest(
406        jobs(new LintJobMaker(js(fromString("return;"))).make()),
407        ("ERROR: testMisplacedExits:1+1 - 7:"
408         + " Return does not appear inside a function"));
409    runLinterTest(
410        jobs(new LintJobMaker(js(fromString(
411            "foo: for (;;) { break bar; }"
412            ))).make()),
413        ("ERROR: testMisplacedExits:1+17 - 26:"
414         + " Unmatched break or continue to label bar"));
415    runLinterTest(
416        jobs(new LintJobMaker(js(fromString(
417            "switch (Math.random()) { case 0: continue; }"
418            ))).make()),
419        ("ERROR: testMisplacedExits:1+34 - 42:"
420         + " Unmatched break or continue to label <default>"));
421  }
422 
423  public final void testIEQuirksScoping() throws Exception {
424    runLinterTest(
425        jobs(new LintJobMaker(js(fromString(
426            ""
427            + "var myObject = {\n"
428            + "    foo: function foo() {}\n"
429            + "};\n"
430            )))
431            .withProvides("myObject")
432            .make()),
433        "LINT: testIEQuirksScoping:2+10 - 27: Undocumented global foo");
434    // No problem if done inside a closure.
435    runLinterTest(
436        jobs(new LintJobMaker(js(fromString(
437            ""
438            + "var myObject = (function () {\n"
439            + "  return {\n"
440            + "      foo: function foo() {}\n"
441            + "  };\n"
442            + "})();"
443            )))
444            .withProvides("myObject")
445            .make()));
446    // Unless doing so would conflict with a local variable.
447    // Even if the variable isn't used later, it could still break recursion
448    // within the function.
449    runLinterTest(
450        jobs(new LintJobMaker(js(fromString(
451            ""
452            + "var myObject = (function () {\n"
453            + "  var foo = {\n"
454            + "      foo: function foo() {}\n"
455            + "  };\n"
456            + "  return foo;\n"
457            + "})();"
458            )))
459            .withProvides("myObject")
460            .make()),
461        ("ERROR: testIEQuirksScoping:3+12 - 29: foo originally defined"
462         + " at testIEQuirksScoping:2+3 - 4+4"));
463    // And we report a slightly different message when there's a block scope in
464    // between.
465    runLinterTest(
466        jobs(new LintJobMaker(js(fromString(
467            ""
468            + "var myObject = (function () {\n"
469            + "  var foo;\n"
470            + "  do \n"
471            + "    foo = {\n"
472            + "        foo: function foo() {}\n"
473            + "    };\n"
474            + "  while (false);\n"
475            + "  return foo;\n"
476            + "})();"
477            )))
478            .withProvides("myObject")
479            .make()),
480        ("ERROR: testIEQuirksScoping:5+14 - 31: foo originally defined"
481         + " at testIEQuirksScoping:2+3 - 10"));
482  }
483 
484  // TODO(mikesamuel):
485  // check that function bodies either never return or never complete.
486  // E.g. function f(x, y) { if (x) { return y; } } is missing an else.
487 
488  final static class LintJobMaker {
489    private Block node;
490    private Set<String> provides = Sets.newLinkedHashSet(),
491        requires = Sets.newLinkedHashSet(),
492        overrides = Sets.newLinkedHashSet();
493    LintJobMaker(Block node) {
494      this.node = node;
495    }
496 
497    LintJobMaker withProvides(String... idents) {
498      provides.addAll(Arrays.asList(idents));
499      return this;
500    }
501 
502    LintJobMaker withRequires(String... idents) {
503      requires.addAll(Arrays.asList(idents));
504      return this;
505    }
506 
507    LintJobMaker withOverrides(String... idents) {
508      overrides.addAll(Arrays.asList(idents));
509      return this;
510    }
511 
512    Linter.LintJob make() {
513      return new Linter.LintJob(
514          node.getFilePosition().source(), requires, provides, overrides, node);
515    }
516  }
517 
518  private void runLinterTest(List<Linter.LintJob> inputs, String... messages) {
519    MessageQueue mq = new SimpleMessageQueue();
520    Linter.lint(inputs, new Linter.Environment(Sets.<String>newHashSet()), mq);
521    List<String> actualMessageStrs = Lists.newArrayList();
522    for (Message msg : mq.getMessages()) {
523      actualMessageStrs.add(
524          msg.getMessageLevel().name() + ": " + msg.format(mc));
525    }
526 
527    List<String> goldenMessageStrs = Lists.newArrayList(messages);
528    Collections.sort(actualMessageStrs);
529    Collections.sort(goldenMessageStrs);
530    MoreAsserts.assertListsEqual(goldenMessageStrs, actualMessageStrs);
531  }
532 
533  private List<Linter.LintJob> jobs(Linter.LintJob... jobs) {
534    return Arrays.asList(jobs);
535  }
536}

[all classes][com.google.caja.ancillary.linter]
EMMA 2.0.5312 (C) Vladimir Roubtsov