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

COVERAGE SUMMARY FOR SOURCE FILE [SnippetProducer.java]

nameclass, %method, %block, %line, %
SnippetProducer.java100% (1/1)100% (13/13)95%  (501/526)95%  (102.1/108)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class SnippetProducer100% (1/1)100% (13/13)95%  (501/526)95%  (102.1/108)
SnippetProducer (Map, MessageContext): void 100% (1/1)100% (7/7)100% (2/2)
SnippetProducer (Map, MessageContext, int): void 100% (1/1)100% (7/7)100% (2/2)
SnippetProducer (Map, MessageContext, int, int): void 100% (1/1)100% (15/15)100% (6/6)
appendSnippet (FilePosition, Appendable): void 100% (1/1)98%  (129/131)98%  (21.6/22)
expandTabs (CharSequence, int, int, int, Appendable): int 100% (1/1)89%  (78/88)89%  (17/19)
fetchLine (CharSequence, int): CharSequence 100% (1/1)93%  (27/29)88%  (7/8)
formatFilePosition (FilePosition, Appendable): void 100% (1/1)100% (17/17)100% (4/4)
formatSnippet (FilePosition, FilePosition, CharSequence, int, int, Appendable... 100% (1/1)100% (84/84)100% (15/15)
getSnippet (Message): String 100% (1/1)82%  (47/57)83%  (11.6/14)
indexOf (CharSequence, char, int, int): int 100% (1/1)100% (16/16)100% (3/3)
isLinebreak (char): boolean 100% (1/1)100% (10/10)100% (1/1)
posPastNextLinebreak (CharSequence, int): int 100% (1/1)98%  (41/42)99%  (6.9/7)
repeat (String, int, Appendable): void 100% (1/1)100% (23/23)100% (5/5)

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.reporting;
16 
17import com.google.caja.SomethingWidgyHappenedError;
18import com.google.caja.lexer.FilePosition;
19import com.google.caja.lexer.InputSource;
20 
21import java.io.IOException;
22import java.util.Map;
23 
24/**
25 * Given original source code, produces snippets for error messages.
26 * <p>
27 * For a {@link Message} message like {@code file:16+10-13: bar not defined},
28 * the snippet might look like
29 * <pre>
30 *     file:16 var foo = bar() + baz
31 *                       ^^^
32 * </pre>
33 *
34 * @author mikesamuel@gmail.com
35 */
36public class SnippetProducer {
37  private static final int DEFAULT_MAX_WIDTH = 80;
38  private static final int DEFAULT_TAB_WIDTH = 8;
39 
40  private final Map<InputSource, ? extends CharSequence> originalSource;
41  protected final MessageContext mc;
42  protected final int maxWidth, tabWidth;
43 
44  public SnippetProducer(
45      Map<InputSource, ? extends CharSequence> originalSource,
46      MessageContext mc) {
47    this(originalSource, mc, DEFAULT_MAX_WIDTH, DEFAULT_TAB_WIDTH);
48  }
49 
50  public SnippetProducer(
51      Map<InputSource, ? extends CharSequence> originalSource,
52      MessageContext mc,
53      int maxWidth) {
54    this(originalSource, mc, maxWidth, DEFAULT_TAB_WIDTH);
55  }
56 
57  public SnippetProducer(
58      Map<InputSource, ? extends CharSequence> originalSource,
59      MessageContext mc, int maxWidth, int tabWidth) {
60    this.originalSource = originalSource;
61    this.mc = mc;
62    this.maxWidth = maxWidth;
63    this.tabWidth = tabWidth;
64  }
65 
66  public final String getSnippet(Message msg) {
67    StringBuilder snippet = new StringBuilder();
68    for (MessagePart mp : msg.getMessageParts()) {
69      if (!(mp instanceof FilePosition)) { continue; }
70      FilePosition pos = (FilePosition) mp;
71      int len = snippet.length();
72      if (len != 0) { snippet.append('\n'); }
73      int snippetStart = snippet.length();
74      try {
75        appendSnippet(pos, snippet);
76      } catch (IOException ex) {
77        throw new SomethingWidgyHappenedError(
78            "StringBuilders shouldn't throw IOExceptions", ex);
79      }
80      // If no content written by appendSnippet, then remove the newline.
81      if (snippet.length() == snippetStart) { snippet.setLength(len); }
82    }
83    return snippet.toString();
84  }
85 
86  public final void appendSnippet(FilePosition pos, Appendable out)
87      throws IOException {
88    InputSource src = pos.source();
89    CharSequence sourceCode = originalSource.get(src);
90    if (sourceCode == null) { return; }  // Can't write.
91 
92    // Pick a representative line from pos.
93    int lineNo = pos.startLineNo();
94    int start = pos.startCharInLine() - 1;
95    CharSequence line = fetchLine(sourceCode, lineNo);
96 
97    if (line != null
98        && (line.length() == 0 || isLinebreak(line.charAt(0)))
99        && lineNo + 1 <= pos.endLineNo()) {
100      // If the start of the pos is a newline, advance to the next.
101      ++lineNo;
102      start = 0;
103      line = fetchLine(sourceCode, lineNo);
104    }
105    if (line == null) { return; }
106 
107    // Be paranoid about position since we don't want bad positions or errors
108    // in the originalSource map to prevent us from reporting errors at all.
109    start = Math.min(line.length(), start);
110    int end = Math.max(
111        Math.min((pos.endLineNo() == lineNo
112                  ? pos.endCharInLine() - 1 : Integer.MAX_VALUE),
113                 line.length()),
114        start);
115 
116    // Reduce line to maxWidth of context.
117    if (0 < maxWidth && maxWidth < line.length()) {
118      end = Math.min(end, start + maxWidth);
119      int left = Math.max(0, end - maxWidth);
120      int right = Math.min(line.length(), left + maxWidth);
121      line = line.subSequence(left, right);
122      start -= left;
123      end -= left;
124    }
125 
126    formatSnippet(pos,
127        FilePosition.instance(src, lineNo, 1, line.length() + 1),
128        line, start, end, out);
129  }
130 
131  /**
132   * May be overridden to format a snippet differently, e.g. by HTML escaping
133   * line and inserting tags around {@code line[start:end]}.
134   *
135   * @param errorPosition actual unmodified error fileposition
136   * @param snippetPos line granularity error position of the snippet
137   * @param end >= start.  Implementations should take care to provide some
138   *   useful information if end == start, since a zero length range might
139   *   be used to indicate where information is missing, or where inferred
140   *   content was inserted.
141   * @throws IOException only if out raised an IOException.
142   */
143  protected void formatSnippet(FilePosition errorPosition,
144      FilePosition snippetPos, CharSequence line, int start, int end,
145      Appendable out)
146      throws IOException {
147    // Write out "file:14: <line-of-sourcecode>"
148    StringBuilder posBuf = new StringBuilder();
149    formatFilePosition(snippetPos, posBuf);
150    posBuf.append(": ");
151    int filePosLength = posBuf.length();
152 
153    int nSpaces = start + filePosLength;
154    int nCarets = end - start;
155 
156    out.append(posBuf);
157    // Expand tabs so that the carets line up with the source.
158    int nExtraSpaces = expandTabs(line, 0, start, 0, out);
159    int nExtraCarets = expandTabs(line, start, end, nExtraSpaces, out);
160    expandTabs(line, end, line.length(), nExtraSpaces + nExtraCarets, out);
161    if (line.length() == 0 || !isLinebreak(line.charAt(line.length() - 1))) {
162      // If the line is the last in the file, it may not end with a newline.
163      out.append("\n");
164    }
165    repeat("                ", nSpaces + nExtraSpaces, out);
166    repeat("^^^^^^^^^^^^^^^^", Math.max(nCarets + nExtraCarets, 1), out);
167  }
168 
169  /**
170   * May be overridden to format a position differently, e.g. by inserting links
171   * to source files.
172   */
173  protected void formatFilePosition(FilePosition pos, Appendable out)
174      throws IOException {
175    pos.source().format(mc, out);
176    out.append(":");
177    out.append(String.valueOf(pos.startLineNo()));
178  }
179 
180  /** Append count characters from pattern onto out, repeating if necessary. */
181  private static void repeat(String pattern, int count, Appendable out)
182      throws IOException {
183    while (count >= pattern.length()) {
184      out.append(pattern);
185      count -= pattern.length();
186    }
187    if (count > 0) { out.append(pattern, 0, count); }
188  }
189 
190 
191  // The scheme below does not take into account different languages'
192  // different definitions of newline, but it does use the same scheme as
193  // CharProducer's language agnostic line counting scheme which agrees
194  // with source code editors.
195  // CharProducer does not bump the lineNo counter on codepoints 0x2028,2029.
196  private static CharSequence fetchLine(CharSequence seq, int lineNo) {
197    int pos = 0;
198    for (int i = lineNo; --i >= 1;) {
199      pos = posPastNextLinebreak(seq, pos);
200    }
201    int start = pos;
202    int end = posPastNextLinebreak(seq, pos);
203    if (start < end) {
204      return seq.subSequence(start, end);
205    }
206    return null;
207  }
208 
209  private static int indexOf(
210      CharSequence seq, char ch, int fromIndex, int toIndex) {
211    for (int i = fromIndex; i < toIndex; ++i) {
212      if (seq.charAt(i) == ch) { return i; }
213    }
214    return -1;
215  }
216 
217  private int expandTabs(
218      CharSequence seq, int start, int end, int nExpanded, Appendable out)
219      throws IOException {
220    final String SPACES = "        ";
221    int tabIdx = indexOf(seq, '\t', start, end);
222    if (tabIdx < 0) {
223      out.append(seq, start, end);
224      return 0;
225    }
226    int nExtra = 0;
227    int done = start;
228    do {
229      out.append(seq, done, tabIdx);
230      int nBefore = nExtra + tabIdx + nExpanded;
231      int nSpaces = tabWidth - (nBefore % tabWidth);
232      nExtra += nSpaces - 1;
233      while (nSpaces >= SPACES.length()) {
234        out.append(SPACES);
235        nSpaces -= SPACES.length();
236      }
237      out.append(SPACES, 0, nSpaces);
238      done = tabIdx + 1;
239    } while ((tabIdx = indexOf(seq, '\t', done, end)) >= 0);
240    out.append(seq, done, end);
241    return nExtra;
242  }
243 
244  private static int posPastNextLinebreak(CharSequence seq, int pos) {
245    int len = seq.length();
246    for (;pos < len; ++pos) {
247      char ch = seq.charAt(pos);
248      if (ch == '\n') { return pos + 1; }
249      if (ch == '\r') {
250        return pos + ((pos + 1 < len && '\n' == seq.charAt(pos + 1)) ? 2 : 1);
251      }
252    }
253    return len;
254  }
255 
256  private static boolean isLinebreak(char ch) {
257    return ch == '\r' || ch == '\n';
258  }
259}

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