• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / brighterscript / #13312

22 Nov 2024 10:12PM UTC coverage: 89.121% (+0.9%) from 88.237%
#13312

push

web-flow
Merge fea67a504 into f6dedf73f

7359 of 8706 branches covered (84.53%)

Branch coverage included in aggregate %.

1121 of 1237 new or added lines in 29 files covered. (90.62%)

24 existing lines in 5 files now uncovered.

9722 of 10460 relevant lines covered (92.94%)

1825.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.38
/src/lsp/worker/WorkerThreadProject.ts
1
import * as EventEmitter from 'eventemitter3';
1✔
2
import { Worker } from 'worker_threads';
1✔
3
import type { WorkerMessage } from './MessageHandler';
4
import { MessageHandler } from './MessageHandler';
1✔
5
import util from '../../util';
1✔
6
import type { LspDiagnostic, ActivateResponse, ProjectConfig } from '../LspProject';
7
import { type LspProject } from '../LspProject';
8
import { isMainThread, parentPort } from 'worker_threads';
1✔
9
import { WorkerThreadProjectRunner } from './WorkerThreadProjectRunner';
1✔
10
import { WorkerPool } from './WorkerPool';
1✔
11
import type { Hover, MaybePromise, SemanticToken } from '../../interfaces';
12
import type { DocumentAction, DocumentActionWithStatus } from '../DocumentManager';
13
import { Deferred } from '../../deferred';
1✔
14
import type { FileTranspileResult, SignatureInfoObj } from '../../Program';
15
import type { Position, Range, Location, DocumentSymbol, WorkspaceSymbol, CodeAction, CompletionList } from 'vscode-languageserver-protocol';
16
import type { Logger } from '../../logging';
17
import { createLogger } from '../../logging';
1✔
18
import * as fsExtra from 'fs-extra';
1✔
19

20
export const workerPool = new WorkerPool(() => {
1✔
21
    return new Worker(
1✔
22
        __filename,
23
        {
24
            //wire up ts-node if we're running in ts-node
25
            execArgv: /\.ts$/i.test(__filename)
26
                ? ['--require', 'ts-node/register']
1✔
27
                /* istanbul ignore next */
28
                : undefined
29
        }
30
    );
31
});
32

33
//if this script is running in a Worker, start the project runner
34
/* istanbul ignore next */
35
if (!isMainThread) {
36
    const runner = new WorkerThreadProjectRunner();
37
    runner.run(parentPort);
38
}
39

