brighterscript 0.66.0-alpha.1 → 0.66.0-alpha.10

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 (380) hide show
  1. package/CHANGELOG.md +215 -10
  2. package/README.md +13 -3
  3. package/bsconfig.schema.json +15 -0
  4. package/dist/ActionPipeline.d.ts +10 -0
  5. package/dist/ActionPipeline.js +40 -0
  6. package/dist/ActionPipeline.js.map +1 -0
  7. package/dist/AstValidationSegmenter.d.ts +25 -0
  8. package/dist/AstValidationSegmenter.js +150 -0
  9. package/dist/AstValidationSegmenter.js.map +1 -0
  10. package/dist/BsConfig.d.ts +13 -4
  11. package/dist/BusyStatusTracker.d.ts +31 -0
  12. package/dist/BusyStatusTracker.js +83 -0
  13. package/dist/BusyStatusTracker.js.map +1 -0
  14. package/dist/Cache.js +3 -3
  15. package/dist/Cache.js.map +1 -1
  16. package/dist/CacheVerifier.d.ts +0 -1
  17. package/dist/CodeActionUtil.d.ts +2 -2
  18. package/dist/CommentFlagProcessor.d.ts +4 -3
  19. package/dist/CommentFlagProcessor.js.map +1 -1
  20. package/dist/DiagnosticCollection.js +8 -5
  21. package/dist/DiagnosticCollection.js.map +1 -1
  22. package/dist/DiagnosticMessages.d.ts +34 -4
  23. package/dist/DiagnosticMessages.js +59 -4
  24. package/dist/DiagnosticMessages.js.map +1 -1
  25. package/dist/FunctionScope.d.ts +1 -1
  26. package/dist/LanguageServer.d.ts +23 -1
  27. package/dist/LanguageServer.js +139 -57
  28. package/dist/LanguageServer.js.map +1 -1
  29. package/dist/Logger.d.ts +3 -2
  30. package/dist/Logger.js +10 -2
  31. package/dist/Logger.js.map +1 -1
  32. package/dist/PluginInterface.d.ts +11 -2
  33. package/dist/PluginInterface.js +69 -10
  34. package/dist/PluginInterface.js.map +1 -1
  35. package/dist/Program.d.ts +138 -49
  36. package/dist/Program.js +637 -349
  37. package/dist/Program.js.map +1 -1
  38. package/dist/ProgramBuilder.d.ts +10 -4
  39. package/dist/ProgramBuilder.js +76 -74
  40. package/dist/ProgramBuilder.js.map +1 -1
  41. package/dist/Scope.d.ts +52 -49
  42. package/dist/Scope.js +298 -274
  43. package/dist/Scope.js.map +1 -1
  44. package/dist/SymbolTable.d.ts +35 -14
  45. package/dist/SymbolTable.js +90 -29
  46. package/dist/SymbolTable.js.map +1 -1
  47. package/dist/Throttler.d.ts +12 -0
  48. package/dist/Throttler.js +39 -0
  49. package/dist/Throttler.js.map +1 -1
  50. package/dist/XmlScope.d.ts +7 -4
  51. package/dist/XmlScope.js +52 -12
  52. package/dist/XmlScope.js.map +1 -1
  53. package/dist/astUtils/{AstEditor.d.ts → Editor.d.ts} +6 -1
  54. package/dist/astUtils/{AstEditor.js → Editor.js} +9 -3
  55. package/dist/astUtils/Editor.js.map +1 -0
  56. package/dist/astUtils/{AstEditor.spec.js → Editor.spec.js} +10 -6
  57. package/dist/astUtils/Editor.spec.js.map +1 -0
  58. package/dist/astUtils/creators.d.ts +3 -1
  59. package/dist/astUtils/creators.js +14 -4
  60. package/dist/astUtils/creators.js.map +1 -1
  61. package/dist/astUtils/reflection.d.ts +37 -9
  62. package/dist/astUtils/reflection.js +83 -14
  63. package/dist/astUtils/reflection.js.map +1 -1
  64. package/dist/astUtils/reflection.spec.js +87 -5
  65. package/dist/astUtils/reflection.spec.js.map +1 -1
  66. package/dist/astUtils/visitors.d.ts +14 -3
  67. package/dist/astUtils/visitors.js +22 -2
  68. package/dist/astUtils/visitors.js.map +1 -1
  69. package/dist/astUtils/visitors.spec.js +58 -7
  70. package/dist/astUtils/visitors.spec.js.map +1 -1
  71. package/dist/bscPlugin/BscPlugin.d.ts +10 -2
  72. package/dist/bscPlugin/BscPlugin.js +24 -4
  73. package/dist/bscPlugin/BscPlugin.js.map +1 -1
  74. package/dist/bscPlugin/CallExpressionInfo.d.ts +3 -3
  75. package/dist/bscPlugin/CallExpressionInfo.js.map +1 -1
  76. package/dist/bscPlugin/FileWriter.d.ts +6 -0
  77. package/dist/bscPlugin/FileWriter.js +24 -0
  78. package/dist/bscPlugin/FileWriter.js.map +1 -0
  79. package/dist/bscPlugin/codeActions/CodeActionsProcessor.js +8 -8
  80. package/dist/bscPlugin/codeActions/CodeActionsProcessor.js.map +1 -1
  81. package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js +4 -4
  82. package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js.map +1 -1
  83. package/dist/bscPlugin/completions/CompletionsProcessor.d.ts +50 -1
  84. package/dist/bscPlugin/completions/CompletionsProcessor.js +442 -23
  85. package/dist/bscPlugin/completions/CompletionsProcessor.js.map +1 -1
  86. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js +1737 -0
  87. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js.map +1 -0
  88. package/dist/bscPlugin/fileProviders/FileProvider.d.ts +9 -0
  89. package/dist/bscPlugin/fileProviders/FileProvider.js +51 -0
  90. package/dist/bscPlugin/fileProviders/FileProvider.js.map +1 -0
  91. package/dist/bscPlugin/hover/HoverProcessor.d.ts +7 -3
  92. package/dist/bscPlugin/hover/HoverProcessor.js +133 -103
  93. package/dist/bscPlugin/hover/HoverProcessor.js.map +1 -1
  94. package/dist/bscPlugin/hover/HoverProcessor.spec.js +241 -29
  95. package/dist/bscPlugin/hover/HoverProcessor.spec.js.map +1 -1
  96. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.d.ts +1 -0
  97. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js +43 -0
  98. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js.map +1 -1
  99. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js +22 -0
  100. package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js.map +1 -1
  101. package/dist/bscPlugin/serialize/BslibInjector.spec.js +19 -0
  102. package/dist/bscPlugin/serialize/BslibInjector.spec.js.map +1 -0
  103. package/dist/bscPlugin/serialize/BslibManager.d.ts +9 -0
  104. package/dist/bscPlugin/serialize/BslibManager.js +40 -0
  105. package/dist/bscPlugin/serialize/BslibManager.js.map +1 -0
  106. package/dist/bscPlugin/serialize/FileSerializer.d.ts +9 -0
  107. package/dist/bscPlugin/serialize/FileSerializer.js +72 -0
  108. package/dist/bscPlugin/serialize/FileSerializer.js.map +1 -0
  109. package/dist/bscPlugin/transpile/{BrsFilePreTranspileProcessor.d.ts → BrsFileTranspileProcessor.d.ts} +4 -2
  110. package/dist/bscPlugin/transpile/{BrsFilePreTranspileProcessor.js → BrsFileTranspileProcessor.js} +29 -5
  111. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.js.map +1 -0
  112. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.d.ts +1 -0
  113. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.js +41 -0
  114. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.spec.js.map +1 -0
  115. package/dist/bscPlugin/transpile/XmlFilePreTranspileProcessor.d.ts +2 -2
  116. package/dist/bscPlugin/transpile/XmlFilePreTranspileProcessor.js.map +1 -1
  117. package/dist/bscPlugin/validation/BrsFileValidator.d.ts +0 -4
  118. package/dist/bscPlugin/validation/BrsFileValidator.js +34 -29
  119. package/dist/bscPlugin/validation/BrsFileValidator.js.map +1 -1
  120. package/dist/bscPlugin/validation/BrsFileValidator.spec.js +1 -1
  121. package/dist/bscPlugin/validation/BrsFileValidator.spec.js.map +1 -1
  122. package/dist/bscPlugin/validation/ScopeValidator.d.ts +28 -7
  123. package/dist/bscPlugin/validation/ScopeValidator.js +393 -205
  124. package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
  125. package/dist/bscPlugin/validation/ScopeValidator.spec.d.ts +1 -0
  126. package/dist/bscPlugin/validation/ScopeValidator.spec.js +2038 -0
  127. package/dist/bscPlugin/validation/ScopeValidator.spec.js.map +1 -0
  128. package/dist/bscPlugin/validation/XmlFileValidator.js +2 -2
  129. package/dist/bscPlugin/validation/XmlFileValidator.js.map +1 -1
  130. package/dist/cli.js +104 -13
  131. package/dist/cli.js.map +1 -1
  132. package/dist/deferred.d.ts +3 -3
  133. package/dist/deferred.js.map +1 -1
  134. package/dist/diagnosticUtils.d.ts +8 -2
  135. package/dist/diagnosticUtils.js +45 -16
  136. package/dist/diagnosticUtils.js.map +1 -1
  137. package/dist/examples/plugins/removePrint.js +1 -1
  138. package/dist/examples/plugins/removePrint.js.map +1 -1
  139. package/dist/files/AssetFile.d.ts +26 -0
  140. package/dist/files/AssetFile.js +26 -0
  141. package/dist/files/AssetFile.js.map +1 -0
  142. package/dist/files/BrsFile.Class.spec.js +383 -56
  143. package/dist/files/BrsFile.Class.spec.js.map +1 -1
  144. package/dist/files/BrsFile.d.ts +73 -46
  145. package/dist/files/BrsFile.js +370 -534
  146. package/dist/files/BrsFile.js.map +1 -1
  147. package/dist/files/BrsFile.spec.js +1139 -682
  148. package/dist/files/BrsFile.spec.js.map +1 -1
  149. package/dist/files/Factory.d.ts +25 -0
  150. package/dist/files/Factory.js +22 -0
  151. package/dist/files/Factory.js.map +1 -0
  152. package/dist/files/File.d.ts +106 -0
  153. package/dist/files/File.js +16 -0
  154. package/dist/files/File.js.map +1 -0
  155. package/dist/files/LazyFileData.d.ts +20 -0
  156. package/dist/files/LazyFileData.js +54 -0
  157. package/dist/files/LazyFileData.js.map +1 -0
  158. package/dist/files/LazyFileData.spec.d.ts +1 -0
  159. package/dist/files/LazyFileData.spec.js +27 -0
  160. package/dist/files/LazyFileData.spec.js.map +1 -0
  161. package/dist/files/XmlFile.d.ts +56 -23
  162. package/dist/files/XmlFile.js +88 -60
  163. package/dist/files/XmlFile.js.map +1 -1
  164. package/dist/files/XmlFile.spec.js +64 -93
  165. package/dist/files/XmlFile.spec.js.map +1 -1
  166. package/dist/files/tests/imports.spec.js +21 -8
  167. package/dist/files/tests/imports.spec.js.map +1 -1
  168. package/dist/files/tests/optionalChaning.spec.js +14 -14
  169. package/dist/files/tests/optionalChaning.spec.js.map +1 -1
  170. package/dist/globalCallables.js +88 -84
  171. package/dist/globalCallables.js.map +1 -1
  172. package/dist/index.d.ts +9 -1
  173. package/dist/index.js +9 -1
  174. package/dist/index.js.map +1 -1
  175. package/dist/interfaces.d.ts +389 -94
  176. package/dist/interfaces.js +13 -2
  177. package/dist/interfaces.js.map +1 -1
  178. package/dist/lexer/Lexer.d.ts +12 -0
  179. package/dist/lexer/Lexer.js +28 -8
  180. package/dist/lexer/Lexer.js.map +1 -1
  181. package/dist/lexer/Lexer.spec.js +40 -0
  182. package/dist/lexer/Lexer.spec.js.map +1 -1
  183. package/dist/lexer/Token.d.ts +4 -0
  184. package/dist/lexer/Token.js.map +1 -1
  185. package/dist/lexer/TokenKind.d.ts +5 -0
  186. package/dist/lexer/TokenKind.js +14 -2
  187. package/dist/lexer/TokenKind.js.map +1 -1
  188. package/dist/parser/AstNode.d.ts +9 -2
  189. package/dist/parser/AstNode.js +16 -0
  190. package/dist/parser/AstNode.js.map +1 -1
  191. package/dist/parser/BrsTranspileState.d.ts +3 -2
  192. package/dist/parser/BrsTranspileState.js +3 -2
  193. package/dist/parser/BrsTranspileState.js.map +1 -1
  194. package/dist/parser/Expression.d.ts +21 -5
  195. package/dist/parser/Expression.js +128 -35
  196. package/dist/parser/Expression.js.map +1 -1
  197. package/dist/parser/Parser.Class.spec.js +103 -1
  198. package/dist/parser/Parser.Class.spec.js.map +1 -1
  199. package/dist/parser/Parser.d.ts +7 -0
  200. package/dist/parser/Parser.js +117 -21
  201. package/dist/parser/Parser.js.map +1 -1
  202. package/dist/parser/Parser.spec.js +557 -5
  203. package/dist/parser/Parser.spec.js.map +1 -1
  204. package/dist/parser/SGParser.d.ts +4 -4
  205. package/dist/parser/SGParser.js +3 -3
  206. package/dist/parser/SGParser.js.map +1 -1
  207. package/dist/parser/SGParser.spec.js +2 -2
  208. package/dist/parser/SGParser.spec.js.map +1 -1
  209. package/dist/parser/SGTypes.d.ts +2 -2
  210. package/dist/parser/Statement.d.ts +37 -12
  211. package/dist/parser/Statement.js +153 -46
  212. package/dist/parser/Statement.js.map +1 -1
  213. package/dist/parser/tests/Parser.spec.js +2 -1
  214. package/dist/parser/tests/Parser.spec.js.map +1 -1
  215. package/dist/parser/tests/controlFlow/For.spec.js +16 -8
  216. package/dist/parser/tests/controlFlow/For.spec.js.map +1 -1
  217. package/dist/parser/tests/controlFlow/ForEach.spec.js +12 -6
  218. package/dist/parser/tests/controlFlow/ForEach.spec.js.map +1 -1
  219. package/dist/parser/tests/controlFlow/While.spec.js +8 -4
  220. package/dist/parser/tests/controlFlow/While.spec.js.map +1 -1
  221. package/dist/parser/tests/expression/Call.spec.js +4 -4
  222. package/dist/parser/tests/expression/Call.spec.js.map +1 -1
  223. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js +29 -29
  224. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js.map +1 -1
  225. package/dist/parser/tests/expression/RegexLiteralExpression.spec.js +10 -10
  226. package/dist/parser/tests/expression/RegexLiteralExpression.spec.js.map +1 -1
  227. package/dist/parser/tests/expression/SourceLiteralExpression.spec.js +24 -24
  228. package/dist/parser/tests/expression/SourceLiteralExpression.spec.js.map +1 -1
  229. package/dist/parser/tests/expression/TemplateStringExpression.spec.js +75 -36
  230. package/dist/parser/tests/expression/TemplateStringExpression.spec.js.map +1 -1
  231. package/dist/parser/tests/expression/TernaryExpression.spec.js +36 -36
  232. package/dist/parser/tests/expression/TernaryExpression.spec.js.map +1 -1
  233. package/dist/parser/tests/expression/UnaryExpression.spec.d.ts +1 -0
  234. package/dist/parser/tests/expression/UnaryExpression.spec.js +52 -0
  235. package/dist/parser/tests/expression/UnaryExpression.spec.js.map +1 -0
  236. package/dist/parser/tests/statement/ConstStatement.spec.js +71 -22
  237. package/dist/parser/tests/statement/ConstStatement.spec.js.map +1 -1
  238. package/dist/parser/tests/statement/Continue.spec.js +2 -2
  239. package/dist/parser/tests/statement/Continue.spec.js.map +1 -1
  240. package/dist/parser/tests/statement/Enum.spec.js +38 -285
  241. package/dist/parser/tests/statement/Enum.spec.js.map +1 -1
  242. package/dist/parser/tests/statement/For.spec.js +6 -6
  243. package/dist/parser/tests/statement/For.spec.js.map +1 -1
  244. package/dist/parser/tests/statement/ForEach.spec.js +4 -4
  245. package/dist/parser/tests/statement/ForEach.spec.js.map +1 -1
  246. package/dist/parser/tests/statement/InterfaceStatement.spec.js +26 -10
  247. package/dist/parser/tests/statement/InterfaceStatement.spec.js.map +1 -1
  248. package/dist/parser/tests/statement/PrintStatement.spec.js +16 -13
  249. package/dist/parser/tests/statement/PrintStatement.spec.js.map +1 -1
  250. package/dist/parser/tests/statement/ReturnStatement.spec.js +5 -3
  251. package/dist/parser/tests/statement/ReturnStatement.spec.js.map +1 -1
  252. package/dist/parser/tests/statement/Set.spec.js +26 -13
  253. package/dist/parser/tests/statement/Set.spec.js.map +1 -1
  254. package/dist/preprocessor/Manifest.d.ts +1 -1
  255. package/dist/preprocessor/Manifest.js +2 -2
  256. package/dist/preprocessor/Manifest.js.map +1 -1
  257. package/dist/roku-types/data.json +243 -293
  258. package/dist/roku-types/index.d.ts +17 -38
  259. package/dist/types/ArrayType.d.ts +4 -1
  260. package/dist/types/ArrayType.js +46 -6
  261. package/dist/types/ArrayType.js.map +1 -1
  262. package/dist/types/ArrayType.spec.js +32 -3
  263. package/dist/types/ArrayType.spec.js.map +1 -1
  264. package/dist/types/AssociativeArrayType.d.ts +11 -0
  265. package/dist/types/AssociativeArrayType.js +52 -0
  266. package/dist/types/AssociativeArrayType.js.map +1 -0
  267. package/dist/types/BaseFunctionType.d.ts +9 -0
  268. package/dist/types/BaseFunctionType.js +25 -0
  269. package/dist/types/BaseFunctionType.js.map +1 -0
  270. package/dist/types/BooleanType.d.ts +2 -1
  271. package/dist/types/BooleanType.js +8 -2
  272. package/dist/types/BooleanType.js.map +1 -1
  273. package/dist/types/BscType.d.ts +10 -6
  274. package/dist/types/BscType.js +69 -16
  275. package/dist/types/BscType.js.map +1 -1
  276. package/dist/types/BscTypeKind.d.ts +3 -0
  277. package/dist/types/BscTypeKind.js +3 -0
  278. package/dist/types/BscTypeKind.js.map +1 -1
  279. package/dist/types/BuiltInInterfaceAdder.d.ts +23 -0
  280. package/dist/types/BuiltInInterfaceAdder.js +157 -0
  281. package/dist/types/BuiltInInterfaceAdder.js.map +1 -0
  282. package/dist/types/BuiltInInterfaceAdder.spec.d.ts +1 -0
  283. package/dist/types/BuiltInInterfaceAdder.spec.js +116 -0
  284. package/dist/types/BuiltInInterfaceAdder.spec.js.map +1 -0
  285. package/dist/types/ClassType.d.ts +10 -4
  286. package/dist/types/ClassType.js +32 -5
  287. package/dist/types/ClassType.js.map +1 -1
  288. package/dist/types/ClassType.spec.js +5 -3
  289. package/dist/types/ClassType.spec.js.map +1 -1
  290. package/dist/types/ComponentType.d.ts +26 -0
  291. package/dist/types/ComponentType.js +83 -0
  292. package/dist/types/ComponentType.js.map +1 -0
  293. package/dist/types/DoubleType.d.ts +2 -1
  294. package/dist/types/DoubleType.js +9 -2
  295. package/dist/types/DoubleType.js.map +1 -1
  296. package/dist/types/DynamicType.d.ts +2 -2
  297. package/dist/types/DynamicType.js +3 -1
  298. package/dist/types/DynamicType.js.map +1 -1
  299. package/dist/types/EnumType.d.ts +24 -6
  300. package/dist/types/EnumType.js +29 -7
  301. package/dist/types/EnumType.js.map +1 -1
  302. package/dist/types/FloatType.d.ts +2 -1
  303. package/dist/types/FloatType.js +9 -2
  304. package/dist/types/FloatType.js.map +1 -1
  305. package/dist/types/FunctionType.d.ts +8 -20
  306. package/dist/types/FunctionType.js +17 -45
  307. package/dist/types/FunctionType.js.map +1 -1
  308. package/dist/types/InheritableType.d.ts +7 -4
  309. package/dist/types/InheritableType.js +67 -3
  310. package/dist/types/InheritableType.js.map +1 -1
  311. package/dist/types/IntegerType.d.ts +2 -1
  312. package/dist/types/IntegerType.js +9 -2
  313. package/dist/types/IntegerType.js.map +1 -1
  314. package/dist/types/InterfaceType.d.ts +6 -4
  315. package/dist/types/InterfaceType.js +8 -11
  316. package/dist/types/InterfaceType.js.map +1 -1
  317. package/dist/types/InterfaceType.spec.js +30 -2
  318. package/dist/types/InterfaceType.spec.js.map +1 -1
  319. package/dist/types/InvalidType.d.ts +2 -1
  320. package/dist/types/InvalidType.js +7 -1
  321. package/dist/types/InvalidType.js.map +1 -1
  322. package/dist/types/LongIntegerType.d.ts +2 -1
  323. package/dist/types/LongIntegerType.js +9 -2
  324. package/dist/types/LongIntegerType.js.map +1 -1
  325. package/dist/types/NamespaceType.d.ts +2 -1
  326. package/dist/types/NamespaceType.js +3 -0
  327. package/dist/types/NamespaceType.js.map +1 -1
  328. package/dist/types/ObjectType.d.ts +2 -2
  329. package/dist/types/ObjectType.js +5 -10
  330. package/dist/types/ObjectType.js.map +1 -1
  331. package/dist/types/ReferenceType.d.ts +15 -3
  332. package/dist/types/ReferenceType.js +173 -24
  333. package/dist/types/ReferenceType.js.map +1 -1
  334. package/dist/types/ReferenceType.spec.js +21 -6
  335. package/dist/types/ReferenceType.spec.js.map +1 -1
  336. package/dist/types/StringType.d.ts +2 -1
  337. package/dist/types/StringType.js +9 -2
  338. package/dist/types/StringType.js.map +1 -1
  339. package/dist/types/TypedFunctionType.d.ts +33 -0
  340. package/dist/types/TypedFunctionType.js +106 -0
  341. package/dist/types/TypedFunctionType.js.map +1 -0
  342. package/dist/types/TypedFunctionType.spec.d.ts +1 -0
  343. package/dist/types/TypedFunctionType.spec.js +122 -0
  344. package/dist/types/TypedFunctionType.spec.js.map +1 -0
  345. package/dist/types/UninitializedType.d.ts +2 -1
  346. package/dist/types/UninitializedType.js +1 -1
  347. package/dist/types/UninitializedType.js.map +1 -1
  348. package/dist/types/UnionType.d.ts +4 -2
  349. package/dist/types/UnionType.js +36 -4
  350. package/dist/types/UnionType.js.map +1 -1
  351. package/dist/types/UnionType.spec.js +46 -19
  352. package/dist/types/UnionType.spec.js.map +1 -1
  353. package/dist/types/VoidType.d.ts +2 -1
  354. package/dist/types/VoidType.js +7 -2
  355. package/dist/types/VoidType.js.map +1 -1
  356. package/dist/types/helper.spec.js +15 -0
  357. package/dist/types/helper.spec.js.map +1 -1
  358. package/dist/types/helpers.d.ts +5 -0
  359. package/dist/types/helpers.js +50 -3
  360. package/dist/types/helpers.js.map +1 -1
  361. package/dist/types/index.d.ts +1 -1
  362. package/dist/types/index.js +1 -1
  363. package/dist/types/index.js.map +1 -1
  364. package/dist/util.d.ts +71 -15
  365. package/dist/util.js +578 -150
  366. package/dist/util.js.map +1 -1
  367. package/dist/validators/ClassValidator.d.ts +0 -1
  368. package/dist/validators/ClassValidator.js +0 -22
  369. package/dist/validators/ClassValidator.js.map +1 -1
  370. package/package.json +3 -2
  371. package/dist/astUtils/AstEditor.js.map +0 -1
  372. package/dist/astUtils/AstEditor.spec.js.map +0 -1
  373. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.js.map +0 -1
  374. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.spec.js +0 -31
  375. package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.spec.js.map +0 -1
  376. package/dist/types/FunctionType.spec.js +0 -23
  377. package/dist/types/FunctionType.spec.js.map +0 -1
  378. /package/dist/astUtils/{AstEditor.spec.d.ts → Editor.spec.d.ts} +0 -0
  379. /package/dist/bscPlugin/{transpile/BrsFilePreTranspileProcessor.spec.d.ts → completions/CompletionsProcessor.spec.d.ts} +0 -0
  380. /package/dist/{types/FunctionType.spec.d.ts → bscPlugin/serialize/BslibInjector.spec.d.ts} +0 -0
