All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.debugging.sourcemap.SourceMapGeneratorV3 Maven / Gradle / Ivy

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2011 The Closure Compiler Authors.
 *
 * 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.debugging.sourcemap;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.nullToEmpty;

import com.google.common.base.Preconditions;
import com.google.debugging.sourcemap.SourceMapConsumerV3.EntryVisitor;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import javax.annotation.Nullable;

/**
 * Collects information mapping the generated (compiled) source back to
 * its original source for debugging purposes.
 *
 * Source Map Revision 3 Proposal:
 * https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?usp=sharing
 */
public final class SourceMapGeneratorV3 implements SourceMapGenerator {

  /**
   * This interface provides the merging strategy when an extension conflict
   * appears because of merging two source maps on method
   * {@link #mergeMapSection}.
   */
  public interface ExtensionMergeAction {

    /**
     * Returns the merged value between two extensions with the same name when
     * merging two source maps
     *
     * @param extensionKey The extension name in conflict
     * @param currentValue The extension value in the current source map
     * @param newValue The extension value in the input source map
     * @return The merged value
     */
    Object merge(String extensionKey, Object currentValue,
        Object newValue);
  }

  private static final int UNMAPPED = -1;


  /**
   * A pre-order traversal ordered list of mappings stored in this map.
   */
  private final List mappings = new ArrayList<>();

  /**
   * A map of source names to source name index
   */
  private final LinkedHashMap sourceFileMap =
       new LinkedHashMap<>();

  /**
   * A map of source names to source file contents
   */
  private final LinkedHashMap sourceFileContentMap =
       new LinkedHashMap<>();

  /**
   * A map of source names to source name index
   */
  private final LinkedHashMap originalNameMap =
       new LinkedHashMap<>();

  /**
   * Cache of the last mappings source name.
   */
  private String lastSourceFile = null;

  /**
   * Cache of the last mappings source name index.
   */
  private int lastSourceFileIndex = -1;

  /**
   * For validation store the last mapping added.
   */
  private Mapping lastMapping;

  /**
   * The position that the current source map is offset in the
   * buffer being used to generated the compiled source file.
   */
  private FilePosition offsetPosition = new FilePosition(0, 0);

  /**
   * The position that the current source map is offset in the
   * generated the compiled source file by the addition of a
   * an output wrapper prefix.
   */
  private FilePosition prefixPosition = new FilePosition(0, 0);

  /**
   * A list of extensions to be added to sourcemap. The value is a object
   * to permit single values, like strings or numbers, and JsonObject or
   * JsonArray objects.
   */
  private final LinkedHashMap extensions = new LinkedHashMap<>();

  /**
   * The source root path for relocating source fails or avoid duplicate values
   * on the source entry.
   */
  private String sourceRootPath;

  /**
   * {@inheritDoc}
   */
  @Override
  public void reset() {
    // Do not reset sourceFileContentMap
    mappings.clear();
    lastMapping = null;
    sourceFileMap.clear();
    originalNameMap.clear();
    lastSourceFile = null;
    lastSourceFileIndex = -1;
    offsetPosition = new FilePosition(0, 0);
    prefixPosition = new FilePosition(0, 0);
  }

  /**
   * @param validate Whether to perform (potentially costly) validation on the
   * generated source map.
   */
  @Override
  public void validate(boolean validate) {
    // Nothing currently.
  }

  /**
   * Sets the prefix used for wrapping the generated source file before
   * it is written. This ensures that the source map is adjusted for the
   * change in character offsets.
   *
   * @param prefix The prefix that is added before the generated source code.
   */
  @Override
  public void setWrapperPrefix(String prefix) {
    // Determine the current line and character position.
    int prefixLine = 0;
    int prefixIndex = 0;

    for (int i = 0; i < prefix.length(); ++i) {
      if (prefix.charAt(i) == '\n') {
        prefixLine++;
        prefixIndex = 0;
      } else {
        prefixIndex++;
      }
    }

    prefixPosition = new FilePosition(prefixLine, prefixIndex);
  }

