brighterscript 1.0.0-alpha.23 → 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 (536) hide show
  1. package/CHANGELOG.md +585 -218
  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 +79 -15
  33. package/dist/DiagnosticMessages.js +134 -21
  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 +222 -87
  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 +162 -81
  54. package/dist/Program.js +903 -732
  55. package/dist/Program.js.map +1 -1
  56. package/dist/ProgramBuilder.d.ts +22 -12
  57. package/dist/ProgramBuilder.js +132 -104
  58. package/dist/ProgramBuilder.js.map +1 -1
  59. package/dist/Scope.d.ts +95 -134
  60. package/dist/Scope.js +477 -551
  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 +95 -29
  65. package/dist/SymbolTable.js +256 -102
  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 -68
  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 -100
  90. package/dist/astUtils/reflection.js +225 -166
  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 +54 -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 +12 -2
  105. package/dist/bscPlugin/BscPlugin.js +41 -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.d.ts +1 -1
  117. package/dist/bscPlugin/codeActions/CodeActionsProcessor.js +21 -12
  118. package/dist/bscPlugin/codeActions/CodeActionsProcessor.js.map +1 -1
  119. package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js +86 -12
  120. package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js.map +1 -1
  121. package/dist/bscPlugin/completions/CompletionsProcessor.d.ts +57 -0
  122. package/dist/bscPlugin/completions/CompletionsProcessor.js +544 -0
  123. package/dist/bscPlugin/completions/CompletionsProcessor.js.map +1 -0
  124. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js +1909 -0
  125. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js.map +1 -0
  126. package/dist/bscPlugin/fileProviders/FileProvider.d.ts +9 -0
  127. package/dist/bscPlugin/fileProviders/FileProvider.js +51 -0
  128. package/dist/bscPlugin/fileProviders/FileProvider.js.map +1 -0
  129. package/dist/bscPlugin/hover/HoverProcessor.d.ts +17 -0
  130. package/dist/bscPlugin/hover/HoverProcessor.js +188 -0
  131. package/dist/bscPlugin/hover/HoverProcessor.js.map +1 -0
  132. package/dist/bscPlugin/hover/HoverProcessor.spec.js +513 -0
  133. package/dist/bscPlugin/hover/HoverProcessor.spec.js.map +1 -0
  134. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.d.ts +3 -1
  135. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js +102 -29
  136. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js.map +1 -1
  137. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js +167 -6
  138. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js.map +1 -1
  139. package/dist/bscPlugin/serialize/BslibInjector.spec.d.ts +1 -0
  140. package/dist/bscPlugin/serialize/BslibInjector.spec.js +19 -0
  141. package/dist/bscPlugin/serialize/BslibInjector.spec.js.map +1 -0
  142. package/dist/bscPlugin/serialize/BslibManager.d.ts +9 -0
  143. package/dist/bscPlugin/serialize/BslibManager.js +40 -0
  144. package/dist/bscPlugin/serialize/BslibManager.js.map +1 -0
  145. package/dist/bscPlugin/serialize/FileSerializer.d.ts +9 -0
  146. package/dist/bscPlugin/serialize/FileSerializer.js +72 -0
  147. package/dist/bscPlugin/serialize/FileSerializer.js.map +1 -0
  148. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.d.ts +16 -0
  149. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.js +123 -0
  150. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.js.map +1 -0
  151. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.d.ts +1 -0
  152. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.js +41 -0
  153. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.js.map +1 -0
  154. package/dist/bscPlugin/transpile/XmlFilePreTranspileProcessor.d.ts +12 -0
  155. package/dist/bscPlugin/transpile/XmlFilePreTranspileProcessor.js +99 -0
  156. package/dist/bscPlugin/transpile/XmlFilePreTranspileProcessor.js.map +1 -0
  157. package/dist/bscPlugin/validation/BrsFileValidator.d.ts +22 -1
  158. package/dist/bscPlugin/validation/BrsFileValidator.js +316 -29
  159. package/dist/bscPlugin/validation/BrsFileValidator.js.map +1 -1
  160. package/dist/bscPlugin/validation/BrsFileValidator.spec.d.ts +1 -0
  161. package/dist/bscPlugin/validation/BrsFileValidator.spec.js +264 -0
  162. package/dist/bscPlugin/validation/BrsFileValidator.spec.js.map +1 -0
  163. package/dist/bscPlugin/validation/ProgramValidator.d.ts +10 -0
  164. package/dist/bscPlugin/validation/ProgramValidator.js +32 -0
  165. package/dist/bscPlugin/validation/ProgramValidator.js.map +1 -0
  166. package/dist/bscPlugin/validation/ScopeValidator.d.ts +56 -8
  167. package/dist/bscPlugin/validation/ScopeValidator.js +514 -116
  168. package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
  169. package/dist/bscPlugin/validation/ScopeValidator.spec.d.ts +1 -0
  170. package/dist/bscPlugin/validation/ScopeValidator.spec.js +2454 -0
  171. package/dist/bscPlugin/validation/ScopeValidator.spec.js.map +1 -0
  172. package/dist/bscPlugin/validation/XmlFileValidator.d.ts +8 -0
  173. package/dist/bscPlugin/validation/XmlFileValidator.js +44 -0
  174. package/dist/bscPlugin/validation/XmlFileValidator.js.map +1 -0
  175. package/dist/cli.js +107 -8
  176. package/dist/cli.js.map +1 -1
  177. package/dist/deferred.d.ts +3 -3
  178. package/dist/deferred.js.map +1 -1
  179. package/dist/diagnosticUtils.d.ts +8 -2
  180. package/dist/diagnosticUtils.js +47 -17
  181. package/dist/diagnosticUtils.js.map +1 -1
  182. package/dist/examples/plugins/removePrint.js +8 -10
  183. package/dist/examples/plugins/removePrint.js.map +1 -1
  184. package/dist/files/AssetFile.d.ts +26 -0
  185. package/dist/files/AssetFile.js +26 -0
  186. package/dist/files/AssetFile.js.map +1 -0
  187. package/dist/files/BrsFile.Class.spec.js +529 -486
  188. package/dist/files/BrsFile.Class.spec.js.map +1 -1
  189. package/dist/files/BrsFile.d.ts +124 -112
  190. package/dist/files/BrsFile.js +819 -1131
  191. package/dist/files/BrsFile.js.map +1 -1
  192. package/dist/files/BrsFile.spec.js +1869 -1277
  193. package/dist/files/BrsFile.spec.js.map +1 -1
  194. package/dist/files/BscFile.d.ts +104 -0
  195. package/dist/files/BscFile.js +16 -0
  196. package/dist/files/BscFile.js.map +1 -0
  197. package/dist/files/Factory.d.ts +25 -0
  198. package/dist/files/Factory.js +22 -0
  199. package/dist/files/Factory.js.map +1 -0
  200. package/dist/files/LazyFileData.d.ts +20 -0
  201. package/dist/files/LazyFileData.js +54 -0
  202. package/dist/files/LazyFileData.js.map +1 -0
  203. package/dist/files/LazyFileData.spec.d.ts +1 -0
  204. package/dist/files/LazyFileData.spec.js +27 -0
  205. package/dist/files/LazyFileData.spec.js.map +1 -0
  206. package/dist/files/XmlFile.d.ts +70 -32
  207. package/dist/files/XmlFile.js +106 -117
  208. package/dist/files/XmlFile.js.map +1 -1
  209. package/dist/files/XmlFile.spec.js +325 -262
  210. package/dist/files/XmlFile.spec.js.map +1 -1
  211. package/dist/files/tests/imports.spec.js +49 -41
  212. package/dist/files/tests/imports.spec.js.map +1 -1
  213. package/dist/files/tests/optionalChaning.spec.js +104 -40
  214. package/dist/files/tests/optionalChaning.spec.js.map +1 -1
  215. package/dist/globalCallables.js +16 -18
  216. package/dist/globalCallables.js.map +1 -1
  217. package/dist/index.d.ts +13 -2
  218. package/dist/index.js +15 -2
  219. package/dist/index.js.map +1 -1
  220. package/dist/interfaces.d.ts +440 -150
  221. package/dist/interfaces.js +27 -0
  222. package/dist/interfaces.js.map +1 -1
  223. package/dist/lexer/Character.spec.js +5 -5
  224. package/dist/lexer/Character.spec.js.map +1 -1
  225. package/dist/lexer/Lexer.d.ts +12 -5
  226. package/dist/lexer/Lexer.js +28 -13
  227. package/dist/lexer/Lexer.js.map +1 -1
  228. package/dist/lexer/Lexer.spec.js +187 -134
  229. package/dist/lexer/Lexer.spec.js.map +1 -1
  230. package/dist/lexer/Token.d.ts +9 -1
  231. package/dist/lexer/Token.js +9 -1
  232. package/dist/lexer/Token.js.map +1 -1
  233. package/dist/lexer/TokenKind.d.ts +9 -0
  234. package/dist/lexer/TokenKind.js +30 -5
  235. package/dist/lexer/TokenKind.js.map +1 -1
  236. package/dist/parser/AstNode.d.ts +162 -0
  237. package/dist/parser/AstNode.js +225 -0
  238. package/dist/parser/AstNode.js.map +1 -0
  239. package/dist/parser/AstNode.spec.d.ts +1 -0
  240. package/dist/parser/AstNode.spec.js +165 -0
  241. package/dist/parser/AstNode.spec.js.map +1 -0
  242. package/dist/parser/BrsTranspileState.d.ts +4 -7
  243. package/dist/parser/BrsTranspileState.js +4 -12
  244. package/dist/parser/BrsTranspileState.js.map +1 -1
  245. package/dist/parser/Expression.d.ts +126 -167
  246. package/dist/parser/Expression.js +524 -394
  247. package/dist/parser/Expression.js.map +1 -1
  248. package/dist/parser/Parser.Class.spec.js +152 -146
  249. package/dist/parser/Parser.Class.spec.js.map +1 -1
  250. package/dist/parser/Parser.d.ts +45 -196
  251. package/dist/parser/Parser.js +470 -926
  252. package/dist/parser/Parser.js.map +1 -1
  253. package/dist/parser/Parser.spec.d.ts +3 -1
  254. package/dist/parser/Parser.spec.js +1034 -805
  255. package/dist/parser/Parser.spec.js.map +1 -1
  256. package/dist/parser/SGParser.d.ts +9 -8
  257. package/dist/parser/SGParser.js +10 -8
  258. package/dist/parser/SGParser.js.map +1 -1
  259. package/dist/parser/SGParser.spec.js +27 -38
  260. package/dist/parser/SGParser.spec.js.map +1 -1
  261. package/dist/parser/SGTypes.d.ts +98 -35
  262. package/dist/parser/SGTypes.js +169 -99
  263. package/dist/parser/SGTypes.js.map +1 -1
  264. package/dist/parser/Statement.d.ts +208 -122
  265. package/dist/parser/Statement.js +599 -364
  266. package/dist/parser/Statement.js.map +1 -1
  267. package/dist/parser/Statement.spec.js +45 -21
  268. package/dist/parser/Statement.spec.js.map +1 -1
  269. package/dist/parser/TranspileState.d.ts +1 -1
  270. package/dist/parser/TranspileState.js +7 -12
  271. package/dist/parser/TranspileState.js.map +1 -1
  272. package/dist/parser/tests/Parser.spec.js +3 -2
  273. package/dist/parser/tests/Parser.spec.js.map +1 -1
  274. package/dist/parser/tests/controlFlow/For.spec.js +33 -23
  275. package/dist/parser/tests/controlFlow/For.spec.js.map +1 -1
  276. package/dist/parser/tests/controlFlow/ForEach.spec.js +25 -20
  277. package/dist/parser/tests/controlFlow/ForEach.spec.js.map +1 -1
  278. package/dist/parser/tests/controlFlow/If.spec.js +96 -94
  279. package/dist/parser/tests/controlFlow/If.spec.js.map +1 -1
  280. package/dist/parser/tests/controlFlow/While.spec.js +22 -16
  281. package/dist/parser/tests/controlFlow/While.spec.js.map +1 -1
  282. package/dist/parser/tests/expression/Additive.spec.js +8 -8
  283. package/dist/parser/tests/expression/Additive.spec.js.map +1 -1
  284. package/dist/parser/tests/expression/ArrayLiterals.spec.js +58 -21
  285. package/dist/parser/tests/expression/ArrayLiterals.spec.js.map +1 -1
  286. package/dist/parser/tests/expression/AssociativeArrayLiterals.spec.js +61 -20
  287. package/dist/parser/tests/expression/AssociativeArrayLiterals.spec.js.map +1 -1
  288. package/dist/parser/tests/expression/Boolean.spec.js +8 -8
  289. package/dist/parser/tests/expression/Boolean.spec.js.map +1 -1
  290. package/dist/parser/tests/expression/Call.spec.js +129 -21
  291. package/dist/parser/tests/expression/Call.spec.js.map +1 -1
  292. package/dist/parser/tests/expression/Exponential.spec.js +5 -5
  293. package/dist/parser/tests/expression/Exponential.spec.js.map +1 -1
  294. package/dist/parser/tests/expression/Function.spec.js +36 -36
  295. package/dist/parser/tests/expression/Function.spec.js.map +1 -1
  296. package/dist/parser/tests/expression/Indexing.spec.js +67 -22
  297. package/dist/parser/tests/expression/Indexing.spec.js.map +1 -1
  298. package/dist/parser/tests/expression/Multiplicative.spec.js +9 -9
  299. package/dist/parser/tests/expression/Multiplicative.spec.js.map +1 -1
  300. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js +123 -81
  301. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js.map +1 -1
  302. package/dist/parser/tests/expression/PrefixUnary.spec.js +12 -12
  303. package/dist/parser/tests/expression/PrefixUnary.spec.js.map +1 -1
  304. package/dist/parser/tests/expression/Primary.spec.js +12 -12
  305. package/dist/parser/tests/expression/Primary.spec.js.map +1 -1
  306. package/dist/parser/tests/expression/RegexLiteralExpression.spec.js +10 -10
  307. package/dist/parser/tests/expression/RegexLiteralExpression.spec.js.map +1 -1
  308. package/dist/parser/tests/expression/Relational.spec.js +13 -13
  309. package/dist/parser/tests/expression/Relational.spec.js.map +1 -1
  310. package/dist/parser/tests/expression/SourceLiteralExpression.spec.js +24 -24
  311. package/dist/parser/tests/expression/SourceLiteralExpression.spec.js.map +1 -1
  312. package/dist/parser/tests/expression/TemplateStringExpression.spec.js +221 -81
  313. package/dist/parser/tests/expression/TemplateStringExpression.spec.js.map +1 -1
  314. package/dist/parser/tests/expression/TernaryExpression.spec.js +287 -105
  315. package/dist/parser/tests/expression/TernaryExpression.spec.js.map +1 -1
  316. package/dist/parser/tests/expression/TypeExpression.spec.d.ts +1 -0
  317. package/dist/parser/tests/expression/TypeExpression.spec.js +127 -0
  318. package/dist/parser/tests/expression/TypeExpression.spec.js.map +1 -0
  319. package/dist/parser/tests/expression/UnaryExpression.spec.d.ts +1 -0
  320. package/dist/parser/tests/expression/UnaryExpression.spec.js +52 -0
  321. package/dist/parser/tests/expression/UnaryExpression.spec.js.map +1 -0
  322. package/dist/parser/tests/statement/AssignmentOperators.spec.js +15 -15
  323. package/dist/parser/tests/statement/AssignmentOperators.spec.js.map +1 -1
  324. package/dist/parser/tests/statement/ConstStatement.spec.d.ts +1 -0
  325. package/dist/parser/tests/statement/ConstStatement.spec.js +262 -0
  326. package/dist/parser/tests/statement/ConstStatement.spec.js.map +1 -0
  327. package/dist/parser/tests/statement/Continue.spec.d.ts +1 -0
  328. package/dist/parser/tests/statement/Continue.spec.js +119 -0
  329. package/dist/parser/tests/statement/Continue.spec.js.map +1 -0
  330. package/dist/parser/tests/statement/Declaration.spec.js +19 -19
  331. package/dist/parser/tests/statement/Declaration.spec.js.map +1 -1
  332. package/dist/parser/tests/statement/Dim.spec.js +22 -22
  333. package/dist/parser/tests/statement/Dim.spec.js.map +1 -1
  334. package/dist/parser/tests/statement/Enum.spec.js +111 -300
  335. package/dist/parser/tests/statement/Enum.spec.js.map +1 -1
  336. package/dist/parser/tests/statement/For.spec.js +9 -10
  337. package/dist/parser/tests/statement/For.spec.js.map +1 -1
  338. package/dist/parser/tests/statement/ForEach.spec.js +8 -9
  339. package/dist/parser/tests/statement/ForEach.spec.js.map +1 -1
  340. package/dist/parser/tests/statement/Function.spec.js +44 -35
  341. package/dist/parser/tests/statement/Function.spec.js.map +1 -1
  342. package/dist/parser/tests/statement/Goto.spec.js +5 -5
  343. package/dist/parser/tests/statement/Goto.spec.js.map +1 -1
  344. package/dist/parser/tests/statement/Increment.spec.js +20 -20
  345. package/dist/parser/tests/statement/Increment.spec.js.map +1 -1
  346. package/dist/parser/tests/statement/InterfaceStatement.spec.js +30 -196
  347. package/dist/parser/tests/statement/InterfaceStatement.spec.js.map +1 -1
  348. package/dist/parser/tests/statement/LibraryStatement.spec.js +11 -11
  349. package/dist/parser/tests/statement/LibraryStatement.spec.js.map +1 -1
  350. package/dist/parser/tests/statement/Misc.spec.js +16 -78
  351. package/dist/parser/tests/statement/Misc.spec.js.map +1 -1
  352. package/dist/parser/tests/statement/PrintStatement.spec.js +107 -90
  353. package/dist/parser/tests/statement/PrintStatement.spec.js.map +1 -1
  354. package/dist/parser/tests/statement/ReturnStatement.spec.js +14 -12
  355. package/dist/parser/tests/statement/ReturnStatement.spec.js.map +1 -1
  356. package/dist/parser/tests/statement/Set.spec.js +48 -35
  357. package/dist/parser/tests/statement/Set.spec.js.map +1 -1
  358. package/dist/parser/tests/statement/Stop.spec.js +6 -6
  359. package/dist/parser/tests/statement/Stop.spec.js.map +1 -1
  360. package/dist/parser/tests/statement/Throw.spec.js +6 -6
  361. package/dist/parser/tests/statement/Throw.spec.js.map +1 -1
  362. package/dist/parser/tests/statement/TryCatch.spec.js +18 -16
  363. package/dist/parser/tests/statement/TryCatch.spec.js.map +1 -1
  364. package/dist/preprocessor/Manifest.d.ts +1 -1
  365. package/dist/preprocessor/Manifest.js +3 -3
  366. package/dist/preprocessor/Manifest.js.map +1 -1
  367. package/dist/preprocessor/Manifest.spec.js +8 -8
  368. package/dist/preprocessor/Manifest.spec.js.map +1 -1
  369. package/dist/preprocessor/Preprocessor.d.ts +5 -6
  370. package/dist/preprocessor/Preprocessor.js +15 -11
  371. package/dist/preprocessor/Preprocessor.js.map +1 -1
  372. package/dist/preprocessor/Preprocessor.spec.js +25 -25
  373. package/dist/preprocessor/Preprocessor.spec.js.map +1 -1
  374. package/dist/preprocessor/PreprocessorParser.d.ts +1 -1
  375. package/dist/preprocessor/PreprocessorParser.js +7 -1
  376. package/dist/preprocessor/PreprocessorParser.js.map +1 -1
  377. package/dist/preprocessor/PreprocessorParser.spec.js +13 -13
  378. package/dist/preprocessor/PreprocessorParser.spec.js.map +1 -1
  379. package/dist/roku-types/data.json +6544 -10519
  380. package/dist/roku-types/index.d.ts +662 -1934
  381. package/dist/types/ArrayType.d.ts +10 -9
  382. package/dist/types/ArrayType.js +65 -60
  383. package/dist/types/ArrayType.js.map +1 -1
  384. package/dist/types/ArrayType.spec.js +36 -68
  385. package/dist/types/ArrayType.spec.js.map +1 -1
  386. package/dist/types/AssociativeArrayType.d.ts +11 -0
  387. package/dist/types/AssociativeArrayType.js +52 -0
  388. package/dist/types/AssociativeArrayType.js.map +1 -0
  389. package/dist/types/BaseFunctionType.d.ts +9 -0
  390. package/dist/types/BaseFunctionType.js +25 -0
  391. package/dist/types/BaseFunctionType.js.map +1 -0
  392. package/dist/types/BooleanType.d.ts +8 -5
  393. package/dist/types/BooleanType.js +14 -7
  394. package/dist/types/BooleanType.js.map +1 -1
  395. package/dist/types/BooleanType.spec.js +10 -6
  396. package/dist/types/BooleanType.spec.js.map +1 -1
  397. package/dist/types/BscType.d.ts +32 -21
  398. package/dist/types/BscType.js +118 -21
  399. package/dist/types/BscType.js.map +1 -1
  400. package/dist/types/BscTypeKind.d.ts +25 -0
  401. package/dist/types/BscTypeKind.js +30 -0
  402. package/dist/types/BscTypeKind.js.map +1 -0
  403. package/dist/types/BuiltInInterfaceAdder.d.ts +23 -0
  404. package/dist/types/BuiltInInterfaceAdder.js +164 -0
  405. package/dist/types/BuiltInInterfaceAdder.js.map +1 -0
  406. package/dist/types/BuiltInInterfaceAdder.spec.d.ts +1 -0
  407. package/dist/types/BuiltInInterfaceAdder.spec.js +116 -0
  408. package/dist/types/BuiltInInterfaceAdder.spec.js.map +1 -0
  409. package/dist/types/ClassType.d.ts +17 -0
  410. package/dist/types/ClassType.js +58 -0
  411. package/dist/types/ClassType.js.map +1 -0
  412. package/dist/types/ClassType.spec.d.ts +1 -0
  413. package/dist/types/ClassType.spec.js +77 -0
  414. package/dist/types/ClassType.spec.js.map +1 -0
  415. package/dist/types/ComponentType.d.ts +26 -0
  416. package/dist/types/ComponentType.js +83 -0
  417. package/dist/types/ComponentType.js.map +1 -0
  418. package/dist/types/DoubleType.d.ts +8 -5
  419. package/dist/types/DoubleType.js +18 -16
  420. package/dist/types/DoubleType.js.map +1 -1
  421. package/dist/types/DoubleType.spec.js +12 -6
  422. package/dist/types/DoubleType.spec.js.map +1 -1
  423. package/dist/types/DynamicType.d.ts +10 -5
  424. package/dist/types/DynamicType.js +16 -4
  425. package/dist/types/DynamicType.js.map +1 -1
  426. package/dist/types/DynamicType.spec.js +16 -5
  427. package/dist/types/DynamicType.spec.js.map +1 -1
  428. package/dist/types/EnumType.d.ts +30 -12
  429. package/dist/types/EnumType.js +43 -17
  430. package/dist/types/EnumType.js.map +1 -1
  431. package/dist/types/EnumType.spec.d.ts +1 -0
  432. package/dist/types/EnumType.spec.js +33 -0
  433. package/dist/types/EnumType.spec.js.map +1 -0
  434. package/dist/types/FloatType.d.ts +8 -5
  435. package/dist/types/FloatType.js +18 -16
  436. package/dist/types/FloatType.js.map +1 -1
  437. package/dist/types/FloatType.spec.js +4 -6
  438. package/dist/types/FloatType.spec.js.map +1 -1
  439. package/dist/types/FunctionType.d.ts +13 -8
  440. package/dist/types/FunctionType.js +30 -14
  441. package/dist/types/FunctionType.js.map +1 -1
  442. package/dist/types/InheritableType.d.ts +28 -0
  443. package/dist/types/InheritableType.js +152 -0
  444. package/dist/types/InheritableType.js.map +1 -0
  445. package/dist/types/IntegerType.d.ts +8 -5
  446. package/dist/types/IntegerType.js +18 -16
  447. package/dist/types/IntegerType.js.map +1 -1
  448. package/dist/types/IntegerType.spec.js +8 -6
  449. package/dist/types/IntegerType.spec.js.map +1 -1
  450. package/dist/types/InterfaceType.d.ts +12 -13
  451. package/dist/types/InterfaceType.js +20 -48
  452. package/dist/types/InterfaceType.js.map +1 -1
  453. package/dist/types/InterfaceType.spec.js +90 -56
  454. package/dist/types/InterfaceType.spec.js.map +1 -1
  455. package/dist/types/InvalidType.d.ts +7 -5
  456. package/dist/types/InvalidType.js +13 -7
  457. package/dist/types/InvalidType.js.map +1 -1
  458. package/dist/types/InvalidType.spec.js +8 -6
  459. package/dist/types/InvalidType.spec.js.map +1 -1
  460. package/dist/types/LongIntegerType.d.ts +8 -5
  461. package/dist/types/LongIntegerType.js +17 -15
  462. package/dist/types/LongIntegerType.js.map +1 -1
  463. package/dist/types/LongIntegerType.spec.js +10 -6
  464. package/dist/types/LongIntegerType.spec.js.map +1 -1
  465. package/dist/types/NamespaceType.d.ts +12 -0
  466. package/dist/types/NamespaceType.js +28 -0
  467. package/dist/types/NamespaceType.js.map +1 -0
  468. package/dist/types/ObjectType.d.ts +9 -8
  469. package/dist/types/ObjectType.js +21 -11
  470. package/dist/types/ObjectType.js.map +1 -1
  471. package/dist/types/ObjectType.spec.js +3 -3
  472. package/dist/types/ObjectType.spec.js.map +1 -1
  473. package/dist/types/ReferenceType.d.ts +63 -0
  474. package/dist/types/ReferenceType.js +423 -0
  475. package/dist/types/ReferenceType.js.map +1 -0
  476. package/dist/types/ReferenceType.spec.d.ts +1 -0
  477. package/dist/types/ReferenceType.spec.js +137 -0
  478. package/dist/types/ReferenceType.spec.js.map +1 -0
  479. package/dist/types/StringType.d.ts +11 -5
  480. package/dist/types/StringType.js +18 -7
  481. package/dist/types/StringType.js.map +1 -1
  482. package/dist/types/StringType.spec.js +3 -5
  483. package/dist/types/StringType.spec.js.map +1 -1
  484. package/dist/types/TypedFunctionType.d.ts +22 -17
  485. package/dist/types/TypedFunctionType.js +78 -60
  486. package/dist/types/TypedFunctionType.js.map +1 -1
  487. package/dist/types/TypedFunctionType.spec.js +105 -20
  488. package/dist/types/TypedFunctionType.spec.js.map +1 -1
  489. package/dist/types/UninitializedType.d.ts +8 -6
  490. package/dist/types/UninitializedType.js +13 -7
  491. package/dist/types/UninitializedType.js.map +1 -1
  492. package/dist/types/UnionType.d.ts +20 -0
  493. package/dist/types/UnionType.js +123 -0
  494. package/dist/types/UnionType.js.map +1 -0
  495. package/dist/types/UnionType.spec.d.ts +1 -0
  496. package/dist/types/UnionType.spec.js +130 -0
  497. package/dist/types/UnionType.spec.js.map +1 -0
  498. package/dist/types/VoidType.d.ts +8 -5
  499. package/dist/types/VoidType.js +14 -7
  500. package/dist/types/VoidType.js.map +1 -1
  501. package/dist/types/VoidType.spec.js +3 -3
  502. package/dist/types/VoidType.spec.js.map +1 -1
  503. package/dist/types/helper.spec.d.ts +1 -0
  504. package/dist/types/helper.spec.js +145 -0
  505. package/dist/types/helper.spec.js.map +1 -0
  506. package/dist/types/helpers.d.ts +19 -37
  507. package/dist/types/helpers.js +159 -99
  508. package/dist/types/helpers.js.map +1 -1
  509. package/dist/types/index.d.ts +22 -0
  510. package/dist/types/index.js +39 -0
  511. package/dist/types/index.js.map +1 -0
  512. package/dist/util.d.ts +167 -131
  513. package/dist/util.js +890 -350
  514. package/dist/util.js.map +1 -1
  515. package/dist/validators/ClassValidator.d.ts +7 -25
  516. package/dist/validators/ClassValidator.js +103 -194
  517. package/dist/validators/ClassValidator.js.map +1 -1
  518. package/package.json +165 -149
  519. package/dist/astUtils/AstEditor.js.map +0 -1
  520. package/dist/astUtils/AstEditor.spec.js.map +0 -1
  521. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.d.ts +0 -8
  522. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.js +0 -40
  523. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.js.map +0 -1
  524. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.spec.js +0 -32
  525. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.spec.js.map +0 -1
  526. package/dist/parser/SGTypes.spec.js +0 -351
  527. package/dist/parser/SGTypes.spec.js.map +0 -1
  528. package/dist/types/CustomType.d.ts +0 -12
  529. package/dist/types/CustomType.js +0 -44
  530. package/dist/types/CustomType.js.map +0 -1
  531. package/dist/types/LazyType.d.ts +0 -16
  532. package/dist/types/LazyType.js +0 -44
  533. package/dist/types/LazyType.js.map +0 -1
  534. /package/dist/astUtils/{AstEditor.spec.d.ts → Editor.spec.d.ts} +0 -0
  535. /package/dist/bscPlugin/{transpile/BrsFilePreTranspileProcessor.spec.d.ts → completions/CompletionsProcessor.spec.d.ts} +0 -0
  536. /package/dist/{parser/SGTypes.spec.d.ts → bscPlugin/hover/HoverProcessor.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
  }
