@formspec/language-server 0.1.0-alpha.22 → 0.1.0-alpha.23
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/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/language-server-alpha.d.ts +166 -0
- package/dist/language-server-beta.d.ts +166 -0
- package/dist/language-server-internal.d.ts +166 -0
- package/dist/plugin-client.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -143,9 +143,10 @@ var import_node_net = __toESM(require("net"), 1);
|
|
|
143
143
|
var import_node_path = __toESM(require("path"), 1);
|
|
144
144
|
var import_node_url = require("url");
|
|
145
145
|
var import_protocol = require("@formspec/analysis/protocol");
|
|
146
|
+
var import_internal3 = require("@formspec/analysis/internal");
|
|
146
147
|
var DEFAULT_PLUGIN_QUERY_TIMEOUT_MS = 2e3;
|
|
147
148
|
function getManifestPath(workspaceRoot) {
|
|
148
|
-
return (0,
|
|
149
|
+
return (0, import_internal3.getFormSpecManifestPath)(workspaceRoot);
|
|
149
150
|
}
|
|
150
151
|
function normalizeWorkspaceRoot(root) {
|
|
151
152
|
const resolved = import_node_path.default.resolve(root);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/providers/completion.ts","../src/providers/hover.ts","../src/providers/definition.ts","../src/diagnostics.ts","../src/plugin-client.ts"],"sourcesContent":["/**\n * \\@formspec/language-server\n *\n * Language server for FormSpec — provides completions, hover documentation,\n * and go-to-definition for FormSpec JSDoc constraint tags (`@Minimum`,\n * `@Maximum`, `@Pattern`, etc.) in TypeScript files.\n *\n * This package implements the Language Server Protocol (LSP) using the\n * `vscode-languageserver` library. Cheap syntax-local behaviors stay in the\n * LSP process, while TypeScript-project-aware semantics are supplied by\n * `@formspec/ts-plugin` over a local manifest + IPC transport.\n *\n * The packaged server acts as a reference implementation over the composable\n * completion, hover, and diagnostics helpers exported from this package.\n *\n * @example\n * ```ts\n * import { createServer } from '@formspec/language-server';\n *\n * const connection = createServer();\n * connection.listen();\n * ```\n *\n * @packageDocumentation\n */\n\nexport { createServer } from \"./server.js\";\nexport type { CreateServerOptions } from \"./server.js\";\nexport type {\n CommentSpan,\n FormSpecAnalysisDiagnostic,\n FormSpecAnalysisDiagnosticCategory,\n FormSpecAnalysisDiagnosticDataValue,\n FormSpecAnalysisDiagnosticLocation,\n} from \"@formspec/analysis/protocol\";\nexport {\n getPluginDiagnosticsForDocument,\n toLspDiagnostics,\n type ToLspDiagnosticsOptions,\n} from \"./diagnostics.js\";\nexport { getCompletionItems } from \"./providers/completion.js\";\nexport { getHoverForTag } from \"./providers/hover.js\";\nexport { getDefinition } from \"./providers/definition.js\";\n","/**\n * FormSpec Language Server\n *\n * Sets up an LSP server connection and registers handlers for:\n * - `textDocument/completion` — FormSpec JSDoc constraint tag completions\n * - `textDocument/hover` — Documentation for recognized constraint tags\n * - `textDocument/definition` — Go-to-definition (stub, returns null)\n *\n * The packaged language server is a reference implementation built on the same\n * composable helpers that downstream consumers can call directly.\n */\n\nimport {\n createConnection,\n Diagnostic,\n ProposedFeatures,\n TextDocuments,\n TextDocumentSyncKind,\n type Connection,\n type InitializeResult,\n} from \"vscode-languageserver/node.js\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport { TextDocument } from \"vscode-languageserver-textdocument\";\nimport { getCompletionItemsAtOffset } from \"./providers/completion.js\";\nimport { getHoverAtOffset } from \"./providers/hover.js\";\nimport { getDefinition } from \"./providers/definition.js\";\nimport { getPluginDiagnosticsForDocument, toLspDiagnostics } from \"./diagnostics.js\";\nimport {\n fileUriToPathOrNull,\n getPluginCompletionContextForDocument,\n getPluginHoverForDocument,\n} from \"./plugin-client.js\";\n\nconst PLUGIN_QUERY_TIMEOUT_ENV_VAR = \"FORMSPEC_PLUGIN_QUERY_TIMEOUT_MS\";\n\nfunction dedupeWorkspaceRoots(workspaceRoots: readonly string[]): string[] {\n return [...new Set(workspaceRoots)];\n}\n\nfunction resolvePluginQueryTimeoutMs(explicitTimeoutMs: number | undefined): number | undefined {\n if (explicitTimeoutMs !== undefined) {\n return explicitTimeoutMs;\n }\n\n const rawValue = process.env[PLUGIN_QUERY_TIMEOUT_ENV_VAR];\n if (rawValue === undefined) {\n return undefined;\n }\n\n const parsed = Number.parseInt(rawValue, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction getWorkspaceRootsFromInitializeParams(params: {\n readonly workspaceFolders?: readonly { readonly uri: string }[] | null;\n readonly rootUri?: string | null;\n readonly rootPath?: string | null;\n}): string[] {\n const workspaceFolders =\n params.workspaceFolders\n ?.map((workspaceFolder) => fileUriToPathOrNull(workspaceFolder.uri))\n .filter((workspaceRoot): workspaceRoot is string => workspaceRoot !== null) ?? [];\n const rootUri =\n params.rootUri === null || params.rootUri === undefined\n ? null\n : fileUriToPathOrNull(params.rootUri);\n const rootPath = params.rootPath ?? null;\n\n return dedupeWorkspaceRoots([\n ...workspaceFolders,\n ...(rootUri === null ? [] : [rootUri]),\n ...(rootPath === null ? [] : [rootPath]),\n ]);\n}\n\n/**\n * Public configuration for constructing the FormSpec language server.\n *\n * @public\n */\nexport interface CreateServerOptions {\n /** Optional extension definitions whose custom tags should be surfaced by tooling. */\n readonly extensions?: readonly ExtensionDefinition[];\n /** Optional workspace roots to use before initialize() provides them. */\n readonly workspaceRoots?: readonly string[];\n /** Set to false to disable tsserver-plugin semantic enrichment. */\n readonly usePluginTransport?: boolean;\n /** IPC timeout, in milliseconds, for semantic plugin requests. */\n readonly pluginQueryTimeoutMs?: number;\n /** Optional diagnostics publishing mode for the packaged reference LSP. */\n readonly diagnosticsMode?: \"off\" | \"plugin\";\n /** Source label to use when publishing plugin-derived diagnostics. */\n readonly diagnosticSource?: string;\n}\n\n/**\n * Creates and configures the FormSpec language server connection.\n *\n * Registers LSP capability handlers and returns the connection.\n * Call `connection.listen()` to start accepting messages.\n *\n * @returns The configured LSP connection (not yet listening)\n * @public\n */\nexport function createServer(options: CreateServerOptions = {}): Connection {\n const connection = createConnection(ProposedFeatures.all);\n const documents = new TextDocuments(TextDocument);\n let workspaceRoots = [...(options.workspaceRoots ?? [])];\n const pluginQueryTimeoutMs = resolvePluginQueryTimeoutMs(options.pluginQueryTimeoutMs);\n const diagnosticsMode = options.diagnosticsMode ?? \"off\";\n const diagnosticSource = options.diagnosticSource ?? \"formspec\";\n\n documents.listen(connection);\n\n async function publishDiagnosticsForDocument(document: TextDocument): Promise<void> {\n if (diagnosticsMode !== \"plugin\" || options.usePluginTransport === false) {\n return;\n }\n\n const filePath = fileUriToPathOrNull(document.uri);\n if (filePath === null) {\n return;\n }\n\n const diagnostics =\n (await getPluginDiagnosticsForDocument(\n workspaceRoots,\n filePath,\n document.getText(),\n pluginQueryTimeoutMs\n )) ?? [];\n\n void connection.sendDiagnostics({\n uri: document.uri,\n diagnostics: toLspDiagnostics(document, diagnostics, {\n source: diagnosticSource,\n }),\n });\n }\n\n connection.onInitialize((params): InitializeResult => {\n workspaceRoots = dedupeWorkspaceRoots([\n ...getWorkspaceRootsFromInitializeParams(params),\n ...workspaceRoots,\n ]);\n\n return {\n capabilities: {\n textDocumentSync: TextDocumentSyncKind.Incremental,\n completionProvider: {\n // Trigger completions inside JSDoc comments for tags and target specifiers\n triggerCharacters: [\"@\", \":\"],\n },\n hoverProvider: true,\n definitionProvider: true,\n },\n serverInfo: {\n name: \"formspec-language-server\",\n version: \"0.1.0\",\n },\n };\n });\n\n connection.onCompletion(async (params) => {\n const document = documents.get(params.textDocument.uri);\n if (!document) {\n return [];\n }\n\n const offset = document.offsetAt(params.position);\n const documentText = document.getText();\n const filePath = fileUriToPathOrNull(params.textDocument.uri);\n const semanticContext =\n options.usePluginTransport === false || filePath === null\n ? null\n : await getPluginCompletionContextForDocument(\n workspaceRoots,\n filePath,\n documentText,\n offset,\n pluginQueryTimeoutMs\n );\n\n return getCompletionItemsAtOffset(documentText, offset, options.extensions, semanticContext);\n });\n\n connection.onHover(async (params) => {\n const document = documents.get(params.textDocument.uri);\n if (!document) {\n return null;\n }\n\n const offset = document.offsetAt(params.position);\n const documentText = document.getText();\n const filePath = fileUriToPathOrNull(params.textDocument.uri);\n const semanticHover =\n options.usePluginTransport === false || filePath === null\n ? null\n : await getPluginHoverForDocument(\n workspaceRoots,\n filePath,\n documentText,\n offset,\n pluginQueryTimeoutMs\n );\n\n return getHoverAtOffset(documentText, offset, options.extensions, semanticHover);\n });\n\n connection.onDefinition((_params) => {\n // Go-to-definition is not yet implemented.\n return getDefinition();\n });\n\n documents.onDidOpen(({ document }) => {\n void publishDiagnosticsForDocument(document).catch((error: unknown) => {\n connection.console.error(`[FormSpec] Failed to publish diagnostics: ${String(error)}`);\n });\n });\n\n documents.onDidChangeContent(({ document }) => {\n void publishDiagnosticsForDocument(document).catch((error: unknown) => {\n connection.console.error(`[FormSpec] Failed to publish diagnostics: ${String(error)}`);\n });\n });\n\n documents.onDidClose(({ document }) => {\n if (diagnosticsMode === \"plugin\") {\n void connection.sendDiagnostics({\n uri: document.uri,\n diagnostics: [] satisfies Diagnostic[],\n });\n }\n });\n\n return connection;\n}\n","/**\n * Completion provider for FormSpec JSDoc constraint tags.\n *\n * Uses the shared tag registry from `@formspec/analysis` so completions stay\n * aligned with the same metadata that powers linting and build-time analysis.\n */\n\nimport {\n type FormSpecSerializedCompletionContext,\n type FormSpecSerializedTagDefinition,\n getConstraintTagDefinitions,\n getSemanticCommentCompletionContextAtOffset,\n type TagDefinition,\n} from \"@formspec/analysis/internal\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport { CompletionItem, CompletionItemKind } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns the full set of tag-name completions currently known to FormSpec.\n *\n * @public\n */\nexport function getCompletionItems(extensions?: readonly ExtensionDefinition[]): CompletionItem[] {\n return getConstraintTagDefinitions(extensions).map((tag) => ({\n label: `@${tag.canonicalName}`,\n kind: CompletionItemKind.Keyword,\n detail: tag.completionDetail,\n }));\n}\n\nfunction toCompletionItem(tag: TagDefinition | FormSpecSerializedTagDefinition): CompletionItem {\n return {\n label: `@${tag.canonicalName}`,\n kind: CompletionItemKind.Keyword,\n detail: tag.completionDetail,\n };\n}\n\nfunction toTargetCompletionItems(\n tagName: string,\n targetCompletions: readonly string[]\n): CompletionItem[] {\n return targetCompletions.map((target: string) => ({\n label: target,\n kind:\n target === \"singular\" || target === \"plural\"\n ? CompletionItemKind.EnumMember\n : CompletionItemKind.Field,\n detail: `Target for @${tagName}`,\n }));\n}\n\nfunction filterTagNameCompletionItems(\n prefix: string,\n availableTags: readonly (TagDefinition | FormSpecSerializedTagDefinition)[]\n): CompletionItem[] {\n const normalizedPrefix = prefix.toLowerCase();\n return availableTags\n .map(toCompletionItem)\n .filter((item) => item.label.slice(1).toLowerCase().startsWith(normalizedPrefix));\n}\n\n/** @internal */\nexport function getCompletionItemsAtOffset(\n documentText: string,\n offset: number,\n extensions?: readonly ExtensionDefinition[],\n semanticContext?: FormSpecSerializedCompletionContext | null\n): CompletionItem[] {\n if (semanticContext !== null && semanticContext !== undefined) {\n if (semanticContext.kind === \"target\") {\n return toTargetCompletionItems(\n semanticContext.semantic.tagName,\n semanticContext.semantic.targetCompletions\n );\n }\n\n if (semanticContext.kind !== \"tag-name\") {\n return [];\n }\n\n return filterTagNameCompletionItems(semanticContext.prefix, semanticContext.availableTags);\n }\n\n const resolvedContext = getSemanticCommentCompletionContextAtOffset(\n documentText,\n offset,\n extensions ? { extensions } : undefined\n );\n\n if (resolvedContext.kind === \"target\") {\n return toTargetCompletionItems(\n resolvedContext.semantic.tag.normalizedTagName,\n resolvedContext.semantic.targetCompletions\n );\n }\n\n if (resolvedContext.kind !== \"tag-name\") {\n return [];\n }\n\n return filterTagNameCompletionItems(resolvedContext.prefix, resolvedContext.availableTags);\n}\n","/**\n * Hover provider for FormSpec JSDoc tags.\n *\n * Uses the shared registry from `@formspec/analysis` so hover content stays in\n * sync with the tag inventory and overload metadata.\n */\n\nimport {\n type FormSpecSerializedHoverInfo,\n getCommentHoverInfoAtOffset,\n getTagDefinition,\n normalizeFormSpecTagName,\n} from \"@formspec/analysis/internal\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport type { Hover } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns hover content for a single FormSpec tag name.\n *\n * @public\n */\nexport function getHoverForTag(\n tagName: string,\n extensions?: readonly ExtensionDefinition[]\n): Hover | null {\n const raw = tagName.startsWith(\"@\") ? tagName.slice(1) : tagName;\n const definition = getTagDefinition(normalizeFormSpecTagName(raw), extensions);\n if (!definition) {\n return null;\n }\n\n return {\n contents: {\n kind: \"markdown\",\n value: definition.hoverMarkdown,\n },\n };\n}\n\n/** @internal */\nexport function getHoverAtOffset(\n documentText: string,\n offset: number,\n extensions?: readonly ExtensionDefinition[],\n semanticHover?: FormSpecSerializedHoverInfo | null\n): Hover | null {\n const hoverInfo =\n semanticHover ??\n getCommentHoverInfoAtOffset(documentText, offset, extensions ? { extensions } : undefined);\n if (hoverInfo === null) {\n return null;\n }\n\n return {\n contents: {\n kind: \"markdown\",\n value: hoverInfo.markdown,\n },\n };\n}\n","/**\n * Go-to-definition provider for FormSpec.\n *\n * This is a stub — go-to-definition support (e.g., navigating from a\n * `field.text(\"name\")` call to the form definition that references it) will\n * be implemented in a future phase.\n */\n\nimport type { Location } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns the definition location for a symbol at the given position.\n *\n * Always returns `null` in this stub implementation.\n *\n * @returns `null` — not yet implemented\n * @public\n */\nexport function getDefinition(): Location | null {\n return null;\n}\n","import type {\n FormSpecAnalysisDiagnostic,\n FormSpecAnalysisDiagnosticLocation,\n} from \"@formspec/analysis/protocol\";\nimport {\n DiagnosticRelatedInformation,\n DiagnosticSeverity,\n Location,\n Range,\n type Diagnostic,\n} from \"vscode-languageserver/node.js\";\nimport type { TextDocument } from \"vscode-languageserver-textdocument\";\nimport { fileUriToPathOrNull } from \"./plugin-client.js\";\nexport { getPluginDiagnosticsForDocument } from \"./plugin-client.js\";\n\n/**\n * Options for converting canonical FormSpec diagnostics into LSP diagnostics.\n *\n * @public\n */\nexport interface ToLspDiagnosticsOptions {\n /** Source label shown by LSP clients. Defaults to `formspec`. */\n readonly source?: string;\n}\n\n/**\n * Converts canonical FormSpec diagnostics into LSP diagnostics.\n *\n * Downstream consumers that want complete white-label control can ignore this\n * helper and render their own messages from `code` + `data`.\n *\n * @public\n */\nexport function toLspDiagnostics(\n document: TextDocument,\n diagnostics: readonly FormSpecAnalysisDiagnostic[],\n options: ToLspDiagnosticsOptions = {}\n): Diagnostic[] {\n const source = options.source ?? \"formspec\";\n return diagnostics.map((diagnostic) => {\n const relatedInformation = toRelatedInformation(document, diagnostic.relatedLocations);\n return {\n range: spanToRange(document, diagnostic.range.start, diagnostic.range.end),\n severity: toLspSeverity(diagnostic.severity),\n source,\n code: diagnostic.code,\n message: diagnostic.message,\n ...(relatedInformation === undefined ? {} : { relatedInformation }),\n data: {\n ...diagnostic.data,\n category: diagnostic.category,\n },\n };\n });\n}\n\nfunction spanToRange(document: TextDocument, start: number, end: number): Range {\n return Range.create(document.positionAt(start), document.positionAt(end));\n}\n\nfunction toLspSeverity(severity: FormSpecAnalysisDiagnostic[\"severity\"]): DiagnosticSeverity {\n switch (severity) {\n case \"error\":\n return DiagnosticSeverity.Error;\n case \"warning\":\n return DiagnosticSeverity.Warning;\n case \"info\":\n return DiagnosticSeverity.Information;\n default:\n return DiagnosticSeverity.Information;\n }\n}\n\nfunction toRelatedInformation(\n document: TextDocument,\n locations: readonly FormSpecAnalysisDiagnosticLocation[]\n): DiagnosticRelatedInformation[] | undefined {\n if (locations.length === 0) {\n return undefined;\n }\n\n const currentDocumentFilePath = getDocumentFilePath(document);\n const relatedInformation = locations\n .filter((location) => location.filePath === currentDocumentFilePath)\n .map((location) =>\n DiagnosticRelatedInformation.create(\n Location.create(\n document.uri,\n spanToRange(document, location.range.start, location.range.end)\n ),\n location.message ?? \"Related FormSpec location\"\n )\n );\n\n return relatedInformation.length === 0 ? undefined : relatedInformation;\n}\n\nfunction getDocumentFilePath(document: TextDocument): string | null {\n return fileUriToPathOrNull(document.uri);\n}\n","import fs from \"node:fs/promises\";\nimport net from \"node:net\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n computeFormSpecTextHash,\n getFormSpecManifestPath,\n isFormSpecAnalysisManifest,\n isFormSpecSemanticResponse,\n type FormSpecAnalysisDiagnostic,\n type FormSpecAnalysisManifest,\n type FormSpecSerializedCompletionContext,\n type FormSpecSerializedHoverInfo,\n type FormSpecSemanticQuery,\n type FormSpecSemanticResponse,\n} from \"@formspec/analysis/protocol\";\n\nconst DEFAULT_PLUGIN_QUERY_TIMEOUT_MS = 2_000;\n\nfunction getManifestPath(workspaceRoot: string): string {\n return getFormSpecManifestPath(workspaceRoot);\n}\n\nfunction normalizeWorkspaceRoot(root: string): string {\n const resolved = path.resolve(root);\n const parsed = path.parse(resolved);\n let normalized = resolved;\n\n while (normalized.length > parsed.root.length && normalized.endsWith(path.sep)) {\n normalized = normalized.slice(0, -path.sep.length);\n }\n\n return normalized;\n}\n\nfunction getMatchingWorkspaceRoot(\n workspaceRoots: readonly string[],\n filePath: string\n): string | null {\n const normalizedFilePath = path.resolve(filePath);\n const normalizedRoots = [...workspaceRoots]\n .map(normalizeWorkspaceRoot)\n .sort((left, right) => right.length - left.length);\n return (\n normalizedRoots.find(\n (workspaceRoot) =>\n normalizedFilePath === workspaceRoot ||\n normalizedFilePath.startsWith(`${workspaceRoot}${path.sep}`)\n ) ?? null\n );\n}\n\nasync function readManifest(workspaceRoot: string): Promise<FormSpecAnalysisManifest | null> {\n try {\n const manifestText = await fs.readFile(getManifestPath(workspaceRoot), \"utf8\");\n const manifest = JSON.parse(manifestText) as unknown;\n if (!isFormSpecAnalysisManifest(manifest)) {\n return null;\n }\n\n return manifest;\n } catch {\n return null;\n }\n}\n\nasync function sendSemanticQuery(\n manifest: FormSpecAnalysisManifest,\n query: FormSpecSemanticQuery,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSemanticResponse | null> {\n return new Promise((resolve) => {\n const socket = net.createConnection(manifest.endpoint.address);\n let buffer = \"\";\n let settled = false;\n\n const finish = (response: FormSpecSemanticResponse | null): void => {\n if (settled) {\n return;\n }\n settled = true;\n socket.removeAllListeners(\"data\");\n socket.destroy();\n resolve(response);\n };\n\n socket.setTimeout(timeoutMs, () => {\n finish(null);\n });\n\n socket.setEncoding(\"utf8\");\n socket.on(\"connect\", () => {\n socket.write(`${JSON.stringify(query)}\\n`);\n });\n socket.on(\"data\", (chunk) => {\n buffer += String(chunk);\n const newlineIndex = buffer.indexOf(\"\\n\");\n if (newlineIndex < 0) {\n return;\n }\n\n const payload = buffer.slice(0, newlineIndex);\n buffer = buffer.slice(newlineIndex + 1);\n try {\n const response = JSON.parse(payload) as unknown;\n finish(isFormSpecSemanticResponse(response) ? response : null);\n } catch {\n finish(null);\n }\n });\n socket.on(\"error\", () => {\n finish(null);\n });\n socket.on(\"close\", () => {\n finish(null);\n });\n });\n}\n\nexport function fileUriToPathOrNull(uri: string): string | null {\n try {\n return fileURLToPath(uri);\n } catch {\n return null;\n }\n}\n\nasync function sendFileQuery(\n workspaceRoots: readonly string[],\n filePath: string,\n query: FormSpecSemanticQuery,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSemanticResponse | null> {\n const workspaceRoot = getMatchingWorkspaceRoot(workspaceRoots, filePath);\n if (workspaceRoot === null) {\n return null;\n }\n\n const manifest = await readManifest(workspaceRoot);\n if (manifest === null) {\n return null;\n }\n\n return sendSemanticQuery(manifest, query, timeoutMs);\n}\n\nexport async function getPluginCompletionContextForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n offset: number,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSerializedCompletionContext | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"completion\",\n filePath,\n offset,\n },\n timeoutMs\n );\n if (response?.kind !== \"completion\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText) ? response.context : null;\n}\n\nexport async function getPluginHoverForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n offset: number,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSerializedHoverInfo | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"hover\",\n filePath,\n offset,\n },\n timeoutMs\n );\n if (response?.kind !== \"hover\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText) ? response.hover : null;\n}\n\n/**\n * Retrieves canonical FormSpec diagnostics for the current document revision\n * from the plugin transport. Returns `null` when the transport is missing,\n * stale, or invalid.\n *\n * @public\n */\nexport async function getPluginDiagnosticsForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<readonly FormSpecAnalysisDiagnostic[] | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"diagnostics\",\n filePath,\n },\n timeoutMs\n );\n if (response?.kind !== \"diagnostics\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText)\n ? response.diagnostics\n : null;\n}\n"],"mappings":"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","names":["import_node","import_internal","import_node","path","fs","net"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/providers/completion.ts","../src/providers/hover.ts","../src/providers/definition.ts","../src/diagnostics.ts","../src/plugin-client.ts"],"sourcesContent":["/**\n * \\@formspec/language-server\n *\n * Language server for FormSpec — provides completions, hover documentation,\n * and go-to-definition for FormSpec JSDoc constraint tags (`@Minimum`,\n * `@Maximum`, `@Pattern`, etc.) in TypeScript files.\n *\n * This package implements the Language Server Protocol (LSP) using the\n * `vscode-languageserver` library. Cheap syntax-local behaviors stay in the\n * LSP process, while TypeScript-project-aware semantics are supplied by\n * `@formspec/ts-plugin` over a local manifest + IPC transport.\n *\n * The packaged server acts as a reference implementation over the composable\n * completion, hover, and diagnostics helpers exported from this package.\n *\n * @example\n * ```ts\n * import { createServer } from '@formspec/language-server';\n *\n * const connection = createServer();\n * connection.listen();\n * ```\n *\n * @packageDocumentation\n */\n\nexport { createServer } from \"./server.js\";\nexport type { CreateServerOptions } from \"./server.js\";\nexport type {\n CommentSpan,\n FormSpecAnalysisDiagnostic,\n FormSpecAnalysisDiagnosticCategory,\n FormSpecAnalysisDiagnosticDataValue,\n FormSpecAnalysisDiagnosticLocation,\n} from \"@formspec/analysis/protocol\";\nexport {\n getPluginDiagnosticsForDocument,\n toLspDiagnostics,\n type ToLspDiagnosticsOptions,\n} from \"./diagnostics.js\";\nexport { getCompletionItems } from \"./providers/completion.js\";\nexport { getHoverForTag } from \"./providers/hover.js\";\nexport { getDefinition } from \"./providers/definition.js\";\n","/**\n * FormSpec Language Server\n *\n * Sets up an LSP server connection and registers handlers for:\n * - `textDocument/completion` — FormSpec JSDoc constraint tag completions\n * - `textDocument/hover` — Documentation for recognized constraint tags\n * - `textDocument/definition` — Go-to-definition (stub, returns null)\n *\n * The packaged language server is a reference implementation built on the same\n * composable helpers that downstream consumers can call directly.\n */\n\nimport {\n createConnection,\n Diagnostic,\n ProposedFeatures,\n TextDocuments,\n TextDocumentSyncKind,\n type Connection,\n type InitializeResult,\n} from \"vscode-languageserver/node.js\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport { TextDocument } from \"vscode-languageserver-textdocument\";\nimport { getCompletionItemsAtOffset } from \"./providers/completion.js\";\nimport { getHoverAtOffset } from \"./providers/hover.js\";\nimport { getDefinition } from \"./providers/definition.js\";\nimport { getPluginDiagnosticsForDocument, toLspDiagnostics } from \"./diagnostics.js\";\nimport {\n fileUriToPathOrNull,\n getPluginCompletionContextForDocument,\n getPluginHoverForDocument,\n} from \"./plugin-client.js\";\n\nconst PLUGIN_QUERY_TIMEOUT_ENV_VAR = \"FORMSPEC_PLUGIN_QUERY_TIMEOUT_MS\";\n\nfunction dedupeWorkspaceRoots(workspaceRoots: readonly string[]): string[] {\n return [...new Set(workspaceRoots)];\n}\n\nfunction resolvePluginQueryTimeoutMs(explicitTimeoutMs: number | undefined): number | undefined {\n if (explicitTimeoutMs !== undefined) {\n return explicitTimeoutMs;\n }\n\n const rawValue = process.env[PLUGIN_QUERY_TIMEOUT_ENV_VAR];\n if (rawValue === undefined) {\n return undefined;\n }\n\n const parsed = Number.parseInt(rawValue, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction getWorkspaceRootsFromInitializeParams(params: {\n readonly workspaceFolders?: readonly { readonly uri: string }[] | null;\n readonly rootUri?: string | null;\n readonly rootPath?: string | null;\n}): string[] {\n const workspaceFolders =\n params.workspaceFolders\n ?.map((workspaceFolder) => fileUriToPathOrNull(workspaceFolder.uri))\n .filter((workspaceRoot): workspaceRoot is string => workspaceRoot !== null) ?? [];\n const rootUri =\n params.rootUri === null || params.rootUri === undefined\n ? null\n : fileUriToPathOrNull(params.rootUri);\n const rootPath = params.rootPath ?? null;\n\n return dedupeWorkspaceRoots([\n ...workspaceFolders,\n ...(rootUri === null ? [] : [rootUri]),\n ...(rootPath === null ? [] : [rootPath]),\n ]);\n}\n\n/**\n * Public configuration for constructing the FormSpec language server.\n *\n * @public\n */\nexport interface CreateServerOptions {\n /** Optional extension definitions whose custom tags should be surfaced by tooling. */\n readonly extensions?: readonly ExtensionDefinition[];\n /** Optional workspace roots to use before initialize() provides them. */\n readonly workspaceRoots?: readonly string[];\n /** Set to false to disable tsserver-plugin semantic enrichment. */\n readonly usePluginTransport?: boolean;\n /** IPC timeout, in milliseconds, for semantic plugin requests. */\n readonly pluginQueryTimeoutMs?: number;\n /** Optional diagnostics publishing mode for the packaged reference LSP. */\n readonly diagnosticsMode?: \"off\" | \"plugin\";\n /** Source label to use when publishing plugin-derived diagnostics. */\n readonly diagnosticSource?: string;\n}\n\n/**\n * Creates and configures the FormSpec language server connection.\n *\n * Registers LSP capability handlers and returns the connection.\n * Call `connection.listen()` to start accepting messages.\n *\n * @returns The configured LSP connection (not yet listening)\n * @public\n */\nexport function createServer(options: CreateServerOptions = {}): Connection {\n const connection = createConnection(ProposedFeatures.all);\n const documents = new TextDocuments(TextDocument);\n let workspaceRoots = [...(options.workspaceRoots ?? [])];\n const pluginQueryTimeoutMs = resolvePluginQueryTimeoutMs(options.pluginQueryTimeoutMs);\n const diagnosticsMode = options.diagnosticsMode ?? \"off\";\n const diagnosticSource = options.diagnosticSource ?? \"formspec\";\n\n documents.listen(connection);\n\n async function publishDiagnosticsForDocument(document: TextDocument): Promise<void> {\n if (diagnosticsMode !== \"plugin\" || options.usePluginTransport === false) {\n return;\n }\n\n const filePath = fileUriToPathOrNull(document.uri);\n if (filePath === null) {\n return;\n }\n\n const diagnostics =\n (await getPluginDiagnosticsForDocument(\n workspaceRoots,\n filePath,\n document.getText(),\n pluginQueryTimeoutMs\n )) ?? [];\n\n void connection.sendDiagnostics({\n uri: document.uri,\n diagnostics: toLspDiagnostics(document, diagnostics, {\n source: diagnosticSource,\n }),\n });\n }\n\n connection.onInitialize((params): InitializeResult => {\n workspaceRoots = dedupeWorkspaceRoots([\n ...getWorkspaceRootsFromInitializeParams(params),\n ...workspaceRoots,\n ]);\n\n return {\n capabilities: {\n textDocumentSync: TextDocumentSyncKind.Incremental,\n completionProvider: {\n // Trigger completions inside JSDoc comments for tags and target specifiers\n triggerCharacters: [\"@\", \":\"],\n },\n hoverProvider: true,\n definitionProvider: true,\n },\n serverInfo: {\n name: \"formspec-language-server\",\n version: \"0.1.0\",\n },\n };\n });\n\n connection.onCompletion(async (params) => {\n const document = documents.get(params.textDocument.uri);\n if (!document) {\n return [];\n }\n\n const offset = document.offsetAt(params.position);\n const documentText = document.getText();\n const filePath = fileUriToPathOrNull(params.textDocument.uri);\n const semanticContext =\n options.usePluginTransport === false || filePath === null\n ? null\n : await getPluginCompletionContextForDocument(\n workspaceRoots,\n filePath,\n documentText,\n offset,\n pluginQueryTimeoutMs\n );\n\n return getCompletionItemsAtOffset(documentText, offset, options.extensions, semanticContext);\n });\n\n connection.onHover(async (params) => {\n const document = documents.get(params.textDocument.uri);\n if (!document) {\n return null;\n }\n\n const offset = document.offsetAt(params.position);\n const documentText = document.getText();\n const filePath = fileUriToPathOrNull(params.textDocument.uri);\n const semanticHover =\n options.usePluginTransport === false || filePath === null\n ? null\n : await getPluginHoverForDocument(\n workspaceRoots,\n filePath,\n documentText,\n offset,\n pluginQueryTimeoutMs\n );\n\n return getHoverAtOffset(documentText, offset, options.extensions, semanticHover);\n });\n\n connection.onDefinition((_params) => {\n // Go-to-definition is not yet implemented.\n return getDefinition();\n });\n\n documents.onDidOpen(({ document }) => {\n void publishDiagnosticsForDocument(document).catch((error: unknown) => {\n connection.console.error(`[FormSpec] Failed to publish diagnostics: ${String(error)}`);\n });\n });\n\n documents.onDidChangeContent(({ document }) => {\n void publishDiagnosticsForDocument(document).catch((error: unknown) => {\n connection.console.error(`[FormSpec] Failed to publish diagnostics: ${String(error)}`);\n });\n });\n\n documents.onDidClose(({ document }) => {\n if (diagnosticsMode === \"plugin\") {\n void connection.sendDiagnostics({\n uri: document.uri,\n diagnostics: [] satisfies Diagnostic[],\n });\n }\n });\n\n return connection;\n}\n","/**\n * Completion provider for FormSpec JSDoc constraint tags.\n *\n * Uses the shared tag registry from `@formspec/analysis` so completions stay\n * aligned with the same metadata that powers linting and build-time analysis.\n */\n\nimport {\n type FormSpecSerializedCompletionContext,\n type FormSpecSerializedTagDefinition,\n getConstraintTagDefinitions,\n getSemanticCommentCompletionContextAtOffset,\n type TagDefinition,\n} from \"@formspec/analysis/internal\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport { CompletionItem, CompletionItemKind } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns the full set of tag-name completions currently known to FormSpec.\n *\n * @public\n */\nexport function getCompletionItems(extensions?: readonly ExtensionDefinition[]): CompletionItem[] {\n return getConstraintTagDefinitions(extensions).map((tag) => ({\n label: `@${tag.canonicalName}`,\n kind: CompletionItemKind.Keyword,\n detail: tag.completionDetail,\n }));\n}\n\nfunction toCompletionItem(tag: TagDefinition | FormSpecSerializedTagDefinition): CompletionItem {\n return {\n label: `@${tag.canonicalName}`,\n kind: CompletionItemKind.Keyword,\n detail: tag.completionDetail,\n };\n}\n\nfunction toTargetCompletionItems(\n tagName: string,\n targetCompletions: readonly string[]\n): CompletionItem[] {\n return targetCompletions.map((target: string) => ({\n label: target,\n kind:\n target === \"singular\" || target === \"plural\"\n ? CompletionItemKind.EnumMember\n : CompletionItemKind.Field,\n detail: `Target for @${tagName}`,\n }));\n}\n\nfunction filterTagNameCompletionItems(\n prefix: string,\n availableTags: readonly (TagDefinition | FormSpecSerializedTagDefinition)[]\n): CompletionItem[] {\n const normalizedPrefix = prefix.toLowerCase();\n return availableTags\n .map(toCompletionItem)\n .filter((item) => item.label.slice(1).toLowerCase().startsWith(normalizedPrefix));\n}\n\n/** @internal */\nexport function getCompletionItemsAtOffset(\n documentText: string,\n offset: number,\n extensions?: readonly ExtensionDefinition[],\n semanticContext?: FormSpecSerializedCompletionContext | null\n): CompletionItem[] {\n if (semanticContext !== null && semanticContext !== undefined) {\n if (semanticContext.kind === \"target\") {\n return toTargetCompletionItems(\n semanticContext.semantic.tagName,\n semanticContext.semantic.targetCompletions\n );\n }\n\n if (semanticContext.kind !== \"tag-name\") {\n return [];\n }\n\n return filterTagNameCompletionItems(semanticContext.prefix, semanticContext.availableTags);\n }\n\n const resolvedContext = getSemanticCommentCompletionContextAtOffset(\n documentText,\n offset,\n extensions ? { extensions } : undefined\n );\n\n if (resolvedContext.kind === \"target\") {\n return toTargetCompletionItems(\n resolvedContext.semantic.tag.normalizedTagName,\n resolvedContext.semantic.targetCompletions\n );\n }\n\n if (resolvedContext.kind !== \"tag-name\") {\n return [];\n }\n\n return filterTagNameCompletionItems(resolvedContext.prefix, resolvedContext.availableTags);\n}\n","/**\n * Hover provider for FormSpec JSDoc tags.\n *\n * Uses the shared registry from `@formspec/analysis` so hover content stays in\n * sync with the tag inventory and overload metadata.\n */\n\nimport {\n type FormSpecSerializedHoverInfo,\n getCommentHoverInfoAtOffset,\n getTagDefinition,\n normalizeFormSpecTagName,\n} from \"@formspec/analysis/internal\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport type { Hover } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns hover content for a single FormSpec tag name.\n *\n * @public\n */\nexport function getHoverForTag(\n tagName: string,\n extensions?: readonly ExtensionDefinition[]\n): Hover | null {\n const raw = tagName.startsWith(\"@\") ? tagName.slice(1) : tagName;\n const definition = getTagDefinition(normalizeFormSpecTagName(raw), extensions);\n if (!definition) {\n return null;\n }\n\n return {\n contents: {\n kind: \"markdown\",\n value: definition.hoverMarkdown,\n },\n };\n}\n\n/** @internal */\nexport function getHoverAtOffset(\n documentText: string,\n offset: number,\n extensions?: readonly ExtensionDefinition[],\n semanticHover?: FormSpecSerializedHoverInfo | null\n): Hover | null {\n const hoverInfo =\n semanticHover ??\n getCommentHoverInfoAtOffset(documentText, offset, extensions ? { extensions } : undefined);\n if (hoverInfo === null) {\n return null;\n }\n\n return {\n contents: {\n kind: \"markdown\",\n value: hoverInfo.markdown,\n },\n };\n}\n","/**\n * Go-to-definition provider for FormSpec.\n *\n * This is a stub — go-to-definition support (e.g., navigating from a\n * `field.text(\"name\")` call to the form definition that references it) will\n * be implemented in a future phase.\n */\n\nimport type { Location } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns the definition location for a symbol at the given position.\n *\n * Always returns `null` in this stub implementation.\n *\n * @returns `null` — not yet implemented\n * @public\n */\nexport function getDefinition(): Location | null {\n return null;\n}\n","import type {\n FormSpecAnalysisDiagnostic,\n FormSpecAnalysisDiagnosticLocation,\n} from \"@formspec/analysis/protocol\";\nimport {\n DiagnosticRelatedInformation,\n DiagnosticSeverity,\n Location,\n Range,\n type Diagnostic,\n} from \"vscode-languageserver/node.js\";\nimport type { TextDocument } from \"vscode-languageserver-textdocument\";\nimport { fileUriToPathOrNull } from \"./plugin-client.js\";\nexport { getPluginDiagnosticsForDocument } from \"./plugin-client.js\";\n\n/**\n * Options for converting canonical FormSpec diagnostics into LSP diagnostics.\n *\n * @public\n */\nexport interface ToLspDiagnosticsOptions {\n /** Source label shown by LSP clients. Defaults to `formspec`. */\n readonly source?: string;\n}\n\n/**\n * Converts canonical FormSpec diagnostics into LSP diagnostics.\n *\n * Downstream consumers that want complete white-label control can ignore this\n * helper and render their own messages from `code` + `data`.\n *\n * @public\n */\nexport function toLspDiagnostics(\n document: TextDocument,\n diagnostics: readonly FormSpecAnalysisDiagnostic[],\n options: ToLspDiagnosticsOptions = {}\n): Diagnostic[] {\n const source = options.source ?? \"formspec\";\n return diagnostics.map((diagnostic) => {\n const relatedInformation = toRelatedInformation(document, diagnostic.relatedLocations);\n return {\n range: spanToRange(document, diagnostic.range.start, diagnostic.range.end),\n severity: toLspSeverity(diagnostic.severity),\n source,\n code: diagnostic.code,\n message: diagnostic.message,\n ...(relatedInformation === undefined ? {} : { relatedInformation }),\n data: {\n ...diagnostic.data,\n category: diagnostic.category,\n },\n };\n });\n}\n\nfunction spanToRange(document: TextDocument, start: number, end: number): Range {\n return Range.create(document.positionAt(start), document.positionAt(end));\n}\n\nfunction toLspSeverity(severity: FormSpecAnalysisDiagnostic[\"severity\"]): DiagnosticSeverity {\n switch (severity) {\n case \"error\":\n return DiagnosticSeverity.Error;\n case \"warning\":\n return DiagnosticSeverity.Warning;\n case \"info\":\n return DiagnosticSeverity.Information;\n default:\n return DiagnosticSeverity.Information;\n }\n}\n\nfunction toRelatedInformation(\n document: TextDocument,\n locations: readonly FormSpecAnalysisDiagnosticLocation[]\n): DiagnosticRelatedInformation[] | undefined {\n if (locations.length === 0) {\n return undefined;\n }\n\n const currentDocumentFilePath = getDocumentFilePath(document);\n const relatedInformation = locations\n .filter((location) => location.filePath === currentDocumentFilePath)\n .map((location) =>\n DiagnosticRelatedInformation.create(\n Location.create(\n document.uri,\n spanToRange(document, location.range.start, location.range.end)\n ),\n location.message ?? \"Related FormSpec location\"\n )\n );\n\n return relatedInformation.length === 0 ? undefined : relatedInformation;\n}\n\nfunction getDocumentFilePath(document: TextDocument): string | null {\n return fileUriToPathOrNull(document.uri);\n}\n","import fs from \"node:fs/promises\";\nimport net from \"node:net\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n computeFormSpecTextHash,\n isFormSpecAnalysisManifest,\n isFormSpecSemanticResponse,\n type FormSpecAnalysisDiagnostic,\n type FormSpecAnalysisManifest,\n type FormSpecSerializedCompletionContext,\n type FormSpecSerializedHoverInfo,\n type FormSpecSemanticQuery,\n type FormSpecSemanticResponse,\n} from \"@formspec/analysis/protocol\";\nimport { getFormSpecManifestPath } from \"@formspec/analysis/internal\";\n\nconst DEFAULT_PLUGIN_QUERY_TIMEOUT_MS = 2_000;\n\nfunction getManifestPath(workspaceRoot: string): string {\n return getFormSpecManifestPath(workspaceRoot);\n}\n\nfunction normalizeWorkspaceRoot(root: string): string {\n const resolved = path.resolve(root);\n const parsed = path.parse(resolved);\n let normalized = resolved;\n\n while (normalized.length > parsed.root.length && normalized.endsWith(path.sep)) {\n normalized = normalized.slice(0, -path.sep.length);\n }\n\n return normalized;\n}\n\nfunction getMatchingWorkspaceRoot(\n workspaceRoots: readonly string[],\n filePath: string\n): string | null {\n const normalizedFilePath = path.resolve(filePath);\n const normalizedRoots = [...workspaceRoots]\n .map(normalizeWorkspaceRoot)\n .sort((left, right) => right.length - left.length);\n return (\n normalizedRoots.find(\n (workspaceRoot) =>\n normalizedFilePath === workspaceRoot ||\n normalizedFilePath.startsWith(`${workspaceRoot}${path.sep}`)\n ) ?? null\n );\n}\n\nasync function readManifest(workspaceRoot: string): Promise<FormSpecAnalysisManifest | null> {\n try {\n const manifestText = await fs.readFile(getManifestPath(workspaceRoot), \"utf8\");\n const manifest = JSON.parse(manifestText) as unknown;\n if (!isFormSpecAnalysisManifest(manifest)) {\n return null;\n }\n\n return manifest;\n } catch {\n return null;\n }\n}\n\nasync function sendSemanticQuery(\n manifest: FormSpecAnalysisManifest,\n query: FormSpecSemanticQuery,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSemanticResponse | null> {\n return new Promise((resolve) => {\n const socket = net.createConnection(manifest.endpoint.address);\n let buffer = \"\";\n let settled = false;\n\n const finish = (response: FormSpecSemanticResponse | null): void => {\n if (settled) {\n return;\n }\n settled = true;\n socket.removeAllListeners(\"data\");\n socket.destroy();\n resolve(response);\n };\n\n socket.setTimeout(timeoutMs, () => {\n finish(null);\n });\n\n socket.setEncoding(\"utf8\");\n socket.on(\"connect\", () => {\n socket.write(`${JSON.stringify(query)}\\n`);\n });\n socket.on(\"data\", (chunk) => {\n buffer += String(chunk);\n const newlineIndex = buffer.indexOf(\"\\n\");\n if (newlineIndex < 0) {\n return;\n }\n\n const payload = buffer.slice(0, newlineIndex);\n buffer = buffer.slice(newlineIndex + 1);\n try {\n const response = JSON.parse(payload) as unknown;\n finish(isFormSpecSemanticResponse(response) ? response : null);\n } catch {\n finish(null);\n }\n });\n socket.on(\"error\", () => {\n finish(null);\n });\n socket.on(\"close\", () => {\n finish(null);\n });\n });\n}\n\nexport function fileUriToPathOrNull(uri: string): string | null {\n try {\n return fileURLToPath(uri);\n } catch {\n return null;\n }\n}\n\nasync function sendFileQuery(\n workspaceRoots: readonly string[],\n filePath: string,\n query: FormSpecSemanticQuery,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSemanticResponse | null> {\n const workspaceRoot = getMatchingWorkspaceRoot(workspaceRoots, filePath);\n if (workspaceRoot === null) {\n return null;\n }\n\n const manifest = await readManifest(workspaceRoot);\n if (manifest === null) {\n return null;\n }\n\n return sendSemanticQuery(manifest, query, timeoutMs);\n}\n\nexport async function getPluginCompletionContextForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n offset: number,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSerializedCompletionContext | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"completion\",\n filePath,\n offset,\n },\n timeoutMs\n );\n if (response?.kind !== \"completion\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText) ? response.context : null;\n}\n\nexport async function getPluginHoverForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n offset: number,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSerializedHoverInfo | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"hover\",\n filePath,\n offset,\n },\n timeoutMs\n );\n if (response?.kind !== \"hover\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText) ? response.hover : null;\n}\n\n/**\n * Retrieves canonical FormSpec diagnostics for the current document revision\n * from the plugin transport. Returns `null` when the transport is missing,\n * stale, or invalid.\n *\n * @public\n */\nexport async function getPluginDiagnosticsForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<readonly FormSpecAnalysisDiagnostic[] | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"diagnostics\",\n filePath,\n },\n timeoutMs\n );\n if (response?.kind !== \"diagnostics\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText)\n ? response.diagnostics\n : null;\n}\n"],"mappings":"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","names":["import_node","import_internal","import_node","import_internal","path","fs","net"]}
|
package/dist/index.js
CHANGED
|
@@ -121,10 +121,10 @@ import { fileURLToPath } from "url";
|
|
|
121
121
|
import {
|
|
122
122
|
FORMSPEC_ANALYSIS_PROTOCOL_VERSION,
|
|
123
123
|
computeFormSpecTextHash,
|
|
124
|
-
getFormSpecManifestPath,
|
|
125
124
|
isFormSpecAnalysisManifest,
|
|
126
125
|
isFormSpecSemanticResponse
|
|
127
126
|
} from "@formspec/analysis/protocol";
|
|
127
|
+
import { getFormSpecManifestPath } from "@formspec/analysis/internal";
|
|
128
128
|
var DEFAULT_PLUGIN_QUERY_TIMEOUT_MS = 2e3;
|
|
129
129
|
function getManifestPath(workspaceRoot) {
|
|
130
130
|
return getFormSpecManifestPath(workspaceRoot);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/providers/completion.ts","../src/providers/hover.ts","../src/providers/definition.ts","../src/diagnostics.ts","../src/plugin-client.ts"],"sourcesContent":["/**\n * FormSpec Language Server\n *\n * Sets up an LSP server connection and registers handlers for:\n * - `textDocument/completion` — FormSpec JSDoc constraint tag completions\n * - `textDocument/hover` — Documentation for recognized constraint tags\n * - `textDocument/definition` — Go-to-definition (stub, returns null)\n *\n * The packaged language server is a reference implementation built on the same\n * composable helpers that downstream consumers can call directly.\n */\n\nimport {\n createConnection,\n Diagnostic,\n ProposedFeatures,\n TextDocuments,\n TextDocumentSyncKind,\n type Connection,\n type InitializeResult,\n} from \"vscode-languageserver/node.js\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport { TextDocument } from \"vscode-languageserver-textdocument\";\nimport { getCompletionItemsAtOffset } from \"./providers/completion.js\";\nimport { getHoverAtOffset } from \"./providers/hover.js\";\nimport { getDefinition } from \"./providers/definition.js\";\nimport { getPluginDiagnosticsForDocument, toLspDiagnostics } from \"./diagnostics.js\";\nimport {\n fileUriToPathOrNull,\n getPluginCompletionContextForDocument,\n getPluginHoverForDocument,\n} from \"./plugin-client.js\";\n\nconst PLUGIN_QUERY_TIMEOUT_ENV_VAR = \"FORMSPEC_PLUGIN_QUERY_TIMEOUT_MS\";\n\nfunction dedupeWorkspaceRoots(workspaceRoots: readonly string[]): string[] {\n return [...new Set(workspaceRoots)];\n}\n\nfunction resolvePluginQueryTimeoutMs(explicitTimeoutMs: number | undefined): number | undefined {\n if (explicitTimeoutMs !== undefined) {\n return explicitTimeoutMs;\n }\n\n const rawValue = process.env[PLUGIN_QUERY_TIMEOUT_ENV_VAR];\n if (rawValue === undefined) {\n return undefined;\n }\n\n const parsed = Number.parseInt(rawValue, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction getWorkspaceRootsFromInitializeParams(params: {\n readonly workspaceFolders?: readonly { readonly uri: string }[] | null;\n readonly rootUri?: string | null;\n readonly rootPath?: string | null;\n}): string[] {\n const workspaceFolders =\n params.workspaceFolders\n ?.map((workspaceFolder) => fileUriToPathOrNull(workspaceFolder.uri))\n .filter((workspaceRoot): workspaceRoot is string => workspaceRoot !== null) ?? [];\n const rootUri =\n params.rootUri === null || params.rootUri === undefined\n ? null\n : fileUriToPathOrNull(params.rootUri);\n const rootPath = params.rootPath ?? null;\n\n return dedupeWorkspaceRoots([\n ...workspaceFolders,\n ...(rootUri === null ? [] : [rootUri]),\n ...(rootPath === null ? [] : [rootPath]),\n ]);\n}\n\n/**\n * Public configuration for constructing the FormSpec language server.\n *\n * @public\n */\nexport interface CreateServerOptions {\n /** Optional extension definitions whose custom tags should be surfaced by tooling. */\n readonly extensions?: readonly ExtensionDefinition[];\n /** Optional workspace roots to use before initialize() provides them. */\n readonly workspaceRoots?: readonly string[];\n /** Set to false to disable tsserver-plugin semantic enrichment. */\n readonly usePluginTransport?: boolean;\n /** IPC timeout, in milliseconds, for semantic plugin requests. */\n readonly pluginQueryTimeoutMs?: number;\n /** Optional diagnostics publishing mode for the packaged reference LSP. */\n readonly diagnosticsMode?: \"off\" | \"plugin\";\n /** Source label to use when publishing plugin-derived diagnostics. */\n readonly diagnosticSource?: string;\n}\n\n/**\n * Creates and configures the FormSpec language server connection.\n *\n * Registers LSP capability handlers and returns the connection.\n * Call `connection.listen()` to start accepting messages.\n *\n * @returns The configured LSP connection (not yet listening)\n * @public\n */\nexport function createServer(options: CreateServerOptions = {}): Connection {\n const connection = createConnection(ProposedFeatures.all);\n const documents = new TextDocuments(TextDocument);\n let workspaceRoots = [...(options.workspaceRoots ?? [])];\n const pluginQueryTimeoutMs = resolvePluginQueryTimeoutMs(options.pluginQueryTimeoutMs);\n const diagnosticsMode = options.diagnosticsMode ?? \"off\";\n const diagnosticSource = options.diagnosticSource ?? \"formspec\";\n\n documents.listen(connection);\n\n async function publishDiagnosticsForDocument(document: TextDocument): Promise<void> {\n if (diagnosticsMode !== \"plugin\" || options.usePluginTransport === false) {\n return;\n }\n\n const filePath = fileUriToPathOrNull(document.uri);\n if (filePath === null) {\n return;\n }\n\n const diagnostics =\n (await getPluginDiagnosticsForDocument(\n workspaceRoots,\n filePath,\n document.getText(),\n pluginQueryTimeoutMs\n )) ?? [];\n\n void connection.sendDiagnostics({\n uri: document.uri,\n diagnostics: toLspDiagnostics(document, diagnostics, {\n source: diagnosticSource,\n }),\n });\n }\n\n connection.onInitialize((params): InitializeResult => {\n workspaceRoots = dedupeWorkspaceRoots([\n ...getWorkspaceRootsFromInitializeParams(params),\n ...workspaceRoots,\n ]);\n\n return {\n capabilities: {\n textDocumentSync: TextDocumentSyncKind.Incremental,\n completionProvider: {\n // Trigger completions inside JSDoc comments for tags and target specifiers\n triggerCharacters: [\"@\", \":\"],\n },\n hoverProvider: true,\n definitionProvider: true,\n },\n serverInfo: {\n name: \"formspec-language-server\",\n version: \"0.1.0\",\n },\n };\n });\n\n connection.onCompletion(async (params) => {\n const document = documents.get(params.textDocument.uri);\n if (!document) {\n return [];\n }\n\n const offset = document.offsetAt(params.position);\n const documentText = document.getText();\n const filePath = fileUriToPathOrNull(params.textDocument.uri);\n const semanticContext =\n options.usePluginTransport === false || filePath === null\n ? null\n : await getPluginCompletionContextForDocument(\n workspaceRoots,\n filePath,\n documentText,\n offset,\n pluginQueryTimeoutMs\n );\n\n return getCompletionItemsAtOffset(documentText, offset, options.extensions, semanticContext);\n });\n\n connection.onHover(async (params) => {\n const document = documents.get(params.textDocument.uri);\n if (!document) {\n return null;\n }\n\n const offset = document.offsetAt(params.position);\n const documentText = document.getText();\n const filePath = fileUriToPathOrNull(params.textDocument.uri);\n const semanticHover =\n options.usePluginTransport === false || filePath === null\n ? null\n : await getPluginHoverForDocument(\n workspaceRoots,\n filePath,\n documentText,\n offset,\n pluginQueryTimeoutMs\n );\n\n return getHoverAtOffset(documentText, offset, options.extensions, semanticHover);\n });\n\n connection.onDefinition((_params) => {\n // Go-to-definition is not yet implemented.\n return getDefinition();\n });\n\n documents.onDidOpen(({ document }) => {\n void publishDiagnosticsForDocument(document).catch((error: unknown) => {\n connection.console.error(`[FormSpec] Failed to publish diagnostics: ${String(error)}`);\n });\n });\n\n documents.onDidChangeContent(({ document }) => {\n void publishDiagnosticsForDocument(document).catch((error: unknown) => {\n connection.console.error(`[FormSpec] Failed to publish diagnostics: ${String(error)}`);\n });\n });\n\n documents.onDidClose(({ document }) => {\n if (diagnosticsMode === \"plugin\") {\n void connection.sendDiagnostics({\n uri: document.uri,\n diagnostics: [] satisfies Diagnostic[],\n });\n }\n });\n\n return connection;\n}\n","/**\n * Completion provider for FormSpec JSDoc constraint tags.\n *\n * Uses the shared tag registry from `@formspec/analysis` so completions stay\n * aligned with the same metadata that powers linting and build-time analysis.\n */\n\nimport {\n type FormSpecSerializedCompletionContext,\n type FormSpecSerializedTagDefinition,\n getConstraintTagDefinitions,\n getSemanticCommentCompletionContextAtOffset,\n type TagDefinition,\n} from \"@formspec/analysis/internal\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport { CompletionItem, CompletionItemKind } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns the full set of tag-name completions currently known to FormSpec.\n *\n * @public\n */\nexport function getCompletionItems(extensions?: readonly ExtensionDefinition[]): CompletionItem[] {\n return getConstraintTagDefinitions(extensions).map((tag) => ({\n label: `@${tag.canonicalName}`,\n kind: CompletionItemKind.Keyword,\n detail: tag.completionDetail,\n }));\n}\n\nfunction toCompletionItem(tag: TagDefinition | FormSpecSerializedTagDefinition): CompletionItem {\n return {\n label: `@${tag.canonicalName}`,\n kind: CompletionItemKind.Keyword,\n detail: tag.completionDetail,\n };\n}\n\nfunction toTargetCompletionItems(\n tagName: string,\n targetCompletions: readonly string[]\n): CompletionItem[] {\n return targetCompletions.map((target: string) => ({\n label: target,\n kind:\n target === \"singular\" || target === \"plural\"\n ? CompletionItemKind.EnumMember\n : CompletionItemKind.Field,\n detail: `Target for @${tagName}`,\n }));\n}\n\nfunction filterTagNameCompletionItems(\n prefix: string,\n availableTags: readonly (TagDefinition | FormSpecSerializedTagDefinition)[]\n): CompletionItem[] {\n const normalizedPrefix = prefix.toLowerCase();\n return availableTags\n .map(toCompletionItem)\n .filter((item) => item.label.slice(1).toLowerCase().startsWith(normalizedPrefix));\n}\n\n/** @internal */\nexport function getCompletionItemsAtOffset(\n documentText: string,\n offset: number,\n extensions?: readonly ExtensionDefinition[],\n semanticContext?: FormSpecSerializedCompletionContext | null\n): CompletionItem[] {\n if (semanticContext !== null && semanticContext !== undefined) {\n if (semanticContext.kind === \"target\") {\n return toTargetCompletionItems(\n semanticContext.semantic.tagName,\n semanticContext.semantic.targetCompletions\n );\n }\n\n if (semanticContext.kind !== \"tag-name\") {\n return [];\n }\n\n return filterTagNameCompletionItems(semanticContext.prefix, semanticContext.availableTags);\n }\n\n const resolvedContext = getSemanticCommentCompletionContextAtOffset(\n documentText,\n offset,\n extensions ? { extensions } : undefined\n );\n\n if (resolvedContext.kind === \"target\") {\n return toTargetCompletionItems(\n resolvedContext.semantic.tag.normalizedTagName,\n resolvedContext.semantic.targetCompletions\n );\n }\n\n if (resolvedContext.kind !== \"tag-name\") {\n return [];\n }\n\n return filterTagNameCompletionItems(resolvedContext.prefix, resolvedContext.availableTags);\n}\n","/**\n * Hover provider for FormSpec JSDoc tags.\n *\n * Uses the shared registry from `@formspec/analysis` so hover content stays in\n * sync with the tag inventory and overload metadata.\n */\n\nimport {\n type FormSpecSerializedHoverInfo,\n getCommentHoverInfoAtOffset,\n getTagDefinition,\n normalizeFormSpecTagName,\n} from \"@formspec/analysis/internal\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport type { Hover } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns hover content for a single FormSpec tag name.\n *\n * @public\n */\nexport function getHoverForTag(\n tagName: string,\n extensions?: readonly ExtensionDefinition[]\n): Hover | null {\n const raw = tagName.startsWith(\"@\") ? tagName.slice(1) : tagName;\n const definition = getTagDefinition(normalizeFormSpecTagName(raw), extensions);\n if (!definition) {\n return null;\n }\n\n return {\n contents: {\n kind: \"markdown\",\n value: definition.hoverMarkdown,\n },\n };\n}\n\n/** @internal */\nexport function getHoverAtOffset(\n documentText: string,\n offset: number,\n extensions?: readonly ExtensionDefinition[],\n semanticHover?: FormSpecSerializedHoverInfo | null\n): Hover | null {\n const hoverInfo =\n semanticHover ??\n getCommentHoverInfoAtOffset(documentText, offset, extensions ? { extensions } : undefined);\n if (hoverInfo === null) {\n return null;\n }\n\n return {\n contents: {\n kind: \"markdown\",\n value: hoverInfo.markdown,\n },\n };\n}\n","/**\n * Go-to-definition provider for FormSpec.\n *\n * This is a stub — go-to-definition support (e.g., navigating from a\n * `field.text(\"name\")` call to the form definition that references it) will\n * be implemented in a future phase.\n */\n\nimport type { Location } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns the definition location for a symbol at the given position.\n *\n * Always returns `null` in this stub implementation.\n *\n * @returns `null` — not yet implemented\n * @public\n */\nexport function getDefinition(): Location | null {\n return null;\n}\n","import type {\n FormSpecAnalysisDiagnostic,\n FormSpecAnalysisDiagnosticLocation,\n} from \"@formspec/analysis/protocol\";\nimport {\n DiagnosticRelatedInformation,\n DiagnosticSeverity,\n Location,\n Range,\n type Diagnostic,\n} from \"vscode-languageserver/node.js\";\nimport type { TextDocument } from \"vscode-languageserver-textdocument\";\nimport { fileUriToPathOrNull } from \"./plugin-client.js\";\nexport { getPluginDiagnosticsForDocument } from \"./plugin-client.js\";\n\n/**\n * Options for converting canonical FormSpec diagnostics into LSP diagnostics.\n *\n * @public\n */\nexport interface ToLspDiagnosticsOptions {\n /** Source label shown by LSP clients. Defaults to `formspec`. */\n readonly source?: string;\n}\n\n/**\n * Converts canonical FormSpec diagnostics into LSP diagnostics.\n *\n * Downstream consumers that want complete white-label control can ignore this\n * helper and render their own messages from `code` + `data`.\n *\n * @public\n */\nexport function toLspDiagnostics(\n document: TextDocument,\n diagnostics: readonly FormSpecAnalysisDiagnostic[],\n options: ToLspDiagnosticsOptions = {}\n): Diagnostic[] {\n const source = options.source ?? \"formspec\";\n return diagnostics.map((diagnostic) => {\n const relatedInformation = toRelatedInformation(document, diagnostic.relatedLocations);\n return {\n range: spanToRange(document, diagnostic.range.start, diagnostic.range.end),\n severity: toLspSeverity(diagnostic.severity),\n source,\n code: diagnostic.code,\n message: diagnostic.message,\n ...(relatedInformation === undefined ? {} : { relatedInformation }),\n data: {\n ...diagnostic.data,\n category: diagnostic.category,\n },\n };\n });\n}\n\nfunction spanToRange(document: TextDocument, start: number, end: number): Range {\n return Range.create(document.positionAt(start), document.positionAt(end));\n}\n\nfunction toLspSeverity(severity: FormSpecAnalysisDiagnostic[\"severity\"]): DiagnosticSeverity {\n switch (severity) {\n case \"error\":\n return DiagnosticSeverity.Error;\n case \"warning\":\n return DiagnosticSeverity.Warning;\n case \"info\":\n return DiagnosticSeverity.Information;\n default:\n return DiagnosticSeverity.Information;\n }\n}\n\nfunction toRelatedInformation(\n document: TextDocument,\n locations: readonly FormSpecAnalysisDiagnosticLocation[]\n): DiagnosticRelatedInformation[] | undefined {\n if (locations.length === 0) {\n return undefined;\n }\n\n const currentDocumentFilePath = getDocumentFilePath(document);\n const relatedInformation = locations\n .filter((location) => location.filePath === currentDocumentFilePath)\n .map((location) =>\n DiagnosticRelatedInformation.create(\n Location.create(\n document.uri,\n spanToRange(document, location.range.start, location.range.end)\n ),\n location.message ?? \"Related FormSpec location\"\n )\n );\n\n return relatedInformation.length === 0 ? undefined : relatedInformation;\n}\n\nfunction getDocumentFilePath(document: TextDocument): string | null {\n return fileUriToPathOrNull(document.uri);\n}\n","import fs from \"node:fs/promises\";\nimport net from \"node:net\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n computeFormSpecTextHash,\n getFormSpecManifestPath,\n isFormSpecAnalysisManifest,\n isFormSpecSemanticResponse,\n type FormSpecAnalysisDiagnostic,\n type FormSpecAnalysisManifest,\n type FormSpecSerializedCompletionContext,\n type FormSpecSerializedHoverInfo,\n type FormSpecSemanticQuery,\n type FormSpecSemanticResponse,\n} from \"@formspec/analysis/protocol\";\n\nconst DEFAULT_PLUGIN_QUERY_TIMEOUT_MS = 2_000;\n\nfunction getManifestPath(workspaceRoot: string): string {\n return getFormSpecManifestPath(workspaceRoot);\n}\n\nfunction normalizeWorkspaceRoot(root: string): string {\n const resolved = path.resolve(root);\n const parsed = path.parse(resolved);\n let normalized = resolved;\n\n while (normalized.length > parsed.root.length && normalized.endsWith(path.sep)) {\n normalized = normalized.slice(0, -path.sep.length);\n }\n\n return normalized;\n}\n\nfunction getMatchingWorkspaceRoot(\n workspaceRoots: readonly string[],\n filePath: string\n): string | null {\n const normalizedFilePath = path.resolve(filePath);\n const normalizedRoots = [...workspaceRoots]\n .map(normalizeWorkspaceRoot)\n .sort((left, right) => right.length - left.length);\n return (\n normalizedRoots.find(\n (workspaceRoot) =>\n normalizedFilePath === workspaceRoot ||\n normalizedFilePath.startsWith(`${workspaceRoot}${path.sep}`)\n ) ?? null\n );\n}\n\nasync function readManifest(workspaceRoot: string): Promise<FormSpecAnalysisManifest | null> {\n try {\n const manifestText = await fs.readFile(getManifestPath(workspaceRoot), \"utf8\");\n const manifest = JSON.parse(manifestText) as unknown;\n if (!isFormSpecAnalysisManifest(manifest)) {\n return null;\n }\n\n return manifest;\n } catch {\n return null;\n }\n}\n\nasync function sendSemanticQuery(\n manifest: FormSpecAnalysisManifest,\n query: FormSpecSemanticQuery,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSemanticResponse | null> {\n return new Promise((resolve) => {\n const socket = net.createConnection(manifest.endpoint.address);\n let buffer = \"\";\n let settled = false;\n\n const finish = (response: FormSpecSemanticResponse | null): void => {\n if (settled) {\n return;\n }\n settled = true;\n socket.removeAllListeners(\"data\");\n socket.destroy();\n resolve(response);\n };\n\n socket.setTimeout(timeoutMs, () => {\n finish(null);\n });\n\n socket.setEncoding(\"utf8\");\n socket.on(\"connect\", () => {\n socket.write(`${JSON.stringify(query)}\\n`);\n });\n socket.on(\"data\", (chunk) => {\n buffer += String(chunk);\n const newlineIndex = buffer.indexOf(\"\\n\");\n if (newlineIndex < 0) {\n return;\n }\n\n const payload = buffer.slice(0, newlineIndex);\n buffer = buffer.slice(newlineIndex + 1);\n try {\n const response = JSON.parse(payload) as unknown;\n finish(isFormSpecSemanticResponse(response) ? response : null);\n } catch {\n finish(null);\n }\n });\n socket.on(\"error\", () => {\n finish(null);\n });\n socket.on(\"close\", () => {\n finish(null);\n });\n });\n}\n\nexport function fileUriToPathOrNull(uri: string): string | null {\n try {\n return fileURLToPath(uri);\n } catch {\n return null;\n }\n}\n\nasync function sendFileQuery(\n workspaceRoots: readonly string[],\n filePath: string,\n query: FormSpecSemanticQuery,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSemanticResponse | null> {\n const workspaceRoot = getMatchingWorkspaceRoot(workspaceRoots, filePath);\n if (workspaceRoot === null) {\n return null;\n }\n\n const manifest = await readManifest(workspaceRoot);\n if (manifest === null) {\n return null;\n }\n\n return sendSemanticQuery(manifest, query, timeoutMs);\n}\n\nexport async function getPluginCompletionContextForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n offset: number,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSerializedCompletionContext | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"completion\",\n filePath,\n offset,\n },\n timeoutMs\n );\n if (response?.kind !== \"completion\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText) ? response.context : null;\n}\n\nexport async function getPluginHoverForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n offset: number,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSerializedHoverInfo | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"hover\",\n filePath,\n offset,\n },\n timeoutMs\n );\n if (response?.kind !== \"hover\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText) ? response.hover : null;\n}\n\n/**\n * Retrieves canonical FormSpec diagnostics for the current document revision\n * from the plugin transport. Returns `null` when the transport is missing,\n * stale, or invalid.\n *\n * @public\n */\nexport async function getPluginDiagnosticsForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<readonly FormSpecAnalysisDiagnostic[] | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"diagnostics\",\n filePath,\n },\n timeoutMs\n );\n if (response?.kind !== \"diagnostics\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText)\n ? response.diagnostics\n : null;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/providers/completion.ts","../src/providers/hover.ts","../src/providers/definition.ts","../src/diagnostics.ts","../src/plugin-client.ts"],"sourcesContent":["/**\n * FormSpec Language Server\n *\n * Sets up an LSP server connection and registers handlers for:\n * - `textDocument/completion` — FormSpec JSDoc constraint tag completions\n * - `textDocument/hover` — Documentation for recognized constraint tags\n * - `textDocument/definition` — Go-to-definition (stub, returns null)\n *\n * The packaged language server is a reference implementation built on the same\n * composable helpers that downstream consumers can call directly.\n */\n\nimport {\n createConnection,\n Diagnostic,\n ProposedFeatures,\n TextDocuments,\n TextDocumentSyncKind,\n type Connection,\n type InitializeResult,\n} from \"vscode-languageserver/node.js\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport { TextDocument } from \"vscode-languageserver-textdocument\";\nimport { getCompletionItemsAtOffset } from \"./providers/completion.js\";\nimport { getHoverAtOffset } from \"./providers/hover.js\";\nimport { getDefinition } from \"./providers/definition.js\";\nimport { getPluginDiagnosticsForDocument, toLspDiagnostics } from \"./diagnostics.js\";\nimport {\n fileUriToPathOrNull,\n getPluginCompletionContextForDocument,\n getPluginHoverForDocument,\n} from \"./plugin-client.js\";\n\nconst PLUGIN_QUERY_TIMEOUT_ENV_VAR = \"FORMSPEC_PLUGIN_QUERY_TIMEOUT_MS\";\n\nfunction dedupeWorkspaceRoots(workspaceRoots: readonly string[]): string[] {\n return [...new Set(workspaceRoots)];\n}\n\nfunction resolvePluginQueryTimeoutMs(explicitTimeoutMs: number | undefined): number | undefined {\n if (explicitTimeoutMs !== undefined) {\n return explicitTimeoutMs;\n }\n\n const rawValue = process.env[PLUGIN_QUERY_TIMEOUT_ENV_VAR];\n if (rawValue === undefined) {\n return undefined;\n }\n\n const parsed = Number.parseInt(rawValue, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction getWorkspaceRootsFromInitializeParams(params: {\n readonly workspaceFolders?: readonly { readonly uri: string }[] | null;\n readonly rootUri?: string | null;\n readonly rootPath?: string | null;\n}): string[] {\n const workspaceFolders =\n params.workspaceFolders\n ?.map((workspaceFolder) => fileUriToPathOrNull(workspaceFolder.uri))\n .filter((workspaceRoot): workspaceRoot is string => workspaceRoot !== null) ?? [];\n const rootUri =\n params.rootUri === null || params.rootUri === undefined\n ? null\n : fileUriToPathOrNull(params.rootUri);\n const rootPath = params.rootPath ?? null;\n\n return dedupeWorkspaceRoots([\n ...workspaceFolders,\n ...(rootUri === null ? [] : [rootUri]),\n ...(rootPath === null ? [] : [rootPath]),\n ]);\n}\n\n/**\n * Public configuration for constructing the FormSpec language server.\n *\n * @public\n */\nexport interface CreateServerOptions {\n /** Optional extension definitions whose custom tags should be surfaced by tooling. */\n readonly extensions?: readonly ExtensionDefinition[];\n /** Optional workspace roots to use before initialize() provides them. */\n readonly workspaceRoots?: readonly string[];\n /** Set to false to disable tsserver-plugin semantic enrichment. */\n readonly usePluginTransport?: boolean;\n /** IPC timeout, in milliseconds, for semantic plugin requests. */\n readonly pluginQueryTimeoutMs?: number;\n /** Optional diagnostics publishing mode for the packaged reference LSP. */\n readonly diagnosticsMode?: \"off\" | \"plugin\";\n /** Source label to use when publishing plugin-derived diagnostics. */\n readonly diagnosticSource?: string;\n}\n\n/**\n * Creates and configures the FormSpec language server connection.\n *\n * Registers LSP capability handlers and returns the connection.\n * Call `connection.listen()` to start accepting messages.\n *\n * @returns The configured LSP connection (not yet listening)\n * @public\n */\nexport function createServer(options: CreateServerOptions = {}): Connection {\n const connection = createConnection(ProposedFeatures.all);\n const documents = new TextDocuments(TextDocument);\n let workspaceRoots = [...(options.workspaceRoots ?? [])];\n const pluginQueryTimeoutMs = resolvePluginQueryTimeoutMs(options.pluginQueryTimeoutMs);\n const diagnosticsMode = options.diagnosticsMode ?? \"off\";\n const diagnosticSource = options.diagnosticSource ?? \"formspec\";\n\n documents.listen(connection);\n\n async function publishDiagnosticsForDocument(document: TextDocument): Promise<void> {\n if (diagnosticsMode !== \"plugin\" || options.usePluginTransport === false) {\n return;\n }\n\n const filePath = fileUriToPathOrNull(document.uri);\n if (filePath === null) {\n return;\n }\n\n const diagnostics =\n (await getPluginDiagnosticsForDocument(\n workspaceRoots,\n filePath,\n document.getText(),\n pluginQueryTimeoutMs\n )) ?? [];\n\n void connection.sendDiagnostics({\n uri: document.uri,\n diagnostics: toLspDiagnostics(document, diagnostics, {\n source: diagnosticSource,\n }),\n });\n }\n\n connection.onInitialize((params): InitializeResult => {\n workspaceRoots = dedupeWorkspaceRoots([\n ...getWorkspaceRootsFromInitializeParams(params),\n ...workspaceRoots,\n ]);\n\n return {\n capabilities: {\n textDocumentSync: TextDocumentSyncKind.Incremental,\n completionProvider: {\n // Trigger completions inside JSDoc comments for tags and target specifiers\n triggerCharacters: [\"@\", \":\"],\n },\n hoverProvider: true,\n definitionProvider: true,\n },\n serverInfo: {\n name: \"formspec-language-server\",\n version: \"0.1.0\",\n },\n };\n });\n\n connection.onCompletion(async (params) => {\n const document = documents.get(params.textDocument.uri);\n if (!document) {\n return [];\n }\n\n const offset = document.offsetAt(params.position);\n const documentText = document.getText();\n const filePath = fileUriToPathOrNull(params.textDocument.uri);\n const semanticContext =\n options.usePluginTransport === false || filePath === null\n ? null\n : await getPluginCompletionContextForDocument(\n workspaceRoots,\n filePath,\n documentText,\n offset,\n pluginQueryTimeoutMs\n );\n\n return getCompletionItemsAtOffset(documentText, offset, options.extensions, semanticContext);\n });\n\n connection.onHover(async (params) => {\n const document = documents.get(params.textDocument.uri);\n if (!document) {\n return null;\n }\n\n const offset = document.offsetAt(params.position);\n const documentText = document.getText();\n const filePath = fileUriToPathOrNull(params.textDocument.uri);\n const semanticHover =\n options.usePluginTransport === false || filePath === null\n ? null\n : await getPluginHoverForDocument(\n workspaceRoots,\n filePath,\n documentText,\n offset,\n pluginQueryTimeoutMs\n );\n\n return getHoverAtOffset(documentText, offset, options.extensions, semanticHover);\n });\n\n connection.onDefinition((_params) => {\n // Go-to-definition is not yet implemented.\n return getDefinition();\n });\n\n documents.onDidOpen(({ document }) => {\n void publishDiagnosticsForDocument(document).catch((error: unknown) => {\n connection.console.error(`[FormSpec] Failed to publish diagnostics: ${String(error)}`);\n });\n });\n\n documents.onDidChangeContent(({ document }) => {\n void publishDiagnosticsForDocument(document).catch((error: unknown) => {\n connection.console.error(`[FormSpec] Failed to publish diagnostics: ${String(error)}`);\n });\n });\n\n documents.onDidClose(({ document }) => {\n if (diagnosticsMode === \"plugin\") {\n void connection.sendDiagnostics({\n uri: document.uri,\n diagnostics: [] satisfies Diagnostic[],\n });\n }\n });\n\n return connection;\n}\n","/**\n * Completion provider for FormSpec JSDoc constraint tags.\n *\n * Uses the shared tag registry from `@formspec/analysis` so completions stay\n * aligned with the same metadata that powers linting and build-time analysis.\n */\n\nimport {\n type FormSpecSerializedCompletionContext,\n type FormSpecSerializedTagDefinition,\n getConstraintTagDefinitions,\n getSemanticCommentCompletionContextAtOffset,\n type TagDefinition,\n} from \"@formspec/analysis/internal\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport { CompletionItem, CompletionItemKind } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns the full set of tag-name completions currently known to FormSpec.\n *\n * @public\n */\nexport function getCompletionItems(extensions?: readonly ExtensionDefinition[]): CompletionItem[] {\n return getConstraintTagDefinitions(extensions).map((tag) => ({\n label: `@${tag.canonicalName}`,\n kind: CompletionItemKind.Keyword,\n detail: tag.completionDetail,\n }));\n}\n\nfunction toCompletionItem(tag: TagDefinition | FormSpecSerializedTagDefinition): CompletionItem {\n return {\n label: `@${tag.canonicalName}`,\n kind: CompletionItemKind.Keyword,\n detail: tag.completionDetail,\n };\n}\n\nfunction toTargetCompletionItems(\n tagName: string,\n targetCompletions: readonly string[]\n): CompletionItem[] {\n return targetCompletions.map((target: string) => ({\n label: target,\n kind:\n target === \"singular\" || target === \"plural\"\n ? CompletionItemKind.EnumMember\n : CompletionItemKind.Field,\n detail: `Target for @${tagName}`,\n }));\n}\n\nfunction filterTagNameCompletionItems(\n prefix: string,\n availableTags: readonly (TagDefinition | FormSpecSerializedTagDefinition)[]\n): CompletionItem[] {\n const normalizedPrefix = prefix.toLowerCase();\n return availableTags\n .map(toCompletionItem)\n .filter((item) => item.label.slice(1).toLowerCase().startsWith(normalizedPrefix));\n}\n\n/** @internal */\nexport function getCompletionItemsAtOffset(\n documentText: string,\n offset: number,\n extensions?: readonly ExtensionDefinition[],\n semanticContext?: FormSpecSerializedCompletionContext | null\n): CompletionItem[] {\n if (semanticContext !== null && semanticContext !== undefined) {\n if (semanticContext.kind === \"target\") {\n return toTargetCompletionItems(\n semanticContext.semantic.tagName,\n semanticContext.semantic.targetCompletions\n );\n }\n\n if (semanticContext.kind !== \"tag-name\") {\n return [];\n }\n\n return filterTagNameCompletionItems(semanticContext.prefix, semanticContext.availableTags);\n }\n\n const resolvedContext = getSemanticCommentCompletionContextAtOffset(\n documentText,\n offset,\n extensions ? { extensions } : undefined\n );\n\n if (resolvedContext.kind === \"target\") {\n return toTargetCompletionItems(\n resolvedContext.semantic.tag.normalizedTagName,\n resolvedContext.semantic.targetCompletions\n );\n }\n\n if (resolvedContext.kind !== \"tag-name\") {\n return [];\n }\n\n return filterTagNameCompletionItems(resolvedContext.prefix, resolvedContext.availableTags);\n}\n","/**\n * Hover provider for FormSpec JSDoc tags.\n *\n * Uses the shared registry from `@formspec/analysis` so hover content stays in\n * sync with the tag inventory and overload metadata.\n */\n\nimport {\n type FormSpecSerializedHoverInfo,\n getCommentHoverInfoAtOffset,\n getTagDefinition,\n normalizeFormSpecTagName,\n} from \"@formspec/analysis/internal\";\nimport type { ExtensionDefinition } from \"@formspec/core\";\nimport type { Hover } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns hover content for a single FormSpec tag name.\n *\n * @public\n */\nexport function getHoverForTag(\n tagName: string,\n extensions?: readonly ExtensionDefinition[]\n): Hover | null {\n const raw = tagName.startsWith(\"@\") ? tagName.slice(1) : tagName;\n const definition = getTagDefinition(normalizeFormSpecTagName(raw), extensions);\n if (!definition) {\n return null;\n }\n\n return {\n contents: {\n kind: \"markdown\",\n value: definition.hoverMarkdown,\n },\n };\n}\n\n/** @internal */\nexport function getHoverAtOffset(\n documentText: string,\n offset: number,\n extensions?: readonly ExtensionDefinition[],\n semanticHover?: FormSpecSerializedHoverInfo | null\n): Hover | null {\n const hoverInfo =\n semanticHover ??\n getCommentHoverInfoAtOffset(documentText, offset, extensions ? { extensions } : undefined);\n if (hoverInfo === null) {\n return null;\n }\n\n return {\n contents: {\n kind: \"markdown\",\n value: hoverInfo.markdown,\n },\n };\n}\n","/**\n * Go-to-definition provider for FormSpec.\n *\n * This is a stub — go-to-definition support (e.g., navigating from a\n * `field.text(\"name\")` call to the form definition that references it) will\n * be implemented in a future phase.\n */\n\nimport type { Location } from \"vscode-languageserver/node.js\";\n\n/**\n * Returns the definition location for a symbol at the given position.\n *\n * Always returns `null` in this stub implementation.\n *\n * @returns `null` — not yet implemented\n * @public\n */\nexport function getDefinition(): Location | null {\n return null;\n}\n","import type {\n FormSpecAnalysisDiagnostic,\n FormSpecAnalysisDiagnosticLocation,\n} from \"@formspec/analysis/protocol\";\nimport {\n DiagnosticRelatedInformation,\n DiagnosticSeverity,\n Location,\n Range,\n type Diagnostic,\n} from \"vscode-languageserver/node.js\";\nimport type { TextDocument } from \"vscode-languageserver-textdocument\";\nimport { fileUriToPathOrNull } from \"./plugin-client.js\";\nexport { getPluginDiagnosticsForDocument } from \"./plugin-client.js\";\n\n/**\n * Options for converting canonical FormSpec diagnostics into LSP diagnostics.\n *\n * @public\n */\nexport interface ToLspDiagnosticsOptions {\n /** Source label shown by LSP clients. Defaults to `formspec`. */\n readonly source?: string;\n}\n\n/**\n * Converts canonical FormSpec diagnostics into LSP diagnostics.\n *\n * Downstream consumers that want complete white-label control can ignore this\n * helper and render their own messages from `code` + `data`.\n *\n * @public\n */\nexport function toLspDiagnostics(\n document: TextDocument,\n diagnostics: readonly FormSpecAnalysisDiagnostic[],\n options: ToLspDiagnosticsOptions = {}\n): Diagnostic[] {\n const source = options.source ?? \"formspec\";\n return diagnostics.map((diagnostic) => {\n const relatedInformation = toRelatedInformation(document, diagnostic.relatedLocations);\n return {\n range: spanToRange(document, diagnostic.range.start, diagnostic.range.end),\n severity: toLspSeverity(diagnostic.severity),\n source,\n code: diagnostic.code,\n message: diagnostic.message,\n ...(relatedInformation === undefined ? {} : { relatedInformation }),\n data: {\n ...diagnostic.data,\n category: diagnostic.category,\n },\n };\n });\n}\n\nfunction spanToRange(document: TextDocument, start: number, end: number): Range {\n return Range.create(document.positionAt(start), document.positionAt(end));\n}\n\nfunction toLspSeverity(severity: FormSpecAnalysisDiagnostic[\"severity\"]): DiagnosticSeverity {\n switch (severity) {\n case \"error\":\n return DiagnosticSeverity.Error;\n case \"warning\":\n return DiagnosticSeverity.Warning;\n case \"info\":\n return DiagnosticSeverity.Information;\n default:\n return DiagnosticSeverity.Information;\n }\n}\n\nfunction toRelatedInformation(\n document: TextDocument,\n locations: readonly FormSpecAnalysisDiagnosticLocation[]\n): DiagnosticRelatedInformation[] | undefined {\n if (locations.length === 0) {\n return undefined;\n }\n\n const currentDocumentFilePath = getDocumentFilePath(document);\n const relatedInformation = locations\n .filter((location) => location.filePath === currentDocumentFilePath)\n .map((location) =>\n DiagnosticRelatedInformation.create(\n Location.create(\n document.uri,\n spanToRange(document, location.range.start, location.range.end)\n ),\n location.message ?? \"Related FormSpec location\"\n )\n );\n\n return relatedInformation.length === 0 ? undefined : relatedInformation;\n}\n\nfunction getDocumentFilePath(document: TextDocument): string | null {\n return fileUriToPathOrNull(document.uri);\n}\n","import fs from \"node:fs/promises\";\nimport net from \"node:net\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n computeFormSpecTextHash,\n isFormSpecAnalysisManifest,\n isFormSpecSemanticResponse,\n type FormSpecAnalysisDiagnostic,\n type FormSpecAnalysisManifest,\n type FormSpecSerializedCompletionContext,\n type FormSpecSerializedHoverInfo,\n type FormSpecSemanticQuery,\n type FormSpecSemanticResponse,\n} from \"@formspec/analysis/protocol\";\nimport { getFormSpecManifestPath } from \"@formspec/analysis/internal\";\n\nconst DEFAULT_PLUGIN_QUERY_TIMEOUT_MS = 2_000;\n\nfunction getManifestPath(workspaceRoot: string): string {\n return getFormSpecManifestPath(workspaceRoot);\n}\n\nfunction normalizeWorkspaceRoot(root: string): string {\n const resolved = path.resolve(root);\n const parsed = path.parse(resolved);\n let normalized = resolved;\n\n while (normalized.length > parsed.root.length && normalized.endsWith(path.sep)) {\n normalized = normalized.slice(0, -path.sep.length);\n }\n\n return normalized;\n}\n\nfunction getMatchingWorkspaceRoot(\n workspaceRoots: readonly string[],\n filePath: string\n): string | null {\n const normalizedFilePath = path.resolve(filePath);\n const normalizedRoots = [...workspaceRoots]\n .map(normalizeWorkspaceRoot)\n .sort((left, right) => right.length - left.length);\n return (\n normalizedRoots.find(\n (workspaceRoot) =>\n normalizedFilePath === workspaceRoot ||\n normalizedFilePath.startsWith(`${workspaceRoot}${path.sep}`)\n ) ?? null\n );\n}\n\nasync function readManifest(workspaceRoot: string): Promise<FormSpecAnalysisManifest | null> {\n try {\n const manifestText = await fs.readFile(getManifestPath(workspaceRoot), \"utf8\");\n const manifest = JSON.parse(manifestText) as unknown;\n if (!isFormSpecAnalysisManifest(manifest)) {\n return null;\n }\n\n return manifest;\n } catch {\n return null;\n }\n}\n\nasync function sendSemanticQuery(\n manifest: FormSpecAnalysisManifest,\n query: FormSpecSemanticQuery,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSemanticResponse | null> {\n return new Promise((resolve) => {\n const socket = net.createConnection(manifest.endpoint.address);\n let buffer = \"\";\n let settled = false;\n\n const finish = (response: FormSpecSemanticResponse | null): void => {\n if (settled) {\n return;\n }\n settled = true;\n socket.removeAllListeners(\"data\");\n socket.destroy();\n resolve(response);\n };\n\n socket.setTimeout(timeoutMs, () => {\n finish(null);\n });\n\n socket.setEncoding(\"utf8\");\n socket.on(\"connect\", () => {\n socket.write(`${JSON.stringify(query)}\\n`);\n });\n socket.on(\"data\", (chunk) => {\n buffer += String(chunk);\n const newlineIndex = buffer.indexOf(\"\\n\");\n if (newlineIndex < 0) {\n return;\n }\n\n const payload = buffer.slice(0, newlineIndex);\n buffer = buffer.slice(newlineIndex + 1);\n try {\n const response = JSON.parse(payload) as unknown;\n finish(isFormSpecSemanticResponse(response) ? response : null);\n } catch {\n finish(null);\n }\n });\n socket.on(\"error\", () => {\n finish(null);\n });\n socket.on(\"close\", () => {\n finish(null);\n });\n });\n}\n\nexport function fileUriToPathOrNull(uri: string): string | null {\n try {\n return fileURLToPath(uri);\n } catch {\n return null;\n }\n}\n\nasync function sendFileQuery(\n workspaceRoots: readonly string[],\n filePath: string,\n query: FormSpecSemanticQuery,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSemanticResponse | null> {\n const workspaceRoot = getMatchingWorkspaceRoot(workspaceRoots, filePath);\n if (workspaceRoot === null) {\n return null;\n }\n\n const manifest = await readManifest(workspaceRoot);\n if (manifest === null) {\n return null;\n }\n\n return sendSemanticQuery(manifest, query, timeoutMs);\n}\n\nexport async function getPluginCompletionContextForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n offset: number,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSerializedCompletionContext | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"completion\",\n filePath,\n offset,\n },\n timeoutMs\n );\n if (response?.kind !== \"completion\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText) ? response.context : null;\n}\n\nexport async function getPluginHoverForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n offset: number,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<FormSpecSerializedHoverInfo | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"hover\",\n filePath,\n offset,\n },\n timeoutMs\n );\n if (response?.kind !== \"hover\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText) ? response.hover : null;\n}\n\n/**\n * Retrieves canonical FormSpec diagnostics for the current document revision\n * from the plugin transport. Returns `null` when the transport is missing,\n * stale, or invalid.\n *\n * @public\n */\nexport async function getPluginDiagnosticsForDocument(\n workspaceRoots: readonly string[],\n filePath: string,\n documentText: string,\n timeoutMs = DEFAULT_PLUGIN_QUERY_TIMEOUT_MS\n): Promise<readonly FormSpecAnalysisDiagnostic[] | null> {\n const response = await sendFileQuery(\n workspaceRoots,\n filePath,\n {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"diagnostics\",\n filePath,\n },\n timeoutMs\n );\n if (response?.kind !== \"diagnostics\") {\n return null;\n }\n\n return response.sourceHash === computeFormSpecTextHash(documentText)\n ? response.diagnostics\n : null;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* \@formspec/language-server
|
|
3
|
+
*
|
|
4
|
+
* Language server for FormSpec — provides completions, hover documentation,
|
|
5
|
+
* and go-to-definition for FormSpec JSDoc constraint tags (`@Minimum`,
|
|
6
|
+
* `@Maximum`, `@Pattern`, etc.) in TypeScript files.
|
|
7
|
+
*
|
|
8
|
+
* This package implements the Language Server Protocol (LSP) using the
|
|
9
|
+
* `vscode-languageserver` library. Cheap syntax-local behaviors stay in the
|
|
10
|
+
* LSP process, while TypeScript-project-aware semantics are supplied by
|
|
11
|
+
* `@formspec/ts-plugin` over a local manifest + IPC transport.
|
|
12
|
+
*
|
|
13
|
+
* The packaged server acts as a reference implementation over the composable
|
|
14
|
+
* completion, hover, and diagnostics helpers exported from this package.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { createServer } from '@formspec/language-server';
|
|
19
|
+
*
|
|
20
|
+
* const connection = createServer();
|
|
21
|
+
* connection.listen();
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { CompletionItem } from 'vscode-languageserver/node.js';
|
|
28
|
+
import { Connection } from 'vscode-languageserver/node.js';
|
|
29
|
+
import { Diagnostic } from 'vscode-languageserver/node.js';
|
|
30
|
+
import type { ExtensionDefinition } from '@formspec/core';
|
|
31
|
+
import type { Hover } from 'vscode-languageserver/node.js';
|
|
32
|
+
import type { Location } from 'vscode-languageserver/node.js';
|
|
33
|
+
import type { TextDocument } from 'vscode-languageserver-textdocument';
|
|
34
|
+
|
|
35
|
+
/** @public */
|
|
36
|
+
export declare interface CommentSpan {
|
|
37
|
+
readonly start: number;
|
|
38
|
+
readonly end: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates and configures the FormSpec language server connection.
|
|
43
|
+
*
|
|
44
|
+
* Registers LSP capability handlers and returns the connection.
|
|
45
|
+
* Call `connection.listen()` to start accepting messages.
|
|
46
|
+
*
|
|
47
|
+
* @returns The configured LSP connection (not yet listening)
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export declare function createServer(options?: CreateServerOptions): Connection;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Public configuration for constructing the FormSpec language server.
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
export declare interface CreateServerOptions {
|
|
58
|
+
/** Optional extension definitions whose custom tags should be surfaced by tooling. */
|
|
59
|
+
readonly extensions?: readonly ExtensionDefinition[];
|
|
60
|
+
/** Optional workspace roots to use before initialize() provides them. */
|
|
61
|
+
readonly workspaceRoots?: readonly string[];
|
|
62
|
+
/** Set to false to disable tsserver-plugin semantic enrichment. */
|
|
63
|
+
readonly usePluginTransport?: boolean;
|
|
64
|
+
/** IPC timeout, in milliseconds, for semantic plugin requests. */
|
|
65
|
+
readonly pluginQueryTimeoutMs?: number;
|
|
66
|
+
/** Optional diagnostics publishing mode for the packaged reference LSP. */
|
|
67
|
+
readonly diagnosticsMode?: "off" | "plugin";
|
|
68
|
+
/** Source label to use when publishing plugin-derived diagnostics. */
|
|
69
|
+
readonly diagnosticSource?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* File-local diagnostic derived from comment parsing or semantic analysis.
|
|
74
|
+
*
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
77
|
+
export declare interface FormSpecAnalysisDiagnostic {
|
|
78
|
+
readonly code: string;
|
|
79
|
+
readonly category: FormSpecAnalysisDiagnosticCategory;
|
|
80
|
+
readonly message: string;
|
|
81
|
+
readonly range: CommentSpan;
|
|
82
|
+
readonly severity: "error" | "warning" | "info";
|
|
83
|
+
readonly relatedLocations: readonly FormSpecAnalysisDiagnosticLocation[];
|
|
84
|
+
readonly data: Record<string, FormSpecAnalysisDiagnosticDataValue>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Machine-readable diagnostic category used by FormSpec tooling surfaces.
|
|
89
|
+
*
|
|
90
|
+
* @public
|
|
91
|
+
*/
|
|
92
|
+
export declare type FormSpecAnalysisDiagnosticCategory = "tag-recognition" | "value-parsing" | "type-compatibility" | "target-resolution" | "constraint-validation" | "infrastructure";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Primitive structured values carried in diagnostic facts for white-label
|
|
96
|
+
* downstream rendering.
|
|
97
|
+
*
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
export declare type FormSpecAnalysisDiagnosticDataValue = string | number | boolean | readonly string[] | readonly number[] | readonly boolean[];
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Additional source location associated with a diagnostic.
|
|
104
|
+
*
|
|
105
|
+
* @public
|
|
106
|
+
*/
|
|
107
|
+
export declare interface FormSpecAnalysisDiagnosticLocation {
|
|
108
|
+
readonly filePath: string;
|
|
109
|
+
readonly range: CommentSpan;
|
|
110
|
+
readonly message?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns the full set of tag-name completions currently known to FormSpec.
|
|
115
|
+
*
|
|
116
|
+
* @public
|
|
117
|
+
*/
|
|
118
|
+
export declare function getCompletionItems(extensions?: readonly ExtensionDefinition[]): CompletionItem[];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Returns the definition location for a symbol at the given position.
|
|
122
|
+
*
|
|
123
|
+
* Always returns `null` in this stub implementation.
|
|
124
|
+
*
|
|
125
|
+
* @returns `null` — not yet implemented
|
|
126
|
+
* @public
|
|
127
|
+
*/
|
|
128
|
+
export declare function getDefinition(): Location | null;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns hover content for a single FormSpec tag name.
|
|
132
|
+
*
|
|
133
|
+
* @public
|
|
134
|
+
*/
|
|
135
|
+
export declare function getHoverForTag(tagName: string, extensions?: readonly ExtensionDefinition[]): Hover | null;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Retrieves canonical FormSpec diagnostics for the current document revision
|
|
139
|
+
* from the plugin transport. Returns `null` when the transport is missing,
|
|
140
|
+
* stale, or invalid.
|
|
141
|
+
*
|
|
142
|
+
* @public
|
|
143
|
+
*/
|
|
144
|
+
export declare function getPluginDiagnosticsForDocument(workspaceRoots: readonly string[], filePath: string, documentText: string, timeoutMs?: number): Promise<readonly FormSpecAnalysisDiagnostic[] | null>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Converts canonical FormSpec diagnostics into LSP diagnostics.
|
|
148
|
+
*
|
|
149
|
+
* Downstream consumers that want complete white-label control can ignore this
|
|
150
|
+
* helper and render their own messages from `code` + `data`.
|
|
151
|
+
*
|
|
152
|
+
* @public
|
|
153
|
+
*/
|
|
154
|
+
export declare function toLspDiagnostics(document: TextDocument, diagnostics: readonly FormSpecAnalysisDiagnostic[], options?: ToLspDiagnosticsOptions): Diagnostic[];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Options for converting canonical FormSpec diagnostics into LSP diagnostics.
|
|
158
|
+
*
|
|
159
|
+
* @public
|
|
160
|
+
*/
|
|
161
|
+
export declare interface ToLspDiagnosticsOptions {
|
|
162
|
+
/** Source label shown by LSP clients. Defaults to `formspec`. */
|
|
163
|
+
readonly source?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export { }
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* \@formspec/language-server
|
|
3
|
+
*
|
|
4
|
+
* Language server for FormSpec — provides completions, hover documentation,
|
|
5
|
+
* and go-to-definition for FormSpec JSDoc constraint tags (`@Minimum`,
|
|
6
|
+
* `@Maximum`, `@Pattern`, etc.) in TypeScript files.
|
|
7
|
+
*
|
|
8
|
+
* This package implements the Language Server Protocol (LSP) using the
|
|
9
|
+
* `vscode-languageserver` library. Cheap syntax-local behaviors stay in the
|
|
10
|
+
* LSP process, while TypeScript-project-aware semantics are supplied by
|
|
11
|
+
* `@formspec/ts-plugin` over a local manifest + IPC transport.
|
|
12
|
+
*
|
|
13
|
+
* The packaged server acts as a reference implementation over the composable
|
|
14
|
+
* completion, hover, and diagnostics helpers exported from this package.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { createServer } from '@formspec/language-server';
|
|
19
|
+
*
|
|
20
|
+
* const connection = createServer();
|
|
21
|
+
* connection.listen();
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { CompletionItem } from 'vscode-languageserver/node.js';
|
|
28
|
+
import { Connection } from 'vscode-languageserver/node.js';
|
|
29
|
+
import { Diagnostic } from 'vscode-languageserver/node.js';
|
|
30
|
+
import type { ExtensionDefinition } from '@formspec/core';
|
|
31
|
+
import type { Hover } from 'vscode-languageserver/node.js';
|
|
32
|
+
import type { Location } from 'vscode-languageserver/node.js';
|
|
33
|
+
import type { TextDocument } from 'vscode-languageserver-textdocument';
|
|
34
|
+
|
|
35
|
+
/** @public */
|
|
36
|
+
export declare interface CommentSpan {
|
|
37
|
+
readonly start: number;
|
|
38
|
+
readonly end: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates and configures the FormSpec language server connection.
|
|
43
|
+
*
|
|
44
|
+
* Registers LSP capability handlers and returns the connection.
|
|
45
|
+
* Call `connection.listen()` to start accepting messages.
|
|
46
|
+
*
|
|
47
|
+
* @returns The configured LSP connection (not yet listening)
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export declare function createServer(options?: CreateServerOptions): Connection;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Public configuration for constructing the FormSpec language server.
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
export declare interface CreateServerOptions {
|
|
58
|
+
/** Optional extension definitions whose custom tags should be surfaced by tooling. */
|
|
59
|
+
readonly extensions?: readonly ExtensionDefinition[];
|
|
60
|
+
/** Optional workspace roots to use before initialize() provides them. */
|
|
61
|
+
readonly workspaceRoots?: readonly string[];
|
|
62
|
+
/** Set to false to disable tsserver-plugin semantic enrichment. */
|
|
63
|
+
readonly usePluginTransport?: boolean;
|
|
64
|
+
/** IPC timeout, in milliseconds, for semantic plugin requests. */
|
|
65
|
+
readonly pluginQueryTimeoutMs?: number;
|
|
66
|
+
/** Optional diagnostics publishing mode for the packaged reference LSP. */
|
|
67
|
+
readonly diagnosticsMode?: "off" | "plugin";
|
|
68
|
+
/** Source label to use when publishing plugin-derived diagnostics. */
|
|
69
|
+
readonly diagnosticSource?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* File-local diagnostic derived from comment parsing or semantic analysis.
|
|
74
|
+
*
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
77
|
+
export declare interface FormSpecAnalysisDiagnostic {
|
|
78
|
+
readonly code: string;
|
|
79
|
+
readonly category: FormSpecAnalysisDiagnosticCategory;
|
|
80
|
+
readonly message: string;
|
|
81
|
+
readonly range: CommentSpan;
|
|
82
|
+
readonly severity: "error" | "warning" | "info";
|
|
83
|
+
readonly relatedLocations: readonly FormSpecAnalysisDiagnosticLocation[];
|
|
84
|
+
readonly data: Record<string, FormSpecAnalysisDiagnosticDataValue>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Machine-readable diagnostic category used by FormSpec tooling surfaces.
|
|
89
|
+
*
|
|
90
|
+
* @public
|
|
91
|
+
*/
|
|
92
|
+
export declare type FormSpecAnalysisDiagnosticCategory = "tag-recognition" | "value-parsing" | "type-compatibility" | "target-resolution" | "constraint-validation" | "infrastructure";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Primitive structured values carried in diagnostic facts for white-label
|
|
96
|
+
* downstream rendering.
|
|
97
|
+
*
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
export declare type FormSpecAnalysisDiagnosticDataValue = string | number | boolean | readonly string[] | readonly number[] | readonly boolean[];
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Additional source location associated with a diagnostic.
|
|
104
|
+
*
|
|
105
|
+
* @public
|
|
106
|
+
*/
|
|
107
|
+
export declare interface FormSpecAnalysisDiagnosticLocation {
|
|
108
|
+
readonly filePath: string;
|
|
109
|
+
readonly range: CommentSpan;
|
|
110
|
+
readonly message?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns the full set of tag-name completions currently known to FormSpec.
|
|
115
|
+
*
|
|
116
|
+
* @public
|
|
117
|
+
*/
|
|
118
|
+
export declare function getCompletionItems(extensions?: readonly ExtensionDefinition[]): CompletionItem[];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Returns the definition location for a symbol at the given position.
|
|
122
|
+
*
|
|
123
|
+
* Always returns `null` in this stub implementation.
|
|
124
|
+
*
|
|
125
|
+
* @returns `null` — not yet implemented
|
|
126
|
+
* @public
|
|
127
|
+
*/
|
|
128
|
+
export declare function getDefinition(): Location | null;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns hover content for a single FormSpec tag name.
|
|
132
|
+
*
|
|
133
|
+
* @public
|
|
134
|
+
*/
|
|
135
|
+
export declare function getHoverForTag(tagName: string, extensions?: readonly ExtensionDefinition[]): Hover | null;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Retrieves canonical FormSpec diagnostics for the current document revision
|
|
139
|
+
* from the plugin transport. Returns `null` when the transport is missing,
|
|
140
|
+
* stale, or invalid.
|
|
141
|
+
*
|
|
142
|
+
* @public
|
|
143
|
+
*/
|
|
144
|
+
export declare function getPluginDiagnosticsForDocument(workspaceRoots: readonly string[], filePath: string, documentText: string, timeoutMs?: number): Promise<readonly FormSpecAnalysisDiagnostic[] | null>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Converts canonical FormSpec diagnostics into LSP diagnostics.
|
|
148
|
+
*
|
|
149
|
+
* Downstream consumers that want complete white-label control can ignore this
|
|
150
|
+
* helper and render their own messages from `code` + `data`.
|
|
151
|
+
*
|
|
152
|
+
* @public
|
|
153
|
+
*/
|
|
154
|
+
export declare function toLspDiagnostics(document: TextDocument, diagnostics: readonly FormSpecAnalysisDiagnostic[], options?: ToLspDiagnosticsOptions): Diagnostic[];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Options for converting canonical FormSpec diagnostics into LSP diagnostics.
|
|
158
|
+
*
|
|
159
|
+
* @public
|
|
160
|
+
*/
|
|
161
|
+
export declare interface ToLspDiagnosticsOptions {
|
|
162
|
+
/** Source label shown by LSP clients. Defaults to `formspec`. */
|
|
163
|
+
readonly source?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export { }
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* \@formspec/language-server
|
|
3
|
+
*
|
|
4
|
+
* Language server for FormSpec — provides completions, hover documentation,
|
|
5
|
+
* and go-to-definition for FormSpec JSDoc constraint tags (`@Minimum`,
|
|
6
|
+
* `@Maximum`, `@Pattern`, etc.) in TypeScript files.
|
|
7
|
+
*
|
|
8
|
+
* This package implements the Language Server Protocol (LSP) using the
|
|
9
|
+
* `vscode-languageserver` library. Cheap syntax-local behaviors stay in the
|
|
10
|
+
* LSP process, while TypeScript-project-aware semantics are supplied by
|
|
11
|
+
* `@formspec/ts-plugin` over a local manifest + IPC transport.
|
|
12
|
+
*
|
|
13
|
+
* The packaged server acts as a reference implementation over the composable
|
|
14
|
+
* completion, hover, and diagnostics helpers exported from this package.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { createServer } from '@formspec/language-server';
|
|
19
|
+
*
|
|
20
|
+
* const connection = createServer();
|
|
21
|
+
* connection.listen();
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { CompletionItem } from 'vscode-languageserver/node.js';
|
|
28
|
+
import { Connection } from 'vscode-languageserver/node.js';
|
|
29
|
+
import { Diagnostic } from 'vscode-languageserver/node.js';
|
|
30
|
+
import type { ExtensionDefinition } from '@formspec/core';
|
|
31
|
+
import type { Hover } from 'vscode-languageserver/node.js';
|
|
32
|
+
import type { Location } from 'vscode-languageserver/node.js';
|
|
33
|
+
import type { TextDocument } from 'vscode-languageserver-textdocument';
|
|
34
|
+
|
|
35
|
+
/** @public */
|
|
36
|
+
export declare interface CommentSpan {
|
|
37
|
+
readonly start: number;
|
|
38
|
+
readonly end: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates and configures the FormSpec language server connection.
|
|
43
|
+
*
|
|
44
|
+
* Registers LSP capability handlers and returns the connection.
|
|
45
|
+
* Call `connection.listen()` to start accepting messages.
|
|
46
|
+
*
|
|
47
|
+
* @returns The configured LSP connection (not yet listening)
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export declare function createServer(options?: CreateServerOptions): Connection;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Public configuration for constructing the FormSpec language server.
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
export declare interface CreateServerOptions {
|
|
58
|
+
/** Optional extension definitions whose custom tags should be surfaced by tooling. */
|
|
59
|
+
readonly extensions?: readonly ExtensionDefinition[];
|
|
60
|
+
/** Optional workspace roots to use before initialize() provides them. */
|
|
61
|
+
readonly workspaceRoots?: readonly string[];
|
|
62
|
+
/** Set to false to disable tsserver-plugin semantic enrichment. */
|
|
63
|
+
readonly usePluginTransport?: boolean;
|
|
64
|
+
/** IPC timeout, in milliseconds, for semantic plugin requests. */
|
|
65
|
+
readonly pluginQueryTimeoutMs?: number;
|
|
66
|
+
/** Optional diagnostics publishing mode for the packaged reference LSP. */
|
|
67
|
+
readonly diagnosticsMode?: "off" | "plugin";
|
|
68
|
+
/** Source label to use when publishing plugin-derived diagnostics. */
|
|
69
|
+
readonly diagnosticSource?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* File-local diagnostic derived from comment parsing or semantic analysis.
|
|
74
|
+
*
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
77
|
+
export declare interface FormSpecAnalysisDiagnostic {
|
|
78
|
+
readonly code: string;
|
|
79
|
+
readonly category: FormSpecAnalysisDiagnosticCategory;
|
|
80
|
+
readonly message: string;
|
|
81
|
+
readonly range: CommentSpan;
|
|
82
|
+
readonly severity: "error" | "warning" | "info";
|
|
83
|
+
readonly relatedLocations: readonly FormSpecAnalysisDiagnosticLocation[];
|
|
84
|
+
readonly data: Record<string, FormSpecAnalysisDiagnosticDataValue>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Machine-readable diagnostic category used by FormSpec tooling surfaces.
|
|
89
|
+
*
|
|
90
|
+
* @public
|
|
91
|
+
*/
|
|
92
|
+
export declare type FormSpecAnalysisDiagnosticCategory = "tag-recognition" | "value-parsing" | "type-compatibility" | "target-resolution" | "constraint-validation" | "infrastructure";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Primitive structured values carried in diagnostic facts for white-label
|
|
96
|
+
* downstream rendering.
|
|
97
|
+
*
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
export declare type FormSpecAnalysisDiagnosticDataValue = string | number | boolean | readonly string[] | readonly number[] | readonly boolean[];
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Additional source location associated with a diagnostic.
|
|
104
|
+
*
|
|
105
|
+
* @public
|
|
106
|
+
*/
|
|
107
|
+
export declare interface FormSpecAnalysisDiagnosticLocation {
|
|
108
|
+
readonly filePath: string;
|
|
109
|
+
readonly range: CommentSpan;
|
|
110
|
+
readonly message?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns the full set of tag-name completions currently known to FormSpec.
|
|
115
|
+
*
|
|
116
|
+
* @public
|
|
117
|
+
*/
|
|
118
|
+
export declare function getCompletionItems(extensions?: readonly ExtensionDefinition[]): CompletionItem[];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Returns the definition location for a symbol at the given position.
|
|
122
|
+
*
|
|
123
|
+
* Always returns `null` in this stub implementation.
|
|
124
|
+
*
|
|
125
|
+
* @returns `null` — not yet implemented
|
|
126
|
+
* @public
|
|
127
|
+
*/
|
|
128
|
+
export declare function getDefinition(): Location | null;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns hover content for a single FormSpec tag name.
|
|
132
|
+
*
|
|
133
|
+
* @public
|
|
134
|
+
*/
|
|
135
|
+
export declare function getHoverForTag(tagName: string, extensions?: readonly ExtensionDefinition[]): Hover | null;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Retrieves canonical FormSpec diagnostics for the current document revision
|
|
139
|
+
* from the plugin transport. Returns `null` when the transport is missing,
|
|
140
|
+
* stale, or invalid.
|
|
141
|
+
*
|
|
142
|
+
* @public
|
|
143
|
+
*/
|
|
144
|
+
export declare function getPluginDiagnosticsForDocument(workspaceRoots: readonly string[], filePath: string, documentText: string, timeoutMs?: number): Promise<readonly FormSpecAnalysisDiagnostic[] | null>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Converts canonical FormSpec diagnostics into LSP diagnostics.
|
|
148
|
+
*
|
|
149
|
+
* Downstream consumers that want complete white-label control can ignore this
|
|
150
|
+
* helper and render their own messages from `code` + `data`.
|
|
151
|
+
*
|
|
152
|
+
* @public
|
|
153
|
+
*/
|
|
154
|
+
export declare function toLspDiagnostics(document: TextDocument, diagnostics: readonly FormSpecAnalysisDiagnostic[], options?: ToLspDiagnosticsOptions): Diagnostic[];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Options for converting canonical FormSpec diagnostics into LSP diagnostics.
|
|
158
|
+
*
|
|
159
|
+
* @public
|
|
160
|
+
*/
|
|
161
|
+
export declare interface ToLspDiagnosticsOptions {
|
|
162
|
+
/** Source label shown by LSP clients. Defaults to `formspec`. */
|
|
163
|
+
readonly source?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export { }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-client.d.ts","sourceRoot":"","sources":["../src/plugin-client.ts"],"names":[],"mappings":"AAIA,OAAO,
|
|
1
|
+
{"version":3,"file":"plugin-client.d.ts","sourceRoot":"","sources":["../src/plugin-client.ts"],"names":[],"mappings":"AAIA,OAAO,EAKL,KAAK,0BAA0B,EAE/B,KAAK,mCAAmC,EACxC,KAAK,2BAA2B,EAGjC,MAAM,6BAA6B,CAAC;AAyGrC,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM9D;AAqBD,wBAAsB,qCAAqC,CACzD,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,SAAS,SAAkC,GAC1C,OAAO,CAAC,mCAAmC,GAAG,IAAI,CAAC,CAiBrD;AAED,wBAAsB,yBAAyB,CAC7C,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,SAAS,SAAkC,GAC1C,OAAO,CAAC,2BAA2B,GAAG,IAAI,CAAC,CAiB7C;AAED;;;;;;GAMG;AACH,wBAAsB,+BAA+B,CACnD,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,SAAS,SAAkC,GAC1C,OAAO,CAAC,SAAS,0BAA0B,EAAE,GAAG,IAAI,CAAC,CAkBvD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formspec/language-server",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.23",
|
|
4
4
|
"description": "Language server for FormSpec — completions, hover, and go-to-definition for JSDoc constraint tags",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"vscode-languageserver": "^9.0.1",
|
|
22
22
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
23
|
-
"@formspec/analysis": "0.1.0-alpha.
|
|
24
|
-
"@formspec/core": "0.1.0-alpha.
|
|
23
|
+
"@formspec/analysis": "0.1.0-alpha.23",
|
|
24
|
+
"@formspec/core": "0.1.0-alpha.23"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"vitest": "^3.0.0"
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
],
|
|
39
39
|
"license": "UNLICENSED",
|
|
40
40
|
"scripts": {
|
|
41
|
-
"build": "tsup && tsc --emitDeclarationOnly && api-extractor
|
|
41
|
+
"build": "tsup && tsc --emitDeclarationOnly && pnpm run api-extractor:local",
|
|
42
42
|
"clean": "rm -rf dist temp",
|
|
43
43
|
"typecheck": "tsc --noEmit",
|
|
44
44
|
"test": "vitest run",
|
|
45
|
-
"api-extractor": "api-extractor run",
|
|
46
|
-
"api-extractor:local": "api-extractor run --local",
|
|
47
|
-
"api-documenter": "api-documenter markdown -i temp -o docs"
|
|
45
|
+
"api-extractor": "api-extractor run && node ../../scripts/normalize-generated-markdown.mjs api-report",
|
|
46
|
+
"api-extractor:local": "api-extractor run --local && node ../../scripts/normalize-generated-markdown.mjs api-report",
|
|
47
|
+
"api-documenter": "api-documenter markdown -i temp -o docs && node ../../scripts/normalize-generated-markdown.mjs docs"
|
|
48
48
|
}
|
|
49
49
|
}
|