@@ -21,20 +21,34 @@ import transform.SymUtils._
21
21
22
22
import scala .collection .mutable
23
23
import scala .annotation .{ threadUnsafe => tu , tailrec }
24
+ import scala .jdk .CollectionConverters ._
24
25
import scala .PartialFunction .condOpt
26
+ import typer .ImportInfo .withRootImports
25
27
26
28
import dotty .tools .dotc .{semanticdb => s }
27
29
import dotty .tools .io .{AbstractFile , JarArchive }
30
+ import dotty .tools .dotc .semanticdb .DiagnosticOps .*
31
+ import scala .util .{Using , Failure , Success }
32
+
28
33
29
34
/** Extract symbol references and uses to semanticdb files.
30
35
* See https://scalameta.org/docs/semanticdb/specification.html#symbol-1
31
36
* for a description of the format.
32
- * TODO: Also extract type information
37
+ *
38
+ * Here, we define two phases for "ExtractSemanticDB", "PostTyper" and "PostInlining".
39
+ *
40
+ * The "PostTyper" phase extracts SemanticDB information such as symbol
41
+ * definitions, symbol occurrences, type information, and synthetics
42
+ * and write .semanticdb file.
43
+ *
44
+ * The "PostInlining" phase extracts diagnostics from "ctx.reporter" and
45
+ * attaches them to the SemanticDB information extracted in the "PostTyper" phase.
46
+ * We need to run this phase after the "CheckUnused.PostInlining" phase
47
+ * so that we can extract the warnings generated by "-Wunused".
33
48
*/
34
- class ExtractSemanticDB extends Phase :
35
- import Scala3 .{_ , given }
49
+ class ExtractSemanticDB private (phaseMode : ExtractSemanticDB .PhaseMode ) extends Phase :
36
50
37
- override val phaseName : String = ExtractSemanticDB .name
51
+ override val phaseName : String = ExtractSemanticDB .phaseNamePrefix + phaseMode.toString()
38
52
39
53
override val description : String = ExtractSemanticDB .description
40
54
@@ -46,14 +60,145 @@ class ExtractSemanticDB extends Phase:
46
60
// Check not needed since it does not transform trees
47
61
override def isCheckable : Boolean = false
48
62
49
- override def run (using Context ): Unit =
50
- val unit = ctx.compilationUnit
51
- val extractor = Extractor ()
52
- extractor.extract(unit.tpdTree)
53
- ExtractSemanticDB .write(unit.source, extractor.occurrences.toList, extractor.symbolInfos.toList, extractor.synthetics.toList)
63
+ override def runOn (units : List [CompilationUnit ])(using ctx : Context ): List [CompilationUnit ] = {
64
+ val sourceRoot = ctx.settings.sourceroot.value
65
+ val appendDiagnostics = phaseMode == ExtractSemanticDB .PhaseMode .AppendDiagnostics
66
+ if (appendDiagnostics)
67
+ val warnings = ctx.reporter.allWarnings.groupBy(w => w.pos.source)
68
+ units.flatMap { unit =>
69
+ warnings.get(unit.source).map { ws =>
70
+ val unitCtx = ctx.fresh.setCompilationUnit(unit).withRootImports
71
+ val outputDir =
72
+ ExtractSemanticDB .semanticdbPath(
73
+ unit.source,
74
+ ExtractSemanticDB .semanticdbOutDir(using unitCtx),
75
+ sourceRoot
76
+ )
77
+ (outputDir, ws.map(_.toSemanticDiagnostic))
78
+ }
79
+ }.asJava.parallelStream().forEach { case (out, warnings) =>
80
+ ExtractSemanticDB .appendDiagnostics(warnings, out)
81
+ }
82
+ else
83
+ val writeSemanticdbText = ctx.settings.semanticdbText.value
84
+ units.foreach { unit =>
85
+ val unitCtx = ctx.fresh.setCompilationUnit(unit).withRootImports
86
+ val outputDir =
87
+ ExtractSemanticDB .semanticdbPath(
88
+ unit.source,
89
+ ExtractSemanticDB .semanticdbOutDir(using unitCtx),
90
+ sourceRoot
91
+ )
92
+ val extractor = ExtractSemanticDB .Extractor ()
93
+ extractor.extract(unit.tpdTree)(using unitCtx)
94
+ ExtractSemanticDB .write(
95
+ unit.source,
96
+ extractor.occurrences.toList,
97
+ extractor.symbolInfos.toList,
98
+ extractor.synthetics.toList,
99
+ outputDir,
100
+ sourceRoot,
101
+ writeSemanticdbText
102
+ )
103
+ }
104
+ units
105
+ }
106
+
107
+ def run (using Context ): Unit = unsupported(" run" )
108
+ end ExtractSemanticDB
109
+
110
+ object ExtractSemanticDB :
111
+ import java .nio .file .Path
112
+ import java .nio .file .Files
113
+ import java .nio .file .Paths
114
+
115
+ val phaseNamePrefix : String = " extractSemanticDB"
116
+ val description : String = " extract info into .semanticdb files"
117
+
118
+ enum PhaseMode :
119
+ case ExtractSemanticInfo
120
+ case AppendDiagnostics
121
+
122
+ class ExtractSemanticInfo extends ExtractSemanticDB (PhaseMode .ExtractSemanticInfo )
123
+
124
+ class AppendDiagnostics extends ExtractSemanticDB (PhaseMode .AppendDiagnostics )
125
+
126
+ private def semanticdbTarget (using Context ): Option [Path ] =
127
+ Option (ctx.settings.semanticdbTarget.value)
128
+ .filterNot(_.isEmpty)
129
+ .map(Paths .get(_))
130
+
131
+ /** Destination for generated classfiles */
132
+ private def outputDirectory (using Context ): AbstractFile =
133
+ ctx.settings.outputDir.value
134
+
135
+ /** Output directory for SemanticDB files */
136
+ private def semanticdbOutDir (using Context ): Path =
137
+ semanticdbTarget.getOrElse(outputDirectory.jpath)
138
+
139
+ private def absolutePath (path : Path ): Path = path.toAbsolutePath.normalize
140
+
141
+ private def write (
142
+ source : SourceFile ,
143
+ occurrences : List [SymbolOccurrence ],
144
+ symbolInfos : List [SymbolInformation ],
145
+ synthetics : List [Synthetic ],
146
+ outpath : Path ,
147
+ sourceRoot : String ,
148
+ semanticdbText : Boolean
149
+ ): Unit =
150
+ Files .createDirectories(outpath.getParent())
151
+ val doc : TextDocument = TextDocument (
152
+ schema = Schema .SEMANTICDB4 ,
153
+ language = Language .SCALA ,
154
+ uri = Tools .mkURIstring(Paths .get(relPath(source, sourceRoot))),
155
+ text = if semanticdbText then String (source.content) else " " ,
156
+ md5 = internal.MD5 .compute(String (source.content)),
157
+ symbols = symbolInfos,
158
+ occurrences = occurrences,
159
+ synthetics = synthetics,
160
+ )
161
+ val docs = TextDocuments (List (doc))
162
+ val out = Files .newOutputStream(outpath)
163
+ try
164
+ val stream = internal.SemanticdbOutputStream .newInstance(out)
165
+ docs.writeTo(stream)
166
+ stream.flush()
167
+ finally
168
+ out.close()
169
+ end write
170
+
171
+ private def appendDiagnostics (
172
+ diagnostics : Seq [Diagnostic ],
173
+ outpath : Path
174
+ ): Unit =
175
+ Using .Manager { use =>
176
+ val in = use(Files .newInputStream(outpath))
177
+ val sin = internal.SemanticdbInputStream .newInstance(in)
178
+ val docs = TextDocuments .parseFrom(sin)
179
+
180
+ val out = use(Files .newOutputStream(outpath))
181
+ val sout = internal.SemanticdbOutputStream .newInstance(out)
182
+ TextDocuments (docs.documents.map(_.withDiagnostics(diagnostics))).writeTo(sout)
183
+ sout.flush()
184
+ } match
185
+ case Failure (ex) => // failed somehow, should we say something?
186
+ case Success (_) => // success to update semanticdb, say nothing
187
+ end appendDiagnostics
188
+
189
+ private def relPath (source : SourceFile , sourceRoot : String ) =
190
+ SourceFile .relativePath(source, sourceRoot)
191
+
192
+ private def semanticdbPath (source : SourceFile , base : Path , sourceRoot : String ): Path =
193
+ absolutePath(base)
194
+ .resolve(" META-INF" )
195
+ .resolve(" semanticdb" )
196
+ .resolve(relPath(source, sourceRoot))
197
+ .resolveSibling(source.name + " .semanticdb" )
54
198
55
199
/** Extractor of symbol occurrences from trees */
56
200
class Extractor extends TreeTraverser :
201
+ import Scala3 .{_ , given }
57
202
given s .SemanticSymbolBuilder = s.SemanticSymbolBuilder ()
58
203
val synth = SyntheticsExtractor ()
59
204
given converter : s.TypeOps = s.TypeOps ()
@@ -465,55 +610,5 @@ class ExtractSemanticDB extends Phase:
465
610
registerSymbol(vparam.symbol, symkinds)
466
611
traverse(vparam.tpt)
467
612
tparams.foreach(tp => traverse(tp.rhs))
468
-
469
-
470
- object ExtractSemanticDB :
471
- import java .nio .file .Path
472
- import java .nio .file .Files
473
- import java .nio .file .Paths
474
-
475
- val name : String = " extractSemanticDB"
476
- val description : String = " extract info into .semanticdb files"
477
-
478
- private def semanticdbTarget (using Context ): Option [Path ] =
479
- Option (ctx.settings.semanticdbTarget.value)
480
- .filterNot(_.isEmpty)
481
- .map(Paths .get(_))
482
-
483
- private def semanticdbText (using Context ): Boolean =
484
- ctx.settings.semanticdbText.value
485
-
486
- private def outputDirectory (using Context ): AbstractFile = ctx.settings.outputDir.value
487
-
488
- def write (
489
- source : SourceFile ,
490
- occurrences : List [SymbolOccurrence ],
491
- symbolInfos : List [SymbolInformation ],
492
- synthetics : List [Synthetic ],
493
- )(using Context ): Unit =
494
- def absolutePath (path : Path ): Path = path.toAbsolutePath.normalize
495
- val relPath = SourceFile .relativePath(source, ctx.settings.sourceroot.value)
496
- val outpath = absolutePath(semanticdbTarget.getOrElse(outputDirectory.jpath))
497
- .resolve(" META-INF" )
498
- .resolve(" semanticdb" )
499
- .resolve(relPath)
500
- .resolveSibling(source.name + " .semanticdb" )
501
- Files .createDirectories(outpath.getParent())
502
- val doc : TextDocument = TextDocument (
503
- schema = Schema .SEMANTICDB4 ,
504
- language = Language .SCALA ,
505
- uri = Tools .mkURIstring(Paths .get(relPath)),
506
- text = if semanticdbText then String (source.content) else " " ,
507
- md5 = internal.MD5 .compute(String (source.content)),
508
- symbols = symbolInfos,
509
- occurrences = occurrences,
510
- synthetics = synthetics,
511
- )
512
- val docs = TextDocuments (List (doc))
513
- val out = Files .newOutputStream(outpath)
514
- try
515
- val stream = internal.SemanticdbOutputStream .newInstance(out)
516
- docs.writeTo(stream)
517
- stream.flush()
518
- finally
519
- out.close()
613
+ end Extractor
614
+ end ExtractSemanticDB
0 commit comments