40
export class WorkerThreadProject implements LspProject {
1✔
41
    public constructor(
42
        options?: {
43
            logger?: Logger;
44
            projectIdentifier?: string;
45
        }
46
    ) {
47
        this.logger = options?.logger ?? createLogger();
4✔
48
        this.projectIdentifier = options?.projectIdentifier ?? '';
4✔
49
    }
50

51
    public async activate(options: ProjectConfig) {
52
        this.activateOptions = options;
4✔
53
        this.projectPath = options.projectPath ? util.standardizePath(options.projectPath) : options.projectPath;
4!
54
        this.workspaceFolder = options.workspaceFolder ? util.standardizePath(options.workspaceFolder) : options.workspaceFolder;
4✔
55
        this.projectNumber = options.projectNumber;
4✔
56
        this.bsconfigPath = options.bsconfigPath ? util.standardizePath(options.bsconfigPath) : options.bsconfigPath;
4!
57

58
        // start a new worker thread or get an unused existing thread
59
        this.worker = workerPool.getWorker();
4✔
60
        this.messageHandler = new MessageHandler<LspProject>({
4✔
61
            name: 'MainThread',
62
            port: this.worker,
63
            onRequest: this.processRequest.bind(this),
64
            onUpdate: this.processUpdate.bind(this)
65
        });
66
        this.disposables.push(this.messageHandler);
4✔
67

68
        const activateResponse = await this.messageHandler.sendRequest<ActivateResponse>('activate', { data: [options] });
4✔
69
        this.bsconfigPath = activateResponse.data.bsconfigPath;
4✔
70
        this.rootDir = activateResponse.data.rootDir;
4✔
71
        this.filePatterns = activateResponse.data.filePatterns;
4✔
72
        this.logger.logLevel = activateResponse.data.logLevel;
4✔
73

74
        //load the bsconfig file contents (used for performance optimizations externally)
75
        try {
4✔
76
            this.bsconfigFileContents = (await fsExtra.readFile(this.bsconfigPath)).toString();
4✔
77
        } catch { }
78

79

80
        this.activationDeferred.resolve();
4✔
81
        return activateResponse.data;
4✔
82
    }
83

84
    public logger: Logger;
85

86
    public isStandaloneProject = false;
4✔
87

88
    private activationDeferred = new Deferred();
4✔
89

90
    /**
91
     * Options used to activate this project
92
     */
93
    public activateOptions: ProjectConfig;
94

95
    /**
96
     * The root directory of the project
97
     */
98
    public rootDir: string;
99

100
    /**
101
     * The file patterns from bsconfig.json that were used to find all files for this project
102
     */
103
    public filePatterns: string[];
104

105
    /**
106
     * Path to a bsconfig.json file that will be used for this project
107
     */
108
    public bsconfigPath?: string;
109

110
    /**
111
     * The contents of the bsconfig.json file. This is used to detect when the bsconfig file has not actually been changed (even if the fs says it did).
112
     *
113
     * Only available after `.activate()` has completed.
114
     * @deprecated do not depend on this property. This will certainly be removed in a future release
115
     */
116
    bsconfigFileContents?: string;
117

118
    /**
119
     * The worker thread where the actual project will execute
120
     */
121
    private worker: Worker;
122

123
    /**
124
     * The path to where the project resides
125
     */
126
    public projectPath: string;
127

128
    /**
129
     * A unique number for this project, generated during this current language server session. Mostly used so we can identify which project is doing logging
130
     */
131
    public projectNumber: number;
132

133
    /**
134
     * A unique name for this project used in logs to help keep track of everything
135
     */
136
    public projectIdentifier: string;
137

138
    /**
139
     * The path to the workspace where this project resides. A workspace can have multiple projects (by adding a bsconfig.json to each folder).
140
     * Defaults to `.projectPath` if not set
141
     */
142
    public workspaceFolder: string;
143

144
    /**
145
     * Promise that resolves when the project finishes activating
146
     * @returns a promise that resolves when the project finishes activating
147
     */
148
    public whenActivated() {
149
        return this.activationDeferred.promise;
1✔
150
    }
151

152

153
    /**
154
     * Validate the project. This will trigger a full validation on any scopes that were changed since the last validation,
155
     * and will also eventually emit a new 'diagnostics' event that includes all diagnostics for the project
156
     */
157
    public async validate() {
NEW
158
        const response = await this.messageHandler.sendRequest<void>('validate');
×
NEW
159
        return response.data;
×
160
    }
161

162
    /**
163
     * Cancel any active validation that's running
164
     */
165
    public async cancelValidate() {
NEW
166
        const response = await this.messageHandler.sendRequest<void>('cancelValidate');
×
NEW
167
        return response.data;
×
168
    }
169

170
    public async getDiagnostics() {
171
        const response = await this.messageHandler.sendRequest<LspDiagnostic[]>('getDiagnostics');
1✔
172
        return response.data;
1✔
173
    }
174

175
    /**
176
     * Apply a series of file changes to the project. This is safe to call any time. Changes will be queued and flushed at the correct times
177
     * during the program's lifecycle flow
178
     */
179
    public async applyFileChanges(documentActions: DocumentAction[]): Promise<DocumentActionWithStatus[]> {
NEW
180
        const response = await this.messageHandler.sendRequest<DocumentActionWithStatus[]>('applyFileChanges', {
×
181
            data: [documentActions]
182
        });
NEW
183
        return response.data;
×
184
    }
185

186
    /**
187
     * Send a request with the standard structure
188
     * @param name the name of the request
189
     * @param data the array of data to send
190
     * @returns the response from the request
191
     */
192
    private async sendStandardRequest<T>(name: string, ...data: any[]) {
193
        const response = await this.messageHandler.sendRequest<T>(name as any, {
1✔
194
            data: data
195
        });
NEW
196
        return response.data;
×
197
    }
198

199
    /**
200
     * Get the full list of semantic tokens for the given file path
201
     */
202
    public async getSemanticTokens(options: { srcPath: string }) {
NEW
203
        return this.sendStandardRequest<SemanticToken[]>('getSemanticTokens', options);
×
204
    }
205

206
    public async transpileFile(options: { srcPath: string }) {
NEW
207
        return this.sendStandardRequest<FileTranspileResult>('transpileFile', options);
×
208
    }
209

210
    public async getHover(options: { srcPath: string; position: Position }): Promise<Hover[]> {
NEW
211
        return this.sendStandardRequest<Hover[]>('getHover', options);
×
212
    }
213

214
    public async getDefinition(options: { srcPath: string; position: Position }): Promise<Location[]> {
NEW
215
        return this.sendStandardRequest<Location[]>('getDefinition', options);
×
216
    }
217

218
    public async getSignatureHelp(options: { srcPath: string; position: Position }): Promise<SignatureInfoObj[]> {
NEW
219
        return this.sendStandardRequest<SignatureInfoObj[]>('getSignatureHelp', options);
×
220
    }
221

222
    public async getDocumentSymbol(options: { srcPath: string }): Promise<DocumentSymbol[]> {
NEW
223
        return this.sendStandardRequest<DocumentSymbol[]>('getDocumentSymbol', options);
×
224
    }
225

226
    public async getWorkspaceSymbol(): Promise<WorkspaceSymbol[]> {
227
        return this.sendStandardRequest<WorkspaceSymbol[]>('getWorkspaceSymbol');
1✔
228
    }
229

230
    public async getReferences(options: { srcPath: string; position: Position }): Promise<Location[]> {
NEW
231
        return this.sendStandardRequest<Location[]>('getReferences', options);
×
232
    }
233

234
    public async getCodeActions(options: { srcPath: string; range: Range }): Promise<CodeAction[]> {
NEW
235
        return this.sendStandardRequest<CodeAction[]>('getCodeActions', options);
×
236
    }
237

238
    public async getCompletions(options: { srcPath: string; position: Position }): Promise<CompletionList> {
NEW
239
        return this.sendStandardRequest<CompletionList>('getCompletions', options);
×
240
    }
241

242
    /**
243
     * Handles request/response/update messages from the worker thread
244
     */
245
    private messageHandler: MessageHandler<LspProject>;
246

247
    private processRequest(request: WorkerMessage) {
248

249
    }
250

251
    private processUpdate(update: WorkerMessage) {
252
        //for now, all updates are treated like "events"
253
        this.emit(update.name as any, update.data);
12✔
254
    }
255

256
    public on(eventName: 'critical-failure', handler: (data: { message: string }) => void);
257
    public on(eventName: 'diagnostics', handler: (data: { diagnostics: LspDiagnostic[] }) => MaybePromise<void>);
258
    public on(eventName: 'all', handler: (eventName: string, data: any) => MaybePromise<void>);
259
    public on(eventName: string, handler: (...args: any[]) => MaybePromise<void>) {
260
        this.emitter.on(eventName, handler as any);
2✔
261
        return () => {
2✔
NEW
262
            this.emitter.removeListener(eventName, handler as any);
×
263
        };
264
    }
265

266
    private emit(eventName: 'critical-failure', data: { message: string });
267
    private emit(eventName: 'diagnostics', data: { diagnostics: LspDiagnostic[] });
268
    private async emit(eventName: string, data?) {
269
        //emit these events on next tick, otherwise they will be processed immediately which could cause issues
270
        await util.sleep(0);
12✔
271
        this.emitter.emit(eventName, data);
12✔
272
        //emit the 'all' event
273
        this.emitter.emit('all', eventName, data);
12✔
274
    }
275
    private emitter = new EventEmitter();
4✔
276

277
    public disposables: LspProject['disposables'] = [];
4✔
278

279
    public dispose() {
280
        for (let disposable of this.disposables ?? []) {
4!
281
            disposable?.dispose?.();
6!
282
        }
283
        this.disposables = [];
4✔
284

285
        //move the worker back to the pool so it can be used again
286
        if (this.worker) {
4!
287
            workerPool.releaseWorker(this.worker);
4✔
288
        }
289
        this.emitter?.removeAllListeners();
4!
290
    }
291
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc