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

rokucommunity / brighterscript / #13266

01 Nov 2024 05:58PM UTC coverage: 89.076% (+0.9%) from 88.214%
#13266

push

web-flow
Merge 30be955de into 7cfaaa047

7248 of 8577 branches covered (84.51%)

Branch coverage included in aggregate %.

1095 of 1214 new or added lines in 28 files covered. (90.2%)

24 existing lines in 5 files now uncovered.

9640 of 10382 relevant lines covered (92.85%)

1783.24 hits per line

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

75.0
/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
            projectIdentifier?: string;
44
        }
45
    ) {
46
        this.logger = options?.logger ?? createLogger();
3✔
47
        this.projectIdentifier = options?.projectIdentifier ?? '';
3✔
48
    }
49

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

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

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

73
        this.activationDeferred.resolve();
3✔
74
        return activateResponse.data;
3✔
75
    }
76

77
    public logger: Logger;
78

79
    public isStandaloneProject = false;
3✔
80

81
    private activationDeferred = new Deferred();
3✔
82

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

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

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

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

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

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

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

118
    /**
119
     * A unique name for this project used in logs to help keep track of everything
120
     */
121
    public projectIdentifier: string;
122

123
    /**
124
     * The path to the workspace where this project resides. A workspace can have multiple projects (by adding a bsconfig.json to each folder).
125
     * Defaults to `.projectPath` if not set
126
     */
127
    public workspaceFolder: string;
128

129
    /**
130
     * Promise that resolves when the project finishes activating
131
     * @returns a promise that resolves when the project finishes activating
132
     */
133
    public whenActivated() {
NEW
134
        return this.activationDeferred.promise;
×
135
    }
136

137

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

147
    /**
148
     * Cancel any active validation that's running
149
     */
150
    public async cancelValidate() {
NEW
151
        const response = await this.messageHandler.sendRequest<void>('cancelValidate');
×
NEW
152
        return response.data;
×
153
    }
154

155
    public async getDiagnostics() {
156
        const response = await this.messageHandler.sendRequest<LspDiagnostic[]>('getDiagnostics');
1✔
157
        return response.data;
1✔
158
    }
159

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

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

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

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

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

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

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

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

211
    public async getWorkspaceSymbol(): Promise<WorkspaceSymbol[]> {
NEW
212
        return this.sendStandardRequest<WorkspaceSymbol[]>('getWorkspaceSymbol');
×
213
    }
214

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

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

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

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

232
    private processRequest(request: WorkerMessage) {
233

234
    }
235

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

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

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

262
    public disposables: LspProject['disposables'] = [];
3✔
263

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

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