@sanity/groq-lsp 0.0.1 → 0.0.3

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.
@@ -631,7 +631,7 @@ function getCompletionTriggerCharacters() {
631
631
  async function formatQuery(query, options = {}) {
632
632
  try {
633
633
  const prettier = await import("prettier");
634
- const groqPlugin = await import("prettier-plugin-groq");
634
+ const groqPlugin = await import("@sanity/prettier-plugin-groq");
635
635
  const formatted = await prettier.format(query.query, {
636
636
  parser: "groq",
637
637
  plugins: [groqPlugin.default ?? groqPlugin],
@@ -700,4 +700,4 @@ export {
700
700
  formatDocument,
701
701
  formatGroqFile
702
702
  };
703
- //# sourceMappingURL=chunk-UOIHOMN2.js.map
703
+ //# sourceMappingURL=chunk-6BZKIQTC.js.map
@@ -1 +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"]}
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('@sanity/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.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  getSchemaLoader,
14
14
  offsetToQueryPosition,
15
15
  positionToQueryOffset
16
- } from "./chunk-UOIHOMN2.js";
16
+ } from "./chunk-6BZKIQTC.js";
17
17
  export {
18
18
  SchemaLoader,
19
19
  computeDocumentDiagnostics,
package/dist/server.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  getCompletions,
11
11
  getHoverInfo,
12
12
  offsetToQueryPosition
13
- } from "./chunk-UOIHOMN2.js";
13
+ } from "./chunk-6BZKIQTC.js";
14
14
 
15
15
  // src/server.ts
16
16
  import {
@@ -23,7 +23,7 @@ import {
23
23
  import { TextDocument } from "vscode-languageserver-textdocument";
24
24
  import { URI } from "vscode-uri";
25
25
  import { initLinter } from "@sanity/groq-lint";
26
- import { initWasmFormatter } from "prettier-plugin-groq";
26
+ import { initWasmFormatter } from "@sanity/prettier-plugin-groq";
27
27
  var connection = createConnection(ProposedFeatures.all);
28
28
  var documents = new TextDocuments(TextDocument);
29
29
  var schemaLoader = new SchemaLoader();
@@ -108,10 +108,17 @@ documents.onDidClose((e) => {
108
108
  documentSettings.delete(e.document.uri);
109
109
  documentStates.delete(e.document.uri);
110
110
  });
111
+ function isNodeModulesPath(uri) {
112
+ return uri.includes("/node_modules/") || uri.includes("\\node_modules\\");
113
+ }
111
114
  function validateDocument(document) {
112
115
  const content = document.getText();
113
116
  const languageId = document.languageId;
114
117
  const uri = document.uri;
118
+ if (isNodeModulesPath(uri)) {
119
+ connection.sendDiagnostics({ uri, diagnostics: [] });
120
+ return;
121
+ }
115
122
  const { queries } = extractQueries(content, languageId);
116
123
  documentStates.set(uri, {
117
124
  uri,
@@ -1 +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":";;;;;;;;;;;;;;;AAWA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OAKK;AACP,SAAS,oBAAoB;AAC7B,SAAS,WAAW;AASpB,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAGlC,IAAM,aAAa,iBAAiB,iBAAiB,GAAG;AAGxD,IAAM,YAAY,IAAI,cAAc,YAAY;AAGhD,IAAM,eAAe,IAAI,aAAa;AAGtC,IAAM,iBAAiB,oBAAI,IAA2B;AAGtD,IAAI,6BAA6B;AACjC,IAAI,+BAA+B;AAenC,IAAM,kBAA4B;AAAA,EAChC,gBAAgB;AAAA,EAChB,kBAAkB;AACpB;AAGA,IAAI,iBAA2B;AAG/B,IAAM,mBAAmB,oBAAI,IAAgC;AAK7D,WAAW,aAAa,CAAC,WAA+C;AACtE,QAAM,eAAe,OAAO;AAE5B,+BAA6B,CAAC,EAAE,aAAa,aAAa,CAAC,CAAC,aAAa,UAAU;AACnF,iCAA+B,CAAC,EAC9B,aAAa,aAAa,CAAC,CAAC,aAAa,UAAU;AAIrD,MAAI,OAAO,oBAAoB,OAAO,iBAAiB,SAAS,GAAG;AACjE,UAAM,eAAe,OAAO,iBAAiB,CAAC,GAAG;AACjD,QAAI,cAAc;AAChB,YAAM,gBAAgB,IAAI,MAAM,YAAY,EAAE;AAC9C,mBAAa,eAAe,aAAa;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,SAA2B;AAAA,IAC/B,cAAc;AAAA,MACZ,kBAAkB,qBAAqB;AAAA;AAAA,MAEvC,oBAAoB;AAAA,QAClB,iBAAiB;AAAA,QACjB,mBAAmB,+BAA+B;AAAA,MACpD;AAAA;AAAA,MAEA,eAAe;AAAA;AAAA,MAEf,4BAA4B;AAAA;AAAA,IAE9B;AAAA,EACF;AAEA,MAAI,8BAA8B;AAChC,WAAO,aAAa,YAAY;AAAA,MAC9B,kBAAkB;AAAA,QAChB,WAAW;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT,CAAC;AAKD,WAAW,cAAc,YAAY;AACnC,MAAI,4BAA4B;AAE9B,eAAW,OAAO,SAAS,mCAAmC,MAAM,MAAS;AAAA,EAC/E;AAGA,MAAI;AACF,UAAM,CAAC,YAAY,aAAa,IAAI,MAAM,QAAQ,IAAI,CAAC,WAAW,GAAG,kBAAkB,CAAC,CAAC;AACzF,QAAI,YAAY;AACd,iBAAW,QAAQ,IAAI,yBAAyB;AAAA,IAClD;AACA,QAAI,eAAe;AACjB,iBAAW,QAAQ,IAAI,4BAA4B;AAAA,IACrD;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,eAAa,cAAc,CAAC,WAAW;AACrC,eAAW,QAAQ,IAAI,UAAU,SAAS,aAAa,SAAS,EAAE;AAElE,cAAU,IAAI,EAAE,QAAQ,gBAAgB;AAAA,EAC1C,CAAC;AAED,aAAW,QAAQ,IAAI,kCAAkC;AAC3D,CAAC;AAKD,WAAW,yBAAyB,CAAC,WAAW;AAC9C,MAAI,4BAA4B;AAE9B,qBAAiB,MAAM;AAAA,EACzB;AAGA,mBAAkB,OAAO,UAAU,QAAqB;AAGxD,MAAI,eAAe,YAAY;AAC7B,iBAAa,aAAa,eAAe,UAAU;AAAA,EACrD;AAGA,YAAU,IAAI,EAAE,QAAQ,gBAAgB;AAC1C,CAAC;AAKD,UAAU,mBAAmB,CAAC,WAAW;AACvC,mBAAiB,OAAO,QAAQ;AAClC,CAAC;AAKD,UAAU,WAAW,CAAC,MAAM;AAC1B,mBAAiB,OAAO,EAAE,SAAS,GAAG;AACtC,iBAAe,OAAO,EAAE,SAAS,GAAG;AACtC,CAAC;AAKD,SAAS,iBAAiB,UAA8B;AACtD,QAAM,UAAU,SAAS,QAAQ;AACjC,QAAM,aAAa,SAAS;AAC5B,QAAM,MAAM,SAAS;AAGrB,QAAM,EAAE,QAAQ,IAAI,eAAe,SAAS,UAAU;AAGtD,iBAAe,IAAI,KAAK;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,SAAS;AAAA,EACpB,CAAC;AAGD,MAAI,QAAQ,WAAW,GAAG;AACxB,eAAW,gBAAgB,EAAE,KAAK,aAAa,CAAC,EAAE,CAAC;AACnD;AAAA,EACF;AAGA,QAAM,SAAS,aAAa,UAAU;AACtC,QAAM,cAAc,2BAA2B,SAAS,EAAE,OAAO,CAAC;AAGlE,QAAM,iBAAiB,eAAe,kBAAkB;AACxD,QAAM,qBAAqB,YAAY,MAAM,GAAG,cAAc;AAG9D,aAAW,gBAAgB,EAAE,KAAK,aAAa,mBAAmB,CAAC;AACrE;AAKA,SAAS,oBACP,UACA,UACuB;AACvB,QAAM,QAAQ,eAAe,IAAI,SAAS,GAAG;AAC7C,MAAI,CAAC,MAAO,QAAO;AAGnB,QAAM,SAAS,SAAS,SAAS,QAAQ;AACzC,SAAO,kBAAkB,MAAM,SAAS,MAAM;AAChD;AAKA,WAAW,QAAQ,CAAC,WAAuC;AACzD,QAAM,WAAW,UAAU,IAAI,OAAO,aAAa,GAAG;AACtD,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,QAAQ,oBAAoB,UAAU,OAAO,QAAQ;AAC3D,MAAI,CAAC,MAAO,QAAO;AAGnB,QAAM,iBAAiB,SAAS,SAAS,OAAO,QAAQ;AACxD,QAAM,cAAc,sBAAsB,OAAO,cAAc;AAE/D,QAAM,SAAS,aAAa,UAAU;AACtC,SAAO,aAAa,OAAO,aAAa,EAAE,OAAO,CAAC;AACpD,CAAC;AAKD,WAAW,aAAa,CAAC,WAA6B;AACpD,QAAM,WAAW,UAAU,IAAI,OAAO,aAAa,GAAG;AACtD,MAAI,CAAC,SAAU,QAAO,CAAC;AAEvB,QAAM,QAAQ,oBAAoB,UAAU,OAAO,QAAQ;AAC3D,MAAI,CAAC,OAAO;AAEV,WAAO,CAAC;AAAA,EACV;AAGA,QAAM,iBAAiB,SAAS,SAAS,OAAO,QAAQ;AACxD,QAAM,cAAc,sBAAsB,OAAO,cAAc;AAE/D,QAAM,SAAS,aAAa,UAAU;AACtC,SAAO,eAAe,OAAO,aAAa,EAAE,OAAO,CAAC;AACtD,CAAC;AAKD,WAAW,qBAAqB,OAAO,WAAqC;AAC1E,MAAI,CAAC,eAAe,kBAAkB;AACpC,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,WAAW,UAAU,IAAI,OAAO,aAAa,GAAG;AACtD,MAAI,CAAC,SAAU,QAAO,CAAC;AAEvB,QAAM,UAAU,SAAS,QAAQ;AACjC,QAAM,aAAa,SAAS;AAG5B,MAAI,eAAe,QAAQ;AACzB,WAAO,eAAe,SAAS;AAAA,MAC7B,SAAS,OAAO,QAAQ;AAAA,MACxB,cAAc,OAAO,QAAQ;AAAA,IAC/B,CAAC;AAAA,EACH;AAGA,QAAM,QAAQ,eAAe,IAAI,SAAS,GAAG;AAC7C,MAAI,CAAC,SAAS,MAAM,QAAQ,WAAW,GAAG;AACxC,WAAO,CAAC;AAAA,EACV;AAEA,SAAO,eAAe,MAAM,SAAS,SAAS;AAAA,IAC5C,SAAS,OAAO,QAAQ;AAAA,IACxB,cAAc,OAAO,QAAQ;AAAA,EAC/B,CAAC;AACH,CAAC;AAGD,UAAU,OAAO,UAAU;AAC3B,WAAW,OAAO;","names":[]}
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 '@sanity/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 * Check if a URI points to a file in node_modules\n */\nfunction isNodeModulesPath(uri: string): boolean {\n return uri.includes('/node_modules/') || uri.includes('\\\\node_modules\\\\')\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 // Skip files in node_modules - they're external dependencies\n if (isNodeModulesPath(uri)) {\n connection.sendDiagnostics({ uri, diagnostics: [] })\n return\n }\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/groq-lsp",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Language Server Protocol implementation for GROQ",
5
5
  "author": "Sanity.io <hello@sanity.io>",
6
6
  "license": "MIT",
@@ -30,9 +30,9 @@
30
30
  "vscode-languageserver": "^9.0.1",
31
31
  "vscode-languageserver-textdocument": "^1.0.12",
32
32
  "vscode-uri": "^3.0.8",
33
- "@sanity/groq-lint": "0.0.1",
34
- "@sanity/lint-core": "0.0.1",
35
- "prettier-plugin-groq": "0.0.1"
33
+ "@sanity/groq-lint": "0.0.2",
34
+ "@sanity/lint-core": "0.0.2",
35
+ "@sanity/prettier-plugin-groq": "0.0.2"
36
36
  },
37
37
  "devDependencies": {
38
38
  "tsup": "^8.3.5",