brighterscript 1.0.0-alpha.21 → 1.0.0-alpha.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +6 -3
  3. package/bsconfig.schema.json +10 -2
  4. package/dist/BsConfig.d.ts +5 -0
  5. package/dist/DependencyGraph.d.ts +2 -2
  6. package/dist/DependencyGraph.js +15 -3
  7. package/dist/DependencyGraph.js.map +1 -1
  8. package/dist/DiagnosticCollection.d.ts +3 -3
  9. package/dist/DiagnosticCollection.js +11 -11
  10. package/dist/DiagnosticCollection.js.map +1 -1
  11. package/dist/DiagnosticFilterer.js +4 -3
  12. package/dist/DiagnosticFilterer.js.map +1 -1
  13. package/dist/LanguageServer.d.ts +48 -26
  14. package/dist/LanguageServer.js +281 -211
  15. package/dist/LanguageServer.js.map +1 -1
  16. package/dist/Program.js +1 -1
  17. package/dist/Program.js.map +1 -1
  18. package/dist/ProgramBuilder.js +11 -11
  19. package/dist/ProgramBuilder.js.map +1 -1
  20. package/dist/Scope.js +6 -3
  21. package/dist/Scope.js.map +1 -1
  22. package/dist/astUtils/AstEditor.d.ts +32 -0
  23. package/dist/astUtils/AstEditor.js +132 -0
  24. package/dist/astUtils/AstEditor.js.map +1 -1
  25. package/dist/astUtils/AstEditor.spec.js +118 -34
  26. package/dist/astUtils/AstEditor.spec.js.map +1 -1
  27. package/dist/astUtils/creators.d.ts +6 -2
  28. package/dist/astUtils/creators.js +9 -2
  29. package/dist/astUtils/creators.js.map +1 -1
  30. package/dist/astUtils/reflection.d.ts +3 -3
  31. package/dist/astUtils/reflection.js +9 -7
  32. package/dist/astUtils/reflection.js.map +1 -1
  33. package/dist/astUtils/visitors.d.ts +74 -64
  34. package/dist/astUtils/visitors.js +30 -10
  35. package/dist/astUtils/visitors.js.map +1 -1
  36. package/dist/astUtils/visitors.spec.js +95 -3
  37. package/dist/astUtils/visitors.spec.js.map +1 -1
  38. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js +36 -25
  39. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js.map +1 -1
  40. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js +57 -0
  41. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js.map +1 -1
  42. package/dist/cli.js +1 -0
  43. package/dist/cli.js.map +1 -1
  44. package/dist/files/BrsFile.Class.spec.js +83 -2
  45. package/dist/files/BrsFile.Class.spec.js.map +1 -1
  46. package/dist/files/BrsFile.d.ts +8 -0
  47. package/dist/files/BrsFile.js +46 -28
  48. package/dist/files/BrsFile.js.map +1 -1
  49. package/dist/files/BrsFile.spec.js +83 -0
  50. package/dist/files/BrsFile.spec.js.map +1 -1
  51. package/dist/files/XmlFile.js +5 -4
  52. package/dist/files/XmlFile.js.map +1 -1
  53. package/dist/index.js +5 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/interfaces.d.ts +1 -1
  56. package/dist/parser/BrsTranspileState.d.ts +2 -0
  57. package/dist/parser/BrsTranspileState.js +5 -0
  58. package/dist/parser/BrsTranspileState.js.map +1 -1
  59. package/dist/parser/Expression.js +6 -23
  60. package/dist/parser/Expression.js.map +1 -1
  61. package/dist/parser/Parser.Class.spec.js.map +1 -1
  62. package/dist/parser/Parser.d.ts +3 -3
  63. package/dist/parser/Parser.js +6 -6
  64. package/dist/parser/Parser.js.map +1 -1
  65. package/dist/parser/Statement.d.ts +12 -16
  66. package/dist/parser/Statement.js +76 -69
  67. package/dist/parser/Statement.js.map +1 -1
  68. package/dist/util.d.ts +4 -5
  69. package/dist/util.js +26 -21
  70. package/dist/util.js.map +1 -1
  71. package/dist/validators/ClassValidator.js +6 -6
  72. package/dist/validators/ClassValidator.js.map +1 -1
  73. package/package.json +11 -11
@@ -8,9 +8,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.CustomCommands = exports.LanguageServer = void 0;
10
10
  require("array-flat-polyfill");
11
- const glob = require("glob");
11
+ const fastGlob = require("fast-glob");
12
12
  const path = require("path");
13
- const rokuDeploy = require("roku-deploy");
13
+ const roku_deploy_1 = require("roku-deploy");
14
14
  const node_1 = require("vscode-languageserver/node");
15
15
  const vscode_uri_1 = require("vscode-uri");
16
16
  const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