@@ -150,15 +262,25 @@ class Program {
150
262
  file: xmlFile,
151
263
  scope: scope
152
264
  });
153
- this.components[key].sort((x, y) => x.file.pkgPath.toLowerCase().localeCompare(y.file.pkgPath.toLowerCase()));
265
+ this.components[key].sort((a, b) => {
266
+ const pathA = a.file.destPath.toLowerCase();
267
+ const pathB = b.file.destPath.toLowerCase();
268
+ if (pathA < pathB) {
269
+ return -1;
270
+ }
271
+ else if (pathA > pathB) {
272
+ return 1;
273
+ }
274
+ return 0;
275
+ });
154
276
  this.syncComponentDependencyGraph(this.components[key]);
277
+ this.addDeferredComponentTypeSymbolCreation(xmlFile);
155
278
  }
156
279
  /**
157
280
  * Remove the specified component from the components map
158
281
  */
159
282
  unregisterComponent(xmlFile) {
160
- var _a, _b;
161
- 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);
162
284
  const arr = this.components[key] || [];
163
285
  for (let i = 0; i < arr.length; i++) {
164
286
  if (arr[i].file === xmlFile) {
@@ -167,6 +289,44 @@ class Program {
167
289
  }
168
290
  }
169
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
+ }
170
330
  }
