brighterscript 1.0.0-alpha.24 → 1.0.0-alpha.25

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 (530) hide show
  1. package/CHANGELOG.md +493 -233
  2. package/README.md +45 -139
  3. package/bsconfig.schema.json +41 -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/AstValidationSegmenter.d.ts +25 -0
  8. package/dist/AstValidationSegmenter.js +152 -0
  9. package/dist/AstValidationSegmenter.js.map +1 -0
  10. package/dist/BsConfig.d.ts +39 -4
  11. package/dist/BusyStatusTracker.d.ts +31 -0
  12. package/dist/BusyStatusTracker.js +83 -0
  13. package/dist/BusyStatusTracker.js.map +1 -0
  14. package/dist/Cache.js +3 -3
  15. package/dist/Cache.js.map +1 -1
  16. package/dist/CacheVerifier.d.ts +7 -0
  17. package/dist/CacheVerifier.js +20 -0
  18. package/dist/CacheVerifier.js.map +1 -0
  19. package/dist/CodeActionUtil.d.ts +3 -3
  20. package/dist/CodeActionUtil.js.map +1 -1
  21. package/dist/CommentFlagProcessor.d.ts +3 -2
  22. package/dist/CommentFlagProcessor.js +5 -4
  23. package/dist/CommentFlagProcessor.js.map +1 -1
  24. package/dist/DependencyGraph.d.ts +3 -2
  25. package/dist/DependencyGraph.js +11 -10
  26. package/dist/DependencyGraph.js.map +1 -1
  27. package/dist/DiagnosticCollection.js +9 -5
  28. package/dist/DiagnosticCollection.js.map +1 -1
  29. package/dist/DiagnosticFilterer.d.ts +1 -0
  30. package/dist/DiagnosticFilterer.js +5 -3
  31. package/dist/DiagnosticFilterer.js.map +1 -1
  32. package/dist/DiagnosticMessages.d.ts +61 -13
  33. package/dist/DiagnosticMessages.js +116 -19
  34. package/dist/DiagnosticMessages.js.map +1 -1
  35. package/dist/DiagnosticSeverityAdjuster.d.ts +7 -0
  36. package/dist/DiagnosticSeverityAdjuster.js +41 -0
  37. package/dist/DiagnosticSeverityAdjuster.js.map +1 -0
  38. package/dist/FunctionScope.d.ts +28 -0
  39. package/dist/FunctionScope.js +52 -0
  40. package/dist/FunctionScope.js.map +1 -0
  41. package/dist/KeyedThrottler.d.ts +3 -3
  42. package/dist/KeyedThrottler.js +3 -3
  43. package/dist/KeyedThrottler.js.map +1 -1
  44. package/dist/LanguageServer.d.ts +23 -11
  45. package/dist/LanguageServer.js +150 -69
  46. package/dist/LanguageServer.js.map +1 -1
  47. package/dist/Logger.d.ts +3 -2
  48. package/dist/Logger.js +11 -3
  49. package/dist/Logger.js.map +1 -1
  50. package/dist/PluginInterface.d.ts +21 -3
  51. package/dist/PluginInterface.js +74 -6
  52. package/dist/PluginInterface.js.map +1 -1
  53. package/dist/Program.d.ts +158 -79
  54. package/dist/Program.js +831 -695
  55. package/dist/Program.js.map +1 -1
  56. package/dist/ProgramBuilder.d.ts +22 -12
  57. package/dist/ProgramBuilder.js +130 -103
  58. package/dist/ProgramBuilder.js.map +1 -1
  59. package/dist/Scope.d.ts +87 -133
  60. package/dist/Scope.js +450 -510
  61. package/dist/Scope.js.map +1 -1
  62. package/dist/Stopwatch.js +1 -1
  63. package/dist/Stopwatch.js.map +1 -1
  64. package/dist/SymbolTable.d.ts +89 -34
  65. package/dist/SymbolTable.js +239 -114
  66. package/dist/SymbolTable.js.map +1 -1
  67. package/dist/Throttler.d.ts +12 -0
  68. package/dist/Throttler.js +39 -0
  69. package/dist/Throttler.js.map +1 -1
  70. package/dist/Watcher.d.ts +0 -3
  71. package/dist/Watcher.js +0 -3
  72. package/dist/Watcher.js.map +1 -1
  73. package/dist/XmlScope.d.ts +4 -6
  74. package/dist/XmlScope.js +74 -69
  75. package/dist/XmlScope.js.map +1 -1
  76. package/dist/astUtils/CachedLookups.d.ts +48 -0
  77. package/dist/astUtils/CachedLookups.js +323 -0
  78. package/dist/astUtils/CachedLookups.js.map +1 -0
  79. package/dist/astUtils/{AstEditor.d.ts → Editor.d.ts} +9 -5
  80. package/dist/astUtils/{AstEditor.js → Editor.js} +10 -4
  81. package/dist/astUtils/Editor.js.map +1 -0
  82. package/dist/astUtils/{AstEditor.spec.js → Editor.spec.js} +68 -64
  83. package/dist/astUtils/Editor.spec.js.map +1 -0
  84. package/dist/astUtils/creators.d.ts +10 -10
  85. package/dist/astUtils/creators.js +26 -16
  86. package/dist/astUtils/creators.js.map +1 -1
  87. package/dist/astUtils/creators.spec.js +5 -5
  88. package/dist/astUtils/creators.spec.js.map +1 -1
  89. package/dist/astUtils/reflection.d.ts +132 -104
  90. package/dist/astUtils/reflection.js +220 -174
  91. package/dist/astUtils/reflection.js.map +1 -1
  92. package/dist/astUtils/reflection.spec.js +208 -126
  93. package/dist/astUtils/reflection.spec.js.map +1 -1
  94. package/dist/astUtils/stackedVisitor.spec.js +12 -12
  95. package/dist/astUtils/stackedVisitor.spec.js.map +1 -1
  96. package/dist/astUtils/visitors.d.ts +53 -35
  97. package/dist/astUtils/visitors.js +29 -3
  98. package/dist/astUtils/visitors.js.map +1 -1
  99. package/dist/astUtils/visitors.spec.js +178 -33
  100. package/dist/astUtils/visitors.spec.js.map +1 -1
  101. package/dist/astUtils/xml.d.ts +9 -9
  102. package/dist/astUtils/xml.js +9 -9
  103. package/dist/astUtils/xml.js.map +1 -1
  104. package/dist/bscPlugin/BscPlugin.d.ts +10 -2
  105. package/dist/bscPlugin/BscPlugin.js +33 -3
  106. package/dist/bscPlugin/BscPlugin.js.map +1 -1
  107. package/dist/bscPlugin/CallExpressionInfo.d.ts +36 -0
  108. package/dist/bscPlugin/CallExpressionInfo.js +131 -0
  109. package/dist/bscPlugin/CallExpressionInfo.js.map +1 -0
  110. package/dist/bscPlugin/FileWriter.d.ts +6 -0
  111. package/dist/bscPlugin/FileWriter.js +24 -0
  112. package/dist/bscPlugin/FileWriter.js.map +1 -0
  113. package/dist/bscPlugin/SignatureHelpUtil.d.ts +10 -0
  114. package/dist/bscPlugin/SignatureHelpUtil.js +135 -0
  115. package/dist/bscPlugin/SignatureHelpUtil.js.map +1 -0
  116. package/dist/bscPlugin/codeActions/CodeActionsProcessor.js +14 -11
  117. package/dist/bscPlugin/codeActions/CodeActionsProcessor.js.map +1 -1
  118. package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js +16 -16
  119. package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js.map +1 -1
  120. package/dist/bscPlugin/completions/CompletionsProcessor.d.ts +52 -1
  121. package/dist/bscPlugin/completions/CompletionsProcessor.js +517 -26
  122. package/dist/bscPlugin/completions/CompletionsProcessor.js.map +1 -1
  123. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js +1909 -0
  124. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js.map +1 -0
  125. package/dist/bscPlugin/fileProviders/FileProvider.d.ts +9 -0
  126. package/dist/bscPlugin/fileProviders/FileProvider.js +51 -0
  127. package/dist/bscPlugin/fileProviders/FileProvider.js.map +1 -0
  128. package/dist/bscPlugin/hover/HoverProcessor.d.ts +7 -7
  129. package/dist/bscPlugin/hover/HoverProcessor.js +123 -125
  130. package/dist/bscPlugin/hover/HoverProcessor.js.map +1 -1
  131. package/dist/bscPlugin/hover/HoverProcessor.spec.js +371 -53
  132. package/dist/bscPlugin/hover/HoverProcessor.spec.js.map +1 -1
  133. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.d.ts +2 -1
  134. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js +85 -23
  135. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js.map +1 -1
  136. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js +83 -6
  137. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js.map +1 -1
  138. package/dist/bscPlugin/serialize/BslibInjector.spec.js +19 -0
  139. package/dist/bscPlugin/serialize/BslibInjector.spec.js.map +1 -0
  140. package/dist/bscPlugin/serialize/BslibManager.d.ts +9 -0
  141. package/dist/bscPlugin/serialize/BslibManager.js +40 -0
  142. package/dist/bscPlugin/serialize/BslibManager.js.map +1 -0
  143. package/dist/bscPlugin/serialize/FileSerializer.d.ts +9 -0
  144. package/dist/bscPlugin/serialize/FileSerializer.js +72 -0
  145. package/dist/bscPlugin/serialize/FileSerializer.js.map +1 -0
  146. package/dist/bscPlugin/transpile/{BrsFilePreTranspileProcessor.d.ts → BrsFileTranspileProcessor.d.ts} +4 -2
  147. package/dist/bscPlugin/transpile/{BrsFilePreTranspileProcessor.js → BrsFileTranspileProcessor.js} +33 -9
  148. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.js.map +1 -0
  149. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.d.ts +1 -0
  150. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.js +41 -0
  151. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.js.map +1 -0
  152. package/dist/bscPlugin/transpile/XmlFilePreTranspileProcessor.d.ts +12 -0
  153. package/dist/bscPlugin/transpile/XmlFilePreTranspileProcessor.js +99 -0
  154. package/dist/bscPlugin/transpile/XmlFilePreTranspileProcessor.js.map +1 -0
  155. package/dist/bscPlugin/validation/BrsFileValidator.d.ts +13 -5
  156. package/dist/bscPlugin/validation/BrsFileValidator.js +259 -49
  157. package/dist/bscPlugin/validation/BrsFileValidator.js.map +1 -1
  158. package/dist/bscPlugin/validation/BrsFileValidator.spec.js +230 -14
  159. package/dist/bscPlugin/validation/BrsFileValidator.spec.js.map +1 -1
  160. package/dist/bscPlugin/validation/ProgramValidator.d.ts +10 -0
  161. package/dist/bscPlugin/validation/ProgramValidator.js +32 -0
  162. package/dist/bscPlugin/validation/ProgramValidator.js.map +1 -0
  163. package/dist/bscPlugin/validation/ScopeValidator.d.ts +54 -27
  164. package/dist/bscPlugin/validation/ScopeValidator.js +483 -286
  165. package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
  166. package/dist/bscPlugin/validation/ScopeValidator.spec.d.ts +1 -0
  167. package/dist/bscPlugin/validation/ScopeValidator.spec.js +2454 -0
  168. package/dist/bscPlugin/validation/ScopeValidator.spec.js.map +1 -0
  169. package/dist/bscPlugin/validation/XmlFileValidator.d.ts +8 -0
  170. package/dist/bscPlugin/validation/XmlFileValidator.js +44 -0
  171. package/dist/bscPlugin/validation/XmlFileValidator.js.map +1 -0
  172. package/dist/cli.js +104 -13
  173. package/dist/cli.js.map +1 -1
  174. package/dist/deferred.d.ts +3 -3
  175. package/dist/deferred.js.map +1 -1
  176. package/dist/diagnosticUtils.d.ts +8 -2
  177. package/dist/diagnosticUtils.js +47 -17
  178. package/dist/diagnosticUtils.js.map +1 -1
  179. package/dist/examples/plugins/removePrint.js +8 -10
  180. package/dist/examples/plugins/removePrint.js.map +1 -1
  181. package/dist/files/AssetFile.d.ts +26 -0
  182. package/dist/files/AssetFile.js +26 -0
  183. package/dist/files/AssetFile.js.map +1 -0
  184. package/dist/files/BrsFile.Class.spec.js +523 -493
  185. package/dist/files/BrsFile.Class.spec.js.map +1 -1
  186. package/dist/files/BrsFile.d.ts +112 -111
  187. package/dist/files/BrsFile.js +741 -1032
  188. package/dist/files/BrsFile.js.map +1 -1
  189. package/dist/files/BrsFile.spec.js +1728 -1232
  190. package/dist/files/BrsFile.spec.js.map +1 -1
  191. package/dist/files/BscFile.d.ts +104 -0
  192. package/dist/files/BscFile.js +16 -0
  193. package/dist/files/BscFile.js.map +1 -0
  194. package/dist/files/Factory.d.ts +25 -0
  195. package/dist/files/Factory.js +22 -0
  196. package/dist/files/Factory.js.map +1 -0
  197. package/dist/files/LazyFileData.d.ts +20 -0
  198. package/dist/files/LazyFileData.js +54 -0
  199. package/dist/files/LazyFileData.js.map +1 -0
  200. package/dist/files/LazyFileData.spec.d.ts +1 -0
  201. package/dist/files/LazyFileData.spec.js +27 -0
  202. package/dist/files/LazyFileData.spec.js.map +1 -0
  203. package/dist/files/XmlFile.d.ts +70 -32
  204. package/dist/files/XmlFile.js +106 -118
  205. package/dist/files/XmlFile.js.map +1 -1
  206. package/dist/files/XmlFile.spec.js +325 -262
  207. package/dist/files/XmlFile.spec.js.map +1 -1
  208. package/dist/files/tests/imports.spec.js +48 -40
  209. package/dist/files/tests/imports.spec.js.map +1 -1
  210. package/dist/files/tests/optionalChaning.spec.js +84 -24
  211. package/dist/files/tests/optionalChaning.spec.js.map +1 -1
  212. package/dist/globalCallables.js +16 -21
  213. package/dist/globalCallables.js.map +1 -1
  214. package/dist/index.d.ts +12 -1
  215. package/dist/index.js +12 -1
  216. package/dist/index.js.map +1 -1
  217. package/dist/interfaces.d.ts +389 -161
  218. package/dist/interfaces.js +27 -0
  219. package/dist/interfaces.js.map +1 -1
  220. package/dist/lexer/Character.spec.js +5 -5
  221. package/dist/lexer/Character.spec.js.map +1 -1
  222. package/dist/lexer/Lexer.d.ts +12 -5
  223. package/dist/lexer/Lexer.js +28 -13
  224. package/dist/lexer/Lexer.js.map +1 -1
  225. package/dist/lexer/Lexer.spec.js +181 -135
  226. package/dist/lexer/Lexer.spec.js.map +1 -1
  227. package/dist/lexer/Token.d.ts +9 -1
  228. package/dist/lexer/Token.js +9 -1
  229. package/dist/lexer/Token.js.map +1 -1
  230. package/dist/lexer/TokenKind.d.ts +8 -0
  231. package/dist/lexer/TokenKind.js +24 -4
  232. package/dist/lexer/TokenKind.js.map +1 -1
  233. package/dist/parser/AstNode.d.ts +162 -0
  234. package/dist/parser/AstNode.js +225 -0
  235. package/dist/parser/AstNode.js.map +1 -0
  236. package/dist/parser/AstNode.spec.d.ts +1 -0
  237. package/dist/parser/AstNode.spec.js +165 -0
  238. package/dist/parser/AstNode.spec.js.map +1 -0
  239. package/dist/parser/BrsTranspileState.d.ts +4 -7
  240. package/dist/parser/BrsTranspileState.js +4 -12
  241. package/dist/parser/BrsTranspileState.js.map +1 -1
  242. package/dist/parser/Expression.d.ts +126 -176
  243. package/dist/parser/Expression.js +523 -405
  244. package/dist/parser/Expression.js.map +1 -1
  245. package/dist/parser/Parser.Class.spec.js +151 -145
  246. package/dist/parser/Parser.Class.spec.js.map +1 -1
  247. package/dist/parser/Parser.d.ts +43 -201
  248. package/dist/parser/Parser.js +446 -962
  249. package/dist/parser/Parser.js.map +1 -1
  250. package/dist/parser/Parser.spec.d.ts +3 -1
  251. package/dist/parser/Parser.spec.js +1002 -846
  252. package/dist/parser/Parser.spec.js.map +1 -1
  253. package/dist/parser/SGParser.d.ts +9 -8
  254. package/dist/parser/SGParser.js +10 -8
  255. package/dist/parser/SGParser.js.map +1 -1
  256. package/dist/parser/SGParser.spec.js +27 -38
  257. package/dist/parser/SGParser.spec.js.map +1 -1
  258. package/dist/parser/SGTypes.d.ts +98 -35
  259. package/dist/parser/SGTypes.js +169 -99
  260. package/dist/parser/SGTypes.js.map +1 -1
  261. package/dist/parser/Statement.d.ts +183 -131
  262. package/dist/parser/Statement.js +549 -387
  263. package/dist/parser/Statement.js.map +1 -1
  264. package/dist/parser/Statement.spec.js +45 -21
  265. package/dist/parser/Statement.spec.js.map +1 -1
  266. package/dist/parser/TranspileState.d.ts +1 -1
  267. package/dist/parser/TranspileState.js +7 -12
  268. package/dist/parser/TranspileState.js.map +1 -1
  269. package/dist/parser/tests/Parser.spec.js +3 -2
  270. package/dist/parser/tests/Parser.spec.js.map +1 -1
  271. package/dist/parser/tests/controlFlow/For.spec.js +33 -23
  272. package/dist/parser/tests/controlFlow/For.spec.js.map +1 -1
  273. package/dist/parser/tests/controlFlow/ForEach.spec.js +25 -20
  274. package/dist/parser/tests/controlFlow/ForEach.spec.js.map +1 -1
  275. package/dist/parser/tests/controlFlow/If.spec.js +96 -94
  276. package/dist/parser/tests/controlFlow/If.spec.js.map +1 -1
  277. package/dist/parser/tests/controlFlow/While.spec.js +22 -16
  278. package/dist/parser/tests/controlFlow/While.spec.js.map +1 -1
  279. package/dist/parser/tests/expression/Additive.spec.js +8 -8
  280. package/dist/parser/tests/expression/Additive.spec.js.map +1 -1
  281. package/dist/parser/tests/expression/ArrayLiterals.spec.js +58 -21
  282. package/dist/parser/tests/expression/ArrayLiterals.spec.js.map +1 -1
  283. package/dist/parser/tests/expression/AssociativeArrayLiterals.spec.js +61 -20
  284. package/dist/parser/tests/expression/AssociativeArrayLiterals.spec.js.map +1 -1
  285. package/dist/parser/tests/expression/Boolean.spec.js +8 -8
  286. package/dist/parser/tests/expression/Boolean.spec.js.map +1 -1
  287. package/dist/parser/tests/expression/Call.spec.js +129 -21
  288. package/dist/parser/tests/expression/Call.spec.js.map +1 -1
  289. package/dist/parser/tests/expression/Exponential.spec.js +5 -5
  290. package/dist/parser/tests/expression/Exponential.spec.js.map +1 -1
  291. package/dist/parser/tests/expression/Function.spec.js +36 -36
  292. package/dist/parser/tests/expression/Function.spec.js.map +1 -1
  293. package/dist/parser/tests/expression/Indexing.spec.js +67 -22
  294. package/dist/parser/tests/expression/Indexing.spec.js.map +1 -1
  295. package/dist/parser/tests/expression/Multiplicative.spec.js +9 -9
  296. package/dist/parser/tests/expression/Multiplicative.spec.js.map +1 -1
  297. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js +59 -59
  298. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js.map +1 -1
  299. package/dist/parser/tests/expression/PrefixUnary.spec.js +12 -12
  300. package/dist/parser/tests/expression/PrefixUnary.spec.js.map +1 -1
  301. package/dist/parser/tests/expression/Primary.spec.js +12 -12
  302. package/dist/parser/tests/expression/Primary.spec.js.map +1 -1
  303. package/dist/parser/tests/expression/RegexLiteralExpression.spec.js +10 -10
  304. package/dist/parser/tests/expression/RegexLiteralExpression.spec.js.map +1 -1
  305. package/dist/parser/tests/expression/Relational.spec.js +13 -13
  306. package/dist/parser/tests/expression/Relational.spec.js.map +1 -1
  307. package/dist/parser/tests/expression/SourceLiteralExpression.spec.js +24 -24
  308. package/dist/parser/tests/expression/SourceLiteralExpression.spec.js.map +1 -1
  309. package/dist/parser/tests/expression/TemplateStringExpression.spec.js +96 -57
  310. package/dist/parser/tests/expression/TemplateStringExpression.spec.js.map +1 -1
  311. package/dist/parser/tests/expression/TernaryExpression.spec.js +89 -89
  312. package/dist/parser/tests/expression/TernaryExpression.spec.js.map +1 -1
  313. package/dist/parser/tests/expression/TypeExpression.spec.d.ts +1 -0
  314. package/dist/parser/tests/expression/TypeExpression.spec.js +127 -0
  315. package/dist/parser/tests/expression/TypeExpression.spec.js.map +1 -0
  316. package/dist/parser/tests/expression/UnaryExpression.spec.d.ts +1 -0
  317. package/dist/parser/tests/expression/UnaryExpression.spec.js +52 -0
  318. package/dist/parser/tests/expression/UnaryExpression.spec.js.map +1 -0
  319. package/dist/parser/tests/statement/AssignmentOperators.spec.js +15 -15
  320. package/dist/parser/tests/statement/AssignmentOperators.spec.js.map +1 -1
  321. package/dist/parser/tests/statement/ConstStatement.spec.js +82 -33
  322. package/dist/parser/tests/statement/ConstStatement.spec.js.map +1 -1
  323. package/dist/parser/tests/statement/Continue.spec.d.ts +1 -0
  324. package/dist/parser/tests/statement/Continue.spec.js +119 -0
  325. package/dist/parser/tests/statement/Continue.spec.js.map +1 -0
  326. package/dist/parser/tests/statement/Declaration.spec.js +19 -19
  327. package/dist/parser/tests/statement/Declaration.spec.js.map +1 -1
  328. package/dist/parser/tests/statement/Dim.spec.js +22 -22
  329. package/dist/parser/tests/statement/Dim.spec.js.map +1 -1
  330. package/dist/parser/tests/statement/Enum.spec.js +98 -302
  331. package/dist/parser/tests/statement/Enum.spec.js.map +1 -1
  332. package/dist/parser/tests/statement/For.spec.js +9 -10
  333. package/dist/parser/tests/statement/For.spec.js.map +1 -1
  334. package/dist/parser/tests/statement/ForEach.spec.js +8 -9
  335. package/dist/parser/tests/statement/ForEach.spec.js.map +1 -1
  336. package/dist/parser/tests/statement/Function.spec.js +44 -35
  337. package/dist/parser/tests/statement/Function.spec.js.map +1 -1
  338. package/dist/parser/tests/statement/Goto.spec.js +5 -5
  339. package/dist/parser/tests/statement/Goto.spec.js.map +1 -1
  340. package/dist/parser/tests/statement/Increment.spec.js +20 -20
  341. package/dist/parser/tests/statement/Increment.spec.js.map +1 -1
  342. package/dist/parser/tests/statement/InterfaceStatement.spec.js +30 -196
  343. package/dist/parser/tests/statement/InterfaceStatement.spec.js.map +1 -1
  344. package/dist/parser/tests/statement/LibraryStatement.spec.js +11 -11
  345. package/dist/parser/tests/statement/LibraryStatement.spec.js.map +1 -1
  346. package/dist/parser/tests/statement/Misc.spec.js +16 -78
  347. package/dist/parser/tests/statement/Misc.spec.js.map +1 -1
  348. package/dist/parser/tests/statement/PrintStatement.spec.js +35 -33
  349. package/dist/parser/tests/statement/PrintStatement.spec.js.map +1 -1
  350. package/dist/parser/tests/statement/ReturnStatement.spec.js +14 -12
  351. package/dist/parser/tests/statement/ReturnStatement.spec.js.map +1 -1
  352. package/dist/parser/tests/statement/Set.spec.js +48 -35
  353. package/dist/parser/tests/statement/Set.spec.js.map +1 -1
  354. package/dist/parser/tests/statement/Stop.spec.js +6 -6
  355. package/dist/parser/tests/statement/Stop.spec.js.map +1 -1
  356. package/dist/parser/tests/statement/Throw.spec.js +6 -6
  357. package/dist/parser/tests/statement/Throw.spec.js.map +1 -1
  358. package/dist/parser/tests/statement/TryCatch.spec.js +18 -16
  359. package/dist/parser/tests/statement/TryCatch.spec.js.map +1 -1
  360. package/dist/preprocessor/Manifest.d.ts +1 -1
  361. package/dist/preprocessor/Manifest.js +2 -2
  362. package/dist/preprocessor/Manifest.js.map +1 -1
  363. package/dist/preprocessor/Manifest.spec.js +8 -8
  364. package/dist/preprocessor/Manifest.spec.js.map +1 -1
  365. package/dist/preprocessor/Preprocessor.d.ts +5 -6
  366. package/dist/preprocessor/Preprocessor.js +5 -5
  367. package/dist/preprocessor/Preprocessor.js.map +1 -1
  368. package/dist/preprocessor/Preprocessor.spec.js +25 -25
  369. package/dist/preprocessor/Preprocessor.spec.js.map +1 -1
  370. package/dist/preprocessor/PreprocessorParser.d.ts +1 -1
  371. package/dist/preprocessor/PreprocessorParser.js +7 -1
  372. package/dist/preprocessor/PreprocessorParser.js.map +1 -1
  373. package/dist/preprocessor/PreprocessorParser.spec.js +13 -13
  374. package/dist/preprocessor/PreprocessorParser.spec.js.map +1 -1
  375. package/dist/roku-types/data.json +5892 -10081
  376. package/dist/roku-types/index.d.ts +622 -1719
  377. package/dist/types/ArrayType.d.ts +10 -9
  378. package/dist/types/ArrayType.js +65 -60
  379. package/dist/types/ArrayType.js.map +1 -1
  380. package/dist/types/ArrayType.spec.js +36 -68
  381. package/dist/types/ArrayType.spec.js.map +1 -1
  382. package/dist/types/AssociativeArrayType.d.ts +11 -0
  383. package/dist/types/AssociativeArrayType.js +52 -0
  384. package/dist/types/AssociativeArrayType.js.map +1 -0
  385. package/dist/types/BaseFunctionType.d.ts +9 -0
  386. package/dist/types/BaseFunctionType.js +25 -0
  387. package/dist/types/BaseFunctionType.js.map +1 -0
  388. package/dist/types/BooleanType.d.ts +8 -5
  389. package/dist/types/BooleanType.js +14 -7
  390. package/dist/types/BooleanType.js.map +1 -1
  391. package/dist/types/BooleanType.spec.js +10 -6
  392. package/dist/types/BooleanType.spec.js.map +1 -1
  393. package/dist/types/BscType.d.ts +32 -21
  394. package/dist/types/BscType.js +118 -21
  395. package/dist/types/BscType.js.map +1 -1
  396. package/dist/types/BscTypeKind.d.ts +25 -0
  397. package/dist/types/BscTypeKind.js +30 -0
  398. package/dist/types/BscTypeKind.js.map +1 -0
  399. package/dist/types/BuiltInInterfaceAdder.d.ts +23 -0
  400. package/dist/types/BuiltInInterfaceAdder.js +164 -0
  401. package/dist/types/BuiltInInterfaceAdder.js.map +1 -0
  402. package/dist/types/BuiltInInterfaceAdder.spec.d.ts +1 -0
  403. package/dist/types/BuiltInInterfaceAdder.spec.js +116 -0
  404. package/dist/types/BuiltInInterfaceAdder.spec.js.map +1 -0
  405. package/dist/types/ClassType.d.ts +17 -0
  406. package/dist/types/ClassType.js +58 -0
  407. package/dist/types/ClassType.js.map +1 -0
  408. package/dist/types/ClassType.spec.d.ts +1 -0
  409. package/dist/types/ClassType.spec.js +77 -0
  410. package/dist/types/ClassType.spec.js.map +1 -0
  411. package/dist/types/ComponentType.d.ts +26 -0
  412. package/dist/types/ComponentType.js +83 -0
  413. package/dist/types/ComponentType.js.map +1 -0
  414. package/dist/types/DoubleType.d.ts +8 -5
  415. package/dist/types/DoubleType.js +18 -16
  416. package/dist/types/DoubleType.js.map +1 -1
  417. package/dist/types/DoubleType.spec.js +12 -6
  418. package/dist/types/DoubleType.spec.js.map +1 -1
  419. package/dist/types/DynamicType.d.ts +9 -5
  420. package/dist/types/DynamicType.js +15 -4
  421. package/dist/types/DynamicType.js.map +1 -1
  422. package/dist/types/DynamicType.spec.js +16 -5
  423. package/dist/types/DynamicType.spec.js.map +1 -1
  424. package/dist/types/EnumType.d.ts +30 -12
  425. package/dist/types/EnumType.js +43 -17
  426. package/dist/types/EnumType.js.map +1 -1
  427. package/dist/types/EnumType.spec.d.ts +1 -0
  428. package/dist/types/EnumType.spec.js +33 -0
  429. package/dist/types/EnumType.spec.js.map +1 -0
  430. package/dist/types/FloatType.d.ts +8 -5
  431. package/dist/types/FloatType.js +18 -16
  432. package/dist/types/FloatType.js.map +1 -1
  433. package/dist/types/FloatType.spec.js +4 -6
  434. package/dist/types/FloatType.spec.js.map +1 -1
  435. package/dist/types/FunctionType.d.ts +13 -8
  436. package/dist/types/FunctionType.js +30 -14
  437. package/dist/types/FunctionType.js.map +1 -1
  438. package/dist/types/InheritableType.d.ts +28 -0
  439. package/dist/types/InheritableType.js +152 -0
  440. package/dist/types/InheritableType.js.map +1 -0
  441. package/dist/types/IntegerType.d.ts +8 -5
  442. package/dist/types/IntegerType.js +18 -16
  443. package/dist/types/IntegerType.js.map +1 -1
  444. package/dist/types/IntegerType.spec.js +8 -6
  445. package/dist/types/IntegerType.spec.js.map +1 -1
  446. package/dist/types/InterfaceType.d.ts +12 -13
  447. package/dist/types/InterfaceType.js +20 -48
  448. package/dist/types/InterfaceType.js.map +1 -1
  449. package/dist/types/InterfaceType.spec.js +90 -56
  450. package/dist/types/InterfaceType.spec.js.map +1 -1
  451. package/dist/types/InvalidType.d.ts +7 -5
  452. package/dist/types/InvalidType.js +13 -7
  453. package/dist/types/InvalidType.js.map +1 -1
  454. package/dist/types/InvalidType.spec.js +8 -6
  455. package/dist/types/InvalidType.spec.js.map +1 -1
  456. package/dist/types/LongIntegerType.d.ts +8 -5
  457. package/dist/types/LongIntegerType.js +17 -15
  458. package/dist/types/LongIntegerType.js.map +1 -1
  459. package/dist/types/LongIntegerType.spec.js +10 -6
  460. package/dist/types/LongIntegerType.spec.js.map +1 -1
  461. package/dist/types/NamespaceType.d.ts +12 -0
  462. package/dist/types/NamespaceType.js +28 -0
  463. package/dist/types/NamespaceType.js.map +1 -0
  464. package/dist/types/ObjectType.d.ts +9 -8
  465. package/dist/types/ObjectType.js +21 -11
  466. package/dist/types/ObjectType.js.map +1 -1
  467. package/dist/types/ObjectType.spec.js +3 -3
  468. package/dist/types/ObjectType.spec.js.map +1 -1
  469. package/dist/types/ReferenceType.d.ts +63 -0
  470. package/dist/types/ReferenceType.js +423 -0
  471. package/dist/types/ReferenceType.js.map +1 -0
  472. package/dist/types/ReferenceType.spec.d.ts +1 -0
  473. package/dist/types/ReferenceType.spec.js +137 -0
  474. package/dist/types/ReferenceType.spec.js.map +1 -0
  475. package/dist/types/StringType.d.ts +11 -5
  476. package/dist/types/StringType.js +18 -7
  477. package/dist/types/StringType.js.map +1 -1
  478. package/dist/types/StringType.spec.js +3 -5
  479. package/dist/types/StringType.spec.js.map +1 -1
  480. package/dist/types/TypedFunctionType.d.ts +22 -17
  481. package/dist/types/TypedFunctionType.js +78 -60
  482. package/dist/types/TypedFunctionType.js.map +1 -1
  483. package/dist/types/TypedFunctionType.spec.js +105 -20
  484. package/dist/types/TypedFunctionType.spec.js.map +1 -1
  485. package/dist/types/UninitializedType.d.ts +8 -6
  486. package/dist/types/UninitializedType.js +13 -7
  487. package/dist/types/UninitializedType.js.map +1 -1
  488. package/dist/types/UnionType.d.ts +20 -0
  489. package/dist/types/UnionType.js +123 -0
  490. package/dist/types/UnionType.js.map +1 -0
  491. package/dist/types/UnionType.spec.d.ts +1 -0
  492. package/dist/types/UnionType.spec.js +130 -0
  493. package/dist/types/UnionType.spec.js.map +1 -0
  494. package/dist/types/VoidType.d.ts +8 -5
  495. package/dist/types/VoidType.js +14 -7
  496. package/dist/types/VoidType.js.map +1 -1
  497. package/dist/types/VoidType.spec.js +3 -3
  498. package/dist/types/VoidType.spec.js.map +1 -1
  499. package/dist/types/helper.spec.d.ts +1 -0
  500. package/dist/types/helper.spec.js +145 -0
  501. package/dist/types/helper.spec.js.map +1 -0
  502. package/dist/types/helpers.d.ts +19 -37
  503. package/dist/types/helpers.js +159 -99
  504. package/dist/types/helpers.js.map +1 -1
  505. package/dist/types/index.d.ts +22 -0
  506. package/dist/types/index.js +39 -0
  507. package/dist/types/index.js.map +1 -0
  508. package/dist/util.d.ts +132 -137
  509. package/dist/util.js +796 -362
  510. package/dist/util.js.map +1 -1
  511. package/dist/validators/ClassValidator.d.ts +8 -25
  512. package/dist/validators/ClassValidator.js +96 -176
  513. package/dist/validators/ClassValidator.js.map +1 -1
  514. package/package.json +165 -152
  515. package/dist/astUtils/AstEditor.js.map +0 -1
  516. package/dist/astUtils/AstEditor.spec.js.map +0 -1
  517. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.js.map +0 -1
  518. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.spec.js +0 -32
  519. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.spec.js.map +0 -1
  520. package/dist/parser/SGTypes.spec.js +0 -351
  521. package/dist/parser/SGTypes.spec.js.map +0 -1
  522. package/dist/types/CustomType.d.ts +0 -12
  523. package/dist/types/CustomType.js +0 -44
  524. package/dist/types/CustomType.js.map +0 -1
  525. package/dist/types/LazyType.d.ts +0 -16
  526. package/dist/types/LazyType.js +0 -44
  527. package/dist/types/LazyType.js.map +0 -1
  528. /package/dist/astUtils/{AstEditor.spec.d.ts → Editor.spec.d.ts} +0 -0
  529. /package/dist/bscPlugin/{transpile/BrsFilePreTranspileProcessor.spec.d.ts → completions/CompletionsProcessor.spec.d.ts} +0 -0
  530. /package/dist/{parser/SGTypes.spec.d.ts → bscPlugin/serialize/BslibInjector.spec.d.ts} +0 -0