@@ -27,18 +27,18 @@ const SemanticTokenUtils_1 = require("./SemanticTokenUtils");
27
27
  class LanguageServer {
28
28
  constructor() {
29
29
  this.connection = undefined;
30
- this.workspaces = [];
30
+ this.projects = [];
31
31
  /**
32
32
  * The number of milliseconds that should be used for language server typing debouncing
33
33
  */
34
34
  this.debounceTimeout = 150;
35
35
  /**
36
- * These workspaces are created on the fly whenever a file is opened that is not included
37
- * in any of the workspace projects.
38
- * Basically these are single-file workspaces to at least get parsing for standalone files.
36
+ * These projects are created on the fly whenever a file is opened that is not included
37
+ * in any of the workspace-based projects.
38
+ * Basically these are single-file projects to at least get parsing for standalone files.
39
39
  * Also, they should only be created when the file is opened, and destroyed when the file is closed.
40
40
  */
41
- this.standaloneFileWorkspaces = {};
41
+ this.standaloneFileProjects = {};
42
42
  this.hasConfigurationCapability = false;
43
43
  /**
44
44
  * Indicates whether the client supports workspace folders
@@ -77,17 +77,11 @@ class LanguageServer {
77
77
  // when the text document is first opened, when its content has changed,
78
78
  // or when document is closed without saving (original contents are sent as a change)
79
79
  //
80
- this.documents.onDidChangeContent(async (change) => {
81
- await this.validateTextDocument(change.document);
82
- });
80
+ this.documents.onDidChangeContent(this.validateTextDocument.bind(this));
83
81
  //whenever a document gets closed
84
- this.documents.onDidClose(async (change) => {
85
- await this.onDocumentClose(change.document);
86
- });
82
+ this.documents.onDidClose(this.onDocumentClose.bind(this));
87
83
  // This handler provides the initial list of the completion items.
88
- this.connection.onCompletion(async (params) => {
89
- return this.onCompletion(params.textDocument.uri, params.position);
90
- });
84
+ this.connection.onCompletion(this.onCompletion.bind(this));
91
85
  // This handler resolves additional information for the item selected in
92
86
  // the completion list.
93
87
  this.connection.onCompletionResolve(this.onCompletionResolve.bind(this));
@@ -169,42 +163,118 @@ class LanguageServer {
169
163
  }
170
164
  };
171
165
  }
166
+ /**
167
+ * Ask the client for the list of `files.exclude` patterns. Useful when determining if we should process a file
168
+ */
169
+ async getWorkspaceExcludeGlobs(workspaceFolder) {
170
+ var _a;
171
+ //get any `files.exclude` globs to use to filter
172
+ let config = await this.connection.workspace.getConfiguration({
173
+ scopeUri: workspaceFolder,
174
+ section: 'files'
175
+ });
176
+ return Object
177
+ .keys((_a = config === null || config === void 0 ? void 0 : config.exclude) !== null && _a !== void 0 ? _a : {})
178
+ .filter(x => { var _a; return (_a = config === null || config === void 0 ? void 0 : config.exclude) === null || _a === void 0 ? void 0 : _a[x]; })
179
+ //vscode files.exclude patterns support ignoring folders without needing to add `**/*`. So for our purposes, we need to
180
+ //append **/* to everything without a file extension or magic at the end
181
+ .map(pattern => [
182
+ //send the pattern as-is (this handles weird cases and exact file matches)
183
+ pattern,
184
+ //treat the pattern as a directory (no harm in doing this because if it's a file, the pattern will just never match anything)
185
+ `${pattern}/**/*`
186
+ ])
187
+ .flat(1)
188
+ .concat([
189
+ //always ignore projects from node_modules
190
+ '**/node_modules/**/*'
191
+ ]);
192
+ }
193
+ /**
194
+ * Scan the workspace for all `bsconfig.json` files. If at least one is found, then only folders who have bsconfig.json are returned.
195
+ * If none are found, then the workspaceFolder itself is treated as a project
196
+ */
197
+ async getProjectPaths(workspaceFolder) {
198
+ const excludes = (await this.getWorkspaceExcludeGlobs(workspaceFolder)).map(x => (0, util_1.standardizePath) `!${x}`);
199
+ const files = await roku_deploy_1.rokuDeploy.getFilePaths([
200
+ '**/bsconfig.json',
201
+ //exclude all files found in `files.exclude`
202
+ ...excludes
203
+ ], workspaceFolder);
204
+ //if we found at least one bsconfig.json, then ALL projects must have a bsconfig.json.
205
+ if (files.length > 0) {
206
+ return files.map(file => (0, util_1.standardizePath) `${path.dirname(file.src)}`);
207
+ }
208
+ else {
209
+ //treat the workspace folder as a brightscript project itself
210
+ return [workspaceFolder];
211
+ }
212
+ }
213
+ /**
214
+ * Find all folders with bsconfig.json files in them, and treat each as a project.
215
+ * Treat workspaces that don't have a bsconfig.json as a project.
216
+ * Handle situations where bsconfig.json files were added or removed (to elevate/lower workspaceFolder projects accordingly)
217
+ * Leave existing projects alone if they are not affected by these changes
218
+ */
219
+ async syncProjects() {
220
+ const workspacePaths = await this.getWorkspacePaths();
221
+ let projectPaths = (await Promise.all(workspacePaths.map(async (workspacePath) => {
222
+ const projectPaths = await this.getProjectPaths(workspacePath);
223
+ return projectPaths.map(projectPath => ({
224
+ projectPath: projectPath,
225
+ workspacePath: workspacePath
226
+ }));
227
+ }))).flat(1);
228
+ //delete projects not represented in the list
229
+ for (const project of this.getProjects()) {
230
+ if (!projectPaths.find(x => x.projectPath === project.projectPath)) {
231
+ this.removeProject(project);
232
+ }
233
+ }
234
+ //exclude paths to projects we already have
235
+ projectPaths = projectPaths.filter(x => {
236
+ //only keep this project path if there's not a project with that path
237
+ return !this.projects.find(project => project.projectPath === x.projectPath);
238
+ });
239
+ //dedupe by project path
240
+ projectPaths = [
241
+ ...projectPaths.reduce((acc, x) => acc.set(x.projectPath, x), new Map()).values()
242
+ ];
243
+ //create missing projects
244
+ await Promise.all(projectPaths.map(x => this.createProject(x.projectPath, x.workspacePath)));
245
+ //flush diagnostics
246
+ await this.sendDiagnostics();
247
+ }
248
+ /**
249
+ * Get all workspace paths from the client
250
+ */
251
+ async getWorkspacePaths() {
252
+ var _a;
253
+ let workspaceFolders = (_a = await this.connection.workspace.getWorkspaceFolders()) !== null && _a !== void 0 ? _a : [];
254
+ return workspaceFolders.map((x) => {
255
+ return util_1.util.uriToPath(x.uri);
256
+ });
257
+ }
172
258
  /**
173
259
  * Called when the client has finished initializing
174
260
  * @param params
175
261
  */
176
262
  async onInitialized() {
177
- var _a;
178
- let workspaceCreatedDeferred = new deferred_1.Deferred();
179
- this.initialWorkspacesCreated = workspaceCreatedDeferred.promise;
263
+ let projectCreatedDeferred = new deferred_1.Deferred();
264
+ this.initialProjectsCreated = projectCreatedDeferred.promise;
180
265
  try {
181
266
  if (this.hasConfigurationCapability) {
182
267
  // Register for all configuration changes.
183
268
  await this.connection.client.register(node_1.DidChangeConfigurationNotification.type, undefined);
184
269
  }
185
- //ask the client for all workspace folders
186
- let workspaceFolders = (_a = await this.connection.workspace.getWorkspaceFolders()) !== null && _a !== void 0 ? _a : [];
187
- let workspacePaths = workspaceFolders.map((x) => {
188
- return util_1.util.uriToPath(x.uri);
189
- });
190
- await this.createWorkspaces(workspacePaths);
270
+ await this.syncProjects();
191
271
  if (this.clientHasWorkspaceFolderCapability) {
192
272
  this.connection.workspace.onDidChangeWorkspaceFolders(async (evt) => {
193
- //remove programs for removed workspace folders
194
- for (let removed of evt.removed) {
195
- let workspacePath = util_1.util.uriToPath(removed.uri);
196
- let workspace = this.workspaces.find((x) => x.workspacePath === workspacePath);
197
- if (workspace) {
198
- workspace.builder.dispose();
199
- this.workspaces.splice(this.workspaces.indexOf(workspace), 1);
200
- }
201
- }
202
- //create programs for new workspace folders
203
- await this.createWorkspaces(evt.added.map((x) => util_1.util.uriToPath(x.uri)));
273
+ await this.syncProjects();
204
274
  });
205
275
  }
206
- await this.waitAllProgramFirstRuns(false);
207
- workspaceCreatedDeferred.resolve();
276
+ await this.waitAllProjectFirstRuns(false);
277
+ projectCreatedDeferred.resolve();
208
278
  await this.sendDiagnostics();
209
279
  }
210
280
  catch (e) {
@@ -224,34 +294,25 @@ class LanguageServer {
224
294
  /**
225
295
  * Wait for all programs' first run to complete
226
296
  */
227
- async waitAllProgramFirstRuns(waitForFirstWorkSpace = true) {
297
+ async waitAllProjectFirstRuns(waitForFirstWorkSpace = true) {
228
298
  if (waitForFirstWorkSpace) {
229
- await this.initialWorkspacesCreated;
299
+ await this.initialProjectsCreated;
230
300
  }
231
301
  let status;
232
- let workspaces = this.getWorkspaces();
233
- for (let workspace of workspaces) {
302
+ for (let project of this.getProjects()) {
234
303
  try {
235
- await workspace.firstRunPromise;
304
+ await project.firstRunPromise;
236
305
  }
237
306
  catch (e) {
238
307
  status = 'critical-error';
239
308
  //the first run failed...that won't change unless we reload the workspace, so replace with resolved promise
240
309
  //so we don't show this error again
241
- workspace.firstRunPromise = Promise.resolve();
310
+ project.firstRunPromise = Promise.resolve();
242
311
  this.sendCriticalFailure(`BrighterScript language server failed to start: \n${e.message}`);
243
312
  }
244
313
  }
245
314
  this.connection.sendNotification('build-status', status ? status : 'success');
246
315
  }
247
- /**
248
- * Create project for each new workspace. If the workspace is already known,
249
- * it is skipped.
250
- * @param workspaceFolders
251
- */
252
- async createWorkspaces(workspacePaths) {
253
- return Promise.all(workspacePaths.map(async (workspacePath) => this.createWorkspace(workspacePath)));
254
- }
255
316
  /**
256
317
  * Event handler for when the program wants to load file contents.
257
318
  * anytime the program wants to load a file, check with our in-memory document cache first
@@ -284,7 +345,7 @@ class LanguageServer {
284
345
  return configFilePath;
285
346
  }
286
347
  else {
287
- this.sendCriticalFailure(`Cannot find config file specified in user/workspace settings at '${configFilePath}'`);
348
+ this.sendCriticalFailure(`Cannot find config file specified in user / workspace settings at '${configFilePath}'`);
288
349
  }
289
350
  }
290
351
  //default to config file path found in the root of the workspace
@@ -300,10 +361,11 @@ class LanguageServer {
300
361
  //no config file could be found
301
362
  return undefined;
302
363
  }
303
- async createWorkspace(workspacePath) {
304
- let workspace = this.workspaces.find((x) => x.workspacePath === workspacePath);
305
- //skip this workspace if we already have it
306
- if (workspace) {
364
+ async createProject(projectPath, workspacePath = projectPath) {
365
+ workspacePath !== null && workspacePath !== void 0 ? workspacePath : (workspacePath = projectPath);
366
+ let project = this.projects.find((x) => x.projectPath === projectPath);
367
+ //skip this project if we already have it
368
+ if (project) {
307
369
  return;
308
370
  }
309
371
  let builder = new ProgramBuilder_1.ProgramBuilder();
@@ -311,8 +373,8 @@ class LanguageServer {
311
373
  builder.allowConsoleClearing = false;
312
374
  //look for files in our in-memory cache before going to the file system
313
375
  builder.addFileResolver(this.documentFileResolver.bind(this));
314
- let configFilePath = await this.getConfigFilePath(workspacePath);
315
- let cwd = workspacePath;
376
+ let configFilePath = await this.getConfigFilePath(projectPath);
377
+ let cwd = projectPath;
316
378
  //if the config file exists, use it and its folder as cwd
317
379
  if (configFilePath && await util_1.util.pathExists(configFilePath)) {
318
380
  cwd = path.dirname(configFilePath);
@@ -333,22 +395,23 @@ class LanguageServer {
333
395
  firstRunPromise.catch((err) => {
334
396
  console.error(err);
335
397
  });
336
- let newWorkspace = {
398
+ let newProject = {
337
399
  builder: builder,
338
400
  firstRunPromise: firstRunPromise,
401
+ projectPath: projectPath,
339
402
  workspacePath: workspacePath,
340
403
  isFirstRunComplete: false,
341
404
  isFirstRunSuccessful: false,
342
405
  configFilePath: configFilePath,
343
- isStandaloneFileWorkspace: false
406
+ isStandaloneFileProject: false
344
407
  };
345
- this.workspaces.push(newWorkspace);
408
+ this.projects.push(newProject);
346
409
  await firstRunPromise.then(() => {
347
- newWorkspace.isFirstRunComplete = true;
348
- newWorkspace.isFirstRunSuccessful = true;
410
+ newProject.isFirstRunComplete = true;
411
+ newProject.isFirstRunSuccessful = true;
349
412
  }).catch(() => {
350
- newWorkspace.isFirstRunComplete = true;
351
- newWorkspace.isFirstRunSuccessful = false;
413
+ newProject.isFirstRunComplete = true;
414
+ newProject.isFirstRunSuccessful = false;
352
415
  }).then(() => {
353
416
  //if we found a deprecated brsconfig.json, add a diagnostic warning the user
354
417
  if (configFilePath && path.basename(configFilePath) === 'brsconfig.json') {
@@ -357,13 +420,10 @@ class LanguageServer {
357
420
  }
358
421
  });
359
422
  }
360
- /**
361
- * @param srcPath The absolute path to the source file on disk
362
- */
363
- async createStandaloneFileWorkspace(srcPath) {
423
+ async createStandaloneFileProject(srcPath) {
364
424
  //skip this workspace if we already have it
365
- if (this.standaloneFileWorkspaces[srcPath]) {
366
- return this.standaloneFileWorkspaces[srcPath];
425
+ if (this.standaloneFileProjects[srcPath]) {
426
+ return this.standaloneFileProjects[srcPath];
367
427
  }
368
428
  let builder = new ProgramBuilder_1.ProgramBuilder();
369
429
  //prevent clearing the console on run...this isn't the CLI so we want to keep a full log of everything
@@ -390,45 +450,46 @@ class LanguageServer {
390
450
  ] })).catch((err) => {
391
451
  console.error(err);
392
452
  });
393
- let newWorkspace = {
453
+ let newProject = {
394
454
  builder: builder,
395
455
  firstRunPromise: firstRunPromise,
456
+ projectPath: srcPath,
396
457
  workspacePath: srcPath,
397
458
  isFirstRunComplete: false,
398
459
  isFirstRunSuccessful: false,
399
460
  configFilePath: configFilePath,
400
- isStandaloneFileWorkspace: true
461
+ isStandaloneFileProject: true
401
462
  };
402
- this.standaloneFileWorkspaces[srcPath] = newWorkspace;
463
+ this.standaloneFileProjects[srcPath] = newProject;
403
464
  await firstRunPromise.then(() => {
404
- newWorkspace.isFirstRunComplete = true;
405
- newWorkspace.isFirstRunSuccessful = true;
465
+ newProject.isFirstRunComplete = true;
466
+ newProject.isFirstRunSuccessful = true;
406
467
  }).catch(() => {
407
- newWorkspace.isFirstRunComplete = true;
408
- newWorkspace.isFirstRunSuccessful = false;
468
+ newProject.isFirstRunComplete = true;
469
+ newProject.isFirstRunSuccessful = false;
409
470
  });
410
- return newWorkspace;
471
+ return newProject;
411
472
  }
412
- getWorkspaces() {
413
- let workspaces = this.workspaces.slice();
414
- for (let key in this.standaloneFileWorkspaces) {
415
- workspaces.push(this.standaloneFileWorkspaces[key]);
473
+ getProjects() {
474
+ let projects = this.projects.slice();
475
+ for (let key in this.standaloneFileProjects) {
476
+ projects.push(this.standaloneFileProjects[key]);
416
477
  }
417
- return workspaces;
478
+ return projects;
418
479
  }
419
480
  /**
420
481
  * Provide a list of completion items based on the current cursor position
421
482
  * @param textDocumentPosition
422
483
  */
423
- async onCompletion(uri, position) {
484
+ async onCompletion(params) {
424
485
  //ensure programs are initialized
425
- await this.waitAllProgramFirstRuns();
426
- let filePath = util_1.util.uriToPath(uri);
486
+ await this.waitAllProjectFirstRuns();
487
+ let filePath = util_1.util.uriToPath(params.textDocument.uri);
427
488
  //wait until the file has settled
428
489
  await this.keyedThrottler.onIdleOnce(filePath, true);
429
490
  let completions = this
430
- .getWorkspaces()
431
- .flatMap(workspace => workspace.builder.program.getCompletions(filePath, position));
491
+ .getProjects()
492
+ .flatMap(workspace => workspace.builder.program.getCompletions(filePath, params.position));
432
493
  for (let completion of completions) {
433
494
  completion.commitCharacters = ['.'];
434
495
  }
@@ -451,12 +512,12 @@ class LanguageServer {
451
512
  }
452
513
  async onCodeAction(params) {
453
514
  //ensure programs are initialized
454
- await this.waitAllProgramFirstRuns();
515
+ await this.waitAllProjectFirstRuns();
455
516
  let srcPath = util_1.util.uriToPath(params.textDocument.uri);
456
517
  //wait until the file has settled
457
518
  await this.keyedThrottler.onIdleOnce(srcPath, true);
458
519
  const codeActions = this
459
- .getWorkspaces()
520
+ .getProjects()
460
521
  //skip programs that don't have this file
461
522
  .filter(x => { var _a, _b; return (_b = (_a = x.builder) === null || _a === void 0 ? void 0 : _a.program) === null || _b === void 0 ? void 0 : _b.hasFile(srcPath); })
462
523
  .flatMap(workspace => workspace.builder.program.getCodeActions(srcPath, params.range));
@@ -469,38 +530,42 @@ class LanguageServer {
469
530
  return codeActions;
470
531
  }
471
532
  /**
472
- * Reload all specified workspaces, or all workspaces if no workspaces are specified
533
+ * Remove a project from the language server
473
534
  */
474
- async reloadWorkspaces(workspaces) {
475
- workspaces = workspaces ? workspaces : this.getWorkspaces();
476
- await Promise.all(workspaces.map(async (workspace) => {
535
+ removeProject(project) {
536
+ var _a;
537
+ const idx = this.projects.indexOf(project);
538
+ if (idx > -1) {
539
+ this.projects.splice(idx, 1);
540
+ }
541
+ (_a = project === null || project === void 0 ? void 0 : project.builder) === null || _a === void 0 ? void 0 : _a.dispose();
542
+ }
543
+ /**
544
+ * Reload each of the specified workspaces
545
+ */
546
+ async reloadProjects(projects) {
547
+ await Promise.all(projects.map(async (project) => {
477
548
  //ensure the workspace has finished starting up
478
549
  try {
479
- await workspace.firstRunPromise;
550
+ await project.firstRunPromise;
480
551
  }
481
552
  catch (e) { }
482
553
  //handle standard workspace
483
- if (workspace.isStandaloneFileWorkspace === false) {
484
- let idx = this.workspaces.indexOf(workspace);
485
- if (idx > -1) {
486
- //remove this workspace
487
- this.workspaces.splice(idx, 1);
488
- //dispose this workspace's resources
489
- workspace.builder.dispose();
490
- }
554
+ if (project.isStandaloneFileProject === false) {
555
+ this.removeProject(project);
491
556
  //create a new workspace/brs program
492
- await this.createWorkspace(workspace.workspacePath);
557
+ await this.createProject(project.projectPath, project.workspacePath);
493
558
  //handle temp workspace
494
559
  }
495
560
  else {
496
- workspace.builder.dispose();
497
- delete this.standaloneFileWorkspaces[workspace.workspacePath];
498
- await this.createStandaloneFileWorkspace(workspace.workspacePath);
561
+ project.builder.dispose();
562
+ delete this.standaloneFileProjects[project.projectPath];
563
+ await this.createStandaloneFileProject(project.projectPath);
499
564
  }
500
565
  }));
501
- if (workspaces.length > 0) {
566
+ if (projects.length > 0) {
502
567
  //wait for all of the programs to finish starting up
503
- await this.waitAllProgramFirstRuns();
568
+ await this.waitAllProjectFirstRuns();
504
569
  // valdiate all workspaces
505
570
  this.validateAllThrottled(); //eslint-disable-line
506
571
  }
@@ -517,42 +582,42 @@ class LanguageServer {
517
582
  *
518
583
  * Sometimes files that used to be included are now excluded, so those open files need to be re-processed as standalone
519
584
  */
520
- async synchronizeStandaloneWorkspaces() {
585
+ async synchronizeStandaloneProjects() {
521
586
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
522
587
  //remove standalone workspaces that are now included in projects
523
- for (let standaloneFilePath in this.standaloneFileWorkspaces) {
524
- let standaloneWorkspace = this.standaloneFileWorkspaces[standaloneFilePath];
525
- for (let workspace of this.workspaces) {
526
- await standaloneWorkspace.firstRunPromise;
527
- let dest = rokuDeploy.getDestPath(standaloneFilePath, (_d = (_c = (_b = (_a = workspace === null || workspace === void 0 ? void 0 : workspace.builder) === null || _a === void 0 ? void 0 : _a.program) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.files) !== null && _d !== void 0 ? _d : [], this.getRootDir(workspace));
588
+ for (let standaloneFilePath in this.standaloneFileProjects) {
589
+ let standaloneProject = this.standaloneFileProjects[standaloneFilePath];
590
+ for (let project of this.projects) {
591
+ await standaloneProject.firstRunPromise;
592
+ let dest = roku_deploy_1.rokuDeploy.getDestPath(standaloneFilePath, (_d = (_c = (_b = (_a = project === null || project === void 0 ? void 0 : project.builder) === null || _a === void 0 ? void 0 : _a.program) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.files) !== null && _d !== void 0 ? _d : [], this.getRootDir(project));
528
593
  //destroy this standalone workspace because the file has now been included in an actual workspace,
529
594
  //or if the workspace wants the file
530
- if (((_f = (_e = workspace === null || workspace === void 0 ? void 0 : workspace.builder) === null || _e === void 0 ? void 0 : _e.program) === null || _f === void 0 ? void 0 : _f.hasFile(standaloneFilePath)) || dest) {
531
- standaloneWorkspace.builder.dispose();
532
- delete this.standaloneFileWorkspaces[standaloneFilePath];
595
+ if (((_f = (_e = project === null || project === void 0 ? void 0 : project.builder) === null || _e === void 0 ? void 0 : _e.program) === null || _f === void 0 ? void 0 : _f.hasFile(standaloneFilePath)) || dest) {
596
+ standaloneProject.builder.dispose();
597
+ delete this.standaloneFileProjects[standaloneFilePath];
533
598
  }
534
599
  }
535
600
  }
536
- //create standalone workspaces for open files that no longer have a project
601
+ //create standalone projects for open files that no longer have a project
537
602
  let textDocuments = this.documents.all();
538
603
  outer: for (let textDocument of textDocuments) {
539
604
  let filePath = vscode_uri_1.URI.parse(textDocument.uri).fsPath;
540
- let workspaces = this.getWorkspaces();
541
- for (let workspace of workspaces) {
542
- let dest = rokuDeploy.getDestPath(filePath, (_k = (_j = (_h = (_g = workspace === null || workspace === void 0 ? void 0 : workspace.builder) === null || _g === void 0 ? void 0 : _g.program) === null || _h === void 0 ? void 0 : _h.options) === null || _j === void 0 ? void 0 : _j.files) !== null && _k !== void 0 ? _k : [], this.getRootDir(workspace));
543
- //if this workspace has the file, or it wants the file, do NOT make a standalone workspace for this file
544
- if (((_m = (_l = workspace === null || workspace === void 0 ? void 0 : workspace.builder) === null || _l === void 0 ? void 0 : _l.program) === null || _m === void 0 ? void 0 : _m.hasFile(filePath)) || dest) {
605
+ for (let project of this.getProjects()) {
606
+ let dest = roku_deploy_1.rokuDeploy.getDestPath(filePath, (_k = (_j = (_h = (_g = project === null || project === void 0 ? void 0 : project.builder) === null || _g === void 0 ? void 0 : _g.program) === null || _h === void 0 ? void 0 : _h.options) === null || _j === void 0 ? void 0 : _j.files) !== null && _k !== void 0 ? _k : [], this.getRootDir(project));
607
+ //if this project has the file, or it wants the file, do NOT make a standaloneProject for this file
608
+ if (((_m = (_l = project === null || project === void 0 ? void 0 : project.builder) === null || _l === void 0 ? void 0 : _l.program) === null || _m === void 0 ? void 0 : _m.hasFile(filePath)) || dest) {
545
609
  continue outer;
546
610
  }
547
611
  }
548
612
  //if we got here, no workspace has this file, so make a standalone file workspace
549
- let workspace = await this.createStandaloneFileWorkspace(filePath);
550
- await workspace.firstRunPromise;
613
+ let project = await this.createStandaloneFileProject(filePath);
614
+ await project.firstRunPromise;
551
615
  }
552
616
  }
553
617
  async onDidChangeConfiguration() {
554
618
  if (this.hasConfigurationCapability) {
555
- await this.reloadWorkspaces();
619
+ //if the user changes any config value, just mass-reload all projects
620
+ await this.reloadProjects(this.getProjects());
556
621
  // Reset all cached document settings
557
622
  }
558
623
  else {
@@ -570,9 +635,9 @@ class LanguageServer {
570
635
  */
571
636
  async onDidChangeWatchedFiles(params) {
572
637
  //ensure programs are initialized
573
- await this.waitAllProgramFirstRuns();
638
+ await this.waitAllProjectFirstRuns();
574
639
  this.connection.sendNotification('build-status', 'building');
575
- let workspaces = this.getWorkspaces();
640
+ let projects = this.getProjects();
576
641
  //convert all file paths to absolute paths
577
642
  let changes = params.changes.map(x => {
578
643
  return {
@@ -585,23 +650,27 @@ class LanguageServer {
585
650
  changes = changes.filter(x => keys.includes(x.srcPath));
586
651
  //if we have changes to work with
587
652
  if (changes.length > 0) {
653
+ //if any bsconfig files were added or deleted, re-sync all projects instead of the more specific approach below
654
+ if (changes.find(x => (x.type === node_1.FileChangeType.Created || x.type === node_1.FileChangeType.Deleted) && path.basename(x.srcPath).toLowerCase() === 'bsconfig.json')) {
655
+ return this.syncProjects();
656
+ }
588
657
  //reload any workspace whose bsconfig.json file has changed
589
658
  {
590
- let workspacesToReload = [];
659
+ let projectsToReload = [];
591
660
  //get the file paths as a string array
592
661
  let filePaths = changes.map((x) => x.srcPath);
593
- for (let workspace of workspaces) {
594
- if (workspace.configFilePath && filePaths.includes(workspace.configFilePath)) {
595
- workspacesToReload.push(workspace);
662
+ for (let project of projects) {
663
+ if (project.configFilePath && filePaths.includes(project.configFilePath)) {
664
+ projectsToReload.push(project);
596
665
  }
597
666
  }
598
- if (workspacesToReload.length > 0) {
599
- //vsc can generate a ton of these changes, for vsc system files, so we need to bail if there's no work to do on any of our actual workspace files
600
- //reload any workspaces that need to be reloaded
601
- await this.reloadWorkspaces(workspacesToReload);
667
+ if (projectsToReload.length > 0) {
668
+ //vsc can generate a ton of these changes, for vsc system files, so we need to bail if there's no work to do on any of our actual project files
669
+ //reload any projects that need to be reloaded
670
+ await this.reloadProjects(projectsToReload);
602
671
  }
603
- //set the list of workspaces to non-reloaded workspaces
604
- workspaces = workspaces.filter(x => !workspacesToReload.includes(x));
672
+ //reassign `projects` to the non-reloaded projects
673
+ projects = projects.filter(x => !projectsToReload.includes(x));
605
674
  }
606
675
  //convert created folders into a list of files of their contents
607
676
  const directoryChanges = changes
@@ -619,10 +688,10 @@ class LanguageServer {
619
688
  .filter(dirPath => !dirPath.includes('.roku-deploy-staging'))
620
689
  //get the files for each folder recursively
621
690
  .flatMap(dirPath => {
622
- //create a glob pattern to match all files
623
- let pattern = rokuDeploy.util.toForwardSlashes(`${dirPath}/**/*`);
624
- let files = glob.sync(pattern, {
625
- absolute: true
691
+ //look up all files
692
+ let files = fastGlob.sync('**/*', {
693
+ absolute: true,
694
+ cwd: roku_deploy_1.util.toForwardSlashes(dirPath)
626
695
  });
627
696
  return files.map(x => {
628
697
  return {
@@ -634,7 +703,7 @@ class LanguageServer {
634
703
  //add the new file changes to the changes array.
635
704
  changes.push(...newFileChanges);
636
705
  //give every workspace the chance to handle file changes
637
- await Promise.all(workspaces.map((workspace) => this.handleFileChanges(workspace, changes)));
706
+ await Promise.all(projects.map((project) => this.handleFileChanges(project, changes)));
638
707
  }
639
708
  this.connection.sendNotification('build-status', 'success');
640
709
  }
@@ -643,13 +712,13 @@ class LanguageServer {
643
712
  * any file changes you receive with no unexpected side-effects
644
713
  * @param changes
645
714
  */
646
- async handleFileChanges(workspace, changes) {
715
+ async handleFileChanges(project, changes) {
647
716
  //this loop assumes paths are both file paths and folder paths, which eliminates the need to detect.
648
717
  //All functions below can handle being given a file path AND a folder path, and will only operate on the one they are looking for
649
718
  let consumeCount = 0;
650
719
  await Promise.all(changes.map(async (change) => {
651
720
  await this.keyedThrottler.run(change.srcPath, async () => {
652
- consumeCount += await this.handleFileChange(workspace, change) ? 1 : 0;
721
+ consumeCount += await this.handleFileChange(project, change) ? 1 : 0;
653
722
  });
654
723
  }));
655
724
  if (consumeCount > 0) {
@@ -661,14 +730,12 @@ class LanguageServer {
661
730
  * any file changes you receive with no unexpected side-effects
662
731
  * @param changes
663
732
  */
664
- async handleFileChange(workspace, change) {
665
- const program = workspace.builder.program;
666
- const options = workspace.builder.options;
667
- const rootDir = workspace.builder.rootDir;
733
+ async handleFileChange(project, change) {
734
+ const { program, options, rootDir } = project.builder;
668
735
  //deleted
669
736
  if (change.type === node_1.FileChangeType.Deleted) {
670
737
  //try to act on this path as a directory
671
- workspace.builder.program.removeFilesInFolder(change.srcPath);
738
+ project.builder.program.removeFilesInFolder(change.srcPath);
672
739
  //if this is a file loaded in the program, remove it
673
740
  if (program.hasFile(change.srcPath)) {
674
741
  program.removeFile(change.srcPath);
@@ -682,13 +749,13 @@ class LanguageServer {
682
749
  else if (change.type === node_1.FileChangeType.Created) {
683
750
  // thanks to `onDidChangeWatchedFiles`, we can safely assume that all "Created" changes are file paths, (not directories)
684
751
  //get the dest path for this file.
685
- let destPath = rokuDeploy.getDestPath(change.srcPath, options.files, rootDir);
752
+ let destPath = roku_deploy_1.rokuDeploy.getDestPath(change.srcPath, options.files, rootDir);
686
753
  //if we got a dest path, then the program wants this file
687
754
  if (destPath) {
688
755
  program.setFile({
689
756
  src: change.srcPath,
690
- dest: rokuDeploy.getDestPath(change.srcPath, options.files, rootDir)
691
- }, await workspace.builder.getFileContents(change.srcPath));
757
+ dest: roku_deploy_1.rokuDeploy.getDestPath(change.srcPath, options.files, rootDir)
758
+ }, await project.builder.getFileContents(change.srcPath));
692
759
  return true;
693
760
  }
694
761
  else {
@@ -703,8 +770,8 @@ class LanguageServer {
703
770
  if (await util_1.util.pathExists(change.srcPath)) {
704
771
  program.setFile({
705
772
  src: change.srcPath,
706
- dest: rokuDeploy.getDestPath(change.srcPath, options.files, rootDir)
707
- }, await workspace.builder.getFileContents(change.srcPath));
773
+ dest: roku_deploy_1.rokuDeploy.getDestPath(change.srcPath, options.files, rootDir)
774
+ }, await project.builder.getFileContents(change.srcPath));
708
775
  }
709
776
  else {
710
777
  program.removeFile(change.srcPath);
@@ -714,75 +781,77 @@ class LanguageServer {
714
781
  }
715
782
  async onHover(params) {
716
783
  //ensure programs are initialized
717
- await this.waitAllProgramFirstRuns();
718
- let srcPath = util_1.util.uriToPath(params.textDocument.uri);
719
- let workspaces = this.getWorkspaces();
720
- let hovers = await Promise.all(Array.prototype.concat.call([], workspaces.map(async (x) => x.builder.program.getHover(srcPath, params.position))));
784
+ await this.waitAllProjectFirstRuns();
785
+ const srcPath = util_1.util.uriToPath(params.textDocument.uri);
786
+ let projects = this.getProjects();
787
+ let hovers = await Promise.all(Array.prototype.concat.call([], projects.map(async (x) => x.builder.program.getHover(srcPath, params.position))));
721
788
  //return the first non-falsey hover. TODO is there a way to handle multiple hover results?
722
789
  let hover = hovers.filter((x) => !!x)[0];
723
790
  return hover;
724
791
  }
725
- async onDocumentClose(textDocument) {
726
- let filePath = vscode_uri_1.URI.parse(textDocument.uri).fsPath;
727
- let standaloneFileWorkspace = this.standaloneFileWorkspaces[filePath];
792
+ async onDocumentClose(event) {
793
+ const { document } = event;
794
+ let filePath = vscode_uri_1.URI.parse(document.uri).fsPath;
795
+ let standaloneFileProject = this.standaloneFileProjects[filePath];
728
796
  //if this was a temp file, close it
729
- if (standaloneFileWorkspace) {
730
- await standaloneFileWorkspace.firstRunPromise;
731
- standaloneFileWorkspace.builder.dispose();
732
- delete this.standaloneFileWorkspaces[filePath];
797
+ if (standaloneFileProject) {
798
+ await standaloneFileProject.firstRunPromise;
799
+ standaloneFileProject.builder.dispose();
800
+ delete this.standaloneFileProjects[filePath];
733
801
  await this.sendDiagnostics();
734
802
  }
735
803
  }
736
- async validateTextDocument(textDocument) {
804
+ async validateTextDocument(event) {
805
+ const { document } = event;
737
806
  //ensure programs are initialized
738
- await this.waitAllProgramFirstRuns();
739
- let filePath = vscode_uri_1.URI.parse(textDocument.uri).fsPath;
807
+ await this.waitAllProjectFirstRuns();
808
+ let filePath = vscode_uri_1.URI.parse(document.uri).fsPath;
740
809
  try {
741
810
  //throttle file processing. first call is run immediately, and then the last call is processed.
742
811
  await this.keyedThrottler.run(filePath, () => {
743
812
  var _a;
744
813
  this.connection.sendNotification('build-status', 'building');
745
- let documentText = textDocument.getText();
746
- for (const workspace of this.getWorkspaces()) {
814
+ let documentText = document.getText();
815
+ for (const project of this.getProjects()) {
747
816
  //only add or replace existing files. All of the files in the project should
748
817
  //have already been loaded by other means
749
- if (workspace.builder.program.hasFile(filePath)) {
750
- let rootDir = (_a = workspace.builder.program.options.rootDir) !== null && _a !== void 0 ? _a : workspace.builder.program.options.cwd;
751
- let dest = rokuDeploy.getDestPath(filePath, workspace.builder.program.options.files, rootDir);
752
- workspace.builder.program.setFile({
818
+ if (project.builder.program.hasFile(filePath)) {
819
+ let rootDir = (_a = project.builder.program.options.rootDir) !== null && _a !== void 0 ? _a : project.builder.program.options.cwd;
820
+ let dest = roku_deploy_1.rokuDeploy.getDestPath(filePath, project.builder.program.options.files, rootDir);
821
+ project.builder.program.setFile({
753
822
  src: filePath,
754
823
  dest: dest
755
824
  }, documentText);
756
825
  }
757
826
  }
758
827
  });
759
- // validate all workspaces
828
+ // validate all projects
760
829
  await this.validateAllThrottled();
761
830
  }
762
831
  catch (e) {
763
- this.sendCriticalFailure(`Critical error parsing/ validating ${filePath}: ${e.message}`);
832
+ this.sendCriticalFailure(`Critical error parsing / validating ${filePath}: ${e.message}`);
764
833
  }
765
834
  }
766
835
  async validateAll() {
767
836
  var _a;
768
837
  try {
769
838
  //synchronize parsing for open files that were included/excluded from projects
770
- await this.synchronizeStandaloneWorkspaces();
771
- let workspaces = this.getWorkspaces();
839
+ await this.synchronizeStandaloneProjects();
840
+ let projects = this.getProjects();
772
841
  //validate all programs
773
- await Promise.all(workspaces.map((x) => x.builder.program.validate()));
842
+ await Promise.all(projects.map((x) => x.builder.program.validate()));
774
843
  await this.sendDiagnostics();
775
844
  }
776
845
  catch (e) {
777
846
  this.connection.console.error(e);
778
- this.sendCriticalFailure(`Critical error validating workspace: ${e.message}${(_a = e.stack) !== null && _a !== void 0 ? _a : ''}`);
847
+ this.sendCriticalFailure(`Critical error validating project: ${e.message}${(_a = e.stack) !== null && _a !== void 0 ? _a : ''}`);
779
848
  }
780
849
  this.connection.sendNotification('build-status', 'success');
781
850
  }
782
851
  async onWorkspaceSymbol(params) {
783
- await this.waitAllProgramFirstRuns();
784
- const results = util_1.util.flatMap(await Promise.all(this.getWorkspaces().map(workspace => {
785
- return workspace.builder.program.getWorkspaceSymbols();
852
+ await this.waitAllProjectFirstRuns();
853
+ const results = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => {
854
+ return project.builder.program.getWorkspaceSymbols();
786
855
  })), c => c);
787
856
  // Remove duplicates
788
857
  const allSymbols = Object.values(results.reduce((map, symbol) => {
@@ -793,31 +862,31 @@ class LanguageServer {
793
862
  return allSymbols;
794
863
  }
795
864
  async onDocumentSymbol(params) {
796
- await this.waitAllProgramFirstRuns();
865
+ await this.waitAllProjectFirstRuns();
797
866
  await this.keyedThrottler.onIdleOnce(util_1.util.uriToPath(params.textDocument.uri), true);
798
867
  const srcPath = util_1.util.uriToPath(params.textDocument.uri);
799
- for (const workspace of this.getWorkspaces()) {
800
- const file = workspace.builder.program.getFile(srcPath);
868
+ for (const project of this.getProjects()) {
869
+ const file = project.builder.program.getFile(srcPath);
801
870
  if ((0, reflection_1.isBrsFile)(file)) {
802
871
  return file.getDocumentSymbols();
803
872
  }
804
873
  }
805
874
  }
806
875
  async onDefinition(params) {
807
- await this.waitAllProgramFirstRuns();
876
+ await this.waitAllProjectFirstRuns();
808
877
  const srcPath = util_1.util.uriToPath(params.textDocument.uri);
809
- const results = util_1.util.flatMap(await Promise.all(this.getWorkspaces().map(workspace => {
810
- return workspace.builder.program.getDefinition(srcPath, params.position);
878
+ const results = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => {
879
+ return project.builder.program.getDefinition(srcPath, params.position);
811
880
  })), c => c);
812
881
  return results;
813
882
  }
814
883
  async onSignatureHelp(params) {
815
884
  var _a, _b, _c;
816
- await this.waitAllProgramFirstRuns();
885
+ await this.waitAllProjectFirstRuns();
817
886
  const filepath = util_1.util.uriToPath(params.textDocument.uri);
818
887
  await this.keyedThrottler.onIdleOnce(filepath, true);
819
888
  try {
820
- const signatures = util_1.util.flatMap(await Promise.all(this.getWorkspaces().map(workspace => workspace.builder.program.getSignatureHelp(filepath, params.position))), c => c);
889
+ const signatures = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => project.builder.program.getSignatureHelp(filepath, params.position))), c => c);
821
890
  const activeSignature = signatures.length > 0 ? 0 : null;
822
891
  const activeParameter = activeSignature >= 0 ? (_a = signatures[activeSignature]) === null || _a === void 0 ? void 0 : _a.index : null;
823
892
  let results = {
@@ -837,22 +906,22 @@ class LanguageServer {
837
906
  }
838
907
  }
839
908
  async onReferences(params) {
840
- await this.waitAllProgramFirstRuns();
909
+ await this.waitAllProjectFirstRuns();
841
910
  const position = params.position;
842
911
  const srcPath = util_1.util.uriToPath(params.textDocument.uri);
843
- const results = util_1.util.flatMap(await Promise.all(this.getWorkspaces().map(workspace => {
844
- return workspace.builder.program.getReferences(srcPath, position);
912
+ const results = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => {
913
+ return project.builder.program.getReferences(srcPath, position);
845
914
  })), c => c);
846
915
  return results.filter((r) => r);
847
916
  }
848
917
  async onFullSemanticTokens(params) {
849
- await this.waitAllProgramFirstRuns();
918
+ await this.waitAllProjectFirstRuns();
850
919
  await this.keyedThrottler.onIdleOnce(util_1.util.uriToPath(params.textDocument.uri), true);
851
920
  const srcPath = util_1.util.uriToPath(params.textDocument.uri);
852
- for (const workspace of this.workspaces) {
921
+ for (const project of this.projects) {
853
922
  //find the first program that has this file, since it would be incredibly inefficient to generate semantic tokens for the same file multiple times.
854
- if (workspace.builder.program.hasFile(srcPath)) {
855
- let semanticTokens = workspace.builder.program.getSemanticTokens(srcPath);
923
+ if (project.builder.program.hasFile(srcPath)) {
924
+ let semanticTokens = project.builder.program.getSemanticTokens(srcPath);
856
925
  return {
857
926
  data: (0, SemanticTokenUtils_1.encodeSemanticTokens)(semanticTokens)
858
927
  };
@@ -861,7 +930,7 @@ class LanguageServer {
861
930
  }
862
931
  async sendDiagnostics() {
863
932
  //Get only the changes to diagnostics since the last time we sent them to the client
864
- const patch = await this.diagnosticCollection.getPatch(this.workspaces);
933
+ const patch = await this.diagnosticCollection.getPatch(this.projects);
865
934
  for (let filePath in patch) {
866
935
  const diagnostics = patch[filePath].map(d => util_1.util.toDiagnostic(d));
867
936
  this.connection.sendDiagnostics({
@@ -871,9 +940,11 @@ class LanguageServer {
871
940
  }
872
941
  }
873
942
  async onExecuteCommand(params) {
874
- await this.waitAllProgramFirstRuns();
943
+ await this.waitAllProjectFirstRuns();
875
944
  if (params.command === CustomCommands.TranspileFile) {
876
- return this.transpileFile(params.arguments[0]);
945
+ const result = await this.transpileFile(params.arguments[0]);
946
+ //back-compat: include `pathAbsolute` property so older vscode versions still work
947
+ result.pathAbsolute = result.srcPath;
877
948
  }
878
949
  }
879
950
  /**
@@ -881,12 +952,11 @@ class LanguageServer {
881
952
  */
882
953
  async transpileFile(srcPath) {
883
954
  //wait all program first runs
884
- await this.waitAllProgramFirstRuns();
885
- let workspaces = this.getWorkspaces();
886
- //find the first workspace that has this file
887
- for (let workspace of workspaces) {
888
- if (workspace.builder.program.hasFile(srcPath)) {
889
- return workspace.builder.program.getTranspiledFileContents(srcPath);
955
+ await this.waitAllProjectFirstRuns();
956
+ //find the first project that has this file
957
+ for (let project of this.getProjects()) {
958
+ if (project.builder.program.hasFile(srcPath)) {
959
+ return project.builder.program.getTranspiledFileContents(srcPath);
890
960
  }
891
961
  }
892
962
  }