brighterscript 0.66.0-alpha.6 → 0.66.0-alpha.7

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 (244) hide show
  1. package/CHANGELOG.md +52 -10
  2. package/README.md +16 -0
  3. package/bsconfig.schema.json +15 -0
  4. package/dist/ActionPipeline.d.ts +10 -0
  5. package/dist/ActionPipeline.js +40 -0
  6. package/dist/ActionPipeline.js.map +1 -0
  7. package/dist/BsConfig.d.ts +15 -1
  8. package/dist/CommentFlagProcessor.d.ts +4 -3
  9. package/dist/CommentFlagProcessor.js.map +1 -1
  10. package/dist/DiagnosticMessages.d.ts +2 -0
  11. package/dist/DiagnosticMessages.js +25 -13
  12. package/dist/DiagnosticMessages.js.map +1 -1
  13. package/dist/LanguageServer.js.map +1 -1
  14. package/dist/PluginInterface.d.ts +11 -2
  15. package/dist/PluginInterface.js +69 -10
  16. package/dist/PluginInterface.js.map +1 -1
  17. package/dist/Program.d.ts +84 -37
  18. package/dist/Program.js +390 -267
  19. package/dist/Program.js.map +1 -1
  20. package/dist/ProgramBuilder.d.ts +10 -4
  21. package/dist/ProgramBuilder.js +44 -54
  22. package/dist/ProgramBuilder.js.map +1 -1
  23. package/dist/Scope.d.ts +13 -13
  24. package/dist/Scope.js +28 -26
  25. package/dist/Scope.js.map +1 -1
  26. package/dist/XmlScope.d.ts +5 -4
  27. package/dist/XmlScope.js +11 -10
  28. package/dist/XmlScope.js.map +1 -1
  29. package/dist/astUtils/{AstEditor.d.ts → Editor.d.ts} +6 -1
  30. package/dist/astUtils/{AstEditor.js → Editor.js} +9 -3
  31. package/dist/astUtils/Editor.js.map +1 -0
  32. package/dist/astUtils/{AstEditor.spec.js → Editor.spec.js} +10 -6
  33. package/dist/astUtils/Editor.spec.js.map +1 -0
  34. package/dist/astUtils/reflection.d.ts +9 -4
  35. package/dist/astUtils/reflection.js +23 -7
  36. package/dist/astUtils/reflection.js.map +1 -1
  37. package/dist/astUtils/reflection.spec.js +2 -2
  38. package/dist/astUtils/reflection.spec.js.map +1 -1
  39. package/dist/astUtils/visitors.d.ts +3 -3
  40. package/dist/astUtils/visitors.spec.js +7 -7
  41. package/dist/astUtils/visitors.spec.js.map +1 -1
  42. package/dist/bscPlugin/BscPlugin.d.ts +10 -2
  43. package/dist/bscPlugin/BscPlugin.js +24 -4
  44. package/dist/bscPlugin/BscPlugin.js.map +1 -1
  45. package/dist/bscPlugin/FileWriter.d.ts +6 -0
  46. package/dist/bscPlugin/FileWriter.js +24 -0
  47. package/dist/bscPlugin/FileWriter.js.map +1 -0
  48. package/dist/bscPlugin/codeActions/CodeActionsProcessor.js +8 -8
  49. package/dist/bscPlugin/codeActions/CodeActionsProcessor.js.map +1 -1
  50. package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js.map +1 -1
  51. package/dist/bscPlugin/completions/CompletionsProcessor.d.ts +5 -0
  52. package/dist/bscPlugin/completions/CompletionsProcessor.js +38 -15
  53. package/dist/bscPlugin/completions/CompletionsProcessor.js.map +1 -1
  54. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js +65 -3
  55. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js.map +1 -1
  56. package/dist/bscPlugin/fileProviders/FileProvider.d.ts +9 -0
  57. package/dist/bscPlugin/fileProviders/FileProvider.js +51 -0
  58. package/dist/bscPlugin/fileProviders/FileProvider.js.map +1 -0
  59. package/dist/bscPlugin/hover/HoverProcessor.d.ts +1 -7
  60. package/dist/bscPlugin/hover/HoverProcessor.js +1 -7
  61. package/dist/bscPlugin/hover/HoverProcessor.js.map +1 -1
  62. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.d.ts +1 -0
  63. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js +43 -0
  64. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js.map +1 -1
  65. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js +22 -0
  66. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js.map +1 -1
  67. package/dist/bscPlugin/serialize/BslibInjector.spec.js +19 -0
  68. package/dist/bscPlugin/serialize/BslibInjector.spec.js.map +1 -0
  69. package/dist/bscPlugin/serialize/BslibManager.d.ts +9 -0
  70. package/dist/bscPlugin/serialize/BslibManager.js +40 -0
  71. package/dist/bscPlugin/serialize/BslibManager.js.map +1 -0
  72. package/dist/bscPlugin/serialize/FileSerializer.d.ts +9 -0
  73. package/dist/bscPlugin/serialize/FileSerializer.js +72 -0
  74. package/dist/bscPlugin/serialize/FileSerializer.js.map +1 -0
  75. package/dist/bscPlugin/transpile/{BrsFilePreTranspileProcessor.d.ts → BrsFileTranspileProcessor.d.ts} +4 -2
  76. package/dist/bscPlugin/transpile/{BrsFilePreTranspileProcessor.js → BrsFileTranspileProcessor.js} +25 -4
  77. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.js.map +1 -0
  78. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.d.ts +1 -0
  79. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.js +41 -0
  80. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.js.map +1 -0
  81. package/dist/bscPlugin/transpile/XmlFilePreTranspileProcessor.d.ts +2 -2
  82. package/dist/bscPlugin/transpile/XmlFilePreTranspileProcessor.js.map +1 -1
  83. package/dist/bscPlugin/validation/ScopeValidator.js +24 -15
  84. package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
  85. package/dist/bscPlugin/validation/ScopeValidator.spec.js +326 -0
  86. package/dist/bscPlugin/validation/ScopeValidator.spec.js.map +1 -1
  87. package/dist/bscPlugin/validation/XmlFileValidator.js +2 -2
  88. package/dist/bscPlugin/validation/XmlFileValidator.js.map +1 -1
  89. package/dist/cli.js +1 -0
  90. package/dist/cli.js.map +1 -1
  91. package/dist/deferred.d.ts +2 -2
  92. package/dist/deferred.js.map +1 -1
  93. package/dist/diagnosticUtils.d.ts +1 -0
  94. package/dist/diagnosticUtils.js +4 -3
  95. package/dist/diagnosticUtils.js.map +1 -1
  96. package/dist/examples/plugins/removePrint.js +1 -1
  97. package/dist/examples/plugins/removePrint.js.map +1 -1
  98. package/dist/files/AssetFile.d.ts +26 -0
  99. package/dist/files/AssetFile.js +26 -0
  100. package/dist/files/AssetFile.js.map +1 -0
  101. package/dist/files/BrsFile.Class.spec.js +40 -40
  102. package/dist/files/BrsFile.Class.spec.js.map +1 -1
  103. package/dist/files/BrsFile.d.ts +42 -15
  104. package/dist/files/BrsFile.js +120 -78
  105. package/dist/files/BrsFile.js.map +1 -1
  106. package/dist/files/BrsFile.spec.js +266 -167
  107. package/dist/files/BrsFile.spec.js.map +1 -1
  108. package/dist/files/Factory.d.ts +25 -0
  109. package/dist/files/Factory.js +22 -0
  110. package/dist/files/Factory.js.map +1 -0
  111. package/dist/files/File.d.ts +106 -0
  112. package/dist/files/File.js +16 -0
  113. package/dist/files/File.js.map +1 -0
  114. package/dist/files/LazyFileData.d.ts +20 -0
  115. package/dist/files/LazyFileData.js +54 -0
  116. package/dist/files/LazyFileData.js.map +1 -0
  117. package/dist/files/LazyFileData.spec.d.ts +1 -0
  118. package/dist/files/LazyFileData.spec.js +27 -0
  119. package/dist/files/LazyFileData.spec.js.map +1 -0
  120. package/dist/files/XmlFile.d.ts +55 -17
  121. package/dist/files/XmlFile.js +88 -47
  122. package/dist/files/XmlFile.js.map +1 -1
  123. package/dist/files/XmlFile.spec.js +64 -57
  124. package/dist/files/XmlFile.spec.js.map +1 -1
  125. package/dist/files/tests/imports.spec.js +21 -8
  126. package/dist/files/tests/imports.spec.js.map +1 -1
  127. package/dist/files/tests/optionalChaning.spec.js +14 -14
  128. package/dist/files/tests/optionalChaning.spec.js.map +1 -1
  129. package/dist/globalCallables.js +1 -1
  130. package/dist/globalCallables.js.map +1 -1
  131. package/dist/index.d.ts +4 -1
  132. package/dist/index.js +4 -1
  133. package/dist/index.js.map +1 -1
  134. package/dist/interfaces.d.ts +312 -84
  135. package/dist/interfaces.js.map +1 -1
  136. package/dist/lexer/Lexer.js +1 -1
  137. package/dist/lexer/TokenKind.js +0 -1
  138. package/dist/lexer/TokenKind.js.map +1 -1
  139. package/dist/parser/AstNode.d.ts +2 -2
  140. package/dist/parser/AstNode.js +1 -1
  141. package/dist/parser/AstNode.js.map +1 -1
  142. package/dist/parser/BrsTranspileState.d.ts +3 -2
  143. package/dist/parser/BrsTranspileState.js +3 -2
  144. package/dist/parser/BrsTranspileState.js.map +1 -1
  145. package/dist/parser/Expression.d.ts +1 -1
  146. package/dist/parser/Expression.js +3 -9
  147. package/dist/parser/Expression.js.map +1 -1
  148. package/dist/parser/Parser.js +3 -0
  149. package/dist/parser/Parser.js.map +1 -1
  150. package/dist/parser/Parser.spec.js +15 -1
  151. package/dist/parser/Parser.spec.js.map +1 -1
  152. package/dist/parser/SGParser.d.ts +2 -2
  153. package/dist/parser/SGParser.js +3 -3
  154. package/dist/parser/SGParser.js.map +1 -1
  155. package/dist/parser/SGParser.spec.js +2 -2
  156. package/dist/parser/SGParser.spec.js.map +1 -1
  157. package/dist/parser/SGTypes.d.ts +1 -1
  158. package/dist/parser/Statement.d.ts +3 -2
  159. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js +10 -10
  160. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js.map +1 -1
  161. package/dist/parser/tests/expression/RegexLiteralExpression.spec.js +10 -10
  162. package/dist/parser/tests/expression/RegexLiteralExpression.spec.js.map +1 -1
  163. package/dist/parser/tests/expression/SourceLiteralExpression.spec.js +24 -24
  164. package/dist/parser/tests/expression/SourceLiteralExpression.spec.js.map +1 -1
  165. package/dist/parser/tests/expression/TemplateStringExpression.spec.js +64 -36
  166. package/dist/parser/tests/expression/TemplateStringExpression.spec.js.map +1 -1
  167. package/dist/parser/tests/expression/TernaryExpression.spec.js +34 -34
  168. package/dist/parser/tests/expression/TernaryExpression.spec.js.map +1 -1
  169. package/dist/parser/tests/statement/ConstStatement.spec.js +16 -16
  170. package/dist/parser/tests/statement/ConstStatement.spec.js.map +1 -1
  171. package/dist/parser/tests/statement/Continue.spec.js +2 -2
  172. package/dist/parser/tests/statement/Continue.spec.js.map +1 -1
  173. package/dist/parser/tests/statement/Enum.spec.js +26 -26
  174. package/dist/parser/tests/statement/Enum.spec.js.map +1 -1
  175. package/dist/parser/tests/statement/For.spec.js +6 -6
  176. package/dist/parser/tests/statement/For.spec.js.map +1 -1
  177. package/dist/parser/tests/statement/ForEach.spec.js +4 -4
  178. package/dist/parser/tests/statement/ForEach.spec.js.map +1 -1
  179. package/dist/parser/tests/statement/InterfaceStatement.spec.js +12 -12
  180. package/dist/parser/tests/statement/InterfaceStatement.spec.js.map +1 -1
  181. package/dist/parser/tests/statement/PrintStatement.spec.js +10 -10
  182. package/dist/parser/tests/statement/PrintStatement.spec.js.map +1 -1
  183. package/dist/preprocessor/Manifest.d.ts +1 -1
  184. package/dist/preprocessor/Manifest.js +2 -2
  185. package/dist/preprocessor/Manifest.js.map +1 -1
  186. package/dist/roku-types/data.json +81 -143
  187. package/dist/roku-types/index.d.ts +15 -11
  188. package/dist/types/ArrayType.d.ts +1 -1
  189. package/dist/types/ArrayType.js +4 -0
  190. package/dist/types/ArrayType.js.map +1 -1
  191. package/dist/types/ArrayType.spec.js +1 -1
  192. package/dist/types/ArrayType.spec.js.map +1 -1
  193. package/dist/types/AssociativeArrayType.d.ts +1 -1
  194. package/dist/types/AssociativeArrayType.js +1 -1
  195. package/dist/types/AssociativeArrayType.js.map +1 -1
  196. package/dist/types/BooleanType.js +2 -1
  197. package/dist/types/BooleanType.js.map +1 -1
  198. package/dist/types/BscType.js +2 -0
  199. package/dist/types/BscType.js.map +1 -1
  200. package/dist/types/BuiltInInterfaceAdder.d.ts +3 -0
  201. package/dist/types/BuiltInInterfaceAdder.js +32 -13
  202. package/dist/types/BuiltInInterfaceAdder.js.map +1 -1
  203. package/dist/types/ComponentType.js +3 -0
  204. package/dist/types/ComponentType.js.map +1 -1
  205. package/dist/types/DoubleType.js +3 -1
  206. package/dist/types/DoubleType.js.map +1 -1
  207. package/dist/types/FloatType.js +3 -1
  208. package/dist/types/FloatType.js.map +1 -1
  209. package/dist/types/IntegerType.js +3 -1
  210. package/dist/types/IntegerType.js.map +1 -1
  211. package/dist/types/InterfaceType.d.ts +1 -1
  212. package/dist/types/InterfaceType.js +1 -8
  213. package/dist/types/InterfaceType.js.map +1 -1
  214. package/dist/types/InterfaceType.spec.js +23 -0
  215. package/dist/types/InterfaceType.spec.js.map +1 -1
  216. package/dist/types/LongIntegerType.js +3 -1
  217. package/dist/types/LongIntegerType.js.map +1 -1
  218. package/dist/types/ObjectType.d.ts +1 -1
  219. package/dist/types/StringType.js +2 -2
  220. package/dist/types/StringType.js.map +1 -1
  221. package/dist/types/TypedFunctionType.d.ts +6 -1
  222. package/dist/types/TypedFunctionType.js +45 -15
  223. package/dist/types/TypedFunctionType.js.map +1 -1
  224. package/dist/types/TypedFunctionType.spec.js +99 -0
  225. package/dist/types/TypedFunctionType.spec.js.map +1 -1
  226. package/dist/types/UnionType.js +7 -0
  227. package/dist/types/UnionType.js.map +1 -1
  228. package/dist/types/helper.spec.js +15 -0
  229. package/dist/types/helper.spec.js.map +1 -1
  230. package/dist/types/helpers.d.ts +3 -0
  231. package/dist/types/helpers.js +29 -1
  232. package/dist/types/helpers.js.map +1 -1
  233. package/dist/util.d.ts +11 -5
  234. package/dist/util.js +79 -40
  235. package/dist/util.js.map +1 -1
  236. package/dist/validators/ClassValidator.js.map +1 -1
  237. package/package.json +1 -1
  238. package/dist/astUtils/AstEditor.js.map +0 -1
  239. package/dist/astUtils/AstEditor.spec.js.map +0 -1
  240. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.js.map +0 -1
  241. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.spec.js +0 -31
  242. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.spec.js.map +0 -1
  243. /package/dist/astUtils/{AstEditor.spec.d.ts → Editor.spec.d.ts} +0 -0
  244. /package/dist/bscPlugin/{transpile/BrsFilePreTranspileProcessor.spec.d.ts → serialize/BslibInjector.spec.d.ts} +0 -0
package/dist/Program.js CHANGED
@@ -6,8 +6,6 @@ const fsExtra = require("fs-extra");
6
6
  const path = require("path");
7
7
  const Scope_1 = require("./Scope");
8
8
  const DiagnosticMessages_1 = require("./DiagnosticMessages");
9
- const BrsFile_1 = require("./files/BrsFile");
10
- const XmlFile_1 = require("./files/XmlFile");
11
9
  const util_1 = require("./util");
12
10
  const XmlScope_1 = require("./XmlScope");
13
11
  const DiagnosticFilterer_1 = require("./DiagnosticFilterer");
@@ -20,8 +18,7 @@ const vscode_uri_1 = require("vscode-uri");
20
18
  const PluginInterface_1 = require("./PluginInterface");
21
19
  const reflection_1 = require("./astUtils/reflection");
22
20
  const BscPlugin_1 = require("./bscPlugin/BscPlugin");
23
- const AstEditor_1 = require("./astUtils/AstEditor");
24
- const roku_deploy_1 = require("roku-deploy");
21
+ const Editor_1 = require("./astUtils/Editor");
25
22
  const CallExpressionInfo_1 = require("./bscPlugin/CallExpressionInfo");
26
23
  const SignatureHelpUtil_1 = require("./bscPlugin/SignatureHelpUtil");
