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

rokucommunity / brighterscript / #13807

02 Apr 2024 06:30PM UTC coverage: 88.997% (+1.6%) from 87.413%
#13807

push

TwitchBronBron
Fix some busy status tracking

6383 of 7614 branches covered (83.83%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

137 existing lines in 5 files now uncovered.

9219 of 9917 relevant lines covered (92.96%)

1700.14 hits per line

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

68.89
/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

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

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

37
export class WorkerThreadProject implements LspProject {
1✔
38

39
    public async activate(options: ProjectConfig) {
40
        this.activateOptions = options;
3✔
41
        this.projectPath = options.projectPath ? util.standardizePath(options.projectPath) : options.projectPath;
3!
42
        this.workspaceFolder = options.workspaceFolder ? util.standardizePath(options.workspaceFolder) : options.workspaceFolder;
3✔
43
        this.projectNumber = options.projectNumber;
3✔
44
        this.bsconfigPath = options.bsconfigPath ? util.standardizePath(options.bsconfigPath) : options.bsconfigPath;
3!
45

46
        // start a new worker thread or get an unused existing thread
47
        this.worker = workerPool.getWorker();
3✔
48
        this.messageHandler = new MessageHandler<LspProject>({
3✔
49
            name: 'MainThread',
50
            port: this.worker,
51
            onRequest: this.processRequest.bind(this),
52
            onUpdate: this.processUpdate.bind(this)
53
        });
54
        this.disposables.push(this.messageHandler);
3✔
55

56
        const activateResponse = await this.messageHandler.sendRequest<ActivateResponse>('activate', { data: [options] });
3✔
57
        this.bsconfigPath = activateResponse.data.bsconfigPath;
3✔
58
        this.rootDir = activateResponse.data.rootDir;
3✔
59
        this.filePatterns = activateResponse.data.filePatterns;
3✔
60

61
        //populate a few properties with data from the thread so we can use them for some synchronous checks
62
        this.filePaths = new Set(await this.getFilePaths());
3✔
63

64
        this.activationDeferred.resolve();
3✔
65
        return activateResponse.data;
3✔
66
    }
67

68
    private activationDeferred = new Deferred();
3✔
69

70
    /**
71
     * Options used to activate this project
72
     */
73
    public activateOptions: ProjectConfig;
74

75
    /**
76
     * The root directory of the project
77
     */
78
    public rootDir: string;
79

80
    /**
81
     * The file patterns from bsconfig.json that were used to find all files for this project
82
     */
83
    public filePatterns: string[];
84

85
    /**
86
     * Path to a bsconfig.json file that will be used for this project
87
     */
88
    public bsconfigPath?: string;
89

90
    /**
91
     * The worker thread where the actual project will execute
92
     */
93
    private worker: Worker;
94

95
    /**
96
     * The path to where the project resides
97
     */
98
    public projectPath: string;
99

100
    /**
101
     * A unique number for this project, generated during this current language server session. Mostly used so we can identify which project is doing logging
102
     */
103
    public projectNumber: number;
104

105
    /**
106
     * The path to the workspace where this project resides. A workspace can have multiple projects (by adding a bsconfig.json to each folder).
107
     * Defaults to `.projectPath` if not set
108
     */
109
    public workspaceFolder: string;
110

111
    /**
112
     * Promise that resolves when the project finishes activating
113
     * @returns a promise that resolves when the project finishes activating
114
     */
115
    public whenActivated() {
UNCOV
116
        return this.activationDeferred.promise;
×
117
    }
118

119

120
    /**
121
     * Validate the project. This will trigger a full validation on any scopes that were changed since the last validation,
122
     * and will also eventually emit a new 'diagnostics' event that includes all diagnostics for the project
123
     */
124
    public async validate() {
UNCOV
125
        const response = await this.messageHandler.sendRequest<void>('validate');
×
126
        return response.data;
×
127
    }
128

129
    /**
130
     * Cancel any active validation that's running
131
     */
132
    public async cancelValidate() {
UNCOV
133
        const response = await this.messageHandler.sendRequest<void>('cancelValidate');
×
UNCOV
134
        return response.data;
×
135
    }
136

137
    /**
138
     * A local copy of all the file paths loaded in this program, stored in lower case.
139
     * This needs to stay in sync with any files we add/delete in the worker thread so we can keep doing in-process `.hasFile()` checks.
140
     */
141
    private filePaths: Set<string>;
142

143
    public async getDiagnostics() {
144
        const response = await this.messageHandler.sendRequest<LspDiagnostic[]>('getDiagnostics');
1✔
145
        return response.data;
1✔
146
    }
147

148
    /**
149
     * Does this project have the specified file. Should only be called after `.activate()` has finished.
150
     */
151
    public hasFile(srcPath: string) {
UNCOV
152
        return this.filePaths.has(srcPath.toLowerCase());
×
153
    }
154

155
    /**
156
     * 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
157
     * during the program's lifecycle flow
158
     */
159
    public async applyFileChanges(documentActions: DocumentAction[]): Promise<DocumentActionWithStatus[]> {
UNCOV
160
        const response = await this.messageHandler.sendRequest<DocumentActionWithStatus[]>('applyFileChanges', {
×
161
            data: [documentActions]
162
        });
UNCOV
163
        return response.data;
×
164
    }
165

166
    /**
167
     * Get the list of all file paths that are currently loaded in the project
168
     */
169
    public async getFilePaths() {
170
        return (await this.messageHandler.sendRequest<string[]>('getFilePaths')).data;
3✔
171
    }
172

173
    /**
174
     * Send a request with the standard structure
175
     * @param name the name of the request
176
     * @param data the array of data to send
177
     * @returns the response from the request
178
     */
179
    private async sendStandardRequest<T>(name: string, ...data: any[]) {
UNCOV
180
        const response = await this.messageHandler.sendRequest<T>(name as any, {
×
181
            data: data
182
        });
183
        return response.data;
×
184
    }
185

186
    /**
187
     * Get the full list of semantic tokens for the given file path
188
     */
189
    public async getSemanticTokens(options: { srcPath: string }) {
UNCOV
190
        return this.sendStandardRequest<SemanticToken[]>('getSemanticTokens', options);
×
191
    }
192

193
    public async transpileFile(options: { srcPath: string }) {
UNCOV
194
        return this.sendStandardRequest<FileTranspileResult>('transpileFile', options);
×
195
    }
196

197
    public async getHover(options: { srcPath: string; position: Position }): Promise<Hover[]> {
UNCOV
198
        return this.sendStandardRequest<Hover[]>('getHover', options);
×
199
    }
200

201
    public async getDefinition(options: { srcPath: string; position: Position }): Promise<Location[]> {
UNCOV
202
        return this.sendStandardRequest<Location[]>('getDefinition', options);
×
203
    }
204

205
    public async getSignatureHelp(options: { srcPath: string; position: Position }): Promise<SignatureInfoObj[]> {
UNCOV
206
        return this.sendStandardRequest<SignatureInfoObj[]>('getSignatureHelp', options);
×
207
    }
208

209
    public async getDocumentSymbol(options: { srcPath: string }): Promise<DocumentSymbol[]> {
UNCOV
210
        return this.sendStandardRequest<DocumentSymbol[]>('getDocumentSymbol', options);
×
211
    }
212

213
    public async getWorkspaceSymbol(): Promise<WorkspaceSymbol[]> {
UNCOV
214
        return this.sendStandardRequest<WorkspaceSymbol[]>('getWorkspaceSymbol');
×
215
    }
216

217
    public async getReferences(options: { srcPath: string; position: Position }): Promise<Location[]> {
UNCOV
218
        return this.sendStandardRequest<Location[]>('getReferences', options);
×
219
    }
220

221
    public async getCodeActions(options: { srcPath: string; range: Range }): Promise<CodeAction[]> {
UNCOV
222
        return this.sendStandardRequest<CodeAction[]>('getCodeActions', options);
×
223
    }
224

225
    public async getCompletions(options: { srcPath: string; position: Position }): Promise<CompletionList> {
UNCOV
226
        return this.sendStandardRequest<CompletionList>('getCompletions', options);
×
227
    }
228

229
    /**
230
     * Handles request/response/update messages from the worker thread
231
     */
232
    private messageHandler: MessageHandler<LspProject>;
233

234
    private processRequest(request: WorkerMessage) {
235

236
    }
237

238
    private processUpdate(update: WorkerMessage) {
239
        //for now, all updates are treated like "events"
240
        this.emit(update.name as any, update.data);
3✔
241
    }
242

243
    public on(eventName: 'critical-failure', handler: (data: { message: string }) => void);
244
    public on(eventName: 'diagnostics', handler: (data: { diagnostics: LspDiagnostic[] }) => MaybePromise<void>);
245
    public on(eventName: 'all', handler: (eventName: string, data: any) => MaybePromise<void>);
246
    public on(eventName: string, handler: (...args: any[]) => MaybePromise<void>) {
247
        this.emitter.on(eventName, handler as any);
1✔
248
        return () => {
1✔
UNCOV
249
            this.emitter.removeListener(eventName, handler as any);
×
250
        };
251
    }
252

253
    private emit(eventName: 'critical-failure', data: { message: string });
254
    private emit(eventName: 'diagnostics', data: { diagnostics: LspDiagnostic[] });
255
    private async emit(eventName: string, data?) {
256
        //emit these events on next tick, otherwise they will be processed immediately which could cause issues
257
        await util.sleep(0);
3✔
258
        this.emitter.emit(eventName, data);
3✔
259
        //emit the 'all' event
260
        this.emitter.emit('all', eventName, data);
3✔
261
    }
262
    private emitter = new EventEmitter();
3✔
263

264
    public disposables: LspProject['disposables'] = [];
3✔
265

266
    public dispose() {
267
        for (let disposable of this.disposables ?? []) {
3!
268
            disposable?.dispose?.();
4!
269
        }
270
        this.disposables = [];
3✔
271

272
        //move the worker back to the pool so it can be used again
273
        if (this.worker) {
3!
274
            workerPool.releaseWorker(this.worker);
3✔
275
        }
276
        this.emitter?.removeAllListeners();
3!
277
    }
278
}
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

© 2026 Coveralls, Inc