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

COVERAGE SUMMARY FOR SOURCE FILE [AlphaRenamingRewriter.java]

nameclass, %method, %block, %line, %
AlphaRenamingRewriter.java100% (11/11)100% (24/24)92%  (881/953)86%  (128.7/149)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class AlphaRenamingRewriter100% (1/1)100% (4/4)95%  (105/111)89%  (8/9)
AlphaRenamingRewriter (MessageQueue, NameContext): void 100% (1/1)100% (89/89)100% (4/4)
access$000 (AlphaRenamingRewriter): Map 100% (1/1)100% (3/3)100% (1/1)
access$100 (List): Statement 100% (1/1)100% (3/3)100% (1/1)
optionalDeclarations (List): Statement 100% (1/1)62%  (10/16)75%  (3/4)
     
class AlphaRenamingRewriter$1100% (1/1)100% (2/2)100% (52/52)100% (8/8)
AlphaRenamingRewriter$1 (AlphaRenamingRewriter, MessageQueue, NameContext): void 100% (1/1)100% (12/12)100% (1/1)
fire (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)100% (40/40)100% (7/7)
     
class AlphaRenamingRewriter$10100% (1/1)100% (2/2)100% (11/11)100% (2/2)
AlphaRenamingRewriter$10 (AlphaRenamingRewriter): void 100% (1/1)100% (6/6)100% (1/1)
fire (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)100% (5/5)100% (1/1)
     
class AlphaRenamingRewriter$2100% (1/1)100% (2/2)91%  (325/357)82%  (45/55)
AlphaRenamingRewriter$2 (AlphaRenamingRewriter): void 100% (1/1)100% (6/6)100% (1/1)
fire (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)91%  (319/351)81%  (44/54)
     
class AlphaRenamingRewriter$3100% (1/1)100% (2/2)77%  (73/95)69%  (11.7/17)
AlphaRenamingRewriter$3 (AlphaRenamingRewriter): void 100% (1/1)100% (6/6)100% (1/1)
fire (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)75%  (67/89)67%  (10.7/16)
     
class AlphaRenamingRewriter$4100% (1/1)100% (2/2)91%  (53/58)85%  (11/13)
AlphaRenamingRewriter$4 (AlphaRenamingRewriter, MessageQueue): void 100% (1/1)100% (9/9)100% (1/1)
fire (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)90%  (44/49)83%  (10/12)
     
class AlphaRenamingRewriter$5100% (1/1)100% (2/2)100% (44/44)100% (5/5)
AlphaRenamingRewriter$5 (AlphaRenamingRewriter): void 100% (1/1)100% (6/6)100% (1/1)
fire (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)100% (38/38)100% (4/4)
     
class AlphaRenamingRewriter$6100% (1/1)100% (2/2)100% (35/35)100% (6/6)
AlphaRenamingRewriter$6 (AlphaRenamingRewriter, MessageQueue): void 100% (1/1)100% (9/9)100% (1/1)
fire (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)100% (26/26)100% (5/5)
     
class AlphaRenamingRewriter$7100% (1/1)100% (2/2)100% (35/35)100% (6/6)
AlphaRenamingRewriter$7 (AlphaRenamingRewriter, MessageQueue): void 100% (1/1)100% (9/9)100% (1/1)
fire (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)100% (26/26)100% (5/5)
     
class AlphaRenamingRewriter$8100% (1/1)100% (2/2)100% (69/69)100% (13/13)
AlphaRenamingRewriter$8 (AlphaRenamingRewriter, MessageQueue): void 100% (1/1)100% (9/9)100% (1/1)
fire (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)100% (60/60)100% (12/12)
     
class AlphaRenamingRewriter$9100% (1/1)100% (2/2)92%  (79/86)87%  (13/15)
AlphaRenamingRewriter$9 (AlphaRenamingRewriter): void 100% (1/1)100% (6/6)100% (1/1)
fire (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)91%  (73/80)86%  (12/14)