171
331
  /**
172
332
  * re-attach the dependency graph with a new key for any component who changed
@@ -180,6 +340,7 @@ class Program {
180
340
  //attach (or re-attach) the dependencyGraph for every component whose position changed
181
341
  if (file.dependencyGraphIndex !== i) {
182
342
  file.dependencyGraphIndex = i;
343
+ this.dependencyGraph.addOrReplace(file.dependencyGraphKey, file.dependencies);
183
344
  file.attachDependencyGraph(this.dependencyGraph);
184
345
  scope.attachDependencyGraph(this.dependencyGraph);
185
346
  }
@@ -191,9 +352,10 @@ class Program {
191
352
  */
192
353
  getUnreferencedFiles() {
193
354
  let result = [];
194
- for (let key in this.files) {
195
- const file = this.files[key];
196
- 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)) {
197
359
  //no scopes reference this file. add it to the list
198
360
  result.push(file);
199
361
  }
@@ -215,13 +377,16 @@ class Program {
215
377
  //get the diagnostics from all unreferenced files
216
378
  let unreferencedFiles = this.getUnreferencedFiles();
217
379
  for (let file of unreferencedFiles) {
218
- diagnostics.push(...file.getDiagnostics());
380
+ diagnostics.push(...file.diagnostics);
219
381
  }
220
382
  const filteredDiagnostics = this.logger.time(Logger_1.LogLevel.debug, ['filter diagnostics'], () => {
221
383
  //filter out diagnostics based on our diagnostic filters
222
384
  let finalDiagnostics = this.diagnosticFilterer.filter(Object.assign(Object.assign({}, this.options), { rootDir: this.options.rootDir }), diagnostics);
223
385
  return finalDiagnostics;
224
386
  });
387
+ this.logger.time(Logger_1.LogLevel.debug, ['adjust diagnostics severity'], () => {
388
+ this.diagnosticAdjuster.adjust(this.options, diagnostics);
389
+ });
225
390
  this.logger.info(`diagnostic counts: total=${chalk_1.default.yellow(diagnostics.length.toString())}, after filter=${chalk_1.default.yellow(filteredDiagnostics.length.toString())}`);
226
391
  return filteredDiagnostics;
227
392
  });
