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

rokucommunity / brighterscript / #13809

03 Apr 2024 07:40PM UTC coverage: 89.065% (+1.7%) from 87.413%
#13809

push

TwitchBronBron
Prevent making standalone projects when unnecessary

6397 of 7624 branches covered (83.91%)

Branch coverage included in aggregate %.

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

134 existing lines in 5 files now uncovered.

9233 of 9925 relevant lines covered (93.03%)

1701.05 hits per line

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

68.97
/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
        this.activationDeferred.resolve();
3✔
62
        return activateResponse.data;
3✔
63
    }
64

65
    private activationDeferred = new Deferred();
3✔
66

67
    /**
68
     * Options used to activate this project
69
     */
70
    public activateOptions: ProjectConfig;
71

72
    /**
73
     * The root directory of the project
74
     */
75
    public rootDir: string;
76

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

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

87
    /**
88
     * The worker thread where the actual project will execute
89
     */
90
    private worker: Worker;
91

92
    /**
93
     * The path to where the project resides
94
     */
95
    public projectPath: string;
96

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

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

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

116

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

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

134
    public async getDiagnostics() {
135
        const response = await this.messageHandler.sendRequest<LspDiagnostic[]>('getDiagnostics');
1✔
136
        return response.data;
1✔
137
    }
138

139
    /**
140
     * 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
141
     * during the program's lifecycle flow
142
     */
143
    public async applyFileChanges(documentActions: DocumentAction[]): Promise<DocumentActionWithStatus[]> {
UNCOV
144
        const response = await this.messageHandler.sendRequest<DocumentActionWithStatus[]>('applyFileChanges', {
×
145
            data: [documentActions]
146
        });
UNCOV
147
        return response.data;
×
148
    }
149

150
    /**
151
     * Send a request with the standard structure
152
     * @param name the name of the request
153
     * @param data the array of data to send
154
     * @returns the response from the request
155
     */
156
    private async sendStandardRequest<T>(name: string, ...data: any[]) {
UNCOV
157
        const response = await this.messageHandler.sendRequest<T>(name as any, {
×
158
            data: data
159
        });
UNCOV
160
        return response.data;
×
161
    }
162

163
    /**
164
     * Get the full list of semantic tokens for the given file path
165
     */
166
    public async getSemanticTokens(options: { srcPath: string }) {
UNCOV
167
        return this.sendStandardRequest<SemanticToken[]>('getSemanticTokens', options);
×
168
    }
169

170
    public async transpileFile(options: { srcPath: string }) {
UNCOV
171
        return this.sendStandardRequest<FileTranspileResult>('transpileFile', options);
×
172
    }
173

174
    public async getHover(options: { srcPath: string; position: Position }): Promise<Hover[]> {
UNCOV
175
        return this.sendStandardRequest<Hover[]>('getHover', options);
×
176
    }
177

178
    public async getDefinition(options: { srcPath: string; position: Position }): Promise<Location[]> {
UNCOV
179
        return this.sendStandardRequest<Location[]>('getDefinition', options);
×
180
    }
181

182
    public async getSignatureHelp(options: { srcPath: string; position: Position }): Promise<SignatureInfoObj[]> {
183
        return this.sendStandardRequest<SignatureInfoObj[]>('getSignatureHelp', options);
×
184
    }
185

186
    public async getDocumentSymbol(options: { srcPath: string }): Promise<DocumentSymbol[]> {
187
        return this.sendStandardRequest<DocumentSymbol[]>('getDocumentSymbol', options);
×
188
    }
189

190
    public async getWorkspaceSymbol(): Promise<WorkspaceSymbol[]> {
191
        return this.sendStandardRequest<WorkspaceSymbol[]>('getWorkspaceSymbol');
×
192
    }
193

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

198
    public async getCodeActions(options: { srcPath: string; range: Range }): Promise<CodeAction[]> {
199
        return this.sendStandardRequest<CodeAction[]>('getCodeActions', options);
×
200
    }
201

202
    public async getCompletions(options: { srcPath: string; position: Position }): Promise<CompletionList> {
203
        return this.sendStandardRequest<CompletionList>('getCompletions', options);
×
204
    }
205

206
    /**
207
     * Handles request/response/update messages from the worker thread
208
     */
209
    private messageHandler: MessageHandler<LspProject>;
210

211
    private processRequest(request: WorkerMessage) {
212

213
    }
214

215
    private processUpdate(update: WorkerMessage) {
216
        //for now, all updates are treated like "events"
217
        this.emit(update.name as any, update.data);
3✔
218
    }
219

220
    public on(eventName: 'critical-failure', handler: (data: { message: string }) => void);
221
    public on(eventName: 'diagnostics', handler: (data: { diagnostics: LspDiagnostic[] }) => MaybePromise<void>);
222
    public on(eventName: 'all', handler: (eventName: string, data: any) => MaybePromise<void>);
223
    public on(eventName: string, handler: (...args: any[]) => MaybePromise<void>) {
224
        this.emitter.on(eventName, handler as any);
1✔
225
        return () => {
1✔
UNCOV
226
            this.emitter.removeListener(eventName, handler as any);
×
227
        };
228
    }
229

230
    private emit(eventName: 'critical-failure', data: { message: string });
231
    private emit(eventName: 'diagnostics', data: { diagnostics: LspDiagnostic[] });
232
    private async emit(eventName: string, data?) {
233
        //emit these events on next tick, otherwise they will be processed immediately which could cause issues
234
        await util.sleep(0);
3✔
235
        this.emitter.emit(eventName, data);
3✔
236
        //emit the 'all' event
237
        this.emitter.emit('all', eventName, data);
3✔
238
    }
239
    private emitter = new EventEmitter();
3✔
240

241
    public disposables: LspProject['disposables'] = [];
3✔
242

243
    public dispose() {
244
        for (let disposable of this.disposables ?? []) {
3!
245
            disposable?.dispose?.();
4!
246
        }
247
        this.disposables = [];
3✔
248

249
        //move the worker back to the pool so it can be used again
250
        if (this.worker) {
3!
251
            workerPool.releaseWorker(this.worker);
3✔
252
        }
253
        this.emitter?.removeAllListeners();
3!
254
    }
255
}
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