27
24
  const DiagnosticSeverityAdjuster_1 = require("./DiagnosticSeverityAdjuster");
@@ -36,11 +33,14 @@ const LongIntegerType_1 = require("./types/LongIntegerType");
36
33
  const ObjectType_1 = require("./types/ObjectType");
37
34
  const VoidType_1 = require("./types/VoidType");
38
35
  const FunctionType_1 = require("./types/FunctionType");
36
+ const Factory_1 = require("./files/Factory");
37
+ const ActionPipeline_1 = require("./ActionPipeline");
38
+ const LazyFileData_1 = require("./files/LazyFileData");
39
+ const roku_deploy_1 = require("roku-deploy");
39
40
  const roku_types_1 = require("./roku-types");
40
41
  const ComponentType_1 = require("./types/ComponentType");
41
42
  const types_1 = require("./types");
42
43
  const BuiltInInterfaceAdder_1 = require("./types/BuiltInInterfaceAdder");
43
- const startOfSourcePkgPath = `source${path.sep}`;
44
44
  const bslibNonAliasedRokuModulesPkgPath = (0, util_1.standardizePath) `source/roku_modules/rokucommunity_bslib/bslib.brs`;
45
45
  const bslibAliasedRokuModulesPkgPath = (0, util_1.standardizePath) `source/roku_modules/bslib/bslib.brs`;