@@ -231,7 +396,7 @@ class Program {
231
396
  }
232
397
  /**
233
398
  * Determine if the specified file is loaded in this program right now.
234
- * @param filePath
399
+ * @param filePath the absolute or relative path to the file
235
400
  * @param normalizePath should the provided path be normalized before use
236
401
  */
237
402
  hasFile(filePath, normalizePath = true) {
@@ -239,12 +404,15 @@ class Program {
239
404
  }
240
405
  /**
241
406
  * roku filesystem is case INsensitive, so find the scope by key case insensitive
242
- * @param scopeName
407
+ * @param scopeName xml scope names are their `destPath`. Source scope is stored with the key `"source"`
243
408
  */
244
409
  getScopeByName(scopeName) {
245
410
  if (!scopeName) {
246
411
  return undefined;
247
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}`;
248
416
  let key = Object.keys(this.scopes).find(x => x.toLowerCase() === scopeName.toLowerCase());
249
417
  return this.scopes[key];
250
418
  }
@@ -265,8 +433,14 @@ class Program {
265
433
  * Update internal maps with this file reference
266
434
  */
267
435
  assignFile(file) {
436
+ const fileAddEvent = {
437
+ file: file,
438
+ program: this
439
+ };
440
+ this.plugins.emit('beforeFileAdd', fileAddEvent);
268
441
  this.files[file.srcPath.toLowerCase()] = file;
269
- this.pkgMap[file.pkgPath.toLowerCase()] = file;
442
+ this.destMap.set(file.destPath.toLowerCase(), file);
443
+ this.plugins.emit('afterFileAdd', fileAddEvent);
270
444
  return file;
271
445
  }
272
446
  /**
@@ -274,108 +448,155 @@ class Program {
274
448
  */
275
449
  unassignFile(file) {
276
450
  delete this.files[file.srcPath.toLowerCase()];
277
- delete this.pkgMap[file.pkgPath.toLowerCase()];
451
+ this.destMap.delete(file.destPath.toLowerCase());
278
452
  return file;
279
453
  }
280
- setFile(fileParam, fileContents) {
281
- assert.ok(fileParam, 'fileParam is required');
282
- let srcPath;
283
- let pkgPath;
284
- if (typeof fileParam === 'string') {
285
- //is a pkg path
286
- if (fileParam.startsWith('pkg:/')) {
287
- //srcPath is the pkgPath relative to the rootDir
288
- srcPath = (0, util_1.standardizePath) `${this.options.rootDir}/${fileParam.substring(5)}`;
289
- pkgPath = fileParam;
290
- //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);
291
462
  }
292
- else if (path.isAbsolute(fileParam)) {
293
- srcPath = util_1.util.standardizePath(fileParam);
294
- //assume the file path is a sub path of rootDir
295
- pkgPath = util_1.util.sanitizePkgPath(roku_deploy_1.util.stringReplaceInsensitive(srcPath, this.options.rootDir, ''));
296
- //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
+ }));
297
476
  }
298
- else {
299
- srcPath = (0, util_1.standardizePath) `${this.options.rootDir}/${fileParam}`;
300
- 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
+ }
301
528
  }
302
- //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, '')}`;
303
547
  }
304
548
  else {
305
- srcPath = (0, util_1.standardizePath) `${fileParam.src}`;
306
- pkgPath = util_1.util.sanitizePkgPath(fileParam.dest);
307
- }
308
- const lowerPkgPath = pkgPath.toLowerCase();
309
- return this.logger.time(Logger_1.LogLevel.debug, ['Program.setFile()', chalk_1.default.green(srcPath)], () => {
310
- assert.ok(srcPath, 'srcPath is required');
311
- assert.ok(pkgPath, 'pkgPath is required');
312
- //if the file is already loaded, remove it
313
- if (this.hasFile(srcPath)) {
314
- this.removeFile(srcPath);
549
+ let param = fileParam;
550
+ if (param.src) {
551
+ srcPath = (0, util_1.standardizePath) `${param.src}`;
315
552
  }
316
- let fileExtension = path.extname(srcPath).toLowerCase();
317
- let file;
318
- const beforeFileParseEvent = {
319
- program: this,
320
- srcPath: srcPath,
321
- source: fileContents
322
- };
323
- if (fileExtension === '.brs' || fileExtension === '.bs') {
324
- //add the file to the program
325
- const brsFile = this.assignFile(new BrsFile_1.BrsFile(srcPath, pkgPath, this));
326
- //add file to the `source` dependency list
327
- if (brsFile.pkgPath.startsWith('pkg:/source/')) {
328
- this.createSourceScope();
329
- this.dependencyGraph.addDependency('scope:source', brsFile.dependencyGraphKey);
330
- }
331
- //add the file to the program
332
- this.assignFile(brsFile);
333
- this.plugins.emit('beforeFileParse', beforeFileParseEvent);
334
- this.logger.time(Logger_1.LogLevel.debug, ['parse', chalk_1.default.green(srcPath)], () => {
335
- brsFile.parse(beforeFileParseEvent.source);
336
- });
337
- //notify plugins that this file has finished parsing
338
- this.plugins.emit('afterFileParse', {
339
- program: this,
340
- file: brsFile
341
- });
342
- file = brsFile;
343
- brsFile.attachDependencyGraph(this.dependencyGraph);
553
+ if (param.srcPath) {
554
+ srcPath = (0, util_1.standardizePath) `${param.srcPath}`;
344
555
  }
345
- else if (
346
- //is xml file
347
- fileExtension === '.xml' &&
348
- //resides in the components folder (Roku will only parse xml files in the components folder)
349
- lowerPkgPath.startsWith('pkg:/components/')) {
350
- //add the file to the program
351
- const xmlFile = this.assignFile(new XmlFile_1.XmlFile(srcPath, pkgPath, this));
352
- this.plugins.emit('beforeFileParse', beforeFileParseEvent);
353
- this.logger.time(Logger_1.LogLevel.debug, ['parse', chalk_1.default.green(srcPath)], () => {
354
- xmlFile.parse(beforeFileParseEvent.source);
355
- });
356
- //notify plugins that this file has finished parsing
357
- this.plugins.emit('afterFileParse', {
358
- program: this,
359
- file: xmlFile
360
- });
361
- file = xmlFile;
362
- //create a new scope for this xml file
363
- let scope = new XmlScope_1.XmlScope(xmlFile, this);
364
- this.addScope(scope);
365
- //register this compoent now that we have parsed it and know its component name
366
- this.registerComponent(xmlFile, scope);
556
+ if (param.dest) {
557
+ destPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.dest)}`;
367
558
  }
368
- else {
369
- //TODO do we actually need to implement this? Figure out how to handle img paths
370
- // let genericFile = this.files[srcPath] = <any>{
371
- // srcPath: srcPath,
372
- // pkgPath: pkgPath,
373
- // wasProcessed: true
374
- // } as File;
375
- // file = <any>genericFile;
559
+ if (param.pkgPath) {
560
+ destPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.pkgPath)}`;
376
561
  }
377
- return file;
378
- });
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);
379
600
  }
380
601
  /**
381
602
  * Ensure source scope is created.
@@ -386,11 +607,15 @@ class Program {
386
607
  const sourceScope = new Scope_1.Scope('source', this, 'scope:source');
387
608
  sourceScope.attachDependencyGraph(this.dependencyGraph);
388
609
  this.addScope(sourceScope);
610
+ this.plugins.emit('afterScopeCreate', {
611
+ program: this,
612
+ scope: sourceScope
613
+ });
389
614
  }
390
615
  }
391
616
  /**
392
617
  * Remove a set of files from the program
393
- * @param filePaths can be an array of srcPath or destPath strings
618
+ * @param srcPaths can be an array of srcPath or destPath strings
394
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
395
620
  */