package/dist/Program.js CHANGED
@@ -4,11 +4,8 @@ exports.Program = void 0;
4
4
  const assert = require("assert");
5
5
  const fsExtra = require("fs-extra");
6
6
  const path = require("path");
7
- const vscode_languageserver_1 = require("vscode-languageserver");
8
7
  const Scope_1 = require("./Scope");
9
8
  const DiagnosticMessages_1 = require("./DiagnosticMessages");
10
- const BrsFile_1 = require("./files/BrsFile");
11
- const XmlFile_1 = require("./files/XmlFile");
12
9
  const util_1 = require("./util");
13
10
  const XmlScope_1 = require("./XmlScope");
14
11
  const DiagnosticFilterer_1 = require("./DiagnosticFilterer");
@@ -20,20 +17,43 @@ const Manifest_1 = require("./preprocessor/Manifest");
20
17
  const vscode_uri_1 = require("vscode-uri");
21
18
  const PluginInterface_1 = require("./PluginInterface");
22
19
  const reflection_1 = require("./astUtils/reflection");
23
- const Parser_1 = require("./parser/Parser");
24
- const TokenKind_1 = require("./lexer/TokenKind");
25
20
  const BscPlugin_1 = require("./bscPlugin/BscPlugin");
21
+ const Editor_1 = require("./astUtils/Editor");
22
+ const CallExpressionInfo_1 = require("./bscPlugin/CallExpressionInfo");
23
+ const SignatureHelpUtil_1 = require("./bscPlugin/SignatureHelpUtil");
24
+ const DiagnosticSeverityAdjuster_1 = require("./DiagnosticSeverityAdjuster");
25
+ const IntegerType_1 = require("./types/IntegerType");
26
+ const StringType_1 = require("./types/StringType");
27
+ const SymbolTable_1 = require("./SymbolTable");
28
+ const BooleanType_1 = require("./types/BooleanType");
29
+ const DoubleType_1 = require("./types/DoubleType");
30
+ const DynamicType_1 = require("./types/DynamicType");
31
+ const FloatType_1 = require("./types/FloatType");
32
+ const LongIntegerType_1 = require("./types/LongIntegerType");
33
+ const ObjectType_1 = require("./types/ObjectType");
34
+ const VoidType_1 = require("./types/VoidType");
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");
26
39
  const roku_deploy_1 = require("roku-deploy");
