@@ -21,20 +21,34 @@ import transform.SymUtils._
2121
2222import scala .collection .mutable
2323import scala .annotation .{ threadUnsafe => tu , tailrec }
24+ import scala .jdk .CollectionConverters ._
2425import scala .PartialFunction .condOpt
26+ import typer .ImportInfo .withRootImports
2527
2628import dotty .tools .dotc .{semanticdb => s }
2729import dotty .tools .io .{AbstractFile , JarArchive }
30+ import dotty .tools .dotc .semanticdb .DiagnosticOps .*
31+ import scala .util .{Using , Failure , Success }
32+
2833
2934/** Extract symbol references and uses to semanticdb files.
3035 * See https://scalameta.org/docs/semanticdb/specification.html#symbol-1
3136 * 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".
3348 */
34- class ExtractSemanticDB extends Phase :
35- import Scala3 .{_ , given }
49+ class ExtractSemanticDB private (phaseMode : ExtractSemanticDB .PhaseMode ) extends Phase :
3650
37- override val phaseName : String = ExtractSemanticDB .name
51+ override val phaseName : String = ExtractSemanticDB .phaseNamePrefix + phaseMode.toString()
3852
3953 override val description : String = ExtractSemanticDB .description
4054
@@ -46,14 +60,145 @@ class ExtractSemanticDB extends Phase:
4660 // Check not needed since it does not transform trees
4761 override def isCheckable : Boolean = false
4862
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" )
54198
55199 /** Extractor of symbol occurrences from trees */
56200 class Extractor extends TreeTraverser :
201+ import Scala3 .{_ , given }
57202 given s .SemanticSymbolBuilder = s.SemanticSymbolBuilder ()
58203 val synth = SyntheticsExtractor ()
59204 given converter : s.TypeOps = s.TypeOps ()
@@ -465,55 +610,5 @@ class ExtractSemanticDB extends Phase:
465610 registerSymbol(vparam.symbol, symkinds)
466611 traverse(vparam.tpt)
467612 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