package/dist/Program.js CHANGED
@@ -4,11 +4,8 @@ exports.Program = void 0;
4
4
  const assert = require("assert");
5
5
  const fsExtra = require("fs-extra");
6
6
  const path = require("path");
7
- const vscode_languageserver_1 = require("vscode-languageserver");
8
7
  const Scope_1 = require("./Scope");
9
8
  const DiagnosticMessages_1 = require("./DiagnosticMessages");
10
- const BrsFile_1 = require("./files/BrsFile");
11
- const XmlFile_1 = require("./files/XmlFile");
12
9
  const util_1 = require("./util");
13
10
  const XmlScope_1 = require("./XmlScope");
14
11
  const DiagnosticFilterer_1 = require("./DiagnosticFilterer");
@@ -21,8 +18,7 @@ const vscode_uri_1 = require("vscode-uri");
21
18
  const PluginInterface_1 = require("./PluginInterface");
22
19
  const reflection_1 = require("./astUtils/reflection");
23
20
  const BscPlugin_1 = require("./bscPlugin/BscPlugin");
24
- const AstEditor_1 = require("./astUtils/AstEditor");
25
- const roku_deploy_1 = require("roku-deploy");
21
+ const Editor_1 = require("./astUtils/Editor");
26
22
  const CallExpressionInfo_1 = require("./bscPlugin/CallExpressionInfo");