396
621
  removeFiles(srcPaths, normalizePath = true) {
@@ -400,69 +625,58 @@ class Program {
400
625
  }
401
626
  /**
402
627
  * Remove a file from the program
403
- * @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:/`
404
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
405
630
  */
406
- removeFile(filePath, normalizePath = true) {
631
+ removeFile(filePath, normalizePath = true, keepSymbolInformation = false) {
632
+ var _a, _b, _c, _d;
407
633
  this.logger.debug('Program.removeFile()', filePath);
408
- let file = this.getFile(filePath, normalizePath);
409
- if (file) {
410
- this.plugins.emit('beforeFileDispose', {
411
- program: this,
412
- file: file
413
- });
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);
414
644
  //if there is a scope named the same as this file's path, remove it (i.e. xml scopes)
415
- let scope = this.scopes[file.pkgPath];
645
+ let scope = this.scopes[file.destPath];
416
646
  if (scope) {
417
- this.plugins.emit('beforeScopeDispose', {
647
+ const scopeDisposeEvent = {
418
648
  program: this,
419
649
  scope: scope
420
- });
650
+ };
651
+ this.plugins.emit('beforeScopeDispose', scopeDisposeEvent);
652
+ this.plugins.emit('onScopeDispose', scopeDisposeEvent);
421
653
  scope.dispose();
422
654
  //notify dependencies of this scope that it has been removed
423
655
  this.dependencyGraph.remove(scope.dependencyGraphKey);
424
- delete this.scopes[file.pkgPath];
425
- this.plugins.emit('afterScopeDispose', {
426
- program: this,
427
- scope: scope
428
- });
656
+ delete this.scopes[file.destPath];
657
+ this.plugins.emit('afterScopeDispose', scopeDisposeEvent);
429
658
  }
430
659
  //remove the file from the program
431
660
  this.unassignFile(file);
432
661
  this.dependencyGraph.remove(file.dependencyGraphKey);
433
662
  //if this is a pkg:/source file, notify the `source` scope that it has changed
434
- if (file.pkgPath.startsWith('pkg:/source/')) {
663
+ if (this.isSourceBrsFile(file)) {
435
664
  this.dependencyGraph.removeDependency('scope:source', file.dependencyGraphKey);
665
+ if (!keepSymbolInformation) {
666
+ this.fileSymbolInformation.delete(file.pkgPath);
667
+ }
436
668
  }
437
669
  //if this is a component, remove it from our components map
438
670
  if ((0, reflection_1.isXmlFile)(file)) {
439
671
  this.unregisterComponent(file);
440
672
  }
441
- //dispose file
442
- file === null || file === void 0 ? void 0 : file.dispose();
443
- this.plugins.emit('afterFileDispose', {
444
- program: this,
445
- file: file
446
- });
447
- }
448
- }
449
- /**
450
- * Remove all files from the program that are in the specified folder path (recursive)
451
- * @param folderSrcPath The absolute path to the folder on disk
452
- * @param normalizePath should the provided path be normalized before use?
453
- */
454
- removeFilesInFolder(folderSrcPath, normalizePath = true) {
455
- if (normalizePath) {
456
- folderSrcPath = util_1.util.standardizePath(folderSrcPath);
457
- }
458
- const lowerFolderSrcPath = folderSrcPath.toLowerCase();
459
- for (const key in this.files) {
460
- const file = this.files[key];
461
- const lowerSrcPath = file.srcPath.toLowerCase();
462
- //if the file path starts with the parent path and the file path does not exactly match the folder path
463
- if (lowerSrcPath.toLowerCase().startsWith(lowerFolderSrcPath) && lowerSrcPath !== lowerFolderSrcPath) {
464
- 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();
465
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);
466
680
  }
467
681
  }
468
682
  /**
@@ -470,91 +684,165 @@ class Program {
470
684
  */
471
685
  validate() {
472
686
  this.logger.time(Logger_1.LogLevel.log, ['Validating project'], () => {
473
- var _a;
687
+ var _a, _b, _c, _d, _e, _f, _g;
474
688
  this.diagnostics = [];
475
- this.plugins.emit('beforeProgramValidate', {
689
+ const programValidateEvent = {
476
690
  program: this
477
- });
691
+ };
692
+ this.plugins.emit('beforeProgramValidate', programValidateEvent);
693
+ this.plugins.emit('onProgramValidate', programValidateEvent);
478
694
  //validate every file
695
+ const brsFilesValidated = [];
479
696
  for (const file of Object.values(this.files)) {
480
- //find any files NOT loaded into a scope
481
- if (!this.fileIsIncludedInAnyScope(file)) {
482
- this.logger.debug('Program.validate(): fileNotReferenced by any scope', () => chalk_1.default.green(file === null || file === void 0 ? void 0 : file.pkgPath));
483
- //the file is not loaded in any scope
484
- this.diagnostics.push(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.fileNotReferencedByAnyOtherFile()), { file: file, range: util_1.util.createRange(0, 0, 0, Number.MAX_VALUE) }));
485
- }
486
697
  //for every unvalidated file, validate it
487
698
  if (!file.isValidated) {
488
- this.plugins.emit('beforeFileValidate', {
699
+ const validateFileEvent = {
489
700
  program: this,
490
701
  file: file
491
- });
702
+ };
703
+ this.plugins.emit('beforeFileValidate', validateFileEvent);
492
704
  //emit an event to allow plugins to contribute to the file validation process
493
- this.plugins.emit('onFileValidate', {
494
- program: this,
495
- file: file
496
- });
497
- //call file.validate() IF the file has that function defined
498
- (_a = file.validate) === null || _a === void 0 ? void 0 : _a.call(file);
705
+ this.plugins.emit('onFileValidate', validateFileEvent);
499
706
  file.isValidated = true;
500
- this.plugins.emit('afterFileValidate', {
501
- program: this,
502
- file: file
503
- });
707
+ if ((0, reflection_1.isBrsFile)(file)) {
708
+ brsFilesValidated.push(file);
709
+ }
710
+ this.plugins.emit('afterFileValidate', validateFileEvent);
504
711
  }
505
712
  }
506
- this.logger.time(Logger_1.LogLevel.info, ['Validate all scopes'], () => {
507
- for (let scope of Object.values(this.scopes)) {
508
- //only validate unvalidated scopes
509
- if (!scope.isValidated) {
510
- this.plugins.emit('beforeScopeValidate', {
511
- program: this,
512
- scope: scope
513
- });
514
- scope.validate();
515
- scope.isValidated = true;
516
- this.plugins.emit('afterScopeValidate', {
517
- program: this,
518
- scope: scope
519
- });
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
+ }
520
773
  }
774
+ if (scopesAreInconsistent) {
775
+ fileInfo.symbolsNotConsistentAcrossScopes.push({ symbol: symbol, scopes: scopesDefiningSymbol });
776
+ }
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);
521
785
  }
786
+ this.componentSymbolsToUpdate.clear();
522
787
  });
523
- this.detectDuplicateComponentNames();
524
- this.plugins.emit('afterProgramValidate', {
525
- 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
+ }
526
804
  });
805
+ this.detectDuplicateComponentNames();
806
+ this.plugins.emit('afterProgramValidate', programValidateEvent);
527
807
  });
528
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
+ }
529
819
  /**
530
820
  * Flag all duplicate component names
531
821
  */
532
822
  detectDuplicateComponentNames() {
533
- var _a;
534
- const componentsByName = new Map();
535
- for (const key in this.files) {
536
- const file = this.files[key];
823
+ const componentsByName = Object.keys(this.files).reduce((map, filePath) => {
824
+ var _a;
825
+ const file = this.files[filePath];
537
826
  //if this is an XmlFile, and it has a valid `componentName` property
538
- if ((0, reflection_1.isXmlFile)(file)) {
539
- const componentNameLower = (_a = file.componentName) === null || _a === void 0 ? void 0 : _a.text.toLowerCase();
540
- if (componentNameLower) {
541
- if (!componentsByName.has(componentNameLower)) {
542
- componentsByName.set(componentNameLower, [file]);
543
- }
544
- else {
545
- componentsByName.get(componentNameLower).push(file);
546
- }
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] = [];
547
831
  }
832
+ map[lowerName].push(file);
548
833
  }
549
- }
550
- for (const xmlFiles of componentsByName.values()) {
834
+ return map;
835
+ }, {});
836
+ for (let name in componentsByName) {
837
+ const xmlFiles = componentsByName[name];
551
838
  //add diagnostics for every duplicate component with this name
552
839
  if (xmlFiles.length > 1) {
553
840
  for (let xmlFile of xmlFiles) {
554
841
  const { componentName } = xmlFile;
555
842
  this.diagnostics.push(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.duplicateComponentName(componentName.text)), { range: xmlFile.componentName.range, file: xmlFile, relatedInformation: xmlFiles.filter(x => x !== xmlFile).map(x => {
843
+ var _a;
556
844
  return {
557
- location: vscode_languageserver_1.Location.create(vscode_uri_1.URI.file(xmlFile.srcPath).toString(), x.componentName.range),
845
+ location: util_1.util.createLocation(vscode_uri_1.URI.file((_a = xmlFile.srcPath) !== null && _a !== void 0 ? _a : xmlFile.srcPath).toString(), x.componentName.range),
558
846
  message: 'Also defined here'
559
847
  };
560
848
  }) }));
@@ -562,17 +850,6 @@ class Program {
562
850
  }
563
851
  }
564
852
  }
565
- /**
566
- * Determine at least one scope has the file
567
- */
568
- fileIsIncludedInAnyScope(file) {
569
- for (let scope of Object.values(this.scopes)) {
570
- if (scope.hasFile(file)) {
571
- return true;
572
- }
573
- }
574
- return false;
575
- }
576
853
  /**
577
854
  * Get the files for a list of filePaths
578
855
  * @param filePaths can be an array of srcPath or a destPath strings
@@ -585,30 +862,34 @@ class Program {
585
862
  }
586
863
  /**
587
864
  * Get the file at the given path
588
- * @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
589
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
590
867
  */
