summaryrefslogtreecommitdiff
path: root/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala
blob: 6f856a9871ccf2d3a4ebb6d724f4d4e0d327eb19 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/*                     __                                               *\
**     ________ ___   / /  ___      __ ____  Scala.js sbt plugin        **
**    / __/ __// _ | / /  / _ | __ / // __/  (c) 2013, LAMP/EPFL        **
**  __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \    http://scala-js.org/       **
** /____/\___/_/ |_/____/_/ | |__/ /____/                               **
**                          |/____/                                     **
\*                                                                      */


package scala.scalajs.tools.sourcemap

import scala.scalajs.tools.classpath._

import com.google.debugging.sourcemap._

class SourceMapper(classpath: CompleteClasspath) {

  def map(ste: StackTraceElement, columnNumber: Int): StackTraceElement = {
    val mapped = for {
      sourceMap <- findSourceMap(ste.getFileName)
    } yield map(ste, columnNumber, sourceMap)

    mapped.getOrElse(ste)
  }

  def map(ste: StackTraceElement, columnNumber: Int,
      sourceMap: String): StackTraceElement = {

    val sourceMapConsumer =
      SourceMapConsumerFactory
        .parse(sourceMap)
        .asInstanceOf[SourceMapConsumerV3]

    /* **Attention**
     * - StackTrace positions are 1-based
     * - SourceMapConsumer.getMappingForLine() is 1-based
     */

    val lineNumber = ste.getLineNumber
    val column =
      if (columnNumber == -1) getFirstColumn(sourceMapConsumer, lineNumber)
      else columnNumber

    val originalMapping =
      sourceMapConsumer.getMappingForLine(lineNumber, column)

    if (originalMapping != null) {
      new StackTraceElement(
          ste.getClassName,
          ste.getMethodName,
          originalMapping.getOriginalFile,
          originalMapping.getLineNumber)
    } else ste
  }

  /** both `lineNumber` and the resulting column are 1-based */
  private def getFirstColumn(sourceMapConsumer: SourceMapConsumerV3,
      lineNumber1Based: Int) = {

    /* **Attention**
     * - SourceMapConsumerV3.EntryVisitor is 0-based!!!
     */

    val lineNumber = lineNumber1Based - 1

    var column: Option[Int] = None

    sourceMapConsumer.visitMappings(
      new SourceMapConsumerV3.EntryVisitor {
        def visit(sourceName: String,
          symbolName: String,
          sourceStartPosition: FilePosition,
          startPosition: FilePosition,
          endPosition: FilePosition): Unit =
          if (!column.isDefined && startPosition.getLine == lineNumber)
            column = Some(startPosition.getColumn)
      })

    val column0Based = column.getOrElse(0)
    column0Based + 1
  }

  private def findSourceMap(path: String) = {
    val candidates = classpath.allCode.filter(_.path == path)
    if (candidates.size != 1) None // better no sourcemap than a wrong one
    else candidates.head.sourceMap
  }
}