Package com.google.caja.reporting

Source Code of com.google.caja.reporting.SnippetProducer

// Copyright (C) 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.caja.reporting;

import com.google.caja.SomethingWidgyHappenedError;
import com.google.caja.lexer.FilePosition;
import com.google.caja.lexer.InputSource;

import java.io.IOException;
import java.util.Map;

/**
* Given original source code, produces snippets for error messages.
* <p>
* For a {@link Message} message like {@code file:16+10-13: bar not defined},
* the snippet might look like
* <pre>
*     file:16 var foo = bar() + baz
*                       ^^^
* </pre>
*
* @author mikesamuel@gmail.com
*/
public class SnippetProducer {
  private static final int DEFAULT_MAX_WIDTH = 80;
  private static final int DEFAULT_TAB_WIDTH = 8;

  private final Map<InputSource, ? extends CharSequence> originalSource;
  protected final MessageContext mc;
  protected final int maxWidth, tabWidth;

  public SnippetProducer(
      Map<InputSource, ? extends CharSequence> originalSource,
      MessageContext mc) {
    this(originalSource, mc, DEFAULT_MAX_WIDTH, DEFAULT_TAB_WIDTH);
  }

  public SnippetProducer(
      Map<InputSource, ? extends CharSequence> originalSource,
      MessageContext mc,
      int maxWidth) {
    this(originalSource, mc, maxWidth, DEFAULT_TAB_WIDTH);
  }

  public SnippetProducer(
      Map<InputSource, ? extends CharSequence> originalSource,
      MessageContext mc, int maxWidth, int tabWidth) {
    this.originalSource = originalSource;
    this.mc = mc;
    this.maxWidth = maxWidth;
    this.tabWidth = tabWidth;
  }

  public final String getSnippet(Message msg) {
    StringBuilder snippet = new StringBuilder();
    for (MessagePart mp : msg.getMessageParts()) {
      if (!(mp instanceof FilePosition)) { continue; }
      FilePosition pos = (FilePosition) mp;
      int len = snippet.length();
      if (len != 0) { snippet.append('\n'); }
      int snippetStart = snippet.length();
      try {
        appendSnippet(pos, snippet);
      } catch (IOException ex) {
        throw new SomethingWidgyHappenedError(
            "StringBuilders shouldn't throw IOExceptions", ex);
      }
      // If no content written by appendSnippet, then remove the newline.
      if (snippet.length() == snippetStart) { snippet.setLength(len); }
    }
    return snippet.toString();
  }