  /**
   * Sets the source code that exists in the buffer for which the
   * generated code is being generated. This ensures that the source map
   * accurately reflects the fact that the source is being appended to
   * an existing buffer and as such, does not start at line 0, position 0
   * but rather some other line and position.
   *
   * @param offsetLine The index of the current line being printed.
   * @param offsetIndex The column index of the current character being printed.
   */
  @Override
  public void setStartingPosition(int offsetLine, int offsetIndex) {
    checkState(offsetLine >= 0);
    checkState(offsetIndex >= 0);
    offsetPosition = new FilePosition(offsetLine, offsetIndex);
  }

  /**
   * Adds a mapping for the given node.  Mappings must be added in order.
   * @param startPosition The position on the starting line
   * @param endPosition The position on the ending line.
   */
  @Override
  public void addMapping(
      String sourceName, @Nullable String symbolName,
      FilePosition sourceStartPosition,
      FilePosition startPosition, FilePosition endPosition) {

    // Don't bother if there is not sufficient information to be useful.
    if (sourceName == null || sourceStartPosition.getLine() < 0) {
      return;
    }

    FilePosition adjustedStart = startPosition;
    FilePosition adjustedEnd = endPosition;

    if (offsetPosition.getLine() != 0
        || offsetPosition.getColumn() != 0) {
      // If the mapping is found on the first line, we need to offset
      // its character position by the number of characters found on
      // the *last* line of the source file to which the code is
      // being generated.
      int offsetLine = offsetPosition.getLine();
      int startOffsetPosition = offsetPosition.getColumn();
      int endOffsetPosition = offsetPosition.getColumn();

      if (startPosition.getLine() > 0) {
        startOffsetPosition = 0;
      }

      if (endPosition.getLine() > 0) {
        endOffsetPosition = 0;
      }

      adjustedStart = new FilePosition(
          startPosition.getLine() + offsetLine,
          startPosition.getColumn() + startOffsetPosition);

      adjustedEnd = new FilePosition(
          endPosition.getLine() + offsetLine,
          endPosition.getColumn() + endOffsetPosition);
    }

    // Create the new mapping.
    Mapping mapping = new Mapping();
    mapping.sourceFile = sourceName;
    mapping.originalPosition = sourceStartPosition;
    mapping.originalName = symbolName;
    mapping.startPosition = adjustedStart;
    mapping.endPosition = adjustedEnd;

    // Validate the mappings are in a proper order.
    if (lastMapping != null) {
      int lastLine = lastMapping.startPosition.getLine();
      int lastColumn = lastMapping.startPosition.getColumn();
      int nextLine = mapping.startPosition.getLine();
      int nextColumn = mapping.startPosition.getColumn();
      Preconditions.checkState(nextLine > lastLine
          || (nextLine == lastLine && nextColumn >= lastColumn),
          "Incorrect source mappings order, previous : (%s,%s)\n"
          + "new : (%s,%s)",
          lastLine, lastColumn, nextLine, nextColumn);
    }

    lastMapping = mapping;
    mappings.add(mapping);
  }

  @Override public void addSourcesContent(String source, String content) {
    sourceFileContentMap.put(source, content);
  }

  class ConsumerEntryVisitor implements EntryVisitor {

    @Override
    public void visit(
        String sourceName, String symbolName,
        FilePosition sourceStartPosition,
        FilePosition startPosition, FilePosition endPosition) {
      addMapping(sourceName, symbolName,
          sourceStartPosition, startPosition, endPosition);
    }
  }

  /**
   * Merges current mapping with {@code mapSectionContents} considering the
   * offset {@code (line, column)}. Any extension in the map section will be
   * ignored.
   *
   * @param line The line offset
   * @param column The column offset
   * @param mapSectionContents The map section to be appended
   * @throws SourceMapParseException
   */
  public void mergeMapSection(int line, int column, String mapSectionContents)
      throws SourceMapParseException {
    setStartingPosition(line, column);
    SourceMapConsumerV3 section = new SourceMapConsumerV3();
    section.parse(mapSectionContents);
    section.visitMappings(new ConsumerEntryVisitor());
  }