1// Copyright (C) 2009 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.parser.quasiliteral;
16 
17import com.google.caja.SomethingWidgyHappenedError;
18import com.google.caja.lexer.FilePosition;
19import com.google.caja.parser.ParseTreeNode;
20import com.google.caja.parser.ParseTreeNodeContainer;
21import com.google.caja.parser.js.Block;
22import com.google.caja.parser.js.CatchStmt;
23import com.google.caja.parser.js.Declaration;
24import com.google.caja.parser.js.Expression;
25import com.google.caja.parser.js.ExpressionStmt;
26import com.google.caja.parser.js.FormalParam;
27import com.google.caja.parser.js.FunctionConstructor;
28import com.google.caja.parser.js.FunctionDeclaration;
29import com.google.caja.parser.js.Identifier;
30import com.google.caja.parser.js.MultiDeclaration;
31import com.google.caja.parser.js.NullLiteral;
32import com.google.caja.parser.js.Reference;
33import com.google.caja.parser.js.Statement;
34import com.google.caja.reporting.MessagePart;
35import com.google.caja.reporting.MessageQueue;
36import com.google.caja.util.Lists;
37import com.google.caja.util.Maps;
38 
39import java.util.Collections;
40import java.util.List;
41import java.util.Map;
42 
43final class AlphaRenamingRewriter extends Rewriter {
44  private final Map<Scope, NameContext<String, ?>> contexts
45      = Maps.newIdentityHashMap();
46 
47  AlphaRenamingRewriter(
48      final MessageQueue mq, final NameContext<String, ?> rootContext) {
49    super(mq, false, false);
50 
51    addRules(new Rule[] {
52      /////////////////
53      // Scope Rules //
54      /////////////////
55 
56        new Rule() {
57        @Override
58        @RuleDescription(
59            name="rootScope",
60            synopsis="introduces a root scope",
61            reason="lets us rename globals",
62            matches="/* Expression */ @e",
63            substitutes="@e")
64        public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
65          if (node instanceof Expression && scope == null) {
66            Expression e = (Expression) node;
67            Block bl = new Block(
68                e.getFilePosition(),
69                Collections.singletonList(new ExpressionStmt(e)));
70            scope = Scope.fromProgram(bl, mq);
71            contexts.put(scope, rootContext);
72            return expand(e, scope);
73          }
74          return NONE;
75        }
76      },
77 
78      new Rule() {
79        @Override
80        @RuleDescription(
81            name="fns",
82            synopsis=("introduces function scope and assigns rewritten names"
83                      + " for function names, formals, and locals"),
84            reason="",
85            matches="function @name?(@params*) { @body* }",
86            substitutes="function @name?(@params*) { @headDecls?; @body* }")
87        public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
88          FunctionConstructor fc;
89          if (node instanceof FunctionConstructor) {
90            fc = (FunctionConstructor) node;
91          } else if (node instanceof FunctionDeclaration) {
92            fc = ((FunctionDeclaration) node).getInitializer();
93          } else {
94            return NONE;
95          }
96          Map<String, ParseTreeNode> bindings = match(fc);
97          if (bindings != null) {
98            boolean isDeclaration = fc != node;
99            NameContext<String, ?> context = contexts.get(scope);
100            NameContext<String, ?> newContext = context.makeChildContext();
101            Scope newScope = Scope.fromFunctionConstructor(scope, fc);
102            List<Declaration> headDecls = Lists.newArrayList();
103 
104            if (newScope.hasFreeThis()) {
105              NameContext.VarInfo<String, ?> vi;
106              try {
107                vi = newContext.declare("this", FilePosition.UNKNOWN);
108              } catch (NameContext.RedeclarationException ex) {
109                throw new SomethingWidgyHappenedError(
110                    "Local variable unexpectedly not set", ex);
111              }
112              headDecls.add((Declaration) QuasiBuilder.substV(
113                  "var @newName = this",
114                  "newName", new Identifier(FilePosition.UNKNOWN, vi.newName)));
115            }
116            if (newScope.hasFreeArguments()) {
117              NameContext.VarInfo<String, ?> vi;
118              try {
119                vi = newContext.declare("arguments", FilePosition.UNKNOWN);
120              } catch (NameContext.RedeclarationException ex) {
121                throw new SomethingWidgyHappenedError(
122                    "Local variable unexpectedly not set", ex);
123              }
124              headDecls.add((Declaration) QuasiBuilder.substV(
125                  "var @newName = arguments",
126                  "newName", new Identifier(FilePosition.UNKNOWN, vi.newName)));
127            }
128 
129            for (String local : newScope.getLocals()) {
130              try {
131                newContext.declare(
132                    local, newScope.getLocationOfDeclaration(local));
133              } catch (NameContext.RedeclarationException ex) {
134                // Might occur if a var named arguments is defined.
135              }
136            }
137            contexts.put(newScope, newContext);
138 
139            Identifier name = fc.getIdentifier();
140            Identifier rewrittenName;
141            if (name.getName() == null) {
142              rewrittenName = name;
143            } else if (!isSynthetic(name)) {
144              rewrittenName = new Identifier(
145                  name.getFilePosition(),
146                  (isDeclaration ? context : newContext)
147                  .lookup(name.getName()).newName);
148            } else {
149              rewrittenName = name;
150            }
151 
152            List<FormalParam> newFormals = Lists.newArrayList();
153            for (FormalParam p : fc.getParams()) {
154              if (!isSynthetic(p.getIdentifier())) {
155                NameContext.VarInfo<String, ?> v
156                    = newContext.lookup(p.getIdentifierName());
157                if (v == null) {
158                  // Occurs when an invalid parameter appears,
159                  // e.g., function (arguments) { ... }
160                  try {
161                    v = newContext.declare(
162                        p.getIdentifierName(), p.getFilePosition());
163                  } catch (NameContext.RedeclarationException ex) {
164                    // If it was previously declared then v wouldn't be null.
165                    throw new SomethingWidgyHappenedError(ex);
166                  }
167                }
168                FormalParam newP = new FormalParam(new Identifier(
169                    p.getFilePosition(), v.newName));
170                newFormals.add(newP);
171              } else {
172                newFormals.add(p);
173              }
174            }
175 
176            // For a declaration, a name is normally introduced in both the
177            // scope containing the declaration, and the function body scope.
178            // We produce a declaration with the outer name, but in the inner
179            // scope the function name should refer to the function itself.
180            // The only exception is that if there is a local declaration
181            // inside the local scope that masks the function name, then we
182            // should not clobber it.
183            // Examples:
184            //     (function f() {
185            //       var f = 0;
186            //       return f;
187            //     })() === 0
188            // and
189            //     (function f() {
190            //       function f() { return 0; }
191            //       return f();
192            //     })() === 0
193            //
194            // Because the var f or inner function f masks the outer function f,
195            // the name "f" should not be considered to refer to the function
196            // within its body. The condition
197            //     newScope.isFunction(name.getName())
198            //     && !newScope.isDeclaredFunction(name.getName())
199            // checks that the name still refers to the outer function, not a
200            // variable or a different function that is declared within the
201            // body.
202            // The second clause is required because isDeclaredFunction implies
203            // isDeclaredFunction but we need to distinguish the two cases.
204            // For a declaration, a name is normally introduced in both the
205            // scope containing the declaration, and the function body scope.
206            // We produce a declaration with the outer name, but in the inner
207            // scope the function name should refer to the function itself.
208            // The only exception is that if there is a local declaration
209            // inside the local scope that masks the function name, then we
210            // should not clobber it.
211            // Examples:
212            //     (function f() {
213            //       var f = 0;
214            //       return f;
215            //     })() === 0
216            // and
217            //     (function f() {
218            //       function f() { return 0; }
219            //       return f();
220            //     })() === 0
221            //
222            // Because the var f or inner function f masks the outer function f,
223            // the name "f" should not be considered to refer to the function
224            // within its body. The condition
225            //     newScope.isFunction(name.getName())
226            //     && !newScope.isDeclaredFunction(name.getName())
227            // checks that the name still refers to the outer function, not a
228            // variable or a different function that is declared within the
229            // body.
230            // The second clause is required because isDeclaredFunction implies
231            // isFunction but we need to distinguish the two cases.
232            if (isDeclaration && !isSynthetic(name)
233                && newScope.isFunction(name.getName())
234                && !newScope.isDeclaredFunction(name.getName())) {
235              headDecls.add((Declaration) QuasiBuilder.substV(
236                  "var @innerName = @outerName;",
237                  "outerName", new Reference(rewrittenName),
238                  "innerName", new Identifier(
239                      name.getFilePosition(),
240                      newContext.lookup(name.getName()).newName)));
241              // TODO(mikesamuel): skip if the self name is never used.
242            }
243 
244            FunctionConstructor out = (FunctionConstructor) substV(
245                "name", rewrittenName,
246                "headDecls", optionalDeclarations(headDecls),
247                "params", new ParseTreeNodeContainer(newFormals),
248                "body", expandAll(bindings.get("body"), newScope));
249            out.setFilePosition(fc.getFilePosition());
250            return isDeclaration ? new FunctionDeclaration(out) : out;
251          }
252          return NONE;
253        }
254      },
255 
256      new Rule() {
257        @Override
258        @RuleDescription(
259            name="block",
260            synopsis="block scoping",
261            reason="",
262            matches="{ @body* }",
263            substitutes="{ @body* }")
264        public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
265          if (node instanceof Block) {
266            Block bl = (Block) node;
267            List<Statement> stmts = Lists.newArrayList();
268            Scope newScope = Scope.fromPlainBlock(scope);
269            NameContext<String, ?> newContext = contexts.get(scope)
270                .makeChildContext();
271            contexts.put(newScope, newContext);
272            for (String local : newScope.getLocals()) {
273              try {
274                newContext.declare(
275                    local, newScope.getLocationOfDeclaration(local));
276              } catch (NameContext.RedeclarationException ex) {
277                throw new SomethingWidgyHappenedError(
278                    "Local variable unexpectedly not set", ex);
279              }
280            }
281            for (Statement s : bl.children()) {
282              stmts.add((Statement) expand(s, newScope));
283            }
284            stmts.addAll(0, newScope.getStartStatements());
285            return new Block(bl.getFilePosition(), stmts);
286          }
287          return NONE;
288        }
289      },
290 
291      new Rule() {
292        @Override
293        @RuleDescription(
294            name="catch",
295            synopsis="catch block scoping",
296            reason="",
297            matches="catch (@e) { @body* }",
298            substitutes="catch (@e) { @body* }")
299        public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
300          if (node instanceof CatchStmt) {
301            CatchStmt cs = (CatchStmt) node;
302            Scope newScope = Scope.fromCatchStmt(scope, cs);
303            NameContext<String, ?> context = contexts.get(scope);
304            NameContext<String, ?> newContext = context.makeChildContext();
305            contexts.put(newScope, newContext);
306            try {
307              newContext.declare(cs.getException().getIdentifierName(),
308                                 cs.getException().getFilePosition());
309            } catch (NameContext.RedeclarationException ex) {
310              ex.toMessageQueue(mq);
311            }
312            return expandAll(cs, newScope);
313          }
314          return NONE;
315        }
316      },
317 
318      //////////////
319      // Renaming //
320      //////////////
321 
322      new Rule() {
323        @Override
324        @RuleDescription(
325            name="memberAccess",
326            synopsis="",
327            reason="so that we do not mistakenly rename property names",
328            matches="@o.@r",
329            substitutes="@o.@r")
330        public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
331          Map<String, ParseTreeNode> bindings = match(node);
332          if (bindings != null) {
333            return substV("o", expand(bindings.get("o"), scope),
334                          "r", bindings.get("r"));
335          }
336          return NONE;
337        }
338      },
339      new Rule() {
340        @Override
341        @RuleDescription(
342            name="thisReference",
343            synopsis="Disallow this in the global scope.",
344            reason="The declaration cannot be rewritten.",
345            matches="this",
346            substitutes="this")
347            public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
348          if (match(node) != null) {
349            if (scope.isOuter()) {
350              mq.addMessage(
351                  RewriterMessageType.THIS_IN_GLOBAL_CONTEXT,
352                  node.getFilePosition());
353              return new NullLiteral(node.getFilePosition());
354            }
355          }
356          return NONE;
357        }
358      },
359      new Rule() {
360        @Override
361        @RuleDescription(
362            name="argumentsReference",
363            synopsis="Disallow arguments in the global scope.",
364            reason="The declaration cannot be rewritten.",
365            matches="arguments",
366            substitutes="arguments")
367            public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
368          if (match(node) != null) {
369            if (scope.isOuter()) {
370              mq.addMessage(
371                  RewriterMessageType.ARGUMENTS_IN_GLOBAL_CONTEXT,
372                  node.getFilePosition());
373              return new NullLiteral(node.getFilePosition());
374            }
375          }
376          return NONE;
377        }
378      },
379      new Rule() {
380        @Override
381        @RuleDescription(
382            name="rename",
383            synopsis="",
384            reason="",
385            matches="/* Reference */ @r",
386            substitutes="@r")
387        public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
388          if (node instanceof Reference) {
389            Reference r = (Reference) node;
390            if (!isSynthetic(r)) {
391              FilePosition pos = r.getFilePosition();
392              String rname = r.getIdentifierName();
393              NameContext<String, ?> context = contexts.get(scope);
394              NameContext.VarInfo<String, ?> vi = context.lookup(rname);
395              if (vi != null) {
396                return new Reference(new Identifier(pos, vi.newName));
397              } else {
398                mq.addMessage(
399                    RewriterMessageType.FREE_VARIABLE, pos,
400                    MessagePart.Factory.valueOf(rname));
401                return new NullLiteral(pos);
402              }
403            }
404          }
405          return NONE;
406        }
407      },
408      new Rule() {
409        @Override
410        @RuleDescription(
411            name="decl",
412            synopsis="rewrite declaration identifiers",
413            reason="",
414            matches="var @i = @v?",
415            substitutes="var @ri = @v?;")
416        public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
417          Map<String, ParseTreeNode> bindings = match(node);
418          if (bindings != null) {
419            Identifier i = (Identifier) bindings.get("i");
420            Expression v = (Expression) bindings.get("v");
421            Identifier ri;
422            if (!isSynthetic(i)) {
423              NameContext<String, ?> context = contexts.get(scope);
424              NameContext.VarInfo<String, ?> var = context.lookup(i.getName());
425              if (var == null) {  // A variable like arguments
426                return expandAll(node, scope);
427              }
428              ri = new Identifier(i.getFilePosition(), var.newName);
429            } else {
430              ri = i;
431            }
432            return substV(
433                "ri", ri,
434                "v", v != null ? expand(v, scope) : null);
435          }
436          return NONE;
437        }
438      },
439      new Rule() {
440        @Override
441        @RuleDescription(
442            name="other",
443            synopsis="",
444            reason="",
445            matches="@n",
446            substitutes="@n")
447        public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
448          return expandAll(node, scope);
449        }
450      },
451    });
452  }
453 
454  private static Statement optionalDeclarations(List<Declaration> decls) {
455    switch (decls.size()) {
456      case 0: return null;
457      case 1: return decls.get(0);
458      default: return new MultiDeclaration(FilePosition.UNKNOWN, decls);
459    }
460  }
461}

[all classes][com.google.caja.parser.quasiliteral]
EMMA 2.0.5312 (C) Vladimir Roubtsov