591
868
  getFile(filePath, normalizePath = true) {
592
869
  if (typeof filePath !== 'string') {
593
870
  return undefined;
871
+ //is the path absolute (or the `virtual:` prefix)
594
872
  }
595
- else if (path.isAbsolute(filePath)) {
873
+ else if (/^(?:(?:virtual:[\/\\])|(?:\w:)|(?:[\/\\]))/gmi.exec(filePath)) {
596
874
  return this.files[(normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase()];
597
875
  }
598
876
  else {
599
- return this.pkgMap[(normalizePath ? util_1.util.sanitizePkgPath(filePath) : filePath).toLowerCase()];
877
+ return this.destMap.get((normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase());
600
878
  }
601
879
  }
602
880
  /**
603
881
  * Get a list of all scopes the file is loaded into
604
- * @param file
882
+ * @param file the file
605
883
  */
606
884
  getScopesForFile(file) {
885
+ const resolvedFile = typeof file === 'string' ? this.getFile(file) : file;
607
886
  let result = [];
608
- for (let key in this.scopes) {
609
- let scope = this.scopes[key];
610
- if (scope.hasFile(file)) {
611
- result.push(scope);
887
+ if (resolvedFile) {
888
+ for (let key in this.scopes) {
889
+ let scope = this.scopes[key];
890
+ if (scope.hasFile(resolvedFile)) {
891
+ result.push(scope);
892
+ }
612
893
  }
613
894
  }
614
895
  return result;
@@ -625,26 +906,37 @@ class Program {
625
906
  }
626
907
  }
627
908
  getStatementsByName(name, originFile, namespaceName) {
628
- var _a, _b;
629
909
  let results = new Map();
630
910
  const filesSearched = new Set();
631
911
  let lowerNamespaceName = namespaceName === null || namespaceName === void 0 ? void 0 : namespaceName.toLowerCase();
632
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
+ }
633
922
  //look through all files in scope for matches
634
923
  for (const scope of this.getScopesForFile(originFile)) {
635
924
  for (const file of scope.getAllFiles()) {
636
- 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)) {
637
927
  continue;
638
928
  }
639
929
  filesSearched.add(file);
640
- for (const statement of [...file.parser.references.functionStatements, ...file.parser.references.classStatements.flatMap((cs) => cs.methods)]) {
641
- let parentNamespaceName = (_b = (_a = statement.namespaceName) === null || _a === void 0 ? void 0 : _a.getName(originFile.parseMode)) === null || _b === void 0 ? void 0 : _b.toLowerCase();
642
- if (statement.name.text.toLowerCase() === lowerName && (!parentNamespaceName || parentNamespaceName === lowerNamespaceName)) {
643
- if (!results.has(statement)) {
644
- results.set(statement, { item: statement, file: file });
645
- }
930
+ file.ast.walk((0, visitors_1.createVisitor)({
931
+ FunctionStatement: (statement) => {
932
+ addToResults(statement, file);
933
+ },
934
+ MethodStatement: (statement) => {
935
+ addToResults(statement, file);
646
936
  }
647
- }
937
+ }), {
938
+ walkMode: visitors_1.WalkMode.visitStatements
939
+ });
648
940
  }
649
941
  }
650
942
  return [...results.values()];
@@ -657,94 +949,76 @@ class Program {
657
949
  let funcNames = new Set();
658
950
  let currentScope = scope;
659
951
  while ((0, reflection_1.isXmlScope)(currentScope)) {
660
- for (let member of (_b = (_a = currentScope.xmlFile.ast.component) === null || _a === void 0 ? void 0 : _a.interfaceMembers) !== null && _b !== void 0 ? _b : []) {
661
- if ((0, reflection_1.isSGInterfaceFunction)(member)) {
662
- const name = member.name;
663
- if (!filterName || name === filterName) {
664
- funcNames.add(name);
665
- }
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);
666
955
  }
667
956
  }
668
957
  currentScope = currentScope.getParentScope();
669
958
  }
670
959
  //look through all files in scope for matches
671
960
  for (const file of scope.getOwnFiles()) {
672
- 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)) {
673
963
  continue;
674
964
  }
675
965
  filesSearched.add(file);
676
- for (const statement of file.parser.references.functionStatements) {
677
- if (funcNames.has(statement.name.text)) {
678
- if (!results.has(statement)) {
679
- 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
+ }
680
972
  }
681
973
  }
682
- }
974
+ }), {
975
+ walkMode: visitors_1.WalkMode.visitStatements
976
+ });
683
977
  }
684
978
  return [...results.values()];
685
979
  }
686
980
  /**
687
981
  * Find all available completion items at the given position
688
- * @param srcPath The absolute path to the source file on disk
689
- * @param lineIndex
690
- * @param columnIndex
982
+ * @param filePath can be a srcPath or a destPath
983
+ * @param position the position (line & column) where completions should be found
691
984
  */
