forked from scala/scala3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTools.scala
150 lines (136 loc) · 6.55 KB
/
Tools.scala
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package dotty.tools.dotc.semanticdb
import java.nio.file._
import java.nio.charset.StandardCharsets
import scala.jdk.CollectionConverters._
import dotty.tools.dotc.util.SourceFile
import dotty.tools.dotc.semanticdb.Scala3.given
object Tools:
/** Converts a Path to a String that is URI encoded, without forcing absolute paths. */
def mkURIstring(path: Path): String =
// Calling `.toUri` on a relative path will convert it to absolute. Iteration through its parts instead preserves
// the resulting URI as relative.
// To prevent colon `:` from being treated as a scheme separator, prepend a slash `/` to each part to trick the URI
// parser into treating it as an absolute path, and then strip the spurious leading slash from the final result.
val uriParts = for part <- path.asScala yield new java.net.URI(null, null, "/" + part.toString, null)
uriParts.mkString.stripPrefix("/")
/** Load SemanticDB TextDocument for a single Scala source file
*
* @param scalaAbsolutePath Absolute path to a Scala source file.
* @param scalaRelativePath scalaAbsolutePath relativized by the sourceroot.
* @param semanticdbAbsolutePath Absolute path to the SemanticDB file.
*/
def loadTextDocument(
scalaAbsolutePath: Path,
scalaRelativePath: Path,
semanticdbAbsolutePath: Path
): TextDocument =
val reluri = mkURIstring(scalaRelativePath)
val sdocs = parseTextDocuments(semanticdbAbsolutePath)
sdocs.documents.find(_.uri == reluri) match
case None => throw new NoSuchElementException(s"$scalaRelativePath")
case Some(document) =>
val text = new String(Files.readAllBytes(scalaAbsolutePath), StandardCharsets.UTF_8)
// Assert the SemanticDB payload is in-sync with the contents of the Scala file on disk.
val md5FingerprintOnDisk = internal.MD5.compute(text)
if document.md5 != md5FingerprintOnDisk then
throw new IllegalArgumentException(s"stale semanticdb: $scalaRelativePath")
else
// Update text document to include full text contents of the file.
document.copy(text = text)
end loadTextDocument
def loadTextDocumentUnsafe(scalaAbsolutePath: Path, semanticdbAbsolutePath: Path): TextDocument =
val docs = parseTextDocuments(semanticdbAbsolutePath).documents
assert(docs.length == 1)
docs.head.copy(text = new String(Files.readAllBytes(scalaAbsolutePath), StandardCharsets.UTF_8))
/** Parses SemanticDB text documents from an absolute path to a `*.semanticdb` file. */
private def parseTextDocuments(path: Path): TextDocuments =
val bytes = Files.readAllBytes(path).nn // NOTE: a semanticdb file is a TextDocuments message, not TextDocument
TextDocuments.parseFrom(bytes)
def metac(doc: TextDocument, realPath: Path)(using sb: StringBuilder): StringBuilder =
val symtab = PrinterSymtab.fromTextDocument(doc)
val symPrinter = SymbolInformationPrinter(symtab)
val realURI = realPath.toString
given sourceFile: SourceFile = SourceFile.virtual(doc.uri, doc.text)
val synthPrinter = SyntheticPrinter(symtab, sourceFile)
sb.append(realURI).nl
sb.append("-" * realURI.length).nl
sb.nl
sb.append("Summary:").nl
sb.append("Schema => ").append(schemaString(doc.schema)).nl
sb.append("Uri => ").append(doc.uri).nl
sb.append("Text => empty").nl
sb.append("Language => ").append(languageString(doc.language)).nl
sb.append("Symbols => ").append(doc.symbols.length).append(" entries").nl
sb.append("Occurrences => ").append(doc.occurrences.length).append(" entries").nl
if doc.diagnostics.nonEmpty then
sb.append("Diagnostics => ").append(doc.diagnostics.length).append(" entries").nl
if doc.synthetics.nonEmpty then
sb.append("Synthetics => ").append(doc.synthetics.length).append(" entries").nl
sb.nl
sb.append("Symbols:").nl
doc.symbols.sorted.foreach(s => processSymbol(s, symPrinter))
sb.nl
sb.append("Occurrences:").nl
doc.occurrences.sorted.foreach(processOccurrence)
sb.nl
if doc.diagnostics.nonEmpty then
sb.append("Diagnostics:").nl
doc.diagnostics.sorted.foreach(d => processDiag(d))
sb.nl
if doc.synthetics.nonEmpty then
sb.append("Synthetics:").nl
doc.synthetics.sorted.foreach(s => processSynth(s, synthPrinter))
sb.nl
sb
end metac
private def schemaString(schema: Schema) =
import Schema._
schema match
case SEMANTICDB3 => "SemanticDB v3"
case SEMANTICDB4 => "SemanticDB v4"
case LEGACY => "SemanticDB legacy"
case Unrecognized(_) => "unknown"
end schemaString
private def languageString(language: Language) =
import Language._
language match
case SCALA => "Scala"
case JAVA => "Java"
case UNKNOWN_LANGUAGE | Unrecognized(_) => "unknown"
end languageString
private def processSymbol(info: SymbolInformation, printer: SymbolInformationPrinter)(using sb: StringBuilder): Unit =
sb.append(printer.pprintSymbolInformation(info)).nl
private def processSynth(synth: Synthetic, printer: SyntheticPrinter)(using sb: StringBuilder): Unit =
sb.append(printer.pprint(synth)).nl
private def processDiag(d: Diagnostic)(using sb: StringBuilder): Unit =
d.range match
case Some(range) => processRange(sb, range)
case _ => sb.append("[):")
sb.append(" ")
d.severity match
case Diagnostic.Severity.ERROR => sb.append("[error]")
case Diagnostic.Severity.WARNING => sb.append("[warning]")
case Diagnostic.Severity.INFORMATION => sb.append("[info]")
case _ => sb.append("[unknown]")
sb.append(" ")
sb.append(d.message)
sb.nl
private def processOccurrence(occ: SymbolOccurrence)(using sb: StringBuilder, sourceFile: SourceFile): Unit =
occ.range match
case Some(range) =>
processRange(sb, range)
if range.endLine == range.startLine
&& range.startCharacter != range.endCharacter
&& !(occ.symbol.isConstructor && occ.role.isDefinition) then
val line = sourceFile.lineContent(sourceFile.lineToOffset(range.startLine))
assert(range.startCharacter <= line.length && range.endCharacter <= line.length,
s"Line is only ${line.length} - start line was ${range.startLine} in source ${sourceFile.name}"
)
sb.append(" ").append(line.substring(range.startCharacter, range.endCharacter))
case _ =>
sb.append("[):")
end match
sb.append(if occ.role.isReference then " -> " else " <- ").append(occ.symbol).nl
end processOccurrence
extension (sb: StringBuilder)
private inline def nl = sb.append(System.lineSeparator)