46
46
  class Program {
@@ -50,6 +50,10 @@ class Program {
50
50
  */
51
51
  options, logger, plugins) {
52
52
  this.options = options;
53
+ /**
54
+ * An editor that plugins can use to modify program-level things during the build flow. Don't use this to edit files (they have their own `.editor`)
55
+ */
56
+ this.editor = new Editor_1.Editor();
53
57
  /**
54
58
  * A graph of all files and their dependencies.
55
59
  * For example:
@@ -68,7 +72,16 @@ class Program {
68
72
  * A map of every file loaded into this program, indexed by its original file location
69
73
  */
70
74
  this.files = {};
71
- this.pkgMap = {};
75
+ /**
76
+ * A map of every file loaded into this program, indexed by its destPath
77
+ */
78
+ this.destMap = new Map();
79
+ /**
80
+ * Plugins can contribute multiple virtual files for a single physical file.
81
+ * This collection links the virtual files back to the physical file that produced them.
82
+ * The key is the standardized and lower-cased srcPath
83
+ */
84
+ this.fileClusters = new Map();
72
85
  this.scopes = {};
73
86
  /**
74
87
  * A map of every component currently loaded into the program, indexed by the component name.
@@ -81,6 +94,8 @@ class Program {
81
94
  * Keeps a set of all the components that need to have their types updated during the current validation cycle
82
95
  */
83
96
  this.componentSymbolsToUpdate = new Set();
97
+ this.getTranspiledFileContentsPipeline = new ActionPipeline_1.ActionPipeline();
98
+ this.buildPipeline = new ActionPipeline_1.ActionPipeline();
84
99
  this.options = util_1.util.normalizeConfig(options);
85
100
  this.logger = logger || new Logger_1.Logger(options.logLevel);
86
101
  this.plugins = plugins || new PluginInterface_1.default([], { logger: this.logger });
@@ -89,6 +104,7 @@ class Program {
89
104
  //normalize the root dir path
90
105
  this.options.rootDir = util_1.util.getRootDir(this.options);
91
106
  this.createGlobalScope();
107
+ this.fileFactory = new Factory_1.FileFactory(this);
92
108
  }
93
109
  createGlobalScope() {
94
110
  //create the 'global' scope
@@ -126,7 +142,7 @@ class Program {
126
142
  this.globalScope.symbolTable.addSymbol(nodeName, { description: nodeData.description }, nodeType, SymbolTable_1.SymbolTypeFlag.typetime);
127
143
  }
128
144
  else {
129
- nodeType = this.globalScope.symbolTable.getSymbolType(nodeData.name, { flags: SymbolTable_1.SymbolTypeFlag.typetime });
145
+ nodeType = this.globalScope.symbolTable.getSymbolType(nodeName, { flags: SymbolTable_1.SymbolTypeFlag.typetime });
130
146
  }
131
147
  return nodeType;
132
148
  }
@@ -182,7 +198,7 @@ class Program {
182
198
  //default to the embedded version
183
199
  }
184
200
  else {
185
- return `source${path.sep}bslib.brs`;
201
+ return `${this.options.bslibDestinationDir}${path.sep}bslib.brs`;
186
202
  }
187
203
  }
188
204
  get bslibPrefix() {
@@ -203,7 +219,7 @@ class Program {
203
219
  var _a;
204
220
  if (componentName) {
205
221
  //return the first compoment in the list with this name
206
- //(components are ordered in this list by pkgPath to ensure consistency)
222
+ //(components are ordered in this list by destPath to ensure consistency)
207
223
  return (_a = this.components[componentName.toLowerCase()]) === null || _a === void 0 ? void 0 : _a[0];
208
224
  }
209
225
  else {
@@ -223,8 +239,8 @@ class Program {
223
239
  scope: scope
224
240
  });
225
241
  this.components[key].sort((a, b) => {
226
- const pathA = a.file.pkgPath.toLowerCase();
227
- const pathB = b.file.pkgPath.toLowerCase();
242
+ const pathA = a.file.destPath.toLowerCase();
243
+ const pathB = b.file.destPath.toLowerCase();
228
244
  if (pathA < pathB) {
229
245
  return -1;
230
246
  }
@@ -300,6 +316,7 @@ class Program {
300
316
  //attach (or re-attach) the dependencyGraph for every component whose position changed
301
317
  if (file.dependencyGraphIndex !== i) {
302
318
  file.dependencyGraphIndex = i;
319
+ this.dependencyGraph.addOrReplace(file.dependencyGraphKey, file.dependencies);
303
320
  file.attachDependencyGraph(this.dependencyGraph);
304
321
  scope.attachDependencyGraph(this.dependencyGraph);
305
322
  }
@@ -336,7 +353,7 @@ class Program {
336
353
  //get the diagnostics from all unreferenced files
337
354
  let unreferencedFiles = this.getUnreferencedFiles();
338
355
  for (let file of unreferencedFiles) {
339
- diagnostics.push(...file.getDiagnostics());
356
+ diagnostics.push(...file.diagnostics);
340
357
  }
341
358
  const filteredDiagnostics = this.logger.time(Logger_1.LogLevel.debug, ['filter diagnostics'], () => {
342
359
  //filter out diagnostics based on our diagnostic filters
@@ -361,11 +378,9 @@ class Program {
361
378
  hasFile(filePath, normalizePath = true) {
362
379
  return !!this.getFile(filePath, normalizePath);
363
380
  }
364
- getPkgPath(...args) {
365
- throw new Error('Not implemented');
366
- }
367
381
  /**
368
382
  * roku filesystem is case INsensitive, so find the scope by key case insensitive
383
+ * @param scopeName xml scope names are their `destPath`. Source scope is stored with the key `"source"`
369
384
  */
370
385
  getScopeByName(scopeName) {
371
386
  if (!scopeName) {
@@ -394,8 +409,14 @@ class Program {
394
409
  * Update internal maps with this file reference
395
410
  */
396
411
  assignFile(file) {
412
+ const fileAddEvent = {
413
+ file: file,
414
+ program: this
415
+ };
416
+ this.plugins.emit('beforeFileAdd', fileAddEvent);
397
417
  this.files[file.srcPath.toLowerCase()] = file;
398
- this.pkgMap[file.pkgPath.toLowerCase()] = file;
418
+ this.destMap.set(file.destPath.toLowerCase(), file);
419
+ this.plugins.emit('afterFileAdd', fileAddEvent);
399
420
  return file;
400
421
  }
401
422
  /**
@@ -403,104 +424,102 @@ class Program {
403
424
  */
404
425
  unassignFile(file) {
405
426
  delete this.files[file.srcPath.toLowerCase()];
406
- delete this.pkgMap[file.pkgPath.toLowerCase()];
427
+ this.destMap.delete(file.destPath.toLowerCase());
407
428
  return file;
408
429
  }
409
- setFile(fileParam, fileContents) {
430
+ setFile(fileParam, fileData) {
410
431
  //normalize the file paths
411
- const { srcPath, pkgPath } = this.getPaths(fileParam, this.options.rootDir);
432
+ const { srcPath, destPath } = this.getPaths(fileParam, this.options.rootDir);
412
433
  let file = this.logger.time(Logger_1.LogLevel.debug, ['Program.setFile()', chalk_1.default.green(srcPath)], () => {
434
+ var _a, _b, _c;
413
435
  //if the file is already loaded, remove it
414
436
  if (this.hasFile(srcPath)) {
415
437
  this.removeFile(srcPath);
416
438
  }
417
- let fileExtension = path.extname(srcPath).toLowerCase();
418
- let file;
419
- if (fileExtension === '.brs' || fileExtension === '.bs') {
420
- //add the file to the program
421
- const brsFile = this.assignFile(new BrsFile_1.BrsFile(srcPath, pkgPath, this));
422
- //add file to the `source` dependency list
423
- if (brsFile.pkgPath.startsWith(startOfSourcePkgPath)) {
424
- this.createSourceScope();
425
- this.dependencyGraph.addDependency('scope:source', brsFile.dependencyGraphKey);
426
- }
427
- let beforeFileParseEvent = {
428
- program: this,
429
- srcPath: srcPath,
430
- source: fileContents
431
- };
432
- this.plugins.emit('beforeFileParse', beforeFileParseEvent);
433
- this.logger.time(Logger_1.LogLevel.debug, ['parse', chalk_1.default.green(srcPath)], () => {
434
- brsFile.parse(beforeFileParseEvent.source);
435
- });
436
- //notify plugins that this file has finished parsing
437
- this.plugins.emit('afterFileParse', {
438
- program: this,
439
- file: brsFile
440
- });
441
- file = brsFile;
442
- brsFile.attachDependencyGraph(this.dependencyGraph);
439
+ const data = new LazyFileData_1.LazyFileData(fileData);
440
+ const event = new ProvideFileEventInternal(this, srcPath, destPath, data, this.fileFactory);
441
+ this.plugins.emit('beforeProvideFile', event);
442
+ this.plugins.emit('provideFile', event);
443
+ this.plugins.emit('afterProvideFile', event);
444
+ //if no files were provided, create a AssetFile to represent it.
445
+ if (event.files.length === 0) {
446
+ event.files.push(this.fileFactory.AssetFile({
447
+ srcPath: event.srcPath,
448
+ destPath: event.destPath,
449
+ pkgPath: event.destPath,
450
+ data: data
451
+ }));
443
452
  }
444
- else if (
445
- //is xml file
446
- fileExtension === '.xml' &&
447
- //resides in the components folder (Roku will only parse xml files in the components folder)
448
- pkgPath.toLowerCase().startsWith(util_1.util.pathSepNormalize(`components/`))) {
449
- //add the file to the program
450
- const xmlFile = this.assignFile(new XmlFile_1.XmlFile(srcPath, pkgPath, this));
451
- let event = {
452
- program: this,
453
- srcPath: srcPath,
454
- source: fileContents
455
- };
456
- this.plugins.emit('beforeFileParse', event);
457
- this.logger.time(Logger_1.LogLevel.debug, ['parse', chalk_1.default.green(srcPath)], () => {
458
- xmlFile.parse(event.source);
459
- });
460
- //notify plugins that this file has finished parsing
461
- this.plugins.emit('afterFileParse', {
462
- program: this,
463
- file: xmlFile
464
- });
465
- file = xmlFile;
466
- //create a new scope for this xml file
467
- let scope = new XmlScope_1.XmlScope(xmlFile, this);
468
- this.addScope(scope);
469
- //register this component now that we have parsed it and know its component name
470
- this.registerComponent(xmlFile, scope);
471
- //notify plugins that the scope is created and the component is registered
472
- this.plugins.emit('afterScopeCreate', {
473
- program: this,
474
- scope: scope
475
- });
453
+ //find the file instance for the srcPath that triggered this action.
454
+ const primaryFile = event.files.find(x => x.srcPath === srcPath);
455
+ if (!primaryFile) {
456
+ throw new Error(`No file provided for srcPath '${srcPath}'. Instead, received ${JSON.stringify(event.files.map(x => ({
457
+ type: x.type,
458
+ srcPath: x.srcPath,
459
+ destPath: x.destPath
460
+ })))}`);
476
461
  }
477
- else {
478
- //TODO do we actually need to implement this? Figure out how to handle img paths
479
- // let genericFile = this.files[srcPath] = <any>{
480
- // srcPath: srcPath,
481
- // pkgPath: pkgPath,
482
- // wasProcessed: true
483
- // } as File;
484
- // file = <any>genericFile;
462
+ //link the virtual files to the primary file
463
+ this.fileClusters.set((_a = primaryFile.srcPath) === null || _a === void 0 ? void 0 : _a.toLowerCase(), event.files);
464
+ for (const file of event.files) {
465
+ file.srcPath = (0, util_1.standardizePath)(file.srcPath);
466
+ if (file.destPath) {
467
+ file.destPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(file.destPath, this.options.rootDir, '')}`;
468
+ }
469
+ if (file.pkgPath) {
470
+ file.pkgPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(file.pkgPath, this.options.rootDir, '')}`;
471
+ }
472
+ else {
473
+ file.pkgPath = file.destPath;
474
+ }
475
+ file.excludeFromOutput = file.excludeFromOutput === true;
476
+ //set the dependencyGraph key for every file to its destPath
477
+ file.dependencyGraphKey = file.destPath.toLowerCase();
478
+ this.assignFile(file);
479
+ //register a callback anytime this file's dependencies change
480
+ if (typeof file.onDependenciesChanged === 'function') {
481
+ (_b = file.disposables) !== null && _b !== void 0 ? _b : (file.disposables = []);
482
+ file.disposables.push(this.dependencyGraph.onchange(file.dependencyGraphKey, file.onDependenciesChanged.bind(file)));
483
+ }
484
+ //register this file (and its dependencies) with the dependency graph
485
+ this.dependencyGraph.addOrReplace(file.dependencyGraphKey, (_c = file.dependencies) !== null && _c !== void 0 ? _c : []);
486
+ //if this is a `source` file, add it to the source scope's dependency list
487
+ if (this.isSourceBrsFile(file)) {
488
+ this.createSourceScope();
489
+ this.dependencyGraph.addDependency('scope:source', file.dependencyGraphKey);
490
+ }
491
+ //if this is an xml file in the components folder, register it as a component
492
+ if (this.isComponentsXmlFile(file)) {
493
+ //create a new scope for this xml file
494
+ let scope = new XmlScope_1.XmlScope(file, this);
495
+ this.addScope(scope);
496
+ //register this compoent now that we have parsed it and know its component name
497
+ this.registerComponent(file, scope);
498
+ //notify plugins that the scope is created and the component is registered
499
+ this.plugins.emit('afterScopeCreate', {
500
+ program: this,
501
+ scope: scope
502
+ });
503
+ }
485
504
  }
486
- return file;
505
+ return primaryFile;
487
506
  });
488
507
  return file;
489
508
  }
490
509
  /**
491
- * Given a srcPath, a pkgPath, or both, resolve whichever is missing, relative to rootDir.
510
+ * Given a srcPath, a destPath, or both, resolve whichever is missing, relative to rootDir.
492
511
  * @param fileParam an object representing file paths
493
512
  * @param rootDir must be a pre-normalized path
494
513
  */
495
514
  getPaths(fileParam, rootDir) {
496
515
  let srcPath;
497
- let pkgPath;
516
+ let destPath;
498
517
  assert.ok(fileParam, 'fileParam is required');
499
- //lift the srcPath and pkgPath vars from the incoming param
518
+ //lift the path vars from the incoming param
500
519
  if (typeof fileParam === 'string') {
501
520
  fileParam = this.removePkgPrefix(fileParam);
502
521
  srcPath = (0, util_1.standardizePath) `${path.resolve(rootDir, fileParam)}`;
503
- pkgPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(srcPath, rootDir, '')}`;
522
+ destPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(srcPath, rootDir, '')}`;
504
523
  }
505
524
  else {
506
525
  let param = fileParam;
@@ -511,30 +530,30 @@ class Program {
511
530
  srcPath = (0, util_1.standardizePath) `${param.srcPath}`;
512
531
  }
513
532
  if (param.dest) {
514
- pkgPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.dest)}`;
533
+ destPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.dest)}`;
515
534
  }
516
535
  if (param.pkgPath) {
517
- pkgPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.pkgPath)}`;
536
+ destPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.pkgPath)}`;
518
537
  }
519
538
  }
520
- //if there's no srcPath, use the pkgPath to build an absolute srcPath
539
+ //if there's no srcPath, use the destPath to build an absolute srcPath
521
540
  if (!srcPath) {
522
- srcPath = (0, util_1.standardizePath) `${rootDir}/${pkgPath}`;
541
+ srcPath = (0, util_1.standardizePath) `${rootDir}/${destPath}`;
523
542
  }
524
543
  //coerce srcPath to an absolute path
525
544
  if (!path.isAbsolute(srcPath)) {
526
545
  srcPath = util_1.util.standardizePath(srcPath);
527
546
  }
528
- //if there's no pkgPath, compute relative path from rootDir
529
- if (!pkgPath) {
530
- pkgPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(srcPath, rootDir, '')}`;
547
+ //if destPath isn't set, compute it from the other paths
548
+ if (!destPath) {
549
+ destPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(srcPath, rootDir, '')}`;
531
550
  }
532
551
  assert.ok(srcPath, 'fileEntry.src is required');
533
- assert.ok(pkgPath, 'fileEntry.dest is required');
552
+ assert.ok(destPath, 'fileEntry.dest is required');
534
553
  return {
535
554
  srcPath: srcPath,
536
- //remove leading slash from pkgPath
537
- pkgPath: pkgPath.replace(/^[\/\\]+/, '')
555
+ //remove leading slash
556
+ destPath: destPath.replace(/^[\/\\]+/, '')
538
557
  };
539
558
  }
540
559
  /**
@@ -543,6 +562,18 @@ class Program {
543
562
  removePkgPrefix(path) {
544
563
  return path.replace(/^pkg:\//i, '');
545
564
  }
565
+ /**
566
+ * Is this file a .brs file found somewhere within the `pkg:/source/` folder?
567
+ */
568
+ isSourceBrsFile(file) {
569
+ return !!/^(pkg:\/)?source[\/\\]/.exec(file.destPath);
570
+ }
571
+ /**
572
+ * Is this file a .brs file found somewhere within the `pkg:/source/` folder?
573
+ */
574
+ isComponentsXmlFile(file) {
575
+ return (0, reflection_1.isXmlFile)(file) && !!/^(pkg:\/)?components[\/\\]/.exec(file.destPath);
576
+ }
546
577
  /**
547
578
  * Ensure source scope is created.
548
579
  * Note: automatically called internally, and no-op if it exists already.
@@ -570,46 +601,55 @@ class Program {
570
601
  }
571
602
  /**
572
603
  * Remove a file from the program
573
- * @param filePath can be a srcPath, a pkgPath, or a destPath (same as pkgPath but without `pkg:/`)
604
+ * @param filePath can be a srcPath, a destPath, or a destPath with leading `pkg:/`
574
605
  * @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
575
606
  */
576
607
  removeFile(filePath, normalizePath = true) {
608
+ var _a, _b, _c, _d;
577
609
  this.logger.debug('Program.removeFile()', filePath);
578
- let file = this.getFile(filePath, normalizePath);
579
- if (file) {
580
- const fileDisposeEvent = {
581
- program: this,
582
- file: file
583
- };
584
- this.plugins.emit('beforeFileDispose', fileDisposeEvent);
610
+ const paths = this.getPaths(filePath, this.options.rootDir);
611
+ //there can be one or more File entries for a single srcPath, so get all of them and remove them all
612
+ const files = (_b = this.fileClusters.get((_a = paths.srcPath) === null || _a === void 0 ? void 0 : _a.toLowerCase())) !== null && _b !== void 0 ? _b : [this.getFile(filePath, normalizePath)];
613
+ for (const file of files) {
614
+ //if a file has already been removed, nothing more needs to be done here
615
+ if (!file || !this.hasFile(file.srcPath)) {
616
+ continue;
617
+ }
618
+ const event = { file: file, program: this };
619
+ this.plugins.emit('beforeFileRemove', event);
585
620
  //if there is a scope named the same as this file's path, remove it (i.e. xml scopes)
586
- let scope = this.scopes[file.pkgPath];
621
+ let scope = this.scopes[file.destPath];
587
622
  if (scope) {
588
623
  const scopeDisposeEvent = {
589
624
  program: this,
590
625
  scope: scope
591
626
  };
592
627
  this.plugins.emit('beforeScopeDispose', scopeDisposeEvent);
628
+ this.plugins.emit('onScopeDispose', scopeDisposeEvent);
593
629
  scope.dispose();
594
630
  //notify dependencies of this scope that it has been removed
595
631
  this.dependencyGraph.remove(scope.dependencyGraphKey);
596
- delete this.scopes[file.pkgPath];
632
+ delete this.scopes[file.destPath];
597
633
  this.plugins.emit('afterScopeDispose', scopeDisposeEvent);
598
634
  }
599
635
  //remove the file from the program
600
636
  this.unassignFile(file);
601
637
  this.dependencyGraph.remove(file.dependencyGraphKey);
602
638
  //if this is a pkg:/source file, notify the `source` scope that it has changed
603
- if (file.pkgPath.startsWith(startOfSourcePkgPath)) {
639
+ if (this.isSourceBrsFile(file)) {
604
640
  this.dependencyGraph.removeDependency('scope:source', file.dependencyGraphKey);
605
641
  }
606
642
  //if this is a component, remove it from our components map
607
643
  if ((0, reflection_1.isXmlFile)(file)) {
608
644
  this.unregisterComponent(file);
609
645
  }
646
+ //dispose any disposable things on the file
647
+ for (const disposable of (_c = file === null || file === void 0 ? void 0 : file.disposables) !== null && _c !== void 0 ? _c : []) {
648
+ disposable();
649
+ }
610
650
  //dispose file
611
- file === null || file === void 0 ? void 0 : file.dispose();
612
- this.plugins.emit('afterFileDispose', fileDisposeEvent);
651
+ (_d = file === null || file === void 0 ? void 0 : file.dispose) === null || _d === void 0 ? void 0 : _d.call(file);
652
+ this.plugins.emit('afterFileRemove', event);
613
653
  }
614
654
  }
615
655
  /**
@@ -622,6 +662,7 @@ class Program {
622
662
  program: this
623
663
  };
624
664
  this.plugins.emit('beforeProgramValidate', programValidateEvent);
665
+ this.plugins.emit('onProgramValidate', programValidateEvent);
625
666
  //validate every file
626
667
  for (const file of Object.values(this.files)) {
627
668
  //for every unvalidated file, validate it
@@ -706,12 +747,13 @@ class Program {
706
747
  getFile(filePath, normalizePath = true) {
707
748
  if (typeof filePath !== 'string') {
708
749
  return undefined;
750
+ //is the path absolute (or the `virtual:` prefix)
709
751
  }
710
- else if (path.isAbsolute(filePath)) {
752
+ else if (/^(?:(?:virtual:[\/\\])|(?:\w:)|(?:[\/\\]))/gmi.exec(filePath)) {
711
753
  return this.files[(normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase()];
712
754
  }
713
755
  else {
714
- return this.pkgMap[(normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase()];
756
+ return this.destMap.get((normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase());
715
757
  }
716
758
  }
717
759
  /**
@@ -753,7 +795,8 @@ class Program {
753
795
  //look through all files in scope for matches
754
796
  for (const scope of this.getScopesForFile(originFile)) {
755
797
  for (const file of scope.getAllFiles()) {
756
- if ((0, reflection_1.isXmlFile)(file) || filesSearched.has(file)) {
798
+ //skip non-brs files, or files we've already processed
799
+ if (!(0, reflection_1.isBrsFile)(file) || filesSearched.has(file)) {
757
800
  continue;
758
801
  }
759
802
  filesSearched.add(file);
@@ -786,7 +829,8 @@ class Program {
786
829
  }
787
830
  //look through all files in scope for matches
788
831
  for (const file of scope.getOwnFiles()) {
789
- if ((0, reflection_1.isXmlFile)(file) || filesSearched.has(file)) {
832
+ //skip non-brs files, or files we've already processed
833
+ if (!(0, reflection_1.isBrsFile)(file) || filesSearched.has(file)) {
790
834
  continue;
791
835
  }
792
836
  filesSearched.add(file);
@@ -935,10 +979,12 @@ class Program {
935
979
  getReferences(srcPath, position) {
936
980
  //find the file
937
981
  let file = this.getFile(srcPath);
938
- if (!file) {
982
+ if ((0, reflection_1.isBrsFile)(file) || (0, reflection_1.isXmlFile)(file)) {
983
+ return file.getReferences(position);
984
+ }
985
+ else {
939
986
  return null;
940
987
  }
941
- return file.getReferences(position);
942
988
  }
943
989
  /**
944
990
  * Transpile a single file and get the result as a string.
@@ -949,168 +995,211 @@ class Program {
949
995
  * @param filePath can be a srcPath or a destPath
950
996
  */
951
997
  async getTranspiledFileContents(filePath) {
952
- let fileMap = await roku_deploy_1.rokuDeploy.getFilePaths(this.options.files, this.options.rootDir);
953
- //remove files currently loaded in the program, we will transpile those instead (even if just for source maps)
954
- let filteredFileMap = [];
955
- for (let fileEntry of fileMap) {
956
- if (this.hasFile(fileEntry.src) === false) {
957
- filteredFileMap.push(fileEntry);
998
+ const file = this.getFile(filePath);
999
+ return this.getTranspiledFileContentsPipeline.run(async () => {
1000
+ const result = {
1001
+ destPath: file.destPath,
1002
+ pkgPath: file.pkgPath,
1003
+ srcPath: file.srcPath
1004
+ };
1005
+ const expectedPkgPath = file.pkgPath.toLowerCase();
1006
+ const expectedMapPath = `${expectedPkgPath}.map`;
1007
+ const expectedTypedefPkgPath = expectedPkgPath.replace(/\.brs$/i, '.d.bs');
1008
+ //add a temporary plugin to tap into the file writing process
1009
+ const plugin = this.plugins.addFirst({
1010
+ name: 'getTranspiledFileContents',
1011
+ beforeWriteFile: (event) => {
1012
+ const pkgPath = event.file.pkgPath.toLowerCase();
1013
+ switch (pkgPath) {
1014
+ //this is the actual transpiled file
1015
+ case expectedPkgPath:
1016
+ result.code = event.file.data.toString();
1017
+ break;
1018
+ //this is the sourcemap
1019
+ case expectedMapPath:
1020
+ result.map = event.file.data.toString();
1021
+ break;
1022
+ //this is the typedef
1023
+ case expectedTypedefPkgPath:
1024
+ result.typedef = event.file.data.toString();
1025
+ break;
1026
+ default:
1027
+ //no idea what this file is. just ignore it
1028
+ }
1029
+ //mark every file as processed so it they don't get written to the output directory
1030
+ event.processedFiles.add(event.file);
1031
+ }
1032
+ });
1033
+ try {
1034
+ //now that the plugin has been registered, run the build with just this file
1035
+ await this.build({
1036
+ files: [file]
1037
+ });
1038
+ }
1039
+ finally {
1040
+ this.plugins.remove(plugin);
958
1041
  }
1042
+ return result;
1043
+ });
1044
+ }
1045
+ /**
1046
+ * Get the absolute output path for a file
1047
+ */
1048
+ getOutputPath(file, stagingDir = this.getStagingDir()) {
1049
+ return (0, util_1.standardizePath) `${stagingDir}/${file.pkgPath}`;
1050
+ }
1051
+ getStagingDir(stagingDir) {
1052
+ var _a, _b;
1053
+ let result = (_a = stagingDir !== null && stagingDir !== void 0 ? stagingDir : this.options.stagingDir) !== null && _a !== void 0 ? _a : this.options.stagingDir;
1054
+ if (!result) {
1055
+ result = roku_deploy_1.rokuDeploy.getOptions(this.options).stagingDir;
959
1056
  }
960
- const { entries, astEditor } = this.beforeProgramTranspile(fileMap, this.options.stagingDir);
961
- const result = this._getTranspiledFileContents(this.getFile(filePath));
962
- this.afterProgramTranspile(entries, astEditor);
1057
+ result = (0, util_1.standardizePath) `${path.resolve((_b = this.options.cwd) !== null && _b !== void 0 ? _b : process.cwd(), result !== null && result !== void 0 ? result : '/')}`;
963
1058
  return result;
964
1059
  }
965
1060
  /**
966
- * Internal function used to transpile files.
967
- * This does not write anything to the file system
1061
+ * Prepare the program for building
1062
+ * @param files the list of files that should be prepared
968
1063
  */
969
- _getTranspiledFileContents(file, outputPath) {
970
- const editor = new AstEditor_1.AstEditor();
971
- this.plugins.emit('beforeFileTranspile', {
1064
+ async prepare(files) {
1065
+ const programEvent = {
972
1066
  program: this,
973
- file: file,
974
- outputPath: outputPath,
975
- editor: editor
976
- });
977
- //if we have any edits, assume the file needs to be transpiled
978
- if (editor.hasChanges) {
979
- //use the `editor` because it'll track the previous value for us and revert later on
980
- editor.setProperty(file, 'needsTranspiled', true);
981
- }
982
- //transpile the file
983
- const result = file.transpile();
984
- //generate the typedef if enabled
985
- let typedef;
986
- if ((0, reflection_1.isBrsFile)(file) && this.options.emitDefinitions) {
987
- typedef = file.getTypedef();
988
- }
989
- const event = {
990
- program: this,
991
- file: file,
992
- outputPath: outputPath,
993
- editor: editor,
994
- code: result.code,
995
- map: result.map,
996
- typedef: typedef
1067
+ editor: this.editor,
1068
+ files: files
997
1069
  };
998
- this.plugins.emit('afterFileTranspile', event);
999
- //undo all `editor` edits that may have been applied to this file.
1000
- editor.undoAll();
1001
- return {
1002
- srcPath: file.srcPath,
1003
- pkgPath: file.pkgPath,
1004
- code: event.code,
1005
- map: event.map,
1006
- typedef: event.typedef
1007
- };
1008
- }
1009
- beforeProgramTranspile(fileEntries, stagingDir) {
1010
- // map fileEntries using their path as key, to avoid excessive "find()" operations
1011
- const mappedFileEntries = fileEntries.reduce((collection, entry) => {
1012
- collection[(0, util_1.standardizePath) `${entry.src}`] = entry;
1013
- return collection;
1014
- }, {});
1015
- const getOutputPath = (file) => {
1016
- let filePathObj = mappedFileEntries[(0, util_1.standardizePath) `${file.srcPath}`];
1017
- if (!filePathObj) {
1018
- //this file has been added in-memory, from a plugin, for example
1019
- filePathObj = {
1020
- //add an interpolated src path (since it doesn't actually exist in memory)
1021
- src: `bsc:/${file.pkgPath}`,
1022
- dest: file.pkgPath
1023
- };
1070
+ //assign an editor to every file
1071
+ for (const file of files) {
1072
+ //if the file doesn't have an editor yet, assign one now
1073
+ if (!file.editor) {
1074
+ file.editor = new Editor_1.Editor();
1024
1075
  }
1025
- //replace the file extension
1026
- let outputPath = filePathObj.dest.replace(/\.bs$/gi, '.brs');
1027
- //prepend the staging folder path
1028
- outputPath = (0, util_1.standardizePath) `${stagingDir}/${outputPath}`;
1029
- return outputPath;
1030
- };
1031
- const entries = Object.values(this.files).map(file => {
1032
- return {
1076
+ }
1077
+ files.sort((a, b) => {
1078
+ if (a.pkgPath < b.pkgPath) {
1079
+ return -1;
1080
+ }
1081
+ else if (a.pkgPath > b.pkgPath) {
1082
+ return 1;
1083
+ }
1084
+ else {
1085
+ return 1;
1086
+ }
1087
+ });
1088
+ await this.plugins.emitAsync('beforePrepareProgram', programEvent);
1089
+ await this.plugins.emitAsync('prepareProgram', programEvent);
1090
+ const stagingDir = this.getStagingDir();
1091
+ const entries = [];
1092
+ for (const file of files) {
1093
+ //if the file doesn't have an editor yet, assign one now
1094
+ if (!file.editor) {
1095
+ file.editor = new Editor_1.Editor();
1096
+ }
1097
+ const event = {
1098
+ program: this,
1033
1099
  file: file,
1034
- outputPath: getOutputPath(file)
1100
+ editor: file.editor,
1101
+ outputPath: this.getOutputPath(file, stagingDir)
1035
1102
  };
1036
- //sort the entries to make transpiling more deterministic
1037
- }).sort((a, b) => {
1038
- return a.file.srcPath < b.file.srcPath ? -1 : 1;
1103
+ await this.plugins.emitAsync('beforePrepareFile', event);
1104
+ await this.plugins.emitAsync('prepareFile', event);
1105
+ await this.plugins.emitAsync('afterPrepareFile', event);
1106
+ //TODO remove this in v1
1107
+ entries.push(event);
1108
+ }
1109
+ await this.plugins.emitAsync('afterPrepareProgram', programEvent);
1110
+ return files;
1111
+ }
1112
+ /**
1113
+ * Generate the contents of every file
1114
+ */
1115
+ async serialize(files) {
1116
+ const allFiles = new Map();
1117
+ const serializeProgramEvent = await this.plugins.emitAsync('beforeSerializeProgram', {
1118
+ program: this,
1119
+ files: files,
1120
+ result: allFiles
1039
1121
  });
1040
- const astEditor = new AstEditor_1.AstEditor();
1041
- this.plugins.emit('beforeProgramTranspile', {
1122
+ await this.plugins.emitAsync('onSerializeProgram', {
1042
1123
  program: this,
1043
- entries: entries,
1044
- editor: astEditor
1124
+ files: files,
1125
+ result: allFiles
1045
1126
  });
1046
- return {
1047
- entries: entries,
1048
- getOutputPath: getOutputPath,
1049
- astEditor: astEditor
1050
- };
1051
- }
1052
- async transpile(fileEntries, stagingDir) {
1053
- const { entries, getOutputPath, astEditor } = this.beforeProgramTranspile(fileEntries, stagingDir);
1054
- const processedFiles = new Set();
1055
- const transpileFile = async (srcPath, outputPath) => {
1056
- //find the file in the program
1057
- const file = this.getFile(srcPath);
1058
- //mark this file as processed so we don't process it more than once
1059
- processedFiles.add(outputPath === null || outputPath === void 0 ? void 0 : outputPath.toLowerCase());
1060
- //skip transpiling typedef files
1061
- if ((0, reflection_1.isBrsFile)(file) && file.isTypedef) {
1062
- return;
1063
- }
1064
- const fileTranspileResult = this._getTranspiledFileContents(file, outputPath);
1065
- //make sure the full dir path exists
1066
- await fsExtra.ensureDir(path.dirname(outputPath));
1067
- if (await fsExtra.pathExists(outputPath)) {
1068
- throw new Error(`Error while transpiling "${file.srcPath}". A file already exists at "${outputPath}" and will not be overwritten.`);
1069
- }
1070
- const writeMapPromise = fileTranspileResult.map ? fsExtra.writeFile(`${outputPath}.map`, fileTranspileResult.map.toString()) : null;
1071
- await Promise.all([
1072
- fsExtra.writeFile(outputPath, fileTranspileResult.code),
1073
- writeMapPromise
1074
- ]);
1075
- if (fileTranspileResult.typedef) {
1076
- const typedefPath = outputPath.replace(/\.brs$/i, '.d.bs');
1077
- await fsExtra.writeFile(typedefPath, fileTranspileResult.typedef);
1078
- }
1079
- };
1080
- let promises = entries.map(async (entry) => {
1081
- var _a;
1082
- return transpileFile((_a = entry === null || entry === void 0 ? void 0 : entry.file) === null || _a === void 0 ? void 0 : _a.srcPath, entry.outputPath);
1127
+ //sort the entries to make transpiling more deterministic
1128
+ files = serializeProgramEvent.files.sort((a, b) => {
1129
+ return a.srcPath < b.srcPath ? -1 : 1;
1083
1130
  });
1084
- //if there's no bslib file already loaded into the program, copy it to the staging directory
1085
- if (!this.getFile(bslibAliasedRokuModulesPkgPath) && !this.getFile((0, util_1.standardizePath) `source/bslib.brs`)) {
1086
- promises.push(util_1.util.copyBslibToStaging(stagingDir));
1131
+ // serialize each file
1132
+ for (const file of files) {
1133
+ const event = {
1134
+ program: this,
1135
+ file: file,
1136
+ result: allFiles
1137
+ };
1138
+ await this.plugins.emitAsync('beforeSerializeFile', event);
1139
+ await this.plugins.emitAsync('serializeFile', event);
1140
+ await this.plugins.emitAsync('afterSerializeFile', event);
1087
1141
  }
1088
- await Promise.all(promises);
1089
- //transpile any new files that plugins added since the start of this transpile process
1090
- do {
1091
- promises = [];
1092
- for (const key in this.files) {
1093
- const file = this.files[key];
1094
- //this is a new file
1095
- const outputPath = getOutputPath(file);
1096
- if (!processedFiles.has(outputPath === null || outputPath === void 0 ? void 0 : outputPath.toLowerCase())) {
1097
- promises.push(transpileFile(file === null || file === void 0 ? void 0 : file.srcPath, outputPath));
1098
- }
1099
- }
1100
- if (promises.length > 0) {
1101
- this.logger.info(`Transpiling ${promises.length} new files`);
1102
- await Promise.all(promises);
1103
- }
1104
- } while (promises.length > 0);
1105
- this.afterProgramTranspile(entries, astEditor);
1142
+ this.plugins.emit('afterSerializeProgram', {
1143
+ program: this,
1144
+ files: files,
1145
+ result: allFiles
1146
+ });
1147
+ return allFiles;
1106
1148
  }
1107
- afterProgramTranspile(entries, astEditor) {
1108
- this.plugins.emit('afterProgramTranspile', {
1149
+ /**
1150
+ * Write the entire project to disk
1151
+ */
1152
+ async write(stagingDir, files) {
1153
+ const programEvent = await this.plugins.emitAsync('beforeWriteProgram', {
1109
1154
  program: this,
1110
- entries: entries,
1111
- editor: astEditor
1155
+ files: files,
1156
+ stagingDir: stagingDir
1157
+ });
1158
+ //empty the staging directory
1159
+ await fsExtra.emptyDir(stagingDir);
1160
+ const serializedFiles = [...files]
1161
+ .map(([, serializedFiles]) => serializedFiles)
1162
+ .flat();
1163
+ //write all the files to disk (asynchronously)
1164
+ await Promise.all(serializedFiles.map(async (file) => {
1165
+ const event = await this.plugins.emitAsync('beforeWriteFile', {
1166
+ program: this,
1167
+ file: file,
1168
+ outputPath: this.getOutputPath(file, stagingDir),
1169
+ processedFiles: new Set()
1170
+ });
1171
+ await this.plugins.emitAsync('writeFile', event);
1172
+ await this.plugins.emitAsync('afterWriteFile', event);
1173
+ }));
1174
+ await this.plugins.emitAsync('afterWriteProgram', programEvent);
1175
+ }
1176
+ /**
1177
+ * Build the project. This transpiles/transforms/copies all files and moves them to the staging directory
1178
+ * @param options the list of options used to build the program
1179
+ */
1180
+ async build(options) {
1181
+ //run a single build at a time
1182
+ await this.buildPipeline.run(async () => {
1183
+ var _a;
1184
+ const stagingDir = this.getStagingDir(options === null || options === void 0 ? void 0 : options.stagingDir);
1185
+ const event = await this.plugins.emitAsync('beforeBuildProgram', {
1186
+ program: this,
1187
+ editor: this.editor,
1188
+ files: (_a = options === null || options === void 0 ? void 0 : options.files) !== null && _a !== void 0 ? _a : Object.values(this.files)
1189
+ });
1190
+ //prepare the program (and files) for building
1191
+ event.files = await this.prepare(event.files);
1192
+ //stage the entire program
1193
+ const serializedFilesByFile = await this.serialize(event.files);
1194
+ await this.write(stagingDir, serializedFilesByFile);
1195
+ await this.plugins.emitAsync('afterBuildProgram', event);
1196
+ //undo all edits for the program
1197
+ this.editor.undoAll();
1198
+ //undo all edits for each file
1199
+ for (const file of event.files) {
1200
+ file.editor.undoAll();
1201
+ }
1112
1202
  });
1113
- astEditor.undoAll();
1114
1203
  }
1115
1204
  /**
1116
1205
  * Find a list of files in the program that have a function with the given name (case INsensitive)
@@ -1185,6 +1274,7 @@ class Program {
1185
1274
  * Get a map of the manifest information
1186
1275
  */
1187
1276
  getManifest() {
1277
+ var _a, _b;
1188
1278
  if (!this._manifest) {
1189
1279
  //load the manifest file.
1190
1280
  //TODO update this to get the manifest from the files array or require it in the options...we shouldn't assume the location of the manifest
@@ -1193,7 +1283,27 @@ class Program {
1193
1283
  try {
1194
1284
  //we only load this manifest once, so do it sync to improve speed downstream
1195
1285
  contents = fsExtra.readFileSync(manifestPath, 'utf-8');
1196
- this._manifest = (0, Manifest_1.parseManifest)(contents);
1286
+ let parsedManifest = (0, Manifest_1.parseManifest)(contents);
1287
+ // Lift the bs_consts defined in the manifest
1288
+ let bsConsts = (0, Manifest_1.getBsConst)(parsedManifest, false);
1289
+ // Override or delete any bs_consts defined in the bs config
1290
+ for (const key in (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.manifest) === null || _b === void 0 ? void 0 : _b.bs_const) {
1291
+ const value = this.options.manifest.bs_const[key];
1292
+ if (value === null) {
1293
+ bsConsts.delete(key);
1294
+ }
1295
+ else {
1296
+ bsConsts.set(key, value);
1297
+ }
1298
+ }
1299
+ // convert the new list of bs consts back into a string for the rest of the down stream systems to use
1300
+ let constString = '';
1301
+ for (const [key, value] of bsConsts) {
1302
+ constString += `${constString !== '' ? ';' : ''}${key}=${value.toString()}`;
1303
+ }
1304
+ // Set the updated bs_const value
1305
+ parsedManifest.set('bs_const', constString);
1306
+ this._manifest = parsedManifest;
1197
1307
  }
1198
1308
  catch (err) {
1199
1309
  this._manifest = new Map();
@@ -1202,16 +1312,29 @@ class Program {
1202
1312
  return this._manifest;
1203
1313
  }
1204
1314
  dispose() {
1315
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1205
1316
  this.plugins.emit('beforeProgramDispose', { program: this });
1206
1317
  for (let filePath in this.files) {
1207
- this.files[filePath].dispose();
1318
+ (_b = (_a = this.files[filePath]) === null || _a === void 0 ? void 0 : _a.dispose) === null || _b === void 0 ? void 0 : _b.call(_a);
1208
1319
  }
1209
1320
  for (let name in this.scopes) {
1210
- this.scopes[name].dispose();
1321
+ (_d = (_c = this.scopes[name]) === null || _c === void 0 ? void 0 : _c.dispose) === null || _d === void 0 ? void 0 : _d.call(_c);
1211
1322
  }
1212
- this.globalScope.dispose();
1213
- this.dependencyGraph.dispose();
1323
+ (_f = (_e = this.globalScope) === null || _e === void 0 ? void 0 : _e.dispose) === null || _f === void 0 ? void 0 : _f.call(_e);
1324
+ (_h = (_g = this.dependencyGraph) === null || _g === void 0 ? void 0 : _g.dispose) === null || _h === void 0 ? void 0 : _h.call(_g);
1214
1325
  }
1215
1326
  }
1216
1327
  exports.Program = Program;
1328
+ class ProvideFileEventInternal {
1329
+ constructor(program, srcPath, destPath, data, fileFactory) {
1330
+ var _a;
1331
+ this.program = program;
1332
+ this.srcPath = srcPath;
1333
+ this.destPath = destPath;
1334
+ this.data = data;
1335
+ this.fileFactory = fileFactory;
1336
+ this.files = [];
1337
+ this.srcExtension = (_a = path.extname(srcPath)) === null || _a === void 0 ? void 0 : _a.toLowerCase();
1338
+ }
1339
+ }
1217
1340
  //# sourceMappingURL=Program.js.map