692
- getCompletions(srcPath, position) {
693
- let file = this.getFile(srcPath);
985
+ getCompletions(filePath, position) {
986
+ let file = this.getFile(filePath);
694
987
  if (!file) {
695
988
  return [];
696
989
  }
697
- let result = [];
698
- if ((0, reflection_1.isBrsFile)(file) && file.parser.isPositionNextToTokenKind(position, TokenKind_1.TokenKind.Callfunc)) {
699
- // is next to a @. callfunc invocation - must be an interface method
700
- for (const scope of this.getScopes().filter((s) => (0, reflection_1.isXmlScope)(s))) {
701
- let fileLinks = this.getStatementsForXmlFile(scope);
702
- for (let fileLink of fileLinks) {
703
- result.push(scope.createCompletionFromFunctionStatement(fileLink.item));
704
- }
705
- }
706
- //no other result is possible in this case
707
- return result;
708
- }
709
990
  //find the scopes for this file
710
991
  let scopes = this.getScopesForFile(file);
711
992
  //if there are no scopes, include the global scope so we at least get the built-in functions
712
993
  scopes = scopes.length > 0 ? scopes : [this.globalScope];
713
- //get the completions from all scopes for this file
714
- let allCompletions = util_1.util.flatMap(scopes.map(ctx => {
715
- ctx.linkSymbolTable();
716
- const completions = file.getCompletions(position, ctx);
717
- ctx.unlinkSymbolTable();
718
- return completions;
719
- }), c => c);
720
- //only keep completions common to every scope for this file
721
- let keyCounts = {};
722
- for (let completion of allCompletions) {
723
- let key = `${completion.label}-${completion.kind}`;
724
- keyCounts[key] = keyCounts[key] ? keyCounts[key] + 1 : 1;
725
- if (keyCounts[key] === scopes.length) {
726
- result.push(completion);
727
- }
728
- }
729
- return result;
994
+ const event = {
995
+ program: this,
996
+ file: file,
997
+ scopes: scopes,
998
+ position: position,
999
+ completions: []
1000
+ };
1001
+ this.plugins.emit('beforeProvideCompletions', event);
1002
+ this.plugins.emit('provideCompletions', event);
1003
+ this.plugins.emit('afterProvideCompletions', event);
1004
+ return event.completions;
730
1005
  }
731
1006
  /**
732
1007
  * Goes through each file and builds a list of workspace symbols for the program. Used by LanguageServer's onWorkspaceSymbol functionality
733
1008
  */
734
1009
  getWorkspaceSymbols() {
735
- const result = [];
736
- for (const key in this.files) {
1010
+ const results = Object.keys(this.files).map(key => {
737
1011
  const file = this.files[key];
738
1012
  if ((0, reflection_1.isBrsFile)(file)) {
739
- result.push(...file.getWorkspaceSymbols());
1013
+ return file.getWorkspaceSymbols();
740
1014
  }
741
- }
742
- return result;
1015
+ return [];
1016
+ });
1017
+ return util_1.util.flatMap(results, c => c);
743
1018
  }
744
1019
  /**
745
1020
  * Given a position in a file, if the position is sitting on some type of identifier,
746
1021
  * go to the definition of that identifier (where this thing was first defined)
747
- * @param srcPath The absolute path to the source file on disk
748
1022
  */
749
1023
  getDefinition(srcPath, position) {
750
1024
  let file = this.getFile(srcPath);
@@ -764,20 +1038,28 @@ class Program {
764
1038
  }
765
1039
  }
766
1040
  /**
767
- * @param srcPath The absolute path to the source file on disk
1041
+ * Get hover information for a file and position
768
1042
  */
769
1043
  getHover(srcPath, position) {
770
- //find the file
771
1044
  let file = this.getFile(srcPath);
772
- if (!file) {
773
- return null;
1045
+ let result;
1046
+ if (file) {
1047
+ const event = {
1048
+ program: this,
1049
+ file: file,
1050
+ position: position,
1051
+ scopes: this.getScopesForFile(file),
1052
+ hovers: []
1053
+ };
1054
+ this.plugins.emit('beforeProvideHover', event);
1055
+ this.plugins.emit('provideHover', event);
1056
+ this.plugins.emit('afterProvideHover', event);
1057
+ result = event.hovers;
774
1058
  }
775
- const hover = file.getHover(position);
776
- return Promise.resolve(hover);
1059
+ return result !== null && result !== void 0 ? result : [];
777
1060
  }
778
1061
  /**
779
1062
  * Compute code actions for the given file and range
780
- * @param srcPath The absolute path to the source file on disk
781
1063
  */
782
1064
  getCodeActions(srcPath, range) {
783
1065
  const codeActions = [];
@@ -789,7 +1071,7 @@ class Program {
789
1071
  //only keep diagnostics related to this file
790
1072
  .filter(x => x.file === file)
791
1073
  //only keep diagnostics that touch this range
792
- .filter(x => util_1.util.rangesIntersect(x.range, range));
1074
+ .filter(x => util_1.util.rangesIntersectOrTouch(x.range, range));
793
1075
  const scopes = this.getScopesForFile(file);
794
1076
  this.plugins.emit('onGetCodeActions', {
795
1077
  program: this,
@@ -819,277 +1101,23 @@ class Program {
819
1101
  }
820
1102
  }
821
1103
  getSignatureHelp(filepath, position) {
822
- var _a;
823
1104
  let file = this.getFile(filepath);
824
1105
  if (!file || !(0, reflection_1.isBrsFile)(file)) {
825
1106
  return [];
826
1107
  }
827
- const results = new Map();
828
- let functionExpression = file.getFunctionExpressionAtPosition(position);
829
- let identifierInfo = this.getPartialStatementInfo(file, position);
830
- if (identifierInfo.statementType === '') {
831
- // just general function calls
832
- let statements = file.program.getStatementsByName(identifierInfo.name, file);
833
- for (let statement of statements) {
834
- //TODO better handling of collisions - if it's a namespace, then don't show any other overrides
835
- //if we're on m - then limit scope to the current class, if present
836
- let sigHelp = statement.file.getSignatureHelpForStatement(statement.item);
837
- if (sigHelp && !results.has[sigHelp.key]) {
838
- sigHelp.index = identifierInfo.commaCount;
839
- results.set(sigHelp.key, sigHelp);
840
- }
841
- }
842
- }
843
- else if (identifierInfo.statementType === '.') {
844
- //if m class reference.. then
845
- //only get statements from the class I am in..
846
- if (functionExpression) {
847
- const currentToken = file.parser.getTokenAt(position);
848
- for (let scope of this.getScopesForFile(file)) {
849
- scope.linkSymbolTable();
850
- let myClass = file.getClassFromToken(currentToken, functionExpression, scope);
851
- if (myClass) {
852
- let classes = scope.getClassHierarchy(myClass.item.getName(Parser_1.ParseMode.BrighterScript).toLowerCase());
853
- //and anything from any class in scope to a non m class
854
- for (let statement of [...classes].filter((i) => (0, reflection_1.isMethodStatement)(i.item))) {
855
- let sigHelp = statement.file.getSignatureHelpForStatement(statement.item);
856
- if (sigHelp && !results.has[sigHelp.key]) {
857
- results.set(sigHelp.key, sigHelp);
858
- return;
859
- }
860
- }
861
- }
862
- scope.unlinkSymbolTable();
863
- }
864
- }
865
- if (identifierInfo.dotPart) {
866
- //potential namespaces
867
- let statements = file.program.getStatementsByName(identifierInfo.name, file, identifierInfo.dotPart);
868
- if (statements.length === 0) {
869
- //was not a namespaced function, it could be any method on any class now
870
- statements = file.program.getStatementsByName(identifierInfo.name, file);
871
- }
872
- for (let statement of statements) {
873
- //TODO better handling of collisions - if it's a namespace, then don't show any other overrides
874
- //if we're on m - then limit scope to the current class, if present
875
- let sigHelp = statement.file.getSignatureHelpForStatement(statement.item);
876
- if (sigHelp && !results.has[sigHelp.key]) {
877
- sigHelp.index = identifierInfo.commaCount;
878
- results.set(sigHelp.key, sigHelp);
879
- }
880
- }
881
- }
882
- }
883
- else if (identifierInfo.statementType === '@.') {
884
- for (const scope of this.getScopes().filter((s) => (0, reflection_1.isXmlScope)(s))) {
885
- let fileLinks = this.getStatementsForXmlFile(scope, identifierInfo.name);
886
- for (let fileLink of fileLinks) {
887
- let sigHelp = fileLink.file.getSignatureHelpForStatement(fileLink.item);
888
- if (sigHelp && !results.has[sigHelp.key]) {
889
- sigHelp.index = identifierInfo.commaCount;
890
- results.set(sigHelp.key, sigHelp);
891
- }
892
- }
893
- }
894
- }
895
- else if (identifierInfo.statementType === 'new') {
896
- let classItem = file.getClassFileLink(identifierInfo.dotPart ? `${identifierInfo.dotPart}.${identifierInfo.name}` : identifierInfo.name);
897
- 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);
898
- if (sigHelp && !results.has(sigHelp.key)) {
899
- sigHelp.index = identifierInfo.commaCount;
900
- results.set(sigHelp.key, sigHelp);
901
- }
902
- }
903
- return [...results.values()];
1108
+ let callExpressionInfo = new CallExpressionInfo_1.CallExpressionInfo(file, position);
1109
+ let signatureHelpUtil = new SignatureHelpUtil_1.SignatureHelpUtil();
1110
+ return signatureHelpUtil.getSignatureHelpItems(callExpressionInfo);
904
1111
  }
905
- getPartialStatementInfo(file, position) {
906
- let lines = util_1.util.splitIntoLines(file.fileContents);
907
- let line = lines[position.line];
908
- let index = position.character;
909
- let itemCounts = this.getPartialItemCounts(line, index);
910
- if (!itemCounts.isArgStartFound && line.charAt(index) === ')') {
911
- //try previous char, in case we were on a close bracket..
912
- index--;
913
- itemCounts = this.getPartialItemCounts(line, index);
914
- }
915
- let argStartIndex = itemCounts.argStartIndex;
916
- index = itemCounts.argStartIndex - 1;
917
- let statementType = '';
918
- let name;
919
- let dotPart;
920
- if (!itemCounts.isArgStartFound) {
921
- //try to get sig help based on the name
922
- index = position.character;
923
- let currentToken = file.parser.getTokenAt(position);
924
- if (currentToken && currentToken.kind !== TokenKind_1.TokenKind.Comment) {
925
- name = file.getPartialVariableName(currentToken, [TokenKind_1.TokenKind.New]);
926
- if (!name) {
927
- //try the previous token, incase we're on a bracket
928
- currentToken = file.parser.getPreviousToken(currentToken);
929
- name = file.getPartialVariableName(currentToken, [TokenKind_1.TokenKind.New]);
930
- }
931
- if (name === null || name === void 0 ? void 0 : name.indexOf('.')) {
932
- let parts = name.split('.');
933
- name = parts[parts.length - 1];
934
- }
935
- index = currentToken.range.start.character;
936
- argStartIndex = index;
937
- }
938
- else {
939
- // invalid location
940
- index = 0;
941
- itemCounts.comma = 0;
942
- }
943
- }
944
- //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).
945
- while (index >= -1) {
946
- if (!(/[a-z0-9_\.\@]/i).test(line.charAt(index))) {
947
- if (!name) {
948
- name = line.substring(index + 1, argStartIndex);
949
- }
950
- else {
951
- dotPart = line.substring(index + 1, argStartIndex);
952
- if (dotPart.endsWith('.')) {
953
- dotPart = dotPart.substr(0, dotPart.length - 1);
954
- }
955
- }
956
- break;
957
- }
958
- if (line.substr(index - 2, 2) === '@.') {
959
- statementType = '@.';
960
- name = name || line.substring(index, argStartIndex);
961
- break;
962
- }
963
- else if (line.charAt(index - 1) === '.' && statementType === '') {
964
- statementType = '.';
965
- name = name || line.substring(index, argStartIndex);
966
- argStartIndex = index;
967
- }
968
- index--;
969
- }
970
- if (line.substring(0, index).trim().endsWith('new')) {
971
- statementType = 'new';
972
- }
973
- return {
974
- commaCount: itemCounts.comma,
975
- statementType: statementType,
976
- name: name,
977
- dotPart: dotPart
978
- };
979
- }
980
- getPartialItemCounts(line, index) {
981
- let isArgStartFound = false;
982
- let itemCounts = {
983
- normal: 0,
984
- square: 0,
985
- curly: 0,
986
- comma: 0,
987
- endIndex: 0,
988
- argStartIndex: index,
989
- isArgStartFound: false
990
- };
991
- while (index >= 0) {
992
- const currentChar = line.charAt(index);
993
- if (currentChar === '\'') { //found comment, invalid index
994
- itemCounts.isArgStartFound = false;
995
- break;
996
- }
997
- if (isArgStartFound) {
998
- if (currentChar !== ' ') {
999
- break;
1000
- }
1001
- }
1002
- else {
1003
- if (currentChar === ')') {
1004
- itemCounts.normal++;
1005
- }
1006
- if (currentChar === ']') {
1007
- itemCounts.square++;
1008
- }
1009
- if (currentChar === '}') {
1010
- itemCounts.curly++;
1011
- }
1012
- if (currentChar === ',' && itemCounts.normal <= 0 && itemCounts.curly <= 0 && itemCounts.square <= 0) {
1013
- itemCounts.comma++;
1014
- }
1015
- if (currentChar === '(') {
1016
- if (itemCounts.normal === 0) {
1017
- itemCounts.isArgStartFound = true;
1018
- itemCounts.argStartIndex = index;
1019
- }
1020
- else {
1021
- itemCounts.normal--;
1022
- }
1023
- }
1024
- if (currentChar === '[') {
1025
- itemCounts.square--;
1026
- }
1027
- if (currentChar === '{') {
1028
- itemCounts.curly--;
1029
- }
1030
- }
1031
- index--;
1032
- }
1033
- return itemCounts;
1034
- }
1035
- /**
1036
- * @param srcPath The absolute path to the source file on disk
1037
- */
1038
1112
  getReferences(srcPath, position) {
1039
1113
  //find the file
1040
1114
  let file = this.getFile(srcPath);
1041
- if (!file) {
1042
- return null;
1115
+ if ((0, reflection_1.isBrsFile)(file) || (0, reflection_1.isXmlFile)(file)) {
1116
+ return file.getReferences(position);
1043
1117
  }
1044
- return file.getReferences(position);
1045
- }
1046
- /**
1047
- * Get a list of all script imports, relative to the specified pkgPath
1048
- * @param sourcePkgPath - the pkgPath of the source that wants to resolve script imports.
1049
- */
1050
- getScriptImportCompletions(sourcePkgPath, scriptImport) {
1051
- let lowerSourcePkgPath = sourcePkgPath.toLowerCase();
1052
- let result = [];
1053
- /**
1054
- * hashtable to prevent duplicate results
1055
- */
1056
- let resultPkgPaths = {};
1057
- //restrict to only .brs files
1058
- for (const key in this.files) {
1059
- const file = this.files[key];
1060
- if (
1061
- //is a BrightScript or BrighterScript file
1062
- (file.extension === '.bs' || file.extension === '.brs') &&
1063
- //this file is not the current file
1064
- lowerSourcePkgPath !== file.pkgPath.toLowerCase()) {
1065
- //add the relative path
1066
- let relativePath = util_1.util.getRelativePath(sourcePkgPath, file.pkgPath).replace(/\\/g, '/');
1067
- const lowerPkgPath = file.pkgPath.toLowerCase();
1068
- if (!resultPkgPaths[lowerPkgPath]) {
1069
- resultPkgPaths[lowerPkgPath] = true;
1070
- result.push({
1071
- label: relativePath,
1072
- detail: file.srcPath,
1073
- kind: vscode_languageserver_1.CompletionItemKind.File,
1074
- textEdit: {
1075
- newText: relativePath,
1076
- range: scriptImport.filePathRange
1077
- }
1078
- });
1079
- //add the absolute path
1080
- result.push({
1081
- label: file.pkgPath,
1082
- detail: file.srcPath,
1083
- kind: vscode_languageserver_1.CompletionItemKind.File,
1084
- textEdit: {
1085
- newText: file.pkgPath,
1086
- range: scriptImport.filePathRange
1087
- }
1088
- });
1089
- }
1090
- }
1118
+ else {
1119
+ return null;
1091
1120
  }
1092
- return result;
1093
1121
  }
1094
1122
  /**
1095
1123
  * Transpile a single file and get the result as a string.
@@ -1100,160 +1128,215 @@ class Program {
1100
1128
  * @param filePath can be a srcPath or a destPath
1101
1129
  */
1102
1130
  async getTranspiledFileContents(filePath) {
1103
- const { entries, astEditor } = this.beforeProgramTranspile(await roku_deploy_1.rokuDeploy.getFilePaths(this.options.files, this.options.rootDir), this.options.stagingFolderPath);
1104
- const result = this._getTranspiledFileContents(this.getFile(filePath));
1105
- 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 : '/')}`;
1106
1191
  return result;
1107
1192
  }
1108
1193
  /**
1109
- * Internal function used to transpile files.
1110
- * 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
1111
1196
  */
1112
- _getTranspiledFileContents(file, outputPath) {
1113
- const editor = new AstEditor_1.AstEditor();
1114
- this.plugins.emit('beforeFileTranspile', {
1197
+ async prepare(files) {
1198
+ const programEvent = {
1115
1199
  program: this,
1116
- file: file,
1117
- outputPath: outputPath,
1118
- 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
+ }
1119
1220
  });
1120
- //if we have any edits, assume the file needs to be transpiled
1121
- if (editor.hasChanges) {
1122
- //use the `editor` because it'll track the previous value for us and revert later on
1123
- editor.setProperty(file, 'needsTranspiled', true);
1124
- }
1125
- //transpile the file
1126
- const result = file.transpile();
1127
- //generate the typedef if enabled
1128
- let typedef;
1129
- if ((0, reflection_1.isBrsFile)(file) && this.options.emitDefinitions) {
1130
- 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);
1131
1241
  }
1132
- const event = {
1133
- program: this,
1134
- file: file,
1135
- outputPath: outputPath,
1136
- editor: editor,
1137
- code: result.code,
1138
- map: result.map,
1139
- typedef: typedef
1140
- };
1141
- this.plugins.emit('afterFileTranspile', event);
1142
- //undo all `editor` edits that may have been applied to this file.
1143
- editor.undoAll();
1144
- return {
1145
- srcPath: file.srcPath,
1146
- pkgPath: file.pkgPath,
1147
- code: event.code,
1148
- map: event.map,
1149
- typedef: event.typedef
1150
- };
1242
+ await this.plugins.emitAsync('afterPrepareProgram', programEvent);
1243
+ return files;
1151
1244
  }
1152
- beforeProgramTranspile(fileEntries, stagingFolderPath) {
1153
- // map fileEntries using their path as key, to avoid excessive "find()" operations
1154
- const mappedFileEntries = fileEntries.reduce((collection, entry) => {
1155
- collection[(0, util_1.standardizePath) `${entry.src}`] = entry;
1156
- return collection;
1157
- }, {});
1158
- const getOutputPath = (file) => {
1159
- let filePathObj = mappedFileEntries[(0, util_1.standardizePath) `${file.srcPath}`];
1160
- if (!filePathObj) {
1161
- //this file has been added in-memory, from a plugin, for example
1162
- filePathObj = {
1163
- //add an interpolated src path (since it doesn't actually exist in memory)
1164
- src: `bsc-in-memory:/${util_1.util.removeProtocol(file.pkgPath)}`,
1165
- dest: file.pkgPath
1166
- };
1167
- }
1168
- //prep the output path
1169
- let outputPath = filePathObj.dest
1170
- //replace any leading protocol
1171
- .replace(/^[-a-z_]+:\//, '')
1172
- //change any .bs file extension to .brs
1173
- .replace(/\.bs$/gi, '.brs');
1174
- //prepend the staging folder path
1175
- outputPath = (0, util_1.standardizePath) `${stagingFolderPath}/${outputPath}`;
1176
- return outputPath;
1177
- };
1178
- const entries = Object.values(this.files).map(file => {
1179
- 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,
1180
1272
  file: file,
1181
- outputPath: getOutputPath(file)
1273
+ result: allFiles
1182
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
1183
1283
  });
1184
- const astEditor = new AstEditor_1.AstEditor();
1185
- 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', {
1186
1291
  program: this,
1187
- entries: entries,
1188
- editor: astEditor
1292
+ files: files,
1293
+ stagingDir: stagingDir
1189
1294
  });
1190
- return {
1191
- entries: entries,
1192
- getOutputPath: getOutputPath,
1193
- astEditor: astEditor
1194
- };
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);
1195
1312
  }
1196
- async transpile(fileEntries, stagingFolderPath) {
1197
- const { entries, getOutputPath, astEditor } = this.beforeProgramTranspile(fileEntries, stagingFolderPath);
1198
- const processedFiles = new Set();
1199
- const transpileFile = async (srcPath, outputPath) => {
1200
- //find the file in the program
1201
- const file = this.getFile(srcPath);
1202
- //mark this file as processed so we don't process it more than once
1203
- processedFiles.add(file);
1204
- //skip transpiling typedef files
1205
- if ((0, reflection_1.isBrsFile)(file) && file.isTypedef) {
1206
- return;
1207
- }
1208
- const fileTranspileResult = this._getTranspiledFileContents(file, outputPath);
1209
- //make sure the full dir path exists
1210
- await fsExtra.ensureDir(path.dirname(outputPath));
1211
- if (await fsExtra.pathExists(outputPath)) {
1212
- throw new Error(`Error while transpiling "${file.srcPath}". A file already exists at "${outputPath}" and will not be overwritten.`);
1213
- }
1214
- const writeMapPromise = fileTranspileResult.map ? fsExtra.writeFile(`${outputPath}.map`, fileTranspileResult.map.toString()) : null;
1215
- await Promise.all([
1216
- fsExtra.writeFile(outputPath, fileTranspileResult.code),
1217
- writeMapPromise
1218
- ]);
1219
- if (fileTranspileResult.typedef) {
1220
- const typedefPath = outputPath.replace(/\.brs$/i, '.d.bs');
1221
- await fsExtra.writeFile(typedefPath, fileTranspileResult.typedef);
1222
- }
1223
- };
1224
- 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 () => {
1225
1320
  var _a;
1226
- return transpileFile((_a = entry === null || entry === void 0 ? void 0 : entry.file) === null || _a === void 0 ? void 0 : _a.srcPath, entry.outputPath);
1227
- });
1228
- //if there's no bslib file already loaded into the program, copy it to the staging directory
1229
- if (!this.getFile(bslibAliasedRokuModulesPkgPath) && !this.getFile((0, util_1.standardizePath) `pkg:/source/bslib.brs`)) {
1230
- promises.push(util_1.util.copyBslibToStaging(stagingFolderPath));
1231
- }
1232
- await Promise.all(promises);
1233
- //transpile any new files that plugins added since the start of this transpile process
1234
- do {
1235
- promises = [];
1236
- for (const key in this.files) {
1237
- const file = this.files[key];
1238
- //this is a new file
1239
- if (!processedFiles.has(file)) {
1240
- promises.push(transpileFile(file === null || file === void 0 ? void 0 : file.srcPath, getOutputPath(file)));
1241
- }
1242
- }
1243
- if (promises.length > 0) {
1244
- this.logger.info(`Transpiling ${promises.length} new files`);
1245
- 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();
1246
1338
  }
1247
- } while (promises.length > 0);
1248
- this.afterProgramTranspile(entries, astEditor);
1249
- }
1250
- afterProgramTranspile(entries, astEditor) {
1251
- this.plugins.emit('afterProgramTranspile', {
1252
- program: this,
1253
- entries: entries,
1254
- editor: astEditor
1255
1339
  });
1256
- astEditor.undoAll();
1257
1340
  }
1258
1341
  /**
1259
1342
  * Find a list of files in the program that have a function with the given name (case INsensitive)
@@ -1266,7 +1349,8 @@ class Program {
1266
1349
  if ((0, reflection_1.isBrsFile)(file)) {
1267
1350
  //TODO handle namespace-relative function calls
1268
1351
  //if the file has a function with this name
1269
- if (file.parser.references.functionStatementLookup.get(lowerFunctionName) !== undefined) {
1352
+ // eslint-disable-next-line @typescript-eslint/dot-notation
1353
+ if (file['_cachedLookups'].functionStatementMap.get(lowerFunctionName)) {
1270
1354
  files.push(file);
1271
1355
  }
1272
1356
  }
@@ -1284,45 +1368,132 @@ class Program {
1284
1368
  if ((0, reflection_1.isBrsFile)(file)) {
1285
1369
  //TODO handle namespace-relative classes
1286
1370
  //if the file has a function with this name
1287
- 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) {
1373
+ files.push(file);
1374
+ }
1375
+ }
1376
+ }
1377
+ return files;
1378
+ }
1379
+ findFilesForNamespace(name) {
1380
+ const files = [];
1381
+ const lowerName = name.toLowerCase();
1382
+ //find every file with this class defined
1383
+ for (const file of Object.values(this.files)) {
1384
+ if ((0, reflection_1.isBrsFile)(file)) {
1385
+ // eslint-disable-next-line @typescript-eslint/dot-notation
1386
+ if (file['_cachedLookups'].namespaceStatements.find((x) => {
1387
+ const namespaceName = x.name.toLowerCase();
1388
+ return (
1389
+ //the namespace name matches exactly
1390
+ namespaceName === lowerName ||
1391
+ //the full namespace starts with the name (honoring the part boundary)
1392
+ namespaceName.startsWith(lowerName + '.'));
1393
+ })) {
1394
+ files.push(file);
1395
+ }
1396
+ }
1397
+ }
1398
+ return files;
1399
+ }
1400
+ findFilesForEnum(name) {
1401
+ const files = [];
1402
+ const lowerName = name.toLowerCase();
1403
+ //find every file with this enum defined
1404
+ for (const file of Object.values(this.files)) {
1405
+ if ((0, reflection_1.isBrsFile)(file)) {
1406
+ // eslint-disable-next-line @typescript-eslint/dot-notation
1407
+ if (file['_cachedLookups'].enumStatementMap.get(lowerName)) {
1288
1408
  files.push(file);
1289
1409
  }
1290
1410
  }
1291
1411
  }
1292
1412
  return files;
1293
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
+ }
1294
1464
  /**
1295
1465
  * Get a map of the manifest information
1296
1466
  */
1297
1467
  getManifest() {
1298
1468
  if (!this._manifest) {
1299
- //load the manifest file.
1300
- //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
1301
- let manifestPath = path.join(this.options.rootDir, 'manifest');
1302
- let contents;
1303
- try {
1304
- //we only load this manifest once, so do it sync to improve speed downstream
1305
- contents = fsExtra.readFileSync(manifestPath, 'utf-8');
1306
- this._manifest = (0, Manifest_1.parseManifest)(contents);
1307
- }
1308
- catch (err) {
1309
- this._manifest = new Map();
1310
- }
1469
+ this.loadManifest();
1311
1470
  }
1312
1471
  return this._manifest;
1313
1472
  }
1314
1473
  dispose() {
1315
- var _a, _b;
1316
- for (const key in this.files) {
1317
- const file = this.files[key];
1318
- (_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);
1319
1478
  }
1320
1479
  for (let name in this.scopes) {
1321
- (_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);
1322
1481
  }
1323
- this.globalScope.dispose();
1324
- 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);
1325
1484
  }
1326
1485
  }
1327
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
+ }
1328
1499
  //# sourceMappingURL=Program.js.map