@formspec/ts-plugin 0.1.0-alpha.20 → 0.1.0-alpha.21
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/README.md +8 -0
- package/dist/__tests__/handle-query.test.d.ts +2 -0
- package/dist/__tests__/handle-query.test.d.ts.map +1 -0
- package/dist/__tests__/helpers.d.ts +11 -0
- package/dist/__tests__/helpers.d.ts.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/index.cjs +291 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +290 -105
- package/dist/index.js.map +1 -1
- package/dist/service.d.ts +18 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/service.ts","../src/workspace.ts"],"sourcesContent":["import type * as tsServer from \"typescript/lib/tsserverlibrary.js\";\nimport { createLanguageServiceProxy, FormSpecPluginService } from \"./service.js\";\n\ninterface ServiceEntry {\n readonly service: FormSpecPluginService;\n referenceCount: number;\n}\n\nconst services = new Map<string, ServiceEntry>();\n\nfunction formatPluginError(error: unknown): string {\n return error instanceof Error ? (error.stack ?? error.message) : String(error);\n}\n\nfunction getOrCreateService(\n info: tsServer.server.PluginCreateInfo,\n typescriptVersion: string\n): FormSpecPluginService {\n const workspaceRoot = info.project.getCurrentDirectory();\n const existing = services.get(workspaceRoot);\n if (existing !== undefined) {\n existing.referenceCount += 1;\n attachProjectCloseHandler(info, workspaceRoot, existing);\n return existing.service;\n }\n\n const service = new FormSpecPluginService({\n workspaceRoot,\n typescriptVersion,\n getProgram: () => info.languageService.getProgram(),\n logger: info.project.projectService.logger,\n });\n\n const serviceEntry: ServiceEntry = {\n service,\n referenceCount: 1,\n };\n attachProjectCloseHandler(info, workspaceRoot, serviceEntry);\n\n service.start().catch((error: unknown) => {\n info.project.projectService.logger.info(\n `[FormSpec] Plugin service failed to start for ${workspaceRoot}: ${formatPluginError(error)}`\n );\n services.delete(workspaceRoot);\n });\n services.set(workspaceRoot, serviceEntry);\n return service;\n}\n\nfunction attachProjectCloseHandler(\n info: tsServer.server.PluginCreateInfo,\n workspaceRoot: string,\n serviceEntry: ServiceEntry\n): void {\n const originalClose = info.project.close.bind(info.project);\n let closed = false;\n\n info.project.close = () => {\n if (closed) {\n originalClose();\n return;\n }\n\n closed = true;\n serviceEntry.referenceCount -= 1;\n if (serviceEntry.referenceCount <= 0) {\n services.delete(workspaceRoot);\n void serviceEntry.service.stop().catch((error: unknown) => {\n info.project.projectService.logger.info(\n `[FormSpec] Failed to stop plugin service for ${workspaceRoot}: ${formatPluginError(error)}`\n );\n });\n }\n originalClose();\n };\n}\n\n/**\n * Initializes the FormSpec TypeScript language service plugin.\n *\n * @public\n */\nexport function init(modules: {\n readonly typescript: typeof tsServer;\n}): tsServer.server.PluginModule {\n const typescriptVersion = modules.typescript.version;\n return {\n create(info) {\n const service = getOrCreateService(info, typescriptVersion);\n return createLanguageServiceProxy(info.languageService, service);\n },\n };\n}\n","import fs from \"node:fs/promises\";\nimport net from \"node:net\";\nimport * as ts from \"typescript\";\nimport {\n FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n buildFormSpecAnalysisFileSnapshot,\n computeFormSpecTextHash,\n findDeclarationForCommentOffset,\n getSubjectType,\n getCommentHoverInfoAtOffset,\n getSemanticCommentCompletionContextAtOffset,\n isFormSpecSemanticQuery,\n resolveDeclarationPlacement,\n serializeCompletionContext,\n serializeHoverInfo,\n type BuildFormSpecAnalysisFileSnapshotOptions,\n type FormSpecAnalysisFileSnapshot,\n type FormSpecAnalysisManifest,\n type FormSpecSemanticQuery,\n type FormSpecSemanticResponse,\n} from \"@formspec/analysis\";\nimport {\n createFormSpecAnalysisManifest,\n getFormSpecWorkspaceRuntimePaths,\n type FormSpecWorkspaceRuntimePaths,\n} from \"./workspace.js\";\n\ninterface LoggerLike {\n info(message: string): void;\n}\n\nexport interface FormSpecPluginServiceOptions {\n readonly workspaceRoot: string;\n readonly typescriptVersion: string;\n readonly getProgram: () => ts.Program | undefined;\n readonly logger?: LoggerLike;\n readonly snapshotDebounceMs?: number;\n readonly now?: () => Date;\n}\n\ninterface CachedFileSnapshot {\n readonly sourceHash: string;\n readonly snapshot: FormSpecAnalysisFileSnapshot;\n}\n\nexport class FormSpecPluginService {\n private readonly manifest: FormSpecAnalysisManifest;\n private readonly runtimePaths: FormSpecWorkspaceRuntimePaths;\n private readonly snapshotCache = new Map<string, CachedFileSnapshot>();\n private readonly refreshTimers = new Map<string, NodeJS.Timeout>();\n private server: net.Server | null = null;\n\n public constructor(private readonly options: FormSpecPluginServiceOptions) {\n this.runtimePaths = getFormSpecWorkspaceRuntimePaths(options.workspaceRoot);\n this.manifest = createFormSpecAnalysisManifest(\n options.workspaceRoot,\n options.typescriptVersion,\n Date.now()\n );\n }\n\n public getManifest(): FormSpecAnalysisManifest {\n return this.manifest;\n }\n\n public async start(): Promise<void> {\n if (this.server !== null) {\n return;\n }\n\n await fs.mkdir(this.runtimePaths.runtimeDirectory, { recursive: true });\n if (this.runtimePaths.endpoint.kind === \"unix-socket\") {\n await fs.rm(this.runtimePaths.endpoint.address, { force: true });\n }\n\n this.server = net.createServer((socket) => {\n let buffer = \"\";\n socket.setEncoding(\"utf8\");\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 const remaining = buffer.slice(newlineIndex + 1);\n if (remaining.trim().length > 0) {\n this.options.logger?.info(\n `[FormSpec] Ignoring extra semantic query payload data for ${this.runtimePaths.workspaceRoot}`\n );\n }\n buffer = remaining;\n // The FormSpec IPC transport is intentionally one-request-per-connection.\n this.respondToSocket(socket, payload);\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n const handleError = (error: Error) => {\n reject(error);\n };\n this.server?.once(\"error\", handleError);\n this.server?.listen(this.runtimePaths.endpoint.address, () => {\n this.server?.off(\"error\", handleError);\n resolve();\n });\n });\n\n await this.writeManifest();\n }\n\n public async stop(): Promise<void> {\n for (const timer of this.refreshTimers.values()) {\n clearTimeout(timer);\n }\n this.refreshTimers.clear();\n this.snapshotCache.clear();\n\n const server = this.server;\n this.server = null;\n if (server?.listening === true) {\n await new Promise<void>((resolve, reject) => {\n server.close((error) => {\n if (error === undefined) {\n resolve();\n return;\n }\n reject(error);\n });\n });\n }\n\n await this.cleanupRuntimeArtifacts();\n }\n\n public scheduleSnapshotRefresh(filePath: string): void {\n const existing = this.refreshTimers.get(filePath);\n if (existing !== undefined) {\n clearTimeout(existing);\n }\n\n const timer = setTimeout(() => {\n try {\n this.getFileSnapshot(filePath);\n } catch (error: unknown) {\n this.options.logger?.info(\n `[FormSpec] Failed to refresh semantic snapshot for ${filePath}: ${String(error)}`\n );\n }\n this.refreshTimers.delete(filePath);\n }, this.options.snapshotDebounceMs ?? 250);\n\n this.refreshTimers.set(filePath, timer);\n }\n\n public handleQuery(query: FormSpecSemanticQuery): FormSpecSemanticResponse {\n switch (query.kind) {\n case \"health\":\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"health\",\n manifest: this.manifest,\n };\n case \"completion\": {\n const environment = this.getSourceEnvironment(query.filePath);\n if (environment === null) {\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"error\",\n error: `Unable to resolve TypeScript source file for ${query.filePath}`,\n };\n }\n\n const declaration = findDeclarationForCommentOffset(environment.sourceFile, query.offset);\n const placement = declaration === null ? null : resolveDeclarationPlacement(declaration);\n const subjectType =\n declaration === null ? undefined : getSubjectType(declaration, environment.checker);\n const context = getSemanticCommentCompletionContextAtOffset(\n environment.sourceFile.text,\n query.offset,\n {\n checker: environment.checker,\n ...(placement === null ? {} : { placement }),\n ...(subjectType === undefined ? {} : { subjectType }),\n }\n );\n\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"completion\",\n sourceHash: computeFormSpecTextHash(environment.sourceFile.text),\n context: serializeCompletionContext(context),\n };\n }\n case \"hover\": {\n const environment = this.getSourceEnvironment(query.filePath);\n if (environment === null) {\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"error\",\n error: `Unable to resolve TypeScript source file for ${query.filePath}`,\n };\n }\n\n const declaration = findDeclarationForCommentOffset(environment.sourceFile, query.offset);\n const placement = declaration === null ? null : resolveDeclarationPlacement(declaration);\n const subjectType =\n declaration === null ? undefined : getSubjectType(declaration, environment.checker);\n const hover = getCommentHoverInfoAtOffset(environment.sourceFile.text, query.offset, {\n checker: environment.checker,\n ...(placement === null ? {} : { placement }),\n ...(subjectType === undefined ? {} : { subjectType }),\n });\n\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"hover\",\n sourceHash: computeFormSpecTextHash(environment.sourceFile.text),\n hover: serializeHoverInfo(hover),\n };\n }\n case \"diagnostics\": {\n const snapshot = this.getFileSnapshot(query.filePath);\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"diagnostics\",\n sourceHash: snapshot.sourceHash,\n diagnostics: snapshot.diagnostics,\n };\n }\n case \"file-snapshot\":\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"file-snapshot\",\n snapshot: this.getFileSnapshot(query.filePath),\n };\n default: {\n throw new Error(`Unhandled semantic query: ${JSON.stringify(query)}`);\n }\n }\n }\n\n private respondToSocket(socket: net.Socket, payload: string): void {\n try {\n const query = JSON.parse(payload) as unknown;\n if (!isFormSpecSemanticQuery(query)) {\n throw new Error(\"Invalid FormSpec semantic query payload\");\n }\n const response = this.handleQuery(query);\n socket.end(`${JSON.stringify(response)}\\n`);\n } catch (error) {\n socket.end(\n `${JSON.stringify({\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"error\",\n error: error instanceof Error ? error.message : String(error),\n } satisfies FormSpecSemanticResponse)}\\n`\n );\n }\n }\n\n private async writeManifest(): Promise<void> {\n const tempManifestPath = `${this.runtimePaths.manifestPath}.tmp`;\n await fs.writeFile(tempManifestPath, `${JSON.stringify(this.manifest, null, 2)}\\n`, \"utf8\");\n await fs.rename(tempManifestPath, this.runtimePaths.manifestPath);\n }\n\n private async cleanupRuntimeArtifacts(): Promise<void> {\n await fs.rm(this.runtimePaths.manifestPath, { force: true });\n if (this.runtimePaths.endpoint.kind === \"unix-socket\") {\n await fs.rm(this.runtimePaths.endpoint.address, { force: true });\n }\n }\n\n private getSourceEnvironment(filePath: string): {\n readonly sourceFile: ts.SourceFile;\n readonly checker: ts.TypeChecker;\n } | null {\n const program = this.options.getProgram();\n if (program === undefined) {\n return null;\n }\n\n const sourceFile = program.getSourceFile(filePath);\n if (sourceFile === undefined) {\n return null;\n }\n\n return {\n sourceFile,\n checker: program.getTypeChecker(),\n };\n }\n\n private getFileSnapshot(filePath: string): FormSpecAnalysisFileSnapshot {\n const environment = this.getSourceEnvironment(filePath);\n if (environment === null) {\n return {\n filePath,\n sourceHash: \"\",\n generatedAt: this.getNow().toISOString(),\n comments: [],\n diagnostics: [\n {\n code: \"MISSING_SOURCE_FILE\",\n message: `Unable to resolve TypeScript source file for ${filePath}`,\n range: { start: 0, end: 0 },\n severity: \"warning\",\n },\n ],\n };\n }\n\n const sourceHash = computeFormSpecTextHash(environment.sourceFile.text);\n const cached = this.snapshotCache.get(filePath);\n if (cached?.sourceHash === sourceHash) {\n return cached.snapshot;\n }\n\n const snapshot = buildFormSpecAnalysisFileSnapshot(environment.sourceFile, {\n checker: environment.checker,\n } satisfies BuildFormSpecAnalysisFileSnapshotOptions);\n this.snapshotCache.set(filePath, {\n sourceHash,\n snapshot,\n });\n return snapshot;\n }\n\n private getNow(): Date {\n return this.options.now?.() ?? new Date();\n }\n}\n\nexport function createLanguageServiceProxy(\n languageService: ts.LanguageService,\n semanticService: FormSpecPluginService\n): ts.LanguageService {\n const wrapWithSnapshotRefresh = <Args extends readonly unknown[], Result>(\n fn: (fileName: string, ...args: Args) => Result\n ) => {\n return (fileName: string, ...args: Args): Result => {\n semanticService.scheduleSnapshotRefresh(fileName);\n return fn(fileName, ...args);\n };\n };\n\n // The plugin keeps semantic snapshots fresh for the lightweight LSP. The\n // underlying tsserver results still come from the original language service.\n const getSemanticDiagnostics = wrapWithSnapshotRefresh((fileName) =>\n languageService.getSemanticDiagnostics(fileName)\n );\n\n const getCompletionsAtPosition = wrapWithSnapshotRefresh(\n (fileName: string, position: number, options: ts.GetCompletionsAtPositionOptions | undefined) =>\n languageService.getCompletionsAtPosition(fileName, position, options)\n );\n\n const getQuickInfoAtPosition = wrapWithSnapshotRefresh((fileName, position: number) =>\n languageService.getQuickInfoAtPosition(fileName, position)\n );\n\n return new Proxy(languageService, {\n get(target, property, receiver) {\n switch (property) {\n case \"getSemanticDiagnostics\":\n return getSemanticDiagnostics;\n case \"getCompletionsAtPosition\":\n return getCompletionsAtPosition;\n case \"getQuickInfoAtPosition\":\n return getQuickInfoAtPosition;\n default:\n return Reflect.get(target, property, receiver) as unknown;\n }\n },\n });\n}\n","import os from \"node:os\";\nimport path from \"node:path\";\nimport {\n FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n FORMSPEC_ANALYSIS_SCHEMA_VERSION,\n getFormSpecManifestPath,\n getFormSpecWorkspaceId,\n getFormSpecWorkspaceRuntimeDirectory,\n type FormSpecAnalysisManifest,\n type FormSpecIpcEndpoint,\n} from \"@formspec/analysis\";\n\nexport interface FormSpecWorkspaceRuntimePaths {\n readonly workspaceRoot: string;\n readonly workspaceId: string;\n readonly runtimeDirectory: string;\n readonly manifestPath: string;\n readonly endpoint: FormSpecIpcEndpoint;\n}\n\nexport function getFormSpecWorkspaceRuntimePaths(\n workspaceRoot: string,\n platform = process.platform,\n userScope = getFormSpecUserScope()\n): FormSpecWorkspaceRuntimePaths {\n const workspaceId = getFormSpecWorkspaceId(workspaceRoot);\n const runtimeDirectory = getFormSpecWorkspaceRuntimeDirectory(workspaceRoot);\n const sanitizedUserScope = sanitizeScopeSegment(userScope);\n const endpoint: FormSpecIpcEndpoint =\n platform === \"win32\"\n ? {\n kind: \"windows-pipe\",\n address: `\\\\\\\\.\\\\pipe\\\\formspec-${sanitizedUserScope}-${workspaceId}`,\n }\n : {\n kind: \"unix-socket\",\n address: path.join(os.tmpdir(), `formspec-${sanitizedUserScope}-${workspaceId}.sock`),\n };\n\n return {\n workspaceRoot,\n workspaceId,\n runtimeDirectory,\n manifestPath: getFormSpecManifestPath(workspaceRoot),\n endpoint,\n };\n}\n\nexport function createFormSpecAnalysisManifest(\n workspaceRoot: string,\n typescriptVersion: string,\n generation: number,\n extensionFingerprint = \"builtin\"\n): FormSpecAnalysisManifest {\n const paths = getFormSpecWorkspaceRuntimePaths(workspaceRoot);\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n analysisSchemaVersion: FORMSPEC_ANALYSIS_SCHEMA_VERSION,\n workspaceRoot,\n workspaceId: paths.workspaceId,\n endpoint: paths.endpoint,\n typescriptVersion,\n extensionFingerprint,\n generation,\n updatedAt: new Date().toISOString(),\n };\n}\n\nfunction getFormSpecUserScope(): string {\n try {\n return sanitizeScopeSegment(os.userInfo().username);\n } catch {\n return sanitizeScopeSegment(\n process.env[\"USER\"] ?? process.env[\"USERNAME\"] ?? process.env[\"LOGNAME\"] ?? \"formspec\"\n );\n }\n}\n\nfunction sanitizeScopeSegment(value: string): string {\n const trimmed = value.trim().toLowerCase();\n const sanitized = trimmed.replace(/[^a-z0-9_-]+/gu, \"-\").replace(/-+/gu, \"-\");\n return sanitized.length > 0 ? sanitized : \"formspec\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,sBAAe;AACf,sBAAgB;AAChB,SAAoB;AACpB,IAAAA,mBAiBO;;;ACpBP,qBAAe;AACf,uBAAiB;AACjB,sBAQO;AAUA,SAAS,iCACd,eACA,WAAW,QAAQ,UACnB,YAAY,qBAAqB,GACF;AAC/B,QAAM,kBAAc,wCAAuB,aAAa;AACxD,QAAM,uBAAmB,sDAAqC,aAAa;AAC3E,QAAM,qBAAqB,qBAAqB,SAAS;AACzD,QAAM,WACJ,aAAa,UACT;AAAA,IACE,MAAM;AAAA,IACN,SAAS,yBAAyB,kBAAkB,IAAI,WAAW;AAAA,EACrE,IACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS,iBAAAC,QAAK,KAAK,eAAAC,QAAG,OAAO,GAAG,YAAY,kBAAkB,IAAI,WAAW,OAAO;AAAA,EACtF;AAEN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAc,yCAAwB,aAAa;AAAA,IACnD;AAAA,EACF;AACF;AAEO,SAAS,+BACd,eACA,mBACA,YACA,uBAAuB,WACG;AAC1B,QAAM,QAAQ,iCAAiC,aAAa;AAC5D,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,uBAAuB;AAAA,IACvB;AAAA,IACA,aAAa,MAAM;AAAA,IACnB,UAAU,MAAM;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AACF;AAEA,SAAS,uBAA+B;AACtC,MAAI;AACF,WAAO,qBAAqB,eAAAA,QAAG,SAAS,EAAE,QAAQ;AAAA,EACpD,QAAQ;AACN,WAAO;AAAA,MACL,QAAQ,IAAI,MAAM,KAAK,QAAQ,IAAI,UAAU,KAAK,QAAQ,IAAI,SAAS,KAAK;AAAA,IAC9E;AAAA,EACF;AACF;AAEA,SAAS,qBAAqB,OAAuB;AACnD,QAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AACzC,QAAM,YAAY,QAAQ,QAAQ,kBAAkB,GAAG,EAAE,QAAQ,QAAQ,GAAG;AAC5E,SAAO,UAAU,SAAS,IAAI,YAAY;AAC5C;;;ADrCO,IAAM,wBAAN,MAA4B;AAAA,EAO1B,YAA6B,SAAuC;AAAvC;AAClC,SAAK,eAAe,iCAAiC,QAAQ,aAAa;AAC1E,SAAK,WAAW;AAAA,MACd,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,KAAK,IAAI;AAAA,IACX;AAAA,EACF;AAAA,EAbiB;AAAA,EACA;AAAA,EACA,gBAAgB,oBAAI,IAAgC;AAAA,EACpD,gBAAgB,oBAAI,IAA4B;AAAA,EACzD,SAA4B;AAAA,EAW7B,cAAwC;AAC7C,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAa,QAAuB;AAClC,QAAI,KAAK,WAAW,MAAM;AACxB;AAAA,IACF;AAEA,UAAM,gBAAAC,QAAG,MAAM,KAAK,aAAa,kBAAkB,EAAE,WAAW,KAAK,CAAC;AACtE,QAAI,KAAK,aAAa,SAAS,SAAS,eAAe;AACrD,YAAM,gBAAAA,QAAG,GAAG,KAAK,aAAa,SAAS,SAAS,EAAE,OAAO,KAAK,CAAC;AAAA,IACjE;AAEA,SAAK,SAAS,gBAAAC,QAAI,aAAa,CAAC,WAAW;AACzC,UAAI,SAAS;AACb,aAAO,YAAY,MAAM;AACzB,aAAO,GAAG,QAAQ,CAAC,UAAU;AAC3B,kBAAU,OAAO,KAAK;AACtB,cAAM,eAAe,OAAO,QAAQ,IAAI;AACxC,YAAI,eAAe,GAAG;AACpB;AAAA,QACF;AAEA,cAAM,UAAU,OAAO,MAAM,GAAG,YAAY;AAC5C,cAAM,YAAY,OAAO,MAAM,eAAe,CAAC;AAC/C,YAAI,UAAU,KAAK,EAAE,SAAS,GAAG;AAC/B,eAAK,QAAQ,QAAQ;AAAA,YACnB,6DAA6D,KAAK,aAAa,aAAa;AAAA,UAC9F;AAAA,QACF;AACA,iBAAS;AAET,aAAK,gBAAgB,QAAQ,OAAO;AAAA,MACtC,CAAC;AAAA,IACH,CAAC;AAED,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,cAAc,CAAC,UAAiB;AACpC,eAAO,KAAK;AAAA,MACd;AACA,WAAK,QAAQ,KAAK,SAAS,WAAW;AACtC,WAAK,QAAQ,OAAO,KAAK,aAAa,SAAS,SAAS,MAAM;AAC5D,aAAK,QAAQ,IAAI,SAAS,WAAW;AACrC,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAED,UAAM,KAAK,cAAc;AAAA,EAC3B;AAAA,EAEA,MAAa,OAAsB;AACjC,eAAW,SAAS,KAAK,cAAc,OAAO,GAAG;AAC/C,mBAAa,KAAK;AAAA,IACpB;AACA,SAAK,cAAc,MAAM;AACzB,SAAK,cAAc,MAAM;AAEzB,UAAM,SAAS,KAAK;AACpB,SAAK,SAAS;AACd,QAAI,QAAQ,cAAc,MAAM;AAC9B,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,eAAO,MAAM,CAAC,UAAU;AACtB,cAAI,UAAU,QAAW;AACvB,oBAAQ;AACR;AAAA,UACF;AACA,iBAAO,KAAK;AAAA,QACd,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAEA,UAAM,KAAK,wBAAwB;AAAA,EACrC;AAAA,EAEO,wBAAwB,UAAwB;AACrD,UAAM,WAAW,KAAK,cAAc,IAAI,QAAQ;AAChD,QAAI,aAAa,QAAW;AAC1B,mBAAa,QAAQ;AAAA,IACvB;AAEA,UAAM,QAAQ,WAAW,MAAM;AAC7B,UAAI;AACF,aAAK,gBAAgB,QAAQ;AAAA,MAC/B,SAAS,OAAgB;AACvB,aAAK,QAAQ,QAAQ;AAAA,UACnB,sDAAsD,QAAQ,KAAK,OAAO,KAAK,CAAC;AAAA,QAClF;AAAA,MACF;AACA,WAAK,cAAc,OAAO,QAAQ;AAAA,IACpC,GAAG,KAAK,QAAQ,sBAAsB,GAAG;AAEzC,SAAK,cAAc,IAAI,UAAU,KAAK;AAAA,EACxC;AAAA,EAEO,YAAY,OAAwD;AACzE,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH,eAAO;AAAA,UACL,iBAAiB;AAAA,UACjB,MAAM;AAAA,UACN,UAAU,KAAK;AAAA,QACjB;AAAA,MACF,KAAK,cAAc;AACjB,cAAM,cAAc,KAAK,qBAAqB,MAAM,QAAQ;AAC5D,YAAI,gBAAgB,MAAM;AACxB,iBAAO;AAAA,YACL,iBAAiB;AAAA,YACjB,MAAM;AAAA,YACN,OAAO,gDAAgD,MAAM,QAAQ;AAAA,UACvE;AAAA,QACF;AAEA,cAAM,kBAAc,kDAAgC,YAAY,YAAY,MAAM,MAAM;AACxF,cAAM,YAAY,gBAAgB,OAAO,WAAO,8CAA4B,WAAW;AACvF,cAAM,cACJ,gBAAgB,OAAO,aAAY,iCAAe,aAAa,YAAY,OAAO;AACpF,cAAM,cAAU;AAAA,UACd,YAAY,WAAW;AAAA,UACvB,MAAM;AAAA,UACN;AAAA,YACE,SAAS,YAAY;AAAA,YACrB,GAAI,cAAc,OAAO,CAAC,IAAI,EAAE,UAAU;AAAA,YAC1C,GAAI,gBAAgB,SAAY,CAAC,IAAI,EAAE,YAAY;AAAA,UACrD;AAAA,QACF;AAEA,eAAO;AAAA,UACL,iBAAiB;AAAA,UACjB,MAAM;AAAA,UACN,gBAAY,0CAAwB,YAAY,WAAW,IAAI;AAAA,UAC/D,aAAS,6CAA2B,OAAO;AAAA,QAC7C;AAAA,MACF;AAAA,MACA,KAAK,SAAS;AACZ,cAAM,cAAc,KAAK,qBAAqB,MAAM,QAAQ;AAC5D,YAAI,gBAAgB,MAAM;AACxB,iBAAO;AAAA,YACL,iBAAiB;AAAA,YACjB,MAAM;AAAA,YACN,OAAO,gDAAgD,MAAM,QAAQ;AAAA,UACvE;AAAA,QACF;AAEA,cAAM,kBAAc,kDAAgC,YAAY,YAAY,MAAM,MAAM;AACxF,cAAM,YAAY,gBAAgB,OAAO,WAAO,8CAA4B,WAAW;AACvF,cAAM,cACJ,gBAAgB,OAAO,aAAY,iCAAe,aAAa,YAAY,OAAO;AACpF,cAAM,YAAQ,8CAA4B,YAAY,WAAW,MAAM,MAAM,QAAQ;AAAA,UACnF,SAAS,YAAY;AAAA,UACrB,GAAI,cAAc,OAAO,CAAC,IAAI,EAAE,UAAU;AAAA,UAC1C,GAAI,gBAAgB,SAAY,CAAC,IAAI,EAAE,YAAY;AAAA,QACrD,CAAC;AAED,eAAO;AAAA,UACL,iBAAiB;AAAA,UACjB,MAAM;AAAA,UACN,gBAAY,0CAAwB,YAAY,WAAW,IAAI;AAAA,UAC/D,WAAO,qCAAmB,KAAK;AAAA,QACjC;AAAA,MACF;AAAA,MACA,KAAK,eAAe;AAClB,cAAM,WAAW,KAAK,gBAAgB,MAAM,QAAQ;AACpD,eAAO;AAAA,UACL,iBAAiB;AAAA,UACjB,MAAM;AAAA,UACN,YAAY,SAAS;AAAA,UACrB,aAAa,SAAS;AAAA,QACxB;AAAA,MACF;AAAA,MACA,KAAK;AACH,eAAO;AAAA,UACL,iBAAiB;AAAA,UACjB,MAAM;AAAA,UACN,UAAU,KAAK,gBAAgB,MAAM,QAAQ;AAAA,QAC/C;AAAA,MACF,SAAS;AACP,cAAM,IAAI,MAAM,6BAA6B,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,gBAAgB,QAAoB,SAAuB;AACjE,QAAI;AACF,YAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,UAAI,KAAC,0CAAwB,KAAK,GAAG;AACnC,cAAM,IAAI,MAAM,yCAAyC;AAAA,MAC3D;AACA,YAAM,WAAW,KAAK,YAAY,KAAK;AACvC,aAAO,IAAI,GAAG,KAAK,UAAU,QAAQ,CAAC;AAAA,CAAI;AAAA,IAC5C,SAAS,OAAO;AACd,aAAO;AAAA,QACL,GAAG,KAAK,UAAU;AAAA,UAChB,iBAAiB;AAAA,UACjB,MAAM;AAAA,UACN,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,QAC9D,CAAoC,CAAC;AAAA;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,gBAA+B;AAC3C,UAAM,mBAAmB,GAAG,KAAK,aAAa,YAAY;AAC1D,UAAM,gBAAAD,QAAG,UAAU,kBAAkB,GAAG,KAAK,UAAU,KAAK,UAAU,MAAM,CAAC,CAAC;AAAA,GAAM,MAAM;AAC1F,UAAM,gBAAAA,QAAG,OAAO,kBAAkB,KAAK,aAAa,YAAY;AAAA,EAClE;AAAA,EAEA,MAAc,0BAAyC;AACrD,UAAM,gBAAAA,QAAG,GAAG,KAAK,aAAa,cAAc,EAAE,OAAO,KAAK,CAAC;AAC3D,QAAI,KAAK,aAAa,SAAS,SAAS,eAAe;AACrD,YAAM,gBAAAA,QAAG,GAAG,KAAK,aAAa,SAAS,SAAS,EAAE,OAAO,KAAK,CAAC;AAAA,IACjE;AAAA,EACF;AAAA,EAEQ,qBAAqB,UAGpB;AACP,UAAM,UAAU,KAAK,QAAQ,WAAW;AACxC,QAAI,YAAY,QAAW;AACzB,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,QAAQ,cAAc,QAAQ;AACjD,QAAI,eAAe,QAAW;AAC5B,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL;AAAA,MACA,SAAS,QAAQ,eAAe;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,gBAAgB,UAAgD;AACtE,UAAM,cAAc,KAAK,qBAAqB,QAAQ;AACtD,QAAI,gBAAgB,MAAM;AACxB,aAAO;AAAA,QACL;AAAA,QACA,YAAY;AAAA,QACZ,aAAa,KAAK,OAAO,EAAE,YAAY;AAAA,QACvC,UAAU,CAAC;AAAA,QACX,aAAa;AAAA,UACX;AAAA,YACE,MAAM;AAAA,YACN,SAAS,gDAAgD,QAAQ;AAAA,YACjE,OAAO,EAAE,OAAO,GAAG,KAAK,EAAE;AAAA,YAC1B,UAAU;AAAA,UACZ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBAAa,0CAAwB,YAAY,WAAW,IAAI;AACtE,UAAM,SAAS,KAAK,cAAc,IAAI,QAAQ;AAC9C,QAAI,QAAQ,eAAe,YAAY;AACrC,aAAO,OAAO;AAAA,IAChB;AAEA,UAAM,eAAW,oDAAkC,YAAY,YAAY;AAAA,MACzE,SAAS,YAAY;AAAA,IACvB,CAAoD;AACpD,SAAK,cAAc,IAAI,UAAU;AAAA,MAC/B;AAAA,MACA;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEQ,SAAe;AACrB,WAAO,KAAK,QAAQ,MAAM,KAAK,oBAAI,KAAK;AAAA,EAC1C;AACF;AAEO,SAAS,2BACd,iBACA,iBACoB;AACpB,QAAM,0BAA0B,CAC9B,OACG;AACH,WAAO,CAAC,aAAqB,SAAuB;AAClD,sBAAgB,wBAAwB,QAAQ;AAChD,aAAO,GAAG,UAAU,GAAG,IAAI;AAAA,IAC7B;AAAA,EACF;AAIA,QAAM,yBAAyB;AAAA,IAAwB,CAAC,aACtD,gBAAgB,uBAAuB,QAAQ;AAAA,EACjD;AAEA,QAAM,2BAA2B;AAAA,IAC/B,CAAC,UAAkB,UAAkB,YACnC,gBAAgB,yBAAyB,UAAU,UAAU,OAAO;AAAA,EACxE;AAEA,QAAM,yBAAyB;AAAA,IAAwB,CAAC,UAAU,aAChE,gBAAgB,uBAAuB,UAAU,QAAQ;AAAA,EAC3D;AAEA,SAAO,IAAI,MAAM,iBAAiB;AAAA,IAChC,IAAI,QAAQ,UAAU,UAAU;AAC9B,cAAQ,UAAU;AAAA,QAChB,KAAK;AACH,iBAAO;AAAA,QACT,KAAK;AACH,iBAAO;AAAA,QACT,KAAK;AACH,iBAAO;AAAA,QACT;AACE,iBAAO,QAAQ,IAAI,QAAQ,UAAU,QAAQ;AAAA,MACjD;AAAA,IACF;AAAA,EACF,CAAC;AACH;;;ADjXA,IAAM,WAAW,oBAAI,IAA0B;AAE/C,SAAS,kBAAkB,OAAwB;AACjD,SAAO,iBAAiB,QAAS,MAAM,SAAS,MAAM,UAAW,OAAO,KAAK;AAC/E;AAEA,SAAS,mBACP,MACA,mBACuB;AACvB,QAAM,gBAAgB,KAAK,QAAQ,oBAAoB;AACvD,QAAM,WAAW,SAAS,IAAI,aAAa;AAC3C,MAAI,aAAa,QAAW;AAC1B,aAAS,kBAAkB;AAC3B,8BAA0B,MAAM,eAAe,QAAQ;AACvD,WAAO,SAAS;AAAA,EAClB;AAEA,QAAM,UAAU,IAAI,sBAAsB;AAAA,IACxC;AAAA,IACA;AAAA,IACA,YAAY,MAAM,KAAK,gBAAgB,WAAW;AAAA,IAClD,QAAQ,KAAK,QAAQ,eAAe;AAAA,EACtC,CAAC;AAED,QAAM,eAA6B;AAAA,IACjC;AAAA,IACA,gBAAgB;AAAA,EAClB;AACA,4BAA0B,MAAM,eAAe,YAAY;AAE3D,UAAQ,MAAM,EAAE,MAAM,CAAC,UAAmB;AACxC,SAAK,QAAQ,eAAe,OAAO;AAAA,MACjC,iDAAiD,aAAa,KAAK,kBAAkB,KAAK,CAAC;AAAA,IAC7F;AACA,aAAS,OAAO,aAAa;AAAA,EAC/B,CAAC;AACD,WAAS,IAAI,eAAe,YAAY;AACxC,SAAO;AACT;AAEA,SAAS,0BACP,MACA,eACA,cACM;AACN,QAAM,gBAAgB,KAAK,QAAQ,MAAM,KAAK,KAAK,OAAO;AAC1D,MAAI,SAAS;AAEb,OAAK,QAAQ,QAAQ,MAAM;AACzB,QAAI,QAAQ;AACV,oBAAc;AACd;AAAA,IACF;AAEA,aAAS;AACT,iBAAa,kBAAkB;AAC/B,QAAI,aAAa,kBAAkB,GAAG;AACpC,eAAS,OAAO,aAAa;AAC7B,WAAK,aAAa,QAAQ,KAAK,EAAE,MAAM,CAAC,UAAmB;AACzD,aAAK,QAAQ,eAAe,OAAO;AAAA,UACjC,gDAAgD,aAAa,KAAK,kBAAkB,KAAK,CAAC;AAAA,QAC5F;AAAA,MACF,CAAC;AAAA,IACH;AACA,kBAAc;AAAA,EAChB;AACF;AAOO,SAAS,KAAK,SAEY;AAC/B,QAAM,oBAAoB,QAAQ,WAAW;AAC7C,SAAO;AAAA,IACL,OAAO,MAAM;AACX,YAAM,UAAU,mBAAmB,MAAM,iBAAiB;AAC1D,aAAO,2BAA2B,KAAK,iBAAiB,OAAO;AAAA,IACjE;AAAA,EACF;AACF;","names":["import_analysis","path","os","fs","net"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/service.ts","../src/workspace.ts","../src/constants.ts"],"sourcesContent":["import type * as tsServer from \"typescript/lib/tsserverlibrary.js\";\nimport { createLanguageServiceProxy, FormSpecPluginService } from \"./service.js\";\n\ninterface ServiceEntry {\n readonly service: FormSpecPluginService;\n referenceCount: number;\n}\n\nconst services = new Map<string, ServiceEntry>();\nconst PERF_LOG_ENV_VAR = \"FORMSPEC_PLUGIN_PROFILE\";\nconst PERF_LOG_THRESHOLD_ENV_VAR = \"FORMSPEC_PLUGIN_PROFILE_THRESHOLD_MS\";\n\nfunction formatPluginError(error: unknown): string {\n return error instanceof Error ? (error.stack ?? error.message) : String(error);\n}\n\nfunction readBooleanEnvFlag(name: string): boolean {\n const rawValue = process.env[name];\n return rawValue === \"1\" || rawValue === \"true\";\n}\n\nfunction readNumberEnvFlag(name: string): number | undefined {\n const rawValue = process.env[name];\n if (rawValue === undefined || rawValue.trim() === \"\") {\n return undefined;\n }\n\n const parsed = Number(rawValue);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction getOrCreateService(\n info: tsServer.server.PluginCreateInfo,\n typescriptVersion: string\n): FormSpecPluginService {\n const workspaceRoot = info.project.getCurrentDirectory();\n const existing = services.get(workspaceRoot);\n if (existing !== undefined) {\n existing.referenceCount += 1;\n attachProjectCloseHandler(info, workspaceRoot, existing);\n return existing.service;\n }\n\n const performanceLogThresholdMs = readNumberEnvFlag(PERF_LOG_THRESHOLD_ENV_VAR);\n const service = new FormSpecPluginService({\n workspaceRoot,\n typescriptVersion,\n getProgram: () => info.languageService.getProgram(),\n logger: info.project.projectService.logger,\n enablePerformanceLogging: readBooleanEnvFlag(PERF_LOG_ENV_VAR),\n ...(performanceLogThresholdMs === undefined ? {} : { performanceLogThresholdMs }),\n });\n\n const serviceEntry: ServiceEntry = {\n service,\n referenceCount: 1,\n };\n attachProjectCloseHandler(info, workspaceRoot, serviceEntry);\n\n service.start().catch((error: unknown) => {\n info.project.projectService.logger.info(\n `[FormSpec] Plugin service failed to start for ${workspaceRoot}: ${formatPluginError(error)}`\n );\n services.delete(workspaceRoot);\n });\n services.set(workspaceRoot, serviceEntry);\n return service;\n}\n\nfunction attachProjectCloseHandler(\n info: tsServer.server.PluginCreateInfo,\n workspaceRoot: string,\n serviceEntry: ServiceEntry\n): void {\n const originalClose = info.project.close.bind(info.project);\n let closed = false;\n\n info.project.close = () => {\n if (closed) {\n originalClose();\n return;\n }\n\n closed = true;\n serviceEntry.referenceCount -= 1;\n if (serviceEntry.referenceCount <= 0) {\n services.delete(workspaceRoot);\n void serviceEntry.service.stop().catch((error: unknown) => {\n info.project.projectService.logger.info(\n `[FormSpec] Failed to stop plugin service for ${workspaceRoot}: ${formatPluginError(error)}`\n );\n });\n }\n originalClose();\n };\n}\n\n/**\n * Initializes the FormSpec TypeScript language service plugin.\n *\n * @public\n */\nexport function init(modules: {\n readonly typescript: typeof tsServer;\n}): tsServer.server.PluginModule {\n const typescriptVersion = modules.typescript.version;\n return {\n create(info) {\n const service = getOrCreateService(info, typescriptVersion);\n return createLanguageServiceProxy(info.languageService, service);\n },\n };\n}\n","import fs from \"node:fs/promises\";\nimport net from \"node:net\";\nimport * as ts from \"typescript\";\nimport {\n FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n computeFormSpecTextHash,\n isFormSpecSemanticQuery,\n type FormSpecAnalysisManifest,\n type FormSpecSemanticQuery,\n type FormSpecSemanticResponse,\n} from \"@formspec/analysis/protocol\";\nimport {\n buildFormSpecAnalysisFileSnapshot,\n createFormSpecPerformanceRecorder,\n findDeclarationForCommentOffset,\n getCommentHoverInfoAtOffset,\n getSemanticCommentCompletionContextAtOffset,\n getFormSpecPerformanceNow,\n getSubjectType,\n optionalMeasure,\n resolveDeclarationPlacement,\n serializeCompletionContext,\n serializeHoverInfo,\n type BuildFormSpecAnalysisFileSnapshotOptions,\n type FormSpecAnalysisFileSnapshot,\n type FormSpecPerformanceEvent,\n type FormSpecPerformanceRecorder,\n} from \"@formspec/analysis/internal\";\nimport {\n createFormSpecAnalysisManifest,\n getFormSpecWorkspaceRuntimePaths,\n type FormSpecWorkspaceRuntimePaths,\n} from \"./workspace.js\";\nimport {\n FORM_SPEC_PLUGIN_DEFAULT_PERFORMANCE_LOG_THRESHOLD_MS,\n FORM_SPEC_PLUGIN_DEFAULT_SNAPSHOT_DEBOUNCE_MS,\n FORM_SPEC_PLUGIN_MAX_SOCKET_PAYLOAD_BYTES,\n FORM_SPEC_PLUGIN_PERFORMANCE_EVENT,\n FORM_SPEC_PLUGIN_SOCKET_IDLE_TIMEOUT_MS,\n} from \"./constants.js\";\n\ninterface LoggerLike {\n info(message: string): void;\n}\n\nexport interface FormSpecPluginServiceOptions {\n readonly workspaceRoot: string;\n readonly typescriptVersion: string;\n readonly getProgram: () => ts.Program | undefined;\n readonly logger?: LoggerLike;\n /**\n * Enables structured hotspot logging for semantic queries.\n *\n * The tsserver plugin sets this from `FORMSPEC_PLUGIN_PROFILE=1`.\n */\n readonly enablePerformanceLogging?: boolean;\n /**\n * Minimum total query duration in milliseconds before profiling is logged.\n *\n * Defaults to `50` when unset. The tsserver plugin sets this from\n * `FORMSPEC_PLUGIN_PROFILE_THRESHOLD_MS`.\n */\n readonly performanceLogThresholdMs?: number;\n readonly snapshotDebounceMs?: number;\n readonly now?: () => Date;\n}\n\ninterface CachedFileSnapshot {\n readonly sourceHash: string;\n readonly snapshot: FormSpecAnalysisFileSnapshot;\n}\n\ninterface SourceEnvironment {\n readonly sourceFile: ts.SourceFile;\n readonly checker: ts.TypeChecker;\n readonly sourceHash: string;\n}\n\ninterface CommentQueryContext extends SourceEnvironment {\n readonly declaration: ts.Node | null;\n readonly placement: ReturnType<typeof resolveDeclarationPlacement>;\n readonly subjectType: ts.Type | undefined;\n}\n\nexport class FormSpecPluginService {\n private readonly manifest: FormSpecAnalysisManifest;\n private readonly runtimePaths: FormSpecWorkspaceRuntimePaths;\n private readonly snapshotCache = new Map<string, CachedFileSnapshot>();\n private readonly refreshTimers = new Map<string, NodeJS.Timeout>();\n private server: net.Server | null = null;\n\n public constructor(private readonly options: FormSpecPluginServiceOptions) {\n this.runtimePaths = getFormSpecWorkspaceRuntimePaths(options.workspaceRoot);\n this.manifest = createFormSpecAnalysisManifest(\n options.workspaceRoot,\n options.typescriptVersion,\n Date.now()\n );\n }\n\n public getManifest(): FormSpecAnalysisManifest {\n return this.manifest;\n }\n\n public async start(): Promise<void> {\n if (this.server !== null) {\n return;\n }\n\n await fs.mkdir(this.runtimePaths.runtimeDirectory, { recursive: true });\n if (this.runtimePaths.endpoint.kind === \"unix-socket\") {\n await fs.rm(this.runtimePaths.endpoint.address, { force: true });\n }\n\n this.server = net.createServer((socket) => {\n let buffer = \"\";\n socket.setEncoding(\"utf8\");\n socket.setTimeout(FORM_SPEC_PLUGIN_SOCKET_IDLE_TIMEOUT_MS, () => {\n this.options.logger?.info(\n `[FormSpec] Closing idle semantic query socket for ${this.runtimePaths.workspaceRoot}`\n );\n socket.destroy();\n });\n socket.on(\"data\", (chunk) => {\n buffer += String(chunk);\n if (buffer.length > FORM_SPEC_PLUGIN_MAX_SOCKET_PAYLOAD_BYTES) {\n socket.end(\n `${JSON.stringify({\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"error\",\n error: `FormSpec semantic query exceeded ${String(FORM_SPEC_PLUGIN_MAX_SOCKET_PAYLOAD_BYTES)} bytes`,\n } satisfies FormSpecSemanticResponse)}\\n`\n );\n return;\n }\n const newlineIndex = buffer.indexOf(\"\\n\");\n if (newlineIndex < 0) {\n return;\n }\n\n const payload = buffer.slice(0, newlineIndex);\n const remaining = buffer.slice(newlineIndex + 1);\n if (remaining.trim().length > 0) {\n this.options.logger?.info(\n `[FormSpec] Ignoring extra semantic query payload data for ${this.runtimePaths.workspaceRoot}`\n );\n }\n buffer = remaining;\n // The FormSpec IPC transport is intentionally one-request-per-connection.\n this.respondToSocket(socket, payload);\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n const handleError = (error: Error) => {\n reject(error);\n };\n this.server?.once(\"error\", handleError);\n this.server?.listen(this.runtimePaths.endpoint.address, () => {\n this.server?.off(\"error\", handleError);\n resolve();\n });\n });\n\n await this.writeManifest();\n }\n\n public async stop(): Promise<void> {\n for (const timer of this.refreshTimers.values()) {\n clearTimeout(timer);\n }\n this.refreshTimers.clear();\n this.snapshotCache.clear();\n\n const server = this.server;\n this.server = null;\n if (server?.listening === true) {\n await new Promise<void>((resolve, reject) => {\n server.close((error) => {\n if (error === undefined) {\n resolve();\n return;\n }\n reject(error);\n });\n });\n }\n\n await this.cleanupRuntimeArtifacts();\n }\n\n public scheduleSnapshotRefresh(filePath: string): void {\n const existing = this.refreshTimers.get(filePath);\n if (existing !== undefined) {\n clearTimeout(existing);\n }\n\n const timer = setTimeout(() => {\n try {\n this.getFileSnapshot(filePath, undefined);\n } catch (error: unknown) {\n this.options.logger?.info(\n `[FormSpec] Failed to refresh semantic snapshot for ${filePath}: ${String(error)}`\n );\n }\n this.refreshTimers.delete(filePath);\n }, this.options.snapshotDebounceMs ?? FORM_SPEC_PLUGIN_DEFAULT_SNAPSHOT_DEBOUNCE_MS);\n\n this.refreshTimers.set(filePath, timer);\n }\n\n public handleQuery(query: FormSpecSemanticQuery): FormSpecSemanticResponse {\n const performance =\n this.options.enablePerformanceLogging === true\n ? createFormSpecPerformanceRecorder()\n : undefined;\n const response = optionalMeasure(\n performance,\n FORM_SPEC_PLUGIN_PERFORMANCE_EVENT.handleQuery,\n {\n kind: query.kind,\n ...(query.kind === \"health\" ? {} : { filePath: query.filePath }),\n },\n () => this.executeQuery(query, performance)\n );\n\n if (performance !== undefined) {\n this.logPerformanceEvents(performance.events);\n }\n\n return response;\n }\n\n private respondToSocket(socket: net.Socket, payload: string): void {\n try {\n const query = JSON.parse(payload) as unknown;\n if (!isFormSpecSemanticQuery(query)) {\n throw new Error(\"Invalid FormSpec semantic query payload\");\n }\n const response = this.handleQuery(query);\n socket.end(`${JSON.stringify(response)}\\n`);\n } catch (error) {\n socket.end(\n `${JSON.stringify({\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"error\",\n error: error instanceof Error ? error.message : String(error),\n } satisfies FormSpecSemanticResponse)}\\n`\n );\n }\n }\n\n private async writeManifest(): Promise<void> {\n const tempManifestPath = `${this.runtimePaths.manifestPath}.tmp`;\n await fs.writeFile(tempManifestPath, `${JSON.stringify(this.manifest, null, 2)}\\n`, \"utf8\");\n await fs.rename(tempManifestPath, this.runtimePaths.manifestPath);\n }\n\n private async cleanupRuntimeArtifacts(): Promise<void> {\n await fs.rm(this.runtimePaths.manifestPath, { force: true });\n if (this.runtimePaths.endpoint.kind === \"unix-socket\") {\n await fs.rm(this.runtimePaths.endpoint.address, { force: true });\n }\n }\n\n private withCommentQueryContext(\n filePath: string,\n offset: number,\n handler: (context: CommentQueryContext) => FormSpecSemanticResponse,\n performance: FormSpecPerformanceRecorder | undefined\n ): FormSpecSemanticResponse {\n return optionalMeasure(\n performance,\n \"plugin.resolveCommentQueryContext\",\n {\n filePath,\n offset,\n },\n () => {\n const environment = this.getSourceEnvironment(filePath, performance);\n if (environment === null) {\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"error\",\n error: `Unable to resolve TypeScript source file for ${filePath}`,\n };\n }\n\n const declaration = optionalMeasure(\n performance,\n \"plugin.findDeclarationForCommentOffset\",\n {\n filePath,\n offset,\n },\n () => findDeclarationForCommentOffset(environment.sourceFile, offset)\n );\n const placement =\n declaration === null\n ? null\n : optionalMeasure(performance, \"plugin.resolveDeclarationPlacement\", undefined, () =>\n resolveDeclarationPlacement(declaration)\n );\n const subjectType =\n declaration === null\n ? undefined\n : optionalMeasure(performance, \"plugin.getSubjectType\", undefined, () =>\n getSubjectType(declaration, environment.checker)\n );\n\n return handler({\n ...environment,\n declaration,\n placement,\n subjectType,\n });\n }\n );\n }\n\n private getFileSnapshot(\n filePath: string,\n performance: FormSpecPerformanceRecorder | undefined\n ): FormSpecAnalysisFileSnapshot {\n const startedAt = getFormSpecPerformanceNow();\n const environment = this.getSourceEnvironment(filePath, performance);\n if (environment === null) {\n const missingSourceSnapshot: FormSpecAnalysisFileSnapshot = {\n filePath,\n sourceHash: \"\",\n generatedAt: this.getNow().toISOString(),\n comments: [],\n diagnostics: [\n {\n code: \"MISSING_SOURCE_FILE\",\n message: `Unable to resolve TypeScript source file for ${filePath}`,\n range: { start: 0, end: 0 },\n severity: \"warning\",\n },\n ],\n };\n performance?.record({\n name: \"plugin.getFileSnapshot\",\n durationMs: getFormSpecPerformanceNow() - startedAt,\n detail: {\n filePath,\n cache: \"missing-source\",\n },\n });\n return missingSourceSnapshot;\n }\n\n const cached = this.snapshotCache.get(filePath);\n if (cached?.sourceHash === environment.sourceHash) {\n performance?.record({\n name: \"plugin.getFileSnapshot\",\n durationMs: getFormSpecPerformanceNow() - startedAt,\n detail: {\n filePath,\n cache: \"hit\",\n },\n });\n return cached.snapshot;\n }\n\n const snapshot = buildFormSpecAnalysisFileSnapshot(environment.sourceFile, {\n checker: environment.checker,\n now: () => this.getNow(),\n ...(performance === undefined ? {} : { performance }),\n } satisfies BuildFormSpecAnalysisFileSnapshotOptions);\n this.snapshotCache.set(filePath, {\n sourceHash: environment.sourceHash,\n snapshot,\n });\n performance?.record({\n name: \"plugin.getFileSnapshot\",\n durationMs: getFormSpecPerformanceNow() - startedAt,\n detail: {\n filePath,\n cache: \"miss\",\n },\n });\n return snapshot;\n }\n\n private getNow(): Date {\n return this.options.now?.() ?? new Date();\n }\n\n private executeQuery(\n query: FormSpecSemanticQuery,\n performance: FormSpecPerformanceRecorder | undefined\n ): FormSpecSemanticResponse {\n switch (query.kind) {\n case \"health\":\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"health\",\n manifest: this.manifest,\n };\n case \"completion\":\n return this.withCommentQueryContext(\n query.filePath,\n query.offset,\n (context) => ({\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"completion\",\n sourceHash: context.sourceHash,\n context: serializeCompletionContext(\n getSemanticCommentCompletionContextAtOffset(context.sourceFile.text, query.offset, {\n checker: context.checker,\n ...(context.placement === null ? {} : { placement: context.placement }),\n ...(context.subjectType === undefined ? {} : { subjectType: context.subjectType }),\n })\n ),\n }),\n performance\n );\n case \"hover\":\n return this.withCommentQueryContext(\n query.filePath,\n query.offset,\n (context) => ({\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"hover\",\n sourceHash: context.sourceHash,\n hover: serializeHoverInfo(\n getCommentHoverInfoAtOffset(context.sourceFile.text, query.offset, {\n checker: context.checker,\n ...(context.placement === null ? {} : { placement: context.placement }),\n ...(context.subjectType === undefined ? {} : { subjectType: context.subjectType }),\n })\n ),\n }),\n performance\n );\n case \"diagnostics\": {\n const snapshot = this.getFileSnapshot(query.filePath, performance);\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"diagnostics\",\n sourceHash: snapshot.sourceHash,\n diagnostics: snapshot.diagnostics,\n };\n }\n case \"file-snapshot\":\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n kind: \"file-snapshot\",\n snapshot: this.getFileSnapshot(query.filePath, performance),\n };\n default: {\n throw new Error(`Unhandled semantic query: ${JSON.stringify(query)}`);\n }\n }\n }\n\n private getSourceEnvironment(\n filePath: string,\n performance: FormSpecPerformanceRecorder | undefined\n ): SourceEnvironment | null {\n return optionalMeasure(\n performance,\n \"plugin.getSourceEnvironment\",\n {\n filePath,\n },\n () => {\n const program = optionalMeasure(\n performance,\n \"plugin.sourceEnvironment.getProgram\",\n undefined,\n () => this.options.getProgram()\n );\n if (program === undefined) {\n return null;\n }\n\n const sourceFile = optionalMeasure(\n performance,\n \"plugin.sourceEnvironment.getSourceFile\",\n undefined,\n () => program.getSourceFile(filePath)\n );\n if (sourceFile === undefined) {\n return null;\n }\n\n const checker = optionalMeasure(\n performance,\n \"plugin.sourceEnvironment.getTypeChecker\",\n undefined,\n () => program.getTypeChecker()\n );\n const sourceHash = optionalMeasure(\n performance,\n \"plugin.sourceEnvironment.computeTextHash\",\n undefined,\n () => computeFormSpecTextHash(sourceFile.text)\n );\n\n return {\n sourceFile,\n checker,\n sourceHash,\n };\n }\n );\n }\n\n private logPerformanceEvents(events: readonly FormSpecPerformanceEvent[]): void {\n const logger = this.options.logger;\n if (logger === undefined || events.length === 0) {\n return;\n }\n\n let rootEvent: FormSpecPerformanceEvent | undefined;\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const candidate = events[index];\n if (candidate?.name === FORM_SPEC_PLUGIN_PERFORMANCE_EVENT.handleQuery) {\n rootEvent = candidate;\n break;\n }\n }\n if (rootEvent === undefined) {\n return;\n }\n\n const thresholdMs =\n this.options.performanceLogThresholdMs ??\n FORM_SPEC_PLUGIN_DEFAULT_PERFORMANCE_LOG_THRESHOLD_MS;\n if (rootEvent.durationMs < thresholdMs) {\n return;\n }\n\n const sortedHotspots = [...events]\n .filter((event) => event.name !== FORM_SPEC_PLUGIN_PERFORMANCE_EVENT.handleQuery)\n .sort((left, right) => right.durationMs - left.durationMs)\n .slice(0, 8);\n const lines = [\n `[FormSpec][perf] ${rootEvent.name} ${formatPerformanceEvent(rootEvent)}`,\n ...sortedHotspots.map((event) => ` ${formatPerformanceEvent(event)}`),\n ];\n logger.info(lines.join(\"\\n\"));\n }\n}\n\nfunction formatPerformanceEvent(event: FormSpecPerformanceEvent): string {\n const detailEntries = Object.entries(event.detail ?? {})\n .map(([key, value]) => `${key}=${String(value)}`)\n .join(\" \");\n return `${event.durationMs.toFixed(1)}ms ${event.name}${detailEntries === \"\" ? \"\" : ` ${detailEntries}`}`;\n}\n\nexport function createLanguageServiceProxy(\n languageService: ts.LanguageService,\n semanticService: FormSpecPluginService\n): ts.LanguageService {\n const wrapWithSnapshotRefresh = <Args extends readonly unknown[], Result>(\n fn: (fileName: string, ...args: Args) => Result\n ) => {\n return (fileName: string, ...args: Args): Result => {\n semanticService.scheduleSnapshotRefresh(fileName);\n return fn(fileName, ...args);\n };\n };\n\n // The plugin keeps semantic snapshots fresh for the lightweight LSP. The\n // underlying tsserver results still come from the original language service.\n const getSemanticDiagnostics = wrapWithSnapshotRefresh((fileName) =>\n languageService.getSemanticDiagnostics(fileName)\n );\n\n const getCompletionsAtPosition = wrapWithSnapshotRefresh(\n (fileName: string, position: number, options: ts.GetCompletionsAtPositionOptions | undefined) =>\n languageService.getCompletionsAtPosition(fileName, position, options)\n );\n\n const getQuickInfoAtPosition = wrapWithSnapshotRefresh((fileName, position: number) =>\n languageService.getQuickInfoAtPosition(fileName, position)\n );\n\n return new Proxy(languageService, {\n get(target, property, receiver) {\n switch (property) {\n case \"getSemanticDiagnostics\":\n return getSemanticDiagnostics;\n case \"getCompletionsAtPosition\":\n return getCompletionsAtPosition;\n case \"getQuickInfoAtPosition\":\n return getQuickInfoAtPosition;\n default:\n return Reflect.get(target, property, receiver) as unknown;\n }\n },\n });\n}\n","import os from \"node:os\";\nimport path from \"node:path\";\nimport {\n FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n FORMSPEC_ANALYSIS_SCHEMA_VERSION,\n getFormSpecManifestPath,\n getFormSpecWorkspaceId,\n getFormSpecWorkspaceRuntimeDirectory,\n type FormSpecAnalysisManifest,\n type FormSpecIpcEndpoint,\n} from \"@formspec/analysis/protocol\";\n\nexport interface FormSpecWorkspaceRuntimePaths {\n readonly workspaceRoot: string;\n readonly workspaceId: string;\n readonly runtimeDirectory: string;\n readonly manifestPath: string;\n readonly endpoint: FormSpecIpcEndpoint;\n}\n\nexport function getFormSpecWorkspaceRuntimePaths(\n workspaceRoot: string,\n platform = process.platform,\n userScope = getFormSpecUserScope()\n): FormSpecWorkspaceRuntimePaths {\n const workspaceId = getFormSpecWorkspaceId(workspaceRoot);\n const runtimeDirectory = getFormSpecWorkspaceRuntimeDirectory(workspaceRoot);\n const sanitizedUserScope = sanitizeScopeSegment(userScope);\n const endpoint: FormSpecIpcEndpoint =\n platform === \"win32\"\n ? {\n kind: \"windows-pipe\",\n address: `\\\\\\\\.\\\\pipe\\\\formspec-${sanitizedUserScope}-${workspaceId}`,\n }\n : {\n kind: \"unix-socket\",\n address: path.join(os.tmpdir(), `formspec-${sanitizedUserScope}-${workspaceId}.sock`),\n };\n\n return {\n workspaceRoot,\n workspaceId,\n runtimeDirectory,\n manifestPath: getFormSpecManifestPath(workspaceRoot),\n endpoint,\n };\n}\n\nexport function createFormSpecAnalysisManifest(\n workspaceRoot: string,\n typescriptVersion: string,\n generation: number,\n extensionFingerprint = \"builtin\"\n): FormSpecAnalysisManifest {\n const paths = getFormSpecWorkspaceRuntimePaths(workspaceRoot);\n return {\n protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION,\n analysisSchemaVersion: FORMSPEC_ANALYSIS_SCHEMA_VERSION,\n workspaceRoot,\n workspaceId: paths.workspaceId,\n endpoint: paths.endpoint,\n typescriptVersion,\n extensionFingerprint,\n generation,\n updatedAt: new Date().toISOString(),\n };\n}\n\nfunction getFormSpecUserScope(): string {\n try {\n return sanitizeScopeSegment(os.userInfo().username);\n } catch {\n return sanitizeScopeSegment(\n process.env[\"USER\"] ?? process.env[\"USERNAME\"] ?? process.env[\"LOGNAME\"] ?? \"formspec\"\n );\n }\n}\n\nfunction sanitizeScopeSegment(value: string): string {\n const trimmed = value.trim().toLowerCase();\n const sanitized = trimmed.replace(/[^a-z0-9_-]+/gu, \"-\").replace(/-+/gu, \"-\");\n return sanitized.length > 0 ? sanitized : \"formspec\";\n}\n","export const FORM_SPEC_PLUGIN_MAX_SOCKET_PAYLOAD_BYTES = 256 * 1024;\nexport const FORM_SPEC_PLUGIN_SOCKET_IDLE_TIMEOUT_MS = 30_000;\nexport const FORM_SPEC_PLUGIN_DEFAULT_PERFORMANCE_LOG_THRESHOLD_MS = 50;\nexport const FORM_SPEC_PLUGIN_DEFAULT_SNAPSHOT_DEBOUNCE_MS = 250;\n\nexport const FORM_SPEC_PLUGIN_PERFORMANCE_EVENT = {\n handleQuery: \"plugin.handleQuery\",\n} as const;\n"],"mappings":"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","names":["import_protocol","path","os","fs","net"]}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,QAAQ,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,QAAQ,MAAM,mCAAmC,CAAC;AAiGnE;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE;IAC5B,QAAQ,CAAC,UAAU,EAAE,OAAO,QAAQ,CAAC;CACtC,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,CAQ/B"}
|
package/dist/index.js
CHANGED
|
@@ -4,17 +4,22 @@ import net from "net";
|
|
|
4
4
|
import "typescript";
|
|
5
5
|
import {
|
|
6
6
|
FORMSPEC_ANALYSIS_PROTOCOL_VERSION as FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
7
|
-
buildFormSpecAnalysisFileSnapshot,
|
|
8
7
|
computeFormSpecTextHash,
|
|
8
|
+
isFormSpecSemanticQuery
|
|
9
|
+
} from "@formspec/analysis/protocol";
|
|
10
|
+
import {
|
|
11
|
+
buildFormSpecAnalysisFileSnapshot,
|
|
12
|
+
createFormSpecPerformanceRecorder,
|
|
9
13
|
findDeclarationForCommentOffset,
|
|
10
|
-
getSubjectType,
|
|
11
14
|
getCommentHoverInfoAtOffset,
|
|
12
15
|
getSemanticCommentCompletionContextAtOffset,
|
|
13
|
-
|
|
16
|
+
getFormSpecPerformanceNow,
|
|
17
|
+
getSubjectType,
|
|
18
|
+
optionalMeasure,
|
|
14
19
|
resolveDeclarationPlacement,
|
|
15
20
|
serializeCompletionContext,
|
|
16
21
|
serializeHoverInfo
|
|
17
|
-
} from "@formspec/analysis";
|
|
22
|
+
} from "@formspec/analysis/internal";
|
|
18
23
|
|
|
19
24
|
// src/workspace.ts
|
|
20
25
|
import os from "os";
|
|
@@ -25,7 +30,7 @@ import {
|
|
|
25
30
|
getFormSpecManifestPath,
|
|
26
31
|
getFormSpecWorkspaceId,
|
|
27
32
|
getFormSpecWorkspaceRuntimeDirectory
|
|
28
|
-
} from "@formspec/analysis";
|
|
33
|
+
} from "@formspec/analysis/protocol";
|
|
29
34
|
function getFormSpecWorkspaceRuntimePaths(workspaceRoot, platform = process.platform, userScope = getFormSpecUserScope()) {
|
|
30
35
|
const workspaceId = getFormSpecWorkspaceId(workspaceRoot);
|
|
31
36
|
const runtimeDirectory = getFormSpecWorkspaceRuntimeDirectory(workspaceRoot);
|
|
@@ -74,6 +79,15 @@ function sanitizeScopeSegment(value) {
|
|
|
74
79
|
return sanitized.length > 0 ? sanitized : "formspec";
|
|
75
80
|
}
|
|
76
81
|
|
|
82
|
+
// src/constants.ts
|
|
83
|
+
var FORM_SPEC_PLUGIN_MAX_SOCKET_PAYLOAD_BYTES = 256 * 1024;
|
|
84
|
+
var FORM_SPEC_PLUGIN_SOCKET_IDLE_TIMEOUT_MS = 3e4;
|
|
85
|
+
var FORM_SPEC_PLUGIN_DEFAULT_PERFORMANCE_LOG_THRESHOLD_MS = 50;
|
|
86
|
+
var FORM_SPEC_PLUGIN_DEFAULT_SNAPSHOT_DEBOUNCE_MS = 250;
|
|
87
|
+
var FORM_SPEC_PLUGIN_PERFORMANCE_EVENT = {
|
|
88
|
+
handleQuery: "plugin.handleQuery"
|
|
89
|
+
};
|
|
90
|
+
|
|
77
91
|
// src/service.ts
|
|
78
92
|
var FormSpecPluginService = class {
|
|
79
93
|
constructor(options) {
|
|
@@ -104,8 +118,25 @@ var FormSpecPluginService = class {
|
|
|
104
118
|
this.server = net.createServer((socket) => {
|
|
105
119
|
let buffer = "";
|
|
106
120
|
socket.setEncoding("utf8");
|
|
121
|
+
socket.setTimeout(FORM_SPEC_PLUGIN_SOCKET_IDLE_TIMEOUT_MS, () => {
|
|
122
|
+
this.options.logger?.info(
|
|
123
|
+
`[FormSpec] Closing idle semantic query socket for ${this.runtimePaths.workspaceRoot}`
|
|
124
|
+
);
|
|
125
|
+
socket.destroy();
|
|
126
|
+
});
|
|
107
127
|
socket.on("data", (chunk) => {
|
|
108
128
|
buffer += String(chunk);
|
|
129
|
+
if (buffer.length > FORM_SPEC_PLUGIN_MAX_SOCKET_PAYLOAD_BYTES) {
|
|
130
|
+
socket.end(
|
|
131
|
+
`${JSON.stringify({
|
|
132
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
133
|
+
kind: "error",
|
|
134
|
+
error: `FormSpec semantic query exceeded ${String(FORM_SPEC_PLUGIN_MAX_SOCKET_PAYLOAD_BYTES)} bytes`
|
|
135
|
+
})}
|
|
136
|
+
`
|
|
137
|
+
);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
109
140
|
const newlineIndex = buffer.indexOf("\n");
|
|
110
141
|
if (newlineIndex < 0) {
|
|
111
142
|
return;
|
|
@@ -161,95 +192,31 @@ var FormSpecPluginService = class {
|
|
|
161
192
|
}
|
|
162
193
|
const timer = setTimeout(() => {
|
|
163
194
|
try {
|
|
164
|
-
this.getFileSnapshot(filePath);
|
|
195
|
+
this.getFileSnapshot(filePath, void 0);
|
|
165
196
|
} catch (error) {
|
|
166
197
|
this.options.logger?.info(
|
|
167
198
|
`[FormSpec] Failed to refresh semantic snapshot for ${filePath}: ${String(error)}`
|
|
168
199
|
);
|
|
169
200
|
}
|
|
170
201
|
this.refreshTimers.delete(filePath);
|
|
171
|
-
}, this.options.snapshotDebounceMs ??
|
|
202
|
+
}, this.options.snapshotDebounceMs ?? FORM_SPEC_PLUGIN_DEFAULT_SNAPSHOT_DEBOUNCE_MS);
|
|
172
203
|
this.refreshTimers.set(filePath, timer);
|
|
173
204
|
}
|
|
174
205
|
handleQuery(query) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
kind: "error",
|
|
188
|
-
error: `Unable to resolve TypeScript source file for ${query.filePath}`
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
const declaration = findDeclarationForCommentOffset(environment.sourceFile, query.offset);
|
|
192
|
-
const placement = declaration === null ? null : resolveDeclarationPlacement(declaration);
|
|
193
|
-
const subjectType = declaration === null ? void 0 : getSubjectType(declaration, environment.checker);
|
|
194
|
-
const context = getSemanticCommentCompletionContextAtOffset(
|
|
195
|
-
environment.sourceFile.text,
|
|
196
|
-
query.offset,
|
|
197
|
-
{
|
|
198
|
-
checker: environment.checker,
|
|
199
|
-
...placement === null ? {} : { placement },
|
|
200
|
-
...subjectType === void 0 ? {} : { subjectType }
|
|
201
|
-
}
|
|
202
|
-
);
|
|
203
|
-
return {
|
|
204
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
205
|
-
kind: "completion",
|
|
206
|
-
sourceHash: computeFormSpecTextHash(environment.sourceFile.text),
|
|
207
|
-
context: serializeCompletionContext(context)
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
case "hover": {
|
|
211
|
-
const environment = this.getSourceEnvironment(query.filePath);
|
|
212
|
-
if (environment === null) {
|
|
213
|
-
return {
|
|
214
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
215
|
-
kind: "error",
|
|
216
|
-
error: `Unable to resolve TypeScript source file for ${query.filePath}`
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
const declaration = findDeclarationForCommentOffset(environment.sourceFile, query.offset);
|
|
220
|
-
const placement = declaration === null ? null : resolveDeclarationPlacement(declaration);
|
|
221
|
-
const subjectType = declaration === null ? void 0 : getSubjectType(declaration, environment.checker);
|
|
222
|
-
const hover = getCommentHoverInfoAtOffset(environment.sourceFile.text, query.offset, {
|
|
223
|
-
checker: environment.checker,
|
|
224
|
-
...placement === null ? {} : { placement },
|
|
225
|
-
...subjectType === void 0 ? {} : { subjectType }
|
|
226
|
-
});
|
|
227
|
-
return {
|
|
228
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
229
|
-
kind: "hover",
|
|
230
|
-
sourceHash: computeFormSpecTextHash(environment.sourceFile.text),
|
|
231
|
-
hover: serializeHoverInfo(hover)
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
case "diagnostics": {
|
|
235
|
-
const snapshot = this.getFileSnapshot(query.filePath);
|
|
236
|
-
return {
|
|
237
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
238
|
-
kind: "diagnostics",
|
|
239
|
-
sourceHash: snapshot.sourceHash,
|
|
240
|
-
diagnostics: snapshot.diagnostics
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
case "file-snapshot":
|
|
244
|
-
return {
|
|
245
|
-
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
246
|
-
kind: "file-snapshot",
|
|
247
|
-
snapshot: this.getFileSnapshot(query.filePath)
|
|
248
|
-
};
|
|
249
|
-
default: {
|
|
250
|
-
throw new Error(`Unhandled semantic query: ${JSON.stringify(query)}`);
|
|
251
|
-
}
|
|
206
|
+
const performance = this.options.enablePerformanceLogging === true ? createFormSpecPerformanceRecorder() : void 0;
|
|
207
|
+
const response = optionalMeasure(
|
|
208
|
+
performance,
|
|
209
|
+
FORM_SPEC_PLUGIN_PERFORMANCE_EVENT.handleQuery,
|
|
210
|
+
{
|
|
211
|
+
kind: query.kind,
|
|
212
|
+
...query.kind === "health" ? {} : { filePath: query.filePath }
|
|
213
|
+
},
|
|
214
|
+
() => this.executeQuery(query, performance)
|
|
215
|
+
);
|
|
216
|
+
if (performance !== void 0) {
|
|
217
|
+
this.logPerformanceEvents(performance.events);
|
|
252
218
|
}
|
|
219
|
+
return response;
|
|
253
220
|
}
|
|
254
221
|
respondToSocket(socket, payload) {
|
|
255
222
|
try {
|
|
@@ -283,24 +250,58 @@ var FormSpecPluginService = class {
|
|
|
283
250
|
await fs.rm(this.runtimePaths.endpoint.address, { force: true });
|
|
284
251
|
}
|
|
285
252
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
253
|
+
withCommentQueryContext(filePath, offset, handler, performance) {
|
|
254
|
+
return optionalMeasure(
|
|
255
|
+
performance,
|
|
256
|
+
"plugin.resolveCommentQueryContext",
|
|
257
|
+
{
|
|
258
|
+
filePath,
|
|
259
|
+
offset
|
|
260
|
+
},
|
|
261
|
+
() => {
|
|
262
|
+
const environment = this.getSourceEnvironment(filePath, performance);
|
|
263
|
+
if (environment === null) {
|
|
264
|
+
return {
|
|
265
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
266
|
+
kind: "error",
|
|
267
|
+
error: `Unable to resolve TypeScript source file for ${filePath}`
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const declaration = optionalMeasure(
|
|
271
|
+
performance,
|
|
272
|
+
"plugin.findDeclarationForCommentOffset",
|
|
273
|
+
{
|
|
274
|
+
filePath,
|
|
275
|
+
offset
|
|
276
|
+
},
|
|
277
|
+
() => findDeclarationForCommentOffset(environment.sourceFile, offset)
|
|
278
|
+
);
|
|
279
|
+
const placement = declaration === null ? null : optionalMeasure(
|
|
280
|
+
performance,
|
|
281
|
+
"plugin.resolveDeclarationPlacement",
|
|
282
|
+
void 0,
|
|
283
|
+
() => resolveDeclarationPlacement(declaration)
|
|
284
|
+
);
|
|
285
|
+
const subjectType = declaration === null ? void 0 : optionalMeasure(
|
|
286
|
+
performance,
|
|
287
|
+
"plugin.getSubjectType",
|
|
288
|
+
void 0,
|
|
289
|
+
() => getSubjectType(declaration, environment.checker)
|
|
290
|
+
);
|
|
291
|
+
return handler({
|
|
292
|
+
...environment,
|
|
293
|
+
declaration,
|
|
294
|
+
placement,
|
|
295
|
+
subjectType
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
299
|
}
|
|
300
|
-
getFileSnapshot(filePath) {
|
|
301
|
-
const
|
|
300
|
+
getFileSnapshot(filePath, performance) {
|
|
301
|
+
const startedAt = getFormSpecPerformanceNow();
|
|
302
|
+
const environment = this.getSourceEnvironment(filePath, performance);
|
|
302
303
|
if (environment === null) {
|
|
303
|
-
|
|
304
|
+
const missingSourceSnapshot = {
|
|
304
305
|
filePath,
|
|
305
306
|
sourceHash: "",
|
|
306
307
|
generatedAt: this.getNow().toISOString(),
|
|
@@ -314,25 +315,192 @@ var FormSpecPluginService = class {
|
|
|
314
315
|
}
|
|
315
316
|
]
|
|
316
317
|
};
|
|
318
|
+
performance?.record({
|
|
319
|
+
name: "plugin.getFileSnapshot",
|
|
320
|
+
durationMs: getFormSpecPerformanceNow() - startedAt,
|
|
321
|
+
detail: {
|
|
322
|
+
filePath,
|
|
323
|
+
cache: "missing-source"
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
return missingSourceSnapshot;
|
|
317
327
|
}
|
|
318
|
-
const sourceHash = computeFormSpecTextHash(environment.sourceFile.text);
|
|
319
328
|
const cached = this.snapshotCache.get(filePath);
|
|
320
|
-
if (cached?.sourceHash === sourceHash) {
|
|
329
|
+
if (cached?.sourceHash === environment.sourceHash) {
|
|
330
|
+
performance?.record({
|
|
331
|
+
name: "plugin.getFileSnapshot",
|
|
332
|
+
durationMs: getFormSpecPerformanceNow() - startedAt,
|
|
333
|
+
detail: {
|
|
334
|
+
filePath,
|
|
335
|
+
cache: "hit"
|
|
336
|
+
}
|
|
337
|
+
});
|
|
321
338
|
return cached.snapshot;
|
|
322
339
|
}
|
|
323
340
|
const snapshot = buildFormSpecAnalysisFileSnapshot(environment.sourceFile, {
|
|
324
|
-
checker: environment.checker
|
|
341
|
+
checker: environment.checker,
|
|
342
|
+
now: () => this.getNow(),
|
|
343
|
+
...performance === void 0 ? {} : { performance }
|
|
325
344
|
});
|
|
326
345
|
this.snapshotCache.set(filePath, {
|
|
327
|
-
sourceHash,
|
|
346
|
+
sourceHash: environment.sourceHash,
|
|
328
347
|
snapshot
|
|
329
348
|
});
|
|
349
|
+
performance?.record({
|
|
350
|
+
name: "plugin.getFileSnapshot",
|
|
351
|
+
durationMs: getFormSpecPerformanceNow() - startedAt,
|
|
352
|
+
detail: {
|
|
353
|
+
filePath,
|
|
354
|
+
cache: "miss"
|
|
355
|
+
}
|
|
356
|
+
});
|
|
330
357
|
return snapshot;
|
|
331
358
|
}
|
|
332
359
|
getNow() {
|
|
333
360
|
return this.options.now?.() ?? /* @__PURE__ */ new Date();
|
|
334
361
|
}
|
|
362
|
+
executeQuery(query, performance) {
|
|
363
|
+
switch (query.kind) {
|
|
364
|
+
case "health":
|
|
365
|
+
return {
|
|
366
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
367
|
+
kind: "health",
|
|
368
|
+
manifest: this.manifest
|
|
369
|
+
};
|
|
370
|
+
case "completion":
|
|
371
|
+
return this.withCommentQueryContext(
|
|
372
|
+
query.filePath,
|
|
373
|
+
query.offset,
|
|
374
|
+
(context) => ({
|
|
375
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
376
|
+
kind: "completion",
|
|
377
|
+
sourceHash: context.sourceHash,
|
|
378
|
+
context: serializeCompletionContext(
|
|
379
|
+
getSemanticCommentCompletionContextAtOffset(context.sourceFile.text, query.offset, {
|
|
380
|
+
checker: context.checker,
|
|
381
|
+
...context.placement === null ? {} : { placement: context.placement },
|
|
382
|
+
...context.subjectType === void 0 ? {} : { subjectType: context.subjectType }
|
|
383
|
+
})
|
|
384
|
+
)
|
|
385
|
+
}),
|
|
386
|
+
performance
|
|
387
|
+
);
|
|
388
|
+
case "hover":
|
|
389
|
+
return this.withCommentQueryContext(
|
|
390
|
+
query.filePath,
|
|
391
|
+
query.offset,
|
|
392
|
+
(context) => ({
|
|
393
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
394
|
+
kind: "hover",
|
|
395
|
+
sourceHash: context.sourceHash,
|
|
396
|
+
hover: serializeHoverInfo(
|
|
397
|
+
getCommentHoverInfoAtOffset(context.sourceFile.text, query.offset, {
|
|
398
|
+
checker: context.checker,
|
|
399
|
+
...context.placement === null ? {} : { placement: context.placement },
|
|
400
|
+
...context.subjectType === void 0 ? {} : { subjectType: context.subjectType }
|
|
401
|
+
})
|
|
402
|
+
)
|
|
403
|
+
}),
|
|
404
|
+
performance
|
|
405
|
+
);
|
|
406
|
+
case "diagnostics": {
|
|
407
|
+
const snapshot = this.getFileSnapshot(query.filePath, performance);
|
|
408
|
+
return {
|
|
409
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
410
|
+
kind: "diagnostics",
|
|
411
|
+
sourceHash: snapshot.sourceHash,
|
|
412
|
+
diagnostics: snapshot.diagnostics
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
case "file-snapshot":
|
|
416
|
+
return {
|
|
417
|
+
protocolVersion: FORMSPEC_ANALYSIS_PROTOCOL_VERSION2,
|
|
418
|
+
kind: "file-snapshot",
|
|
419
|
+
snapshot: this.getFileSnapshot(query.filePath, performance)
|
|
420
|
+
};
|
|
421
|
+
default: {
|
|
422
|
+
throw new Error(`Unhandled semantic query: ${JSON.stringify(query)}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
getSourceEnvironment(filePath, performance) {
|
|
427
|
+
return optionalMeasure(
|
|
428
|
+
performance,
|
|
429
|
+
"plugin.getSourceEnvironment",
|
|
430
|
+
{
|
|
431
|
+
filePath
|
|
432
|
+
},
|
|
433
|
+
() => {
|
|
434
|
+
const program = optionalMeasure(
|
|
435
|
+
performance,
|
|
436
|
+
"plugin.sourceEnvironment.getProgram",
|
|
437
|
+
void 0,
|
|
438
|
+
() => this.options.getProgram()
|
|
439
|
+
);
|
|
440
|
+
if (program === void 0) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
const sourceFile = optionalMeasure(
|
|
444
|
+
performance,
|
|
445
|
+
"plugin.sourceEnvironment.getSourceFile",
|
|
446
|
+
void 0,
|
|
447
|
+
() => program.getSourceFile(filePath)
|
|
448
|
+
);
|
|
449
|
+
if (sourceFile === void 0) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
const checker = optionalMeasure(
|
|
453
|
+
performance,
|
|
454
|
+
"plugin.sourceEnvironment.getTypeChecker",
|
|
455
|
+
void 0,
|
|
456
|
+
() => program.getTypeChecker()
|
|
457
|
+
);
|
|
458
|
+
const sourceHash = optionalMeasure(
|
|
459
|
+
performance,
|
|
460
|
+
"plugin.sourceEnvironment.computeTextHash",
|
|
461
|
+
void 0,
|
|
462
|
+
() => computeFormSpecTextHash(sourceFile.text)
|
|
463
|
+
);
|
|
464
|
+
return {
|
|
465
|
+
sourceFile,
|
|
466
|
+
checker,
|
|
467
|
+
sourceHash
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
logPerformanceEvents(events) {
|
|
473
|
+
const logger = this.options.logger;
|
|
474
|
+
if (logger === void 0 || events.length === 0) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
let rootEvent;
|
|
478
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
479
|
+
const candidate = events[index];
|
|
480
|
+
if (candidate?.name === FORM_SPEC_PLUGIN_PERFORMANCE_EVENT.handleQuery) {
|
|
481
|
+
rootEvent = candidate;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (rootEvent === void 0) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const thresholdMs = this.options.performanceLogThresholdMs ?? FORM_SPEC_PLUGIN_DEFAULT_PERFORMANCE_LOG_THRESHOLD_MS;
|
|
489
|
+
if (rootEvent.durationMs < thresholdMs) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const sortedHotspots = [...events].filter((event) => event.name !== FORM_SPEC_PLUGIN_PERFORMANCE_EVENT.handleQuery).sort((left, right) => right.durationMs - left.durationMs).slice(0, 8);
|
|
493
|
+
const lines = [
|
|
494
|
+
`[FormSpec][perf] ${rootEvent.name} ${formatPerformanceEvent(rootEvent)}`,
|
|
495
|
+
...sortedHotspots.map((event) => ` ${formatPerformanceEvent(event)}`)
|
|
496
|
+
];
|
|
497
|
+
logger.info(lines.join("\n"));
|
|
498
|
+
}
|
|
335
499
|
};
|
|
500
|
+
function formatPerformanceEvent(event) {
|
|
501
|
+
const detailEntries = Object.entries(event.detail ?? {}).map(([key, value]) => `${key}=${String(value)}`).join(" ");
|
|
502
|
+
return `${event.durationMs.toFixed(1)}ms ${event.name}${detailEntries === "" ? "" : ` ${detailEntries}`}`;
|
|
503
|
+
}
|
|
336
504
|
function createLanguageServiceProxy(languageService, semanticService) {
|
|
337
505
|
const wrapWithSnapshotRefresh = (fn) => {
|
|
338
506
|
return (fileName, ...args) => {
|
|
@@ -367,9 +535,23 @@ function createLanguageServiceProxy(languageService, semanticService) {
|
|
|
367
535
|
|
|
368
536
|
// src/index.ts
|
|
369
537
|
var services = /* @__PURE__ */ new Map();
|
|
538
|
+
var PERF_LOG_ENV_VAR = "FORMSPEC_PLUGIN_PROFILE";
|
|
539
|
+
var PERF_LOG_THRESHOLD_ENV_VAR = "FORMSPEC_PLUGIN_PROFILE_THRESHOLD_MS";
|
|
370
540
|
function formatPluginError(error) {
|
|
371
541
|
return error instanceof Error ? error.stack ?? error.message : String(error);
|
|
372
542
|
}
|
|
543
|
+
function readBooleanEnvFlag(name) {
|
|
544
|
+
const rawValue = process.env[name];
|
|
545
|
+
return rawValue === "1" || rawValue === "true";
|
|
546
|
+
}
|
|
547
|
+
function readNumberEnvFlag(name) {
|
|
548
|
+
const rawValue = process.env[name];
|
|
549
|
+
if (rawValue === void 0 || rawValue.trim() === "") {
|
|
550
|
+
return void 0;
|
|
551
|
+
}
|
|
552
|
+
const parsed = Number(rawValue);
|
|
553
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
554
|
+
}
|
|
373
555
|
function getOrCreateService(info, typescriptVersion) {
|
|
374
556
|
const workspaceRoot = info.project.getCurrentDirectory();
|
|
375
557
|
const existing = services.get(workspaceRoot);
|
|
@@ -378,11 +560,14 @@ function getOrCreateService(info, typescriptVersion) {
|
|
|
378
560
|
attachProjectCloseHandler(info, workspaceRoot, existing);
|
|
379
561
|
return existing.service;
|
|
380
562
|
}
|
|
563
|
+
const performanceLogThresholdMs = readNumberEnvFlag(PERF_LOG_THRESHOLD_ENV_VAR);
|
|
381
564
|
const service = new FormSpecPluginService({
|
|
382
565
|
workspaceRoot,
|
|
383
566
|
typescriptVersion,
|
|
384
567
|
getProgram: () => info.languageService.getProgram(),
|
|
385
|
-
logger: info.project.projectService.logger
|
|
568
|
+
logger: info.project.projectService.logger,
|
|
569
|
+
enablePerformanceLogging: readBooleanEnvFlag(PERF_LOG_ENV_VAR),
|
|
570
|
+
...performanceLogThresholdMs === void 0 ? {} : { performanceLogThresholdMs }
|
|
386
571
|
});
|
|
387
572
|
const serviceEntry = {
|
|
388
573
|
service,
|