Skip to content

Commit 127d0dd

Browse files
vasilmkdbuilduser
authored andcommitted
[sbt] Add support for using the Scala 3 language in sbt 2 build files #SCL-24201 fixed
(cherry picked from commit f22c50f)
1 parent 992da5b commit 127d0dd

File tree

15 files changed

+166
-63
lines changed

15 files changed

+166
-63
lines changed

sbt/sbt-api/src/org/jetbrains/sbt/language/SbtLanguageScala3.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ package org.jetbrains.sbt.language
22

33
import com.intellij.lang.{DependentLanguage, Language}
44
import com.intellij.openapi.fileTypes.LanguageFileType
5-
import org.jetbrains.plugins.scala.ScalaLanguage
65

76
final class SbtLanguageScala3 private
8-
extends Language(ScalaLanguage.INSTANCE, "sbt (scala 3)")
7+
extends Language(SbtLanguage.INSTANCE, "sbt Scala 3")
98
with DependentLanguage {
109

1110
override def getAssociatedFileType: LanguageFileType = SbtFileType

sbt/sbt-api/src/org/jetbrains/sbt/language/SbtLanguageSubstitutor.scala

Lines changed: 0 additions & 26 deletions
This file was deleted.

sbt/sbt-impl/resources/META-INF/sbt.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
<extensions defaultExtensionNs="com.intellij">
3333
<lang.parserDefinition language="sbt" implementationClass="org.jetbrains.sbt.language.SbtParserDefinition"/>
34+
<lang.parserDefinition language="sbt Scala 3" implementationClass="org.jetbrains.sbt.language.SbtParserDefinitionScala3"/>
35+
<lang.substitutor language="sbt" implementationClass="org.jetbrains.sbt.language.SbtLanguageSubstitutor"/>
3436
<lang.fileViewProviderFactory language="sbt"
3537
implementationClass="org.jetbrains.sbt.language.SbtFileViewProviderFactory"/>
3638
<fileType name="sbt" language="sbt" extensions="sbt"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.jetbrains.sbt.language
2+
3+
import com.intellij.openapi.application.ApplicationManager
4+
import com.intellij.openapi.module.{Module, ModuleManager}
5+
import com.intellij.openapi.project.Project
6+
import org.jetbrains.plugins.scala.project.ModuleExt
7+
import org.jetbrains.sbt.Sbt
8+
import org.jetbrains.sbt.project.SbtBuildModuleUriProvider
9+
import org.jetbrains.sbt.project.module.SbtModule.Build
10+
11+
object SbtBuildModuleSupport {
12+
def findBuildModule(module: Module, project: Project): Option[Module] =
13+
if (module.hasBuildModuleType)
14+
Some(module)
15+
else {
16+
val manager = ModuleManager.getInstance(project)
17+
val modules = manager.getModules
18+
val sbtBuildModuleUri = SbtBuildModuleUriProvider.getBuildModuleUri(module)
19+
val result = for {
20+
buildModuleUri <- sbtBuildModuleUri
21+
module <- modules.find(Build(_) == buildModuleUri)
22+
} yield module
23+
24+
if (result.isEmpty && ApplicationManager.getApplication.isUnitTestMode) {
25+
//NOTE: right now this legacy way of determining build module is left for tests only
26+
//It simplifies setup logic for tests which work with sbt files.
27+
//In theory we could remove this extra branch, but we would need to improve setup for tests
28+
val buildModuleName = module.getName + Sbt.BuildModuleSuffix
29+
modules.find(_.getName == buildModuleName)
30+
}
31+
else result
32+
}
33+
}

sbt/sbt-impl/src/org/jetbrains/sbt/language/SbtFileImpl.scala

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package org.jetbrains.sbt
22
package language
33

4-
import com.intellij.openapi.application.ApplicationManager
5-
import com.intellij.openapi.module.{Module, ModuleManager, ModuleUtilCore}
4+
import com.intellij.openapi.module.{Module, ModuleUtilCore}
65
import com.intellij.openapi.roots.ProjectRootManager
76
import com.intellij.psi._
87
import com.intellij.psi.search.{GlobalSearchScope, searches}
@@ -12,9 +11,7 @@ import org.jetbrains.plugins.scala.extensions.PsiClassExt
1211
import org.jetbrains.plugins.scala.lang.psi.ScDeclarationSequenceHolder
1312
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScTypeDefinition
1413
import org.jetbrains.plugins.scala.lang.psi.impl._
15-
import org.jetbrains.plugins.scala.project.{ModuleExt, ScalaFeatures}
16-
import org.jetbrains.sbt.project.SbtBuildModuleUriProvider
17-
import org.jetbrains.sbt.project.module.SbtModule.{Build, Imports}
14+
import org.jetbrains.sbt.project.module.SbtModule.Imports
1815

1916
import scala.jdk.CollectionConverters._
2017

@@ -88,26 +85,7 @@ final class SbtFileImpl private[language](provider: FileViewProvider)
8885
}
8986

9087
override def findBuildModule(module: Module): Option[Module] =
91-
if (module.hasBuildModuleType)
92-
Some(module)
93-
else {
94-
val manager = ModuleManager.getInstance(getProject)
95-
val modules = manager.getModules
96-
val sbtBuildModuleUri = SbtBuildModuleUriProvider.getBuildModuleUri(module)
97-
val result = for {
98-
buildModuleUri <- sbtBuildModuleUri
99-
module <- modules.find(Build(_) == buildModuleUri)
100-
} yield module
101-
102-
if (result.isEmpty && ApplicationManager.getApplication.isUnitTestMode) {
103-
//NOTE: right now this legacy way of determining build module is left for tests only
104-
//It simplifies setup logic for tests which work with sbt files.
105-
//In theory we could remove this extra branch, but we would need to improve setup for tests
106-
val buildModuleName = module.getName + Sbt.BuildModuleSuffix
107-
modules.find(_.getName == buildModuleName)
108-
}
109-
else result
110-
}
88+
SbtBuildModuleSupport.findBuildModule(module, getProject)
11189
}
11290

11391
object SbtFileImpl {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.jetbrains.sbt.language
2+
3+
import com.intellij.lang.Language
4+
import com.intellij.openapi.vfs.VirtualFile
5+
import com.intellij.psi.{PsiManager, SingleRootFileViewProvider}
6+
7+
private final class SbtFileViewProvider(
8+
manager: PsiManager,
9+
file: VirtualFile,
10+
eventSystemEnabled: Boolean,
11+
language: Language
12+
) extends SingleRootFileViewProvider(manager, file, eventSystemEnabled, language)

sbt/sbt-impl/src/org/jetbrains/sbt/language/SbtFileViewProviderFactory.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ final class SbtFileViewProviderFactory extends FileViewProviderFactory {
1111
language: Language,
1212
manager: PsiManager,
1313
eventSystemEnabled: Boolean): SingleRootFileViewProvider =
14-
new SingleRootFileViewProvider(manager, file, eventSystemEnabled, language) {}
14+
new SbtFileViewProvider(manager, file, eventSystemEnabled, language)
1515
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.jetbrains.sbt.language
2+
3+
import com.intellij.lang.Language
4+
import com.intellij.openapi.module.ModuleUtilCore
5+
import com.intellij.openapi.project.Project
6+
import com.intellij.openapi.vfs.VirtualFile
7+
import com.intellij.psi.LanguageSubstitutor
8+
import com.intellij.util.SlowOperations
9+
import org.jetbrains.plugins.scala.project.ModuleExt
10+
11+
import scala.util.Using
12+
13+
final class SbtLanguageSubstitutor extends LanguageSubstitutor {
14+
override def getLanguage(file: VirtualFile, project: Project): Language = {
15+
val module = Using.resource(SlowOperations.knownIssue("SCL-21147")) { _ =>
16+
ModuleUtilCore.findModuleForFile(file, project)
17+
}
18+
Option(module).flatMap(SbtBuildModuleSupport.findBuildModule(_, project)) match {
19+
case Some(buildModule) if buildModule.hasScala3 => SbtLanguageScala3.INSTANCE
20+
case _ => null
21+
}
22+
}
23+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.jetbrains.sbt.language
2+
3+
import com.intellij.lang.PsiParser
4+
import com.intellij.lexer.Lexer
5+
import com.intellij.openapi.project.Project
6+
import com.intellij.psi.FileViewProvider
7+
import com.intellij.psi.tree.IFileElementType
8+
import org.jetbrains.plugins.scala.lang.lexer.ScalaLexer
9+
import org.jetbrains.plugins.scala.lang.parser.{ScalaParser, ScalaParserDefinitionBase}
10+
import org.jetbrains.plugins.scala.lang.psi.api.ScalaFile
11+
12+
final class SbtParserDefinitionScala3 extends ScalaParserDefinitionBase {
13+
override def createFile(viewProvider: FileViewProvider): ScalaFile = new SbtFileImpl(viewProvider)
14+
15+
override def createLexer(project: Project): Lexer = new ScalaLexer(/* isScala3 = */ true, project)
16+
17+
override def createParser(project: Project): PsiParser = new ScalaParser(isScala3 = true)
18+
19+
override val getFileNodeType: IFileElementType = new IFileElementType("sbt.FILE", SbtLanguageScala3.INSTANCE)
20+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.jetbrains.sbt.project
2+
3+
import com.intellij.openapi.vfs.VfsUtil
4+
import com.intellij.pom.java.LanguageLevel
5+
import com.intellij.psi.{PsiFile, PsiManager}
6+
import org.jetbrains.plugins.scala.Scala3Language
7+
import org.jetbrains.plugins.scala.extensions.PathExt
8+
import org.jetbrains.plugins.scala.util.TestUtils
9+
import org.jetbrains.sbt.SbtVersion
10+
import org.jetbrains.sbt.language.SbtLanguageScala3
11+
import org.junit.Assert.{assertNotNull, assertTrue}
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
import org.junit.runners.JUnit4
15+
16+
import java.nio.file.Path
17+
18+
@RunWith(classOf[JUnit4])
19+
class SbtBuildFilesLanguageTest extends SbtExternalSystemImportingTestLike {
20+
override protected def getTestDataProjectPath: String =
21+
s"${TestUtils.getTestDataPath}/sbt/projects/${getTestName(true)}"
22+
23+
override protected def copyTestProjectToTemporaryDir: Boolean = true
24+
25+
override protected def projectJdkLanguageLevel: LanguageLevel = LanguageLevel.JDK_17
26+
27+
@Test
28+
def buildFilesLanguage(): Unit = {
29+
injectVariable(
30+
getTestProjectPath / "project" / "build.properties",
31+
"$LATEST_SBT_2$",
32+
SbtVersion.Latest.Sbt_2.minor
33+
)
34+
35+
importProject(false)
36+
37+
val dayEnumPsiFile = findPsiFile(getTestProjectPath / "project" / "Day.scala")
38+
val buildSbtPsiFile = findPsiFile(getTestProjectPath / "build.sbt")
39+
40+
assertTrue(s"Day.scala enum is not a Scala 3 source file", dayEnumPsiFile.getLanguage.isKindOf(Scala3Language.INSTANCE))
41+
assertTrue(s"build.sbt is not a Scala 3 sbt build file", buildSbtPsiFile.getLanguage.isKindOf(SbtLanguageScala3.INSTANCE))
42+
}
43+
44+
private def findPsiFile(path: Path): PsiFile = {
45+
val virtualFile = VfsUtil.findFile(path, true)
46+
assertNotNull(s"Could not find a virtual file for: $path", virtualFile)
47+
val psiFile = PsiManager.getInstance(getProject).findFile(virtualFile)
48+
assertNotNull(s"Could not find a PSI file for: $path", psiFile)
49+
psiFile
50+
}
51+
}

0 commit comments

Comments
 (0)