Hone logo
Hone
Problems

Building a Basic Language Server for Vue with TypeScript

This challenge involves creating a fundamental language server that can provide basic language features for Vue.js Single File Components (SFCs) using TypeScript. Building language servers is crucial for enhancing developer experience in modern IDEs by enabling features like syntax highlighting, autocompletion, error checking, and more. You will leverage the Language Server Protocol (LSP) to communicate with a hypothetical editor client.

Problem Description

Your task is to implement a simplified language server for Vue SFCs. This server will run in a separate process and communicate with an editor (which we will simulate) using the Language Server Protocol (LSP). The primary goal is to handle basic text synchronization and respond to a simple "hover" request.

What needs to be achieved:

  1. Initialize Communication: Establish a connection with the client and handle the initialize and initialized handshake.
  2. Text Synchronization: Process textDocument/didOpen, textDocument/didChange, and textDocument/didClose notifications to keep track of the open Vue SFC documents.
  3. Hover Feature: Respond to textDocument/hover requests for specific tags within a Vue SFC template.

Key Requirements:

  • LSP Compliance: Implement the necessary LSP methods to communicate with a client.
  • Vue SFC Awareness: The server should recognize .vue files and process their content.
  • Basic Hover: When the cursor is over a common Vue tag (e.g., <template>, <script>, <style>, <div>, <button>), the server should return a simple hover description.
  • TypeScript Implementation: The entire language server should be written in TypeScript.

Expected Behavior:

  • When the client sends an initialize request, the server should respond with its capabilities, indicating support for hover and textDocumentSync kind Incremental.
  • When a .vue file is opened, the server should store its content.
  • When a .vue file is changed, the server should update its stored content.
  • When a textDocument/hover request is received for a position within a known Vue tag in a .vue file, the server should return a Hover object containing a contents field with a markdown string describing the tag. For unknown tags or positions outside tags, it should return null.

Edge Cases:

  • Requests for files other than .vue should be ignored gracefully.
  • Hover requests for positions that are not part of a recognized tag should return null.
  • Handling of malformed SFCs is not a primary concern for this challenge, but the server should not crash.

Examples

Example 1: Initialization and Document Open

// Client sends to Server
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "processId": 12345,
    "capabilities": {} // Client capabilities
  }
}

// Server responds to Client
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "capabilities": {
      "textDocumentSync": 1, // 1 = Incremental
      "hoverProvider": true
    }
  }
}

// Client sends to Server (after initialization)
{
  "jsonrpc": "2.0",
  "method": "initialized"
}

// Client sends to Server
{
  "jsonrpc": "2.0",
  "method": "textDocument/didOpen",
  "params": {
    "textDocument": {
      "uri": "file:///path/to/MyComponent.vue",
      "languageId": "vue",
      "version": 1,
      "text": "<template>\n  <div>Hello</div>\n</template>\n<script>\nexport default {}\n</script>\n<style>\n.my-class {}\n</style>"
    }
  }
}

Explanation: The client initiates the connection. The server declares its capabilities (supports incremental text sync and hover). The client confirms initialization. The client then opens a .vue file, sending its content. The server should store this content associated with the URI.

Example 2: Hover Request

// Client sends to Server
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "textDocument/hover",
  "params": {
    "textDocument": {
      "uri": "file:///path/to/MyComponent.vue"
    },
    "position": {
      "line": 0, // Corresponds to the line containing "<template>"
      "character": 2 // Within the "<template>" tag
    }
  }
}

// Server responds to Client
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "contents": {
      "kind": "markdown",
      "value": "The **`<template>`** block defines the HTML structure of your Vue component."
    }
  }
}

Explanation: The client requests hover information for a specific position within the <template> tag. The server analyzes the position, identifies it's within a <template> tag, and returns a markdown description.

Example 3: Hover Request (Different Tag)

// Client sends to Server
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "textDocument/hover",
  "params": {
    "textDocument": {
      "uri": "file:///path/to/MyComponent.vue"
    },
    "position": {
      "line": 3, // Corresponds to the line containing "<script>"
      "character": 2 // Within the "<script>" tag
    }
  }
}

// Server responds to Client
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "contents": {
      "kind": "markdown",
      "value": "The **`<script>`** block contains the JavaScript or TypeScript logic for your Vue component."
    }
  }
}

Explanation: Similar to Example 2, but for the <script> tag.

Example 4: Hover Request (Outside Tag)

// Client sends to Server
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "textDocument/hover",
  "params": {
    "textDocument": {
      "uri": "file:///path/to/MyComponent.vue"
    },
    "position": {
      "line": 1, // Corresponds to the line containing "<div>Hello</div>"
      "character": 6 // Within "Hello"
    }
  }
}

// Server responds to Client
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": null
}

Explanation: The client requests hover information for a position that is not directly within a recognized SFC block tag (<template>, <script>, <style>) or a common HTML element tag. The server returns null.

Constraints

  • The language server must implement at least the following LSP methods: initialize, textDocument/didOpen, textDocument/didChange, textDocument/didClose, and textDocument/hover.
  • Communication between the client and server will be via standard input/output using JSON-RPC.
  • The server should be written entirely in TypeScript.
  • For hover requests, only consider the top-level SFC blocks (<template>, <script>, <style>) and common HTML elements like <div>, <button>, <span>, <p>, <h1> to <h6>.
  • The server does not need to parse the content of the script or style blocks, nor the full AST of the template. A simple regex or string matching for tag names is sufficient for this challenge.

Notes

  • You will need to implement a basic JSON-RPC handler for communication. Libraries like vscode-languageserver can greatly simplify this, but for this challenge, you are encouraged to implement a minimal RPC layer yourself to understand the protocol better.
  • Consider how to parse incoming JSON-RPC messages from stdin.
  • When responding to requests, ensure you correctly format the JSON-RPC response with id, method (for notifications), and result or error.
  • The position in LSP is zero-based for both line and character.
  • The textDocumentSync capability should be set to 1 (meaning Incremental) in your initialize response.
  • For the hover feature, you can maintain a simple mapping of tag names to their descriptions.
Loading editor...
typescript