  public final void appendSnippet(FilePosition pos, Appendable out)
      throws IOException {
    InputSource src = pos.source();
    CharSequence sourceCode = originalSource.get(src);
    if (sourceCode == null) { return; // Can't write.

    // Pick a representative line from pos.
    int lineNo = pos.startLineNo();
    // TODO(mikesamuel): startCharInLine shouldn't ever return a number < 1
    int start = Math.max(0, pos.startCharInLine() - 1);
    CharSequence line = fetchLine(sourceCode, lineNo);

    if (line != null
        && (line.length() == 0 || isLinebreak(line.charAt(0)))
        && lineNo + 1 <= pos.endLineNo()) {
      // If the start of the pos is a newline, advance to the next.
      ++lineNo;
      start = 0;
      line = fetchLine(sourceCode, lineNo);
    }
    if (line == null) { return; }

    // Be paranoid about position since we don't want bad positions or errors
    // in the originalSource map to prevent us from reporting errors at all.
    start = Math.min(line.length(), start);
    int end = Math.max(
        Math.min((pos.endLineNo() == lineNo
                  ? pos.endCharInLine() - 1 : Integer.MAX_VALUE),
                 line.length()),
        start);

    // Reduce line to maxWidth of context.
    if (0 < maxWidth && maxWidth < line.length()) {
      end = Math.min(end, start + maxWidth);
      int left = Math.max(0, end - maxWidth);
      int right = Math.min(line.length(), left + maxWidth);
      line = line.subSequence(left, right);
      start -= left;
      end -= left;
    }

    formatSnippet(pos,
        FilePosition.instance(src, lineNo, 1, line.length() + 1),
        line, start, end, out);
  }

  /**
   * May be overridden to format a snippet differently, e.g. by HTML escaping
   * line and inserting tags around {@code line[start:end]}.
   *
   * @param errorPosition actual unmodified error fileposition
   * @param snippetPos line granularity error position of the snippet
   * @param end >= start.  Implementations should take care to provide some
   *   useful information if end == start, since a zero length range might
   *   be used to indicate where information is missing, or where inferred
   *   content was inserted.
   * @throws IOException only if out raised an IOException.
   */
  protected void formatSnippet(FilePosition errorPosition,
      FilePosition snippetPos, CharSequence line, int start, int end,
      Appendable out)
      throws IOException {
    // Write out "file:14: <line-of-sourcecode>"
    StringBuilder posBuf = new StringBuilder();
    formatFilePosition(snippetPos, posBuf);
    posBuf.append(": ");
    int filePosLength = posBuf.length();

    int nSpaces = start + filePosLength;
    int nCarets = end - start;

    out.append(posBuf);
    // Expand tabs so that the carets line up with the source.
    int nExtraSpaces = expandTabs(line, 0, start, 0, out);
    int nExtraCarets = expandTabs(line, start, end, nExtraSpaces, out);
    expandTabs(line, end, line.length(), nExtraSpaces + nExtraCarets, out);
    if (line.length() == 0 || !isLinebreak(line.charAt(line.length() - 1))) {
      // If the line is the last in the file, it may not end with a newline.
      out.append("\n");
    }
    repeat("                ", nSpaces + nExtraSpaces, out);
    repeat("^^^^^^^^^^^^^^^^", Math.max(nCarets + nExtraCarets, 1), out);
  }

  /**
   * May be overridden to format a position differently, e.g. by inserting links
   * to source files.
   */
  protected void formatFilePosition(FilePosition pos, Appendable out)
      throws IOException {
    pos.source().format(mc, out);
    out.append(":");
    out.append(String.valueOf(pos.startLineNo()));
  }

  /** Append count characters from pattern onto out, repeating if necessary. */
  private static void repeat(String pattern, int count, Appendable out)
      throws IOException {
    while (count >= pattern.length()) {
      out.append(pattern);
      count -= pattern.length();
    }
    if (count > 0) { out.append(pattern, 0, count); }
  }


  // The scheme below does not take into account different languages'
  // different definitions of newline, but it does use the same scheme as
  // CharProducer's language agnostic line counting scheme which agrees
  // with source code editors.
  // CharProducer does not bump the lineNo counter on codepoints 0x2028,2029.
  private static CharSequence fetchLine(CharSequence seq, int lineNo) {
    int pos = 0;
    for (int i = lineNo; --i >= 1;) {
      pos = posPastNextLinebreak(seq, pos);
    }
    int start = pos;
    int end = posPastNextLinebreak(seq, pos);
    if (start < end) {
      return seq.subSequence(start, end);
    }
    return null;
  }

  private static int indexOf(
      CharSequence seq, char ch, int fromIndex, int toIndex) {
    for (int i = fromIndex; i < toIndex; ++i) {
      if (seq.charAt(i) == ch) { return i; }
    }
    return -1;
  }

  private int expandTabs(
      CharSequence seq, int start, int end, int nExpanded, Appendable out)
      throws IOException {
    final String SPACES = "        ";
    int tabIdx = indexOf(seq, '\t', start, end);
    if (tabIdx < 0) {
      out.append(seq, start, end);
      return 0;
    }
    int nExtra = 0;
    int done = start;
    do {
      out.append(seq, done, tabIdx);
      int nBefore = nExtra + tabIdx + nExpanded;
      int nSpaces = tabWidth - (nBefore % tabWidth);
      nExtra += nSpaces - 1;
      while (nSpaces >= SPACES.length()) {
        out.append(SPACES);
        nSpaces -= SPACES.length();
      }
      out.append(SPACES, 0, nSpaces);
      done = tabIdx + 1;
    } while ((tabIdx = indexOf(seq, '\t', done, end)) >= 0);
    out.append(seq, done, end);
    return nExtra;
  }

  private static int posPastNextLinebreak(CharSequence seq, int pos) {
    int len = seq.length();
    for (;pos < len; ++pos) {
      char ch = seq.charAt(pos);
      if (ch == '\n') { return pos + 1; }
      if (ch == '\r') {
        return pos + ((pos + 1 < len && '\n' == seq.charAt(pos + 1)) ? 2 : 1);
      }
    }
    return len;
  }

  private static boolean isLinebreak(char ch) {
    return ch == '\r' || ch == '\n';
  }
}
TOP

Related Classes of com.google.caja.reporting.SnippetProducer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.