brighterscript 0.43.0 → 0.45.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 (112) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/Cache.d.ts +3 -8
  3. package/dist/Cache.js +9 -14
  4. package/dist/Cache.js.map +1 -1
  5. package/dist/DiagnosticMessages.d.ts +21 -1
  6. package/dist/DiagnosticMessages.js +20 -0
  7. package/dist/DiagnosticMessages.js.map +1 -1
  8. package/dist/LanguageServer.d.ts +1 -6
  9. package/dist/LanguageServer.js +3 -3
  10. package/dist/LanguageServer.js.map +1 -1
  11. package/dist/PluginInterface.d.ts +3 -3
  12. package/dist/PluginInterface.js +3 -0
  13. package/dist/PluginInterface.js.map +1 -1
  14. package/dist/Program.d.ts +67 -24
  15. package/dist/Program.js +155 -88
  16. package/dist/Program.js.map +1 -1
  17. package/dist/ProgramBuilder.js +3 -3
  18. package/dist/ProgramBuilder.js.map +1 -1
  19. package/dist/Scope.d.ts +16 -10
  20. package/dist/Scope.js +28 -1
  21. package/dist/Scope.js.map +1 -1
  22. package/dist/XmlScope.d.ts +3 -3
  23. package/dist/astUtils/AstEditor.d.ts +6 -0
  24. package/dist/astUtils/AstEditor.js +10 -0
  25. package/dist/astUtils/AstEditor.js.map +1 -1
  26. package/dist/astUtils/AstEditor.spec.js +37 -0
  27. package/dist/astUtils/AstEditor.spec.js.map +1 -1
  28. package/dist/astUtils/reflection.d.ts +3 -1
  29. package/dist/astUtils/reflection.js +10 -2
  30. package/dist/astUtils/reflection.js.map +1 -1
  31. package/dist/astUtils/visitors.d.ts +3 -1
  32. package/dist/astUtils/visitors.js.map +1 -1
  33. package/dist/astUtils/visitors.spec.js +6 -6
  34. package/dist/astUtils/visitors.spec.js.map +1 -1
  35. package/dist/bscPlugin/BscPlugin.d.ts +4 -1
  36. package/dist/bscPlugin/BscPlugin.js +21 -2
  37. package/dist/bscPlugin/BscPlugin.js.map +1 -1
  38. package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js +18 -16
  39. package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js.map +1 -1
  40. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.d.ts +9 -0
  41. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js +97 -0
  42. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js.map +1 -0
  43. package/dist/bscPlugin/semanticTokens/{SemanticTokensProcessor.spec.d.ts → BrsFileSemanticTokensProcessor.spec.d.ts} +0 -0
  44. package/dist/bscPlugin/semanticTokens/{SemanticTokensProcessor.spec.js → BrsFileSemanticTokensProcessor.spec.js} +32 -4
  45. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js.map +1 -0
  46. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.d.ts +8 -0
  47. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.js +36 -0
  48. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.js.map +1 -0
  49. package/dist/bscPlugin/validation/BrsFileValidator.d.ts +9 -0
  50. package/dist/bscPlugin/validation/BrsFileValidator.js +66 -0
  51. package/dist/bscPlugin/validation/BrsFileValidator.js.map +1 -0
  52. package/dist/bscPlugin/validation/ScopeValidator.d.ts +11 -0
  53. package/dist/bscPlugin/validation/ScopeValidator.js +94 -0
  54. package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -0
  55. package/dist/diagnosticUtils.js +3 -3
  56. package/dist/diagnosticUtils.js.map +1 -1
  57. package/dist/files/BrsFile.Class.spec.js +47 -47
  58. package/dist/files/BrsFile.Class.spec.js.map +1 -1
  59. package/dist/files/BrsFile.d.ts +16 -3
  60. package/dist/files/BrsFile.js +112 -30
  61. package/dist/files/BrsFile.js.map +1 -1
  62. package/dist/files/BrsFile.spec.js +194 -95
  63. package/dist/files/BrsFile.spec.js.map +1 -1
  64. package/dist/files/XmlFile.d.ts +10 -5
  65. package/dist/files/XmlFile.js +6 -1
  66. package/dist/files/XmlFile.js.map +1 -1
  67. package/dist/files/XmlFile.spec.js +83 -81
  68. package/dist/files/XmlFile.spec.js.map +1 -1
  69. package/dist/files/tests/imports.spec.js +23 -23
  70. package/dist/files/tests/imports.spec.js.map +1 -1
  71. package/dist/interfaces.d.ts +29 -8
  72. package/dist/lexer/Lexer.spec.js +8 -0
  73. package/dist/lexer/Lexer.spec.js.map +1 -1
  74. package/dist/lexer/TokenKind.d.ts +2 -0
  75. package/dist/lexer/TokenKind.js +5 -0
  76. package/dist/lexer/TokenKind.js.map +1 -1
  77. package/dist/parser/Parser.d.ts +10 -1
  78. package/dist/parser/Parser.js +90 -1
  79. package/dist/parser/Parser.js.map +1 -1
  80. package/dist/parser/SGParser.spec.js +1 -1
  81. package/dist/parser/SGParser.spec.js.map +1 -1
  82. package/dist/parser/Statement.d.ts +52 -0
  83. package/dist/parser/Statement.js +154 -1
  84. package/dist/parser/Statement.js.map +1 -1
  85. package/dist/parser/Statement.spec.js +1 -1
  86. package/dist/parser/Statement.spec.js.map +1 -1
  87. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js +1 -1
  88. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js.map +1 -1
  89. package/dist/parser/tests/expression/RegexLiteralExpression.spec.js +1 -1
  90. package/dist/parser/tests/expression/RegexLiteralExpression.spec.js.map +1 -1
  91. package/dist/parser/tests/expression/TemplateStringExpression.spec.js +1 -1
  92. package/dist/parser/tests/expression/TemplateStringExpression.spec.js.map +1 -1
  93. package/dist/parser/tests/expression/TernaryExpression.spec.js +1 -1
  94. package/dist/parser/tests/expression/TernaryExpression.spec.js.map +1 -1
  95. package/dist/parser/tests/statement/Enum.spec.d.ts +1 -0
  96. package/dist/parser/tests/statement/Enum.spec.js +774 -0
  97. package/dist/parser/tests/statement/Enum.spec.js.map +1 -0
  98. package/dist/parser/tests/statement/InterfaceStatement.spec.js +1 -1
  99. package/dist/parser/tests/statement/InterfaceStatement.spec.js.map +1 -1
  100. package/dist/types/FunctionType.d.ts +2 -2
  101. package/dist/types/FunctionType.js +3 -3
  102. package/dist/types/FunctionType.js.map +1 -1
  103. package/dist/types/FunctionType.spec.js +2 -2
  104. package/dist/types/FunctionType.spec.js.map +1 -1
  105. package/dist/util.d.ts +22 -0
  106. package/dist/util.js +60 -0
  107. package/dist/util.js.map +1 -1
  108. package/package.json +2 -3
  109. package/dist/bscPlugin/semanticTokens/SemanticTokensProcessor.d.ts +0 -7
  110. package/dist/bscPlugin/semanticTokens/SemanticTokensProcessor.js +0 -63
  111. package/dist/bscPlugin/semanticTokens/SemanticTokensProcessor.js.map +0 -1
  112. package/dist/bscPlugin/semanticTokens/SemanticTokensProcessor.spec.js.map +0 -1