27
- const AstEditor_1 = require("./astUtils/AstEditor");
28
- const bslibNonAliasedRokuModulesPkgPath = `pkg:/source/roku_modules/rokucommunity_bslib/bslib.brs`;
29
- const bslibAliasedRokuModulesPkgPath = `pkg:/source/roku_modules/bslib/bslib.brs`;
40
+ const roku_types_1 = require("./roku-types");
41
+ const ComponentType_1 = require("./types/ComponentType");
42
+ const types_1 = require("./types");
43
+ const BuiltInInterfaceAdder_1 = require("./types/BuiltInInterfaceAdder");
44
+ const visitors_1 = require("./astUtils/visitors");
45
+ const bslibNonAliasedRokuModulesPkgPath = (0, util_1.standardizePath) `source/roku_modules/rokucommunity_bslib/bslib.brs`;
46
+ const bslibAliasedRokuModulesPkgPath = (0, util_1.standardizePath) `source/roku_modules/bslib/bslib.brs`;
30
47
  class Program {
31
48
  constructor(
32
49
  /**
33
50
  * The root directory for this program
34
51
  */
35
52
  options, logger, plugins) {
36
- 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();
37
57
  /**
38
58
  * A graph of all files and their dependencies.
39
59
  * For example:
@@ -42,19 +62,32 @@ class Program {
42
62
  */
43
63
  this.dependencyGraph = new DependencyGraph_1.DependencyGraph();
44
64
  this.diagnosticFilterer = new DiagnosticFilterer_1.DiagnosticFilterer();
65
+ this.diagnosticAdjuster = new DiagnosticSeverityAdjuster_1.DiagnosticSeverityAdjuster();
66
+ /**
67
+ * A scope that contains all built-in global functions.
68
+ * All scopes should directly or indirectly inherit from this scope
69
+ */
70
+ this.globalScope = undefined;
45
71
  /**
46
72
  * A set of diagnostics. This does not include any of the scope diagnostics.
47
73
  * Should only be set from `this.validate()`
48
74
  */
49
75
  this.diagnostics = [];
76
+ this.fileSymbolInformation = new Map();
50
77
  /**
51
- * A map of every file loaded ino this program, indexed by its lower-case pkgPath
78
+ * A map of every file loaded into this program, indexed by its original file location
52
79
  */
53
- this.pkgMap = {};
80
+ this.files = {};
54
81
  /**
55
- * A map of every file loaded into this program, indexed by its lower-case srcPath
82
+ * A map of every file loaded into this program, indexed by its destPath
56
83
  */
57
- this.files = {};
84
+ this.destMap = new Map();
85
+ /**
86
+ * Plugins can contribute multiple virtual files for a single physical file.
87
+ * This collection links the virtual files back to the physical file that produced them.
88
+ * The key is the standardized and lower-cased srcPath
89
+ */
90
+ this.fileClusters = new Map();
58
91
  this.scopes = {};
59
92
  /**
60
93
  * A map of every component currently loaded into the program, indexed by the component name.
@@ -63,28 +96,118 @@ class Program {
63
96
  * but if you do, only ever use the component at index 0.
64
97
  */
65
98
  this.components = {};
99
+ /**
100
+ * Keeps a set of all the components that need to have their types updated during the current validation cycle
101
+ */
102
+ this.componentSymbolsToUpdate = new Set();
103
+ this.lastValidationInfo = new Map();
104
+ this.getTranspiledFileContentsPipeline = new ActionPipeline_1.ActionPipeline();
105
+ this.buildPipeline = new ActionPipeline_1.ActionPipeline();
66
106
  this.options = util_1.util.normalizeConfig(options);
67
107
  this.logger = logger || new Logger_1.Logger(options.logLevel);
68
- this.plugins = plugins || new PluginInterface_1.default([], this.logger);
108
+ this.plugins = plugins || new PluginInterface_1.default([], { logger: this.logger });
69
109
  //inject the bsc plugin as the first plugin in the stack.
70
110
  this.plugins.addFirst(new BscPlugin_1.BscPlugin());
71
111
  //normalize the root dir path
72
112
  this.options.rootDir = util_1.util.getRootDir(this.options);
73
113
  this.createGlobalScope();
114
+ this.fileFactory = new Factory_1.FileFactory(this);
74
115
  }
75
116
  createGlobalScope() {
76
117
  //create the 'global' scope
77
118
  this.globalScope = new Scope_1.Scope('global', this, 'scope:global');
78
119
  this.globalScope.attachDependencyGraph(this.dependencyGraph);
79
120
  this.scopes.global = this.globalScope;
121
+ this.populateGlobalSymbolTable();
80
122
  //hardcode the files list for global scope to only contain the global file
81
123
  this.globalScope.getAllFiles = () => [globalCallables_1.globalFile];
124
+ globalCallables_1.globalFile.isValidated = true;
82
125
  this.globalScope.validate();
83
126
  //for now, disable validation of global scope because the global files have some duplicate method declarations
84
127
  this.globalScope.getDiagnostics = () => [];
85
128
  //TODO we might need to fix this because the isValidated clears stuff now
86
129
  this.globalScope.isValidated = true;
87
130
  }
131
+ recursivelyAddNodeToSymbolTable(nodeData) {
132
+ if (!nodeData) {
133
+ return;
134
+ }
135
+ let nodeType;
136
+ const nodeName = util_1.util.getSgNodeTypeName(nodeData.name);
137
+ if (!this.globalScope.symbolTable.hasSymbol(nodeName, SymbolTable_1.SymbolTypeFlag.typetime)) {
138
+ let parentNode;
139
+ if (nodeData.extends) {
140
+ const parentNodeData = roku_types_1.nodes[nodeData.extends.name.toLowerCase()];
141
+ try {
142
+ parentNode = this.recursivelyAddNodeToSymbolTable(parentNodeData);
143
+ }
144
+ catch (error) {
145
+ console.log(error, nodeData);
146
+ }
147
+ }
148
+ nodeType = new ComponentType_1.ComponentType(nodeData.name, parentNode);
149
+ nodeType.addBuiltInInterfaces();
150
+ if (nodeData.name === 'Node') {
151
+ // Add `roSGNode` as shorthand for `roSGNodeNode`
152
+ this.globalScope.symbolTable.addSymbol('roSGNode', { description: nodeData.description }, nodeType, SymbolTable_1.SymbolTypeFlag.typetime);
153
+ }
154
+ this.globalScope.symbolTable.addSymbol(nodeName, { description: nodeData.description }, nodeType, SymbolTable_1.SymbolTypeFlag.typetime);
155
+ }
156
+ else {
157
+ nodeType = this.globalScope.symbolTable.getSymbolType(nodeName, { flags: SymbolTable_1.SymbolTypeFlag.typetime });
158
+ }
159
+ return nodeType;
160
+ }
161
+ /**
162
+ * Do all setup required for the global symbol table.
163
+ */
164
+ populateGlobalSymbolTable() {
165
+ //Setup primitive types in global symbolTable
166
+ this.globalScope.symbolTable.addSymbol('boolean', undefined, BooleanType_1.BooleanType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
167
+ this.globalScope.symbolTable.addSymbol('double', undefined, DoubleType_1.DoubleType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
168
+ this.globalScope.symbolTable.addSymbol('dynamic', undefined, DynamicType_1.DynamicType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
169
+ this.globalScope.symbolTable.addSymbol('float', undefined, FloatType_1.FloatType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
170
+ this.globalScope.symbolTable.addSymbol('function', undefined, new FunctionType_1.FunctionType(), SymbolTable_1.SymbolTypeFlag.typetime);
171
+ this.globalScope.symbolTable.addSymbol('integer', undefined, IntegerType_1.IntegerType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
172
+ this.globalScope.symbolTable.addSymbol('longinteger', undefined, LongIntegerType_1.LongIntegerType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
173
+ this.globalScope.symbolTable.addSymbol('object', undefined, new ObjectType_1.ObjectType(), SymbolTable_1.SymbolTypeFlag.typetime);
174
+ this.globalScope.symbolTable.addSymbol('string', undefined, StringType_1.StringType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
175
+ this.globalScope.symbolTable.addSymbol('void', undefined, VoidType_1.VoidType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
176
+ BuiltInInterfaceAdder_1.BuiltInInterfaceAdder.getLookupTable = () => this.globalScope.symbolTable;
177
+ for (const callable of globalCallables_1.globalCallables) {
178
+ this.globalScope.symbolTable.addSymbol(callable.name, { description: callable.shortDescription }, callable.type, SymbolTable_1.SymbolTypeFlag.runtime);
179
+ }
180
+ for (const nodeData of Object.values(roku_types_1.nodes)) {
181
+ this.recursivelyAddNodeToSymbolTable(nodeData);
182
+ }
183
+ for (const componentData of Object.values(roku_types_1.components)) {
184
+ const nodeType = new types_1.InterfaceType(componentData.name);
185
+ nodeType.addBuiltInInterfaces();
186
+ if (componentData.name !== 'roSGNode') {
187
+ // we will add `roSGNode` as shorthand for `roSGNodeNode`, since all roSgNode components are SceneGraph nodes
188
+ this.globalScope.symbolTable.addSymbol(componentData.name, { description: componentData.description }, nodeType, SymbolTable_1.SymbolTypeFlag.typetime);
189
+ }
190
+ }
191
+ for (const ifaceData of Object.values(roku_types_1.interfaces)) {
192
+ const nodeType = new types_1.InterfaceType(ifaceData.name);
193
+ nodeType.addBuiltInInterfaces();
194
+ this.globalScope.symbolTable.addSymbol(ifaceData.name, { description: ifaceData.description }, nodeType, SymbolTable_1.SymbolTypeFlag.typetime);
195
+ }
196
+ for (const eventData of Object.values(roku_types_1.events)) {
197
+ const nodeType = new types_1.InterfaceType(eventData.name);
198
+ nodeType.addBuiltInInterfaces();
199
+ this.globalScope.symbolTable.addSymbol(eventData.name, { description: eventData.description }, nodeType, SymbolTable_1.SymbolTypeFlag.typetime);
200
+ }
201
+ }
202
+ addFileSymbolInfo(file) {
203
+ this.fileSymbolInformation.set(file.pkgPath, {
204
+ provides: file.providedSymbols,
205
+ requires: file.requiredSymbols
206
+ });
207
+ }
208
+ getFileSymbolInfo(file) {
209
+ return this.fileSymbolInformation.get(file.pkgPath);
210
+ }
88
211
  /**
89
212
  * The path to bslib.brs (the BrightScript runtime for certain BrighterScript features)
90
213
  */
@@ -99,7 +222,7 @@ class Program {
99
222
  //default to the embedded version
100
223
  }
101
224
  else {
102
- return `pkg:/source/bslib.brs`;
225
+ return `${this.options.bslibDestinationDir}${path.sep}bslib.brs`;
103
226
  }
104
227
  }
105
228
  get bslibPrefix() {
@@ -110,18 +233,8 @@ class Program {
110
233
  return 'bslib';
111
234
  }
112
235
  }
113
- /**
114
- * Get a copy of the list of files currently loaded in the program
115
- */
116
- getAllFiles() {
117
- return Object.values(this.files);
118
- }
119
236
  addScope(scope) {
120
237
  this.scopes[scope.name] = scope;
121
- this.plugins.emit('afterScopeCreate', {
122
- program: this,
123
- scope: scope
124
- });
125
238
  }
126
239
  /**
127
240
  * Get the component with the specified name
@@ -130,7 +243,7 @@ class Program {
130
243
  var _a;
131
244
  if (componentName) {
132
245
  //return the first compoment in the list with this name
133
- //(components are ordered in this list by pkgPath to ensure consistency)
246
+ //(components are ordered in this list by destPath to ensure consistency)
134
247
  return (_a = this.components[componentName.toLowerCase()]) === null || _a === void 0 ? void 0 : _a[0];
135
248
  }
136
249
  else {
@@ -141,8 +254,7 @@ class Program {
141
254
  * Register (or replace) the reference to a component in the component map
142
255
  */
143
256
  registerComponent(xmlFile, scope) {
144
- var _a, _b;
145
- const key = ((_b = (_a = xmlFile.componentName) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : xmlFile.pkgPath).toLowerCase();
257
+ const key = this.getComponentKey(xmlFile);
146
258
  if (!this.components[key]) {
147
259
  this.components[key] = [];
148
260
  }
@@ -151,8 +263,8 @@ class Program {
151
263
  scope: scope
152
264
  });
153
265
  this.components[key].sort((a, b) => {
154
- const pathA = a.file.pkgPath.toLowerCase();
155
- const pathB = b.file.pkgPath.toLowerCase();
266
+ const pathA = a.file.destPath.toLowerCase();
267
+ const pathB = b.file.destPath.toLowerCase();
156
268
  if (pathA < pathB) {
157
269
  return -1;
158
270
  }
@@ -162,13 +274,13 @@ class Program {
162
274
  return 0;
163
275
  });
164
276
  this.syncComponentDependencyGraph(this.components[key]);
277
+ this.addDeferredComponentTypeSymbolCreation(xmlFile);
165
278
  }
166
279
  /**
167
280
  * Remove the specified component from the components map
168
281
  */
169
282
  unregisterComponent(xmlFile) {
170
- var _a, _b;
171
- const key = ((_b = (_a = xmlFile.componentName) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : xmlFile.pkgPath).toLowerCase();
283
+ const key = this.getComponentKey(xmlFile);
172
284
  const arr = this.components[key] || [];
173
285
  for (let i = 0; i < arr.length; i++) {
174
286
  if (arr[i].file === xmlFile) {
@@ -177,6 +289,44 @@ class Program {
177
289
  }
178
290
  }
179
291
  this.syncComponentDependencyGraph(arr);
292
+ this.addDeferredComponentTypeSymbolCreation(xmlFile);
293
+ }
294
+ /**
295
+ * Adds a component described in an XML to the set of components that needs to be updated this validation cycle.
296
+ * @param xmlFile XML file with <component> tag
297
+ */
298
+ addDeferredComponentTypeSymbolCreation(xmlFile) {
299
+ var _a;
300
+ this.componentSymbolsToUpdate.add({ componentKey: this.getComponentKey(xmlFile), componentName: (_a = xmlFile.componentName) === null || _a === void 0 ? void 0 : _a.text });
301
+ }
302
+ getComponentKey(xmlFile) {
303
+ var _a, _b;
304
+ return ((_b = (_a = xmlFile.componentName) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : xmlFile.pkgPath).toLowerCase();
305
+ }
306
+ /**
307
+ * Updates the global symbol table with the first component in this.components to have the same name as the component in the file
308
+ * @param componentKey key getting a component from `this.components`
309
+ * @param componentName the unprefixed name of the component that will be added (e.g. 'MyLabel' NOT 'roSgNodeMyLabel')
310
+ */
311
+ updateComponentSymbolInGlobalScope(componentKey, componentName) {
312
+ const symbolName = componentName ? util_1.util.getSgNodeTypeName(componentName) : undefined;
313
+ if (!symbolName) {
314
+ return;
315
+ }
316
+ const components = this.components[componentKey] || [];
317
+ // Remove any existing symbols that match
318
+ this.globalScope.symbolTable.removeSymbol(symbolName);
319
+ // There is a component that can be added - use it.
320
+ if (components.length > 0) {
321
+ const componentScope = components[0].scope;
322
+ // TODO: May need to link symbol tables to get correct types for callfuncs
323
+ // componentScope.linkSymbolTable();
324
+ const componentType = componentScope.getComponentType();
325
+ if (componentType) {
326
+ this.globalScope.symbolTable.addSymbol(symbolName, {}, componentType, SymbolTable_1.SymbolTypeFlag.typetime);
327
+ }
328
+ // TODO: Remember to unlink! componentScope.unlinkSymbolTable();
329
+ }
180
330
  }
181
331
  /**
182
332
  * re-attach the dependency graph with a new key for any component who changed
@@ -190,6 +340,7 @@ class Program {
190
340
  //attach (or re-attach) the dependencyGraph for every component whose position changed
191
341
  if (file.dependencyGraphIndex !== i) {
192
342
  file.dependencyGraphIndex = i;
343
+ this.dependencyGraph.addOrReplace(file.dependencyGraphKey, file.dependencies);
193
344
  file.attachDependencyGraph(this.dependencyGraph);
194
345
  scope.attachDependencyGraph(this.dependencyGraph);
195
346
  }
@@ -201,9 +352,10 @@ class Program {
201
352
  */
202
353
  getUnreferencedFiles() {
203
354
  let result = [];
204
- for (let key in this.files) {
205
- const file = this.files[key];
206
- if (!this.fileIsIncludedInAnyScope(file)) {
355
+ for (let filePath in this.files) {
356
+ let file = this.files[filePath];
357
+ //is this file part of a scope
358
+ if (!this.getFirstScopeForFile(file)) {
207
359
  //no scopes reference this file. add it to the list
208
360
  result.push(file);
209
361
  }
@@ -225,13 +377,16 @@ class Program {
225
377
  //get the diagnostics from all unreferenced files
226
378
  let unreferencedFiles = this.getUnreferencedFiles();
227
379
  for (let file of unreferencedFiles) {
228
- diagnostics.push(...file.getDiagnostics());
380
+ diagnostics.push(...file.diagnostics);
229
381
  }
230
382
  const filteredDiagnostics = this.logger.time(Logger_1.LogLevel.debug, ['filter diagnostics'], () => {
231
383
  //filter out diagnostics based on our diagnostic filters
232
384
  let finalDiagnostics = this.diagnosticFilterer.filter(Object.assign(Object.assign({}, this.options), { rootDir: this.options.rootDir }), diagnostics);
233
385
  return finalDiagnostics;
234
386
  });
387
+ this.logger.time(Logger_1.LogLevel.debug, ['adjust diagnostics severity'], () => {
388
+ this.diagnosticAdjuster.adjust(this.options, diagnostics);
389
+ });
235
390
  this.logger.info(`diagnostic counts: total=${chalk_1.default.yellow(diagnostics.length.toString())}, after filter=${chalk_1.default.yellow(filteredDiagnostics.length.toString())}`);
236
391
  return filteredDiagnostics;
237
392
  });
@@ -241,7 +396,7 @@ class Program {
241
396
  }
242
397
  /**
243
398
  * Determine if the specified file is loaded in this program right now.
244
- * @param filePath
399
+ * @param filePath the absolute or relative path to the file
245
400
  * @param normalizePath should the provided path be normalized before use
246
401
  */
247
402
  hasFile(filePath, normalizePath = true) {
@@ -249,12 +404,15 @@ class Program {
249
404
  }
250
405
  /**
251
406
  * roku filesystem is case INsensitive, so find the scope by key case insensitive
252
- * @param scopeName
407
+ * @param scopeName xml scope names are their `destPath`. Source scope is stored with the key `"source"`
253
408
  */
254
409
  getScopeByName(scopeName) {
255
410
  if (!scopeName) {
256
411
  return undefined;
257
412
  }
413
+ //most scopes are xml file pkg paths. however, the ones that are not are single names like "global" and "scope",
414
+ //so it's safe to run the standardizePkgPath method
415
+ scopeName = (0, util_1.standardizePath) `${scopeName}`;
258
416
  let key = Object.keys(this.scopes).find(x => x.toLowerCase() === scopeName.toLowerCase());
259
417
  return this.scopes[key];
260
418
  }
@@ -275,8 +433,14 @@ class Program {
275
433
  * Update internal maps with this file reference
276
434
  */
277
435
  assignFile(file) {
436
+ const fileAddEvent = {
437
+ file: file,
438
+ program: this
439
+ };
440
+ this.plugins.emit('beforeFileAdd', fileAddEvent);
278
441
  this.files[file.srcPath.toLowerCase()] = file;
279
- this.pkgMap[file.pkgPath.toLowerCase()] = file;
442
+ this.destMap.set(file.destPath.toLowerCase(), file);
443
+ this.plugins.emit('afterFileAdd', fileAddEvent);
280
444
  return file;
281
445
  }
282
446
  /**
@@ -284,108 +448,155 @@ class Program {
284
448
  */
285
449
  unassignFile(file) {
286
450
  delete this.files[file.srcPath.toLowerCase()];
287
- delete this.pkgMap[file.pkgPath.toLowerCase()];
451
+ this.destMap.delete(file.destPath.toLowerCase());
288
452
  return file;
289
453
  }
290
- setFile(fileParam, fileContents) {
291
- assert.ok(fileParam, 'fileParam is required');
292
- let srcPath;
293
- let pkgPath;
294
- if (typeof fileParam === 'string') {
295
- //is a pkg path
296
- if (fileParam.startsWith('pkg:/')) {
297
- //srcPath is the pkgPath relative to the rootDir
298
- srcPath = (0, util_1.standardizePath) `${this.options.rootDir}/${fileParam.substring(5)}`;
299
- pkgPath = fileParam;
300
- //is a srcPath (absolute path to src file location)
454
+ setFile(fileParam, fileData) {
455
+ //normalize the file paths
456
+ const { srcPath, destPath } = this.getPaths(fileParam, this.options.rootDir);
457
+ let file = this.logger.time(Logger_1.LogLevel.debug, ['Program.setFile()', chalk_1.default.green(srcPath)], () => {
458
+ var _a, _b, _c;
459
+ //if the file is already loaded, remove it
460
+ if (this.hasFile(srcPath)) {
461
+ this.removeFile(srcPath, true, true);
301
462
  }
302
- else if (path.isAbsolute(fileParam)) {
303
- srcPath = util_1.util.standardizePath(fileParam);
304
- //assume the file path is a sub path of rootDir
305
- pkgPath = util_1.util.sanitizePkgPath(roku_deploy_1.util.stringReplaceInsensitive(srcPath, this.options.rootDir, ''));
306
- //is destPath (path relative to rootDir and `pkg:/`)
463
+ const data = new LazyFileData_1.LazyFileData(fileData);
464
+ const event = new ProvideFileEventInternal(this, srcPath, destPath, data, this.fileFactory);
465
+ this.plugins.emit('beforeProvideFile', event);
466
+ this.plugins.emit('provideFile', event);
467
+ this.plugins.emit('afterProvideFile', event);
468
+ //if no files were provided, create a AssetFile to represent it.
469
+ if (event.files.length === 0) {
470
+ event.files.push(this.fileFactory.AssetFile({
471
+ srcPath: event.srcPath,
472
+ destPath: event.destPath,
473
+ pkgPath: event.destPath,
474
+ data: data
475
+ }));
307
476
  }
308
- else {
309
- srcPath = (0, util_1.standardizePath) `${this.options.rootDir}/${fileParam}`;
310
- pkgPath = util_1.util.sanitizePkgPath(fileParam);
477
+ //find the file instance for the srcPath that triggered this action.
478
+ const primaryFile = event.files.find(x => x.srcPath === srcPath);
479
+ if (!primaryFile) {
480
+ throw new Error(`No file provided for srcPath '${srcPath}'. Instead, received ${JSON.stringify(event.files.map(x => ({
481
+ type: x.type,
482
+ srcPath: x.srcPath,
483
+ destPath: x.destPath
484
+ })))}`);
485
+ }
486
+ //link the virtual files to the primary file
487
+ this.fileClusters.set((_a = primaryFile.srcPath) === null || _a === void 0 ? void 0 : _a.toLowerCase(), event.files);
488
+ for (const file of event.files) {
489
+ file.srcPath = (0, util_1.standardizePath)(file.srcPath);
490
+ if (file.destPath) {
491
+ file.destPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(file.destPath, this.options.rootDir, '')}`;
492
+ }
493
+ if (file.pkgPath) {
494
+ file.pkgPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(file.pkgPath, this.options.rootDir, '')}`;
495
+ }
496
+ else {
497
+ file.pkgPath = file.destPath;
498
+ }
499
+ file.excludeFromOutput = file.excludeFromOutput === true;
500
+ //set the dependencyGraph key for every file to its destPath
501
+ file.dependencyGraphKey = file.destPath.toLowerCase();
502
+ this.assignFile(file);
503
+ //register a callback anytime this file's dependencies change
504
+ if (typeof file.onDependenciesChanged === 'function') {
505
+ (_b = file.disposables) !== null && _b !== void 0 ? _b : (file.disposables = []);
506
+ file.disposables.push(this.dependencyGraph.onchange(file.dependencyGraphKey, file.onDependenciesChanged.bind(file)));
507
+ }
508
+ //register this file (and its dependencies) with the dependency graph
509
+ this.dependencyGraph.addOrReplace(file.dependencyGraphKey, (_c = file.dependencies) !== null && _c !== void 0 ? _c : []);
510
+ //if this is a `source` file, add it to the source scope's dependency list
511
+ if (this.isSourceBrsFile(file)) {
512
+ this.createSourceScope();
513
+ this.dependencyGraph.addDependency('scope:source', file.dependencyGraphKey);
514
+ }
515
+ //if this is an xml file in the components folder, register it as a component
516
+ if (this.isComponentsXmlFile(file)) {
517
+ //create a new scope for this xml file
518
+ let scope = new XmlScope_1.XmlScope(file, this);
519
+ this.addScope(scope);
520
+ //register this compoent now that we have parsed it and know its component name
521
+ this.registerComponent(file, scope);
522
+ //notify plugins that the scope is created and the component is registered
523
+ this.plugins.emit('afterScopeCreate', {
524
+ program: this,
525
+ scope: scope
526
+ });
527
+ }
311
528
  }
312
- //is a FileObj
529
+ return primaryFile;
530
+ });
531
+ return file;
532
+ }
533
+ /**
534
+ * Given a srcPath, a destPath, or both, resolve whichever is missing, relative to rootDir.
535
+ * @param fileParam an object representing file paths
536
+ * @param rootDir must be a pre-normalized path
537
+ */
538
+ getPaths(fileParam, rootDir) {
539
+ let srcPath;
540
+ let destPath;
541
+ assert.ok(fileParam, 'fileParam is required');
542
+ //lift the path vars from the incoming param
543
+ if (typeof fileParam === 'string') {
544
+ fileParam = this.removePkgPrefix(fileParam);
545
+ srcPath = (0, util_1.standardizePath) `${path.resolve(rootDir, fileParam)}`;
546
+ destPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(srcPath, rootDir, '')}`;
313
547
  }
314
548
  else {
315
- srcPath = (0, util_1.standardizePath) `${fileParam.src}`;
316
- pkgPath = util_1.util.sanitizePkgPath(fileParam.dest);
317
- }
318
- const lowerPkgPath = pkgPath.toLowerCase();
319
- return this.logger.time(Logger_1.LogLevel.debug, ['Program.setFile()', chalk_1.default.green(srcPath)], () => {
320
- assert.ok(srcPath, 'srcPath is required');
321
- assert.ok(pkgPath, 'pkgPath is required');
322
- //if the file is already loaded, remove it
323
- if (this.hasFile(srcPath)) {
324
- this.removeFile(srcPath);
549
+ let param = fileParam;
550
+ if (param.src) {
551
+ srcPath = (0, util_1.standardizePath) `${param.src}`;
325
552
  }
326
- let fileExtension = path.extname(srcPath).toLowerCase();
327
- let file;
328
- const beforeFileParseEvent = {
329
- program: this,
330
- srcPath: srcPath,
331
- source: fileContents
332
- };
333
- if (fileExtension === '.brs' || fileExtension === '.bs') {
334
- //add the file to the program
335
- const brsFile = this.assignFile(new BrsFile_1.BrsFile(srcPath, pkgPath, this));
336
- //add file to the `source` dependency list
337
- if (brsFile.pkgPath.startsWith('pkg:/source/')) {
338
- this.createSourceScope();
339
- this.dependencyGraph.addDependency('scope:source', brsFile.dependencyGraphKey);
340
- }
341
- //add the file to the program
342
- this.assignFile(brsFile);
343
- this.plugins.emit('beforeFileParse', beforeFileParseEvent);
344
- this.logger.time(Logger_1.LogLevel.debug, ['parse', chalk_1.default.green(srcPath)], () => {
345
- brsFile.parse(beforeFileParseEvent.source);
346
- });
347
- //notify plugins that this file has finished parsing
348
- this.plugins.emit('afterFileParse', {
349
- program: this,
350
- file: brsFile
351
- });
352
- file = brsFile;
353
- brsFile.attachDependencyGraph(this.dependencyGraph);
553
+ if (param.srcPath) {
554
+ srcPath = (0, util_1.standardizePath) `${param.srcPath}`;
354
555
  }
355
- else if (
356
- //is xml file
357
- fileExtension === '.xml' &&
358
- //resides in the components folder (Roku will only parse xml files in the components folder)
359
- lowerPkgPath.startsWith('pkg:/components/')) {
360
- //add the file to the program
361
- const xmlFile = this.assignFile(new XmlFile_1.XmlFile(srcPath, pkgPath, this));
362
- this.plugins.emit('beforeFileParse', beforeFileParseEvent);
363
- this.logger.time(Logger_1.LogLevel.debug, ['parse', chalk_1.default.green(srcPath)], () => {
364
- xmlFile.parse(beforeFileParseEvent.source);
365
- });
366
- //notify plugins that this file has finished parsing
367
- this.plugins.emit('afterFileParse', {
368
- program: this,
369
- file: xmlFile
370
- });
371
- file = xmlFile;
372
- //create a new scope for this xml file
373
- let scope = new XmlScope_1.XmlScope(xmlFile, this);
374
- this.addScope(scope);
375
- //register this compoent now that we have parsed it and know its component name
376
- this.registerComponent(xmlFile, scope);
556
+ if (param.dest) {
557
+ destPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.dest)}`;
377
558
  }
378
- else {
379
- //TODO do we actually need to implement this? Figure out how to handle img paths
380
- // let genericFile = this.files[srcPath] = <any>{
381
- // srcPath: srcPath,
382
- // pkgPath: pkgPath,
383
- // wasProcessed: true
384
- // } as File;
385
- // file = <any>genericFile;
559
+ if (param.pkgPath) {
560
+ destPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.pkgPath)}`;
386
561
  }
387
- return file;
388
- });
562
+ }
563
+ //if there's no srcPath, use the destPath to build an absolute srcPath
564
+ if (!srcPath) {
565
+ srcPath = (0, util_1.standardizePath) `${rootDir}/${destPath}`;
566
+ }
567
+ //coerce srcPath to an absolute path
568
+ if (!path.isAbsolute(srcPath)) {
569
+ srcPath = util_1.util.standardizePath(srcPath);
570
+ }
571
+ //if destPath isn't set, compute it from the other paths
572
+ if (!destPath) {
573
+ destPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(srcPath, rootDir, '')}`;
574
+ }
575
+ assert.ok(srcPath, 'fileEntry.src is required');
576
+ assert.ok(destPath, 'fileEntry.dest is required');
577
+ return {
578
+ srcPath: srcPath,
579
+ //remove leading slash
580
+ destPath: destPath.replace(/^[\/\\]+/, '')
581
+ };
582
+ }
583
+ /**
584
+ * Remove any leading `pkg:/` found in the path
585
+ */
586
+ removePkgPrefix(path) {
587
+ return path.replace(/^pkg:\//i, '');
588
+ }
589
+ /**
590
+ * Is this file a .brs file found somewhere within the `pkg:/source/` folder?
591
+ */
592
+ isSourceBrsFile(file) {
593
+ return !!/^(pkg:\/)?source[\/\\]/.exec(file.destPath);
594
+ }
595
+ /**
596
+ * Is this file a .brs file found somewhere within the `pkg:/source/` folder?
597
+ */
598
+ isComponentsXmlFile(file) {
599
+ return (0, reflection_1.isXmlFile)(file) && !!/^(pkg:\/)?components[\/\\]/.exec(file.destPath);
389
600
  }
390
601
  /**
391
602
  * Ensure source scope is created.
@@ -396,11 +607,15 @@ class Program {
396
607
  const sourceScope = new Scope_1.Scope('source', this, 'scope:source');
397
608
  sourceScope.attachDependencyGraph(this.dependencyGraph);
398
609
  this.addScope(sourceScope);
610
+ this.plugins.emit('afterScopeCreate', {
611
+ program: this,
612
+ scope: sourceScope
613
+ });
399
614
  }
400
615
  }
401
616
  /**
402
617
  * Remove a set of files from the program
403
- * @param filePaths can be an array of srcPath or destPath strings
618
+ * @param srcPaths can be an array of srcPath or destPath strings
404
619
  * @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
405
620
  */
406
621
  removeFiles(srcPaths, normalizePath = true) {
@@ -410,69 +625,58 @@ class Program {
410
625
  }
411
626
  /**
412
627
  * Remove a file from the program
413
- * @param filePath can be a srcPath, a pkgPath, or a destPath (same as pkgPath but without `pkg:/`)
628
+ * @param filePath can be a srcPath, a destPath, or a destPath with leading `pkg:/`
414
629
  * @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
415
630
  */
416
- removeFile(filePath, normalizePath = true) {
631
+ removeFile(filePath, normalizePath = true, keepSymbolInformation = false) {
632
+ var _a, _b, _c, _d;
417
633
  this.logger.debug('Program.removeFile()', filePath);
418
- let file = this.getFile(filePath, normalizePath);
419
- if (file) {
420
- this.plugins.emit('beforeFileDispose', {
421
- program: this,
422
- file: file
423
- });
634
+ const paths = this.getPaths(filePath, this.options.rootDir);
635
+ //there can be one or more File entries for a single srcPath, so get all of them and remove them all
636
+ 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)];
637
+ for (const file of files) {
638
+ //if a file has already been removed, nothing more needs to be done here
639
+ if (!file || !this.hasFile(file.srcPath)) {
640
+ continue;
641
+ }
642
+ const event = { file: file, program: this };
643
+ this.plugins.emit('beforeFileRemove', event);
424
644
  //if there is a scope named the same as this file's path, remove it (i.e. xml scopes)
425
- let scope = this.scopes[file.pkgPath];
645
+ let scope = this.scopes[file.destPath];
426
646
  if (scope) {
427
- this.plugins.emit('beforeScopeDispose', {
647
+ const scopeDisposeEvent = {
428
648
  program: this,
429
649
  scope: scope
430
- });
650
+ };
651
+ this.plugins.emit('beforeScopeDispose', scopeDisposeEvent);
652
+ this.plugins.emit('onScopeDispose', scopeDisposeEvent);
431
653
  scope.dispose();
432
654
  //notify dependencies of this scope that it has been removed
433
655
  this.dependencyGraph.remove(scope.dependencyGraphKey);
434
- delete this.scopes[file.pkgPath];
435
- this.plugins.emit('afterScopeDispose', {
436
- program: this,
437
- scope: scope
438
- });
656
+ delete this.scopes[file.destPath];
657
+ this.plugins.emit('afterScopeDispose', scopeDisposeEvent);
439
658
  }
440
659
  //remove the file from the program
441
660
  this.unassignFile(file);
442
661
  this.dependencyGraph.remove(file.dependencyGraphKey);
443
662
  //if this is a pkg:/source file, notify the `source` scope that it has changed
444
- if (file.pkgPath.startsWith('pkg:/source/')) {
663
+ if (this.isSourceBrsFile(file)) {
445
664
  this.dependencyGraph.removeDependency('scope:source', file.dependencyGraphKey);
665
+ if (!keepSymbolInformation) {
666
+ this.fileSymbolInformation.delete(file.pkgPath);
667
+ }
446
668
  }
447
669
  //if this is a component, remove it from our components map
448
670
  if ((0, reflection_1.isXmlFile)(file)) {
449
671
  this.unregisterComponent(file);
450
672
  }
451
- //dispose file
452
- file === null || file === void 0 ? void 0 : file.dispose();
453
- this.plugins.emit('afterFileDispose', {
454
- program: this,
455
- file: file
456
- });
457
- }
458
- }
459
- /**
460
- * Remove all files from the program that are in the specified folder path (recursive)
461
- * @param folderSrcPath The absolute path to the folder on disk
462
- * @param normalizePath should the provided path be normalized before use?
463
- */
464
- removeFilesInFolder(folderSrcPath, normalizePath = true) {
465
- if (normalizePath) {
466
- folderSrcPath = util_1.util.standardizePath(folderSrcPath);
467
- }
468
- const lowerFolderSrcPath = folderSrcPath.toLowerCase();
469
- for (const key in this.files) {
470
- const file = this.files[key];
471
- const lowerSrcPath = file.srcPath.toLowerCase();
472
- //if the file path starts with the parent path and the file path does not exactly match the folder path
473
- if (lowerSrcPath.toLowerCase().startsWith(lowerFolderSrcPath) && lowerSrcPath !== lowerFolderSrcPath) {
474
- this.removeFile(file.srcPath, false);
673
+ //dispose any disposable things on the file
674
+ for (const disposable of (_c = file === null || file === void 0 ? void 0 : file.disposables) !== null && _c !== void 0 ? _c : []) {
675
+ disposable();
475
676
  }
677
+ //dispose file
678
+ (_d = file === null || file === void 0 ? void 0 : file.dispose) === null || _d === void 0 ? void 0 : _d.call(file);
679
+ this.plugins.emit('afterFileRemove', event);
476
680
  }
477
681
  }
478
682
  /**
@@ -480,78 +684,157 @@ class Program {
480
684
  */
481
685
  validate() {
482
686
  this.logger.time(Logger_1.LogLevel.log, ['Validating project'], () => {
483
- var _a;
687
+ var _a, _b, _c, _d, _e, _f, _g;
484
688
  this.diagnostics = [];
485
- this.plugins.emit('beforeProgramValidate', {
689
+ const programValidateEvent = {
486
690
  program: this
487
- });
691
+ };
692
+ this.plugins.emit('beforeProgramValidate', programValidateEvent);
693
+ this.plugins.emit('onProgramValidate', programValidateEvent);
488
694
  //validate every file
695
+ const brsFilesValidated = [];
489
696
  for (const file of Object.values(this.files)) {
490
- //find any files NOT loaded into a scope
491
- if (!this.fileIsIncludedInAnyScope(file)) {
492
- this.logger.debug('Program.validate(): fileNotReferenced by any scope', () => chalk_1.default.green(file === null || file === void 0 ? void 0 : file.pkgPath));
493
- //the file is not loaded in any scope
494
- this.diagnostics.push(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.fileNotReferencedByAnyOtherFile()), { file: file, range: util_1.util.createRange(0, 0, 0, Number.MAX_VALUE) }));
495
- }
496
697
  //for every unvalidated file, validate it
497
698
  if (!file.isValidated) {
498
- this.plugins.emit('beforeFileValidate', {
699
+ const validateFileEvent = {
499
700
  program: this,
500
701
  file: file
501
- });
702
+ };
703
+ this.plugins.emit('beforeFileValidate', validateFileEvent);
502
704
  //emit an event to allow plugins to contribute to the file validation process
503
- this.plugins.emit('onFileValidate', {
504
- program: this,
505
- file: file
506
- });
507
- //call file.validate() IF the file has that function defined
508
- (_a = file.validate) === null || _a === void 0 ? void 0 : _a.call(file);
705
+ this.plugins.emit('onFileValidate', validateFileEvent);
509
706
  file.isValidated = true;
510
- this.plugins.emit('afterFileValidate', {
511
- program: this,
512
- file: file
513
- });
707
+ if ((0, reflection_1.isBrsFile)(file)) {
708
+ brsFilesValidated.push(file);
709
+ }
710
+ this.plugins.emit('afterFileValidate', validateFileEvent);
514
711
  }
515
712
  }
516
- this.logger.time(Logger_1.LogLevel.info, ['Validate all scopes'], () => {
517
- for (let scope of Object.values(this.scopes)) {
518
- //only validate unvalidated scopes
519
- if (!scope.isValidated) {
520
- scope.linkSymbolTable();
521
- scope.validate();
522
- scope.unlinkSymbolTable();
523
- scope.isValidated = true;
713
+ // build list of all changed symbols in each file that changed
714
+ this.lastValidationInfo.clear();
715
+ for (const file of brsFilesValidated) {
716
+ const fileInfo = {
717
+ symbolsNotDefinedInEveryScope: [],
718
+ duplicateSymbolsInSameScope: [],
719
+ symbolsNotConsistentAcrossScopes: []
720
+ };
721
+ const scopesToCheckForConsistency = this.getScopesForFile(file);
722
+ for (const symbol of file.requiredSymbols) {
723
+ let providedSymbolType;
724
+ let scopesDefiningSymbol = [];
725
+ let scopesAreInconsistent = false;
726
+ for (const scope of scopesToCheckForConsistency) {
727
+ let symbolFoundInScope = false;
728
+ for (const scopeFile of scope.getAllFiles()) {
729
+ if (!(0, reflection_1.isBrsFile)(scopeFile) || scopeFile.isTypedef || scopeFile.hasTypedef) {
730
+ continue;
731
+ }
732
+ const lowerFirstSymbolName = (_b = (_a = symbol.typeChain) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.name.toLowerCase();
733
+ let symbolInThisScope = (_d = (_c = scopeFile.providedSymbols.symbolMap) === null || _c === void 0 ? void 0 : _c.get(symbol.flags)) === null || _d === void 0 ? void 0 : _d.get(lowerFirstSymbolName);
734
+ if (!symbolInThisScope && ((_e = symbol.containingNamespaces) === null || _e === void 0 ? void 0 : _e.length) > 0) {
735
+ const fullNameWithNamespaces = (symbol.containingNamespaces.join('.') + '.' + lowerFirstSymbolName).toLowerCase();
736
+ symbolInThisScope = (_g = (_f = scopeFile.providedSymbols.symbolMap) === null || _f === void 0 ? void 0 : _f.get(symbol.flags)) === null || _g === void 0 ? void 0 : _g.get(fullNameWithNamespaces);
737
+ }
738
+ if (symbolInThisScope) {
739
+ if (symbolFoundInScope) {
740
+ // this is duplicately defined!
741
+ fileInfo.duplicateSymbolsInSameScope.push({ symbol: symbol, scope: scope });
742
+ }
743
+ else {
744
+ symbolFoundInScope = true;
745
+ scopesDefiningSymbol.push(scope);
746
+ //check for consistency across scopes
747
+ if (!providedSymbolType) {
748
+ providedSymbolType = symbolInThisScope.type;
749
+ }
750
+ else {
751
+ //get more general type
752
+ if (providedSymbolType.isEqual(symbolInThisScope.type)) {
753
+ //type in this scope is the same as one we're already checking
754
+ }
755
+ else if (providedSymbolType.isTypeCompatible(symbolInThisScope.type)) {
756
+ //type in this scope is compatible with one we're storing. use most generic
757
+ providedSymbolType = symbolInThisScope.type;
758
+ }
759
+ else if (symbolInThisScope.type.isTypeCompatible(providedSymbolType)) {
760
+ // type we're storing is more generic that the type in this scope
761
+ }
762
+ else {
763
+ // type in this scope is not compatible with other types for this symbol
764
+ scopesAreInconsistent = true;
765
+ }
766
+ }
767
+ }
768
+ }
769
+ }
770
+ if (!symbolFoundInScope) {
771
+ fileInfo.symbolsNotDefinedInEveryScope.push({ symbol: symbol, scope: scope });
772
+ }
773
+ }
774
+ if (scopesAreInconsistent) {
775
+ fileInfo.symbolsNotConsistentAcrossScopes.push({ symbol: symbol, scopes: scopesDefiningSymbol });
524
776
  }
525
777
  }
778
+ this.lastValidationInfo.set(file.srcPath.toLowerCase(), fileInfo);
779
+ }
780
+ this.detectIncompatibleSymbolsAcrossScopes();
781
+ // Build component types for any component that changes
782
+ this.logger.time(Logger_1.LogLevel.info, ['Build component types'], () => {
783
+ for (let { componentKey, componentName } of this.componentSymbolsToUpdate) {
784
+ this.updateComponentSymbolInGlobalScope(componentKey, componentName);
785
+ }
786
+ this.componentSymbolsToUpdate.clear();
526
787
  });
527
- this.detectDuplicateComponentNames();
528
- this.plugins.emit('afterProgramValidate', {
529
- program: this
788
+ const changedSymbolsMapArr = brsFilesValidated === null || brsFilesValidated === void 0 ? void 0 : brsFilesValidated.map(f => {
789
+ if ((0, reflection_1.isBrsFile)(f)) {
790
+ return f.providedSymbols.changes;
791
+ }
792
+ return null;
793
+ }).filter(x => x);
794
+ const changedSymbols = new Map();
795
+ for (const flag of [SymbolTable_1.SymbolTypeFlag.runtime, SymbolTable_1.SymbolTypeFlag.typetime]) {
796
+ const changedSymbolsSetArr = changedSymbolsMapArr.map(symMap => symMap.get(flag));
797
+ changedSymbols.set(flag, new Set(...changedSymbolsSetArr));
798
+ }
799
+ this.logger.time(Logger_1.LogLevel.info, ['Validate all scopes'], () => {
800
+ for (let scopeName in this.scopes) {
801
+ let scope = this.scopes[scopeName];
802
+ scope.validate({ changedFiles: brsFilesValidated, changedSymbols: changedSymbols });
803
+ }
530
804
  });
805
+ this.detectDuplicateComponentNames();
806
+ this.plugins.emit('afterProgramValidate', programValidateEvent);
531
807
  });
532
808
  }
809
+ detectIncompatibleSymbolsAcrossScopes() {
810
+ for (const [lowerFilePath, fileInfo] of this.lastValidationInfo.entries()) {
811
+ const file = this.files[lowerFilePath];
812
+ for (const symbolAndScopes of fileInfo.symbolsNotConsistentAcrossScopes) {
813
+ const typeChainResult = util_1.util.processTypeChain(symbolAndScopes.symbol.typeChain);
814
+ const scopeListName = symbolAndScopes.scopes.map(s => s.name).join(', ');
815
+ this.diagnostics.push(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.incompatibleSymbolDefinition(typeChainResult.fullNameOfItem, scopeListName)), { file: file, range: typeChainResult.range }));
816
+ }
817
+ }
818
+ }
533
819
  /**
534
820
  * Flag all duplicate component names
535
821
  */
536
822
  detectDuplicateComponentNames() {
537
- var _a;
538
- const componentsByName = new Map();
539
- for (const key in this.files) {
540
- const file = this.files[key];
823
+ const componentsByName = Object.keys(this.files).reduce((map, filePath) => {
824
+ var _a;
825
+ const file = this.files[filePath];
541
826
  //if this is an XmlFile, and it has a valid `componentName` property
542
- if ((0, reflection_1.isXmlFile)(file)) {
543
- const componentNameLower = (_a = file.componentName) === null || _a === void 0 ? void 0 : _a.text.toLowerCase();
544
- if (componentNameLower) {
545
- if (!componentsByName.has(componentNameLower)) {
546
- componentsByName.set(componentNameLower, [file]);
547
- }
548
- else {
549
- componentsByName.get(componentNameLower).push(file);
550
- }
827
+ if ((0, reflection_1.isXmlFile)(file) && ((_a = file.componentName) === null || _a === void 0 ? void 0 : _a.text)) {
828
+ let lowerName = file.componentName.text.toLowerCase();
829
+ if (!map[lowerName]) {
830
+ map[lowerName] = [];
551
831
  }
832
+ map[lowerName].push(file);
552
833
  }
553
- }
554
- for (const xmlFiles of componentsByName.values()) {
834
+ return map;
835
+ }, {});
836
+ for (let name in componentsByName) {
837
+ const xmlFiles = componentsByName[name];
555
838
  //add diagnostics for every duplicate component with this name
556
839
  if (xmlFiles.length > 1) {
557
840
  for (let xmlFile of xmlFiles) {
@@ -567,17 +850,6 @@ class Program {
567
850
  }
568
851
  }
569
852
  }
570
- /**
571
- * Determine at least one scope has the file
572
- */
573
- fileIsIncludedInAnyScope(file) {
574
- for (let scope of Object.values(this.scopes)) {
575
- if (scope.hasFile(file)) {
576
- return true;
577
- }
578
- }
579
- return false;
580
- }
581
853
  /**
582
854
  * Get the files for a list of filePaths
583
855
  * @param filePaths can be an array of srcPath or a destPath strings
@@ -590,33 +862,32 @@ class Program {
590
862
  }
591
863
  /**
592
864
  * Get the file at the given path
593
- * @param filePath can be a srcPath, a pkgPath, or a destPath (same as pkgPath but without `pkg:/`)
865
+ * @param filePath can be a srcPath or a destPath
594
866
  * @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
595
867
  */
596
868
  getFile(filePath, normalizePath = true) {
597
869
  if (typeof filePath !== 'string') {
598
870
  return undefined;
871
+ //is the path absolute (or the `virtual:` prefix)
599
872
  }
600
- else if (path.isAbsolute(filePath)) {
873
+ else if (/^(?:(?:virtual:[\/\\])|(?:\w:)|(?:[\/\\]))/gmi.exec(filePath)) {
601
874
  return this.files[(normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase()];
602
875
  }
603
876
  else {
604
- return this.pkgMap[(normalizePath ? util_1.util.sanitizePkgPath(filePath) : filePath).toLowerCase()];
877
+ return this.destMap.get((normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase());
605
878
  }
606
879
  }
607
880
  /**
608
881
  * Get a list of all scopes the file is loaded into
609
- * @param file
882
+ * @param file the file
610
883
  */
611
884
  getScopesForFile(file) {
612
- if (typeof file === 'string') {
613
- file = this.getFile(file);
614
- }
885
+ const resolvedFile = typeof file === 'string' ? this.getFile(file) : file;
615
886
  let result = [];
616
- if (file) {
887
+ if (resolvedFile) {
617
888
  for (let key in this.scopes) {
618
889
  let scope = this.scopes[key];
619
- if (scope.hasFile(file)) {
890
+ if (scope.hasFile(resolvedFile)) {
620
891
  result.push(scope);
621
892
  }
622
893
  }
@@ -635,26 +906,37 @@ class Program {
635
906
  }
636
907
  }
637
908
  getStatementsByName(name, originFile, namespaceName) {
638
- var _a, _b;
639
909
  let results = new Map();
640
910
  const filesSearched = new Set();
641
911
  let lowerNamespaceName = namespaceName === null || namespaceName === void 0 ? void 0 : namespaceName.toLowerCase();
642
912
  let lowerName = name === null || name === void 0 ? void 0 : name.toLowerCase();
913
+ function addToResults(statement, file) {
914
+ var _a, _b;
915
+ let parentNamespaceName = (_b = (_a = statement.findAncestor(reflection_1.isNamespaceStatement)) === null || _a === void 0 ? void 0 : _a.getName(originFile.parseMode)) === null || _b === void 0 ? void 0 : _b.toLowerCase();
916
+ if (statement.name.text.toLowerCase() === lowerName && (!lowerNamespaceName || parentNamespaceName === lowerNamespaceName)) {
917
+ if (!results.has(statement)) {
918
+ results.set(statement, { item: statement, file: file });
919
+ }
920
+ }
921
+ }
643
922
  //look through all files in scope for matches
644
923
  for (const scope of this.getScopesForFile(originFile)) {
645
924
  for (const file of scope.getAllFiles()) {
646
- if ((0, reflection_1.isXmlFile)(file) || filesSearched.has(file)) {
925
+ //skip non-brs files, or files we've already processed
926
+ if (!(0, reflection_1.isBrsFile)(file) || filesSearched.has(file)) {
647
927
  continue;
648
928
  }
649
929
  filesSearched.add(file);
650
- for (const statement of [...file.parser.references.functionStatements, ...file.parser.references.classStatements.flatMap((cs) => cs.methods)]) {
651
- let parentNamespaceName = (_b = (_a = statement.namespaceName) === null || _a === void 0 ? void 0 : _a.getName(originFile.parseMode)) === null || _b === void 0 ? void 0 : _b.toLowerCase();
652
- if (statement.name.text.toLowerCase() === lowerName && (!parentNamespaceName || parentNamespaceName === lowerNamespaceName)) {
653
- if (!results.has(statement)) {
654
- results.set(statement, { item: statement, file: file });
655
- }
930
+ file.ast.walk((0, visitors_1.createVisitor)({
931
+ FunctionStatement: (statement) => {
932
+ addToResults(statement, file);
933
+ },
934
+ MethodStatement: (statement) => {
935
+ addToResults(statement, file);
656
936
  }
657
- }
937
+ }), {
938
+ walkMode: visitors_1.WalkMode.visitStatements
939
+ });
658
940
  }
659
941
  }
660
942
  return [...results.values()];
@@ -667,40 +949,41 @@ class Program {
667
949
  let funcNames = new Set();
668
950
  let currentScope = scope;
669
951
  while ((0, reflection_1.isXmlScope)(currentScope)) {
670
- for (let member of (_b = (_a = currentScope.xmlFile.ast.component) === null || _a === void 0 ? void 0 : _a.interfaceMembers) !== null && _b !== void 0 ? _b : []) {
671
- if ((0, reflection_1.isSGInterfaceFunction)(member)) {
672
- const name = member.name;
673
- if (!filterName || name === filterName) {
674
- funcNames.add(name);
675
- }
952
+ for (let name of (_b = (_a = currentScope.xmlFile.ast.componentElement.interfaceElement) === null || _a === void 0 ? void 0 : _a.functions.map((f) => f.name)) !== null && _b !== void 0 ? _b : []) {
953
+ if (!filterName || name === filterName) {
954
+ funcNames.add(name);
676
955
  }
677
956
  }
678
957
  currentScope = currentScope.getParentScope();
679
958
  }
680
959
  //look through all files in scope for matches
681
960
  for (const file of scope.getOwnFiles()) {
682
- if ((0, reflection_1.isXmlFile)(file) || filesSearched.has(file)) {
961
+ //skip non-brs files, or files we've already processed
962
+ if (!(0, reflection_1.isBrsFile)(file) || filesSearched.has(file)) {
683
963
  continue;
684
964
  }
685
965
  filesSearched.add(file);
686
- for (const statement of file.parser.references.functionStatements) {
687
- if (funcNames.has(statement.name.text)) {
688
- if (!results.has(statement)) {
689
- results.set(statement, { item: statement, file: file });
966
+ file.ast.walk((0, visitors_1.createVisitor)({
967
+ FunctionStatement: (statement) => {
968
+ if (funcNames.has(statement.name.text)) {
969
+ if (!results.has(statement)) {
970
+ results.set(statement, { item: statement, file: file });
971
+ }
690
972
  }
691
973
  }
692
- }
974
+ }), {
975
+ walkMode: visitors_1.WalkMode.visitStatements
976
+ });
693
977
  }
694
978
  return [...results.values()];
695
979
  }
696
980
  /**
697
981
  * Find all available completion items at the given position
698
- * @param srcPath The absolute path to the source file on disk
699
- * @param lineIndex
700
- * @param columnIndex
982
+ * @param filePath can be a srcPath or a destPath
983
+ * @param position the position (line & column) where completions should be found
701
984
  */
702
- getCompletions(srcPath, position) {
703
- let file = this.getFile(srcPath);
985
+ getCompletions(filePath, position) {
986
+ let file = this.getFile(filePath);
704
987
  if (!file) {
705
988
  return [];
706
989
  }
@@ -724,19 +1007,18 @@ class Program {
724
1007
  * Goes through each file and builds a list of workspace symbols for the program. Used by LanguageServer's onWorkspaceSymbol functionality
725
1008
  */
726
1009
  getWorkspaceSymbols() {
727
- const result = [];
728
- for (const key in this.files) {
1010
+ const results = Object.keys(this.files).map(key => {
729
1011
  const file = this.files[key];
730
1012
  if ((0, reflection_1.isBrsFile)(file)) {
731
- result.push(...file.getWorkspaceSymbols());
1013
+ return file.getWorkspaceSymbols();
732
1014
  }
733
- }
734
- return result;
1015
+ return [];
1016
+ });
1017
+ return util_1.util.flatMap(results, c => c);
735
1018
  }
736
1019
  /**
737
1020
  * Given a position in a file, if the position is sitting on some type of identifier,
738
1021
  * go to the definition of that identifier (where this thing was first defined)
739
- * @param srcPath The absolute path to the source file on disk
740
1022
  */
741
1023
  getDefinition(srcPath, position) {
742
1024
  let file = this.getFile(srcPath);
@@ -757,7 +1039,6 @@ class Program {
757
1039
  }
758
1040
  /**
759
1041
  * Get hover information for a file and position
760
- * @param srcPath The absolute path to the source file on disk
761
1042
  */
762
1043
  getHover(srcPath, position) {
763
1044
  let file = this.getFile(srcPath);
@@ -779,7 +1060,6 @@ class Program {
779
1060
  }
780
1061
  /**
781
1062
  * Compute code actions for the given file and range
782
- * @param srcPath The absolute path to the source file on disk
783
1063
  */
784
1064
  getCodeActions(srcPath, range) {
785
1065
  const codeActions = [];
@@ -821,277 +1101,23 @@ class Program {
821
1101
  }
822
1102
  }
823
1103
  getSignatureHelp(filepath, position) {
824
- var _a;
825
1104
  let file = this.getFile(filepath);
826
1105
  if (!file || !(0, reflection_1.isBrsFile)(file)) {
827
1106
  return [];
828
1107
  }
829
- const results = new Map();
830
- let functionExpression = file.getFunctionExpressionAtPosition(position);
831
- let identifierInfo = this.getPartialStatementInfo(file, position);
832
- if (identifierInfo.statementType === '') {
833
- // just general function calls
834
- let statements = file.program.getStatementsByName(identifierInfo.name, file);
835
- for (let statement of statements) {
836
- //TODO better handling of collisions - if it's a namespace, then don't show any other overrides
837
- //if we're on m - then limit scope to the current class, if present
838
- let sigHelp = statement.file.getSignatureHelpForStatement(statement.item);
839
- if (sigHelp && !results.has[sigHelp.key]) {
840
- sigHelp.index = identifierInfo.commaCount;
841
- results.set(sigHelp.key, sigHelp);
842
- }
843
- }
844
- }
845
- else if (identifierInfo.statementType === '.') {
846
- //if m class reference.. then
847
- //only get statements from the class I am in..
848
- if (functionExpression) {
849
- const currentToken = file.parser.getTokenAt(position);
850
- for (let scope of this.getScopesForFile(file)) {
851
- scope.linkSymbolTable();
852
- let myClass = file.getClassFromToken(currentToken, functionExpression, scope);
853
- if (myClass) {
854
- let classes = scope.getClassHierarchy(myClass.item.getName(Parser_1.ParseMode.BrighterScript).toLowerCase());
855
- //and anything from any class in scope to a non m class
856
- for (let statement of [...classes].filter((i) => (0, reflection_1.isMethodStatement)(i.item))) {
857
- let sigHelp = statement.file.getSignatureHelpForStatement(statement.item);
858
- if (sigHelp && !results.has[sigHelp.key]) {
859
- results.set(sigHelp.key, sigHelp);
860
- return;
861
- }
862
- }
863
- }
864
- scope.unlinkSymbolTable();
865
- }
866
- }
867
- if (identifierInfo.dotPart) {
868
- //potential namespaces
869
- let statements = file.program.getStatementsByName(identifierInfo.name, file, identifierInfo.dotPart);
870
- if (statements.length === 0) {
871
- //was not a namespaced function, it could be any method on any class now
872
- statements = file.program.getStatementsByName(identifierInfo.name, file);
873
- }
874
- for (let statement of statements) {
875
- //TODO better handling of collisions - if it's a namespace, then don't show any other overrides
876
- //if we're on m - then limit scope to the current class, if present
877
- let sigHelp = statement.file.getSignatureHelpForStatement(statement.item);
878
- if (sigHelp && !results.has[sigHelp.key]) {
879
- sigHelp.index = identifierInfo.commaCount;
880
- results.set(sigHelp.key, sigHelp);
881
- }
882
- }
883
- }
884
- }
885
- else if (identifierInfo.statementType === '@.') {
886
- for (const scope of this.getScopes().filter((s) => (0, reflection_1.isXmlScope)(s))) {
887
- let fileLinks = this.getStatementsForXmlFile(scope, identifierInfo.name);
888
- for (let fileLink of fileLinks) {
889
- let sigHelp = fileLink.file.getSignatureHelpForStatement(fileLink.item);
890
- if (sigHelp && !results.has[sigHelp.key]) {
891
- sigHelp.index = identifierInfo.commaCount;
892
- results.set(sigHelp.key, sigHelp);
893
- }
894
- }
895
- }
896
- }
897
- else if (identifierInfo.statementType === 'new') {
898
- let classItem = file.getClassFileLink(identifierInfo.dotPart ? `${identifierInfo.dotPart}.${identifierInfo.name}` : identifierInfo.name);
899
- let sigHelp = (_a = classItem === null || classItem === void 0 ? void 0 : classItem.file) === null || _a === void 0 ? void 0 : _a.getClassSignatureHelp(classItem === null || classItem === void 0 ? void 0 : classItem.item);
900
- if (sigHelp && !results.has(sigHelp.key)) {
901
- sigHelp.index = identifierInfo.commaCount;
902
- results.set(sigHelp.key, sigHelp);
903
- }
904
- }
905
- return [...results.values()];
906
- }
907
- getPartialStatementInfo(file, position) {
908
- let lines = util_1.util.splitIntoLines(file.fileContents);
909
- let line = lines[position.line];
910
- let index = position.character;
911
- let itemCounts = this.getPartialItemCounts(line, index);
912
- if (!itemCounts.isArgStartFound && line.charAt(index) === ')') {
913
- //try previous char, in case we were on a close bracket..
914
- index--;
915
- itemCounts = this.getPartialItemCounts(line, index);
916
- }
917
- let argStartIndex = itemCounts.argStartIndex;
918
- index = itemCounts.argStartIndex - 1;
919
- let statementType = '';
920
- let name;
921
- let dotPart;
922
- if (!itemCounts.isArgStartFound) {
923
- //try to get sig help based on the name
924
- index = position.character;
925
- let currentToken = file.parser.getTokenAt(position);
926
- if (currentToken && currentToken.kind !== TokenKind_1.TokenKind.Comment) {
927
- name = file.getPartialVariableName(currentToken, [TokenKind_1.TokenKind.New]);
928
- if (!name) {
929
- //try the previous token, incase we're on a bracket
930
- currentToken = file.parser.getPreviousToken(currentToken);
931
- name = file.getPartialVariableName(currentToken, [TokenKind_1.TokenKind.New]);
932
- }
933
- if (name === null || name === void 0 ? void 0 : name.indexOf('.')) {
934
- let parts = name.split('.');
935
- name = parts[parts.length - 1];
936
- }
937
- index = currentToken.range.start.character;
938
- argStartIndex = index;
939
- }
940
- else {
941
- // invalid location
942
- index = 0;
943
- itemCounts.comma = 0;
944
- }
945
- }
946
- //this loop is quirky. walk to -1 (which will result in the last char being '' thus satisfying the situation where there is no leading whitespace).
947
- while (index >= -1) {
948
- if (!(/[a-z0-9_\.\@]/i).test(line.charAt(index))) {
949
- if (!name) {
950
- name = line.substring(index + 1, argStartIndex);
951
- }
952
- else {
953
- dotPart = line.substring(index + 1, argStartIndex);
954
- if (dotPart.endsWith('.')) {
955
- dotPart = dotPart.substr(0, dotPart.length - 1);
956
- }
957
- }
958
- break;
959
- }
960
- if (line.substr(index - 2, 2) === '@.') {
961
- statementType = '@.';
962
- name = name || line.substring(index, argStartIndex);
963
- break;
964
- }
965
- else if (line.charAt(index - 1) === '.' && statementType === '') {
966
- statementType = '.';
967
- name = name || line.substring(index, argStartIndex);
968
- argStartIndex = index;
969
- }
970
- index--;
971
- }
972
- if (line.substring(0, index).trim().endsWith('new')) {
973
- statementType = 'new';
974
- }
975
- return {
976
- commaCount: itemCounts.comma,
977
- statementType: statementType,
978
- name: name,
979
- dotPart: dotPart
980
- };
981
- }
982
- getPartialItemCounts(line, index) {
983
- let isArgStartFound = false;
984
- let itemCounts = {
985
- normal: 0,
986
- square: 0,
987
- curly: 0,
988
- comma: 0,
989
- endIndex: 0,
990
- argStartIndex: index,
991
- isArgStartFound: false
992
- };
993
- while (index >= 0) {
994
- const currentChar = line.charAt(index);
995
- if (currentChar === '\'') { //found comment, invalid index
996
- itemCounts.isArgStartFound = false;
997
- break;
998
- }
999
- if (isArgStartFound) {
1000
- if (currentChar !== ' ') {
1001
- break;
1002
- }
1003
- }
1004
- else {
1005
- if (currentChar === ')') {
1006
- itemCounts.normal++;
1007
- }
1008
- if (currentChar === ']') {
1009
- itemCounts.square++;
1010
- }
1011
- if (currentChar === '}') {
1012
- itemCounts.curly++;
1013
- }
1014
- if (currentChar === ',' && itemCounts.normal <= 0 && itemCounts.curly <= 0 && itemCounts.square <= 0) {
1015
- itemCounts.comma++;
1016
- }
1017
- if (currentChar === '(') {
1018
- if (itemCounts.normal === 0) {
1019
- itemCounts.isArgStartFound = true;
1020
- itemCounts.argStartIndex = index;
1021
- }
1022
- else {
1023
- itemCounts.normal--;
1024
- }
1025
- }
1026
- if (currentChar === '[') {
1027
- itemCounts.square--;
1028
- }
1029
- if (currentChar === '{') {
1030
- itemCounts.curly--;
1031
- }
1032
- }
1033
- index--;
1034
- }
1035
- return itemCounts;
1108
+ let callExpressionInfo = new CallExpressionInfo_1.CallExpressionInfo(file, position);
1109
+ let signatureHelpUtil = new SignatureHelpUtil_1.SignatureHelpUtil();
1110
+ return signatureHelpUtil.getSignatureHelpItems(callExpressionInfo);
1036
1111
  }
1037
- /**
1038
- * @param srcPath The absolute path to the source file on disk
1039
- */
1040
1112
  getReferences(srcPath, position) {
1041
1113
  //find the file
1042
1114
  let file = this.getFile(srcPath);
1043
- if (!file) {
1044
- return null;
1115
+ if ((0, reflection_1.isBrsFile)(file) || (0, reflection_1.isXmlFile)(file)) {
1116
+ return file.getReferences(position);
1045
1117
  }
1046
- return file.getReferences(position);
1047
- }
1048
- /**
1049
- * Get a list of all script imports, relative to the specified pkgPath
1050
- * @param sourcePkgPath - the pkgPath of the source that wants to resolve script imports.
1051
- */
1052
- getScriptImportCompletions(sourcePkgPath, scriptImport) {
1053
- let lowerSourcePkgPath = sourcePkgPath.toLowerCase();
1054
- let result = [];
1055
- /**
1056
- * hashtable to prevent duplicate results
1057
- */
1058
- let resultPkgPaths = {};
1059
- //restrict to only .brs files
1060
- for (const key in this.files) {
1061
- const file = this.files[key];
1062
- if (
1063
- //is a BrightScript or BrighterScript file
1064
- (file.extension === '.bs' || file.extension === '.brs') &&
1065
- //this file is not the current file
1066
- lowerSourcePkgPath !== file.pkgPath.toLowerCase()) {
1067
- //add the relative path
1068
- let relativePath = util_1.util.getRelativePath(sourcePkgPath, file.pkgPath).replace(/\\/g, '/');
1069
- const lowerPkgPath = file.pkgPath.toLowerCase();
1070
- if (!resultPkgPaths[lowerPkgPath]) {
1071
- resultPkgPaths[lowerPkgPath] = true;
1072
- result.push({
1073
- label: relativePath,
1074
- detail: file.srcPath,
1075
- kind: vscode_languageserver_1.CompletionItemKind.File,
1076
- textEdit: {
1077
- newText: relativePath,
1078
- range: scriptImport.filePathRange
1079
- }
1080
- });
1081
- //add the absolute path
1082
- result.push({
1083
- label: file.pkgPath,
1084
- detail: file.srcPath,
1085
- kind: vscode_languageserver_1.CompletionItemKind.File,
1086
- textEdit: {
1087
- newText: file.pkgPath,
1088
- range: scriptImport.filePathRange
1089
- }
1090
- });
1091
- }
1092
- }
1118
+ else {
1119
+ return null;
1093
1120
  }
1094
- return result;
1095
1121
  }
1096
1122
  /**
1097
1123
  * Transpile a single file and get the result as a string.
@@ -1102,160 +1128,215 @@ class Program {
1102
1128
  * @param filePath can be a srcPath or a destPath
1103
1129
  */
1104
1130
  async getTranspiledFileContents(filePath) {
1105
- const { entries, astEditor } = this.beforeProgramTranspile(await roku_deploy_1.rokuDeploy.getFilePaths(this.options.files, this.options.rootDir), this.options.stagingFolderPath);
1106
- const result = this._getTranspiledFileContents(this.getFile(filePath));
1107
- this.afterProgramTranspile(entries, astEditor);
1131
+ const file = this.getFile(filePath);
1132
+ return this.getTranspiledFileContentsPipeline.run(async () => {
1133
+ const result = {
1134
+ destPath: file.destPath,
1135
+ pkgPath: file.pkgPath,
1136
+ srcPath: file.srcPath
1137
+ };
1138
+ const expectedPkgPath = file.pkgPath.toLowerCase();
1139
+ const expectedMapPath = `${expectedPkgPath}.map`;
1140
+ const expectedTypedefPkgPath = expectedPkgPath.replace(/\.brs$/i, '.d.bs');
1141
+ //add a temporary plugin to tap into the file writing process
1142
+ const plugin = this.plugins.addFirst({
1143
+ name: 'getTranspiledFileContents',
1144
+ beforeWriteFile: (event) => {
1145
+ const pkgPath = event.file.pkgPath.toLowerCase();
1146
+ switch (pkgPath) {
1147
+ //this is the actual transpiled file
1148
+ case expectedPkgPath:
1149
+ result.code = event.file.data.toString();
1150
+ break;
1151
+ //this is the sourcemap
1152
+ case expectedMapPath:
1153
+ result.map = event.file.data.toString();
1154
+ break;
1155
+ //this is the typedef
1156
+ case expectedTypedefPkgPath:
1157
+ result.typedef = event.file.data.toString();
1158
+ break;
1159
+ default:
1160
+ //no idea what this file is. just ignore it
1161
+ }
1162
+ //mark every file as processed so it they don't get written to the output directory
1163
+ event.processedFiles.add(event.file);
1164
+ }
1165
+ });
1166
+ try {
1167
+ //now that the plugin has been registered, run the build with just this file
1168
+ await this.build({
1169
+ files: [file]
1170
+ });
1171
+ }
1172
+ finally {
1173
+ this.plugins.remove(plugin);
1174
+ }
1175
+ return result;
1176
+ });
1177
+ }
1178
+ /**
1179
+ * Get the absolute output path for a file
1180
+ */
1181
+ getOutputPath(file, stagingDir = this.getStagingDir()) {
1182
+ return (0, util_1.standardizePath) `${stagingDir}/${file.pkgPath}`;
1183
+ }
1184
+ getStagingDir(stagingDir) {
1185
+ var _a, _b;
1186
+ let result = (_a = stagingDir !== null && stagingDir !== void 0 ? stagingDir : this.options.stagingDir) !== null && _a !== void 0 ? _a : this.options.stagingDir;
1187
+ if (!result) {
1188
+ result = roku_deploy_1.rokuDeploy.getOptions(this.options).stagingDir;
1189
+ }
1190
+ result = (0, util_1.standardizePath) `${path.resolve((_b = this.options.cwd) !== null && _b !== void 0 ? _b : process.cwd(), result !== null && result !== void 0 ? result : '/')}`;
1108
1191
  return result;
1109
1192
  }
1110
1193
  /**
1111
- * Internal function used to transpile files.
1112
- * This does not write anything to the file system
1194
+ * Prepare the program for building
1195
+ * @param files the list of files that should be prepared
1113
1196
  */
1114
- _getTranspiledFileContents(file, outputPath) {
1115
- const editor = new AstEditor_1.AstEditor();
1116
- this.plugins.emit('beforeFileTranspile', {
1197
+ async prepare(files) {
1198
+ const programEvent = {
1117
1199
  program: this,
1118
- file: file,
1119
- outputPath: outputPath,
1120
- editor: editor
1200
+ editor: this.editor,
1201
+ files: files
1202
+ };
1203
+ //assign an editor to every file
1204
+ for (const file of files) {
1205
+ //if the file doesn't have an editor yet, assign one now
1206
+ if (!file.editor) {
1207
+ file.editor = new Editor_1.Editor();
1208
+ }
1209
+ }
1210
+ files.sort((a, b) => {
1211
+ if (a.pkgPath < b.pkgPath) {
1212
+ return -1;
1213
+ }
1214
+ else if (a.pkgPath > b.pkgPath) {
1215
+ return 1;
1216
+ }
1217
+ else {
1218
+ return 1;
1219
+ }
1121
1220
  });
1122
- //if we have any edits, assume the file needs to be transpiled
1123
- if (editor.hasChanges) {
1124
- //use the `editor` because it'll track the previous value for us and revert later on
1125
- editor.setProperty(file, 'needsTranspiled', true);
1126
- }
1127
- //transpile the file
1128
- const result = file.transpile();
1129
- //generate the typedef if enabled
1130
- let typedef;
1131
- if ((0, reflection_1.isBrsFile)(file) && this.options.emitDefinitions) {
1132
- typedef = file.getTypedef();
1221
+ await this.plugins.emitAsync('beforePrepareProgram', programEvent);
1222
+ await this.plugins.emitAsync('prepareProgram', programEvent);
1223
+ const stagingDir = this.getStagingDir();
1224
+ const entries = [];
1225
+ for (const file of files) {
1226
+ //if the file doesn't have an editor yet, assign one now
1227
+ if (!file.editor) {
1228
+ file.editor = new Editor_1.Editor();
1229
+ }
1230
+ const event = {
1231
+ program: this,
1232
+ file: file,
1233
+ editor: file.editor,
1234
+ outputPath: this.getOutputPath(file, stagingDir)
1235
+ };
1236
+ await this.plugins.emitAsync('beforePrepareFile', event);
1237
+ await this.plugins.emitAsync('prepareFile', event);
1238
+ await this.plugins.emitAsync('afterPrepareFile', event);
1239
+ //TODO remove this in v1
1240
+ entries.push(event);
1133
1241
  }
1134
- const event = {
1135
- program: this,
1136
- file: file,
1137
- outputPath: outputPath,
1138
- editor: editor,
1139
- code: result.code,
1140
- map: result.map,
1141
- typedef: typedef
1142
- };
1143
- this.plugins.emit('afterFileTranspile', event);
1144
- //undo all `editor` edits that may have been applied to this file.
1145
- editor.undoAll();
1146
- return {
1147
- srcPath: file.srcPath,
1148
- pkgPath: file.pkgPath,
1149
- code: event.code,
1150
- map: event.map,
1151
- typedef: event.typedef
1152
- };
1242
+ await this.plugins.emitAsync('afterPrepareProgram', programEvent);
1243
+ return files;
1153
1244
  }
1154
- beforeProgramTranspile(fileEntries, stagingFolderPath) {
1155
- // map fileEntries using their path as key, to avoid excessive "find()" operations
1156
- const mappedFileEntries = fileEntries.reduce((collection, entry) => {
1157
- collection[(0, util_1.standardizePath) `${entry.src}`] = entry;
1158
- return collection;
1159
- }, {});
1160
- const getOutputPath = (file) => {
1161
- let filePathObj = mappedFileEntries[(0, util_1.standardizePath) `${file.srcPath}`];
1162
- if (!filePathObj) {
1163
- //this file has been added in-memory, from a plugin, for example
1164
- filePathObj = {
1165
- //add an interpolated src path (since it doesn't actually exist in memory)
1166
- src: `bsc-in-memory:/${util_1.util.removeProtocol(file.pkgPath)}`,
1167
- dest: file.pkgPath
1168
- };
1169
- }
1170
- //prep the output path
1171
- let outputPath = filePathObj.dest
1172
- //replace any leading protocol
1173
- .replace(/^[-a-z_]+:\//, '')
1174
- //change any .bs file extension to .brs
1175
- .replace(/\.bs$/gi, '.brs');
1176
- //prepend the staging folder path
1177
- outputPath = (0, util_1.standardizePath) `${stagingFolderPath}/${outputPath}`;
1178
- return outputPath;
1179
- };
1180
- const entries = Object.values(this.files).map(file => {
1181
- return {
1245
+ /**
1246
+ * Generate the contents of every file
1247
+ */
1248
+ async serialize(files) {
1249
+ const allFiles = new Map();
1250
+ //exclude prunable files if that option is enabled
1251
+ if (this.options.pruneEmptyCodeFiles === true) {
1252
+ files = files.filter(x => x.canBePruned !== true);
1253
+ }
1254
+ const serializeProgramEvent = await this.plugins.emitAsync('beforeSerializeProgram', {
1255
+ program: this,
1256
+ files: files,
1257
+ result: allFiles
1258
+ });
1259
+ await this.plugins.emitAsync('onSerializeProgram', {
1260
+ program: this,
1261
+ files: files,
1262
+ result: allFiles
1263
+ });
1264
+ //sort the entries to make transpiling more deterministic
1265
+ files = serializeProgramEvent.files.sort((a, b) => {
1266
+ return a.srcPath < b.srcPath ? -1 : 1;
1267
+ });
1268
+ // serialize each file
1269
+ for (const file of files) {
1270
+ const event = {
1271
+ program: this,
1182
1272
  file: file,
1183
- outputPath: getOutputPath(file)
1273
+ result: allFiles
1184
1274
  };
1275
+ await this.plugins.emitAsync('beforeSerializeFile', event);
1276
+ await this.plugins.emitAsync('serializeFile', event);
1277
+ await this.plugins.emitAsync('afterSerializeFile', event);
1278
+ }
1279
+ this.plugins.emit('afterSerializeProgram', {
1280
+ program: this,
1281
+ files: files,
1282
+ result: allFiles
1185
1283
  });
1186
- const astEditor = new AstEditor_1.AstEditor();
1187
- this.plugins.emit('beforeProgramTranspile', {
1284
+ return allFiles;
1285
+ }
1286
+ /**
1287
+ * Write the entire project to disk
1288
+ */
1289
+ async write(stagingDir, files) {
1290
+ const programEvent = await this.plugins.emitAsync('beforeWriteProgram', {
1188
1291
  program: this,
1189
- entries: entries,
1190
- editor: astEditor
1292
+ files: files,
1293
+ stagingDir: stagingDir
1191
1294
  });
1192
- return {
1193
- entries: entries,
1194
- getOutputPath: getOutputPath,
1195
- astEditor: astEditor
1196
- };
1295
+ //empty the staging directory
1296
+ await fsExtra.emptyDir(stagingDir);
1297
+ const serializedFiles = [...files]
1298
+ .map(([, serializedFiles]) => serializedFiles)
1299
+ .flat();
1300
+ //write all the files to disk (asynchronously)
1301
+ await Promise.all(serializedFiles.map(async (file) => {
1302
+ const event = await this.plugins.emitAsync('beforeWriteFile', {
1303
+ program: this,
1304
+ file: file,
1305
+ outputPath: this.getOutputPath(file, stagingDir),
1306
+ processedFiles: new Set()
1307
+ });
1308
+ await this.plugins.emitAsync('writeFile', event);
1309
+ await this.plugins.emitAsync('afterWriteFile', event);
1310
+ }));
1311
+ await this.plugins.emitAsync('afterWriteProgram', programEvent);
1197
1312
  }
1198
- async transpile(fileEntries, stagingFolderPath) {
1199
- const { entries, getOutputPath, astEditor } = this.beforeProgramTranspile(fileEntries, stagingFolderPath);
1200
- const processedFiles = new Set();
1201
- const transpileFile = async (srcPath, outputPath) => {
1202
- //find the file in the program
1203
- const file = this.getFile(srcPath);
1204
- //mark this file as processed so we don't process it more than once
1205
- processedFiles.add(file);
1206
- //skip transpiling typedef files
1207
- if ((0, reflection_1.isBrsFile)(file) && file.isTypedef) {
1208
- return;
1209
- }
1210
- const fileTranspileResult = this._getTranspiledFileContents(file, outputPath);
1211
- //make sure the full dir path exists
1212
- await fsExtra.ensureDir(path.dirname(outputPath));
1213
- if (await fsExtra.pathExists(outputPath)) {
1214
- throw new Error(`Error while transpiling "${file.srcPath}". A file already exists at "${outputPath}" and will not be overwritten.`);
1215
- }
1216
- const writeMapPromise = fileTranspileResult.map ? fsExtra.writeFile(`${outputPath}.map`, fileTranspileResult.map.toString()) : null;
1217
- await Promise.all([
1218
- fsExtra.writeFile(outputPath, fileTranspileResult.code),
1219
- writeMapPromise
1220
- ]);
1221
- if (fileTranspileResult.typedef) {
1222
- const typedefPath = outputPath.replace(/\.brs$/i, '.d.bs');
1223
- await fsExtra.writeFile(typedefPath, fileTranspileResult.typedef);
1224
- }
1225
- };
1226
- let promises = entries.map(async (entry) => {
1313
+ /**
1314
+ * Build the project. This transpiles/transforms/copies all files and moves them to the staging directory
1315
+ * @param options the list of options used to build the program
1316
+ */
1317
+ async build(options) {
1318
+ //run a single build at a time
1319
+ await this.buildPipeline.run(async () => {
1227
1320
  var _a;
1228
- return transpileFile((_a = entry === null || entry === void 0 ? void 0 : entry.file) === null || _a === void 0 ? void 0 : _a.srcPath, entry.outputPath);
1229
- });
1230
- //if there's no bslib file already loaded into the program, copy it to the staging directory
1231
- if (!this.getFile(bslibAliasedRokuModulesPkgPath) && !this.getFile((0, util_1.standardizePath) `pkg:/source/bslib.brs`)) {
1232
- promises.push(util_1.util.copyBslibToStaging(stagingFolderPath));
1233
- }
1234
- await Promise.all(promises);
1235
- //transpile any new files that plugins added since the start of this transpile process
1236
- do {
1237
- promises = [];
1238
- for (const key in this.files) {
1239
- const file = this.files[key];
1240
- //this is a new file
1241
- if (!processedFiles.has(file)) {
1242
- promises.push(transpileFile(file === null || file === void 0 ? void 0 : file.srcPath, getOutputPath(file)));
1243
- }
1244
- }
1245
- if (promises.length > 0) {
1246
- this.logger.info(`Transpiling ${promises.length} new files`);
1247
- await Promise.all(promises);
1321
+ const stagingDir = this.getStagingDir(options === null || options === void 0 ? void 0 : options.stagingDir);
1322
+ const event = await this.plugins.emitAsync('beforeBuildProgram', {
1323
+ program: this,
1324
+ editor: this.editor,
1325
+ files: (_a = options === null || options === void 0 ? void 0 : options.files) !== null && _a !== void 0 ? _a : Object.values(this.files)
1326
+ });
1327
+ //prepare the program (and files) for building
1328
+ event.files = await this.prepare(event.files);
1329
+ //stage the entire program
1330
+ const serializedFilesByFile = await this.serialize(event.files);
1331
+ await this.write(stagingDir, serializedFilesByFile);
1332
+ await this.plugins.emitAsync('afterBuildProgram', event);
1333
+ //undo all edits for the program
1334
+ this.editor.undoAll();
1335
+ //undo all edits for each file
1336
+ for (const file of event.files) {
1337
+ file.editor.undoAll();
1248
1338
  }
1249
- } while (promises.length > 0);
1250
- this.afterProgramTranspile(entries, astEditor);
1251
- }
1252
- afterProgramTranspile(entries, astEditor) {
1253
- this.plugins.emit('afterProgramTranspile', {
1254
- program: this,
1255
- entries: entries,
1256
- editor: astEditor
1257
1339
  });
1258
- astEditor.undoAll();
1259
1340
  }
1260
1341
  /**
1261
1342
  * Find a list of files in the program that have a function with the given name (case INsensitive)
@@ -1268,7 +1349,8 @@ class Program {
1268
1349
  if ((0, reflection_1.isBrsFile)(file)) {
1269
1350
  //TODO handle namespace-relative function calls
1270
1351
  //if the file has a function with this name
1271
- if (file.parser.references.functionStatementLookup.get(lowerFunctionName) !== undefined) {
1352
+ // eslint-disable-next-line @typescript-eslint/dot-notation
1353
+ if (file['_cachedLookups'].functionStatementMap.get(lowerFunctionName)) {
1272
1354
  files.push(file);
1273
1355
  }
1274
1356
  }
@@ -1286,7 +1368,8 @@ class Program {
1286
1368
  if ((0, reflection_1.isBrsFile)(file)) {
1287
1369
  //TODO handle namespace-relative classes
1288
1370
  //if the file has a function with this name
1289
- if (file.parser.references.classStatementLookup.get(lowerClassName) !== undefined) {
1371
+ // eslint-disable-next-line @typescript-eslint/dot-notation
1372
+ if (file['_cachedLookups'].classStatementMap.get(lowerClassName) !== undefined) {
1290
1373
  files.push(file);
1291
1374
  }
1292
1375
  }
@@ -1299,7 +1382,8 @@ class Program {
1299
1382
  //find every file with this class defined
1300
1383
  for (const file of Object.values(this.files)) {
1301
1384
  if ((0, reflection_1.isBrsFile)(file)) {
1302
- if (file.parser.references.namespaceStatements.find((x) => {
1385
+ // eslint-disable-next-line @typescript-eslint/dot-notation
1386
+ if (file['_cachedLookups'].namespaceStatements.find((x) => {
1303
1387
  const namespaceName = x.name.toLowerCase();
1304
1388
  return (
1305
1389
  //the namespace name matches exactly
@@ -1316,48 +1400,100 @@ class Program {
1316
1400
  findFilesForEnum(name) {
1317
1401
  const files = [];
1318
1402
  const lowerName = name.toLowerCase();
1319
- //find every file with this class defined
1403
+ //find every file with this enum defined
1320
1404
  for (const file of Object.values(this.files)) {
1321
1405
  if ((0, reflection_1.isBrsFile)(file)) {
1322
- if (file.parser.references.enumStatementLookup.get(lowerName)) {
1406
+ // eslint-disable-next-line @typescript-eslint/dot-notation
1407
+ if (file['_cachedLookups'].enumStatementMap.get(lowerName)) {
1323
1408
  files.push(file);
1324
1409
  }
1325
1410
  }
1326
1411
  }
1327
1412
  return files;
1328
1413
  }
1414
+ /**
1415
+ * Modify a parsed manifest map by reading `bs_const` and injecting values from `options.manifest.bs_const`
1416
+ * @param parsedManifest The manifest map to read from and modify
1417
+ */
1418
+ buildBsConstsIntoParsedManifest(parsedManifest) {
1419
+ var _a, _b;
1420
+ // Lift the bs_consts defined in the manifest
1421
+ let bsConsts = (0, Manifest_1.getBsConst)(parsedManifest, false);
1422
+ // Override or delete any bs_consts defined in the bs config
1423
+ 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) {
1424
+ const value = this.options.manifest.bs_const[key];
1425
+ if (value === null) {
1426
+ bsConsts.delete(key);
1427
+ }
1428
+ else {
1429
+ bsConsts.set(key, value);
1430
+ }
1431
+ }
1432
+ // convert the new list of bs consts back into a string for the rest of the down stream systems to use
1433
+ let constString = '';
1434
+ for (const [key, value] of bsConsts) {
1435
+ constString += `${constString !== '' ? ';' : ''}${key}=${value.toString()}`;
1436
+ }
1437
+ // Set the updated bs_const value
1438
+ parsedManifest.set('bs_const', constString);
1439
+ }
1440
+ /**
1441
+ * Try to find and load the manifest into memory
1442
+ * @param manifestFileObj A pointer to a potential manifest file object found during loading
1443
+ * @param replaceIfAlreadyLoaded should we overwrite the internal `_manifest` if it already exists
1444
+ */
1445
+ loadManifest(manifestFileObj, replaceIfAlreadyLoaded = true) {
1446
+ //if we already have a manifest instance, and should not replace...then don't replace
1447
+ if (!replaceIfAlreadyLoaded && this._manifest) {
1448
+ return;
1449
+ }
1450
+ let manifestPath = manifestFileObj
1451
+ ? manifestFileObj.src
1452
+ : path.join(this.options.rootDir, 'manifest');
1453
+ try {
1454
+ // we only load this manifest once, so do it sync to improve speed downstream
1455
+ const contents = fsExtra.readFileSync(manifestPath, 'utf-8');
1456
+ const parsedManifest = (0, Manifest_1.parseManifest)(contents);
1457
+ this.buildBsConstsIntoParsedManifest(parsedManifest);
1458
+ this._manifest = parsedManifest;
1459
+ }
1460
+ catch (e) {
1461
+ this._manifest = new Map();
1462
+ }
1463
+ }
1329
1464
  /**
1330
1465
  * Get a map of the manifest information
1331
1466
  */
1332
1467
  getManifest() {
1333
1468
  if (!this._manifest) {
1334
- //load the manifest file.
1335
- //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
1336
- let manifestPath = path.join(this.options.rootDir, 'manifest');
1337
- let contents;
1338
- try {
1339
- //we only load this manifest once, so do it sync to improve speed downstream
1340
- contents = fsExtra.readFileSync(manifestPath, 'utf-8');
1341
- this._manifest = (0, Manifest_1.parseManifest)(contents);
1342
- }
1343
- catch (err) {
1344
- this._manifest = new Map();
1345
- }
1469
+ this.loadManifest();
1346
1470
  }
1347
1471
  return this._manifest;
1348
1472
  }
1349
1473
  dispose() {
1350
- var _a, _b;
1351
- for (const key in this.files) {
1352
- const file = this.files[key];
1353
- (_a = file.dispose) === null || _a === void 0 ? void 0 : _a.call(file);
1474
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1475
+ this.plugins.emit('beforeProgramDispose', { program: this });
1476
+ for (let filePath in this.files) {
1477
+ (_b = (_a = this.files[filePath]) === null || _a === void 0 ? void 0 : _a.dispose) === null || _b === void 0 ? void 0 : _b.call(_a);
1354
1478
  }
1355
1479
  for (let name in this.scopes) {
1356
- (_b = this.scopes[name]) === null || _b === void 0 ? void 0 : _b.dispose();
1480
+ (_d = (_c = this.scopes[name]) === null || _c === void 0 ? void 0 : _c.dispose) === null || _d === void 0 ? void 0 : _d.call(_c);
1357
1481
  }
1358
- this.globalScope.dispose();
1359
- this.dependencyGraph.dispose();
1482
+ (_f = (_e = this.globalScope) === null || _e === void 0 ? void 0 : _e.dispose) === null || _f === void 0 ? void 0 : _f.call(_e);
1483
+ (_h = (_g = this.dependencyGraph) === null || _g === void 0 ? void 0 : _g.dispose) === null || _h === void 0 ? void 0 : _h.call(_g);
1360
1484
  }
1361
1485
  }
1362
1486
  exports.Program = Program;
1487
+ class ProvideFileEventInternal {
1488
+ constructor(program, srcPath, destPath, data, fileFactory) {
1489
+ var _a;
1490
+ this.program = program;
1491
+ this.srcPath = srcPath;
1492
+ this.destPath = destPath;
1493
+ this.data = data;
1494
+ this.fileFactory = fileFactory;
1495
+ this.files = [];
1496
+ this.srcExtension = (_a = path.extname(srcPath)) === null || _a === void 0 ? void 0 : _a.toLowerCase();
1497
+ }
1498
+ }
1363
1499
  //# sourceMappingURL=Program.js.map