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

rokucommunity / brighterscript / #13062

23 Sep 2024 02:51PM UTC coverage: 88.769% (+0.8%) from 87.933%
#13062

push

web-flow
Merge 373852d93 into 56dcaaa63

6620 of 7920 branches covered (83.59%)

Branch coverage included in aggregate %.

1025 of 1156 new or added lines in 28 files covered. (88.67%)

24 existing lines in 5 files now uncovered.

9456 of 10190 relevant lines covered (92.8%)

1711.5 hits per line

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

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

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

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

39
export class WorkerThreadProject implements LspProject {
1✔
40
    public constructor(
41
        options?: {
42
            logger?: Logger;
43
        }
44
    ) {
45
        this.logger = options?.logger ?? createLogger();
3✔
46
    }
47

48
    public async activate(options: ProjectConfig) {
49
        this.activateOptions = options;
3✔
50
        this.projectPath = options.projectPath ? util.standardizePath(options.projectPath) : options.projectPath;
3!
51
        this.workspaceFolder = options.workspaceFolder ? util.standardizePath(options.workspaceFolder) : options.workspaceFolder;
3✔
52
        this.projectNumber = options.projectNumber;
3✔
53
        this.bsconfigPath = options.bsconfigPath ? util.standardizePath(options.bsconfigPath) : options.bsconfigPath;
3!
54

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

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

71
        this.activationDeferred.resolve();
3✔
72
        return activateResponse.data;
3✔
73
    }
74

75
    public logger: Logger;
76

77
    public isStandaloneProject = false;
3✔
78

79
    private activationDeferred = new Deferred();
3✔
80

81
    /**
82
     * Options used to activate this project
83
     */
84
    public activateOptions: ProjectConfig;
85

86
    /**
87
     * The root directory of the project
88
     */
89
    public rootDir: string;
90

91
    /**
92
     * The file patterns from bsconfig.json that were used to find all files for this project
93
     */
94
    public filePatterns: string[];
95

96
    /**
97
     * Path to a bsconfig.json file that will be used for this project
98
     */
99
    public bsconfigPath?: string;
100

101
    /**
102
     * The worker thread where the actual project will execute
103
     */
104
    private worker: Worker;
105

106
    /**
107
     * The path to where the project resides
108
     */
109
    public projectPath: string;
110

111
    /**
112
     * A unique number for this project, generated during this current language server session. Mostly used so we can identify which project is doing logging
113
     */
114
    public projectNumber: number;
115

116
    /**
117
     * The path to the workspace where this project resides. A workspace can have multiple projects (by adding a bsconfig.json to each folder).
118
     * Defaults to `.projectPath` if not set
119
     */
120
    public workspaceFolder: string;
121

122
    /**
123
     * Promise that resolves when the project finishes activating
124
     * @returns a promise that resolves when the project finishes activating
125
     */
126
    public whenActivated() {
NEW
127
        return this.activationDeferred.promise;
×
128
    }
129

130

131
    /**
132
     * Validate the project. This will trigger a full validation on any scopes that were changed since the last validation,
133
     * and will also eventually emit a new 'diagnostics' event that includes all diagnostics for the project
134
     */
135
    public async validate() {
NEW
136
        const response = await this.messageHandler.sendRequest<void>('validate');
×
NEW
137
        return response.data;
×
138
    }
139

140
    /**
141
     * Cancel any active validation that's running
142
     */
143
    public async cancelValidate() {
NEW
144
        const response = await this.messageHandler.sendRequest<void>('cancelValidate');
×
NEW
145
        return response.data;
×
146
    }
147

148
    public async getDiagnostics() {
149
        const response = await this.messageHandler.sendRequest<LspDiagnostic[]>('getDiagnostics');
1✔
150
        return response.data;
1✔
151
    }
152

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

164
    /**
165
     * Send a request with the standard structure
166
     * @param name the name of the request
167
     * @param data the array of data to send
168
     * @returns the response from the request
169
     */
170
    private async sendStandardRequest<T>(name: string, ...data: any[]) {
NEW
171
        const response = await this.messageHandler.sendRequest<T>(name as any, {
×
172
            data: data
173
        });
NEW
174
        return response.data;
×
175
    }
176

177
    /**
178
     * Get the full list of semantic tokens for the given file path
179
     */
180
    public async getSemanticTokens(options: { srcPath: string }) {
NEW
181
        return this.sendStandardRequest<SemanticToken[]>('getSemanticTokens', options);
×
182
    }
183

184
    public async transpileFile(options: { srcPath: string }) {
NEW
185
        return this.sendStandardRequest<FileTranspileResult>('transpileFile', options);
×
186
    }
187

188
    public async getHover(options: { srcPath: string; position: Position }): Promise<Hover[]> {
NEW
189
        return this.sendStandardRequest<Hover[]>('getHover', options);
×
190
    }
191

192
    public async getDefinition(options: { srcPath: string; position: Position }): Promise<Location[]> {
NEW
193
        return this.sendStandardRequest<Location[]>('getDefinition', options);
×
194
    }
195

196
    public async getSignatureHelp(options: { srcPath: string; position: Position }): Promise<SignatureInfoObj[]> {
NEW
197
        return this.sendStandardRequest<SignatureInfoObj[]>('getSignatureHelp', options);
×
198
    }
199

200
    public async getDocumentSymbol(options: { srcPath: string }): Promise<DocumentSymbol[]> {
NEW
201
        return this.sendStandardRequest<DocumentSymbol[]>('getDocumentSymbol', options);
×
202
    }
203

204
    public async getWorkspaceSymbol(): Promise<WorkspaceSymbol[]> {
NEW
205
        return this.sendStandardRequest<WorkspaceSymbol[]>('getWorkspaceSymbol');
×
206
    }
207

208
    public async getReferences(options: { srcPath: string; position: Position }): Promise<Location[]> {
NEW
209
        return this.sendStandardRequest<Location[]>('getReferences', options);
×
210
    }
211

212
    public async getCodeActions(options: { srcPath: string; range: Range }): Promise<CodeAction[]> {
NEW
213
        return this.sendStandardRequest<CodeAction[]>('getCodeActions', options);
×
214
    }
215

216
    public async getCompletions(options: { srcPath: string; position: Position }): Promise<CompletionList> {
NEW
217
        return this.sendStandardRequest<CompletionList>('getCompletions', options);
×
218
    }
219

220
    /**
221
     * Handles request/response/update messages from the worker thread
222
     */
223
    private messageHandler: MessageHandler<LspProject>;
224

225
    private processRequest(request: WorkerMessage) {
226

227
    }
228

229
    private processUpdate(update: WorkerMessage) {
230
        //for now, all updates are treated like "events"
231
        this.emit(update.name as any, update.data);
9✔
232
    }
233

234
    public on(eventName: 'critical-failure', handler: (data: { message: string }) => void);
235
    public on(eventName: 'diagnostics', handler: (data: { diagnostics: LspDiagnostic[] }) => MaybePromise<void>);
236
    public on(eventName: 'all', handler: (eventName: string, data: any) => MaybePromise<void>);
237
    public on(eventName: string, handler: (...args: any[]) => MaybePromise<void>) {
238
        this.emitter.on(eventName, handler as any);
1✔
239
        return () => {
1✔
NEW
240
            this.emitter.removeListener(eventName, handler as any);
×
241
        };
242
    }
243

244
    private emit(eventName: 'critical-failure', data: { message: string });
245
    private emit(eventName: 'diagnostics', data: { diagnostics: LspDiagnostic[] });
246
    private async emit(eventName: string, data?) {
247
        //emit these events on next tick, otherwise they will be processed immediately which could cause issues
248
        await util.sleep(0);
9✔
249
        this.emitter.emit(eventName, data);
9✔
250
        //emit the 'all' event
251
        this.emitter.emit('all', eventName, data);
9✔
252
    }
253
    private emitter = new EventEmitter();
3✔
254

255
    public disposables: LspProject['disposables'] = [];
3✔
256

257
    public dispose() {
258
        for (let disposable of this.disposables ?? []) {
3!
259
            disposable?.dispose?.();
4!
260
        }
261
        this.disposables = [];
3✔
262

263
        //move the worker back to the pool so it can be used again
264
        if (this.worker) {
3!
265
            workerPool.releaseWorker(this.worker);
3✔
266
        }
267
        this.emitter?.removeAllListeners();
3!
268
    }
269
}
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