Skip to content

Support reading source files from jars #588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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