11package dotty .tools .languageserver .util .server
22
33import java .io .PrintWriter
4+ import java .io .File .{pathSeparator , separator }
45import java .net .URI
5- import java .nio .file .Path
6+ import java .nio .file .{ Files , Path }
67import java .util
78
9+ import dotty .tools .dotc .Main
10+ import dotty .tools .dotc .reporting .{Reporter , ThrowingReporter }
11+ import dotty .tools .io .Directory
812import dotty .tools .languageserver .DottyLanguageServer
13+ import dotty .tools .languageserver .util .Code .{TastyWithPositions , Project }
914import org .eclipse .lsp4j .{ DidOpenTextDocumentParams , InitializeParams , InitializeResult , TextDocumentItem }
1015
11- class TestServer (testFolder : Path ) {
16+ class TestServer (testFolder : Path , projects : List [ Project ] ) {
1217
1318 val server = new DottyLanguageServer
1419 var client : TestClient = _
1520
1621 init()
1722
1823 private [this ] def init (): InitializeResult = {
19- // Fill the configuration with values populated by sbt
20- def showSeq [T ](lst : Seq [T ]): String =
21- lst
22- .map(elem => '"' + elem.toString.replace('\\ ' , '/' ) + '"' )
23- .mkString(" [ " , " , " , " ]" )
24- val dottyIdeJson : String =
25- s """ [ {
26- | "id" : "dotty-ide-test",
24+ var compiledProjects : Set [Project ] = Set .empty
25+
26+ /** Compile the dependencies of the given project, and then the project. */
27+ def compileProjectAndDependencies (project : Project ): Unit =
28+ if (! compiledProjects.contains(project)) {
29+ project.dependsOn.foreach(compileProjectAndDependencies)
30+ compileProject(project)
31+ compiledProjects += project
32+ }
33+
34+ /**
35+ * Set up given project, return JSON config.
36+ *
37+ * If the project has dependencies, these dependencies are compiled. The classfiles of the
38+ * dependent projects are put on the classpath of this project.
39+ *
40+ * @param project The project to configure.
41+ * @return A JSON object representing the configuration for this project.
42+ */
43+ def projectSetup (project : Project ): String = {
44+ def showSeq [T ](lst : Seq [T ]): String =
45+ lst
46+ .map(elem => '"' + elem.toString.replace('\\ ' , '/' ) + '"' )
47+ .mkString(" [ " , " , " , " ]" )
48+
49+ if (project.sources.exists(_.isInstanceOf [TastyWithPositions ])) {
50+ compileProjectAndDependencies(project)
51+ } else {
52+ // Compile all the dependencies of this project
53+ project.dependsOn.foreach(compileProjectAndDependencies)
54+ }
55+
56+ s """ {
57+ | "id" : " ${project.name}",
2758 | "compilerVersion" : " ${BuildInfo .ideTestsCompilerVersion}",
2859 | "compilerArguments" : ${showSeq(BuildInfo .ideTestsCompilerArguments)},
29- | "sourceDirectories" : ${showSeq(BuildInfo .ideTestsSourceDirectories )},
30- | "dependencyClasspath" : ${showSeq(BuildInfo .ideTestsDependencyClasspath )},
31- | "classDirectory" : " ${BuildInfo .ideTestsClassDirectory .toString.replace('\\ ' ,'/' )}"
60+ | "sourceDirectories" : ${showSeq(sourceDirectory(project, wipe = false ) :: Nil )},
61+ | "dependencyClasspath" : ${showSeq(dependencyClasspath(project) )},
62+ | "classDirectory" : " ${classDirectory(project, wipe = false ) .toString.replace('\\ ' ,'/' )}"
3263 |}
33- |] """ .stripMargin
64+ | """ .stripMargin
65+ }
66+
67+ Files .createDirectories(testFolder)
3468 val configFile = testFolder.resolve(DottyLanguageServer .IDE_CONFIG_FILE )
35- testFolder.toFile.mkdirs()
36- testFolder.resolve(" src" ).toFile.mkdirs()
37- testFolder.resolve(" out" ).toFile.mkdirs()
69+ val configuration = projects.map(projectSetup).mkString(" [" , " ," , " ]" )
3870
3971 new PrintWriter (configFile.toString) {
40- write(dottyIdeJson )
72+ write(configuration )
4173 close()
4274 }
4375
@@ -52,17 +84,71 @@ class TestServer(testFolder: Path) {
5284 /** Open the code in the given file and returns the file.
5385 * @param code code in file
5486 * @param fileName file path in the source directory
87+ * @param openInIDE If true, send `textDocument/didOpen` to the server.
5588 * @return the file opened
5689 */
57- def openCode (code : String , fileName : String ): TestFile = {
58- val testFile = new TestFile (fileName)
59- val dotdp = new DidOpenTextDocumentParams ()
90+ def openCode (code : String , project : Project , fileName : String , openInIDE : Boolean ): TestFile = {
91+ val testFile = new TestFile (project.name + separator + fileName)
6092 val tdi = new TextDocumentItem ()
6193 tdi.setUri(testFile.uri)
6294 tdi.setText(code)
63- dotdp.setTextDocument(tdi)
64- server.didOpen(dotdp)
95+
96+ if (openInIDE) {
97+ val dotdp = new DidOpenTextDocumentParams ()
98+ dotdp.setTextDocument(tdi)
99+ server.didOpen(dotdp)
100+ }
101+
65102 testFile
66103 }
67104
105+ private def classDirectory (project : Project , wipe : Boolean ): Path = {
106+ val path = testFolder.resolve(project.name).resolve(" out" )
107+ if (wipe) {
108+ Directory (path).deleteRecursively()
109+ Files .createDirectories(path)
110+ }
111+ path.toAbsolutePath
112+ }
113+
114+ private def dependencyClasspath (project : Project ): Seq [String ] = {
115+ BuildInfo .ideTestsDependencyClasspath.map(_.getAbsolutePath) ++
116+ project.dependsOn.flatMap { dep =>
117+ classDirectory(dep, wipe = false ).toString +: dependencyClasspath(dep)
118+ }
119+ }.distinct
120+
121+ private def sourceDirectory (project : Project , wipe : Boolean ): Path = {
122+ val path = TestFile .sourceDir.resolve(project.name).toAbsolutePath
123+ if (wipe) {
124+ Directory (path).deleteRecursively()
125+ Files .createDirectories(path)
126+ }
127+ path
128+ }
129+
130+ /**
131+ * Sets up the sources of the given project, creates the necessary directories
132+ * and compile the sources.
133+ *
134+ * @param project The project to set up.
135+ */
136+ private def compileProject (project : Project ): Unit = {
137+ val sourcesDir = sourceDirectory(project, wipe = true )
138+ val sources = project.sources.zipWithIndex.map { case (src, id) =>
139+ val path = sourcesDir.resolve(src.sourceName(id)).toAbsolutePath
140+ Files .write(path, src.text.getBytes(" UTF-8" ))
141+ path.toString
142+ }
143+
144+ val compileOptions =
145+ sources.toArray ++
146+ Array (
147+ " -classpath" , dependencyClasspath(project).mkString(pathSeparator),
148+ " -d" , classDirectory(project, wipe = true ).toString
149+ )
150+ val reporter = new ThrowingReporter (Reporter .NoReporter )
151+ Main .process(compileOptions, reporter)
152+ }
153+
68154}
0 commit comments