@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.
- package/LICENSE +21 -0
- package/README.md +329 -0
- package/dist/chunk-UOIHOMN2.js +703 -0
- package/dist/chunk-UOIHOMN2.js.map +1 -0
- package/dist/index.d.ts +271 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +185 -0
- package/dist/server.js.map +1 -0
- package/package.json +59 -0
|
@@ -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"]}
|
package/dist/index.d.ts
ADDED
|
@@ -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":[]}
|
package/dist/server.d.ts
ADDED
|
@@ -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":[]}
|