  /**
   * Works like {@link #mergeMapSection(int, int, String)}, except that
   * extensions from the @{code mapSectionContents} are merged to the top level
   * source map. For conflicts a {@code mergeAction} is performed.
   *
   * @param line The line offset
   * @param column The column offset
   * @param mapSectionContents The map section to be appended
   * @param mergeAction The merge action for conflicting extensions
   * @throws SourceMapParseException
   */
  public void mergeMapSection(int line, int column, String mapSectionContents,
      ExtensionMergeAction mergeAction)
      throws SourceMapParseException {
    setStartingPosition(line, column);
    SourceMapConsumerV3 section = new SourceMapConsumerV3();
    section.parse(mapSectionContents);
    section.visitMappings(new ConsumerEntryVisitor());
    for (Entry entry : section.getExtensions().entrySet()) {
       String extensionKey = entry.getKey();
       if (extensions.containsKey(extensionKey)) {
         extensions.put(extensionKey,
             mergeAction.merge(extensionKey,
                               extensions.get(extensionKey),
                               entry.getValue()));
       } else {
         extensions.put(extensionKey, entry.getValue());
       }
     }
  }

  /**
   * Writes out the source map in the following format (line numbers are for reference only and are
   * not part of the format):
   *
   * 
   * 1.  {
   * 2.    version: 3,
   * 3.    file: "out.js",
   * 4.    lineCount: 2,
   * 5.    sourceRoot: "",
   * 6.    sources: ["foo.js", "bar.js"],
   * 7.    sourcesContent: ["var foo", "var bar"],
   * 8.    names: ["src", "maps", "are", "fun"],
   * 9.    mappings: "a;;abcde,abcd,a;"
   * 10.   x_org_extension: value
   * 11. }
   * 
* *
    *
  1. Line 1 : The entire file is a single JSON object *
  2. Line 2 : File version (always the first entry in the object) *
  3. Line 3 : [Optional] The name of the file that this source map is associated with. *
  4. Line 4 : [Optional] The number of lines represented in the source map. *
  5. Line 5 : [Optional] An optional source root, useful for relocating source files on a * server or removing repeated prefix values in the "sources" entry. *
  6. Line 6 : A list of sources used by the "mappings" entry relative to the sourceRoot. *
  7. Line 7 : An optional list of the full content of the source files. *
  8. Line 8 : A list of symbol names used by the "mapping" entry. This list may be incomplete. * Line 9 : The mappings field. *
  9. Line 10: Any custom field (extension). *
*/ @Override public void appendTo(Appendable out, @Nullable String name) throws IOException { int maxLine = prepMappings() + 1; // Add the header fields. out.append("{\n"); appendFirstField(out, "version", "3"); if (name != null) { appendField(out, "file", escapeString(name)); } appendField(out, "lineCount", String.valueOf(maxLine)); //optional source root if (this.sourceRootPath != null && !this.sourceRootPath.isEmpty()) { appendField(out, "sourceRoot", escapeString(this.sourceRootPath)); } // Add the mappings themselves. appendFieldStart(out, "mappings"); // out.append("["); (new LineMapper(out, maxLine)).appendLineMappings(); // out.append("]"); appendFieldEnd(out); // Files names appendFieldStart(out, "sources"); out.append("["); addSourceNameMap(out); out.append("]"); appendFieldEnd(out); // Sources contents addSourcesContentMap(out); // Identifier names appendFieldStart(out, "names"); out.append("["); addSymbolNameMap(out); out.append("]"); appendFieldEnd(out); // Extensions, only if there is any for (String key : this.extensions.keySet()) { Object objValue = this.extensions.get(key); String value; if (objValue instanceof String) { value = escapeString((String) objValue); // escapes native String } else { value = objValue.toString(); } appendField(out, key, value); } out.append("\n}\n"); } /** * A prefix to be added to the beginning of each sourceName passed to * {@link #addMapping}. Debuggers expect (prefix + sourceName) to be a URL * for loading the source code. * * @param path The URL prefix to save in the sourcemap file. (Not validated.) */ public void setSourceRoot(String path){ this.sourceRootPath = path; } /** * Adds field extensions to the json source map. The value is allowed to be * any value accepted by json, eg. string, JsonObject, JsonArray, etc. * * Extensions must follow the format x_orgranization_field (based on V3 * proposal), otherwise a {@code SourceMapParseExtension} will be thrown. * * @param name The name of the extension with format organization_field * @param object The value of the extension as a valid json value * @throws SourceMapParseException if extension name is malformed */ public void addExtension(String name, Object object) throws SourceMapParseException{ if (!name.startsWith("x_")){ throw new SourceMapParseException("Extension '" + name + "' must start with 'x_'"); } this.extensions.put(name, object); } /** * Removes an extension by name if present. * * @param name The name of the extension with format organization_field */ public void removeExtension(String name) { if (this.extensions.containsKey(name)) { this.extensions.remove(name); } } /** * Check whether or not the sourcemap has an extension. * * @param name The name of the extension with format organization_field * @return If the extension exist */ public boolean hasExtension(String name) { return this.extensions.containsKey(name); } /** * Returns the value mapped by the specified extension * or {@code null} if this extension does not exist. * * @param name * @return the extension value or {@code null} */ public Object getExtension(String name) { return this.extensions.get(name); } /** * Writes the source name map to 'out'. */ private void addSourceNameMap(Appendable out) throws IOException { addNameMap(out, sourceFileMap); } private void addSourcesContentMap(Appendable out) throws IOException { boolean found = false; int size = sourceFileMap.size(); List contents = new ArrayList<>(size); contents.addAll(Collections.nCopies(size, "")); for (Map.Entry entry : sourceFileMap.entrySet()) { Integer index = entry.getValue(); checkState(index < size); String content = sourceFileContentMap.get(entry.getKey()); if (content != null) { contents.set(index, content); found = true; } } if (!found) { return; } appendFieldStart(out, "sourcesContent"); out.append("["); for (int i = 0; i < size; i++) { if (i != 0) { out.append(","); } String sourceContent = contents.get(i); out.append(escapeString(nullToEmpty(sourceContent))); } out.append("]"); appendFieldEnd(out); } /** * Writes the source name map to 'out'. */ private void addSymbolNameMap(Appendable out) throws IOException { addNameMap(out, originalNameMap); } private static void addNameMap(Appendable out, Map map) throws IOException { int i = 0; for (Entry entry : map.entrySet()) { String key = entry.getKey(); if (i != 0) { out.append(","); } out.append(escapeString(key)); i++; } } /** * Escapes the given string for JSON. */ private static String escapeString(String value) { return Util.escapeString(value); } // Source map field helpers. private static void appendFirstField( Appendable out, String name, CharSequence value) throws IOException { appendFieldStart(out, name, true); out.append(value); } private static void appendField( Appendable out, String name, CharSequence value) throws IOException { appendFieldStart(out, name, false); out.append(value); } private static void appendFieldStart(Appendable out, String name) throws IOException { appendFieldStart(out, name, false); } private static void appendFieldStart(Appendable out, String name, boolean first) throws IOException { if (!first) { out.append(",\n"); } out.append("\""); out.append(name); out.append("\""); out.append(":"); } @SuppressWarnings("unused") private static void appendFieldEnd(Appendable out) throws IOException { } /** * Assigns sequential ids to used mappings, and returns the last line mapped. */ private int prepMappings() throws IOException { // Mark any unused mappings. (new MappingTraversal()).traverse(new UsedMappingCheck()); // Renumber used mappings and keep track of the last line. int id = 0; int maxLine = 0; for (Mapping m : mappings) { if (m.used) { m.id = id++; int endPositionLine = m.endPosition.getLine(); maxLine = Math.max(maxLine, endPositionLine); } } // Adjust for the prefix. return maxLine + prefixPosition.getLine(); } /** * A mapping from a given position in an input source file to a given position * in the generated code. */ static class Mapping { /** * A unique ID for this mapping for record keeping purposes. */ int id = UNMAPPED; /** * The source file index. */ String sourceFile; /** * The position of the code in the input source file. Both * the line number and the character index are indexed by * 1 for legacy reasons via the Rhino Node class. */ FilePosition originalPosition; /** * The starting position of the code in the generated source * file which this mapping represents. Indexed by 0. */ FilePosition startPosition; /** * The ending position of the code in the generated source * file which this mapping represents. Indexed by 0. */ FilePosition endPosition; /** * The original name of the token found at the position * represented by this mapping (if any). */ String originalName; /** * Whether the mapping is actually used by the source map. */ boolean used = false; } /** * Mark any visited mapping as "used". */ private static class UsedMappingCheck implements MappingVisitor { /** * @throws IOException */ @Override public void visit(Mapping m, int line, int col, int nextLine, int nextCol) throws IOException { if (m != null) { m.used = true; } } } private interface MappingVisitor { /** * @param m The mapping for the current code segment. null if the segment * is unmapped. * @param line The starting line for this code segment. * @param col The starting column for this code segment. * @param endLine The ending line * @param endCol The ending column * @throws IOException */ void visit(Mapping m, int line, int col, int endLine, int endCol) throws IOException; } /** * Walk the mappings and visit each segment of the mappings, unmapped * segments are visited with a null mapping, unused mapping are not visited. */ private class MappingTraversal { // The last line and column written private int line; private int col; MappingTraversal() { } // Append the line mapping entries. void traverse(MappingVisitor v) throws IOException { // The mapping list is ordered as a pre-order traversal. The mapping // positions give us enough information to rebuild the stack and this // allows the building of the source map in O(n) time. Deque stack = new ArrayDeque<>(); for (Mapping m : mappings) { // Find the closest ancestor of the current mapping: // An overlapping mapping is an ancestor of the current mapping, any // non-overlapping mappings are siblings (or cousins) and must be // closed in the reverse order of when they encountered. while (!stack.isEmpty() && !isOverlapped(stack.peek(), m)) { Mapping previous = stack.pop(); maybeVisit(v, previous); } // Any gaps between the current line position and the start of the // current mapping belong to the parent. Mapping parent = stack.peek(); maybeVisitParent(v, parent, m); stack.push(m); } // There are no more children to be had, simply close the remaining // mappings in the reverse order of when they encountered. while (!stack.isEmpty()) { Mapping m = stack.pop(); maybeVisit(v, m); } } /** * @return The line adjusted for the prefix position. */ private int getAdjustedLine(FilePosition p) { return p.getLine() + prefixPosition.getLine(); } /** * @return The column adjusted for the prefix position. */ private int getAdjustedCol(FilePosition p) { int rawLine = p.getLine(); int rawCol = p.getColumn(); // Only the first line needs the character position adjusted. return (rawLine != 0) ? rawCol : rawCol + prefixPosition.getColumn(); } /** * @return Whether m1 ends before m2 starts. */ private boolean isOverlapped(Mapping m1, Mapping m2) { // No need to use adjusted values here, relative positions are sufficient. int l1 = m1.endPosition.getLine(); int l2 = m2.startPosition.getLine(); int c1 = m1.endPosition.getColumn(); int c2 = m2.startPosition.getColumn(); return (l1 == l2 && c1 >= c2) || l1 > l2; } /** * Write any needed entries from the current position to the end of the * provided mapping. */ private void maybeVisit(MappingVisitor v, Mapping m) throws IOException { int nextLine = getAdjustedLine(m.endPosition); int nextCol = getAdjustedCol(m.endPosition); // If this anything remaining in this mapping beyond the // current line and column position, write it out now. if (line < nextLine || (line == nextLine && col < nextCol)) { visit(v, m, nextLine, nextCol); } } /** * Write any needed entries to complete the provided mapping. */ private void maybeVisitParent(MappingVisitor v, Mapping parent, Mapping m) throws IOException { int nextLine = getAdjustedLine(m.startPosition); int nextCol = getAdjustedCol(m.startPosition); // If the previous value is null, no mapping exists. checkState(line < nextLine || col <= nextCol); if (line < nextLine || (line == nextLine && col < nextCol)) { visit(v, parent, nextLine, nextCol); } } /** * Write any entries needed between the current position the next position * and update the current position. */ private void visit(MappingVisitor v, Mapping m, int nextLine, int nextCol) throws IOException { checkState(line <= nextLine); checkState(line < nextLine || col < nextCol); if (line == nextLine && col == nextCol) { // Nothing to do. throw new IllegalStateException(); } v.visit(m, line, col, nextLine, nextCol); line = nextLine; col = nextCol; } } /** * Appends the index source map to the given buffer. * * @param out The stream to which the map will be appended. * @param name The name of the generated source file that this source map * represents. * @param sections An ordered list of map sections to include in the index. * @throws IOException */ @Override public void appendIndexMapTo( Appendable out, String name, List sections) throws IOException { // Add the header fields. out.append("{\n"); appendFirstField(out, "version", "3"); appendField(out, "file", escapeString(name)); // Add the line character maps. appendFieldStart(out, "sections"); out.append("[\n"); boolean first = true; for (SourceMapSection section : sections) { if (first) { first = false; } else { out.append(",\n"); } out.append("{\n"); appendFieldStart(out, "offset", true); appendOffsetValue(out, section.getLine(), section.getColumn()); if (section.getSectionType() == SourceMapSection.SectionType.URL) { appendField(out, "url", escapeString(section.getSectionValue())); } else if (section.getSectionType() == SourceMapSection.SectionType.MAP) { appendField(out, "map", section.getSectionValue()); } else { throw new IOException("Unexpected section type"); } out.append("\n}"); } out.append("\n]"); appendFieldEnd(out); out.append("\n}\n"); } private static void appendOffsetValue(Appendable out, int line, int column) throws IOException { out.append("{\n"); appendFirstField(out, "line", String.valueOf(line)); appendField(out, "column", String.valueOf(column)); out.append("\n}"); } private int getSourceId(String sourceName) { if (!Objects.equals(sourceName, lastSourceFile)) { lastSourceFile = sourceName; Integer index = sourceFileMap.get(sourceName); if (index != null) { lastSourceFileIndex = index; } else { lastSourceFileIndex = sourceFileMap.size(); sourceFileMap.put(sourceName, lastSourceFileIndex); } } return lastSourceFileIndex; } private int getNameId(String symbolName) { int originalNameIndex; Integer index = originalNameMap.get(symbolName); if (index != null) { originalNameIndex = index; } else { originalNameIndex = originalNameMap.size(); originalNameMap.put(symbolName, originalNameIndex); } return originalNameIndex; } private class LineMapper implements MappingVisitor { // The destination. private final Appendable out; private final int maxLine; // TODO(johnlenz): This shouldn't be necessary to track. private int previousLine = -1; private int previousColumn = 0; // Previous values used for storing relative ids. private int previousSourceFileId; private int previousSourceLine; private int previousSourceColumn; private int previousNameId; LineMapper(Appendable out, int maxLine) { this.out = out; this.maxLine = maxLine; } /** * As each segment is visited write out the appropriate line mapping. */ @Override public void visit(Mapping m, int line, int col, int nextLine, int nextCol) throws IOException { if (previousLine != line) { previousColumn = 0; } if (line != nextLine || col != nextCol) { // TODO(johnlenz): For some reason, we have mappings beyond the max line. // So far they're just null mappings and we can ignore them. // (If they're non-null, we assert-fail.) if (line < maxLine) { if (previousLine == line) { // not the first entry for the line out.append(','); } writeEntry(m, col); previousLine = line; previousColumn = col; } else { checkState(m == null); } } for (int i = line; i <= nextLine && i < maxLine; i++) { if (i == nextLine) { break; } closeLine(false); openLine(false); } } /** * Writes an entry for the given column (of the generated text) and * associated mapping. * The values are stored as relative to the last seen values for each * field and encoded as Base64VLQs. */ void writeEntry(Mapping m, int column) throws IOException { // The relative generated column number Base64VLQ.encode(out, column - previousColumn); previousColumn = column; if (m != null) { // The relative source file id int sourceId = getSourceId(m.sourceFile); Base64VLQ.encode(out, sourceId - previousSourceFileId); previousSourceFileId = sourceId; // The relative source file line and column int srcline = m.originalPosition.getLine(); int srcColumn = m.originalPosition.getColumn(); Base64VLQ.encode(out, srcline - previousSourceLine); previousSourceLine = srcline; Base64VLQ.encode(out, srcColumn - previousSourceColumn); previousSourceColumn = srcColumn; if (m.originalName != null) { // The relative id for the associated symbol name int nameId = getNameId(m.originalName); Base64VLQ.encode(out, (nameId - previousNameId)); previousNameId = nameId; } } } // Append the line mapping entries. void appendLineMappings() throws IOException { // Start the first line. openLine(true); (new MappingTraversal()).traverse(this); // And close the final line. closeLine(true); } /** * Begin the entry for a new line. */ private void openLine(boolean firstEntry) throws IOException { if (firstEntry) { out.append('\"'); } } /** * End the entry for a line. */ private void closeLine(boolean finalEntry) throws IOException { out.append(';'); if (finalEntry) { out.append('\"'); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy