From d79cb5ca1d47dded6ed38a4a41ed2b4cebb22424 Mon Sep 17 00:00:00 2001 From: hfhbd Date: Tue, 12 Dec 2023 21:56:17 +0100 Subject: [PATCH] Support reading source files from jars --- .../sql/psi/core/SqlCoreEnvironment.kt | 52 +++++++++++++------ .../psi/core/PassingPredefinedTablesTest.kt | 7 +-- .../sql/psi/test/fixtures/CompileFile.kt | 7 +-- .../sql/psi/test/fixtures/FixturesTest.kt | 2 +- .../psi/test/fixtures/TestHeadlessParser.kt | 8 +-- sample-core/build.gradle | 1 + .../testFixtures/kotlin/SqliteTestFixtures.kt | 28 ++++++++++ .../testFixtures/resources}/test.samplesql | 0 .../testFixtures/resources/test2.samplesql | 10 ++++ sample-headless/build.gradle | 1 + .../sample/headless/SampleHeadlessParser.kt | 5 +- .../sql/psi/sample/headless/testing.samplesql | 10 ++++ .../headless/SampleHeadlessParserTest.kt | 41 +++++++++++++-- 13 files changed, 139 insertions(+), 33 deletions(-) create mode 100644 sample-core/src/testFixtures/kotlin/SqliteTestFixtures.kt rename {sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless => sample-core/src/testFixtures/resources}/test.samplesql (100%) create mode 100644 sample-core/src/testFixtures/resources/test2.samplesql create mode 100644 sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless/testing.samplesql diff --git a/core/src/main/kotlin/com/alecstrong/sql/psi/core/SqlCoreEnvironment.kt b/core/src/main/kotlin/com/alecstrong/sql/psi/core/SqlCoreEnvironment.kt index d78f9265..8ded7a13 100644 --- a/core/src/main/kotlin/com/alecstrong/sql/psi/core/SqlCoreEnvironment.kt +++ b/core/src/main/kotlin/com/alecstrong/sql/psi/core/SqlCoreEnvironment.kt @@ -22,7 +22,6 @@ import com.intellij.openapi.roots.impl.ProjectRootManagerImpl import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.VirtualFileSystem import com.intellij.psi.PsiElement import com.intellij.psi.PsiErrorElement @@ -31,7 +30,8 @@ import com.intellij.psi.PsiManager import com.intellij.psi.impl.smartPointers.SmartPointerAnchorProvider import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil -import java.io.File +import java.nio.file.Path +import kotlin.io.path.pathString import kotlin.reflect.KClass private class ApplicationEnvironment { @@ -57,8 +57,8 @@ private class ApplicationEnvironment { } open class SqlCoreEnvironment( - sourceFolders: List, - dependencies: List, + sourceFolders: List, + dependencies: List, ) : AutoCloseable { private val fileIndex: CoreFileIndex @@ -68,9 +68,8 @@ open class SqlCoreEnvironment( env.coreApplicationEnvironment, ) - protected val localFileSystem: VirtualFileSystem = VirtualFileManager.getInstance().getFileSystem( - StandardFileSystems.FILE_PROTOCOL, - ) + private val localFileSystem: VirtualFileSystem = StandardFileSystems.local() + private val jarFileSystem: VirtualFileSystem = StandardFileSystems.jar() init { projectEnvironment.registerProjectComponent( @@ -84,10 +83,20 @@ open class SqlCoreEnvironment( DirectoryIndexImpl(projectEnvironment.project), ) - fileIndex = CoreFileIndex(sourceFolders, localFileSystem, projectEnvironment.project) + fileIndex = CoreFileIndex( + sourceFolders, + localFileSystem, + jarFileSystem, + project = projectEnvironment.project, + ) projectEnvironment.project.registerService(ProjectFileIndex::class.java, fileIndex) - val contributorIndex = CoreFileIndex(sourceFolders + dependencies, localFileSystem, projectEnvironment.project) + val contributorIndex = CoreFileIndex( + sourceFolders + dependencies, + localFileSystem, + jarFileSystem, + project = projectEnvironment.project, + ) projectEnvironment.project.registerService( SchemaContributorIndex::class.java, object : SchemaContributorIndex { @@ -145,7 +154,7 @@ open class SqlCoreEnvironment( otherFailures.forEach { it.invoke() } } - inline fun forSourceFiles(noinline action: (T) -> Unit) { + inline fun forSourceFiles(noinline action: (T) -> Unit) { forSourceFiles(T::class, action) } @@ -198,16 +207,27 @@ fun interface SqlCompilerAnnotator { } private class CoreFileIndex( - val sourceFolders: List, - private val localFileSystem: VirtualFileSystem, + val sourceFolders: List, + val localFileSystems: VirtualFileSystem, + val jarFileSystem: VirtualFileSystem, project: Project, ) : ProjectFileIndexImpl(project) { override fun iterateContent(iterator: ContentIterator): Boolean { - return sourceFolders.all { - val file = localFileSystem.findFileByPath(it.absolutePath) - ?: throw NullPointerException("File ${it.absolutePath} not found") - iterateContentUnderDirectory(file, iterator) + for (file in sourceFolders) { + val vFile = when (val schema = file.fileSystem.provider().scheme) { + StandardFileSystems.JAR_PROTOCOL -> { + val jarFilePath = file.toUri().toString().removePrefix("jar:file://") + jarFileSystem.findFileByPath(jarFilePath) + } + StandardFileSystems.FILE_PROTOCOL -> localFileSystems.findFileByPath(file.pathString) + else -> error("Not supported schema $schema") + } ?: throw NullPointerException("File ${file.pathString} not found") + + if (!iterateContentUnderDirectory(vFile, iterator)) { + return false + } } + return true } override fun iterateContentUnderDirectory(file: VirtualFile, iterator: ContentIterator): Boolean { diff --git a/core/src/test/kotlin/com/alecstrong/sql/psi/core/PassingPredefinedTablesTest.kt b/core/src/test/kotlin/com/alecstrong/sql/psi/core/PassingPredefinedTablesTest.kt index f24923d4..c1ff3105 100644 --- a/core/src/test/kotlin/com/alecstrong/sql/psi/core/PassingPredefinedTablesTest.kt +++ b/core/src/test/kotlin/com/alecstrong/sql/psi/core/PassingPredefinedTablesTest.kt @@ -3,14 +3,15 @@ package com.alecstrong.sql.psi.core import com.alecstrong.sql.psi.test.fixtures.TestHeadlessParser import org.junit.Assert.fail import org.junit.Test -import java.io.File import java.nio.file.Files +import kotlin.io.path.div +import kotlin.io.path.writeText class PassingPredefinedTablesTest { @Test fun mirrorSqlDelight() { - val temp = Files.createTempDirectory("predefinedTest").toFile() - File(temp, "Test.s").writeText( + val temp = Files.createTempDirectory("predefinedTest") + (temp / "Test.s").writeText( """ SELECT * FROM dual; SELECT name FROM dual; diff --git a/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/CompileFile.kt b/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/CompileFile.kt index 67ff4a2d..07c4ad3d 100644 --- a/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/CompileFile.kt +++ b/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/CompileFile.kt @@ -2,8 +2,9 @@ package com.alecstrong.sql.psi.test.fixtures import com.alecstrong.sql.psi.core.SqlFileBase import com.intellij.core.CoreApplicationEnvironment -import java.io.File import java.nio.file.Files +import kotlin.io.path.div +import kotlin.io.path.writeText fun compileFile( // language=sql @@ -23,9 +24,9 @@ fun compileFiles( predefined: List = emptyList(), action: (List) -> Unit, ) { - val directory = Files.createTempDirectory("sql-psi").toFile() + val directory = Files.createTempDirectory("sql-psi") for ((index, content) in files.withIndex()) { - val file = File(directory, "$index.s") + val file = directory / "$index.s" file.writeText(content) } diff --git a/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/FixturesTest.kt b/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/FixturesTest.kt index dd674a45..bc5b8d92 100644 --- a/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/FixturesTest.kt +++ b/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/FixturesTest.kt @@ -33,7 +33,7 @@ abstract class FixturesTest( } val environment = TestHeadlessParser.build( - root = newRoot.path, + root = newRoot.toPath(), customInit = { setupDialect() }, diff --git a/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/TestHeadlessParser.kt b/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/TestHeadlessParser.kt index bb93b830..3c451e7a 100644 --- a/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/TestHeadlessParser.kt +++ b/core/src/testFixtures/kotlin/com/alecstrong/sql/psi/test/fixtures/TestHeadlessParser.kt @@ -11,20 +11,20 @@ import com.intellij.openapi.fileTypes.LanguageFileType import com.intellij.psi.FileViewProvider import com.intellij.psi.PsiFileFactory import com.intellij.psi.tree.IFileElementType -import java.io.File +import java.nio.file.Path object TestHeadlessParser { fun build( - root: String, + root: Path, annotator: SqlAnnotationHolder, predefinedTables: List = emptyList(), customInit: CoreApplicationEnvironment.() -> Unit = { }, ): SqlCoreEnvironment { - return build(listOf(File(root)), annotator, predefinedTables, customInit) + return build(listOf(root), annotator, predefinedTables, customInit) } fun build( - sourceFolders: List, + sourceFolders: List, annotator: SqlAnnotationHolder, predefinedTables: List = emptyList(), customInit: CoreApplicationEnvironment.() -> Unit = { }, diff --git a/sample-core/build.gradle b/sample-core/build.gradle index d1b2e26a..cad2f83d 100644 --- a/sample-core/build.gradle +++ b/sample-core/build.gradle @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.kotlinJvm) alias(libs.plugins.grammarKitComposer) + id("java-test-fixtures") } grammarKit { diff --git a/sample-core/src/testFixtures/kotlin/SqliteTestFixtures.kt b/sample-core/src/testFixtures/kotlin/SqliteTestFixtures.kt new file mode 100644 index 00000000..7ef5d8c7 --- /dev/null +++ b/sample-core/src/testFixtures/kotlin/SqliteTestFixtures.kt @@ -0,0 +1,28 @@ +import com.alecstrong.sql.psi.sample.core.SampleFileType +import java.net.URI +import java.nio.file.FileSystemNotFoundException +import java.nio.file.FileSystems +import java.nio.file.Path +import kotlin.io.path.toPath +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +object SqliteTestFixtures : ReadOnlyProperty { + val jarFile: Path get() = SqliteTestFixtures::class.java.getResource("/SqliteTestFixtures.class")!!.toURI().toJarPath().parent + + override operator fun getValue(thisRef: Nothing?, property: KProperty<*>): Path { + val uri = + SqliteTestFixtures::class.java.getResource("/${property.name}.${SampleFileType.defaultExtension}")!!.toURI() + return uri.toJarPath() + } + + private fun URI.toJarPath(): Path { + try { + FileSystems.getFileSystem(this) + } catch (ignored: FileSystemNotFoundException) { + val env = mapOf("create" to "true") + FileSystems.newFileSystem(this, env) + } + return toPath() + } +} diff --git a/sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless/test.samplesql b/sample-core/src/testFixtures/resources/test.samplesql similarity index 100% rename from sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless/test.samplesql rename to sample-core/src/testFixtures/resources/test.samplesql diff --git a/sample-core/src/testFixtures/resources/test2.samplesql b/sample-core/src/testFixtures/resources/test2.samplesql new file mode 100644 index 00000000..433d7eff --- /dev/null +++ b/sample-core/src/testFixtures/resources/test2.samplesql @@ -0,0 +1,10 @@ +CREATE TABLE test2 ( + sample_column 42 TEXT AS "java.util.List" NOT NULL +); + +SELECT * +FROM test, test2 +WHERE test.sample_column = "foo"; + +SELECT * FROM test2 WHERE (1 = 1) FOO 13; +SELECT * FROM test2 WHERE (1 = 1); diff --git a/sample-headless/build.gradle b/sample-headless/build.gradle index a8009499..00646444 100644 --- a/sample-headless/build.gradle +++ b/sample-headless/build.gradle @@ -11,4 +11,5 @@ dependencies { } testImplementation libs.coroutines.core testImplementation "org.jetbrains.kotlin:kotlin-test" + testImplementation(testFixtures(project(':sample-core'))) } diff --git a/sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless/SampleHeadlessParser.kt b/sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless/SampleHeadlessParser.kt index 2e24ecf8..ef3ee9e0 100644 --- a/sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless/SampleHeadlessParser.kt +++ b/sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless/SampleHeadlessParser.kt @@ -6,9 +6,10 @@ import com.alecstrong.sql.psi.sample.core.SampleFileType import com.alecstrong.sql.psi.sample.core.SampleParserDefinition import com.intellij.psi.PsiDocumentManager import java.io.File +import java.nio.file.Path class SampleHeadlessParser { - fun parseSqlite(sourceFolders: List, onError: (String) -> Unit): List { + fun parseSqlite(sourceFolders: List, onError: (String) -> Unit): List { val parserDefinition = SampleParserDefinition() val environment = object : SqlCoreEnvironment( sourceFolders = sourceFolders, @@ -39,7 +40,7 @@ class SampleHeadlessParser { } fun main() { - SampleHeadlessParser().parseSqlite(listOf(File("sample-headless"))) { + SampleHeadlessParser().parseSqlite(listOf(File("sample-headless").toPath())) { System.err.println(it) } } diff --git a/sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless/testing.samplesql b/sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless/testing.samplesql new file mode 100644 index 00000000..d48877e0 --- /dev/null +++ b/sample-headless/src/main/kotlin/com/alecstrong/sql/psi/sample/headless/testing.samplesql @@ -0,0 +1,10 @@ +CREATE TABLE test ( + sample_column 42 TEXT AS "java.util.List" NOT NULL +); + +SELECT * +FROM test +WHERE sample_column = "foo"; + +SELECT * FROM test WHERE (1 = 1) FOO 13; +SELECT * FROM test WHERE (1 = 1); diff --git a/sample-headless/src/test/kotlin/com/alecstrong/sql/psi/sample/headless/SampleHeadlessParserTest.kt b/sample-headless/src/test/kotlin/com/alecstrong/sql/psi/sample/headless/SampleHeadlessParserTest.kt index 6fd79a70..222bbae1 100644 --- a/sample-headless/src/test/kotlin/com/alecstrong/sql/psi/sample/headless/SampleHeadlessParserTest.kt +++ b/sample-headless/src/test/kotlin/com/alecstrong/sql/psi/sample/headless/SampleHeadlessParserTest.kt @@ -1,20 +1,53 @@ package com.alecstrong.sql.psi.sample.headless +import SqliteTestFixtures import com.alecstrong.sql.psi.core.psi.SqlLiteralExpr +import com.alecstrong.sql.psi.sample.core.SampleFile import com.alecstrong.sql.psi.sample.core.psi.CustomExpr import com.intellij.psi.util.childrenOfType -import java.io.File +import kotlin.io.path.Path import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.fail class SampleHeadlessParserTest { @Test - fun parserIsSuccessful() { - val files = SampleHeadlessParser().parseSqlite(listOf(File("../sample-headless"))) { + fun parserIsSuccessfulWithSourceFolder() { + val files = SampleHeadlessParser().parseSqlite(listOf(Path("../sample-headless"))) { fail(it) } - for (file in files) { + files.test() + } + + @Test + fun parserIsSuccessfulWithFileInJarSource() { + val test by SqliteTestFixtures + val files = SampleHeadlessParser().parseSqlite(listOf(test)) { + fail(it) + } + files.test() + } + + @Test + fun parserIsSuccessfulWithSourceFolderAndFileInJarSource() { + val test2 by SqliteTestFixtures + val files = SampleHeadlessParser().parseSqlite(listOf(Path("../sample-headless"), test2)) { + fail(it) + } + files.test() + } + + @Test + fun parserIsSuccessfulWithJarSource() { + val files = SampleHeadlessParser().parseSqlite(listOf(SqliteTestFixtures.jarFile)) { + fail(it) + } + assertEquals(2, files.size) + files.test() + } + + private fun List.test() { + for (file in this) { val stmts = file.sqlStmtList ?: continue for (stmt in stmts.stmtList) { when {