27
23
  const SignatureHelpUtil_1 = require("./bscPlugin/SignatureHelpUtil");
28
24
  const DiagnosticSeverityAdjuster_1 = require("./DiagnosticSeverityAdjuster");
@@ -33,14 +29,18 @@ const BooleanType_1 = require("./types/BooleanType");
33
29
  const DoubleType_1 = require("./types/DoubleType");
34
30
  const DynamicType_1 = require("./types/DynamicType");
35
31
  const FloatType_1 = require("./types/FloatType");
36
- const FunctionType_1 = require("./types/FunctionType");
37
32
  const LongIntegerType_1 = require("./types/LongIntegerType");
38
33
  const ObjectType_1 = require("./types/ObjectType");
39
34
  const VoidType_1 = require("./types/VoidType");
40
- const CacheVerifier_1 = require("./CacheVerifier");
41
- const ReferenceType_1 = require("./types/ReferenceType");
42
- const UnionType_1 = require("./types/UnionType");
43
- const startOfSourcePkgPath = `source${path.sep}`;
35
+ const FunctionType_1 = require("./types/FunctionType");
36
+ const Factory_1 = require("./files/Factory");
37
+ const ActionPipeline_1 = require("./ActionPipeline");
38
+ const LazyFileData_1 = require("./files/LazyFileData");
39
+ const roku_deploy_1 = require("roku-deploy");
40
+ const roku_types_1 = require("./roku-types");
41
+ const ComponentType_1 = require("./types/ComponentType");
42
+ const types_1 = require("./types");
43
+ const BuiltInInterfaceAdder_1 = require("./types/BuiltInInterfaceAdder");
44
44
  const bslibNonAliasedRokuModulesPkgPath = (0, util_1.standardizePath) `source/roku_modules/rokucommunity_bslib/bslib.brs`;
45
45
  const bslibAliasedRokuModulesPkgPath = (0, util_1.standardizePath) `source/roku_modules/bslib/bslib.brs`;
