brighterscript 1.0.0-alpha.3 → 1.0.0-alpha.31

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