package/dist/Program.d.ts CHANGED
@@ -4,12 +4,13 @@ import type { BsConfig } from './BsConfig';
4
4
  import { Scope } from './Scope';
5
5
  import { BrsFile } from './files/BrsFile';
6
6
  import { XmlFile } from './files/XmlFile';
7
- import type { BsDiagnostic, File, FileReference, FileObj, BscFile, SemanticToken } from './interfaces';
7
+ import type { BsDiagnostic, File, FileReference, FileObj, BscFile, SemanticToken, FileLink } from './interfaces';
8
8
  import { XmlScope } from './XmlScope';
9
9
  import { Logger } from './Logger';
10
10
  import type { ManifestValue } from './preprocessor/Manifest';
11
11
  import PluginInterface from './PluginInterface';
12
12
  import type { FunctionStatement, Statement } from './parser/Statement';
13
+ import type { SourceMapGenerator } from 'source-map';
13
14
  export interface SourceObj {
14
15
  pathAbsolute: string;
15
16
  source: string;
@@ -24,10 +25,6 @@ export interface SignatureInfoObj {
24
25
  key: string;
25
26
  signature: SignatureInformation;
26
27
  }
27
- export interface FileLink<T> {
28
- item: T;
29
- file: BrsFile;
30
- }
31
28
  export declare class Program {
32
29
  /**
33
30
  * The root directory for this program
@@ -116,8 +113,9 @@ export declare class Program {
116
113
  /**
117
114
  * Determine if the specified file is loaded in this program right now.
118
115
  * @param filePath
116
+ * @param normalizePath should the provided path be normalized before use
119
117
  */
120
- hasFile(filePath: string): boolean;
118
+ hasFile(filePath: string, normalizePath?: boolean): boolean;
121
119
  getPkgPath(...args: any[]): any;
122
120
  /**
123
121
  * roku filesystem is case INsensitive, so find the scope by key case insensitive
@@ -132,19 +130,42 @@ export declare class Program {
132
130
  * Find the scope for the specified component
133
131
  */
134
132
  getComponentScope(componentName: string): XmlScope;
133
+ /**
134
+ * Update internal maps with this file reference
135
+ */
136
+ private assignFile;
137
+ /**
138
+ * Remove this file from internal maps
139
+ */
140
+ private unassignFile;
135
141
  /**
136
142
  * Load a file into the program. If that file already exists, it is replaced.
137
143
  * If file contents are provided, those are used, Otherwise, the file is loaded from the file system
138
- * @param relativePath the file path relative to the root dir
144
+ * @param srcPath the file path relative to the root dir
139
145
  * @param fileContents the file contents
146
+ * @deprecated use `setFile` instead
140
147
  */
141
- addOrReplaceFile<T extends BscFile>(relativePath: string, fileContents: string): T;
148
+ addOrReplaceFile<T extends BscFile>(srcPath: string, fileContents: string): T;
142
149
  /**
143
150
  * Load a file into the program. If that file already exists, it is replaced.
144
151
  * @param fileEntry an object that specifies src and dest for the file.
145
152
  * @param fileContents the file contents. If not provided, the file will be loaded from disk
153
+ * @deprecated use `setFile` instead
146
154
  */
147
155
  addOrReplaceFile<T extends BscFile>(fileEntry: FileObj, fileContents: string): T;
156
+ /**
157
+ * Load a file into the program. If that file already exists, it is replaced.
158
+ * If file contents are provided, those are used, Otherwise, the file is loaded from the file system
159
+ * @param srcDestOrPkgPath the absolute path, the pkg path (i.e. `pkg:/path/to/file.brs`), or the destPath (i.e. `path/to/file.brs` relative to `pkg:/`)
160
+ * @param fileContents the file contents
161
+ */
162
+ setFile<T extends BscFile>(srcDestOrPkgPath: string, fileContents: string): T;
163
+ /**
164
+ * Load a file into the program. If that file already exists, it is replaced.
165
+ * @param fileEntry an object that specifies src and dest for the file.
166
+ * @param fileContents the file contents. If not provided, the file will be loaded from disk
167
+ */
168
+ setFile<T extends BscFile>(fileEntry: FileObj, fileContents: string): T;
148
169
  /**
149
170
  * Ensure source scope is created.
150
171
  * Note: automatically called internally, and no-op if it exists already.
@@ -155,28 +176,33 @@ export declare class Program {
155
176
  * Roku is a case insensitive file system. It is an error to have multiple files
156
177
  * with the same path with only case being different.
157
178
  * @param pathAbsolute
179
+ * @deprecated use `getFile` instead, which auto-detects the path type
158
180
  */
159
181
  getFileByPathAbsolute<T extends BrsFile | XmlFile>(pathAbsolute: string): T;
160
182
  /**
161
183
  * Get a list of files for the given (platform-normalized) pkgPath array.
162
184
  * Missing files are just ignored.
185
+ * @deprecated use `getFiles` instead, which auto-detects the path types
163
186
  */
164
187
  getFilesByPkgPaths<T extends BscFile[]>(pkgPaths: string[]): T;
165
188
  /**
166
189
  * Get a file with the specified (platform-normalized) pkg path.
167
190
  * If not found, return undefined
191
+ * @deprecated use `getFile` instead, which auto-detects the path type
168
192
  */
169
193
  getFileByPkgPath<T extends BscFile>(pkgPath: string): T;
170
194
  /**
171
195
  * Remove a set of files from the program
172
- * @param absolutePaths
196
+ * @param filePaths can be an array of srcPath or destPath strings
197
+ * @param normalizePath should this function repair and standardize the filePaths? Passing false should have a performance boost if you can guarantee your paths are already sanitized
173
198
  */
174
- removeFiles(absolutePaths: string[]): void;
199
+ removeFiles(srcPaths: string[], normalizePath?: boolean): void;
175
200
  /**
176
201
  * Remove a file from the program
177
- * @param pathAbsolute
202
+ * @param filePath can be a srcPath, a pkgPath, or a destPath (same as pkgPath but without `pkg:/`)
203
+ * @param normalizePath should this function repair and standardize the path? Passing false should have a performance boost if you can guarantee your path is already sanitized
178
204
  */
179
- removeFile(pathAbsolute: string): void;
205
+ removeFile(filePath: string, normalizePath?: boolean): void;
180
206
  /**
181
207
  * Traverse the entire project, and validate all scopes
182
208
  * @param force - if true, then all scopes are force to validate, even if they aren't marked as dirty
@@ -190,25 +216,35 @@ export declare class Program {
190
216
  * Determine if the given file is included in at least one scope in this program
191
217
  */
192
218
  private fileIsIncludedInAnyScope;
219
+ /**
220
+ * Get the files for a list of filePaths
221
+ * @param filePaths can be an array of srcPath or a destPath strings
222
+ * @param normalizePath should this function repair and standardize the paths? Passing false should have a performance boost if you can guarantee your paths are already sanitized
223
+ */
224
+ getFiles<T extends BscFile>(filePaths: string[], normalizePath?: boolean): T[];
193
225
  /**
194
226
  * Get the file at the given path
195
- * @param pathAbsolute
227
+ * @param filePath can be a srcPath or a destPath
228
+ * @param normalizePath should this function repair and standardize the path? Passing false should have a performance boost if you can guarantee your path is already sanitized
196
229
  */
197
- private getFile;
230
+ getFile<T extends BscFile>(filePath: string, normalizePath?: boolean): T;
198
231
  /**
199
232
  * Get a list of all scopes the file is loaded into
200
233
  * @param file
201
234
  */
202
235
  getScopesForFile(file: XmlFile | BrsFile): Scope[];
236
+ /**
237
+ * Get the first found scope for a file.
238
+ */
239
+ getFirstScopeForFile(file: XmlFile | BrsFile): Scope;
203
240
  getStatementsByName(name: string, originFile: BrsFile, namespaceName?: string): FileLink<Statement>[];
204
241
  getStatementsForXmlFile(scope: XmlScope, filterName?: string): FileLink<FunctionStatement>[];
205
242
  /**
206
243
  * Find all available completion items at the given position
207
- * @param pathAbsolute
208
- * @param lineIndex
209
- * @param columnIndex
244
+ * @param filePath can be a srcPath or a destPath
245
+ * @param position the position (line & column) where completions should be found
210
246
  */
211
- getCompletions(pathAbsolute: string, position: Position): CompletionItem[];
247
+ getCompletions(filePath: string, position: Position): CompletionItem[];
212
248
  /**
213
249
  * Goes through each file and builds a list of workspace symbols for the program. Used by LanguageServer's onWorkspaceSymbol functionality
214
250
  */
@@ -240,12 +276,12 @@ export declare class Program {
240
276
  * Transpile a single file and get the result as a string.
241
277
  * This does not write anything to the file system.
242
278
  */
243
- getTranspiledFileContents(pathAbsolute: string): {
244
- pathAbsolute: string;
245
- pkgPath: string;
246
- code: string;
247
- map: import("source-map").SourceMapGenerator;
248
- };
279
+ getTranspiledFileContents(pathAbsolute: string): FileTranspileResult;
280
+ /**
281
+ * Internal function used to transpile files.
282
+ * This does not write anything to the file system
283
+ */
284
+ private _getTranspiledFileContents;
249
285
  transpile(fileEntries: FileObj[], stagingFolderPath: string): Promise<void>;
250
286
  /**
251
287
  * Find a list of files in the program that have a function with the given name (case INsensitive)
@@ -262,3 +298,10 @@ export declare class Program {
262
298
  private _manifest;
263
299
  dispose(): void;
264
300
  }
301
+ export interface FileTranspileResult {
302
+ pathAbsolute: string;
303
+ pkgPath: string;
304
+ code: string;
305
+ map: SourceMapGenerator;
306
+ typedef: string;
307
+ }
package/dist/Program.js CHANGED
@@ -87,11 +87,11 @@ class Program {
87
87
  */
88
88
  get bslibPkgPath() {
89
89
  //if there's an aliased (preferred) version of bslib from roku_modules loaded into the program, use that
90
- if (this.getFileByPkgPath(bslibAliasedRokuModulesPkgPath)) {
90
+ if (this.getFile(bslibAliasedRokuModulesPkgPath)) {
91
91
  return bslibAliasedRokuModulesPkgPath;
92
92
  //if there's a non-aliased version of bslib from roku_modules, use that
93
93
  }
94
- else if (this.getFileByPkgPath(bslibNonAliasedRokuModulesPkgPath)) {
94
+ else if (this.getFile(bslibNonAliasedRokuModulesPkgPath)) {
95
95
  return bslibNonAliasedRokuModulesPkgPath;
96
96
  //default to the embedded version
97
97
  }
@@ -220,10 +220,10 @@ class Program {
220
220
  /**
221
221
  * Determine if the specified file is loaded in this program right now.
222
222
  * @param filePath
223
+ * @param normalizePath should the provided path be normalized before use
223
224
  */
224
- hasFile(filePath) {
225
- filePath = (0, util_1.standardizePath) `${filePath}`;
226
- return this.files[filePath] !== undefined;
225
+ hasFile(filePath, normalizePath = true) {
226
+ return !!this.getFile(filePath, normalizePath);
227
227
  }
228
228
  getPkgPath(...args) {
229
229
  throw new Error('Not implemented');
@@ -255,8 +255,27 @@ class Program {
255
255
  var _a;
256
256
  return (_a = this.getComponent(componentName)) === null || _a === void 0 ? void 0 : _a.scope;
257
257
  }
258
+ /**
259
+ * Update internal maps with this file reference
260
+ */
261
+ assignFile(file) {
262
+ this.files[file.pathAbsolute.toLowerCase()] = file;
263
+ this.pkgMap[file.pkgPath.toLowerCase()] = file;
264
+ return file;
265
+ }
266
+ /**
267
+ * Remove this file from internal maps
268
+ */
269
+ unassignFile(file) {
270
+ delete this.files[file.pathAbsolute.toLowerCase()];
271
+ delete this.pkgMap[file.pkgPath.toLowerCase()];
272
+ return file;
273
+ }
258
274
  addOrReplaceFile(fileParam, fileContents) {
259
- assert.ok(fileParam, 'fileEntry is required');
275
+ return this.setFile(fileParam, fileContents);
276
+ }
277
+ setFile(fileParam, fileContents) {
278
+ assert.ok(fileParam, 'fileParam is required');
260
279
  let srcPath;
261
280
  let pkgPath;
262
281
  if (typeof fileParam === 'string') {
@@ -267,7 +286,7 @@ class Program {
267
286
  srcPath = (0, util_1.standardizePath) `${fileParam.src}`;
268
287
  pkgPath = (0, util_1.standardizePath) `${fileParam.dest}`;
269
288
  }
270
- let file = this.logger.time(Logger_1.LogLevel.debug, ['Program.addOrReplaceFile()', chalk_1.default.green(srcPath)], () => {
289
+ let file = this.logger.time(Logger_1.LogLevel.debug, ['Program.setFile()', chalk_1.default.green(srcPath)], () => {
271
290
  assert.ok(srcPath, 'fileEntry.src is required');
272
291
  assert.ok(pkgPath, 'fileEntry.dest is required');
273
292
  //if the file is already loaded, remove it
@@ -277,15 +296,13 @@ class Program {
277
296
  let fileExtension = path.extname(srcPath).toLowerCase();
278
297
  let file;
279
298
  if (fileExtension === '.brs' || fileExtension === '.bs') {
280
- let brsFile = new BrsFile_1.BrsFile(srcPath, pkgPath, this);
299
+ //add the file to the program
300
+ const brsFile = this.assignFile(new BrsFile_1.BrsFile(srcPath, pkgPath, this));
281
301
  //add file to the `source` dependency list
282
302
  if (brsFile.pkgPath.startsWith(startOfSourcePkgPath)) {
283
303
  this.createSourceScope();
284
304
  this.dependencyGraph.addDependency('scope:source', brsFile.dependencyGraphKey);
285
305
  }
286
- //add the file to the program
287
- this.files[srcPath] = brsFile;
288
- this.pkgMap[brsFile.pkgPath.toLowerCase()] = brsFile;
289
306
  let sourceObj = {
290
307
  pathAbsolute: srcPath,
291
308
  source: fileContents
@@ -298,27 +315,14 @@ class Program {
298
315
  this.plugins.emit('afterFileParse', brsFile);
299
316
  file = brsFile;
300
317
  brsFile.attachDependencyGraph(this.dependencyGraph);
301
- this.plugins.emit('beforeFileValidate', {
302
- program: this,
303
- file: file
304
- });
305
- file.validate();
306
- //emit an event to allow plugins to contribute to the file validation process
307
- this.plugins.emit('onFileValidate', {
308
- program: this,
309
- file: file
310
- });
311
- this.plugins.emit('afterFileValidate', brsFile);
312
318
  }
313
319
  else if (
314
320
  //is xml file
315
321
  fileExtension === '.xml' &&
316
322
  //resides in the components folder (Roku will only parse xml files in the components folder)
317
323
  pkgPath.toLowerCase().startsWith(util_1.util.pathSepNormalize(`components/`))) {
318
- let xmlFile = new XmlFile_1.XmlFile(srcPath, pkgPath, this);
319
324
  //add the file to the program
320
- this.files[srcPath] = xmlFile;
321
- this.pkgMap[xmlFile.pkgPath.toLowerCase()] = xmlFile;
325
+ const xmlFile = this.assignFile(new XmlFile_1.XmlFile(srcPath, pkgPath, this));
322
326
  let sourceObj = {
323
327
  pathAbsolute: srcPath,
324
328
  source: fileContents
@@ -335,18 +339,6 @@ class Program {
335
339
  this.addScope(scope);
336
340
  //register this compoent now that we have parsed it and know its component name
337
341
  this.registerComponent(xmlFile, scope);
338
- //emit an event before starting to validate this file
339
- this.plugins.emit('beforeFileValidate', {
340
- file: file,
341
- program: this
342
- });
343
- xmlFile.validate();
344
- //emit an event to allow plugins to contribute to the file validation process
345
- this.plugins.emit('onFileValidate', {
346
- file: xmlFile,
347
- program: this
348
- });
349
- this.plugins.emit('afterFileValidate', xmlFile);
350
342
  }
351
343
  else {
352
344
  //TODO do we actually need to implement this? Figure out how to handle img paths
@@ -377,6 +369,7 @@ class Program {
377
369
  * Roku is a case insensitive file system. It is an error to have multiple files
378
370
  * with the same path with only case being different.
379
371
  * @param pathAbsolute
372
+ * @deprecated use `getFile` instead, which auto-detects the path type
380
373
  */
381
374
  getFileByPathAbsolute(pathAbsolute) {
382
375
  pathAbsolute = (0, util_1.standardizePath) `${pathAbsolute}`;
@@ -389,6 +382,7 @@ class Program {
389
382
  /**
390
383
  * Get a list of files for the given (platform-normalized) pkgPath array.
391
384
  * Missing files are just ignored.
385
+ * @deprecated use `getFiles` instead, which auto-detects the path types
392
386
  */
393
387
  getFilesByPkgPaths(pkgPaths) {
394
388
  return pkgPaths
@@ -398,29 +392,29 @@ class Program {
398
392
  /**
399
393
  * Get a file with the specified (platform-normalized) pkg path.
400
394
  * If not found, return undefined
395
+ * @deprecated use `getFile` instead, which auto-detects the path type
401
396
  */
402
397
  getFileByPkgPath(pkgPath) {
403
398
  return this.pkgMap[pkgPath.toLowerCase()];
404
399
  }
405
400
  /**
406
401
  * Remove a set of files from the program
407
- * @param absolutePaths
402
+ * @param filePaths can be an array of srcPath or destPath strings
403
+ * @param normalizePath should this function repair and standardize the filePaths? Passing false should have a performance boost if you can guarantee your paths are already sanitized
408
404
  */
409
- removeFiles(absolutePaths) {
410
- for (let pathAbsolute of absolutePaths) {
411
- this.removeFile(pathAbsolute);
405
+ removeFiles(srcPaths, normalizePath = true) {
406
+ for (let srcPath of srcPaths) {
407
+ this.removeFile(srcPath, normalizePath);
412
408
  }
413
409
  }
414
410
  /**
415
411
  * Remove a file from the program
416
- * @param pathAbsolute
412
+ * @param filePath can be a srcPath, a pkgPath, or a destPath (same as pkgPath but without `pkg:/`)
413
+ * @param normalizePath should this function repair and standardize the path? Passing false should have a performance boost if you can guarantee your path is already sanitized
417
414
  */
418
- removeFile(pathAbsolute) {
419
- this.logger.debug('Program.removeFile()', pathAbsolute);
420
- if (!path.isAbsolute(pathAbsolute)) {
421
- throw new Error(`Path must be absolute: "${pathAbsolute}"`);
422
- }
423
- let file = this.getFile(pathAbsolute);
415
+ removeFile(filePath, normalizePath = true) {
416
+ this.logger.debug('Program.removeFile()', filePath);
417
+ let file = this.getFile(filePath, normalizePath);
424
418
  if (file) {
425
419
  this.plugins.emit('beforeFileDispose', file);
426
420
  //if there is a scope named the same as this file's path, remove it (i.e. xml scopes)
@@ -434,8 +428,7 @@ class Program {
434
428
  this.plugins.emit('afterScopeDispose', scope);
435
429
  }
436
430
  //remove the file from the program
437
- delete this.files[file.pathAbsolute];
438
- delete this.pkgMap[file.pkgPath.toLowerCase()];
431
+ this.unassignFile(file);
439
432
  this.dependencyGraph.remove(file.dependencyGraphKey);
440
433
  //if this is a pkg:/source file, notify the `source` scope that it has changed
441
434
  if (file.pkgPath.startsWith(startOfSourcePkgPath)) {
@@ -454,23 +447,40 @@ class Program {
454
447
  */
455
448
  validate() {
456
449
  this.logger.time(Logger_1.LogLevel.log, ['Validating project'], () => {
450
+ var _a;
457
451
  this.diagnostics = [];
458
452
  this.plugins.emit('beforeProgramValidate', this);
459
- this.logger.time(Logger_1.LogLevel.info, ['Validate all scopes'], () => {
460
- for (let scopeName in this.scopes) {
461
- let scope = this.scopes[scopeName];
462
- scope.validate();
463
- }
464
- });
465
- //find any files NOT loaded into a scope
466
- for (let filePath in this.files) {
467
- let file = this.files[filePath];
453
+ //validate every file
454
+ for (const file of Object.values(this.files)) {
455
+ //find any files NOT loaded into a scope
468
456
  if (!this.fileIsIncludedInAnyScope(file)) {
469
457
  this.logger.debug('Program.validate(): fileNotReferenced by any scope', () => chalk_1.default.green(file === null || file === void 0 ? void 0 : file.pkgPath));
470
458
  //the file is not loaded in any scope
471
459
  this.diagnostics.push(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.fileNotReferencedByAnyOtherFile()), { file: file, range: util_1.util.createRange(0, 0, 0, Number.MAX_VALUE) }));
472
460
  }
461
+ //for every unvalidated file, validate it
462
+ if (!file.isValidated) {
463
+ this.plugins.emit('beforeFileValidate', {
464
+ program: this,
465
+ file: file
466
+ });
467
+ //emit an event to allow plugins to contribute to the file validation process
468
+ this.plugins.emit('onFileValidate', {
469
+ program: this,
470
+ file: file
471
+ });
472
+ //call file.validate() IF the file has that function defined
473
+ (_a = file.validate) === null || _a === void 0 ? void 0 : _a.call(file);
474
+ file.isValidated = true;
475
+ this.plugins.emit('afterFileValidate', file);
476
+ }
473
477
  }
478
+ this.logger.time(Logger_1.LogLevel.info, ['Validate all scopes'], () => {
479
+ for (let scopeName in this.scopes) {
480
+ let scope = this.scopes[scopeName];
481
+ scope.validate();
482
+ }
483
+ });
474
484
  this.detectDuplicateComponentNames();
475
485
  this.plugins.emit('afterProgramValidate', this);
476
486
  });
@@ -519,13 +529,31 @@ class Program {
519
529
  }
520
530
  return false;
521
531
  }
532
+ /**
533
+ * Get the files for a list of filePaths
534
+ * @param filePaths can be an array of srcPath or a destPath strings
535
+ * @param normalizePath should this function repair and standardize the paths? Passing false should have a performance boost if you can guarantee your paths are already sanitized
536
+ */
537
+ getFiles(filePaths, normalizePath = true) {
538
+ return filePaths
539
+ .map(filePath => this.getFile(filePath, normalizePath))
540
+ .filter(file => file !== undefined);
541
+ }
522
542
  /**
523
543
  * Get the file at the given path
524
- * @param pathAbsolute
544
+ * @param filePath can be a srcPath or a destPath
545
+ * @param normalizePath should this function repair and standardize the path? Passing false should have a performance boost if you can guarantee your path is already sanitized
525
546
  */
526
- getFile(pathAbsolute) {
527
- pathAbsolute = (0, util_1.standardizePath) `${pathAbsolute}`;
528
- return this.files[pathAbsolute];
547
+ getFile(filePath, normalizePath = true) {
548
+ if (typeof filePath !== 'string') {
549
+ return undefined;
550
+ }
551
+ else if (path.isAbsolute(filePath)) {
552
+ return this.files[(normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase()];
553
+ }
554
+ else {
555
+ return this.pkgMap[(normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase()];
556
+ }
529
557
  }
530
558
  /**
531
559
  * Get a list of all scopes the file is loaded into
@@ -541,6 +569,17 @@ class Program {
541
569
  }
542
570
  return result;
543
571
  }
572
+ /**
573
+ * Get the first found scope for a file.
574
+ */
575
+ getFirstScopeForFile(file) {
576
+ for (let key in this.scopes) {
577
+ let scope = this.scopes[key];
578
+ if (scope.hasFile(file)) {
579
+ return scope;
580
+ }
581
+ }
582
+ }
544
583
  getStatementsByName(name, originFile, namespaceName) {
545
584
  var _a, _b;
546
585
  let results = new Map();
@@ -599,12 +638,11 @@ class Program {
599
638
  }
600
639
  /**
601
640
  * Find all available completion items at the given position
602
- * @param pathAbsolute
603
- * @param lineIndex
604
- * @param columnIndex
641
+ * @param filePath can be a srcPath or a destPath
642
+ * @param position the position (line & column) where completions should be found
605
643
  */
606
- getCompletions(pathAbsolute, position) {
607
- let file = this.getFile(pathAbsolute);
644
+ getCompletions(filePath, position) {
645
+ let file = this.getFile(filePath);
608
646
  if (!file) {
609
647
  return [];
610
648
  }
@@ -996,9 +1034,49 @@ class Program {
996
1034
  * This does not write anything to the file system.
997
1035
  */
998
1036
  getTranspiledFileContents(pathAbsolute) {
999
- let file = this.getFile(pathAbsolute);
1000
- let result = file.transpile();
1001
- return Object.assign(Object.assign({}, result), { pathAbsolute: file.pathAbsolute, pkgPath: file.pkgPath });
1037
+ return this._getTranspiledFileContents(this.getFile(pathAbsolute));
1038
+ }
1039
+ /**
1040
+ * Internal function used to transpile files.
1041
+ * This does not write anything to the file system
1042
+ */
1043
+ _getTranspiledFileContents(file, outputPath) {
1044
+ const editor = new AstEditor_1.AstEditor();
1045
+ this.plugins.emit('beforeFileTranspile', {
1046
+ file: file,
1047
+ outputPath: outputPath,
1048
+ editor: editor
1049
+ });
1050
+ //if we have any edits, assume the file needs to be transpiled
1051
+ if (editor.hasChanges) {
1052
+ //use the `editor` because it'll track the previous value for us and revert later on
1053
+ editor.setProperty(file, 'needsTranspiled', true);
1054
+ }
1055
+ //transpile the file
1056
+ const result = file.transpile();
1057
+ //generate the typedef if enabled
1058
+ let typedef;
1059
+ if ((0, reflection_1.isBrsFile)(file) && this.options.emitDefinitions) {
1060
+ typedef = file.getTypedef();
1061
+ }
1062
+ const event = {
1063
+ file: file,
1064
+ outputPath: outputPath,
1065
+ editor: editor,
1066
+ code: result.code,
1067
+ map: result.map,
1068
+ typedef: typedef
1069
+ };
1070
+ this.plugins.emit('afterFileTranspile', event);
1071
+ //undo all `editor` edits that may have been applied to this file.
1072
+ editor.undoAll();
1073
+ return {
1074
+ pathAbsolute: file.pathAbsolute,
1075
+ pkgPath: file.pkgPath,
1076
+ code: event.code,
1077
+ map: event.map,
1078
+ typedef: event.typedef
1079
+ };
1002
1080
  }
1003
1081
  async transpile(fileEntries, stagingFolderPath) {
1004
1082
  // map fileEntries using their path as key, to avoid excessive "find()" operations
@@ -1022,8 +1100,7 @@ class Program {
1022
1100
  outputPath = (0, util_1.standardizePath) `${stagingFolderPath}/${outputPath}`;
1023
1101
  return {
1024
1102
  file: file,
1025
- outputPath: outputPath,
1026
- editor: new AstEditor_1.AstEditor()
1103
+ outputPath: outputPath
1027
1104
  };
1028
1105
  });
1029
1106
  this.plugins.emit('beforeProgramTranspile', this, entries);
@@ -1032,32 +1109,22 @@ class Program {
1032
1109
  if ((0, reflection_1.isBrsFile)(entry.file) && entry.file.isTypedef) {
1033
1110
  return;
1034
1111
  }
1035
- this.plugins.emit('beforeFileTranspile', entry);
1036
1112
  const { file, outputPath } = entry;
1037
- //if we have any edits, assume the file needs to be transpiled
1038
- if (entry.editor.hasChanges) {
1039
- //use the `editor` because it'll track the previous value for us and revert later on
1040
- entry.editor.setProperty(file, 'needsTranspiled', true);
1041
- }
1042
- const result = file.transpile();
1113
+ const fileTranspileResult = this._getTranspiledFileContents(file, outputPath);
1043
1114
  //make sure the full dir path exists
1044
1115
  await fsExtra.ensureDir(path.dirname(outputPath));
1045
1116
  if (await fsExtra.pathExists(outputPath)) {
1046
1117
  throw new Error(`Error while transpiling "${file.pathAbsolute}". A file already exists at "${outputPath}" and will not be overwritten.`);
1047
1118
  }
1048
- const writeMapPromise = result.map ? fsExtra.writeFile(`${outputPath}.map`, result.map.toString()) : null;
1119
+ const writeMapPromise = fileTranspileResult.map ? fsExtra.writeFile(`${outputPath}.map`, fileTranspileResult.map.toString()) : null;
1049
1120
  await Promise.all([
1050
- fsExtra.writeFile(outputPath, result.code),
1121
+ fsExtra.writeFile(outputPath, fileTranspileResult.code),
1051
1122
  writeMapPromise
1052
1123
  ]);
1053
- if ((0, reflection_1.isBrsFile)(file) && this.options.emitDefinitions) {
1054
- const typedef = file.getTypedef();
1124
+ if (fileTranspileResult.typedef) {
1055
1125
  const typedefPath = outputPath.replace(/\.brs$/i, '.d.bs');
1056
- await fsExtra.writeFile(typedefPath, typedef);
1126
+ await fsExtra.writeFile(typedefPath, fileTranspileResult.typedef);
1057
1127
  }
1058
- this.plugins.emit('afterFileTranspile', entry);
1059
- //undo all `editor` edits that may have been applied to this file.
1060
- entry.editor.undoAll();
1061
1128
  });
1062
1129
  //if there's no bslib file already loaded into the program, copy it to the staging directory
1063
1130
  if (!this.getFileByPkgPath(bslibAliasedRokuModulesPkgPath) && !this.getFileByPkgPath((0, util_1.standardizePath) `source/bslib.brs`)) {