46
46
  class Program {
@@ -50,7 +50,10 @@ class Program {
50
50
  */
51
51
  options, logger, plugins) {
52
52
  this.options = options;
53
- this.typeCacheVerifier = new CacheVerifier_1.CacheVerifier();
53
+ /**
54
+ * An editor that plugins can use to modify program-level things during the build flow. Don't use this to edit files (they have their own `.editor`)
55
+ */
56
+ this.editor = new Editor_1.Editor();
54
57
  /**
55
58
  * A graph of all files and their dependencies.
56
59
  * For example:
@@ -65,11 +68,21 @@ class Program {
65
68
  * Should only be set from `this.validate()`
66
69
  */
67
70
  this.diagnostics = [];
71
+ this.fileSymbolInformation = new Map();
68
72
  /**
69
73
  * A map of every file loaded into this program, indexed by its original file location
70
74
  */
71
75
  this.files = {};
72
- this.pkgMap = {};
76
+ /**
77
+ * A map of every file loaded into this program, indexed by its destPath
78
+ */
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();
73
86
  this.scopes = {};
74
87
  /**
75
88
  * A map of every component currently loaded into the program, indexed by the component name.
@@ -78,6 +91,13 @@ class Program {
78
91
  * but if you do, only ever use the component at index 0.
79
92
  */
80
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.lastValidationInfo = new Map();
99
+ this.getTranspiledFileContentsPipeline = new ActionPipeline_1.ActionPipeline();
100
+ this.buildPipeline = new ActionPipeline_1.ActionPipeline();
81
101
  this.options = util_1.util.normalizeConfig(options);
82
102
  this.logger = logger || new Logger_1.Logger(options.logLevel);
83
103
  this.plugins = plugins || new PluginInterface_1.default([], { logger: this.logger });
@@ -86,6 +106,7 @@ class Program {
86
106
  //normalize the root dir path
87
107
  this.options.rootDir = util_1.util.getRootDir(this.options);
88
108
  this.createGlobalScope();
109
+ this.fileFactory = new Factory_1.FileFactory(this);
89
110
  }
90
111
  createGlobalScope() {
91
112
  //create the 'global' scope
@@ -95,36 +116,85 @@ class Program {
95
116
  this.populateGlobalSymbolTable();
96
117
  //hardcode the files list for global scope to only contain the global file
97
118
  this.globalScope.getAllFiles = () => [globalCallables_1.globalFile];
119
+ globalCallables_1.globalFile.isValidated = true;
98
120
  this.globalScope.validate();
99
121
  //for now, disable validation of global scope because the global files have some duplicate method declarations
100
122
  this.globalScope.getDiagnostics = () => [];
101
123
  //TODO we might need to fix this because the isValidated clears stuff now
102
124
  this.globalScope.isValidated = true;
103
- // Adds a factory to SymbolTable so it can create ReferenceTypes
104
- SymbolTable_1.SymbolTable.ReferenceTypeFactory = ReferenceType_1.referenceTypeFactory;
105
- SymbolTable_1.SymbolTable.cacheVerifier = this.typeCacheVerifier;
106
- SymbolTable_1.SymbolTable.UnionTypeFactory = UnionType_1.unionTypeFactory;
125
+ }
126
+ recursivelyAddNodeToSymbolTable(nodeData) {
127
+ if (!nodeData) {
128
+ return;
129
+ }
130
+ let nodeType;
131
+ const nodeName = util_1.util.getSgNodeTypeName(nodeData.name);
132
+ if (!this.globalScope.symbolTable.hasSymbol(nodeName, SymbolTable_1.SymbolTypeFlag.typetime)) {
133
+ let parentNode;
134
+ if (nodeData.extends) {
135
+ const parentNodeData = roku_types_1.nodes[nodeData.extends.name.toLowerCase()];
136
+ try {
137
+ parentNode = this.recursivelyAddNodeToSymbolTable(parentNodeData);
138
+ }
139
+ catch (error) {
140
+ console.log(error, nodeData);
141
+ }
142
+ }
143
+ nodeType = new ComponentType_1.ComponentType(nodeData.name, parentNode);
144
+ nodeType.addBuiltInInterfaces();
145
+ this.globalScope.symbolTable.addSymbol(nodeName, { description: nodeData.description }, nodeType, SymbolTable_1.SymbolTypeFlag.typetime);
146
+ }
147
+ else {
148
+ nodeType = this.globalScope.symbolTable.getSymbolType(nodeName, { flags: SymbolTable_1.SymbolTypeFlag.typetime });
149
+ }
150
+ return nodeType;
107
151
  }
108
152
  /**
109
153
  * Do all setup required for the global symbol table.
110
154
  */
111
155
  populateGlobalSymbolTable() {
112
156
  //Setup primitive types in global symbolTable
113
- //TODO: Need to handle Array types
114
157
  this.globalScope.symbolTable.addSymbol('boolean', undefined, BooleanType_1.BooleanType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
115
158
  this.globalScope.symbolTable.addSymbol('double', undefined, DoubleType_1.DoubleType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
116
159
  this.globalScope.symbolTable.addSymbol('dynamic', undefined, DynamicType_1.DynamicType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
117
160
  this.globalScope.symbolTable.addSymbol('float', undefined, FloatType_1.FloatType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
118
- this.globalScope.symbolTable.addSymbol('function', undefined, new FunctionType_1.FunctionType(DynamicType_1.DynamicType.instance), SymbolTable_1.SymbolTypeFlag.typetime);
161
+ this.globalScope.symbolTable.addSymbol('function', undefined, new FunctionType_1.FunctionType(), SymbolTable_1.SymbolTypeFlag.typetime);
119
162
  this.globalScope.symbolTable.addSymbol('integer', undefined, IntegerType_1.IntegerType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
120
163
  this.globalScope.symbolTable.addSymbol('longinteger', undefined, LongIntegerType_1.LongIntegerType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
121
164
  this.globalScope.symbolTable.addSymbol('object', undefined, new ObjectType_1.ObjectType(), SymbolTable_1.SymbolTypeFlag.typetime);
122
165
  this.globalScope.symbolTable.addSymbol('string', undefined, StringType_1.StringType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
123
166
  this.globalScope.symbolTable.addSymbol('void', undefined, VoidType_1.VoidType.instance, SymbolTable_1.SymbolTypeFlag.typetime);
124
- for (let pair of globalCallables_1.globalCallableMap) {
125
- let [key, callable] = pair;
126
- this.globalScope.symbolTable.addSymbol(key, undefined, callable.type, SymbolTable_1.SymbolTypeFlag.runtime);
167
+ BuiltInInterfaceAdder_1.BuiltInInterfaceAdder.getLookupTable = () => this.globalScope.symbolTable;
168
+ for (const callable of globalCallables_1.globalCallables) {
169
+ this.globalScope.symbolTable.addSymbol(callable.name, { description: callable.shortDescription }, callable.type, SymbolTable_1.SymbolTypeFlag.runtime);
170
+ }
171
+ for (const componentData of Object.values(roku_types_1.components)) {
172
+ const nodeType = new types_1.InterfaceType(componentData.name);
173
+ nodeType.addBuiltInInterfaces();
174
+ this.globalScope.symbolTable.addSymbol(componentData.name, { description: componentData.description }, nodeType, SymbolTable_1.SymbolTypeFlag.typetime);
127
175
  }
176
+ for (const ifaceData of Object.values(roku_types_1.interfaces)) {
177
+ const nodeType = new types_1.InterfaceType(ifaceData.name);
178
+ nodeType.addBuiltInInterfaces();
179
+ this.globalScope.symbolTable.addSymbol(ifaceData.name, { description: ifaceData.description }, nodeType, SymbolTable_1.SymbolTypeFlag.typetime);
180
+ }
181
+ for (const eventData of Object.values(roku_types_1.events)) {
182
+ const nodeType = new types_1.InterfaceType(eventData.name);
183
+ nodeType.addBuiltInInterfaces();
184
+ this.globalScope.symbolTable.addSymbol(eventData.name, { description: eventData.description }, nodeType, SymbolTable_1.SymbolTypeFlag.typetime);
185
+ }
186
+ for (const nodeData of Object.values(roku_types_1.nodes)) {
187
+ this.recursivelyAddNodeToSymbolTable(nodeData);
188
+ }
189
+ }
190
+ addFileSymbolInfo(file) {
191
+ this.fileSymbolInformation.set(file.pkgPath, {
192
+ provides: file.providedSymbols,
193
+ requires: file.requiredSymbols
194
+ });
195
+ }
196
+ getFileSymbolInfo(file) {
197
+ return this.fileSymbolInformation.get(file.pkgPath);
128
198
  }
129
199
  /**
130
200
  * The path to bslib.brs (the BrightScript runtime for certain BrighterScript features)
@@ -140,7 +210,7 @@ class Program {
140
210
  //default to the embedded version
141
211
  }
142
212
  else {
143
- return `source${path.sep}bslib.brs`;
213
+ return `${this.options.bslibDestinationDir}${path.sep}bslib.brs`;
144
214
  }
145
215
  }
146
216
  get bslibPrefix() {
@@ -153,10 +223,6 @@ class Program {
153
223
  }
154
224
  addScope(scope) {
155
225
  this.scopes[scope.name] = scope;
156
- this.plugins.emit('afterScopeCreate', {
157
- program: this,
158
- scope: scope
159
- });
160
226
  }
161
227
  /**
162
228
  * Get the component with the specified name
@@ -165,7 +231,7 @@ class Program {
165
231
  var _a;
166
232
  if (componentName) {
167
233
  //return the first compoment in the list with this name
168
- //(components are ordered in this list by pkgPath to ensure consistency)
234
+ //(components are ordered in this list by destPath to ensure consistency)
169
235
  return (_a = this.components[componentName.toLowerCase()]) === null || _a === void 0 ? void 0 : _a[0];
170
236
  }
171
237
  else {
@@ -176,8 +242,7 @@ class Program {
176
242
  * Register (or replace) the reference to a component in the component map
177
243
  */
178
244
  registerComponent(xmlFile, scope) {
179
- var _a, _b;
180
- const key = ((_b = (_a = xmlFile.componentName) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : xmlFile.pkgPath).toLowerCase();
245
+ const key = this.getComponentKey(xmlFile);
181
246
  if (!this.components[key]) {
182
247
  this.components[key] = [];
183
248
  }
@@ -186,8 +251,8 @@ class Program {
186
251
  scope: scope
187
252
  });
188
253
  this.components[key].sort((a, b) => {
189
- const pathA = a.file.pkgPath.toLowerCase();
190
- const pathB = b.file.pkgPath.toLowerCase();
254
+ const pathA = a.file.destPath.toLowerCase();
255
+ const pathB = b.file.destPath.toLowerCase();
191
256
  if (pathA < pathB) {
192
257
  return -1;
193
258
  }
@@ -197,13 +262,13 @@ class Program {
197
262
  return 0;
198
263
  });
199
264
  this.syncComponentDependencyGraph(this.components[key]);
265
+ this.addDeferredComponentTypeSymbolCreation(xmlFile);
200
266
  }
201
267
  /**
202
268
  * Remove the specified component from the components map
203
269
  */
204
270
  unregisterComponent(xmlFile) {
205
- var _a, _b;
206
- 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);
207
272
  const arr = this.components[key] || [];
208
273
  for (let i = 0; i < arr.length; i++) {
209
274
  if (arr[i].file === xmlFile) {
@@ -212,6 +277,44 @@ class Program {
212
277
  }
213
278
  }
214
279
  this.syncComponentDependencyGraph(arr);
280
+ this.addDeferredComponentTypeSymbolCreation(xmlFile);
281
+ }
282
+ /**
283
+ * Adds a component described in an XML to the set of components that needs to be updated this validation cycle.
284
+ * @param xmlFile XML file with <component> tag
285
+ */
286
+ addDeferredComponentTypeSymbolCreation(xmlFile) {
287
+ var _a;
288
+ this.componentSymbolsToUpdate.add({ componentKey: this.getComponentKey(xmlFile), componentName: (_a = xmlFile.componentName) === null || _a === void 0 ? void 0 : _a.text });
289
+ }
290
+ getComponentKey(xmlFile) {
291
+ var _a, _b;
292
+ return ((_b = (_a = xmlFile.componentName) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : xmlFile.pkgPath).toLowerCase();
293
+ }
294
+ /**
295
+ * Updates the global symbol table with the first component in this.components to have the same name as the component in the file
296
+ * @param componentKey key getting a component from `this.components`
297
+ * @param componentName the unprefixed name of the component that will be added (e.g. 'MyLabel' NOT 'roSgNodeMyLabel')
298
+ */
299
+ updateComponentSymbolInGlobalScope(componentKey, componentName) {
300
+ const symbolName = componentName ? util_1.util.getSgNodeTypeName(componentName) : undefined;
301
+ if (!symbolName) {
302
+ return;
303
+ }
304
+ const components = this.components[componentKey] || [];
305
+ // Remove any existing symbols that match
306
+ this.globalScope.symbolTable.removeSymbol(symbolName);
307
+ // There is a component that can be added - use it.
308
+ if (components.length > 0) {
309
+ const componentScope = components[0].scope;
310
+ // TODO: May need to link symbol tables to get correct types for callfuncs
311
+ // componentScope.linkSymbolTable();
312
+ const componentType = componentScope.getComponentType();
313
+ if (componentType) {
314
+ this.globalScope.symbolTable.addSymbol(symbolName, {}, componentType, SymbolTable_1.SymbolTypeFlag.typetime);
315
+ }
316
+ // TODO: Remember to unlink! componentScope.unlinkSymbolTable();
317
+ }
215
318
  }
216
319
  /**
217
320
  * re-attach the dependency graph with a new key for any component who changed
@@ -225,6 +328,7 @@ class Program {
225
328
  //attach (or re-attach) the dependencyGraph for every component whose position changed
226
329
  if (file.dependencyGraphIndex !== i) {
227
330
  file.dependencyGraphIndex = i;
331
+ this.dependencyGraph.addOrReplace(file.dependencyGraphKey, file.dependencies);
228
332
  file.attachDependencyGraph(this.dependencyGraph);
229
333
  scope.attachDependencyGraph(this.dependencyGraph);
230
334
  }
@@ -261,7 +365,7 @@ class Program {
261
365
  //get the diagnostics from all unreferenced files
262
366
  let unreferencedFiles = this.getUnreferencedFiles();
263
367
  for (let file of unreferencedFiles) {
264
- diagnostics.push(...file.getDiagnostics());
368
+ diagnostics.push(...file.diagnostics);
265
369
  }
266
370
  const filteredDiagnostics = this.logger.time(Logger_1.LogLevel.debug, ['filter diagnostics'], () => {
267
371
  //filter out diagnostics based on our diagnostic filters
@@ -286,11 +390,9 @@ class Program {
286
390
  hasFile(filePath, normalizePath = true) {
287
391
  return !!this.getFile(filePath, normalizePath);
288
392
  }
289
- getPkgPath(...args) {
290
- throw new Error('Not implemented');
291
- }
292
393
  /**
293
394
  * roku filesystem is case INsensitive, so find the scope by key case insensitive
395
+ * @param scopeName xml scope names are their `destPath`. Source scope is stored with the key `"source"`
294
396
  */
295
397
  getScopeByName(scopeName) {
296
398
  if (!scopeName) {
@@ -298,8 +400,8 @@ class Program {
298
400
  }
299
401
  //most scopes are xml file pkg paths. however, the ones that are not are single names like "global" and "scope",
300
402
  //so it's safe to run the standardizePkgPath method
301
- scopeName = (0, util_1.standardizePath) `${scopeName}`;
302
- let key = Object.keys(this.scopes).find(x => x.toLowerCase() === scopeName.toLowerCase());
403
+ scopeName = (0, util_1.standardizePath) `${scopeName.toLowerCase()}`;
404
+ let key = Object.keys(this.scopes).find(x => x.toLowerCase() === scopeName);
303
405
  return this.scopes[key];
304
406
  }
305
407
  /**
@@ -319,8 +421,14 @@ class Program {
319
421
  * Update internal maps with this file reference
320
422
  */
321
423
  assignFile(file) {
424
+ const fileAddEvent = {
425
+ file: file,
426
+ program: this
427
+ };
428
+ this.plugins.emit('beforeFileAdd', fileAddEvent);
322
429
  this.files[file.srcPath.toLowerCase()] = file;
323
- this.pkgMap[file.pkgPath.toLowerCase()] = file;
430
+ this.destMap.set(file.destPath.toLowerCase(), file);
431
+ this.plugins.emit('afterFileAdd', fileAddEvent);
324
432
  return file;
325
433
  }
326
434
  /**
@@ -328,99 +436,102 @@ class Program {
328
436
  */
329
437
  unassignFile(file) {
330
438
  delete this.files[file.srcPath.toLowerCase()];
331
- delete this.pkgMap[file.pkgPath.toLowerCase()];
439
+ this.destMap.delete(file.destPath.toLowerCase());
332
440
  return file;
333
441
  }
334
- setFile(fileParam, fileContents) {
442
+ setFile(fileParam, fileData) {
335
443
  //normalize the file paths
336
- const { srcPath, pkgPath } = this.getPaths(fileParam, this.options.rootDir);
444
+ const { srcPath, destPath } = this.getPaths(fileParam, this.options.rootDir);
337
445
  let file = this.logger.time(Logger_1.LogLevel.debug, ['Program.setFile()', chalk_1.default.green(srcPath)], () => {
446
+ var _a, _b, _c;
338
447
  //if the file is already loaded, remove it
339
448
  if (this.hasFile(srcPath)) {
340
- this.removeFile(srcPath);
449
+ this.removeFile(srcPath, true, true);
341
450
  }
342
- let fileExtension = path.extname(srcPath).toLowerCase();
343
- let file;
344
- if (fileExtension === '.brs' || fileExtension === '.bs') {
345
- //add the file to the program
346
- const brsFile = this.assignFile(new BrsFile_1.BrsFile(srcPath, pkgPath, this));
347
- //add file to the `source` dependency list
348
- if (brsFile.pkgPath.startsWith(startOfSourcePkgPath)) {
349
- this.createSourceScope();
350
- this.dependencyGraph.addDependency('scope:source', brsFile.dependencyGraphKey);
351
- }
352
- let beforeFileParseEvent = {
353
- program: this,
354
- srcPath: srcPath,
355
- source: fileContents
356
- };
357
- this.plugins.emit('beforeFileParse', beforeFileParseEvent);
358
- this.logger.time(Logger_1.LogLevel.debug, ['parse', chalk_1.default.green(srcPath)], () => {
359
- brsFile.parse(beforeFileParseEvent.source);
360
- });
361
- //notify plugins that this file has finished parsing
362
- this.plugins.emit('afterFileParse', {
363
- program: this,
364
- file: brsFile
365
- });
366
- file = brsFile;
367
- brsFile.attachDependencyGraph(this.dependencyGraph);
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
+ }));
368
464
  }
369
- else if (
370
- //is xml file
371
- fileExtension === '.xml' &&
372
- //resides in the components folder (Roku will only parse xml files in the components folder)
373
- pkgPath.toLowerCase().startsWith(util_1.util.pathSepNormalize(`components/`))) {
374
- //add the file to the program
375
- const xmlFile = this.assignFile(new XmlFile_1.XmlFile(srcPath, pkgPath, this));
376
- let event = {
377
- program: this,
378
- srcPath: srcPath,
379
- source: fileContents
380
- };
381
- this.plugins.emit('beforeFileParse', event);
382
- this.logger.time(Logger_1.LogLevel.debug, ['parse', chalk_1.default.green(srcPath)], () => {
383
- xmlFile.parse(event.source);
384
- });
385
- //notify plugins that this file has finished parsing
386
- this.plugins.emit('afterFileParse', {
387
- program: this,
388
- file: xmlFile
389
- });
390
- file = xmlFile;
391
- //create a new scope for this xml file
392
- let scope = new XmlScope_1.XmlScope(xmlFile, this);
393
- this.addScope(scope);
394
- //register this compoent now that we have parsed it and know its component name
395
- this.registerComponent(xmlFile, scope);
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
+ })))}`);
396
473
  }
397
- else {
398
- //TODO do we actually need to implement this? Figure out how to handle img paths
399
- // let genericFile = this.files[srcPath] = <any>{
400
- // srcPath: srcPath,
401
- // pkgPath: pkgPath,
402
- // wasProcessed: true
403
- // } as File;
404
- // file = <any>genericFile;
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
+ }
405
516
  }
406
- return file;
517
+ return primaryFile;
407
518
  });
408
519
  return file;
409
520
  }
410
521
  /**
411
- * Given a srcPath, a pkgPath, or both, resolve whichever is missing, relative to rootDir.
522
+ * Given a srcPath, a destPath, or both, resolve whichever is missing, relative to rootDir.
412
523
  * @param fileParam an object representing file paths
413
524
  * @param rootDir must be a pre-normalized path
414
525
  */
415
526
  getPaths(fileParam, rootDir) {
416
527
  let srcPath;
417
- let pkgPath;
528
+ let destPath;
418
529
  assert.ok(fileParam, 'fileParam is required');
419
- //lift the srcPath and pkgPath vars from the incoming param
530
+ //lift the path vars from the incoming param
420
531
  if (typeof fileParam === 'string') {
421
532
  fileParam = this.removePkgPrefix(fileParam);
422
533
  srcPath = (0, util_1.standardizePath) `${path.resolve(rootDir, fileParam)}`;
423
- pkgPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(srcPath, rootDir, '')}`;
534
+ destPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(srcPath, rootDir, '')}`;
424
535
  }
425
536
  else {
426
537
  let param = fileParam;
@@ -431,30 +542,30 @@ class Program {
431
542
  srcPath = (0, util_1.standardizePath) `${param.srcPath}`;
432
543
  }
433
544
  if (param.dest) {
434
- pkgPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.dest)}`;
545
+ destPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.dest)}`;
435
546
  }
436
547
  if (param.pkgPath) {
437
- pkgPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.pkgPath)}`;
548
+ destPath = (0, util_1.standardizePath) `${this.removePkgPrefix(param.pkgPath)}`;
438
549
  }
439
550
  }
440
- //if there's no srcPath, use the pkgPath to build an absolute srcPath
551
+ //if there's no srcPath, use the destPath to build an absolute srcPath
441
552
  if (!srcPath) {
442
- srcPath = (0, util_1.standardizePath) `${rootDir}/${pkgPath}`;
553
+ srcPath = (0, util_1.standardizePath) `${rootDir}/${destPath}`;
443
554
  }
444
555
  //coerce srcPath to an absolute path
445
556
  if (!path.isAbsolute(srcPath)) {
446
557
  srcPath = util_1.util.standardizePath(srcPath);
447
558
  }
448
- //if there's no pkgPath, compute relative path from rootDir
449
- if (!pkgPath) {
450
- pkgPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(srcPath, rootDir, '')}`;
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, '')}`;
451
562
  }
452
563
  assert.ok(srcPath, 'fileEntry.src is required');
453
- assert.ok(pkgPath, 'fileEntry.dest is required');
564
+ assert.ok(destPath, 'fileEntry.dest is required');
454
565
  return {
455
566
  srcPath: srcPath,
456
- //remove leading slash from pkgPath
457
- pkgPath: pkgPath.replace(/^[\/\\]+/, '')
567
+ //remove leading slash
568
+ destPath: destPath.replace(/^[\/\\]+/, '')
458
569
  };
459
570
  }
460
571
  /**
@@ -463,6 +574,18 @@ class Program {
463
574
  removePkgPrefix(path) {
464
575
  return path.replace(/^pkg:\//i, '');
465
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);
588
+ }
466
589
  /**
467
590
  * Ensure source scope is created.
468
591
  * Note: automatically called internally, and no-op if it exists already.
@@ -472,6 +595,10 @@ class Program {
472
595
  const sourceScope = new Scope_1.Scope('source', this, 'scope:source');
473
596
  sourceScope.attachDependencyGraph(this.dependencyGraph);
474
597
  this.addScope(sourceScope);
598
+ this.plugins.emit('afterScopeCreate', {
599
+ program: this,
600
+ scope: sourceScope
601
+ });
475
602
  }
476
603
  }
477
604
  /**
@@ -486,46 +613,58 @@ class Program {
486
613
  }
487
614
  /**
488
615
  * Remove a file from the program
489
- * @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:/`
490
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
491
618
  */
492
- removeFile(filePath, normalizePath = true) {
619
+ removeFile(filePath, normalizePath = true, keepSymbolInformation = false) {
620
+ var _a, _b, _c, _d;
493
621
  this.logger.debug('Program.removeFile()', filePath);
494
- let file = this.getFile(filePath, normalizePath);
495
- if (file) {
496
- const fileDisposeEvent = {
497
- program: this,
498
- file: file
499
- };
500
- this.plugins.emit('beforeFileDispose', fileDisposeEvent);
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
+ const event = { file: file, program: this };
631
+ this.plugins.emit('beforeFileRemove', event);
501
632
  //if there is a scope named the same as this file's path, remove it (i.e. xml scopes)
502
- let scope = this.scopes[file.pkgPath];
633
+ let scope = this.scopes[file.destPath];
503
634
  if (scope) {
504
635
  const scopeDisposeEvent = {
505
636
  program: this,
506
637
  scope: scope
507
638
  };
508
639
  this.plugins.emit('beforeScopeDispose', scopeDisposeEvent);
640
+ this.plugins.emit('onScopeDispose', scopeDisposeEvent);
509
641
  scope.dispose();
510
642
  //notify dependencies of this scope that it has been removed
511
643
  this.dependencyGraph.remove(scope.dependencyGraphKey);
512
- delete this.scopes[file.pkgPath];
644
+ delete this.scopes[file.destPath];
513
645
  this.plugins.emit('afterScopeDispose', scopeDisposeEvent);
514
646
  }
515
647
  //remove the file from the program
516
648
  this.unassignFile(file);
517
649
  this.dependencyGraph.remove(file.dependencyGraphKey);
518
650
  //if this is a pkg:/source file, notify the `source` scope that it has changed
519
- if (file.pkgPath.startsWith(startOfSourcePkgPath)) {
651
+ if (this.isSourceBrsFile(file)) {
520
652
  this.dependencyGraph.removeDependency('scope:source', file.dependencyGraphKey);
653
+ if (!keepSymbolInformation) {
654
+ this.fileSymbolInformation.delete(file.pkgPath);
655
+ }
521
656
  }
522
657
  //if this is a component, remove it from our components map
523
658
  if ((0, reflection_1.isXmlFile)(file)) {
524
659
  this.unregisterComponent(file);
525
660
  }
661
+ //dispose any disposable things on the file
662
+ for (const disposable of (_c = file === null || file === void 0 ? void 0 : file.disposables) !== null && _c !== void 0 ? _c : []) {
663
+ disposable();
664
+ }
526
665
  //dispose file
527
- file === null || file === void 0 ? void 0 : file.dispose();
528
- this.plugins.emit('afterFileDispose', fileDisposeEvent);
666
+ (_d = file === null || file === void 0 ? void 0 : file.dispose) === null || _d === void 0 ? void 0 : _d.call(file);
667
+ this.plugins.emit('afterFileRemove', event);
529
668
  }
530
669
  }
531
670
  /**
@@ -533,12 +672,15 @@ class Program {
533
672
  */
534
673
  validate() {
535
674
  this.logger.time(Logger_1.LogLevel.log, ['Validating project'], () => {
675
+ var _a, _b, _c, _d, _e, _f, _g;
536
676
  this.diagnostics = [];
537
677
  const programValidateEvent = {
538
678
  program: this
539
679
  };
540
680
  this.plugins.emit('beforeProgramValidate', programValidateEvent);
681
+ this.plugins.emit('onProgramValidate', programValidateEvent);
541
682
  //validate every file
683
+ const brsFilesValidated = [];
542
684
  for (const file of Object.values(this.files)) {
543
685
  //for every unvalidated file, validate it
544
686
  if (!file.isValidated) {
@@ -550,21 +692,118 @@ class Program {
550
692
  //emit an event to allow plugins to contribute to the file validation process
551
693
  this.plugins.emit('onFileValidate', validateFileEvent);
552
694
  file.isValidated = true;
695
+ if ((0, reflection_1.isBrsFile)(file)) {
696
+ brsFilesValidated.push(file);
697
+ }
553
698
  this.plugins.emit('afterFileValidate', validateFileEvent);
554
699
  }
555
700
  }
701
+ // build list of all changed symbols in each file that changed
702
+ this.lastValidationInfo.clear();
703
+ for (const file of brsFilesValidated) {
704
+ const fileInfo = {
705
+ symbolsNotDefinedInEveryScope: [],
706
+ duplicateSymbolsInSameScope: [],
707
+ symbolsNotConsistentAcrossScopes: []
708
+ };
709
+ const scopesToCheckForConsistency = this.getScopesForFile(file);
710
+ for (const symbol of file.requiredSymbols) {
711
+ let providedSymbolType;
712
+ let scopesDefiningSymbol = [];
713
+ let scopesAreInconsistent = false;
714
+ for (const scope of scopesToCheckForConsistency) {
715
+ let symbolFoundInScope = false;
716
+ for (const scopeFile of scope.getAllFiles()) {
717
+ if (!(0, reflection_1.isBrsFile)(scopeFile) || scopeFile.isTypedef || scopeFile.hasTypedef) {
718
+ continue;
719
+ }
720
+ const lowerFirstSymbolName = (_b = (_a = symbol.typeChain) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.name.toLowerCase();
721
+ let symbolInThisScope = (_d = (_c = scopeFile.providedSymbols.symbolMap) === null || _c === void 0 ? void 0 : _c.get(symbol.flags)) === null || _d === void 0 ? void 0 : _d.get(lowerFirstSymbolName);
722
+ if (!symbolInThisScope && ((_e = symbol.containingNamespaces) === null || _e === void 0 ? void 0 : _e.length) > 0) {
723
+ const fullNameWithNamespaces = (symbol.containingNamespaces.join('.') + '.' + lowerFirstSymbolName).toLowerCase();
724
+ symbolInThisScope = (_g = (_f = scopeFile.providedSymbols.symbolMap) === null || _f === void 0 ? void 0 : _f.get(symbol.flags)) === null || _g === void 0 ? void 0 : _g.get(fullNameWithNamespaces);
725
+ }
726
+ if (symbolInThisScope) {
727
+ if (symbolFoundInScope) {
728
+ // this is duplicately defined!
729
+ fileInfo.duplicateSymbolsInSameScope.push({ symbol: symbol, scope: scope });
730
+ }
731
+ else {
732
+ symbolFoundInScope = true;
733
+ scopesDefiningSymbol.push(scope);
734
+ //check for consistency across scopes
735
+ if (!providedSymbolType) {
736
+ providedSymbolType = symbolInThisScope.type;
737
+ }
738
+ else {
739
+ //get more general type
740
+ if (providedSymbolType.isEqual(symbolInThisScope.type)) {
741
+ //type in this scope is the same as one we're already checking
742
+ }
743
+ else if (providedSymbolType.isTypeCompatible(symbolInThisScope.type)) {
744
+ //type in this scope is compatible with one we're storing. use most generic
745
+ providedSymbolType = symbolInThisScope.type;
746
+ }
747
+ else if (symbolInThisScope.type.isTypeCompatible(providedSymbolType)) {
748
+ // type we're storing is more generic that the type in this scope
749
+ }
750
+ else {
751
+ // type in this scope is not compatible with other types for this symbol
752
+ scopesAreInconsistent = true;
753
+ }
754
+ }
755
+ }
756
+ }
757
+ }
758
+ if (!symbolFoundInScope) {
759
+ fileInfo.symbolsNotDefinedInEveryScope.push({ symbol: symbol, scope: scope });
760
+ }
761
+ }
762
+ if (scopesAreInconsistent) {
763
+ fileInfo.symbolsNotConsistentAcrossScopes.push({ symbol: symbol, scopes: scopesDefiningSymbol });
764
+ }
765
+ }
766
+ this.lastValidationInfo.set(file.srcPath.toLowerCase(), fileInfo);
767
+ }
768
+ this.detectIncompatibleSymbolsAcrossScopes();
769
+ // Build component types for any component that changes
770
+ this.logger.time(Logger_1.LogLevel.info, ['Build component types'], () => {
771
+ for (let { componentKey, componentName } of this.componentSymbolsToUpdate) {
772
+ this.updateComponentSymbolInGlobalScope(componentKey, componentName);
773
+ }
774
+ this.componentSymbolsToUpdate.clear();
775
+ });
776
+ const changedSymbolsMapArr = brsFilesValidated === null || brsFilesValidated === void 0 ? void 0 : brsFilesValidated.map(f => {
777
+ if ((0, reflection_1.isBrsFile)(f)) {
778
+ return f.providedSymbols.changes;
779
+ }
780
+ return null;
781
+ }).filter(x => x);
782
+ const changedSymbols = new Map();
783
+ for (const flag of [SymbolTable_1.SymbolTypeFlag.runtime, SymbolTable_1.SymbolTypeFlag.typetime]) {
784
+ const changedSymbolsSetArr = changedSymbolsMapArr.map(symMap => symMap.get(flag));
785
+ changedSymbols.set(flag, new Set(...changedSymbolsSetArr));
786
+ }
556
787
  this.logger.time(Logger_1.LogLevel.info, ['Validate all scopes'], () => {
557
788
  for (let scopeName in this.scopes) {
558
789
  let scope = this.scopes[scopeName];
559
- scope.linkSymbolTable();
560
- scope.validate();
561
- scope.unlinkSymbolTable();
790
+ scope.validate({ changedFiles: brsFilesValidated, changedSymbols: changedSymbols });
562
791
  }
563
792
  });
564
793
  this.detectDuplicateComponentNames();
565
794
  this.plugins.emit('afterProgramValidate', programValidateEvent);
566
795
  });
567
796
  }
797
+ detectIncompatibleSymbolsAcrossScopes() {
798
+ for (const [lowerFilePath, fileInfo] of this.lastValidationInfo.entries()) {
799
+ const file = this.files[lowerFilePath];
800
+ for (const symbolAndScopes of fileInfo.symbolsNotConsistentAcrossScopes) {
801
+ const typeChainResult = util_1.util.processTypeChain(symbolAndScopes.symbol.typeChain);
802
+ const scopeListName = symbolAndScopes.scopes.map(s => s.name).join(', ');
803
+ this.diagnostics.push(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.incompatibleSymbolDefinition(typeChainResult.fullNameOfItem, scopeListName)), { file: file, range: typeChainResult.range }));
804
+ }
805
+ }
806
+ }
568
807
  /**
569
808
  * Flag all duplicate component names
570
809
  */
@@ -617,12 +856,13 @@ class Program {
617
856
  getFile(filePath, normalizePath = true) {
618
857
  if (typeof filePath !== 'string') {
619
858
  return undefined;
859
+ //is the path absolute (or the `virtual:` prefix)
620
860
  }
621
- else if (path.isAbsolute(filePath)) {
861
+ else if (/^(?:(?:virtual:[\/\\])|(?:\w:)|(?:[\/\\]))/gmi.exec(filePath)) {
622
862
  return this.files[(normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase()];
623
863
  }
624
864
  else {
625
- return this.pkgMap[(normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase()];
865
+ return this.destMap.get((normalizePath ? util_1.util.standardizePath(filePath) : filePath).toLowerCase());
626
866
  }
627
867
  }
628
868
  /**
@@ -664,7 +904,8 @@ class Program {
664
904
  //look through all files in scope for matches
665
905
  for (const scope of this.getScopesForFile(originFile)) {
666
906
  for (const file of scope.getAllFiles()) {
667
- if ((0, reflection_1.isXmlFile)(file) || filesSearched.has(file)) {
907
+ //skip non-brs files, or files we've already processed
908
+ if (!(0, reflection_1.isBrsFile)(file) || filesSearched.has(file)) {
668
909
  continue;
669
910
  }
670
911
  filesSearched.add(file);
@@ -697,7 +938,8 @@ class Program {
697
938
  }
698
939
  //look through all files in scope for matches
699
940
  for (const file of scope.getOwnFiles()) {
700
- if ((0, reflection_1.isXmlFile)(file) || filesSearched.has(file)) {
941
+ //skip non-brs files, or files we've already processed
942
+ if (!(0, reflection_1.isBrsFile)(file) || filesSearched.has(file)) {
701
943
  continue;
702
944
  }
703
945
  filesSearched.add(file);
@@ -846,60 +1088,12 @@ class Program {
846
1088
  getReferences(srcPath, position) {
847
1089
  //find the file
848
1090
  let file = this.getFile(srcPath);
849
- if (!file) {
850
- return null;
1091
+ if ((0, reflection_1.isBrsFile)(file) || (0, reflection_1.isXmlFile)(file)) {
1092
+ return file.getReferences(position);
851
1093
  }
852
- return file.getReferences(position);
853
- }
854
- /**
855
- * Get a list of all script imports, relative to the specified pkgPath
856
- * @param sourcePkgPath - the pkgPath of the source that wants to resolve script imports.
857
- */
858
- getScriptImportCompletions(sourcePkgPath, scriptImport) {
859
- let lowerSourcePkgPath = sourcePkgPath.toLowerCase();
860
- let result = [];
861
- /**
862
- * hashtable to prevent duplicate results
863
- */
864
- let resultPkgPaths = {};
865
- //restrict to only .brs files
866
- for (let key in this.files) {
867
- let file = this.files[key];
868
- if (
869
- //is a BrightScript or BrighterScript file
870
- (file.extension === '.bs' || file.extension === '.brs') &&
871
- //this file is not the current file
872
- lowerSourcePkgPath !== file.pkgPath.toLowerCase()) {
873
- //add the relative path
874
- let relativePath = util_1.util.getRelativePath(sourcePkgPath, file.pkgPath).replace(/\\/g, '/');
875
- let pkgPathStandardized = file.pkgPath.replace(/\\/g, '/');
876
- let filePkgPath = `pkg:/${pkgPathStandardized}`;
877
- let lowerFilePkgPath = filePkgPath.toLowerCase();
878
- if (!resultPkgPaths[lowerFilePkgPath]) {
879
- resultPkgPaths[lowerFilePkgPath] = true;
880
- result.push({
881
- label: relativePath,
882
- detail: file.srcPath,
883
- kind: vscode_languageserver_1.CompletionItemKind.File,
884
- textEdit: {
885
- newText: relativePath,
886
- range: scriptImport.filePathRange
887
- }
888
- });
889
- //add the absolute path
890
- result.push({
891
- label: filePkgPath,
892
- detail: file.srcPath,
893
- kind: vscode_languageserver_1.CompletionItemKind.File,
894
- textEdit: {
895
- newText: filePkgPath,
896
- range: scriptImport.filePathRange
897
- }
898
- });
899
- }
900
- }
1094
+ else {
1095
+ return null;
901
1096
  }
902
- return result;
903
1097
  }
904
1098
  /**
905
1099
  * Transpile a single file and get the result as a string.
@@ -910,165 +1104,211 @@ class Program {
910
1104
  * @param filePath can be a srcPath or a destPath
911
1105
  */
912
1106
  async getTranspiledFileContents(filePath) {
913
- let fileMap = await roku_deploy_1.rokuDeploy.getFilePaths(this.options.files, this.options.rootDir);
914
- //remove files currently loaded in the program, we will transpile those instead (even if just for source maps)
915
- let filteredFileMap = [];
916
- for (let fileEntry of fileMap) {
917
- if (this.hasFile(fileEntry.src) === false) {
918
- filteredFileMap.push(fileEntry);
1107
+ const file = this.getFile(filePath);
1108
+ return this.getTranspiledFileContentsPipeline.run(async () => {
1109
+ const result = {
1110
+ destPath: file.destPath,
1111
+ pkgPath: file.pkgPath,
1112
+ srcPath: file.srcPath
1113
+ };
1114
+ const expectedPkgPath = file.pkgPath.toLowerCase();
1115
+ const expectedMapPath = `${expectedPkgPath}.map`;
1116
+ const expectedTypedefPkgPath = expectedPkgPath.replace(/\.brs$/i, '.d.bs');
1117
+ //add a temporary plugin to tap into the file writing process
1118
+ const plugin = this.plugins.addFirst({
1119
+ name: 'getTranspiledFileContents',
1120
+ beforeWriteFile: (event) => {
1121
+ const pkgPath = event.file.pkgPath.toLowerCase();
1122
+ switch (pkgPath) {
1123
+ //this is the actual transpiled file
1124
+ case expectedPkgPath:
1125
+ result.code = event.file.data.toString();
1126
+ break;
1127
+ //this is the sourcemap
1128
+ case expectedMapPath:
1129
+ result.map = event.file.data.toString();
1130
+ break;
1131
+ //this is the typedef
1132
+ case expectedTypedefPkgPath:
1133
+ result.typedef = event.file.data.toString();
1134
+ break;
1135
+ default:
1136
+ //no idea what this file is. just ignore it
1137
+ }
1138
+ //mark every file as processed so it they don't get written to the output directory
1139
+ event.processedFiles.add(event.file);
1140
+ }
1141
+ });
1142
+ try {
1143
+ //now that the plugin has been registered, run the build with just this file
1144
+ await this.build({
1145
+ files: [file]
1146
+ });
919
1147
  }
1148
+ finally {
1149
+ this.plugins.remove(plugin);
1150
+ }
1151
+ return result;
1152
+ });
1153
+ }
1154
+ /**
1155
+ * Get the absolute output path for a file
1156
+ */
1157
+ getOutputPath(file, stagingDir = this.getStagingDir()) {
1158
+ return (0, util_1.standardizePath) `${stagingDir}/${file.pkgPath}`;
1159
+ }
1160
+ getStagingDir(stagingDir) {
1161
+ var _a, _b;
1162
+ let result = (_a = stagingDir !== null && stagingDir !== void 0 ? stagingDir : this.options.stagingDir) !== null && _a !== void 0 ? _a : this.options.stagingDir;
1163
+ if (!result) {
1164
+ result = roku_deploy_1.rokuDeploy.getOptions(this.options).stagingDir;
920
1165
  }
921
- const { entries, astEditor } = this.beforeProgramTranspile(fileMap, this.options.stagingDir);
922
- const result = this._getTranspiledFileContents(this.getFile(filePath));
923
- this.afterProgramTranspile(entries, astEditor);
1166
+ result = (0, util_1.standardizePath) `${path.resolve((_b = this.options.cwd) !== null && _b !== void 0 ? _b : process.cwd(), result !== null && result !== void 0 ? result : '/')}`;
924
1167
  return result;
925
1168
  }
926
1169
  /**
927
- * Internal function used to transpile files.
928
- * This does not write anything to the file system
1170
+ * Prepare the program for building
1171
+ * @param files the list of files that should be prepared
929
1172
  */
930
- _getTranspiledFileContents(file, outputPath) {
931
- const editor = new AstEditor_1.AstEditor();
932
- this.plugins.emit('beforeFileTranspile', {
1173
+ async prepare(files) {
1174
+ const programEvent = {
933
1175
  program: this,
934
- file: file,
935
- outputPath: outputPath,
936
- editor: editor
1176
+ editor: this.editor,
1177
+ files: files
1178
+ };
1179
+ //assign an editor to every file
1180
+ for (const file of files) {
1181
+ //if the file doesn't have an editor yet, assign one now
1182
+ if (!file.editor) {
1183
+ file.editor = new Editor_1.Editor();
1184
+ }
1185
+ }
1186
+ files.sort((a, b) => {
1187
+ if (a.pkgPath < b.pkgPath) {
1188
+ return -1;
1189
+ }
1190
+ else if (a.pkgPath > b.pkgPath) {
1191
+ return 1;
1192
+ }
1193
+ else {
1194
+ return 1;
1195
+ }
937
1196
  });
938
- //if we have any edits, assume the file needs to be transpiled
939
- if (editor.hasChanges) {
940
- //use the `editor` because it'll track the previous value for us and revert later on
941
- editor.setProperty(file, 'needsTranspiled', true);
942
- }
943
- //transpile the file
944
- const result = file.transpile();
945
- //generate the typedef if enabled
946
- let typedef;
947
- if ((0, reflection_1.isBrsFile)(file) && this.options.emitDefinitions) {
948
- typedef = file.getTypedef();
1197
+ await this.plugins.emitAsync('beforePrepareProgram', programEvent);
1198
+ await this.plugins.emitAsync('prepareProgram', programEvent);
1199
+ const stagingDir = this.getStagingDir();
1200
+ const entries = [];
1201
+ for (const file of files) {
1202
+ //if the file doesn't have an editor yet, assign one now
1203
+ if (!file.editor) {
1204
+ file.editor = new Editor_1.Editor();
1205
+ }
1206
+ const event = {
1207
+ program: this,
1208
+ file: file,
1209
+ editor: file.editor,
1210
+ outputPath: this.getOutputPath(file, stagingDir)
1211
+ };
1212
+ await this.plugins.emitAsync('beforePrepareFile', event);
1213
+ await this.plugins.emitAsync('prepareFile', event);
1214
+ await this.plugins.emitAsync('afterPrepareFile', event);
1215
+ //TODO remove this in v1
1216
+ entries.push(event);
949
1217
  }
950
- const event = {
951
- program: this,
952
- file: file,
953
- outputPath: outputPath,
954
- editor: editor,
955
- code: result.code,
956
- map: result.map,
957
- typedef: typedef
958
- };
959
- this.plugins.emit('afterFileTranspile', event);
960
- //undo all `editor` edits that may have been applied to this file.
961
- editor.undoAll();
962
- return {
963
- srcPath: file.srcPath,
964
- pkgPath: file.pkgPath,
965
- code: event.code,
966
- map: event.map,
967
- typedef: event.typedef
968
- };
1218
+ await this.plugins.emitAsync('afterPrepareProgram', programEvent);
1219
+ return files;
969
1220
  }
970
- beforeProgramTranspile(fileEntries, stagingDir) {
971
- // map fileEntries using their path as key, to avoid excessive "find()" operations
972
- const mappedFileEntries = fileEntries.reduce((collection, entry) => {
973
- collection[(0, util_1.standardizePath) `${entry.src}`] = entry;
974
- return collection;
975
- }, {});
976
- const getOutputPath = (file) => {
977
- let filePathObj = mappedFileEntries[(0, util_1.standardizePath) `${file.srcPath}`];
978
- if (!filePathObj) {
979
- //this file has been added in-memory, from a plugin, for example
980
- filePathObj = {
981
- //add an interpolated src path (since it doesn't actually exist in memory)
982
- src: `bsc:/${file.pkgPath}`,
983
- dest: file.pkgPath
984
- };
985
- }
986
- //replace the file extension
987
- let outputPath = filePathObj.dest.replace(/\.bs$/gi, '.brs');
988
- //prepend the staging folder path
989
- outputPath = (0, util_1.standardizePath) `${stagingDir}/${outputPath}`;
990
- return outputPath;
991
- };
992
- const entries = Object.values(this.files).map(file => {
993
- return {
1221
+ /**
1222
+ * Generate the contents of every file
1223
+ */
1224
+ async serialize(files) {
1225
+ const allFiles = new Map();
1226
+ const serializeProgramEvent = await this.plugins.emitAsync('beforeSerializeProgram', {
1227
+ program: this,
1228
+ files: files,
1229
+ result: allFiles
1230
+ });
1231
+ await this.plugins.emitAsync('onSerializeProgram', {
1232
+ program: this,
1233
+ files: files,
1234
+ result: allFiles
1235
+ });
1236
+ //sort the entries to make transpiling more deterministic
1237
+ files = serializeProgramEvent.files.sort((a, b) => {
1238
+ return a.srcPath < b.srcPath ? -1 : 1;
1239
+ });
1240
+ // serialize each file
1241
+ for (const file of files) {
1242
+ const event = {
1243
+ program: this,
994
1244
  file: file,
995
- outputPath: getOutputPath(file)
1245
+ result: allFiles
996
1246
  };
1247
+ await this.plugins.emitAsync('beforeSerializeFile', event);
1248
+ await this.plugins.emitAsync('serializeFile', event);
1249
+ await this.plugins.emitAsync('afterSerializeFile', event);
1250
+ }
1251
+ this.plugins.emit('afterSerializeProgram', {
1252
+ program: this,
1253
+ files: files,
1254
+ result: allFiles
997
1255
  });
998
- const astEditor = new AstEditor_1.AstEditor();
999
- this.plugins.emit('beforeProgramTranspile', {
1256
+ return allFiles;
1257
+ }
1258
+ /**
1259
+ * Write the entire project to disk
1260
+ */
1261
+ async write(stagingDir, files) {
1262
+ const programEvent = await this.plugins.emitAsync('beforeWriteProgram', {
1000
1263
  program: this,
1001
- entries: entries,
1002
- editor: astEditor
1264
+ files: files,
1265
+ stagingDir: stagingDir
1003
1266
  });
1004
- return {
1005
- entries: entries,
1006
- getOutputPath: getOutputPath,
1007
- astEditor: astEditor
1008
- };
1267
+ //empty the staging directory
1268
+ await fsExtra.emptyDir(stagingDir);
1269
+ const serializedFiles = [...files]
1270
+ .map(([, serializedFiles]) => serializedFiles)
1271
+ .flat();
1272
+ //write all the files to disk (asynchronously)
1273
+ await Promise.all(serializedFiles.map(async (file) => {
1274
+ const event = await this.plugins.emitAsync('beforeWriteFile', {
1275
+ program: this,
1276
+ file: file,
1277
+ outputPath: this.getOutputPath(file, stagingDir),
1278
+ processedFiles: new Set()
1279
+ });
1280
+ await this.plugins.emitAsync('writeFile', event);
1281
+ await this.plugins.emitAsync('afterWriteFile', event);
1282
+ }));
1283
+ await this.plugins.emitAsync('afterWriteProgram', programEvent);
1009
1284
  }
1010
- async transpile(fileEntries, stagingDir) {
1011
- const { entries, getOutputPath, astEditor } = this.beforeProgramTranspile(fileEntries, stagingDir);
1012
- const processedFiles = new Set();
1013
- const transpileFile = async (srcPath, outputPath) => {
1014
- //find the file in the program
1015
- const file = this.getFile(srcPath);
1016
- //mark this file as processed so we don't process it more than once
1017
- processedFiles.add(outputPath === null || outputPath === void 0 ? void 0 : outputPath.toLowerCase());
1018
- //skip transpiling typedef files
1019
- if ((0, reflection_1.isBrsFile)(file) && file.isTypedef) {
1020
- return;
1021
- }
1022
- const fileTranspileResult = this._getTranspiledFileContents(file, outputPath);
1023
- //make sure the full dir path exists
1024
- await fsExtra.ensureDir(path.dirname(outputPath));
1025
- if (await fsExtra.pathExists(outputPath)) {
1026
- throw new Error(`Error while transpiling "${file.srcPath}". A file already exists at "${outputPath}" and will not be overwritten.`);
1027
- }
1028
- const writeMapPromise = fileTranspileResult.map ? fsExtra.writeFile(`${outputPath}.map`, fileTranspileResult.map.toString()) : null;
1029
- await Promise.all([
1030
- fsExtra.writeFile(outputPath, fileTranspileResult.code),
1031
- writeMapPromise
1032
- ]);
1033
- if (fileTranspileResult.typedef) {
1034
- const typedefPath = outputPath.replace(/\.brs$/i, '.d.bs');
1035
- await fsExtra.writeFile(typedefPath, fileTranspileResult.typedef);
1036
- }
1037
- };
1038
- let promises = entries.map(async (entry) => {
1285
+ /**
1286
+ * Build the project. This transpiles/transforms/copies all files and moves them to the staging directory
1287
+ * @param options the list of options used to build the program
1288
+ */
1289
+ async build(options) {
1290
+ //run a single build at a time
1291
+ await this.buildPipeline.run(async () => {
1039
1292
  var _a;
1040
- return transpileFile((_a = entry === null || entry === void 0 ? void 0 : entry.file) === null || _a === void 0 ? void 0 : _a.srcPath, entry.outputPath);
1041
- });
1042
- //if there's no bslib file already loaded into the program, copy it to the staging directory
1043
- if (!this.getFile(bslibAliasedRokuModulesPkgPath) && !this.getFile((0, util_1.standardizePath) `source/bslib.brs`)) {
1044
- promises.push(util_1.util.copyBslibToStaging(stagingDir));
1045
- }
1046
- await Promise.all(promises);
1047
- //transpile any new files that plugins added since the start of this transpile process
1048
- do {
1049
- promises = [];
1050
- for (const key in this.files) {
1051
- const file = this.files[key];
1052
- //this is a new file
1053
- const outputPath = getOutputPath(file);
1054
- if (!processedFiles.has(outputPath === null || outputPath === void 0 ? void 0 : outputPath.toLowerCase())) {
1055
- promises.push(transpileFile(file === null || file === void 0 ? void 0 : file.srcPath, outputPath));
1056
- }
1057
- }
1058
- if (promises.length > 0) {
1059
- this.logger.info(`Transpiling ${promises.length} new files`);
1060
- await Promise.all(promises);
1293
+ const stagingDir = this.getStagingDir(options === null || options === void 0 ? void 0 : options.stagingDir);
1294
+ const event = await this.plugins.emitAsync('beforeBuildProgram', {
1295
+ program: this,
1296
+ editor: this.editor,
1297
+ files: (_a = options === null || options === void 0 ? void 0 : options.files) !== null && _a !== void 0 ? _a : Object.values(this.files)
1298
+ });
1299
+ //prepare the program (and files) for building
1300
+ event.files = await this.prepare(event.files);
1301
+ //stage the entire program
1302
+ const serializedFilesByFile = await this.serialize(event.files);
1303
+ await this.write(stagingDir, serializedFilesByFile);
1304
+ await this.plugins.emitAsync('afterBuildProgram', event);
1305
+ //undo all edits for the program
1306
+ this.editor.undoAll();
1307
+ //undo all edits for each file
1308
+ for (const file of event.files) {
1309
+ file.editor.undoAll();
1061
1310
  }
1062
- } while (promises.length > 0);
1063
- this.afterProgramTranspile(entries, astEditor);
1064
- }
1065
- afterProgramTranspile(entries, astEditor) {
1066
- this.plugins.emit('afterProgramTranspile', {
1067
- program: this,
1068
- entries: entries,
1069
- editor: astEditor
1070
1311
  });
1071
- astEditor.undoAll();
1072
1312
  }
1073
1313
  /**
1074
1314
  * Find a list of files in the program that have a function with the given name (case INsensitive)
@@ -1139,36 +1379,84 @@ class Program {
1139
1379
  }
1140
1380
  return files;
1141
1381
  }
1382
+ /**
1383
+ * Modify a parsed manifest map by reading `bs_const` and injecting values from `options.manifest.bs_const`
1384
+ * @param parsedManifest The manifest map to read from and modify
1385
+ */
1386
+ buildBsConstsIntoParsedManifest(parsedManifest) {
1387
+ var _a, _b;
1388
+ // Lift the bs_consts defined in the manifest
1389
+ let bsConsts = (0, Manifest_1.getBsConst)(parsedManifest, false);
1390
+ // Override or delete any bs_consts defined in the bs config
1391
+ 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) {
1392
+ const value = this.options.manifest.bs_const[key];
1393
+ if (value === null) {
1394
+ bsConsts.delete(key);
1395
+ }
1396
+ else {
1397
+ bsConsts.set(key, value);
1398
+ }
1399
+ }
1400
+ // convert the new list of bs consts back into a string for the rest of the down stream systems to use
1401
+ let constString = '';
1402
+ for (const [key, value] of bsConsts) {
1403
+ constString += `${constString !== '' ? ';' : ''}${key}=${value.toString()}`;
1404
+ }
1405
+ // Set the updated bs_const value
1406
+ parsedManifest.set('bs_const', constString);
1407
+ }
1408
+ /**
1409
+ * Try to find and load the manifest into memory
1410
+ * @param manifestFileObj A pointer to a potential manifest file object found during loading
1411
+ */
1412
+ loadManifest(manifestFileObj) {
1413
+ let manifestPath = manifestFileObj
1414
+ ? manifestFileObj.src
1415
+ : path.join(this.options.rootDir, 'manifest');
1416
+ try {
1417
+ // we only load this manifest once, so do it sync to improve speed downstream
1418
+ const contents = fsExtra.readFileSync(manifestPath, 'utf-8');
1419
+ const parsedManifest = (0, Manifest_1.parseManifest)(contents);
1420
+ this.buildBsConstsIntoParsedManifest(parsedManifest);
1421
+ this._manifest = parsedManifest;
1422
+ }
1423
+ catch (e) {
1424
+ this._manifest = new Map();
1425
+ }
1426
+ }
1142
1427
  /**
1143
1428
  * Get a map of the manifest information
1144
1429
  */
1145
1430
  getManifest() {
1146
1431
  if (!this._manifest) {
1147
- //load the manifest file.
1148
- //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
1149
- let manifestPath = path.join(this.options.rootDir, 'manifest');
1150
- let contents;
1151
- try {
1152
- //we only load this manifest once, so do it sync to improve speed downstream
1153
- contents = fsExtra.readFileSync(manifestPath, 'utf-8');
1154
- this._manifest = (0, Manifest_1.parseManifest)(contents);
1155
- }
1156
- catch (err) {
1157
- this._manifest = new Map();
1158
- }
1432
+ this.loadManifest();
1159
1433
  }
1160
1434
  return this._manifest;
1161
1435
  }
1162
1436
  dispose() {
1437
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1438
+ this.plugins.emit('beforeProgramDispose', { program: this });
1163
1439
  for (let filePath in this.files) {
1164
- this.files[filePath].dispose();
1440
+ (_b = (_a = this.files[filePath]) === null || _a === void 0 ? void 0 : _a.dispose) === null || _b === void 0 ? void 0 : _b.call(_a);
1165
1441
  }
1166
1442
  for (let name in this.scopes) {
1167
- this.scopes[name].dispose();
1443
+ (_d = (_c = this.scopes[name]) === null || _c === void 0 ? void 0 : _c.dispose) === null || _d === void 0 ? void 0 : _d.call(_c);
1168
1444
  }
1169
- this.globalScope.dispose();
1170
- this.dependencyGraph.dispose();
1445
+ (_f = (_e = this.globalScope) === null || _e === void 0 ? void 0 : _e.dispose) === null || _f === void 0 ? void 0 : _f.call(_e);
1446
+ (_h = (_g = this.dependencyGraph) === null || _g === void 0 ? void 0 : _g.dispose) === null || _h === void 0 ? void 0 : _h.call(_g);
1171
1447
  }
1172
1448
  }
1173
1449
  exports.Program = Program;
1450
+ class ProvideFileEventInternal {
1451
+ constructor(program, srcPath, destPath, data, fileFactory) {
1452
+ var _a;
1453
+ this.program = program;
1454
+ this.srcPath = srcPath;
1455
+ this.destPath = destPath;
1456
+ this.data = data;
1457
+ this.fileFactory = fileFactory;
1458
+ this.files = [];
1459
+ this.srcExtension = (_a = path.extname(srcPath)) === null || _a === void 0 ? void 0 : _a.toLowerCase();
1460
+ }
1461
+ }
1174
1462
  //# sourceMappingURL=Program.js.map