PSI-LSIF Indexer is an IntelliJ IDEA plugin used to generate LSIF dump.

    The Language Server Index Format (LSIF) is a standard format for language servers or other programming tools to emit their knowledge about a code workspace. This persisted information can later be used to answer LSP requests for the same workspace without running a language server

    The Program Structure Interface (PSI) is a layer in the IntelliJ Platform that is responsible for parsing files and creating the syntactic and semantic code model that powers so many of the platform’s features.

    The reasons of building an LSIF indexer on top of IntelliJ platform:

    • PSI capability: IntelliJ IDEs (and language plugins) already have PSI implementations for most languages, so the psi-lsif plugin could be used across different IDEs.
    • Client-side generation: reuse the available indexes already generated from IDE, thus reduce the cost of generating LSIF from analysing the source to just converting PSI information to LSIF.

    Future work

    • Extensibility of LSIF:
      • Cross-file & cross-repo implementation
      • There are numerous code inspections exist for Java and other languages, these code analysis capability could be also extended to the LSIF implementation
    • Server-side generation:
      • Integrate the psi-lsif indexer into headless IntelliJ runtime

    Protocol & ADT

    constants

    enum class ElementTypes {
      @SerializedName("vertex") VERTEX,
      @SerializedName("edge") EDGE
    }
    object Label {
      object Vertex {
        const val document = "document"
        const val metaData = "metaData"
        const val project = "project"
        const val hoverResult = "hoverResult"
        const val definitionResult = "definitionResult"
        const val referenceResult = "referenceResult"
        const val resultSet = "resultSet"
        const val range = "range"
      }
      object Edge {
        const val hover = "textDocument/hover"
        const val definition = "textDocument/definition"
        const val references = "textDocument/references"
        const val next = "next"
        const val item = "item"
        const val contains = "contains"
      }
      object Property {
        const val definitions = "definitions"
        const val references = "references"
      }
    }
    typealias Uri = String
    

    Edge definitions

    abstract class Edge(
        val type: ElementTypes = ElementTypes.EDGE
    ) {
        abstract val id: Number
        abstract val label: String
        abstract val outV: Number
    }
    
    data class Edge11( ..., val inV: Number) : Edge()
    data class Edge1N( ...,
        val inVs: List<Number>,
        val property: String? = null,
        val document: Number? = null
    ) : Edge()
    

    Vertex definitions

    abstract class Vertex(
        val type: ElementTypes = ElementTypes.VERTEX
    ) {
        abstract val id: Number
        abstract val label: String
    }
    
    data class DocumentVertex(
        override val id: Number,
        val languageId: String,
        val uri: Uri,
        val contents: String,
        override val label: String = Label.Vertex.document
    ): Vertex()
    data class BaseVertex( ... ): Vertex()
    data class MetaDataVertex( ... ): Vertex()
    data class ProjectVertex( ... ): Vertex()
    data class RangeVertex( ... ): Vertex()
    data class HoverResultVertex( ... ): Vertex()
    

    Emitter

    helper class with static method for dumping lsif file

    object Emitter {
        private val gson = Gson()
        private val edges = mutableListOf<Edge1N>()
        private var writer: BufferedWriter? = null
    
        fun start(lsifPath: String, id: AtomicInteger, action: () -> Unit) {
            ApplicationManager.getApplication().executeOnPooledThread {
                // try catch block
                writer = FileOutputStream(File(lsifPath)).bufferedWriter(Charsets.UTF_8)
                action()
                unionEdge1NsAndCommit(id)
                writer?.close()
            }
        }
    
        fun emitVertex(vertex: Vertex) {
            writer?.append("${gson.toJson(vertex)}\n")
        }
    
        fun emitEdge(edge: Edge) {
            if (edge is Edge1N) {
                edges.add(edge)
            } else {
                writer?.append("${gson.toJson(edge)}\n")
            }
        }
    
        private fun unionEdge1NsAndCommit(id: AtomicInteger) {
            edges.groupBy { it.label }.forEach { (_, list) ->
                list.groupBy { it.outV }.forEach { (k, v) ->
                    val item = v.first()
                    val edge = Edge1N(id.getAndIncrement(), item.label, k, v.flatMap { it.inVs }.distinct(), item.property, item.document)
                    writer?.append("${gson.toJson(edge)}\n")
                }
            }
        }
    }
    

    Indexer

    finally the projectIndexer & documentIndexer

    class DocumentIndexer (
        private val id: AtomicInteger,
        private val psiFile: PsiFile,
        private val document: Document
    ) {
    
        private fun buildHoverText(psiElement: PsiElement) {
            val (name, doc) = when (psiElement) {
                is PsiClass -> listOf(
                    PsiFormatUtil.formatClass(
                        psiElement,
                        SHOW_NAME or SHOW_FQ_NAME or SHOW_ANONYMOUS_CLASS_VERBOSE)
                    , psiElement.docComment?.text.orEmpty()
                )
                is PsiMethod -> listOf(
                    PsiFormatUtil.formatMethod(
                        psiElement,
                        PsiSubstitutor.EMPTY,
                        SHOW_NAME or SHOW_PARAMETERS or SHOW_TYPE or
                                SHOW_MODIFIERS or SHOW_THROWS,
                        SHOW_NAME or SHOW_PARAMETERS or SHOW_TYPE
                    ), psiElement.docComment?.text.orEmpty()
                )
                is PsiVariable -> listOf("", psiElement.text.orEmpty())
                else -> listOf("", "")
            }
            return "```${JAVA_MKD_PREFIX}\n" + (if (doc.isBlank()) "" else "${doc.padJavaDocIndent()}\n") + "${name}\n```";
        }
    
        private fun emitDefinition(psiElement: PsiElement) {
            // ...
        }
    }