microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
4d92e0825d81d6432d29b0fade7b161a2e2aafb7

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/compiler/testing/test-server-host.ts

128lines · modecode

1import { ok } from "assert";
2import { pathToFileURL } from "url";
3import { TextDocument } from "vscode-languageserver-textdocument";
4import { Diagnostic } from "vscode-languageserver/node.js";
5import { parse, visitChildren } from "../core/parser.js";
6import { IdentifierNode, SyntaxKind } from "../core/types.js";
7import { createStringMap } from "../core/util.js";
8import { createServer, Server, ServerHost } from "../server/index.js";
9import {
10 createTestFileSystem,
11 resolveVirtualPath,
12 StandardTestLibrary,
13 TestHostOptions,
14} from "./test-host.js";
15import { TestFileSystem } from "./types.js";
16
17export interface TestServerHost extends ServerHost, TestFileSystem {
18 server: Server;
19 logMessages: readonly string[];
20 getOpenDocument(path: string): TextDocument | undefined;
21 addOrUpdateDocument(path: string, content: string): TextDocument;
22 getDiagnostics(path: string): readonly Diagnostic[];
23 getURL(path: string): string;
24}
25
26export async function createTestServerHost(options?: TestHostOptions) {
27 const logMessages: string[] = [];
28 const documents = createStringMap<TextDocument>(!!options?.caseInsensitiveFileSystem);
29 const diagnostics = createStringMap<Diagnostic[]>(!!options?.caseInsensitiveFileSystem);
30 const fileSystem = await createTestFileSystem({ ...options, excludeTestLib: true });
31 await fileSystem.addCadlLibrary(StandardTestLibrary);
32
33 const serverHost: TestServerHost = {
34 ...fileSystem,
35 server: undefined!, // initialized later due to cycle
36 logMessages,
37 getOpenDocumentByURL(url) {
38 return documents.get(url);
39 },
40 getOpenDocument(path: string) {
41 return this.getOpenDocumentByURL(this.getURL(path));
42 },
43 addOrUpdateDocument(path: string, content: string) {
44 const url = this.getURL(path);
45 const version = documents.get(url)?.version ?? 1;
46 const document = TextDocument.create(url, "cadl", version, content);
47 documents.set(url, document);
48 return document;
49 },
50 getDiagnostics(path) {
51 return diagnostics.get(this.getURL(path)) ?? [];
52 },
53 sendDiagnostics(params) {
54 if (params.version && documents.get(params.uri)?.version !== params.version) {
55 return;
56 }
57 diagnostics.set(params.uri, params.diagnostics);
58 },
59 log(message) {
60 logMessages.push(message);
61 },
62 getURL(path: string) {
63 if (path.startsWith("untitled:")) {
64 return path;
65 }
66 return pathToFileURL(resolveVirtualPath(path)).href;
67 },
68 };
69
70 const rootUri = serverHost.getURL("./");
71 const server = createServer(serverHost);
72 await server.initialize({
73 rootUri: options?.caseInsensitiveFileSystem ? rootUri.toUpperCase() : rootUri,
74 capabilities: {},
75 processId: null,
76 workspaceFolders: null,
77 });
78 server.initialized({});
79 serverHost.server = server;
80 return serverHost;
81}
82
83/**
84 * Takes source code with a cursor position indicated by the given marker
85 * ("┆" by default), and returns the source without the marker along with
86 * the cursor position.
87 */
88export function extractCursor(
89 sourceWithCursor: string,
90 marker = "┆"
91): { source: string; pos: number } {
92 const pos = sourceWithCursor.indexOf(marker);
93 ok(pos >= 0, "marker not found");
94 const source = sourceWithCursor.replace(marker, "");
95 return { source, pos };
96}
97
98/**
99 * Takes source code with start and end positions indicated by given marker
100 * ("~~~" by default) and returns the source without the markers along with
101 * the start and end positions.
102 */
103export function extractSquiggles(
104 sourceWithSquiggles: string,
105 marker = "~~~"
106): { source: string; pos: number; end: number } {
107 const { source: sourceWithoutFistSquiggle, pos } = extractCursor(sourceWithSquiggles, marker);
108 const { source, pos: end } = extractCursor(sourceWithoutFistSquiggle, marker);
109 return { source, pos, end };
110}
111
112/**
113 * Extracts all identifiers marked with trailing empty comments from source
114 */
115export function getTestIdentifiers(source: string): IdentifierNode[] {
116 const identifiers: IdentifierNode[] = [];
117 const ast = parse(source);
118 visitChildren(ast, function visit(node) {
119 if (node.kind === SyntaxKind.Identifier) {
120 if (source.substring(node.end, node.end + "/**/".length) === "/**/") {
121 identifiers.push(node);
122 }
123 }
124 visitChildren(node, visit);
125 });
126 identifiers.sort((x, y) => x.pos - y.pos);
127 return identifiers;
128}