Skip to content

Commit

Permalink
Support reading source files from jars
Browse files Browse the repository at this point in the history
  • Loading branch information
hfhbd committed Dec 12, 2023
1 parent 8df857e commit d79cb5c
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -57,8 +57,8 @@ private class ApplicationEnvironment {
}

open class SqlCoreEnvironment(
sourceFolders: List<File>,
dependencies: List<File>,
sourceFolders: List<Path>,
dependencies: List<Path>,
) : AutoCloseable {
private val fileIndex: CoreFileIndex

Expand All @@ -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(
Expand All @@ -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 {
Expand Down Expand Up @@ -145,7 +154,7 @@ open class SqlCoreEnvironment(
otherFailures.forEach { it.invoke() }
}

inline fun<reified T : PsiFile> forSourceFiles(noinline action: (T) -> Unit) {
inline fun <reified T : PsiFile> forSourceFiles(noinline action: (T) -> Unit) {
forSourceFiles(T::class, action)
}

Expand Down Expand Up @@ -198,16 +207,27 @@ fun interface SqlCompilerAnnotator {
}

private class CoreFileIndex(
val sourceFolders: List<File>,
private val localFileSystem: VirtualFileSystem,
val sourceFolders: List<Path>,
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -23,9 +24,9 @@ fun compileFiles(
predefined: List<String> = emptyList(),
action: (List<SqlFileBase>) -> 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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ abstract class FixturesTest(
}

val environment = TestHeadlessParser.build(
root = newRoot.path,
root = newRoot.toPath(),
customInit = {
setupDialect()
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = emptyList(),
customInit: CoreApplicationEnvironment.() -> Unit = { },
): SqlCoreEnvironment {
return build(listOf(File(root)), annotator, predefinedTables, customInit)
return build(listOf(root), annotator, predefinedTables, customInit)
}

fun build(
sourceFolders: List<File>,
sourceFolders: List<Path>,
annotator: SqlAnnotationHolder,
predefinedTables: List<String> = emptyList(),
customInit: CoreApplicationEnvironment.() -> Unit = { },
Expand Down
1 change: 1 addition & 0 deletions sample-core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.kotlinJvm)
alias(libs.plugins.grammarKitComposer)
id("java-test-fixtures")
}

grammarKit {
Expand Down
28 changes: 28 additions & 0 deletions sample-core/src/testFixtures/kotlin/SqliteTestFixtures.kt
Original file line number Diff line number Diff line change
@@ -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<Nothing?, Path> {
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()
}
}
10 changes: 10 additions & 0 deletions sample-core/src/testFixtures/resources/test2.samplesql
Original file line number Diff line number Diff line change
@@ -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);
1 change: 1 addition & 0 deletions sample-headless/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ dependencies {
}
testImplementation libs.coroutines.core
testImplementation "org.jetbrains.kotlin:kotlin-test"
testImplementation(testFixtures(project(':sample-core')))
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<File>, onError: (String) -> Unit): List<SampleFile> {
fun parseSqlite(sourceFolders: List<Path>, onError: (String) -> Unit): List<SampleFile> {
val parserDefinition = SampleParserDefinition()
val environment = object : SqlCoreEnvironment(
sourceFolders = sourceFolders,
Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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<SampleFile>.test() {
for (file in this) {
val stmts = file.sqlStmtList ?: continue
for (stmt in stmts.stmtList) {
when {
Expand Down

0 comments on commit d79cb5c

Please sign in to comment.