Skip to content

Commit 386fff2

Browse files
committed
Custom binary encoding for symbols and types
1 parent 04b0181 commit 386fff2

File tree

12 files changed

+252
-131
lines changed

12 files changed

+252
-131
lines changed

_api/src/async/api.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import {
88
Type as BaseType,
99
} from "../base/api.ts";
1010
import type {
11-
ProjectData,
12-
SymbolData,
13-
TypeData,
14-
} from "../types.ts";
11+
ProjectResponse,
12+
SymbolResponse,
13+
TypeResponse,
14+
} from "../base/proto.ts";
1515
import { Client } from "./client.ts";
1616
import { startLSPServer } from "./lsp.ts";
1717

@@ -50,7 +50,7 @@ export class API implements BaseAPI<true> {
5050
export class Project extends BaseProject<true> {
5151
private client: Client;
5252

53-
constructor(client: Client, data: ProjectData) {
53+
constructor(client: Client, data: ProjectResponse) {
5454
super(data);
5555
this.client = client;
5656
}
@@ -69,7 +69,7 @@ export class Symbol extends BaseSymbol<true> {
6969
private client: Client;
7070
private project: Project;
7171

72-
constructor(client: Client, project: Project, data: SymbolData) {
72+
constructor(client: Client, project: Project, data: SymbolResponse) {
7373
super(data);
7474
this.client = client;
7575
this.project = project;
@@ -84,7 +84,7 @@ export class Symbol extends BaseSymbol<true> {
8484
export class Type extends BaseType<true> {
8585
private client: Client;
8686

87-
constructor(client: Client, data: TypeData) {
87+
constructor(client: Client, data: TypeResponse) {
8888
super(data);
8989
this.client = client;
9090
}

_api/src/base/api.ts

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import { SymbolFlags } from "#symbolFlags";
22
import type { SourceFile as SourceFileNode } from "../ast/ast.ts";
33
import { RemoteNode } from "../ast/node.ts";
4+
import type { MaybeAsync } from "../types.ts";
45
import type {
5-
MaybeAsync,
6-
ParsedCommandLine,
7-
ProjectData,
8-
SymbolData,
9-
TypeData,
10-
} from "../types.ts";
6+
ConfigResponse,
7+
ProjectResponse,
8+
} from "./proto.ts";
119

1210
export interface APIOptions {
1311
tsserverPath: string;
@@ -16,7 +14,7 @@ export interface APIOptions {
1614
}
1715

1816
export interface API<Async extends boolean> {
19-
parseConfigFile(fileName: string): MaybeAsync<Async, ParsedCommandLine>;
17+
parseConfigFile(fileName: string): MaybeAsync<Async, ConfigResponse>;
2018
loadProject(configFileName: string): MaybeAsync<Async, Project<Async>>;
2119
}
2220

@@ -26,20 +24,20 @@ export abstract class Project<Async extends boolean> {
2624
compilerOptions!: Record<string, unknown>;
2725
rootFiles!: readonly string[];
2826

29-
constructor(data: ProjectData) {
27+
constructor(data: ProjectResponse) {
3028
this.id = data.id;
3129
this.loadData(data);
3230
}
3331

34-
loadData(data: ProjectData): void {
32+
loadData(data: ProjectResponse): void {
3533
this.configFileName = data.configFileName;
3634
this.compilerOptions = data.compilerOptions;
3735
this.rootFiles = data.rootFiles;
3836
}
3937

4038
abstract reload(): MaybeAsync<Async, void>;
4139
abstract getSourceFile(fileName: string): MaybeAsync<Async, SourceFileNode | undefined>;
42-
abstract getSymbolAtPosition(fileName: string, position: number): MaybeAsync<Async, Symbol<Async> | undefined>;
40+
abstract getSymbolAtPosition(fileName: string, position: number): MaybeAsync<Async, Symbol | undefined>;
4341
}
4442

4543
export abstract class SourceFile extends RemoteNode {
@@ -51,28 +49,46 @@ export abstract class SourceFile extends RemoteNode {
5149

5250
export { SymbolFlags };
5351

54-
export abstract class Symbol<Async extends boolean> {
55-
protected id: number;
56-
name: string;
57-
flags: SymbolFlags;
58-
checkFlags: number;
52+
export abstract class Symbol {
53+
private data: Uint8Array;
54+
private view: DataView;
55+
private decoder: TextDecoder;
5956

60-
constructor(data: SymbolData) {
61-
this.id = data.id;
62-
this.name = data.name;
63-
this.flags = data.flags;
64-
this.checkFlags = data.checkFlags;
57+
constructor(data: Uint8Array, decoder: TextDecoder) {
58+
this.data = data;
59+
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
60+
this.decoder = decoder;
61+
}
62+
63+
get id(): number {
64+
return this.view.getUint32(0, true);
65+
}
66+
67+
get flags(): number {
68+
return this.view.getUint32(4, true);
69+
}
70+
71+
get checkFlags(): number {
72+
return this.view.getUint32(8, true);
6573
}
6674

67-
abstract getType(): MaybeAsync<Async, Type<Async> | undefined>;
75+
get name(): string {
76+
return this.decoder.decode(this.data.subarray(12));
77+
}
6878
}
6979

7080
export abstract class Type<Async extends boolean> {
71-
protected id: number;
72-
flags: number;
81+
private view: DataView;
7382

74-
constructor(data: TypeData) {
75-
this.id = data.id;
76-
this.flags = data.flags;
83+
constructor(data: Uint8Array) {
84+
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
85+
}
86+
87+
get id(): number {
88+
return this.view.getUint32(0, true);
89+
}
90+
91+
get flags(): number {
92+
return this.view.getUint32(4, true);
7793
}
7894
}

_api/src/base/binary.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type {
2+
GetSymbolAtPositionRequest,
3+
GetTypeOfSymbolRequest,
4+
SymbolResponse,
5+
} from "./proto.ts";
6+
7+
export function encodeGetSymbolAtPositionRequest(projectId: number, fileName: string, position: number, encoder: TextEncoder): Uint8Array {
8+
// assume ASCII filename
9+
const asciiLength = fileName.length;
10+
const result = new Uint8Array(2 + 4 + asciiLength);
11+
const view = new DataView(result.buffer);
12+
view.setUint16(0, projectId, true);
13+
view.setUint32(2, position, true);
14+
const { read } = encoder.encodeInto(fileName, result.subarray(2 + 4));
15+
// check if ASCII assumption was correct
16+
if (read !== asciiLength) {
17+
const encodedFileName = encoder.encode(fileName);
18+
const newResult = new Uint8Array(2 + 4 + encodedFileName.length);
19+
newResult.set(result.subarray(0, 2 + 4));
20+
newResult.set(encodedFileName, 2 + 4);
21+
return newResult;
22+
}
23+
24+
return result;
25+
}
26+
27+
export function encodeGetTypeOfSymbolRequest(projectId: number, symbolId: number): Uint8Array {
28+
const result = new Uint8Array(6);
29+
const view = new DataView(result.buffer);
30+
view.setUint16(0, projectId, true);
31+
view.setUint32(2, symbolId, true);
32+
return result;
33+
}

_api/src/base/proto.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export interface ConfigResponse {
2+
options: Record<string, unknown>;
3+
fileNames: string[];
4+
}
5+
6+
export interface ProjectResponse {
7+
id: number;
8+
configFileName: string;
9+
compilerOptions: Record<string, unknown>;
10+
rootFiles: string[];
11+
}
12+
13+
export interface GetSymbolAtPositionRequest {
14+
project: number;
15+
fileName: string;
16+
position: number;
17+
}
18+
19+
export interface SymbolResponse {
20+
id: number;
21+
name: string;
22+
flags: number;
23+
checkFlags: number;
24+
}
25+
26+
export interface GetTypeOfSymbolRequest {
27+
symbol: number;
28+
}
29+
30+
export interface TypeResponse {
31+
id: number;
32+
flags: number;
33+
}

_api/src/bench.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { API } from "./sync/api.ts";
3535
if (isIdentifier(node)) {
3636
const symbol = project.getSymbolAtPosition("checker.ts", node.pos);
3737
if (symbol?.flags! & SymbolFlags.Value) {
38-
symbol?.getType();
38+
project.getTypeOfSymbol(symbol!);
3939
}
4040
}
4141
node.forEachChild(child => visitNode(child));

_api/src/sync/api.ts

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import {
88
Type as BaseType,
99
} from "../base/api.ts";
1010
import type {
11-
FileSystemEntries,
12-
ParsedCommandLine,
13-
ProjectData,
14-
SymbolData,
15-
TypeData,
16-
} from "../types.ts";
11+
ConfigResponse,
12+
ProjectResponse,
13+
SymbolResponse,
14+
TypeResponse,
15+
} from "../base/proto.ts";
1716
import { Client } from "./client.ts";
1817

1918
export interface APIOptions extends BaseAPIOptions {
@@ -25,7 +24,7 @@ export class API implements BaseAPI<false> {
2524
this.client = new Client(options);
2625
}
2726

28-
parseConfigFile(fileName: string): ParsedCommandLine {
27+
parseConfigFile(fileName: string): ConfigResponse {
2928
return this.client.request("parseConfigFile", { fileName });
3029
}
3130

@@ -42,7 +41,7 @@ export class API implements BaseAPI<false> {
4241
export class Project extends BaseProject<false> {
4342
private client: Client;
4443

45-
constructor(client: Client, data: ProjectData) {
44+
constructor(client: Client, data: ProjectResponse) {
4645
super(data);
4746
this.client = client;
4847
}
@@ -60,14 +59,19 @@ export class Project extends BaseProject<false> {
6059
getSymbolAtPosition(fileName: string, position: number): Symbol | undefined;
6160
getSymbolAtPosition(...params: [fileName: string, position: number] | [readonly { fileName: string; position: number; }[]]): Symbol | undefined | (Symbol | undefined)[] {
6261
if (params.length === 2) {
63-
const data = this.client.request("getSymbolAtPosition", { project: this.id, fileName: params[0], position: params[1] });
64-
return data ? new Symbol(this.client, this, data) : undefined;
62+
const data = this.client.getSymbolAtPosition(this.id, params[0], params[1]);
63+
return data.length ? new Symbol(this.client, data) : undefined;
6564
}
6665
else {
67-
const data = this.client.request("getSymbolAtPosition", params[0].map(({ fileName, position }) => ({ project: this.id, fileName, position })));
68-
return data.map((d: SymbolData | null) => d ? new Symbol(this.client, this, d) : undefined);
66+
// const data = this.client.request("getSymbolAtPosition", params[0].map(({ fileName, position }) => ({ project: this.id, fileName, position })));
67+
// return data.map((d: SymbolResponse | null) => d ? new Symbol(this.client, this, d) : undefined);
6968
}
7069
}
70+
71+
getTypeOfSymbol(symbol: Symbol): Type | undefined {
72+
const data = this.client.getTypeOfSymbol(this.id, symbol.id);
73+
return data ? new Type(this.client, data) : undefined;
74+
}
7175
}
7276

7377
export class SourceFile extends BaseSourceFile {
@@ -80,25 +84,18 @@ export class SourceFile extends BaseSourceFile {
8084
}
8185
}
8286

83-
export class Symbol extends BaseSymbol<false> {
87+
export class Symbol extends BaseSymbol {
8488
private client: Client;
85-
private project: Project;
8689

87-
constructor(client: Client, project: Project, data: SymbolData) {
88-
super(data);
90+
constructor(client: Client, data: Uint8Array) {
91+
super(data, client.decoder);
8992
this.client = client;
90-
this.project = project;
91-
}
92-
93-
getType(): Type | undefined {
94-
const data = this.client.request("getTypeOfSymbol", { project: this.project.id, symbol: this.id });
95-
return data ? new Type(this.client, data) : undefined;
9693
}
9794
}
9895

9996
export class Type extends BaseType<false> {
10097
private client: Client;
101-
constructor(client: Client, data: TypeData) {
98+
constructor(client: Client, data: Uint8Array) {
10299
super(data);
103100
this.client = client;
104101
}

_api/src/sync/client.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { SyncRpcChannel } from "libsyncrpc";
2+
import {
3+
encodeGetSymbolAtPositionRequest,
4+
encodeGetTypeOfSymbolRequest,
5+
} from "../base/binary.ts";
26
import type { FileSystemEntries } from "../types.ts";
37

48
export interface ClientOptions {
@@ -16,8 +20,8 @@ export interface ClientOptions {
1620

1721
export class Client {
1822
private channel: SyncRpcChannel;
19-
private decoder = new TextDecoder();
20-
private encoder = new TextEncoder();
23+
decoder: TextDecoder = new TextDecoder();
24+
encoder: TextEncoder = new TextEncoder();
2125

2226
constructor(options: ClientOptions) {
2327
this.channel = new SyncRpcChannel(options.tsserverPath, [
@@ -33,14 +37,20 @@ export class Client {
3337
callbacks: Object.keys(options.fs ?? {}),
3438
}),
3539
);
40+
for (const callback in options.fs) {
41+
this.channel.registerCallback(callback, (_, arg) => {
42+
const result = options.fs?.[callback as keyof typeof options.fs]?.(JSON.parse(this.decoder.decode(arg)));
43+
return JSON.stringify(result) ?? "";
44+
});
45+
}
3646
}
3747

38-
registerCallback(method: string, callback: (payload: any) => any): void {
39-
this.channel.registerCallback(method, (_, arg) => {
40-
const result = callback(JSON.parse(this.decoder.decode(arg)));
41-
return JSON.stringify(result) ?? "";
42-
});
43-
this.channel.requestSync("registerCallback", method);
48+
getSymbolAtPosition(projectId: number, fileName: string, position: number): Uint8Array {
49+
return this.channel.requestBinarySync("getSymbolAtPosition", encodeGetSymbolAtPositionRequest(projectId, fileName, position, this.encoder));
50+
}
51+
52+
getTypeOfSymbol(projectId: number, symbolId: number): Uint8Array {
53+
return this.channel.requestBinarySync("getTypeOfSymbol", encodeGetTypeOfSymbolRequest(projectId, symbolId));
4454
}
4555

4656
request(method: string, payload: any): any {

_api/src/types.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,3 @@ export interface FileSystemEntries {
44
files: string[];
55
directories: string[];
66
}
7-
8-
export interface ParsedCommandLine {
9-
options: Record<string, unknown>;
10-
fileNames: string[];
11-
}
12-
13-
export interface ProjectData {
14-
id: number;
15-
configFileName: string;
16-
compilerOptions: Record<string, unknown>;
17-
rootFiles: string[];
18-
}
19-
20-
export interface SymbolData {
21-
id: number;
22-
name: string;
23-
flags: number;
24-
checkFlags: number;
25-
}
26-
27-
export interface TypeData {
28-
id: number;
29-
flags: number;
30-
}

0 commit comments

Comments
 (0)