brighterscript 0.68.5 → 0.69.1

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 (114) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/BusyStatusTracker.d.ts +37 -7
  3. package/dist/BusyStatusTracker.js +73 -8
  4. package/dist/BusyStatusTracker.js.map +1 -1
  5. package/dist/DiagnosticCollection.d.ts +19 -5
  6. package/dist/DiagnosticCollection.js +67 -16
  7. package/dist/DiagnosticCollection.js.map +1 -1
  8. package/dist/LanguageServer.d.ts +82 -132
  9. package/dist/LanguageServer.js +403 -940
  10. package/dist/LanguageServer.js.map +1 -1
  11. package/dist/Logger.d.ts +9 -4
  12. package/dist/Logger.js +30 -6
  13. package/dist/Logger.js.map +1 -1
  14. package/dist/PluginInterface.d.ts +1 -1
  15. package/dist/PluginInterface.js.map +1 -1
  16. package/dist/Program.d.ts +20 -2
  17. package/dist/Program.js +124 -49
  18. package/dist/Program.js.map +1 -1
  19. package/dist/ProgramBuilder.d.ts +13 -6
  20. package/dist/ProgramBuilder.js +25 -15
  21. package/dist/ProgramBuilder.js.map +1 -1
  22. package/dist/Scope.js +6 -3
  23. package/dist/Scope.js.map +1 -1
  24. package/dist/SemanticTokenUtils.js +1 -1
  25. package/dist/SemanticTokenUtils.js.map +1 -1
  26. package/dist/bscPlugin/CallExpressionInfo.js +2 -1
  27. package/dist/bscPlugin/CallExpressionInfo.js.map +1 -1
  28. package/dist/bscPlugin/completions/CompletionsProcessor.js +14 -4
  29. package/dist/bscPlugin/completions/CompletionsProcessor.js.map +1 -1
  30. package/dist/bscPlugin/hover/HoverProcessor.js +1 -1
  31. package/dist/bscPlugin/hover/HoverProcessor.js.map +1 -1
  32. package/dist/bscPlugin/validation/ScopeValidator.js +9 -9
  33. package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
  34. package/dist/common/Sequencer.d.ts +27 -0
  35. package/dist/common/Sequencer.js +113 -0
  36. package/dist/common/Sequencer.js.map +1 -0
  37. package/dist/common/Sequencer.spec.d.ts +1 -0
  38. package/dist/common/Sequencer.spec.js +75 -0
  39. package/dist/common/Sequencer.spec.js.map +1 -0
  40. package/dist/deferred.d.ts +2 -0
  41. package/dist/deferred.js +10 -0
  42. package/dist/deferred.js.map +1 -1
  43. package/dist/files/BrsFile.d.ts +1 -1
  44. package/dist/files/BrsFile.js +10 -15
  45. package/dist/files/BrsFile.js.map +1 -1
  46. package/dist/files/BrsFile.spec.js +8 -0
  47. package/dist/files/BrsFile.spec.js.map +1 -1
  48. package/dist/interfaces.d.ts +22 -2
  49. package/dist/lexer/Lexer.js +1 -1
  50. package/dist/lexer/Lexer.js.map +1 -1
  51. package/dist/logging.d.ts +6 -1
  52. package/dist/logging.js +14 -1
  53. package/dist/logging.js.map +1 -1
  54. package/dist/lsp/ActionQueue.d.ts +35 -0
  55. package/dist/lsp/ActionQueue.js +115 -0
  56. package/dist/lsp/ActionQueue.js.map +1 -0
  57. package/dist/lsp/ActionQueue.spec.d.ts +1 -0
  58. package/dist/lsp/ActionQueue.spec.js +80 -0
  59. package/dist/lsp/ActionQueue.spec.js.map +1 -0
  60. package/dist/lsp/DocumentManager.d.ts +63 -0
  61. package/dist/lsp/DocumentManager.js +122 -0
  62. package/dist/lsp/DocumentManager.js.map +1 -0
  63. package/dist/lsp/DocumentManager.spec.d.ts +1 -0
  64. package/dist/lsp/DocumentManager.spec.js +103 -0
  65. package/dist/lsp/DocumentManager.spec.js.map +1 -0
  66. package/dist/lsp/LspProject.d.ts +231 -0
  67. package/dist/lsp/LspProject.js +3 -0
  68. package/dist/lsp/LspProject.js.map +1 -0
  69. package/dist/lsp/PathFilterer.d.ts +75 -0
  70. package/dist/lsp/PathFilterer.js +196 -0
  71. package/dist/lsp/PathFilterer.js.map +1 -0
  72. package/dist/lsp/PathFilterer.spec.d.ts +1 -0
  73. package/dist/lsp/PathFilterer.spec.js +182 -0
  74. package/dist/lsp/PathFilterer.spec.js.map +1 -0
  75. package/dist/lsp/Project.d.ts +178 -0
  76. package/dist/lsp/Project.js +438 -0
  77. package/dist/lsp/Project.js.map +1 -0
  78. package/dist/lsp/Project.spec.d.ts +1 -0
  79. package/dist/lsp/Project.spec.js +236 -0
  80. package/dist/lsp/Project.spec.js.map +1 -0
  81. package/dist/lsp/ProjectManager.d.ts +221 -0
  82. package/dist/lsp/ProjectManager.js +735 -0
  83. package/dist/lsp/ProjectManager.js.map +1 -0
  84. package/dist/lsp/ProjectManager.spec.d.ts +1 -0
  85. package/dist/lsp/ProjectManager.spec.js +756 -0
  86. package/dist/lsp/ProjectManager.spec.js.map +1 -0
  87. package/dist/lsp/ReaderWriterManager.d.ts +21 -0
  88. package/dist/lsp/ReaderWriterManager.js +60 -0
  89. package/dist/lsp/ReaderWriterManager.js.map +1 -0
  90. package/dist/lsp/worker/MessageHandler.d.ts +99 -0
  91. package/dist/lsp/worker/MessageHandler.js +138 -0
  92. package/dist/lsp/worker/MessageHandler.js.map +1 -0
  93. package/dist/lsp/worker/MessageHandler.spec.d.ts +1 -0
  94. package/dist/lsp/worker/MessageHandler.spec.js +64 -0
  95. package/dist/lsp/worker/MessageHandler.spec.js.map +1 -0
  96. package/dist/lsp/worker/WorkerPool.d.ts +38 -0
  97. package/dist/lsp/worker/WorkerPool.js +78 -0
  98. package/dist/lsp/worker/WorkerPool.js.map +1 -0
  99. package/dist/lsp/worker/WorkerPool.spec.d.ts +1 -0
  100. package/dist/lsp/worker/WorkerPool.spec.js +59 -0
  101. package/dist/lsp/worker/WorkerPool.spec.js.map +1 -0
  102. package/dist/lsp/worker/WorkerThreadProject.d.ts +144 -0
  103. package/dist/lsp/worker/WorkerThreadProject.js +183 -0
  104. package/dist/lsp/worker/WorkerThreadProject.js.map +1 -0
  105. package/dist/lsp/worker/WorkerThreadProject.spec.d.ts +2 -0
  106. package/dist/lsp/worker/WorkerThreadProject.spec.js +68 -0
  107. package/dist/lsp/worker/WorkerThreadProject.spec.js.map +1 -0
  108. package/dist/lsp/worker/WorkerThreadProjectRunner.d.ts +15 -0
  109. package/dist/lsp/worker/WorkerThreadProjectRunner.js +58 -0
  110. package/dist/lsp/worker/WorkerThreadProjectRunner.js.map +1 -0
  111. package/dist/util.d.ts +32 -5
  112. package/dist/util.js +117 -19
  113. package/dist/util.js.map +1 -1
  114. package/package.json +13 -3
