Skip to content

Commit 6361419

Browse files
committed
Make semanticdb cli able to generate a semanticdb file for a given scala file
1 parent c81c79c commit 6361419

File tree

5 files changed

+210
-41
lines changed

5 files changed

+210
-41
lines changed

semanticdb/src/dotty/semanticdb/Main.scala

Lines changed: 78 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,99 @@ import scala.tasty.file._
55
import scala.NotImplementedError
66

77
import dotty.tools.dotc.Driver
8+
import dotty.tools.dotc.reporting.Reporter
9+
10+
import java.io.File
11+
import java.nio.file._
812

913
object Main {
10-
def parseArguments(args: Array[String]): (Map[String, String], Boolean) = {
11-
def nextArgument(optionMap: Map[String, String], args: List[String]): Map[String, String] = args match {
12-
case "--out" :: file :: tail => nextArgument(optionMap + ("outfile" -> file), tail)
13-
case "-o" :: file :: tail => nextArgument(optionMap + ("outfile" -> file), tail)
14+
val userHome = System.getProperty("user.home")
15+
val classpaths =
16+
userHome + "/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.12.8.jar" ::
17+
"out/bootstrap/dotty-library-bootstrapped/scala-0.12/dotty-library_0.12-0.12.0-bin-SNAPSHOT.jar" :: Nil
18+
19+
val help = """Usage semanticdb [options] [file]
20+
|Generate semanticdb's information related to the source file [file]
21+
|Options are:
22+
| -h,--help Show help
23+
| -o <file>, --out <file> Place the output into <file> (default: out.semanticdb)
24+
| -t <folder>, --temp <folder> Use <folder> as the temp directory to store build artifacts
25+
""".stripMargin
26+
27+
type CliArgs = Map[String, String]
28+
29+
def parseArguments(args: Array[String]): Option[CliArgs] = {
30+
val optRegex = "$-.*".r
31+
def nextArgument(optionMap: CliArgs, args: List[String]): Option[CliArgs] = args match {
32+
case "--out" :: file :: tail => nextArgument(optionMap + ("out" -> file), tail)
33+
case "-o" :: file :: tail => nextArgument(optionMap + ("out" -> file), tail)
1434
case "--help" :: tail => nextArgument(optionMap + ("help" -> ""), tail)
1535
case "-h" :: tail => nextArgument(optionMap + ("help" -> ""), tail)
16-
case "--temp" :: folder :: tail => nextArgument(optionMap + ("temp" -> folder), tail)
17-
case "-t" :: folder :: tail => nextArgument(optionMap + ("temp" -> folder), tail)
36+
case "--classpath" :: folder :: tail => nextArgument(optionMap + ("classpath" -> folder), tail)
37+
case "-c" :: folder :: tail => nextArgument(optionMap + ("classpath" -> folder), tail)
38+
case optRegex(_) :: _=> None
1839
case file :: tail => nextArgument(optionMap + ("input" -> file), tail)
19-
case Nil => optionMap
40+
case Nil => Some(optionMap)
2041
}
2142

22-
val help = """Usage semanticdb [options] [file]
23-
|Generate semanticdb's information related to the source file [file]
24-
|Options are:
25-
| -h,--help Show help
26-
| -o <file>, --out <file> Place the output into <file>
27-
| -t <folder>, --temp <folder> Use <folder> as the temp directory to store build artifacts
28-
""".stripMargin
29-
30-
var optionMap = nextArgument(Map(), args.toList)
31-
if (optionMap.contains("help") || !optionMap.contains("input")) {
32-
println(help)
33-
return (optionMap, false)
34-
} else {
35-
if (!optionMap.contains("temp")) {
36-
optionMap += ("temp" -> "/home/pierre/epfl/semester/dotty/semanticdb/testClass/")
43+
nextArgument(Map(), args.toList) match {
44+
case Some(args : CliArgs) => {
45+
var cleanedArgs = args
46+
cleanedArgs += "out" -> cleanedArgs.getOrElse("out", "out.semanticdb")
47+
if (cleanedArgs.contains("help") || !cleanedArgs.contains("input")) {
48+
return None
49+
} else {
50+
cleanedArgs += "classpath" -> cleanedArgs.getOrElse("classpath", Files.createTempDirectory("semanticdb").toString)
51+
val tempFolder = new File(cleanedArgs("classpath"));
52+
if (!tempFolder.exists()){
53+
tempFolder.mkdir();
54+
}
55+
return Some(cleanedArgs)
3756
}
38-
val driver = new Driver
39-
val compilerParams : List[String] =
40-
optionMap("input") ::
41-
"-Yno-inline" ::
42-
"-d" :: optionMap("temp") ::
43-
"-classpath" :: "/home/pierre/epfl/semester/dotty/library/../out/bootstrap/dotty-library-bootstrapped/scala-0.12/dotty-library_0.12-0.12.0-bin-SNAPSHOT.jar" ::
44-
Nil
45-
46-
val foo = driver.process(compilerParams.toArray)
47-
return (optionMap, true)
4857
}
58+
case None => None
59+
}
60+
}
61+
62+
def compile(cliArgs : CliArgs) : Reporter
63+
= {
64+
val driver = new Driver
65+
val compilerParams : List[String] =
66+
"-classpath" :: classpaths.mkString(":") ::
67+
"-Yno-inline" ::
68+
"-d" :: cliArgs("classpath") ::
69+
cliArgs("input") ::
70+
Nil
71+
72+
val reporter = driver.process(compilerParams.toArray)
73+
return reporter
4974
}
5075

5176

5277
def main(args: Array[String]): Unit = {
5378
val extraClasspath = "." // TODO allow to set it from the args with -classpath XYZ
5479
val classes = args.toList
55-
val (optionMap, canTreat) = parseArguments(args)
56-
if (args.isEmpty) {
57-
println("Dotty Semantic DB: No classes where passed as argument")
58-
} else {
59-
println("Running Dotty Semantic DB on: " + args.mkString(" "))
60-
//ConsumeTasty(extraClasspath, classes, new SemanticdbConsumer())
80+
81+
82+
parseArguments(args) match {
83+
case None => println(help)
84+
case Some(cliArgs) => {
85+
val reporter = compile(cliArgs)
86+
87+
if (reporter.hasErrors) {
88+
println("Compile error:")
89+
println(reporter)
90+
} else {
91+
val classNames = Utils.getClassNames(cliArgs("classpath"), cliArgs("input"))
92+
val scalaFile = Paths.get(cliArgs("input")).toAbsolutePath
93+
val sdbconsumer = new SemanticdbConsumer(scalaFile)
94+
val _ = ConsumeTasty(cliArgs("classpath"), classNames, sdbconsumer)
95+
val textDocument = sdbconsumer.toSemanticdb()
96+
val os = Files.newOutputStream(Paths.get(cliArgs("out")))
97+
try textDocument.writeTo(os)
98+
finally os.close()
99+
}
100+
}
61101
}
62102
}
63103
}

semanticdb/src/dotty/semanticdb/SemanticdbConsumer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ class SemanticdbConsumer(sourceFile: java.nio.file.Path) extends TastyConsumer {
1919
val semantic: s.TextDocument = s.TextDocument()
2020
var occurrences: Seq[s.SymbolOccurrence] = Seq()
2121

22-
def toSemanticdb(text: String): s.TextDocument = {
23-
s.TextDocument(text = text, occurrences = occurrences)
22+
def toSemanticdb(): s.TextDocument = {
23+
s.TextDocument(text = sourceCode, occurrences = occurrences)
2424
}
2525
val package_definitions: Set[Tuple2[String, Int]] = Set()
2626
val symbolsCache: HashMap[(String, s.Range), String] = HashMap()
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package dotty.semanticdb
2+
3+
import scala.tasty.Reflection
4+
5+
import scala.meta.internal.{semanticdb => s}
6+
import scala.tasty.Reflection
7+
import scala.tasty.file.TastyConsumer
8+
import java.lang.reflect.InvocationTargetException
9+
10+
class TastyScalaFileInferrer extends TastyConsumer {
11+
/* Visitor over a tasty tree.
12+
Aims at finding the scala file from where this tree originated.
13+
*/
14+
15+
/* If a scala file was found sourcePath is Some(scalaFile),
16+
Otherwise None */
17+
var sourcePath: Option[String] = None
18+
final def apply(reflect: Reflection)(root: reflect.Tree): Unit = {
19+
import reflect._
20+
object ChildTraverser extends TreeTraverser {
21+
override def traverseTree(tree: Tree)(implicit ctx: Context): Unit =
22+
tree match {
23+
case IsClassDef(cdef) => {
24+
cdef.symbol.annots.foreach { annot =>
25+
annot match {
26+
case Term.Apply(Term.Select(Term.New(t), _),
27+
List(Term.Literal(Constant.String(path))))
28+
if t.symbol.name == "SourceFile" =>
29+
// we found the path to a file. In this case, we do not need to
30+
// continue traversing the tree
31+
sourcePath = Some(path)
32+
case x => super.traverseTree(tree)
33+
}
34+
true
35+
}
36+
}
37+
case _ => {
38+
// If we already found a sourcePath in this tasty file,
39+
// we abort our search here to avoid spending too much time here
40+
if (sourcePath == None)
41+
super.traverseTree(tree)
42+
else
43+
()
44+
}
45+
}
46+
}
47+
ChildTraverser.traverseTree(root)(reflect.rootContext)
48+
}
49+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package dotty.semanticdb
2+
3+
4+
import scala.tasty.Reflection
5+
import scala.tasty.file._
6+
import scala.collection.mutable.HashMap
7+
8+
import org.junit.Test
9+
import org.junit.Assert._
10+
import java.nio.file._
11+
import scala.meta.internal.{semanticdb => s}
12+
import scala.collection.JavaConverters._
13+
import java.io.File
14+
import scala.tasty.Reflection
15+
import scala.tasty.file.TastyConsumer
16+
import java.lang.reflect.InvocationTargetException
17+
18+
object Utils {
19+
/** Infers a tuple (class path, class name) from a given path */
20+
def getClasspathClassname(file: Path): (String, String) = {
21+
val pat = """(.*)\..*""".r
22+
val classpath = file.getParent().getParent().toString()
23+
val modulename = file.getParent().getFileName().toString()
24+
val sourcename =
25+
file.toFile().getName().toString() match {
26+
case pat(name) => name
27+
case _ => ""
28+
}
29+
return (classpath, modulename + "." + sourcename)
30+
}
31+
32+
/** List all tasty files occuring in the folder f or one of its subfolders */
33+
def recursiveListFiles(f: File): Array[File] = {
34+
val pattern = ".*\\.tasty".r
35+
val files = f.listFiles
36+
val folders = files.filter(_.isDirectory)
37+
val tastyfiles = files.filter(_.toString match {
38+
case pattern(x: _*) => true
39+
case _ => false
40+
})
41+
tastyfiles ++ folders.flatMap(recursiveListFiles)
42+
}
43+
44+
/** Returns a mapping from *.scala file to a list of tasty files. */
45+
def getTastyFiles(classPath: Path): HashMap[String, List[Path]] = {
46+
val source_to_tasty: HashMap[String, List[Path]] = HashMap()
47+
val tastyfiles = recursiveListFiles(classPath.toFile())
48+
recursiveListFiles(classPath.toFile()).map(tasty_path => {
49+
val (classpath, classname) = getClasspathClassname(tasty_path.toPath())
50+
// We add an exception here to avoid crashing if we encountered
51+
// a bad tasty file
52+
try {
53+
val inspecter = new TastyScalaFileInferrer
54+
ConsumeTasty(classpath, classname :: Nil, inspecter)
55+
inspecter.sourcePath.foreach(
56+
source =>
57+
source_to_tasty +=
58+
(source -> (tasty_path
59+
.toPath() :: source_to_tasty.getOrElse(source, Nil))))
60+
} catch {
61+
case _: InvocationTargetException => println(tasty_path)
62+
}
63+
})
64+
source_to_tasty
65+
}
66+
67+
/*
68+
Returns the list of names of class defined inside the scala file [scalaFile]
69+
extracted from the compilation artifacts found in [classPath].
70+
*/
71+
def getClassNames(classPath: String, scalaFile: String): List[String] = {
72+
val tastyFiles =
73+
getTastyFiles(Paths.get(classPath).toAbsolutePath)
74+
.getOrElse(Paths.get(scalaFile).toAbsolutePath.toString, Nil)
75+
76+
val tastyClasses = tastyFiles.map(getClasspathClassname)
77+
val (_, classnames) = tastyClasses.unzip
78+
return classnames
79+
}
80+
}

semanticdb/test/dotty/semanticdb/Tests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class Tests {
132132
val sdbconsumer = new SemanticdbConsumer(scalaFile)
133133

134134
val _ = ConsumeTasty(classpaths.head, classnames, sdbconsumer)
135-
sdbconsumer.toSemanticdb(scalac.text)
135+
sdbconsumer.toSemanticdb()
136136
}
137137
}
138138

0 commit comments

Comments
 (0)