@sanity/groq-lsp 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema/loader.ts","../src/utils/groq-extractor.ts","../src/capabilities/diagnostics.ts","../src/capabilities/hover.ts","../src/capabilities/completion.ts","../src/capabilities/formatting.ts"],"sourcesContent":["/**\n * Schema loading and caching for the LSP server\n *\n * Handles:\n * - Loading schema.json from disk\n * - Auto-discovery of schema files\n * - Caching and invalidation\n * - File watching for hot-reload\n */\n\nimport { readFileSync, existsSync, statSync, watch, type FSWatcher } from 'node:fs'\nimport { join } from 'node:path'\nimport type { SchemaType } from 'groq-js'\nimport type { SchemaState } from '../types.js'\n\n/**\n * Well-known schema file locations to search for\n */\nconst SCHEMA_CANDIDATES = [\n 'schema.json',\n 'sanity.schema.json',\n '.sanity/schema.json',\n 'studio/schema.json',\n]\n\n/**\n * SchemaLoader manages schema state for the LSP server\n */\nexport class SchemaLoader {\n private state: SchemaState = {}\n private watcher: FSWatcher | null = null\n private onChangeCallback: ((schema: SchemaType | undefined) => void) | null = null\n\n /**\n * Load schema from a specific path\n */\n loadFromPath(schemaPath: string): SchemaState {\n try {\n if (!existsSync(schemaPath)) {\n this.state = {\n path: schemaPath,\n error: `Schema file not found: ${schemaPath}`,\n }\n return this.state\n }\n\n const content = readFileSync(schemaPath, 'utf-8')\n const schema = JSON.parse(content) as SchemaType\n const stats = statSync(schemaPath)\n\n this.state = {\n schema,\n path: schemaPath,\n lastModified: stats.mtimeMs,\n }\n\n return this.state\n } catch (e) {\n this.state = {\n path: schemaPath,\n error: `Failed to load schema: ${e instanceof Error ? e.message : String(e)}`,\n }\n return this.state\n }\n }\n\n /**\n * Auto-discover schema file in workspace\n */\n discoverSchema(workspaceRoot: string): SchemaState {\n for (const candidate of SCHEMA_CANDIDATES) {\n const candidatePath = join(workspaceRoot, candidate)\n if (existsSync(candidatePath)) {\n return this.loadFromPath(candidatePath)\n }\n }\n\n this.state = {\n error: 'No schema.json found. Generate with: npx sanity schema extract',\n }\n return this.state\n }\n\n /**\n * Get the current schema state\n */\n getState(): SchemaState {\n return this.state\n }\n\n /**\n * Get the loaded schema (if any)\n */\n getSchema(): SchemaType | undefined {\n return this.state.schema\n }\n\n /**\n * Check if schema needs reloading (file changed)\n */\n needsReload(): boolean {\n if (!this.state.path || !existsSync(this.state.path)) {\n return false\n }\n\n try {\n const stats = statSync(this.state.path)\n return stats.mtimeMs !== this.state.lastModified\n } catch {\n return false\n }\n }\n\n /**\n * Reload schema if the file has changed\n */\n reloadIfNeeded(): boolean {\n if (this.needsReload() && this.state.path) {\n this.loadFromPath(this.state.path)\n return true\n }\n return false\n }\n\n /**\n * Start watching the schema file for changes\n */\n startWatching(onChange: (schema: SchemaType | undefined) => void): void {\n this.onChangeCallback = onChange\n\n if (!this.state.path || !existsSync(this.state.path)) {\n return\n }\n\n try {\n this.watcher = watch(this.state.path, (eventType) => {\n if (eventType === 'change' && this.state.path) {\n this.loadFromPath(this.state.path)\n this.onChangeCallback?.(this.state.schema)\n }\n })\n } catch (e) {\n // Watching may fail on some file systems\n console.error(`Failed to watch schema file: ${e}`)\n }\n }\n\n /**\n * Stop watching the schema file\n */\n stopWatching(): void {\n this.watcher?.close()\n this.watcher = null\n this.onChangeCallback = null\n }\n\n /**\n * Clear the loaded schema\n */\n clear(): void {\n this.stopWatching()\n this.state = {}\n }\n}\n\n/**\n * Singleton instance for convenience\n */\nlet defaultLoader: SchemaLoader | null = null\n\nexport function getSchemaLoader(): SchemaLoader {\n if (!defaultLoader) {\n defaultLoader = new SchemaLoader()\n }\n return defaultLoader\n}\n","/**\n * Extract GROQ queries from source files\n *\n * Supports:\n * - .groq files (entire content is GROQ)\n * - .ts/.tsx/.js/.jsx files (groq`...` template literals)\n */\n\nimport type { ExtractionResult, GroqQuery } from '../types.js'\n\n/**\n * Extract GROQ queries from a document based on its language/file type\n */\nexport function extractQueries(content: string, languageId: string): ExtractionResult {\n switch (languageId) {\n case 'groq':\n return extractFromGroqFile(content)\n case 'typescript':\n case 'typescriptreact':\n case 'javascript':\n case 'javascriptreact':\n return extractFromJsTs(content)\n default:\n return { queries: [], errors: [] }\n }\n}\n\n/**\n * Extract from a .groq file - the entire content is a GROQ query\n */\nfunction extractFromGroqFile(content: string): ExtractionResult {\n const trimmed = content.trim()\n if (!trimmed) {\n return { queries: [], errors: [] }\n }\n\n return {\n queries: [\n {\n query: trimmed,\n start: content.indexOf(trimmed),\n end: content.indexOf(trimmed) + trimmed.length,\n line: 0,\n column: 0,\n },\n ],\n errors: [],\n }\n}\n\n/**\n * Extract GROQ from JavaScript/TypeScript files\n * Looks for groq`...` tagged template literals\n */\nfunction extractFromJsTs(content: string): ExtractionResult {\n const queries: GroqQuery[] = []\n const errors: string[] = []\n\n // Match groq`...` template literals\n // This regex handles:\n // - groq`query`\n // - groq `query` (with space)\n // - Nested backticks are not handled (would need a proper parser)\n const groqTagRegex = /\\bgroq\\s*`([^`]*)`/g\n\n let match\n while ((match = groqTagRegex.exec(content)) !== null) {\n const queryContent = match[1]\n if (queryContent === undefined) continue\n\n const fullMatchStart = match.index\n // The query content starts after \"groq`\"\n const queryStart = fullMatchStart + match[0].indexOf('`') + 1\n\n // Calculate line and column\n const beforeMatch = content.slice(0, queryStart)\n const lines = beforeMatch.split('\\n')\n const line = lines.length - 1\n const column = lines[lines.length - 1]?.length ?? 0\n\n queries.push({\n query: queryContent,\n start: queryStart,\n end: queryStart + queryContent.length,\n line,\n column,\n })\n }\n\n return { queries, errors }\n}\n\n/**\n * Find which query (if any) contains a given offset\n */\nexport function findQueryAtOffset(queries: GroqQuery[], offset: number): GroqQuery | undefined {\n return queries.find((q) => offset >= q.start && offset <= q.end)\n}\n\n/**\n * Convert a document offset to a position within a query\n * Returns the offset relative to the query start\n */\nexport function offsetToQueryPosition(query: GroqQuery, documentOffset: number): number {\n return documentOffset - query.start\n}\n","/**\n * Diagnostics capability for the GROQ Language Server\n *\n * Converts groq-lint findings to LSP diagnostics\n */\n\nimport type { Diagnostic, DiagnosticSeverity, Range, Position } from 'vscode-languageserver'\nimport { lint, type Finding, type Severity } from '@sanity/groq-lint'\nimport type { SchemaType } from 'groq-js'\nimport type { GroqQuery } from '../types.js'\n\n/**\n * Options for computing diagnostics\n */\nexport interface DiagnosticsOptions {\n /** Schema for schema-aware rules */\n schema?: SchemaType | undefined\n}\n\n/**\n * Result of computing diagnostics for a query\n */\nexport interface QueryDiagnostics {\n /** The query that was analyzed */\n query: GroqQuery\n /** Diagnostics found */\n diagnostics: Diagnostic[]\n /** Parse error if query was invalid */\n parseError?: string\n}\n\n/**\n * Compute diagnostics for a single GROQ query\n */\nexport function computeQueryDiagnostics(\n query: GroqQuery,\n options: DiagnosticsOptions = {}\n): QueryDiagnostics {\n const result = lint(query.query, options.schema ? { schema: options.schema } : undefined)\n\n if (result.parseError) {\n // Create a diagnostic for the parse error\n const diagnostic: Diagnostic = {\n range: {\n start: { line: query.line, character: query.column },\n end: { line: query.line, character: query.column + query.query.length },\n },\n severity: 1 as DiagnosticSeverity, // Error\n source: 'groq',\n message: `Parse error: ${result.parseError}`,\n }\n\n return {\n query,\n diagnostics: [diagnostic],\n parseError: result.parseError,\n }\n }\n\n // Convert findings to diagnostics\n const diagnostics = result.findings.map((finding) => findingToDiagnostic(finding, query))\n\n return { query, diagnostics }\n}\n\n/**\n * Compute diagnostics for multiple queries in a document\n */\nexport function computeDocumentDiagnostics(\n queries: GroqQuery[],\n options: DiagnosticsOptions = {}\n): Diagnostic[] {\n const allDiagnostics: Diagnostic[] = []\n\n for (const query of queries) {\n const result = computeQueryDiagnostics(query, options)\n allDiagnostics.push(...result.diagnostics)\n }\n\n return allDiagnostics\n}\n\n/**\n * Convert a groq-lint Finding to an LSP Diagnostic\n */\nfunction findingToDiagnostic(finding: Finding, query: GroqQuery): Diagnostic {\n const range = findingToRange(finding, query)\n\n const diagnostic: Diagnostic = {\n range,\n severity: severityToLsp(finding.severity),\n source: 'groq',\n code: finding.ruleId,\n message: finding.message,\n }\n\n // Add help text as related information if available\n if (finding.help) {\n diagnostic.message += `\\n\\nHelp: ${finding.help}`\n }\n\n return diagnostic\n}\n\n/**\n * Convert a Finding's span to an LSP Range\n * Adjusts positions based on query location in document\n */\nfunction findingToRange(finding: Finding, query: GroqQuery): Range {\n if (finding.span) {\n // Adjust line and column based on query position in document\n const startLine = query.line + finding.span.start.line - 1\n const endLine = query.line + finding.span.end.line - 1\n\n // For the first line of a multi-line span, add the query column offset\n const startChar =\n finding.span.start.line === 1\n ? query.column + finding.span.start.column - 1\n : finding.span.start.column - 1\n\n const endChar =\n finding.span.end.line === 1\n ? query.column + finding.span.end.column - 1\n : finding.span.end.column - 1\n\n return {\n start: { line: startLine, character: startChar },\n end: { line: endLine, character: endChar },\n }\n }\n\n // No span - highlight the entire query\n return {\n start: { line: query.line, character: query.column },\n end: { line: query.line, character: query.column + query.query.length },\n }\n}\n\n/**\n * Convert groq-lint Severity to LSP DiagnosticSeverity\n */\nfunction severityToLsp(severity: Severity): DiagnosticSeverity {\n switch (severity) {\n case 'error':\n return 1 // DiagnosticSeverity.Error\n case 'warning':\n return 2 // DiagnosticSeverity.Warning\n case 'info':\n return 3 // DiagnosticSeverity.Information\n default:\n return 4 // DiagnosticSeverity.Hint\n }\n}\n\n/**\n * Convert LSP Position to offset within a query\n */\nexport function positionToQueryOffset(\n position: Position,\n query: GroqQuery,\n _documentLines: string[]\n): number | null {\n // Check if position is within the query\n const queryLines = query.query.split('\\n')\n const queryEndLine = query.line + queryLines.length - 1\n\n if (position.line < query.line || position.line > queryEndLine) {\n return null\n }\n\n // Calculate offset within the query\n let offset = 0\n\n for (let i = query.line; i < position.line; i++) {\n const lineInQuery = i - query.line\n if (lineInQuery < queryLines.length) {\n offset += (queryLines[lineInQuery]?.length ?? 0) + 1 // +1 for newline\n }\n }\n\n // Add character offset for the final line\n const lineInQuery = position.line - query.line\n if (lineInQuery === 0) {\n // First line of query - subtract query column offset\n offset += position.character - query.column\n } else {\n offset += position.character\n }\n\n // Validate offset is within query bounds\n if (offset < 0 || offset > query.query.length) {\n return null\n }\n\n return offset\n}\n","/**\n * Hover capability for the GROQ Language Server\n *\n * Shows type information and documentation when hovering over GROQ elements\n */\n\nimport type { Hover, MarkupContent } from 'vscode-languageserver'\nimport { parse, typeEvaluate, type TypeNode, type SchemaType } from 'groq-js'\nimport type { GroqQuery, TypeInfo } from '../types.js'\n\n/**\n * Options for computing hover information\n */\nexport interface HoverOptions {\n /** Schema for type evaluation */\n schema?: SchemaType | undefined\n}\n\n/**\n * Get hover information for a position in a GROQ query\n */\nexport function getHoverInfo(\n query: GroqQuery,\n positionInQuery: number,\n options: HoverOptions = {}\n): Hover | null {\n try {\n const ast = parse(query.query)\n\n // Find the node at the cursor position\n const nodeInfo = findNodeAtPosition(ast, positionInQuery, query.query)\n if (!nodeInfo) {\n return null\n }\n\n // Evaluate type if schema is available\n let typeInfo: TypeInfo | null = null\n if (options.schema) {\n try {\n const resultType = typeEvaluate(ast, options.schema)\n typeInfo = typeNodeToInfo(resultType, nodeInfo.text)\n } catch {\n // Type evaluation failed, continue without type info\n }\n }\n\n // Build hover content\n const content = buildHoverContent(nodeInfo, typeInfo)\n if (!content) {\n return null\n }\n\n return {\n contents: content,\n }\n } catch {\n // Parse failed, no hover info\n return null\n }\n}\n\n/**\n * Information about a node at a position\n */\ninterface NodeInfo {\n type: string\n text: string\n documentation?: string\n}\n\n/**\n * Find the node at a given position in the query\n * Returns basic info about what's at that position\n */\nfunction findNodeAtPosition(_ast: unknown, position: number, queryText: string): NodeInfo | null {\n // Extract the word/identifier at the position\n const { word, context } = extractWordAtPosition(queryText, position)\n\n if (!word) {\n return null\n }\n\n // Identify the type of element based on context\n if (context === 'filter' && word.startsWith('_type')) {\n return {\n type: 'filter',\n text: word,\n documentation: 'Filters documents by their type',\n }\n }\n\n if (word.startsWith('_')) {\n return {\n type: 'system-field',\n text: word,\n documentation: getSystemFieldDoc(word),\n }\n }\n\n if (word === '->') {\n return {\n type: 'dereference',\n text: '->',\n documentation: 'Dereferences a reference to fetch the referenced document',\n }\n }\n\n // Check if it's a GROQ function\n const funcDoc = getGroqFunctionDoc(word)\n if (funcDoc) {\n return {\n type: 'function',\n text: word,\n documentation: funcDoc,\n }\n }\n\n // Default to field access\n return {\n type: 'field',\n text: word,\n }\n}\n\n/**\n * Extract the word at a position and its context\n */\nfunction extractWordAtPosition(\n text: string,\n position: number\n): { word: string | null; context: string } {\n // Find word boundaries\n let start = position\n let end = position\n\n // Move start backwards to find word beginning\n while (start > 0 && isWordChar(text[start - 1])) {\n start--\n }\n\n // Move end forwards to find word end\n while (end < text.length && isWordChar(text[end])) {\n end++\n }\n\n const word = text.slice(start, end)\n\n // Determine context by looking at surrounding characters\n const before = text.slice(0, start).trim()\n let context = 'unknown'\n\n if (before.endsWith('[')) {\n context = 'filter'\n } else if (before.endsWith('{') || before.endsWith(',')) {\n context = 'projection'\n } else if (before.endsWith('->')) {\n context = 'dereference'\n }\n\n return { word: word || null, context }\n}\n\n/**\n * Check if a character is part of a word/identifier\n */\nfunction isWordChar(char: string | undefined): boolean {\n if (!char) return false\n return /[a-zA-Z0-9_]/.test(char)\n}\n\n/**\n * Get documentation for system fields\n */\nfunction getSystemFieldDoc(field: string): string {\n const docs: Record<string, string> = {\n _id: 'Unique document identifier',\n _type: 'Document type name',\n _rev: 'Document revision (changes on every update)',\n _createdAt: 'Timestamp when the document was created',\n _updatedAt: 'Timestamp when the document was last updated',\n _key: 'Array item key (unique within the array)',\n _ref: 'Reference target document ID',\n _weak: 'Whether this is a weak reference',\n }\n\n return docs[field] ?? `System field: ${field}`\n}\n\n/**\n * Get documentation for GROQ functions\n */\nfunction getGroqFunctionDoc(name: string): string | null {\n const docs: Record<string, string> = {\n count: 'count(array) - Returns the number of items in an array',\n defined: 'defined(value) - Returns true if the value is not null',\n coalesce: 'coalesce(a, b, ...) - Returns the first non-null value',\n select: 'select(conditions) - Returns value based on conditions',\n length: 'length(string|array) - Returns the length',\n lower: 'lower(string) - Converts string to lowercase',\n upper: 'upper(string) - Converts string to uppercase',\n now: 'now() - Returns the current timestamp',\n round: 'round(number, precision?) - Rounds a number',\n string: 'string(value) - Converts value to string',\n references: 'references(id) - Returns true if document references the ID',\n dateTime: 'dateTime(string) - Parses an ISO 8601 date string',\n boost: 'boost(condition, value) - Boosts score in text search',\n score: 'score(conditions) - Returns relevance score',\n order: 'order(field, direction?) - Sorts results',\n pt: 'pt::text(blocks) - Extracts plain text from Portable Text',\n geo: 'geo::distance(a, b) - Calculates distance between points',\n math: 'math::sum(array), math::avg(array), etc. - Math operations',\n array: 'array::unique(arr), array::compact(arr) - Array operations',\n sanity: 'sanity::projectId(), sanity::dataset() - Sanity project info',\n }\n\n return docs[name] ?? null\n}\n\n/**\n * Convert a groq-js TypeNode to a human-readable TypeInfo\n */\nfunction typeNodeToInfo(typeNode: TypeNode, fieldName: string): TypeInfo | null {\n // Simplified type display\n const typeStr = formatTypeNode(typeNode)\n\n return {\n type: typeStr,\n schemaType: fieldName,\n }\n}\n\n/**\n * Format a TypeNode as a human-readable string\n */\nfunction formatTypeNode(node: TypeNode): string {\n switch (node.type) {\n case 'null':\n return 'null'\n case 'boolean':\n return 'boolean'\n case 'number':\n return 'number'\n case 'string':\n return node.value !== undefined ? `\"${node.value}\"` : 'string'\n case 'array':\n return `array<${formatTypeNode(node.of)}>`\n case 'object':\n return 'object'\n case 'union':\n if (node.of.length <= 3) {\n return node.of.map(formatTypeNode).join(' | ')\n }\n return `union<${node.of.length} types>`\n case 'unknown':\n return 'unknown'\n case 'inline':\n return node.name\n default:\n return 'unknown'\n }\n}\n\n/**\n * Build hover content from node info and type info\n */\nfunction buildHoverContent(nodeInfo: NodeInfo, typeInfo: TypeInfo | null): MarkupContent | null {\n const parts: string[] = []\n\n // Type information\n if (typeInfo) {\n parts.push('```groq')\n parts.push(`${nodeInfo.text}: ${typeInfo.type}`)\n parts.push('```')\n } else {\n parts.push(`**${nodeInfo.text}** (${nodeInfo.type})`)\n }\n\n // Documentation\n if (nodeInfo.documentation) {\n parts.push('')\n parts.push(nodeInfo.documentation)\n }\n\n if (parts.length === 0) {\n return null\n }\n\n return {\n kind: 'markdown',\n value: parts.join('\\n'),\n }\n}\n","/**\n * Completion capability for the GROQ Language Server\n *\n * Provides auto-completion for:\n * - Field names (from schema)\n * - _type values (document types)\n * - GROQ functions\n * - System fields (_id, _type, etc.)\n */\n\nimport type { CompletionItem, CompletionItemKind } from 'vscode-languageserver'\nimport type { SchemaType } from 'groq-js'\nimport type { GroqQuery } from '../types.js'\n\n/**\n * Options for computing completions\n */\nexport interface CompletionOptions {\n /** Schema for field/type completions */\n schema?: SchemaType | undefined\n}\n\n/**\n * Context about where completion is triggered\n */\ninterface CompletionContext {\n /** What kind of completion to provide */\n kind: 'type-value' | 'field' | 'function' | 'unknown'\n /** Current document type context, if determinable */\n documentType?: string | undefined\n /** Partial text being typed */\n prefix: string\n}\n\n/**\n * Get completions for a position in a GROQ query\n */\nexport function getCompletions(\n query: GroqQuery,\n positionInQuery: number,\n options: CompletionOptions = {}\n): CompletionItem[] {\n const context = analyzeCompletionContext(query.query, positionInQuery)\n\n switch (context.kind) {\n case 'type-value':\n return getTypeValueCompletions(options.schema, context.prefix)\n case 'field':\n return getFieldCompletions(options.schema, context.documentType, context.prefix)\n case 'function':\n return getFunctionCompletions(context.prefix)\n default:\n // Provide all possible completions\n return [\n ...getFieldCompletions(options.schema, context.documentType, context.prefix),\n ...getFunctionCompletions(context.prefix),\n ...getSystemFieldCompletions(context.prefix),\n ]\n }\n}\n\n/**\n * Analyze the query context to determine what kind of completions to provide\n */\nfunction analyzeCompletionContext(query: string, position: number): CompletionContext {\n const before = query.slice(0, position)\n const prefix = extractPrefix(before)\n\n // Check if we're completing a _type value\n // Patterns: _type == \", _type == ', _type != \"\n if (/_type\\s*[=!]=\\s*[\"']$/.test(before) || /_type\\s*[=!]=\\s*[\"'][a-zA-Z0-9]*$/.test(before)) {\n return { kind: 'type-value', prefix: prefix.replace(/[\"']/g, '') }\n }\n\n // Try to find document type from earlier in the query\n // This matches _type == \"typeName\" anywhere in the query before our position\n const typeMatch = before.match(/_type\\s*==\\s*[\"'](\\w+)[\"']/i)\n const documentType = typeMatch?.[1]\n\n // Check if we're after a dot (field access) or in a projection\n if (/\\.\\s*\\w*$/.test(before) || /{\\s*[\\w,\\s]*$/.test(before)) {\n return { kind: 'field', documentType, prefix }\n }\n\n // Check if we're likely typing a function\n if (/[a-z]+$/.test(prefix) && !/->/.test(before.slice(-10))) {\n return { kind: 'function', prefix }\n }\n\n return { kind: 'unknown', documentType, prefix }\n}\n\n/**\n * Extract the prefix being typed (for filtering completions)\n */\nfunction extractPrefix(text: string): string {\n const match = text.match(/[a-zA-Z_][a-zA-Z0-9_]*$/)\n return match?.[0] ?? ''\n}\n\n/**\n * Get completions for _type values from schema\n */\nfunction getTypeValueCompletions(schema: SchemaType | undefined, prefix: string): CompletionItem[] {\n if (!schema || !Array.isArray(schema)) {\n return []\n }\n\n return schema\n .filter(\n (type) => type.type === 'document' && type.name.toLowerCase().includes(prefix.toLowerCase())\n )\n .map((type) => ({\n label: type.name,\n kind: 12 as CompletionItemKind, // Value\n detail: 'Document type',\n insertText: type.name,\n documentation: `Document type: ${type.name}`,\n }))\n}\n\n/**\n * Get completions for field names\n */\nfunction getFieldCompletions(\n schema: SchemaType | undefined,\n documentType: string | undefined,\n prefix: string\n): CompletionItem[] {\n const completions: CompletionItem[] = []\n\n // Add system fields\n completions.push(...getSystemFieldCompletions(prefix))\n\n // Add schema fields if schema and document type are available\n if (schema && Array.isArray(schema) && documentType) {\n const typeSchema = schema.find((t) => t.name === documentType)\n if (typeSchema && 'attributes' in typeSchema && typeSchema.attributes) {\n for (const [fieldName, _fieldDef] of Object.entries(typeSchema.attributes)) {\n if (fieldName.toLowerCase().includes(prefix.toLowerCase())) {\n completions.push({\n label: fieldName,\n kind: 5 as CompletionItemKind, // Field\n detail: `Field on ${documentType}`,\n insertText: fieldName,\n })\n }\n }\n }\n }\n\n return completions\n}\n\n/**\n * Get completions for system fields\n */\nfunction getSystemFieldCompletions(prefix: string): CompletionItem[] {\n const systemFields = [\n { name: '_id', doc: 'Unique document identifier' },\n { name: '_type', doc: 'Document type name' },\n { name: '_rev', doc: 'Document revision' },\n { name: '_createdAt', doc: 'Creation timestamp' },\n { name: '_updatedAt', doc: 'Last update timestamp' },\n { name: '_key', doc: 'Array item key' },\n { name: '_ref', doc: 'Reference target ID' },\n ]\n\n return systemFields\n .filter((f) => f.name.toLowerCase().includes(prefix.toLowerCase()))\n .map((f) => ({\n label: f.name,\n kind: 5 as CompletionItemKind, // Field\n detail: 'System field',\n documentation: f.doc,\n insertText: f.name,\n }))\n}\n\n/**\n * Get completions for GROQ functions\n */\nfunction getFunctionCompletions(prefix: string): CompletionItem[] {\n const functions = [\n { name: 'count', sig: 'count(array)', doc: 'Returns the number of items' },\n { name: 'defined', sig: 'defined(value)', doc: 'Returns true if value is not null' },\n { name: 'coalesce', sig: 'coalesce(a, b, ...)', doc: 'Returns first non-null value' },\n { name: 'select', sig: 'select(cond => val, ...)', doc: 'Conditional value selection' },\n { name: 'length', sig: 'length(str|arr)', doc: 'Returns length' },\n { name: 'lower', sig: 'lower(string)', doc: 'Converts to lowercase' },\n { name: 'upper', sig: 'upper(string)', doc: 'Converts to uppercase' },\n { name: 'now', sig: 'now()', doc: 'Current timestamp' },\n { name: 'round', sig: 'round(num, precision?)', doc: 'Rounds a number' },\n { name: 'string', sig: 'string(value)', doc: 'Converts to string' },\n { name: 'references', sig: 'references(id)', doc: 'Checks if document references ID' },\n { name: 'dateTime', sig: 'dateTime(string)', doc: 'Parses ISO 8601 date' },\n { name: 'order', sig: 'order(field, dir?)', doc: 'Sorts results' },\n { name: 'score', sig: 'score(...conditions)', doc: 'Relevance scoring' },\n { name: 'boost', sig: 'boost(cond, value)', doc: 'Boosts search score' },\n // Namespace functions\n { name: 'pt::text', sig: 'pt::text(blocks)', doc: 'Extract text from Portable Text' },\n { name: 'geo::distance', sig: 'geo::distance(a, b)', doc: 'Distance between points' },\n { name: 'math::sum', sig: 'math::sum(array)', doc: 'Sum of numbers' },\n { name: 'math::avg', sig: 'math::avg(array)', doc: 'Average of numbers' },\n { name: 'math::min', sig: 'math::min(array)', doc: 'Minimum value' },\n { name: 'math::max', sig: 'math::max(array)', doc: 'Maximum value' },\n { name: 'array::unique', sig: 'array::unique(arr)', doc: 'Remove duplicates' },\n { name: 'array::compact', sig: 'array::compact(arr)', doc: 'Remove nulls' },\n { name: 'array::join', sig: 'array::join(arr, sep)', doc: 'Join into string' },\n { name: 'string::split', sig: 'string::split(str, sep)', doc: 'Split string' },\n { name: 'string::startsWith', sig: 'string::startsWith(str, prefix)', doc: 'Check prefix' },\n { name: 'sanity::projectId', sig: 'sanity::projectId()', doc: 'Current project ID' },\n { name: 'sanity::dataset', sig: 'sanity::dataset()', doc: 'Current dataset' },\n ]\n\n return functions\n .filter((f) => f.name.toLowerCase().includes(prefix.toLowerCase()))\n .map((f) => ({\n label: f.name,\n kind: 3 as CompletionItemKind, // Function\n detail: f.sig,\n documentation: f.doc,\n insertText: f.name.includes('::') ? f.name : `${f.name}($1)`,\n insertTextFormat: 2, // Snippet\n }))\n}\n\n/**\n * Get trigger characters for completion\n */\nexport function getCompletionTriggerCharacters(): string[] {\n return ['.', '\"', \"'\", '[', '{', ':']\n}\n","/**\n * Formatting capability for the GROQ Language Server\n *\n * Uses prettier-plugin-groq for GROQ formatting\n */\n\nimport type { TextEdit, Range } from 'vscode-languageserver'\nimport type { GroqQuery } from '../types.js'\n\n/**\n * Options for formatting\n */\nexport interface FormattingOptions {\n /** Tab size in spaces */\n tabSize?: number\n /** Use tabs instead of spaces */\n insertSpaces?: boolean\n /** Print width (line length) */\n printWidth?: number\n}\n\n/**\n * Format a GROQ query using prettier\n */\nexport async function formatQuery(\n query: GroqQuery,\n options: FormattingOptions = {}\n): Promise<TextEdit[]> {\n try {\n // Dynamic import to avoid bundling issues\n const prettier = await import('prettier')\n const groqPlugin = await import('prettier-plugin-groq')\n\n const formatted = await prettier.format(query.query, {\n parser: 'groq',\n plugins: [groqPlugin.default ?? groqPlugin],\n tabWidth: options.tabSize ?? 2,\n useTabs: !(options.insertSpaces ?? true),\n printWidth: options.printWidth ?? 80,\n })\n\n // Trim trailing newline that prettier adds\n const trimmedFormatted = formatted.trimEnd()\n\n // No changes needed\n if (trimmedFormatted === query.query) {\n return []\n }\n\n // Calculate the range to replace\n const range = queryToRange(query)\n\n return [\n {\n range,\n newText: trimmedFormatted,\n },\n ]\n } catch (error) {\n // Formatting failed (likely parse error), return empty edits\n console.error('Formatting failed:', error)\n return []\n }\n}\n\n/**\n * Format all queries in a document\n */\nexport async function formatDocument(\n queries: GroqQuery[],\n _documentContent: string,\n options: FormattingOptions = {}\n): Promise<TextEdit[]> {\n const edits: TextEdit[] = []\n\n // Process queries in reverse order so edits don't affect subsequent positions\n const sortedQueries = [...queries].sort((a, b) => b.start - a.start)\n\n for (const query of sortedQueries) {\n const queryEdits = await formatQuery(query, options)\n edits.push(...queryEdits)\n }\n\n return edits\n}\n\n/**\n * Convert a GroqQuery to an LSP Range\n */\nfunction queryToRange(query: GroqQuery): Range {\n // Calculate end position\n const lines = query.query.split('\\n')\n const endLine = query.line + lines.length - 1\n const endChar =\n lines.length === 1 ? query.column + query.query.length : (lines[lines.length - 1]?.length ?? 0)\n\n return {\n start: { line: query.line, character: query.column },\n end: { line: endLine, character: endChar },\n }\n}\n\n/**\n * Format a range in a GROQ file (.groq)\n * The entire file is treated as a single query\n */\nexport async function formatGroqFile(\n content: string,\n options: FormattingOptions = {}\n): Promise<TextEdit[]> {\n const query: GroqQuery = {\n query: content.trim(),\n start: 0,\n end: content.length,\n line: 0,\n column: 0,\n }\n\n return formatQuery(query, options)\n}\n"],"mappings":"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","names":["lineInQuery"]}
@@ -0,0 +1,271 @@
1
+ import { SchemaType } from 'groq-js';
2
+ import { Diagnostic, Position, Hover, CompletionItem, TextEdit } from 'vscode-languageserver';
3
+
4
+ /**
5
+ * Types for the GROQ Language Server
6
+ */
7
+
8
+ /**
9
+ * Represents a GROQ query found in a document
10
+ */
11
+ interface GroqQuery {
12
+ /** The GROQ query string */
13
+ query: string;
14
+ /** Start offset in the document */
15
+ start: number;
16
+ /** End offset in the document */
17
+ end: number;
18
+ /** Line number (0-indexed) */
19
+ line: number;
20
+ /** Column number (0-indexed) */
21
+ column: number;
22
+ }
23
+ /**
24
+ * Server configuration options
25
+ */
26
+ interface ServerConfig {
27
+ /** Path to schema.json file */
28
+ schemaPath?: string;
29
+ /** Whether to watch for schema changes */
30
+ watchSchema?: boolean;
31
+ /** Root directory for schema discovery */
32
+ workspaceRoot?: string;
33
+ }
34
+ /**
35
+ * Schema state maintained by the server
36
+ */
37
+ interface SchemaState {
38
+ /** The loaded schema, if any */
39
+ schema?: SchemaType;
40
+ /** Path the schema was loaded from */
41
+ path?: string;
42
+ /** Last modification time */
43
+ lastModified?: number;
44
+ /** Loading error, if any */
45
+ error?: string;
46
+ }
47
+ /**
48
+ * Document state maintained per-file
49
+ */
50
+ interface DocumentState {
51
+ /** Document URI */
52
+ uri: string;
53
+ /** Document content */
54
+ content: string;
55
+ /** Extracted GROQ queries */
56
+ queries: GroqQuery[];
57
+ /** Document version for incremental updates */
58
+ version: number;
59
+ }
60
+ /**
61
+ * Result from extracting GROQ from source files
62
+ */
63
+ interface ExtractionResult {
64
+ /** Extracted queries with positions */
65
+ queries: GroqQuery[];
66
+ /** Any extraction errors */
67
+ errors: string[];
68
+ }
69
+ /**
70
+ * Type information for hover display
71
+ */
72
+ interface TypeInfo {
73
+ /** Human-readable type description */
74
+ type: string;
75
+ /** Optional documentation */
76
+ documentation?: string;
77
+ /** Source schema type name */
78
+ schemaType?: string;
79
+ }
80
+
81
+ /**
82
+ * Schema loading and caching for the LSP server
83
+ *
84
+ * Handles:
85
+ * - Loading schema.json from disk
86
+ * - Auto-discovery of schema files
87
+ * - Caching and invalidation
88
+ * - File watching for hot-reload
89
+ */
90
+
91
+ /**
92
+ * SchemaLoader manages schema state for the LSP server
93
+ */
94
+ declare class SchemaLoader {
95
+ private state;
96
+ private watcher;
97
+ private onChangeCallback;
98
+ /**
99
+ * Load schema from a specific path
100
+ */
101
+ loadFromPath(schemaPath: string): SchemaState;
102
+ /**
103
+ * Auto-discover schema file in workspace
104
+ */
105
+ discoverSchema(workspaceRoot: string): SchemaState;
106
+ /**
107
+ * Get the current schema state
108
+ */
109
+ getState(): SchemaState;
110
+ /**
111
+ * Get the loaded schema (if any)
112
+ */
113
+ getSchema(): SchemaType | undefined;
114
+ /**
115
+ * Check if schema needs reloading (file changed)
116
+ */
117
+ needsReload(): boolean;
118
+ /**
119
+ * Reload schema if the file has changed
120
+ */
121
+ reloadIfNeeded(): boolean;
122
+ /**
123
+ * Start watching the schema file for changes
124
+ */
125
+ startWatching(onChange: (schema: SchemaType | undefined) => void): void;
126
+ /**
127
+ * Stop watching the schema file
128
+ */
129
+ stopWatching(): void;
130
+ /**
131
+ * Clear the loaded schema
132
+ */
133
+ clear(): void;
134
+ }
135
+ declare function getSchemaLoader(): SchemaLoader;
136
+
137
+ /**
138
+ * Extract GROQ queries from source files
139
+ *
140
+ * Supports:
141
+ * - .groq files (entire content is GROQ)
142
+ * - .ts/.tsx/.js/.jsx files (groq`...` template literals)
143
+ */
144
+
145
+ /**
146
+ * Extract GROQ queries from a document based on its language/file type
147
+ */
148
+ declare function extractQueries(content: string, languageId: string): ExtractionResult;
149
+ /**
150
+ * Find which query (if any) contains a given offset
151
+ */
152
+ declare function findQueryAtOffset(queries: GroqQuery[], offset: number): GroqQuery | undefined;
153
+ /**
154
+ * Convert a document offset to a position within a query
155
+ * Returns the offset relative to the query start
156
+ */
157
+ declare function offsetToQueryPosition(query: GroqQuery, documentOffset: number): number;
158
+
159
+ /**
160
+ * Diagnostics capability for the GROQ Language Server
161
+ *
162
+ * Converts groq-lint findings to LSP diagnostics
163
+ */
164
+
165
+ /**
166
+ * Options for computing diagnostics
167
+ */
168
+ interface DiagnosticsOptions {
169
+ /** Schema for schema-aware rules */
170
+ schema?: SchemaType | undefined;
171
+ }
172
+ /**
173
+ * Result of computing diagnostics for a query
174
+ */
175
+ interface QueryDiagnostics {
176
+ /** The query that was analyzed */
177
+ query: GroqQuery;
178
+ /** Diagnostics found */
179
+ diagnostics: Diagnostic[];
180
+ /** Parse error if query was invalid */
181
+ parseError?: string;
182
+ }
183
+ /**
184
+ * Compute diagnostics for a single GROQ query
185
+ */
186
+ declare function computeQueryDiagnostics(query: GroqQuery, options?: DiagnosticsOptions): QueryDiagnostics;
187
+ /**
188
+ * Compute diagnostics for multiple queries in a document
189
+ */
190
+ declare function computeDocumentDiagnostics(queries: GroqQuery[], options?: DiagnosticsOptions): Diagnostic[];
191
+ /**
192
+ * Convert LSP Position to offset within a query
193
+ */
194
+ declare function positionToQueryOffset(position: Position, query: GroqQuery, _documentLines: string[]): number | null;
195
+
196
+ /**
197
+ * Hover capability for the GROQ Language Server
198
+ *
199
+ * Shows type information and documentation when hovering over GROQ elements
200
+ */
201
+
202
+ /**
203
+ * Options for computing hover information
204
+ */
205
+ interface HoverOptions {
206
+ /** Schema for type evaluation */
207
+ schema?: SchemaType | undefined;
208
+ }
209
+ /**
210
+ * Get hover information for a position in a GROQ query
211
+ */
212
+ declare function getHoverInfo(query: GroqQuery, positionInQuery: number, options?: HoverOptions): Hover | null;
213
+
214
+ /**
215
+ * Completion capability for the GROQ Language Server
216
+ *
217
+ * Provides auto-completion for:
218
+ * - Field names (from schema)
219
+ * - _type values (document types)
220
+ * - GROQ functions
221
+ * - System fields (_id, _type, etc.)
222
+ */
223
+
224
+ /**
225
+ * Options for computing completions
226
+ */
227
+ interface CompletionOptions {
228
+ /** Schema for field/type completions */
229
+ schema?: SchemaType | undefined;
230
+ }
231
+ /**
232
+ * Get completions for a position in a GROQ query
233
+ */
234
+ declare function getCompletions(query: GroqQuery, positionInQuery: number, options?: CompletionOptions): CompletionItem[];
235
+ /**
236
+ * Get trigger characters for completion
237
+ */
238
+ declare function getCompletionTriggerCharacters(): string[];
239
+
240
+ /**
241
+ * Formatting capability for the GROQ Language Server
242
+ *
243
+ * Uses prettier-plugin-groq for GROQ formatting
244
+ */
245
+
246
+ /**
247
+ * Options for formatting
248
+ */
249
+ interface FormattingOptions {
250
+ /** Tab size in spaces */
251
+ tabSize?: number;
252
+ /** Use tabs instead of spaces */
253
+ insertSpaces?: boolean;
254
+ /** Print width (line length) */
255
+ printWidth?: number;
256
+ }
257
+ /**
258
+ * Format a GROQ query using prettier
259
+ */
260
+ declare function formatQuery(query: GroqQuery, options?: FormattingOptions): Promise<TextEdit[]>;
261
+ /**
262
+ * Format all queries in a document
263
+ */
264
+ declare function formatDocument(queries: GroqQuery[], _documentContent: string, options?: FormattingOptions): Promise<TextEdit[]>;
265
+ /**
266
+ * Format a range in a GROQ file (.groq)
267
+ * The entire file is treated as a single query
268
+ */
269
+ declare function formatGroqFile(content: string, options?: FormattingOptions): Promise<TextEdit[]>;
270
+
271
+ export { type CompletionOptions, type DiagnosticsOptions, type DocumentState, type ExtractionResult, type FormattingOptions, type GroqQuery, type HoverOptions, type QueryDiagnostics, SchemaLoader, type SchemaState, type ServerConfig, type TypeInfo, computeDocumentDiagnostics, computeQueryDiagnostics, extractQueries, findQueryAtOffset, formatDocument, formatGroqFile, formatQuery, getCompletionTriggerCharacters, getCompletions, getHoverInfo, getSchemaLoader, offsetToQueryPosition, positionToQueryOffset };
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ import {
2
+ SchemaLoader,
3
+ computeDocumentDiagnostics,
4
+ computeQueryDiagnostics,
5
+ extractQueries,
6
+ findQueryAtOffset,
7
+ formatDocument,
8
+ formatGroqFile,
9
+ formatQuery,
10
+ getCompletionTriggerCharacters,
11
+ getCompletions,
12
+ getHoverInfo,
13
+ getSchemaLoader,
14
+ offsetToQueryPosition,
15
+ positionToQueryOffset
16
+ } from "./chunk-UOIHOMN2.js";
17
+ export {
18
+ SchemaLoader,
19
+ computeDocumentDiagnostics,
20
+ computeQueryDiagnostics,
21
+ extractQueries,
22
+ findQueryAtOffset,
23
+ formatDocument,
24
+ formatGroqFile,
25
+ formatQuery,
26
+ getCompletionTriggerCharacters,
27
+ getCompletions,
28
+ getHoverInfo,
29
+ getSchemaLoader,
30
+ offsetToQueryPosition,
31
+ positionToQueryOffset
32
+ };
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/server.js ADDED
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ SchemaLoader,
4
+ computeDocumentDiagnostics,
5
+ extractQueries,
6
+ findQueryAtOffset,
7
+ formatDocument,
8
+ formatGroqFile,
9
+ getCompletionTriggerCharacters,
10
+ getCompletions,
11
+ getHoverInfo,
12
+ offsetToQueryPosition
13
+ } from "./chunk-UOIHOMN2.js";
14
+
15
+ // src/server.ts
16
+ import {
17
+ createConnection,
18
+ ProposedFeatures,
19
+ TextDocuments,
20
+ TextDocumentSyncKind,
21
+ DidChangeConfigurationNotification
22
+ } from "vscode-languageserver/node.js";
23
+ import { TextDocument } from "vscode-languageserver-textdocument";
24
+ import { URI } from "vscode-uri";
25
+ import { initLinter } from "@sanity/groq-lint";
26
+ import { initWasmFormatter } from "prettier-plugin-groq";
27
+ var connection = createConnection(ProposedFeatures.all);
28
+ var documents = new TextDocuments(TextDocument);
29
+ var schemaLoader = new SchemaLoader();
30
+ var documentStates = /* @__PURE__ */ new Map();
31
+ var hasConfigurationCapability = false;
32
+ var hasWorkspaceFolderCapability = false;
33
+ var defaultSettings = {
34
+ maxDiagnostics: 100,
35
+ enableFormatting: true
36
+ };
37
+ var globalSettings = defaultSettings;
38
+ var documentSettings = /* @__PURE__ */ new Map();
39
+ connection.onInitialize((params) => {
40
+ const capabilities = params.capabilities;
41
+ hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration);
42
+ hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders);
43
+ if (params.workspaceFolders && params.workspaceFolders.length > 0) {
44
+ const workspaceUri = params.workspaceFolders[0]?.uri;
45
+ if (workspaceUri) {
46
+ const workspacePath = URI.parse(workspaceUri).fsPath;
47
+ schemaLoader.discoverSchema(workspacePath);
48
+ }
49
+ }
50
+ const result = {
51
+ capabilities: {
52
+ textDocumentSync: TextDocumentSyncKind.Incremental,
53
+ // Completion
54
+ completionProvider: {
55
+ resolveProvider: false,
56
+ triggerCharacters: getCompletionTriggerCharacters()
57
+ },
58
+ // Hover
59
+ hoverProvider: true,
60
+ // Formatting
61
+ documentFormattingProvider: true
62
+ // We'll compute diagnostics on document change
63
+ }
64
+ };
65
+ if (hasWorkspaceFolderCapability) {
66
+ result.capabilities.workspace = {
67
+ workspaceFolders: {
68
+ supported: true
69
+ }
70
+ };
71
+ }
72
+ return result;
73
+ });
74
+ connection.onInitialized(async () => {
75
+ if (hasConfigurationCapability) {
76
+ connection.client.register(DidChangeConfigurationNotification.type, void 0);
77
+ }
78
+ try {
79
+ const [linterWasm, formatterWasm] = await Promise.all([initLinter(), initWasmFormatter()]);
80
+ if (linterWasm) {
81
+ connection.console.log("WASM linter initialized");
82
+ }
83
+ if (formatterWasm) {
84
+ connection.console.log("WASM formatter initialized");
85
+ }
86
+ } catch {
87
+ }
88
+ schemaLoader.startWatching((schema) => {
89
+ connection.console.log(`Schema ${schema ? "reloaded" : "cleared"}`);
90
+ documents.all().forEach(validateDocument);
91
+ });
92
+ connection.console.log("GROQ Language Server initialized");
93
+ });
94
+ connection.onDidChangeConfiguration((change) => {
95
+ if (hasConfigurationCapability) {
96
+ documentSettings.clear();
97
+ }
98
+ globalSettings = change.settings?.groq ?? defaultSettings;
99
+ if (globalSettings.schemaPath) {
100
+ schemaLoader.loadFromPath(globalSettings.schemaPath);
101
+ }
102
+ documents.all().forEach(validateDocument);
103
+ });
104
+ documents.onDidChangeContent((change) => {
105
+ validateDocument(change.document);
106
+ });
107
+ documents.onDidClose((e) => {
108
+ documentSettings.delete(e.document.uri);
109
+ documentStates.delete(e.document.uri);
110
+ });
111
+ function validateDocument(document) {
112
+ const content = document.getText();
113
+ const languageId = document.languageId;
114
+ const uri = document.uri;
115
+ const { queries } = extractQueries(content, languageId);
116
+ documentStates.set(uri, {
117
+ uri,
118
+ content,
119
+ queries,
120
+ version: document.version
121
+ });
122
+ if (queries.length === 0) {
123
+ connection.sendDiagnostics({ uri, diagnostics: [] });
124
+ return;
125
+ }
126
+ const schema = schemaLoader.getSchema();
127
+ const diagnostics = computeDocumentDiagnostics(queries, { schema });
128
+ const maxDiagnostics = globalSettings.maxDiagnostics ?? 100;
129
+ const limitedDiagnostics = diagnostics.slice(0, maxDiagnostics);
130
+ connection.sendDiagnostics({ uri, diagnostics: limitedDiagnostics });
131
+ }
132
+ function findQueryAtPosition(document, position) {
133
+ const state = documentStates.get(document.uri);
134
+ if (!state) return void 0;
135
+ const offset = document.offsetAt(position);
136
+ return findQueryAtOffset(state.queries, offset);
137
+ }
138
+ connection.onHover((params) => {
139
+ const document = documents.get(params.textDocument.uri);
140
+ if (!document) return null;
141
+ const query = findQueryAtPosition(document, params.position);
142
+ if (!query) return null;
143
+ const documentOffset = document.offsetAt(params.position);
144
+ const queryOffset = offsetToQueryPosition(query, documentOffset);
145
+ const schema = schemaLoader.getSchema();
146
+ return getHoverInfo(query, queryOffset, { schema });
147
+ });
148
+ connection.onCompletion((params) => {
149
+ const document = documents.get(params.textDocument.uri);
150
+ if (!document) return [];
151
+ const query = findQueryAtPosition(document, params.position);
152
+ if (!query) {
153
+ return [];
154
+ }
155
+ const documentOffset = document.offsetAt(params.position);
156
+ const queryOffset = offsetToQueryPosition(query, documentOffset);
157
+ const schema = schemaLoader.getSchema();
158
+ return getCompletions(query, queryOffset, { schema });
159
+ });
160
+ connection.onDocumentFormatting(async (params) => {
161
+ if (!globalSettings.enableFormatting) {
162
+ return [];
163
+ }
164
+ const document = documents.get(params.textDocument.uri);
165
+ if (!document) return [];
166
+ const content = document.getText();
167
+ const languageId = document.languageId;
168
+ if (languageId === "groq") {
169
+ return formatGroqFile(content, {
170
+ tabSize: params.options.tabSize,
171
+ insertSpaces: params.options.insertSpaces
172
+ });
173
+ }
174
+ const state = documentStates.get(document.uri);
175
+ if (!state || state.queries.length === 0) {
176
+ return [];
177
+ }
178
+ return formatDocument(state.queries, content, {
179
+ tabSize: params.options.tabSize,
180
+ insertSpaces: params.options.insertSpaces
181
+ });
182
+ });
183
+ documents.listen(connection);
184
+ connection.listen();
185
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * GROQ Language Server\n *\n * Provides IDE features for GROQ queries:\n * - Diagnostics (linting)\n * - Hover (type information)\n * - Completion (fields, functions, types)\n * - Formatting (via prettier-plugin-groq)\n */\n\nimport {\n createConnection,\n ProposedFeatures,\n TextDocuments,\n TextDocumentSyncKind,\n InitializeResult,\n DidChangeConfigurationNotification,\n type InitializeParams,\n type TextDocumentPositionParams,\n type DocumentFormattingParams,\n type CompletionParams,\n} from 'vscode-languageserver/node.js'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { URI } from 'vscode-uri'\n\nimport { SchemaLoader } from './schema/loader.js'\nimport { extractQueries, findQueryAtOffset, offsetToQueryPosition } from './utils/groq-extractor.js'\nimport { computeDocumentDiagnostics } from './capabilities/diagnostics.js'\nimport { getHoverInfo } from './capabilities/hover.js'\nimport { getCompletions, getCompletionTriggerCharacters } from './capabilities/completion.js'\nimport { formatDocument, formatGroqFile } from './capabilities/formatting.js'\nimport type { GroqQuery, DocumentState } from './types.js'\nimport { initLinter } from '@sanity/groq-lint'\nimport { initWasmFormatter } from 'prettier-plugin-groq'\n\n// Create connection using Node IPC\nconst connection = createConnection(ProposedFeatures.all)\n\n// Document manager\nconst documents = new TextDocuments(TextDocument)\n\n// Schema loader\nconst schemaLoader = new SchemaLoader()\n\n// Document states (caches extracted queries)\nconst documentStates = new Map<string, DocumentState>()\n\n// Server capabilities\nlet hasConfigurationCapability = false\nlet hasWorkspaceFolderCapability = false\n\n/**\n * Server settings\n */\ninterface Settings {\n /** Path to schema.json */\n schemaPath?: string\n /** Maximum number of diagnostics to report */\n maxDiagnostics?: number\n /** Enable formatting */\n enableFormatting?: boolean\n}\n\n// Default settings\nconst defaultSettings: Settings = {\n maxDiagnostics: 100,\n enableFormatting: true,\n}\n\n// Global settings\nlet globalSettings: Settings = defaultSettings\n\n// Per-document settings (for future use)\nconst documentSettings = new Map<string, Thenable<Settings>>()\n\n/**\n * Initialize the server\n */\nconnection.onInitialize((params: InitializeParams): InitializeResult => {\n const capabilities = params.capabilities\n\n hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration)\n hasWorkspaceFolderCapability = !!(\n capabilities.workspace && !!capabilities.workspace.workspaceFolders\n )\n\n // Try to discover schema in workspace\n if (params.workspaceFolders && params.workspaceFolders.length > 0) {\n const workspaceUri = params.workspaceFolders[0]?.uri\n if (workspaceUri) {\n const workspacePath = URI.parse(workspaceUri).fsPath\n schemaLoader.discoverSchema(workspacePath)\n }\n }\n\n const result: InitializeResult = {\n capabilities: {\n textDocumentSync: TextDocumentSyncKind.Incremental,\n // Completion\n completionProvider: {\n resolveProvider: false,\n triggerCharacters: getCompletionTriggerCharacters(),\n },\n // Hover\n hoverProvider: true,\n // Formatting\n documentFormattingProvider: true,\n // We'll compute diagnostics on document change\n },\n }\n\n if (hasWorkspaceFolderCapability) {\n result.capabilities.workspace = {\n workspaceFolders: {\n supported: true,\n },\n }\n }\n\n return result\n})\n\n/**\n * After initialization\n */\nconnection.onInitialized(async () => {\n if (hasConfigurationCapability) {\n // Register for configuration changes\n connection.client.register(DidChangeConfigurationNotification.type, undefined)\n }\n\n // Initialize WASM for better performance (linting and formatting)\n try {\n const [linterWasm, formatterWasm] = await Promise.all([initLinter(), initWasmFormatter()])\n if (linterWasm) {\n connection.console.log('WASM linter initialized')\n }\n if (formatterWasm) {\n connection.console.log('WASM formatter initialized')\n }\n } catch {\n // WASM not available, using TypeScript fallback\n }\n\n // Start watching schema for changes\n schemaLoader.startWatching((schema) => {\n connection.console.log(`Schema ${schema ? 'reloaded' : 'cleared'}`)\n // Re-validate all open documents\n documents.all().forEach(validateDocument)\n })\n\n connection.console.log('GROQ Language Server initialized')\n})\n\n/**\n * Configuration changed\n */\nconnection.onDidChangeConfiguration((change) => {\n if (hasConfigurationCapability) {\n // Reset cached settings\n documentSettings.clear()\n }\n\n // Update global settings\n globalSettings = (change.settings?.groq as Settings) ?? defaultSettings\n\n // Load schema from settings if specified\n if (globalSettings.schemaPath) {\n schemaLoader.loadFromPath(globalSettings.schemaPath)\n }\n\n // Re-validate all documents\n documents.all().forEach(validateDocument)\n})\n\n/**\n * Document opened or changed\n */\ndocuments.onDidChangeContent((change) => {\n validateDocument(change.document)\n})\n\n/**\n * Document closed\n */\ndocuments.onDidClose((e) => {\n documentSettings.delete(e.document.uri)\n documentStates.delete(e.document.uri)\n})\n\n/**\n * Validate a document and send diagnostics\n */\nfunction validateDocument(document: TextDocument): void {\n const content = document.getText()\n const languageId = document.languageId\n const uri = document.uri\n\n // Extract queries from document\n const { queries } = extractQueries(content, languageId)\n\n // Update document state\n documentStates.set(uri, {\n uri,\n content,\n queries,\n version: document.version,\n })\n\n // No queries to validate\n if (queries.length === 0) {\n connection.sendDiagnostics({ uri, diagnostics: [] })\n return\n }\n\n // Compute diagnostics\n const schema = schemaLoader.getSchema()\n const diagnostics = computeDocumentDiagnostics(queries, { schema })\n\n // Apply limit\n const maxDiagnostics = globalSettings.maxDiagnostics ?? 100\n const limitedDiagnostics = diagnostics.slice(0, maxDiagnostics)\n\n // Send diagnostics\n connection.sendDiagnostics({ uri, diagnostics: limitedDiagnostics })\n}\n\n/**\n * Find the query at a position in a document\n */\nfunction findQueryAtPosition(\n document: TextDocument,\n position: { line: number; character: number }\n): GroqQuery | undefined {\n const state = documentStates.get(document.uri)\n if (!state) return undefined\n\n // Convert LSP position to offset\n const offset = document.offsetAt(position)\n return findQueryAtOffset(state.queries, offset)\n}\n\n/**\n * Hover handler\n */\nconnection.onHover((params: TextDocumentPositionParams) => {\n const document = documents.get(params.textDocument.uri)\n if (!document) return null\n\n const query = findQueryAtPosition(document, params.position)\n if (!query) return null\n\n // Get offset within the query\n const documentOffset = document.offsetAt(params.position)\n const queryOffset = offsetToQueryPosition(query, documentOffset)\n\n const schema = schemaLoader.getSchema()\n return getHoverInfo(query, queryOffset, { schema })\n})\n\n/**\n * Completion handler\n */\nconnection.onCompletion((params: CompletionParams) => {\n const document = documents.get(params.textDocument.uri)\n if (!document) return []\n\n const query = findQueryAtPosition(document, params.position)\n if (!query) {\n // Not inside a query, provide no completions\n return []\n }\n\n // Get offset within the query\n const documentOffset = document.offsetAt(params.position)\n const queryOffset = offsetToQueryPosition(query, documentOffset)\n\n const schema = schemaLoader.getSchema()\n return getCompletions(query, queryOffset, { schema })\n})\n\n/**\n * Formatting handler\n */\nconnection.onDocumentFormatting(async (params: DocumentFormattingParams) => {\n if (!globalSettings.enableFormatting) {\n return []\n }\n\n const document = documents.get(params.textDocument.uri)\n if (!document) return []\n\n const content = document.getText()\n const languageId = document.languageId\n\n // Handle .groq files specially (entire file is GROQ)\n if (languageId === 'groq') {\n return formatGroqFile(content, {\n tabSize: params.options.tabSize,\n insertSpaces: params.options.insertSpaces,\n })\n }\n\n // For JS/TS files, format embedded queries\n const state = documentStates.get(document.uri)\n if (!state || state.queries.length === 0) {\n return []\n }\n\n return formatDocument(state.queries, content, {\n tabSize: params.options.tabSize,\n insertSpaces: params.options.insertSpaces,\n })\n})\n\n// Start listening\ndocuments.listen(connection)\nconnection.listen()\n"],"mappings":"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","names":[]}