@@ -0,0 +1,756 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const ProjectManager_1 = require("./ProjectManager");
5
+ const testHelpers_spec_1 = require("../testHelpers.spec");
6
+ const fsExtra = require("fs-extra");
7
+ const util_1 = require("../util");
8
+ const sinon_1 = require("sinon");
9
+ const Project_1 = require("./Project");
10
+ const WorkerThreadProject_1 = require("./worker/WorkerThreadProject");
11
+ const WorkerThreadProject_spec_1 = require("./worker/WorkerThreadProject.spec");
12
+ const DiagnosticMessages_1 = require("../DiagnosticMessages");
13
+ const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
14
+ const PathFilterer_1 = require("./PathFilterer");
15
+ const deferred_1 = require("../deferred");
16
+ const net = require("net");
17
+ const getPort = require("get-port");
18
+ const sinon = (0, sinon_1.createSandbox)();
19
+ describe('ProjectManager', () => {
20
+ let manager;
21
+ let pathFilterer;
22
+ beforeEach(() => {
23
+ pathFilterer = new PathFilterer_1.PathFilterer();
24
+ manager = new ProjectManager_1.ProjectManager({
25
+ pathFilterer: pathFilterer
26
+ });
27
+ fsExtra.emptyDirSync(testHelpers_spec_1.tempDir);
28
+ sinon.restore();
29
+ diagnosticsListeners = [];
30
+ diagnosticsResponses = [];
31
+ manager.on('diagnostics', (event) => {
32
+ var _a;
33
+ if (diagnosticsListeners.length > 0) {
34
+ (_a = diagnosticsListeners.shift()) === null || _a === void 0 ? void 0 : _a(event.diagnostics);
35
+ }
36
+ else {
37
+ diagnosticsResponses.push(event.diagnostics);
38
+ }
39
+ });
40
+ });
41
+ afterEach(() => {
42
+ fsExtra.emptyDirSync(testHelpers_spec_1.tempDir);
43
+ sinon.restore();
44
+ manager.dispose();
45
+ });
46
+ let diagnosticsListeners = [];
47
+ let diagnosticsResponses = [];
48
+ /**
49
+ * Get a promise that resolves when the next diagnostics event is emitted (or pop the earliest unhandled diagnostics list if some are already here)
50
+ */
51
+ function onNextDiagnostics() {
52
+ if (diagnosticsResponses.length > 0) {
53
+ return Promise.resolve(diagnosticsResponses.shift());
54
+ }
55
+ else {
56
+ return new Promise((resolve) => {
57
+ diagnosticsListeners.push(resolve);
58
+ });
59
+ }
60
+ }
61
+ async function setFile(srcPath, contents) {
62
+ //set the namespace first
63
+ await manager.handleFileChanges([{
64
+ srcPath: srcPath,
65
+ type: vscode_languageserver_protocol_1.FileChangeType.Changed,
66
+ fileContents: contents,
67
+ allowStandaloneProject: false
68
+ }]);
69
+ }
70
+ describe('on', () => {
71
+ it('emits events', async () => {
72
+ const stub = sinon.stub();
73
+ const off = manager.on('diagnostics', stub);
74
+ await manager['emit']('diagnostics', { project: undefined, diagnostics: [] });
75
+ (0, chai_1.expect)(stub.callCount).to.eql(1);
76
+ await manager['emit']('diagnostics', { project: undefined, diagnostics: [] });
77
+ (0, chai_1.expect)(stub.callCount).to.eql(2);
78
+ off();
79
+ await manager['emit']('diagnostics', { project: undefined, diagnostics: [] });
80
+ (0, chai_1.expect)(stub.callCount).to.eql(2);
81
+ });
82
+ });
83
+ describe('validation tracking', () => {
84
+ it('tracks validation state', async () => {
85
+ await manager.syncProjects([{
86
+ workspaceFolder: testHelpers_spec_1.rootDir
87
+ }]);
88
+ const project = manager.projects[0];
89
+ //force validation to take a while
90
+ sinon.stub(project['builder'].program, 'validate').callsFake(async () => {
91
+ await util_1.default.sleep(100);
92
+ });
93
+ (0, chai_1.expect)(manager.busyStatusTracker.status).to.eql('idle');
94
+ //run several validations (which cancel the previous)
95
+ void project.validate();
96
+ await util_1.default.sleep(10);
97
+ void project.validate();
98
+ await util_1.default.sleep(10);
99
+ void project.validate();
100
+ await util_1.default.sleep(10);
101
+ //busy status should be active
102
+ (0, chai_1.expect)(manager.busyStatusTracker.status).to.eql('busy');
103
+ });
104
+ });
105
+ describe('syncProjects', () => {
106
+ it('does not crash on zero projects', async () => {
107
+ await manager.syncProjects([]);
108
+ });
109
+ it('finds bsconfig in a folder', async () => {
110
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/bsconfig.json`, '');
111
+ await manager.syncProjects([{
112
+ workspaceFolder: testHelpers_spec_1.rootDir
113
+ }]);
114
+ (0, chai_1.expect)(manager.projects[0].projectPath).to.eql((0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}`);
115
+ });
116
+ it('finds bsconfig at root and also in subfolder', async () => {
117
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/bsconfig.json`, '');
118
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/subdir/bsconfig.json`, '');
119
+ await manager.syncProjects([{
120
+ workspaceFolder: testHelpers_spec_1.rootDir
121
+ }]);
122
+ (0, chai_1.expect)(manager.projects.map(x => x.projectPath).sort()).to.eql([
123
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}`,
124
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/subdir`
125
+ ]);
126
+ });
127
+ it('skips excluded bsconfig bsconfig in a folder', async () => {
128
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/bsconfig.json`, '');
129
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/subdir/bsconfig.json`, '');
130
+ await manager.syncProjects([{
131
+ workspaceFolder: testHelpers_spec_1.rootDir,
132
+ excludePatterns: ['subdir/**/*']
133
+ }]);
134
+ (0, chai_1.expect)(manager.projects.map(x => x.projectPath)).to.eql([
135
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}`
136
+ ]);
137
+ });
138
+ it('uses rootDir when manifest found but no brightscript file', async () => {
139
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/subdir/manifest`, '');
140
+ await manager.syncProjects([{
141
+ workspaceFolder: testHelpers_spec_1.rootDir
142
+ }]);
143
+ (0, chai_1.expect)(manager.projects.map(x => x.projectPath)).to.eql([
144
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}`
145
+ ]);
146
+ });
147
+ it('gets diagnostics from plugins added in afterProgramValidate', async () => {
148
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/plugin.js`, `
149
+ module.exports = function () {
150
+ return {
151
+ afterProgramValidate: function(program) {
152
+ var file = program.getFile('source/main.brs');
153
+ //add a diagnostic from a plugin
154
+ file.addDiagnostic({
155
+ message: 'Test diagnostic',
156
+ code: 'test-123',
157
+ severity: 1
158
+ });
159
+ }
160
+ }
161
+ }
162
+ `);
163
+ fsExtra.outputJsonSync(`${testHelpers_spec_1.rootDir}/bsconfig.json`, {
164
+ plugins: [
165
+ './plugin.js'
166
+ ]
167
+ });
168
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/main.brs`, `
169
+ sub test()
170
+ print nameNotDefined
171
+ end sub
172
+ `);
173
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/manifest`, '');
174
+ await manager.syncProjects([{
175
+ workspaceFolder: testHelpers_spec_1.rootDir
176
+ }]);
177
+ (0, testHelpers_spec_1.expectDiagnostics)(await onNextDiagnostics(), [
178
+ DiagnosticMessages_1.DiagnosticMessages.cannotFindName('nameNotDefined').message,
179
+ 'Test diagnostic'
180
+ ]);
181
+ });
182
+ it('uses subdir when manifest and brightscript file found', async () => {
183
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/subdir/manifest`, '');
184
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/subdir/source/main.brs`, '');
185
+ await manager.syncProjects([{
186
+ workspaceFolder: testHelpers_spec_1.rootDir
187
+ }]);
188
+ (0, chai_1.expect)(manager.projects.map(x => x.projectPath)).to.eql([
189
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/subdir`
190
+ ]);
191
+ });
192
+ it('removes stale projects', async () => {
193
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/subdir1/bsconfig.json`, '');
194
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/subdir2/bsconfig.json`, '');
195
+ await manager.syncProjects([{
196
+ workspaceFolder: testHelpers_spec_1.rootDir
197
+ }]);
198
+ (0, chai_1.expect)(manager.projects.map(x => x.projectPath).sort()).to.eql([
199
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/subdir1`,
200
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/subdir2`
201
+ ]);
202
+ fsExtra.removeSync(`${testHelpers_spec_1.rootDir}/subdir1/bsconfig.json`);
203
+ await manager.syncProjects([{
204
+ workspaceFolder: testHelpers_spec_1.rootDir
205
+ }]);
206
+ (0, chai_1.expect)(manager.projects.map(x => x.projectPath).sort()).to.eql([
207
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/subdir2`
208
+ ]);
209
+ });
210
+ it('keeps existing projects on subsequent sync calls', async () => {
211
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/subdir1/bsconfig.json`, '');
212
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/subdir2/bsconfig.json`, '');
213
+ await manager.syncProjects([{
214
+ workspaceFolder: testHelpers_spec_1.rootDir
215
+ }]);
216
+ (0, chai_1.expect)(manager.projects.map(x => x.projectPath).sort()).to.eql([
217
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/subdir1`,
218
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/subdir2`
219
+ ]);
220
+ await manager.syncProjects([{
221
+ workspaceFolder: testHelpers_spec_1.rootDir
222
+ }]);
223
+ (0, chai_1.expect)(manager.projects.map(x => x.projectPath).sort()).to.eql([
224
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/subdir1`,
225
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/subdir2`
226
+ ]);
227
+ });
228
+ });
229
+ describe('getCompletions', () => {
230
+ it('works for quick file changes', async () => {
231
+ //set up the project
232
+ await manager.syncProjects([{
233
+ workspaceFolder: testHelpers_spec_1.rootDir
234
+ }]);
235
+ //add the namespace first
236
+ await setFile((0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/alpha.bs`, `
237
+ namespace alpha
238
+ enum Direction
239
+ up
240
+ end enum
241
+ end namespace
242
+ `);
243
+ //add the baseline file
244
+ await setFile((0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/main.bs`, `
245
+ sub test()
246
+ thing = alpha.Directio
247
+ end sub
248
+ `);
249
+ await manager.onIdle();
250
+ //now for the test. type a char, request completions, type a char, request completions (just like how vscode does it)
251
+ void setFile((0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/main.bs`, `
252
+ sub test()
253
+ thing = alpha.Direction
254
+ end sub
255
+ `);
256
+ // const completionsPromise1 = manager.getCompletions({
257
+ // srcPath: s`${rootDir}/source/main.bs`,
258
+ // position: util.createPosition(2, 43)
259
+ // });
260
+ //request completions
261
+ void setFile((0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/main.bs`, `
262
+ sub test()
263
+ thing = alpha.Direction.
264
+ end sub
265
+ `);
266
+ const completionsPromise2 = manager.getCompletions({
267
+ srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/main.bs`,
268
+ position: util_1.default.createPosition(2, 44)
269
+ });
270
+ // //the first set of completions should only have the `alpha.Direction` enum
271
+ // expectCompletionsIncludes(await completionsPromise1, [{
272
+ // label: 'Direction'
273
+ // }]);
274
+ //the next set of completions should only have the alpha.Direction.up enum member
275
+ (0, testHelpers_spec_1.expectCompletionsIncludes)(await completionsPromise2, [{
276
+ label: 'up'
277
+ }]);
278
+ });
279
+ });
280
+ describe('flushDocumentChanges', () => {
281
+ it('does not crash when getting undefined back from projects', async () => {
282
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/main.brs`, ``);
283
+ fsExtra.outputJsonSync(`${testHelpers_spec_1.rootDir}/project1/bsconfig.json`, {
284
+ rootDir: testHelpers_spec_1.rootDir
285
+ });
286
+ await manager.syncProjects([{
287
+ workspaceFolder: testHelpers_spec_1.rootDir
288
+ }]);
289
+ sinon.stub(manager.projects[0], 'applyFileChanges').returns(Promise.resolve([
290
+ //return an undefined item, which used to cause a specific crash
291
+ undefined
292
+ ]));
293
+ await manager['flushDocumentChanges']({
294
+ actions: [{
295
+ srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/main.brs`,
296
+ type: 'set',
297
+ fileContents: 'sub main():end sub',
298
+ allowStandaloneProject: true
299
+ }]
300
+ });
301
+ });
302
+ });
303
+ describe('handleFileChanges', () => {
304
+ it('only sends files to the project that match the include patterns for that project', async () => {
305
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/lib1/a.brs`, ``);
306
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/lib2/a.brs`, ``);
307
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/lib1/b.brs`, ``);
308
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/lib2/b.brs`, ``);
309
+ fsExtra.outputJsonSync(`${testHelpers_spec_1.rootDir}/project1/bsconfig.json`, {
310
+ rootDir: testHelpers_spec_1.rootDir,
311
+ files: [
312
+ 'source/**/a.brs'
313
+ ]
314
+ });
315
+ fsExtra.outputJsonSync(`${testHelpers_spec_1.rootDir}/project2/bsconfig.json`, {
316
+ rootDir: testHelpers_spec_1.rootDir,
317
+ files: [
318
+ 'source/**/b.brs'
319
+ ]
320
+ });
321
+ await manager.syncProjects([{
322
+ workspaceFolder: testHelpers_spec_1.rootDir
323
+ }]);
324
+ let deferred1 = new deferred_1.Deferred();
325
+ let deferred2 = new deferred_1.Deferred();
326
+ const project1 = manager.projects.find(x => x.bsconfigPath.includes('project1'));
327
+ const project2 = manager.projects.find(x => x.bsconfigPath.includes('project2'));
328
+ const project1Stub = sinon.stub(project1, 'applyFileChanges').callsFake(async (...args) => {
329
+ const result = await project1Stub.wrappedMethod.apply(project1, args);
330
+ deferred1.resolve();
331
+ return result;
332
+ });
333
+ const project2Stub = sinon.stub(project2, 'applyFileChanges').callsFake(async (...args) => {
334
+ const result = await project2Stub.wrappedMethod.apply(project1, args);
335
+ deferred2.resolve();
336
+ return result;
337
+ });
338
+ await manager.handleFileChanges([
339
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/lib1/a.brs`, type: vscode_languageserver_protocol_1.FileChangeType.Changed },
340
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/lib2/a.brs`, type: vscode_languageserver_protocol_1.FileChangeType.Changed },
341
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/lib1/b.brs`, type: vscode_languageserver_protocol_1.FileChangeType.Changed },
342
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/lib2/b.brs`, type: vscode_languageserver_protocol_1.FileChangeType.Changed }
343
+ ]);
344
+ //wait for the functions to finish being called
345
+ await Promise.all([
346
+ deferred1.promise,
347
+ deferred2.promise
348
+ ]);
349
+ //project1 should only receive a.brs files
350
+ (0, chai_1.expect)(project1Stub.getCall(0).args[0].map(x => x.srcPath)).to.eql([
351
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/lib1/a.brs`,
352
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/lib2/a.brs`
353
+ ]);
354
+ //project2 should only receive b.brs files
355
+ (0, chai_1.expect)(project2Stub.getCall(0).args[0].map(x => x.srcPath)).to.eql([
356
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/lib1/b.brs`,
357
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/lib2/b.brs`
358
+ ]);
359
+ });
360
+ it('excludes files based on global exclude patterns', async () => {
361
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/file1.md`, ``);
362
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/file2.brs`, ``);
363
+ fsExtra.outputJsonSync(`${testHelpers_spec_1.rootDir}/bsconfig.json`, {
364
+ files: [
365
+ 'source/**/*.brs'
366
+ ]
367
+ });
368
+ await manager.syncProjects([{
369
+ workspaceFolder: testHelpers_spec_1.rootDir
370
+ }]);
371
+ const stub = sinon.stub(manager, 'handleFileChange').callThrough();
372
+ //register an exclusion filter
373
+ pathFilterer.registerExcludeList(testHelpers_spec_1.rootDir, [
374
+ '**/*.md'
375
+ ]);
376
+ //make sure the .md file is ignored
377
+ await manager.handleFileChanges([
378
+ { srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/file1.md`, type: vscode_languageserver_protocol_1.FileChangeType.Created },
379
+ { srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/file2.brs`, type: vscode_languageserver_protocol_1.FileChangeType.Created }
380
+ ]);
381
+ await manager.onIdle();
382
+ (0, chai_1.expect)(stub.getCalls().map(x => x.args[0]).map(x => x.srcPath)).to.eql([
383
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/file2.brs`
384
+ ]);
385
+ stub.reset();
386
+ //remove all filters, make sure the markdown file is included
387
+ pathFilterer.clear();
388
+ await manager.handleFileChanges([
389
+ { srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/file1.md`, type: vscode_languageserver_protocol_1.FileChangeType.Created },
390
+ { srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/file2.brs`, type: vscode_languageserver_protocol_1.FileChangeType.Created }
391
+ ]);
392
+ await manager.onIdle();
393
+ (0, chai_1.expect)(stub.getCalls().flatMap(x => x.args[0]).map(x => x.srcPath)).to.eql([
394
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/file1.md`,
395
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/file2.brs`
396
+ ]);
397
+ });
398
+ it('keeps files from bsconfig.json even if the path matches an exclude list', async () => {
399
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/file1.md`, ``);
400
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/file2.brs`, ``);
401
+ fsExtra.outputJsonSync(`${testHelpers_spec_1.rootDir}/bsconfig.json`, {
402
+ files: ['source/**/*']
403
+ });
404
+ await manager.syncProjects([{
405
+ workspaceFolder: testHelpers_spec_1.rootDir
406
+ }]);
407
+ const stub = sinon.stub(manager['projects'][0], 'applyFileChanges').callThrough();
408
+ //register an exclusion filter
409
+ pathFilterer.registerExcludeList(testHelpers_spec_1.rootDir, [
410
+ '**/*.md'
411
+ ]);
412
+ //make sure the .md file is included because of its project's files array
413
+ await manager.handleFileChanges([
414
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/file1.md`, type: vscode_languageserver_protocol_1.FileChangeType.Created },
415
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/file2.brs`, type: vscode_languageserver_protocol_1.FileChangeType.Created }
416
+ ]);
417
+ await manager.onIdle();
418
+ (0, chai_1.expect)(stub.getCalls().flatMap(x => x.args[0]).map(x => x.srcPath)).to.eql([
419
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/file1.md`,
420
+ (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/file2.brs`
421
+ ]);
422
+ });
423
+ it('does not create a standalone project for files that exist in a known project', async () => {
424
+ fsExtra.outputFileSync((0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/main.brs`, `sub main() : end sub`);
425
+ await manager.syncProjects([{
426
+ workspaceFolder: testHelpers_spec_1.rootDir
427
+ }]);
428
+ await onNextDiagnostics();
429
+ await manager.handleFileChanges([
430
+ { srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/main.brs`, type: vscode_languageserver_protocol_1.FileChangeType.Changed, fileContents: `'test`, allowStandaloneProject: true }
431
+ ]);
432
+ await onNextDiagnostics();
433
+ //there should NOT be a standalone project
434
+ (0, chai_1.expect)(manager['standaloneProjects'].size).to.eql(0);
435
+ });
436
+ it('converts a missing file to a delete', async () => {
437
+ await manager.syncProjects([{
438
+ workspaceFolder: testHelpers_spec_1.rootDir
439
+ }]);
440
+ await onNextDiagnostics();
441
+ let applyFileChangesDeferred = new deferred_1.Deferred();
442
+ const project1 = manager.projects[0];
443
+ const project1Stub = sinon.stub(project1, 'applyFileChanges').callsFake(async (...args) => {
444
+ const result = await project1Stub.wrappedMethod.apply(project1, args);
445
+ applyFileChangesDeferred.resolve(result);
446
+ return result;
447
+ });
448
+ //emit created and changed events for files that don't exist. These turn into delete events
449
+ await manager.handleFileChanges([
450
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/missing1.brs`, type: vscode_languageserver_protocol_1.FileChangeType.Created },
451
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/missing2.brs`, type: vscode_languageserver_protocol_1.FileChangeType.Changed }
452
+ ]);
453
+ //wait for the next set of diagnostics to arrive (signifying the files have been applied)
454
+ const result = await applyFileChangesDeferred.promise;
455
+ //make sure the project has these files
456
+ (0, chai_1.expect)(result.map(x => {
457
+ return { type: x.type, srcPath: x.srcPath };
458
+ })).to.eql([{
459
+ srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/missing1.brs`,
460
+ type: 'set'
461
+ }, {
462
+ srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/missing2.brs`,
463
+ type: 'set'
464
+ }, {
465
+ srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/missing1.brs`,
466
+ type: 'delete'
467
+ }, {
468
+ srcPath: (0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/missing2.brs`,
469
+ type: 'delete'
470
+ }]);
471
+ });
472
+ it('properly syncs changes', async () => {
473
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/lib1.brs`, `sub test1():print "alpha":end sub`);
474
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/lib2.brs`, `sub test2():print "beta":end sub`);
475
+ await manager.syncProjects([{
476
+ workspaceFolder: testHelpers_spec_1.rootDir
477
+ }]);
478
+ (0, testHelpers_spec_1.expectZeroDiagnostics)(await onNextDiagnostics());
479
+ await manager.handleFileChanges([
480
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/lib1.brs`, fileContents: `sub test1():print alpha:end sub`, type: vscode_languageserver_protocol_1.FileChangeType.Changed },
481
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/lib2.brs`, fileContents: `sub test2()::print beta:end sub`, type: vscode_languageserver_protocol_1.FileChangeType.Changed }
482
+ ]);
483
+ (0, testHelpers_spec_1.expectDiagnostics)(await onNextDiagnostics(), [
484
+ DiagnosticMessages_1.DiagnosticMessages.cannotFindName('alpha').message,
485
+ DiagnosticMessages_1.DiagnosticMessages.cannotFindName('beta').message
486
+ ]);
487
+ await manager.handleFileChanges([
488
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/lib1.brs`, fileContents: `sub test1():print "alpha":end sub`, type: vscode_languageserver_protocol_1.FileChangeType.Changed },
489
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/lib2.brs`, fileContents: `sub test2()::print "beta":end sub`, type: vscode_languageserver_protocol_1.FileChangeType.Changed }
490
+ ]);
491
+ (0, testHelpers_spec_1.expectZeroDiagnostics)(await onNextDiagnostics());
492
+ });
493
+ it('adds all new files in a folder', async () => {
494
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/main.brs`, `sub main():print "main":end sub`);
495
+ await manager.syncProjects([{
496
+ workspaceFolder: testHelpers_spec_1.rootDir
497
+ }]);
498
+ (0, testHelpers_spec_1.expectZeroDiagnostics)(await onNextDiagnostics());
499
+ //add a few files to a folder, then register that folder as an "add"
500
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/libs/alpha/beta.brs`, `sub beta(): print one: end sub`);
501
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/libs/alpha/charlie/delta.brs`, `sub delta():print two:end sub`);
502
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/libs/echo/foxtrot.brs`, `sub foxtrot():print three:end sub`);
503
+ await manager.handleFileChanges([
504
+ //register the entire folder as an "add"
505
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/libs`, type: vscode_languageserver_protocol_1.FileChangeType.Created }
506
+ ]);
507
+ (0, testHelpers_spec_1.expectDiagnostics)(await onNextDiagnostics(), [
508
+ DiagnosticMessages_1.DiagnosticMessages.cannotFindName('one').message,
509
+ DiagnosticMessages_1.DiagnosticMessages.cannotFindName('two').message,
510
+ DiagnosticMessages_1.DiagnosticMessages.cannotFindName('three').message
511
+ ]);
512
+ });
513
+ it('removes all files in a folder', async () => {
514
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/main.brs`, `sub main():print "main":end sub`);
515
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/libs/alpha/beta.brs`, `sub beta(): print one: end sub`);
516
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/libs/alpha/charlie/delta.brs`, `sub delta():print two:end sub`);
517
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/source/libs/echo/foxtrot.brs`, `sub foxtrot():print three:end sub`);
518
+ await manager.syncProjects([{
519
+ workspaceFolder: testHelpers_spec_1.rootDir
520
+ }]);
521
+ (0, testHelpers_spec_1.expectDiagnostics)(await onNextDiagnostics(), [
522
+ DiagnosticMessages_1.DiagnosticMessages.cannotFindName('one').message,
523
+ DiagnosticMessages_1.DiagnosticMessages.cannotFindName('two').message,
524
+ DiagnosticMessages_1.DiagnosticMessages.cannotFindName('three').message
525
+ ]);
526
+ await manager.handleFileChanges([
527
+ //register the entire folder as an "add"
528
+ { srcPath: `${testHelpers_spec_1.rootDir}/source/libs`, type: vscode_languageserver_protocol_1.FileChangeType.Deleted }
529
+ ]);
530
+ (0, testHelpers_spec_1.expectZeroDiagnostics)(await onNextDiagnostics());
531
+ });
532
+ });
533
+ describe('threading', () => {
534
+ before(async function workerThreadWarmup() {
535
+ this.timeout(20000);
536
+ await (0, WorkerThreadProject_spec_1.getWakeWorkerThreadPromise)();
537
+ });
538
+ it('spawns a worker thread when threading is enabled', async () => {
539
+ await manager.syncProjects([{
540
+ workspaceFolder: testHelpers_spec_1.rootDir,
541
+ enableThreading: true
542
+ }]);
543
+ (0, chai_1.expect)(manager.projects[0]).instanceof(WorkerThreadProject_1.WorkerThreadProject);
544
+ });
545
+ });
546
+ describe('getProject', () => {
547
+ it('uses .projectPath if param is not a string', async () => {
548
+ await manager.syncProjects([{
549
+ workspaceFolder: testHelpers_spec_1.rootDir
550
+ }]);
551
+ (0, chai_1.expect)(manager['getProject']({
552
+ projectPath: testHelpers_spec_1.rootDir
553
+ })).to.include({
554
+ projectPath: testHelpers_spec_1.rootDir
555
+ });
556
+ });
557
+ });
558
+ describe('createAndActivateProject', () => {
559
+ it('skips creating project if we already have it', async () => {
560
+ await manager.syncProjects([{
561
+ workspaceFolder: testHelpers_spec_1.rootDir
562
+ }]);
563
+ await manager['createAndActivateProject']({
564
+ projectPath: testHelpers_spec_1.rootDir
565
+ });
566
+ (0, chai_1.expect)(manager.projects).to.be.length(1);
567
+ });
568
+ it('uses given projectNumber', async () => {
569
+ await manager['createAndActivateProject']({
570
+ projectPath: testHelpers_spec_1.rootDir,
571
+ workspaceFolder: testHelpers_spec_1.rootDir,
572
+ projectNumber: 3
573
+ });
574
+ (0, chai_1.expect)(manager.projects[0].projectNumber).to.eql(3);
575
+ });
576
+ it('properly tracks a failed run', async () => {
577
+ //force a total crash
578
+ sinon.stub(Project_1.Project.prototype, 'activate').returns(Promise.reject(new Error('Critical failure')));
579
+ let error;
580
+ try {
581
+ await manager['createAndActivateProject']({
582
+ projectPath: testHelpers_spec_1.rootDir,
583
+ workspaceFolder: testHelpers_spec_1.rootDir,
584
+ bsconfigPath: 'subdir1/brsconfig.json'
585
+ });
586
+ }
587
+ catch (e) {
588
+ error = e;
589
+ }
590
+ (0, chai_1.expect)(error).to.include({ message: 'Critical failure' });
591
+ });
592
+ });
593
+ describe('removeProject', () => {
594
+ it('handles undefined', async () => {
595
+ manager['removeProject'](undefined);
596
+ await manager.syncProjects([{
597
+ workspaceFolder: testHelpers_spec_1.rootDir
598
+ }]);
599
+ manager['removeProject'](undefined);
600
+ });
601
+ it('does not crash when removing project that is not there', () => {
602
+ manager['removeProject']({
603
+ projectPath: testHelpers_spec_1.rootDir,
604
+ dispose: () => { }
605
+ });
606
+ });
607
+ });
608
+ describe('getSemanticTokens', () => {
609
+ it('waits until the project is ready', () => {
610
+ });
611
+ });
612
+ describe('standalone projects', () => {
613
+ it('creates a standalone project for files not found in a project', async () => {
614
+ var _a;
615
+ await manager.syncProjects([]);
616
+ await manager.handleFileChanges([{
617
+ srcPath: `${testHelpers_spec_1.rootDir}/source/main.brs`,
618
+ type: vscode_languageserver_protocol_1.FileChangeType.Created,
619
+ fileContents: `sub main():print "main":end sub`,
620
+ allowStandaloneProject: true
621
+ }]);
622
+ await onNextDiagnostics();
623
+ (0, chai_1.expect)((_a = [...manager['standaloneProjects'].values()][0]) === null || _a === void 0 ? void 0 : _a.srcPath).to.eql((0, util_1.standardizePath) `${testHelpers_spec_1.rootDir}/source/main.brs`);
624
+ //it deletes the standalone project when the file is closed
625
+ await manager.handleFileClose({
626
+ srcPath: `${testHelpers_spec_1.rootDir}/source/main.brs`
627
+ });
628
+ (0, chai_1.expect)(manager['standaloneProjects'].size).to.eql(0);
629
+ });
630
+ });
631
+ it('completes promise when project is disposed in the middle of a flow', async function () {
632
+ this.timeout(20000);
633
+ //small plugin to communicate over a socket inside the worker thread.
634
+ //This transpiles from tsc use `require()` for all imports and don't reference external vars
635
+ class Plugin {
636
+ constructor(port, host) {
637
+ this.deferred = this.defer();
638
+ // eslint-disable-next-line
639
+ const net = require('net');
640
+ console.log('Starting server');
641
+ this.server = net.createServer((socket) => {
642
+ console.log('Client connected');
643
+ socket.on('data', (data) => {
644
+ let text = data.toString();
645
+ console.log('message received', JSON.stringify(text));
646
+ //when we get the event to resolve, do it
647
+ if (text === 'resolve') {
648
+ console.log('Resolving promise');
649
+ this.deferred.resolve();
650
+ this.server.close();
651
+ }
652
+ });
653
+ });
654
+ this.server.listen(port, host);
655
+ }
656
+ afterProgramCreate(program) {
657
+ // hijack the function to get workspace symbols, return a promise that resolves in the future
658
+ program.getWorkspaceSymbols = () => {
659
+ return this.deferred.promise;
660
+ };
661
+ }
662
+ defer() {
663
+ let resolve;
664
+ let reject;
665
+ let promise = new Promise((res, rej) => {
666
+ resolve = res;
667
+ reject = rej;
668
+ });
669
+ return {
670
+ resolve: resolve,
671
+ reject: reject,
672
+ promise: promise
673
+ };
674
+ }
675
+ }
676
+ const port = await getPort();
677
+ const host = '127.0.0.1';
678
+ //write a small brighterscript plugin to allow this test to communicate with the thread
679
+ fsExtra.outputFileSync(`${testHelpers_spec_1.rootDir}/plugin.js`, `
680
+ ${Plugin.toString()};
681
+ exports.default = function() {
682
+ return new Plugin(${port}, "${host}");
683
+ };
684
+ `);
685
+ //write a bsconfig that will load this plugin
686
+ fsExtra.outputJsonSync(`${testHelpers_spec_1.rootDir}/bsconfig.json`, {
687
+ plugins: [
688
+ `${testHelpers_spec_1.rootDir}/plugin.js`
689
+ ]
690
+ });
691
+ //wait for the projects to finish syncing/loading
692
+ await manager.syncProjects([{
693
+ workspaceFolder: testHelpers_spec_1.rootDir,
694
+ enableThreading: true
695
+ }]);
696
+ //establish the connection with the plugin
697
+ const connection = net.createConnection({
698
+ host: host,
699
+ port: port
700
+ });
701
+ //do the request to fetch symbols (this will be stalled on purpose by our test plugin)
702
+ let managerGetWorkspaceSymbolPromise = manager.getWorkspaceSymbol();
703
+ //small sleep to let things settle
704
+ await util_1.default.sleep(20);
705
+ //now dispose the project (which should destroy all of the listeners)
706
+ manager['removeProject'](manager.projects[0]);
707
+ //settle again
708
+ await util_1.default.sleep(20);
709
+ console.log('Asking the client to resolve');
710
+ //resolve the request
711
+ connection.write('resolve');
712
+ //now wait to see if we ever get the response back
713
+ let result = await managerGetWorkspaceSymbolPromise;
714
+ //the result should be an empty array, since the only project was rejected in the middle of the request
715
+ (0, chai_1.expect)(result).to.eql([]);
716
+ //test passes if the promise resolves
717
+ });
718
+ it('properly handles reloading when bsconfig.json contents change', async () => {
719
+ fsExtra.outputJsonSync(`${testHelpers_spec_1.rootDir}/bsconfig.json`, {
720
+ files: [
721
+ 'one'
722
+ ]
723
+ });
724
+ //wait for the projects to finish syncing/loading
725
+ await manager.syncProjects([{
726
+ workspaceFolder: testHelpers_spec_1.rootDir,
727
+ enableThreading: false
728
+ }]);
729
+ const stub = sinon.stub(manager, 'reloadProject').callThrough();
730
+ //change the file to new contents
731
+ fsExtra.outputJsonSync(`${testHelpers_spec_1.rootDir}/bsconfig.json`, {
732
+ files: [
733
+ 'two'
734
+ ]
735
+ });
736
+ await manager.handleFileChanges([{
737
+ srcPath: `${testHelpers_spec_1.rootDir}/bsconfig.json`,
738
+ type: vscode_languageserver_protocol_1.FileChangeType.Changed
739
+ }]);
740
+ //the project was reloaded
741
+ (0, chai_1.expect)(stub.callCount).to.eql(1);
742
+ //change the file to the same contents
743
+ fsExtra.outputJsonSync(`${testHelpers_spec_1.rootDir}/bsconfig.json`, {
744
+ files: [
745
+ 'two'
746
+ ]
747
+ });
748
+ await manager.handleFileChanges([{
749
+ srcPath: `${testHelpers_spec_1.rootDir}/bsconfig.json`,
750
+ type: vscode_languageserver_protocol_1.FileChangeType.Changed
751
+ }]);
752
+ //the project was not reloaded this time
753
+ (0, chai_1.expect)(stub.callCount).to.eql(1);
754
+ });
755
+ });
756
+ //# sourceMappingURL=ProjectManager.spec.js.map