brighterscript 1.0.0-alpha.52 → 1.0.0-alpha.53

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 (163) hide show
  1. package/README.md +5 -3
  2. package/bsconfig.schema.json +151 -0
  3. package/dist/BsConfig.d.ts +158 -44
  4. package/dist/BsConfig.js +24 -0
  5. package/dist/BsConfig.js.map +1 -1
  6. package/dist/BusyStatusTracker.js +3 -0
  7. package/dist/BusyStatusTracker.js.map +1 -1
  8. package/dist/CrossScopeValidator.js +5 -0
  9. package/dist/CrossScopeValidator.js.map +1 -1
  10. package/dist/DiagnosticFilterer.d.ts +17 -1
  11. package/dist/DiagnosticFilterer.js +69 -6
  12. package/dist/DiagnosticFilterer.js.map +1 -1
  13. package/dist/DiagnosticManager.d.ts +11 -1
  14. package/dist/DiagnosticManager.js +22 -3
  15. package/dist/DiagnosticManager.js.map +1 -1
  16. package/dist/DiagnosticMessages.d.ts +93 -4
  17. package/dist/DiagnosticMessages.js +91 -5
  18. package/dist/DiagnosticMessages.js.map +1 -1
  19. package/dist/LanguageServer.d.ts +15 -3
  20. package/dist/LanguageServer.js +53 -6
  21. package/dist/LanguageServer.js.map +1 -1
  22. package/dist/Logger.d.ts +7 -7
  23. package/dist/Logger.js +2 -2
  24. package/dist/Logger.js.map +1 -1
  25. package/dist/PluginInterface.js +2 -1
  26. package/dist/PluginInterface.js.map +1 -1
  27. package/dist/Program.d.ts +106 -9
  28. package/dist/Program.js +273 -23
  29. package/dist/Program.js.map +1 -1
  30. package/dist/ProgramBuilder.js +6 -0
  31. package/dist/ProgramBuilder.js.map +1 -1
  32. package/dist/RokuConstants.d.ts +123 -0
  33. package/dist/RokuConstants.js +87 -0
  34. package/dist/RokuConstants.js.map +1 -0
  35. package/dist/ScopeNamespaceLookup.js.map +1 -1
  36. package/dist/Stopwatch.js +2 -2
  37. package/dist/Stopwatch.js.map +1 -1
  38. package/dist/SymbolTable.d.ts +1 -0
  39. package/dist/SymbolTable.js +20 -12
  40. package/dist/SymbolTable.js.map +1 -1
  41. package/dist/Throttler.d.ts +1 -1
  42. package/dist/Throttler.js.map +1 -1
  43. package/dist/astUtils/reflection.d.ts +6 -1
  44. package/dist/astUtils/reflection.js +15 -4
  45. package/dist/astUtils/reflection.js.map +1 -1
  46. package/dist/astUtils/visitors.d.ts +1 -1
  47. package/dist/astUtils/visitors.js +2 -0
  48. package/dist/astUtils/visitors.js.map +1 -1
  49. package/dist/bscPlugin/BscPlugin.d.ts +2 -2
  50. package/dist/bscPlugin/BscPlugin.js +1 -1
  51. package/dist/bscPlugin/BscPlugin.js.map +1 -1
  52. package/dist/bscPlugin/CallExpressionInfo.js.map +1 -1
  53. package/dist/bscPlugin/SignatureHelpUtil.js.map +1 -1
  54. package/dist/bscPlugin/codeActions/FixAllCodeActionsProcessor.d.ts +3 -3
  55. package/dist/bscPlugin/codeActions/FixAllCodeActionsProcessor.js.map +1 -1
  56. package/dist/bscPlugin/completions/CompletionsProcessor.js +2 -3
  57. package/dist/bscPlugin/completions/CompletionsProcessor.js.map +1 -1
  58. package/dist/bscPlugin/definition/DefinitionProvider.d.ts +67 -2
  59. package/dist/bscPlugin/definition/DefinitionProvider.js +244 -5
  60. package/dist/bscPlugin/definition/DefinitionProvider.js.map +1 -1
  61. package/dist/bscPlugin/references/ReferencesProvider.js +7 -1
  62. package/dist/bscPlugin/references/ReferencesProvider.js.map +1 -1
  63. package/dist/bscPlugin/validation/BrsFileValidator.d.ts +40 -6
  64. package/dist/bscPlugin/validation/BrsFileValidator.js +109 -18
  65. package/dist/bscPlugin/validation/BrsFileValidator.js.map +1 -1
  66. package/dist/bscPlugin/validation/ProgramValidator.d.ts +10 -0
  67. package/dist/bscPlugin/validation/ProgramValidator.js +54 -0
  68. package/dist/bscPlugin/validation/ProgramValidator.js.map +1 -1
  69. package/dist/bscPlugin/validation/ScopeValidator.js +55 -30
  70. package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
  71. package/dist/bscPlugin/validation/XmlFileValidator.d.ts +18 -0
  72. package/dist/bscPlugin/validation/XmlFileValidator.js +51 -0
  73. package/dist/bscPlugin/validation/XmlFileValidator.js.map +1 -1
  74. package/dist/cli.js.map +1 -1
  75. package/dist/diagnosticUtils.d.ts +1 -1
  76. package/dist/diagnosticUtils.js.map +1 -1
  77. package/dist/files/BrsFile.d.ts +2 -2
  78. package/dist/files/BrsFile.js +2 -2
  79. package/dist/files/BrsFile.js.map +1 -1
  80. package/dist/files/XmlFile.d.ts +51 -1
  81. package/dist/files/XmlFile.js +264 -0
  82. package/dist/files/XmlFile.js.map +1 -1
  83. package/dist/formatUtils.d.ts +20 -0
  84. package/dist/formatUtils.js +38 -0
  85. package/dist/formatUtils.js.map +1 -0
  86. package/dist/globalCallables.d.ts +1 -1
  87. package/dist/globalCallables.js +4 -1
  88. package/dist/globalCallables.js.map +1 -1
  89. package/dist/interfaces.d.ts +60 -5
  90. package/dist/interfaces.js.map +1 -1
  91. package/dist/lexer/Lexer.d.ts +26 -0
  92. package/dist/lexer/Lexer.js +140 -15
  93. package/dist/lexer/Lexer.js.map +1 -1
  94. package/dist/lexer/TokenKind.d.ts +27 -0
  95. package/dist/lexer/TokenKind.js +77 -3
  96. package/dist/lexer/TokenKind.js.map +1 -1
  97. package/dist/lsp/ActionQueue.js +1 -2
  98. package/dist/lsp/ActionQueue.js.map +1 -1
  99. package/dist/lsp/DocumentManager.js +4 -0
  100. package/dist/lsp/DocumentManager.js.map +1 -1
  101. package/dist/lsp/LspProject.d.ts +2 -2
  102. package/dist/lsp/Project.d.ts +2 -2
  103. package/dist/lsp/Project.js +2 -0
  104. package/dist/lsp/Project.js.map +1 -1
  105. package/dist/lsp/ProjectManager.d.ts +7 -2
  106. package/dist/lsp/ProjectManager.js +3 -1
  107. package/dist/lsp/ProjectManager.js.map +1 -1
  108. package/dist/lsp/worker/MessageHandler.d.ts +1 -0
  109. package/dist/lsp/worker/MessageHandler.js +9 -0
  110. package/dist/lsp/worker/MessageHandler.js.map +1 -1
  111. package/dist/lsp/worker/WorkerPool.d.ts +51 -17
  112. package/dist/lsp/worker/WorkerPool.js +118 -40
  113. package/dist/lsp/worker/WorkerPool.js.map +1 -1
  114. package/dist/lsp/worker/WorkerThreadProject.d.ts +16 -3
  115. package/dist/lsp/worker/WorkerThreadProject.js +50 -10
  116. package/dist/lsp/worker/WorkerThreadProject.js.map +1 -1
  117. package/dist/lsp/worker/WorkerThreadProjectRunner.js +6 -0
  118. package/dist/lsp/worker/WorkerThreadProjectRunner.js.map +1 -1
  119. package/dist/lsp/worker/run.js +20 -3
  120. package/dist/lsp/worker/run.js.map +1 -1
  121. package/dist/parser/AstNode.d.ts +59 -2
  122. package/dist/parser/AstNode.js +63 -5
  123. package/dist/parser/AstNode.js.map +1 -1
  124. package/dist/parser/BrsTranspileState.d.ts +44 -0
  125. package/dist/parser/BrsTranspileState.js +49 -0
  126. package/dist/parser/BrsTranspileState.js.map +1 -1
  127. package/dist/parser/Expression.d.ts +56 -8
  128. package/dist/parser/Expression.js +91 -8
  129. package/dist/parser/Expression.js.map +1 -1
  130. package/dist/parser/Parser.d.ts +0 -5
  131. package/dist/parser/Parser.js +5 -9
  132. package/dist/parser/Parser.js.map +1 -1
  133. package/dist/parser/SGParser.d.ts +7 -1
  134. package/dist/parser/SGParser.js +20 -10
  135. package/dist/parser/SGParser.js.map +1 -1
  136. package/dist/parser/SGTypes.js +8 -2
  137. package/dist/parser/SGTypes.js.map +1 -1
  138. package/dist/parser/Statement.d.ts +21 -10
  139. package/dist/parser/Statement.js +61 -1
  140. package/dist/parser/Statement.js.map +1 -1
  141. package/dist/preprocessor/Manifest.d.ts +16 -0
  142. package/dist/preprocessor/Manifest.js +21 -5
  143. package/dist/preprocessor/Manifest.js.map +1 -1
  144. package/dist/roku-types/index.d.ts +3 -0
  145. package/dist/types/BuiltInInterfaceAdder.js +1 -1
  146. package/dist/types/BuiltInInterfaceAdder.js.map +1 -1
  147. package/dist/types/InheritableType.d.ts +0 -1
  148. package/dist/types/InheritableType.js +12 -11
  149. package/dist/types/InheritableType.js.map +1 -1
  150. package/dist/types/InterfaceType.js +6 -0
  151. package/dist/types/InterfaceType.js.map +1 -1
  152. package/dist/types/IntersectionType.d.ts +1 -1
  153. package/dist/types/IntersectionType.js +7 -7
  154. package/dist/types/IntersectionType.js.map +1 -1
  155. package/dist/types/ReferenceType.js +1 -1
  156. package/dist/types/ReferenceType.js.map +1 -1
  157. package/dist/types/helpers.d.ts +2 -2
  158. package/dist/types/helpers.js +8 -5
  159. package/dist/types/helpers.js.map +1 -1
  160. package/dist/util.d.ts +38 -4
  161. package/dist/util.js +296 -29
  162. package/dist/util.js.map +1 -1
  163. package/package.json +12 -18
package/dist/Program.js CHANGED
@@ -4,6 +4,7 @@ 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 semver = require("semver");
7
8
  const vscode_languageserver_1 = require("vscode-languageserver");
8
9
  const Scope_1 = require("./Scope");
9
10
  const SymbolTable_1 = require("./SymbolTable");
@@ -16,6 +17,7 @@ const logging_1 = require("./logging");
16
17
  const chalk_1 = require("chalk");
17
18
  const globalCallables_1 = require("./globalCallables");
18
19
  const Manifest_1 = require("./preprocessor/Manifest");
20
+ const RokuConstants_1 = require("./RokuConstants");
19
21
  const vscode_uri_1 = require("vscode-uri");
20
22
  const PluginInterface_1 = require("./PluginInterface");
21
23
  const reflection_1 = require("./astUtils/reflection");
@@ -53,6 +55,10 @@ const deferred_1 = require("./deferred");
53
55
  const roFunctionType_1 = require("./types/roFunctionType");
54
56
  const bslibNonAliasedRokuModulesPkgPath = (0, util_1.standardizePath) `source/roku_modules/rokucommunity_bslib/bslib.brs`;
55
57
  const bslibAliasedRokuModulesPkgPath = (0, util_1.standardizePath) `source/roku_modules/bslib/bslib.brs`;
58
+ /**
59
+ * The built-in Roku SceneGraph nodes, keyed by their lower-case name
60
+ */
61
+ const builtInSceneGraphNodes = roku_types_1.nodes;
56
62
  class Program {
57
63
  constructor(
58
64
  /**
@@ -121,6 +127,7 @@ class Program {
121
127
  xmlFilesValidated: [],
122
128
  changedSymbols: new Map(),
123
129
  changedComponentTypes: [],
130
+ scopesToInvalidate: [],
124
131
  scopesToValidate: [],
125
132
  filesToBeValidatedInScopeContext: new Set()
126
133
  };
@@ -140,12 +147,19 @@ class Program {
140
147
  this.validationMinSyncDuration = 75;
141
148
  this.getFilePathCache = new Map();
142
149
  this.sortedScopeNames = undefined;
150
+ this.scopesPerFile = new Map();
143
151
  this.getTranspiledFileContentsPipeline = new ActionPipeline_1.ActionPipeline();
144
152
  this.buildPipeline = new ActionPipeline_1.ActionPipeline();
145
153
  this.options = util_1.util.normalizeConfig(options);
146
154
  this.logger = logger !== null && logger !== void 0 ? logger : (0, logging_1.createLogger)(options);
147
155
  this.plugins = plugins || new PluginInterface_1.default([], { logger: this.logger });
148
156
  this.diagnostics = diagnosticsManager || new DiagnosticManager_1.DiagnosticManager();
157
+ //surface warnings for any deprecated bsconfig options that were used to build `this.options`
158
+ const deprecationDiagnostics = this.options._deprecationDiagnostics;
159
+ if ((deprecationDiagnostics === null || deprecationDiagnostics === void 0 ? void 0 : deprecationDiagnostics.length) > 0) {
160
+ this.diagnostics.register(deprecationDiagnostics);
161
+ }
162
+ delete this.options._deprecationDiagnostics;
149
163
  //try to find a location for the diagnostic if it doesn't have one
150
164
  this.diagnostics.locationResolver = (args) => {
151
165
  //find the first xml scope for this diagnostic
@@ -657,6 +671,112 @@ class Program {
657
671
  var _a;
658
672
  return (_a = this.getComponent(componentName)) === null || _a === void 0 ? void 0 : _a.scope;
659
673
  }
674
+ /**
675
+ * Get the names of all known SceneGraph nodes: the built-in Roku nodes plus every component
676
+ * defined in this program. Names keep their original casing and are deduplicated by lower-case name.
677
+ */
678
+ getSceneGraphNodeNames() {
679
+ var _a, _b;
680
+ const namesByLowerName = new Map();
681
+ for (const node of Object.values(builtInSceneGraphNodes)) {
682
+ namesByLowerName.set(node.name.toLowerCase(), node.name);
683
+ }
684
+ for (const componentName in this.components) {
685
+ const displayName = (_b = (_a = this.components[componentName][0]) === null || _a === void 0 ? void 0 : _a.file.componentName) === null || _b === void 0 ? void 0 : _b.text;
686
+ if (displayName) {
687
+ namesByLowerName.set(displayName.toLowerCase(), displayName);
688
+ }
689
+ }
690
+ return [...namesByLowerName.values()];
691
+ }
692
+ /**
693
+ * Determine whether a SceneGraph node with the given name exists, either as a built-in Roku node
694
+ * or as a component defined in this program.
695
+ */
696
+ hasSceneGraphNode(nodeName) {
697
+ if (!nodeName) {
698
+ return false;
699
+ }
700
+ return !!builtInSceneGraphNodes[nodeName.toLowerCase()] || !!this.getComponent(nodeName);
701
+ }
702
+ /**
703
+ * Get the built-in Roku node data and/or the project component file backing a SceneGraph node name.
704
+ * Returns `undefined` when no node or component matches.
705
+ */
706
+ getSceneGraphNode(nodeName) {
707
+ var _a;
708
+ if (!nodeName) {
709
+ return undefined;
710
+ }
711
+ const builtInNode = builtInSceneGraphNodes[nodeName.toLowerCase()];
712
+ const componentFile = (_a = this.getComponent(nodeName)) === null || _a === void 0 ? void 0 : _a.file;
713
+ if (!builtInNode && !componentFile) {
714
+ return undefined;
715
+ }
716
+ return { builtInNode: builtInNode, componentFile: componentFile };
717
+ }
718
+ /**
719
+ * Get every field available on a SceneGraph node, walking the full `extends` chain across both
720
+ * built-in Roku nodes and project components. Fields declared closer to the node take precedence
721
+ * over inherited fields with the same name.
722
+ */
723
+ getSceneGraphNodeFields(nodeName) {
724
+ const fieldsByLowerName = new Map();
725
+ const visitedNodeNames = new Set();
726
+ const addField = (field) => {
727
+ if (!field.name) {
728
+ return;
729
+ }
730
+ const lowerName = field.name.toLowerCase();
731
+ //the first definition we encounter is the closest one, so it wins
732
+ if (!fieldsByLowerName.has(lowerName)) {
733
+ fieldsByLowerName.set(lowerName, field);
734
+ }
735
+ };
736
+ const walk = (currentNodeName, origin) => {
737
+ var _a, _b, _c, _d, _e, _f;
738
+ const lowerName = currentNodeName === null || currentNodeName === void 0 ? void 0 : currentNodeName.toLowerCase();
739
+ if (!lowerName || visitedNodeNames.has(lowerName)) {
740
+ return;
741
+ }
742
+ visitedNodeNames.add(lowerName);
743
+ //prefer a project component (a project component can shadow a built-in of the same name)
744
+ const component = this.getComponent(currentNodeName);
745
+ if (component) {
746
+ for (const field of (_c = (_b = (_a = component.file.ast.componentElement) === null || _a === void 0 ? void 0 : _a.interfaceElement) === null || _b === void 0 ? void 0 : _b.fields) !== null && _c !== void 0 ? _c : []) {
747
+ addField({ name: field.id, type: field.type, default: field.value, origin: origin });
748
+ }
749
+ const parentName = (_d = component.file.ast.componentElement) === null || _d === void 0 ? void 0 : _d.extends;
750
+ if (parentName) {
751
+ walk(parentName, 'inherited');
752
+ }
753
+ return;
754
+ }
755
+ const builtInNode = builtInSceneGraphNodes[lowerName];
756
+ if (builtInNode) {
757
+ for (const field of (_e = builtInNode.fields) !== null && _e !== void 0 ? _e : []) {
758
+ addField({
759
+ name: field.name,
760
+ type: field.type,
761
+ default: field.default,
762
+ description: field.description,
763
+ accessPermission: field.accessPermission,
764
+ origin: origin
765
+ });
766
+ }
767
+ if ((_f = builtInNode.extends) === null || _f === void 0 ? void 0 : _f.name) {
768
+ walk(builtInNode.extends.name, 'inherited');
769
+ }
770
+ else if (lowerName !== 'node') {
771
+ //some scraped node entries are missing `extends` data, but every SceneGraph node
772
+ //ultimately descends from Node, so fall back to Node to keep its universal fields
773
+ walk('Node', 'inherited');
774
+ }
775
+ }
776
+ };
777
+ walk(nodeName, 'own');
778
+ return [...fieldsByLowerName.values()];
779
+ }
660
780
  /**
661
781
  * Update internal maps with this file reference
662
782
  */
@@ -665,9 +785,11 @@ class Program {
665
785
  file: file,
666
786
  program: this
667
787
  };
788
+ this.scopesPerFile.clear();
668
789
  this.plugins.emit('beforeAddFile', fileAddEvent);
669
790
  this.files[file.srcPath.toLowerCase()] = file;
670
791
  this.destMap.set(file.destPath.toLowerCase(), file);
792
+ this.plugins.emit('addFile', fileAddEvent);
671
793
  this.plugins.emit('afterAddFile', fileAddEvent);
672
794
  return file;
673
795
  }
@@ -677,6 +799,7 @@ class Program {
677
799
  unassignFile(file) {
678
800
  delete this.files[file.srcPath.toLowerCase()];
679
801
  this.destMap.delete(file.destPath.toLowerCase());
802
+ this.scopesPerFile.clear();
680
803
  return file;
681
804
  }
682
805
  setFile(fileParam, fileData) {
@@ -785,6 +908,7 @@ class Program {
785
908
  destPath = (0, util_1.standardizePath) `${util_1.util.replaceCaseInsensitive(srcPath, rootDir, '')}`;
786
909
  }
787
910
  else {
911
+ //`fileParam` here is `FileObj | { srcPath?: string; pkgPath?: string }`; duck-type across both shapes
788
912
  let param = fileParam;
789
913
  if (param.src) {
790
914
  srcPath = (0, util_1.standardizePath) `${param.src}`;
@@ -884,6 +1008,7 @@ class Program {
884
1008
  this.diagnostics.clearForFile(file.srcPath);
885
1009
  const event = { file: file, program: this };
886
1010
  this.plugins.emit('beforeRemoveFile', event);
1011
+ this.plugins.emit('removeFile', event);
887
1012
  //if there is a scope named the same as this file's path, remove it (i.e. xml scopes)
888
1013
  let scope = this.scopes[file.destPath];
889
1014
  if (scope) {
@@ -950,11 +1075,12 @@ class Program {
950
1075
  const xmlFilesValidated = this.validationDetails.xmlFilesValidated;
951
1076
  const changedSymbols = this.validationDetails.changedSymbols;
952
1077
  const changedComponentTypes = this.validationDetails.changedComponentTypes;
1078
+ const scopesToInvalidate = this.validationDetails.scopesToInvalidate;
953
1079
  const scopesToValidate = this.validationDetails.scopesToValidate;
954
1080
  const filesToBeValidatedInScopeContext = this.validationDetails.filesToBeValidatedInScopeContext;
955
1081
  //validate every file
956
1082
  let logValidateEnd = (status) => { };
957
- //will be populated later on during the correspnding sequencer event
1083
+ //will be populated later on during the corresponding sequencer event
958
1084
  let filesToProcess;
959
1085
  const sequencer = new Sequencer_1.Sequencer({
960
1086
  name: 'program.validate',
@@ -1028,7 +1154,7 @@ class Program {
1028
1154
  if ((0, reflection_1.isXmlFile)(file)) {
1029
1155
  this.addDeferredComponentTypeSymbolCreation(file);
1030
1156
  }
1031
- else if ((0, reflection_1.isBrsFile)(file)) {
1157
+ else if ((0, reflection_1.isBrsFile)(file) && !this.isFirstValidation) {
1032
1158
  const fileHasChanges = file.providedSymbols.changes.get(1 /* SymbolTypeFlag.runtime */).size > 0 || file.providedSymbols.changes.get(2 /* SymbolTypeFlag.typetime */).size > 0;
1033
1159
  if (fileHasChanges) {
1034
1160
  for (const scope of this.getScopesForFile(file)) {
@@ -1136,9 +1262,11 @@ class Program {
1136
1262
  this.logger.time(logging_1.LogLevel.info, ['addDiagnosticsForScopes'], () => {
1137
1263
  this.crossScopeValidation.addDiagnosticsForScopes(scopesToCheck);
1138
1264
  });
1139
- const filesToRevalidate = this.crossScopeValidation.getFilesRequiringChangedSymbol(scopesToCheck, changedSymbols);
1140
- for (const file of filesToRevalidate) {
1141
- filesToBeValidatedInScopeContext.add(file);
1265
+ if (!this.isFirstValidation) {
1266
+ const filesToRevalidate = this.crossScopeValidation.getFilesRequiringChangedSymbol(scopesToCheck, changedSymbols);
1267
+ for (const file of filesToRevalidate) {
1268
+ filesToBeValidatedInScopeContext.add(file);
1269
+ }
1142
1270
  }
1143
1271
  this.currentScopeValidationOptions = {
1144
1272
  filesToBeValidatedInScopeContext: filesToBeValidatedInScopeContext,
@@ -1149,18 +1277,29 @@ class Program {
1149
1277
  //can reset changedComponent types
1150
1278
  this.validationDetails.changedComponentTypes = [];
1151
1279
  })
1152
- .forEach('invalidate affected scopes', () => filesToBeValidatedInScopeContext, (file) => {
1280
+ .forEach('invalidating file segments', () => filesToBeValidatedInScopeContext, (file) => {
1153
1281
  if ((0, reflection_1.isBrsFile)(file)) {
1154
1282
  file.validationSegmenter.unValidateAllSegments();
1155
- for (const scope of this.getScopesForFile(file)) {
1283
+ if (!this.isFirstValidation) {
1284
+ scopesToInvalidate.push(...this.getScopesForFile(file));
1285
+ }
1286
+ }
1287
+ })
1288
+ .once('invalidate affected scopes', () => {
1289
+ if (this.isFirstValidation) {
1290
+ for (const scope of this.getAllUserScopes()) {
1291
+ scope.invalidate();
1292
+ }
1293
+ }
1294
+ else {
1295
+ for (const scope of scopesToInvalidate) {
1156
1296
  scope.invalidate();
1157
1297
  }
1158
1298
  }
1159
1299
  })
1160
1300
  .once('checking scopes to validate', () => {
1161
1301
  //sort the scope names so we get consistent results
1162
- for (const scopeName of this.getSortedScopeNames()) {
1163
- let scope = this.scopes[scopeName];
1302
+ for (const scope of this.getAllUserScopes()) {
1164
1303
  if (scope.shouldValidate(this.currentScopeValidationOptions)) {
1165
1304
  scopesToValidate.push(scope);
1166
1305
  }
@@ -1184,17 +1323,21 @@ class Program {
1184
1323
  })
1185
1324
  .once('detect duplicate component names', () => {
1186
1325
  this.detectDuplicateComponentNames();
1187
- this.isFirstValidation = false;
1188
- // can reset other validation details
1189
- this.validationDetails.changedSymbols = new Map();
1190
- this.validationDetails.scopesToValidate = [];
1191
- this.validationDetails.filesToBeValidatedInScopeContext = new Set();
1326
+ })
1327
+ .once('detect diagnostic filter issues', () => {
1328
+ this.diagnostics.detectPathLikeDiagnosticFilterCodes(this.options, { tags: [ProgramValidator_1.ProgramValidatorDiagnosticsTag] });
1192
1329
  })
1193
1330
  .onCancel(() => {
1194
1331
  logValidateEnd('cancelled');
1195
1332
  })
1196
1333
  .onSuccess(() => {
1197
1334
  logValidateEnd();
1335
+ this.isFirstValidation = false;
1336
+ // can reset other validation details
1337
+ this.validationDetails.changedSymbols = new Map();
1338
+ this.validationDetails.scopesToInvalidate = [];
1339
+ this.validationDetails.scopesToValidate = [];
1340
+ this.validationDetails.filesToBeValidatedInScopeContext = new Set();
1198
1341
  })
1199
1342
  .onComplete(() => {
1200
1343
  var _a, _b;
@@ -1228,8 +1371,7 @@ class Program {
1228
1371
  }
1229
1372
  getScopesForCrossScopeValidation(someComponentTypeChanged, didProvidedSymbolChange) {
1230
1373
  const scopesForCrossScopeValidation = [];
1231
- for (let scopeName of this.getSortedScopeNames()) {
1232
- let scope = this.scopes[scopeName];
1374
+ for (let scope of this.getAllUserScopes()) {
1233
1375
  if (this.globalScope === scope) {
1234
1376
  continue;
1235
1377
  }
@@ -1358,12 +1500,19 @@ class Program {
1358
1500
  }
1359
1501
  return this.sortedScopeNames;
1360
1502
  }
1503
+ getAllUserScopes() {
1504
+ return Object.values(this.scopes).filter(s => s.name !== 'global');
1505
+ }
1361
1506
  /**
1362
1507
  * Get a list of all scopes the file is loaded into
1363
1508
  * @param file the file
1364
1509
  */
1365
1510
  getScopesForFile(file) {
1366
1511
  const resolvedFile = typeof file === 'string' ? this.getFile(file) : file;
1512
+ const cachedResult = this.scopesPerFile.get(resolvedFile);
1513
+ if (cachedResult) {
1514
+ return cachedResult;
1515
+ }
1367
1516
  let result = [];
1368
1517
  if (resolvedFile) {
1369
1518
  const scopeKeys = this.getSortedScopeNames();
@@ -1374,6 +1523,7 @@ class Program {
1374
1523
  }
1375
1524
  }
1376
1525
  }
1526
+ this.scopesPerFile.set(resolvedFile, result);
1377
1527
  return result;
1378
1528
  }
1379
1529
  /**
@@ -1515,9 +1665,6 @@ class Program {
1515
1665
  this.plugins.emit('afterProvideDefinition', event);
1516
1666
  return event.definitions;
1517
1667
  }
1518
- /**
1519
- * Get hover information for a file and position
1520
- */
1521
1668
  getHover(srcPath, position) {
1522
1669
  let file = this.getFile(srcPath);
1523
1670
  let result;
@@ -1643,8 +1790,9 @@ class Program {
1643
1790
  }
1644
1791
  /**
1645
1792
  * Compute "source fix all" code actions for the given file.
1646
- * Fires the `onGetSourceFixAllCodeActions` plugin event with all diagnostics for the file (no range filter),
1647
- * then converts each contributed SourceFixAllCodeAction into an LSP CodeAction.
1793
+ * Fires the `provideSourceFixAllCodeActions` plugin event (along with its before/after variants)
1794
+ * with all diagnostics for the file (no range filter), then converts each contributed
1795
+ * SourceFixAllCodeAction into an LSP CodeAction.
1648
1796
  */
1649
1797
  getSourceFixAllCodeActions(srcPath) {
1650
1798
  const actions = [];
@@ -1655,13 +1803,16 @@ class Program {
1655
1803
  .getDiagnostics()
1656
1804
  .filter(x => { var _a; return ((_a = x.location) === null || _a === void 0 ? void 0 : _a.uri) === fileUri; });
1657
1805
  const scopes = this.getScopesForFile(file);
1658
- this.plugins.emit('onGetSourceFixAllCodeActions', {
1806
+ const event = {
1659
1807
  program: this,
1660
1808
  file: file,
1661
1809
  diagnostics: diagnostics,
1662
1810
  scopes: scopes,
1663
1811
  actions: actions
1664
- });
1812
+ };
1813
+ this.plugins.emit('beforeProvideSourceFixAllCodeActions', event);
1814
+ this.plugins.emit('provideSourceFixAllCodeActions', event);
1815
+ this.plugins.emit('afterProvideSourceFixAllCodeActions', event);
1665
1816
  }
1666
1817
  return actions.map(action => {
1667
1818
  var _a;
@@ -1899,6 +2050,7 @@ class Program {
1899
2050
  files: files,
1900
2051
  outDir: outDir
1901
2052
  });
2053
+ await this.plugins.emitAsync('writeProgram', programEvent);
1902
2054
  //empty the out directory
1903
2055
  await fsExtra.emptyDir(outDir);
1904
2056
  const serializedFiles = [...files]
@@ -1931,6 +2083,7 @@ class Program {
1931
2083
  editor: this.editor,
1932
2084
  files: (_a = options === null || options === void 0 ? void 0 : options.files) !== null && _a !== void 0 ? _a : Object.values(this.files)
1933
2085
  });
2086
+ await this.plugins.emitAsync('buildProgram', event);
1934
2087
  //prepare the program (and files) for building
1935
2088
  event.files = await this.prepare(event.files);
1936
2089
  //stage the entire program
@@ -2073,9 +2226,13 @@ class Program {
2073
2226
  const parsedManifest = (0, Manifest_1.parseManifest)(contents);
2074
2227
  this.buildBsConstsIntoParsedManifest(parsedManifest);
2075
2228
  this._manifest = parsedManifest;
2229
+ this._manifestEntries = (0, Manifest_1.parseManifestEntries)(contents);
2230
+ this._manifestPath = manifestPath;
2076
2231
  }
2077
2232
  catch (e) {
2078
2233
  this._manifest = new Map();
2234
+ this._manifestEntries = [];
2235
+ this._manifestPath = undefined;
2079
2236
  }
2080
2237
  }
2081
2238
  /**
@@ -2087,6 +2244,99 @@ class Program {
2087
2244
  }
2088
2245
  return this._manifest;
2089
2246
  }
2247
+ /**
2248
+ * Get the manifest as a list of `{ key, value, range }` entries with line/column ranges
2249
+ * suitable for attaching diagnostics to specific manifest lines.
2250
+ *
2251
+ * NOTE: protected for now. The shape of this data is likely to evolve as we build out
2252
+ * more manifest-aware features (validation rules, autocomplete, etc.). External plugins
2253
+ * shouldn't depend on this until we commit to a stable API.
2254
+ */
2255
+ getManifestEntries() {
2256
+ if (!this._manifestEntries) {
2257
+ this.loadManifest();
2258
+ }
2259
+ return this._manifestEntries;
2260
+ }
2261
+ /**
2262
+ * Returns the absolute path of the loaded manifest file, or undefined if no manifest was found.
2263
+ *
2264
+ * NOTE: protected for now. Once brighterscript treats the manifest as a proper file
2265
+ * (with editor / BscFile integration) callers should consume that instead of poking at
2266
+ * the Program. External plugins shouldn't depend on this in the meantime.
2267
+ */
2268
+ getManifestPath() {
2269
+ if (!this._manifest) {
2270
+ this.loadManifest();
2271
+ }
2272
+ return this._manifestPath;
2273
+ }
2274
+ /**
2275
+ * The minimum Roku firmware version brighterscript should assume the user is targeting.
2276
+ * If `options.minFirmwareVersion` is set AND parseable as semver, the canonical coerced
2277
+ * form ("15.0" → "15.0.0") wins; otherwise (unset or unparseable) falls back to
2278
+ * {@link DEFAULT_MIN_FIRMWARE_VERSION}. The return is always a valid semver string so
2279
+ * downstream callers can pass it directly to `semver.gte`/`semver.lt` without re-coercing.
2280
+ * Cached after first call.
2281
+ */
2282
+ getMinFirmwareVersion() {
2283
+ if (this._minFirmwareVersion === undefined) {
2284
+ const userValue = this.options.minFirmwareVersion;
2285
+ const coerced = userValue ? semver.coerce(userValue) : undefined;
2286
+ this._minFirmwareVersion = coerced ? coerced.version : RokuConstants_1.DEFAULT_MIN_FIRMWARE_VERSION;
2287
+ }
2288
+ return this._minFirmwareVersion;
2289
+ }
2290
+ /**
2291
+ * What the project's target firmware natively understands, derived from
2292
+ * {@link getMinFirmwareVersion}. These are facts about the device, not decisions about what
2293
+ * to do — a caller finding a missing capability decides whether to transpile around it (as
2294
+ * `continue` does) or report a diagnostic (as optional chaining does).
2295
+ * Cached after first call.
2296
+ */
2297
+ get firmwareCapabilities() {
2298
+ if (this._firmwareCapabilities === undefined) {
2299
+ this._firmwareCapabilities = (0, RokuConstants_1.getFirmwareCapabilities)(this.getMinFirmwareVersion());
2300
+ }
2301
+ return this._firmwareCapabilities;
2302
+ }
2303
+ /**
2304
+ * Returns the effective `rsg_version` for this program in canonical semver form (e.g. "1.2"
2305
+ * → "1.2.0"). If the manifest declares a value explicitly and it's parseable, the canonical
2306
+ * form wins; otherwise (manifest silent OR value is malformed) we fall back to the highest
2307
+ * known rsg_version whose `becameDefaultAt` is `<=` the effective minimum firmware version,
2308
+ * driven by {@link RSG_VERSIONS}. Cached after first call.
2309
+ *
2310
+ * Manifest validation (format errors, etc.) happens in `ProgramValidator` against the raw
2311
+ * entry — that path doesn't go through this method.
2312
+ */
2313
+ getRsgVersion() {
2314
+ if (this._rsgVersion !== undefined) {
2315
+ return this._rsgVersion;
2316
+ }
2317
+ const explicit = this.getManifest().get('rsg_version');
2318
+ const explicitCoerced = explicit !== undefined ? semver.coerce(explicit.trim()) : undefined;
2319
+ if (explicitCoerced) {
2320
+ this._rsgVersion = explicitCoerced.version;
2321
+ return this._rsgVersion;
2322
+ }
2323
+ //walk known rsg_versions in descending order (newest first) and return the first whose
2324
+ //becameDefaultAt <= effective firmware. As long as some entry has becameDefaultAt: '0.0.0'
2325
+ //(currently `1.1`), this loop always finds a match.
2326
+ const minFirmwareVersion = this.getMinFirmwareVersion();
2327
+ const candidates = Object.entries(RokuConstants_1.RSG_VERSIONS)
2328
+ .filter(([, info]) => info.becameDefaultAt !== undefined)
2329
+ .sort(([a], [b]) => semver.rcompare(semver.coerce(a), semver.coerce(b)));
2330
+ for (const [version, info] of candidates) {
2331
+ if (semver.gte(minFirmwareVersion, info.becameDefaultAt)) {
2332
+ this._rsgVersion = semver.coerce(version).version;
2333
+ return this._rsgVersion;
2334
+ }
2335
+ }
2336
+ //unreachable as long as RSG_VERSIONS contains an entry with becameDefaultAt: '0.0.0'
2337
+ this._rsgVersion = RokuConstants_1.DEFAULT_MIN_FIRMWARE_VERSION;
2338
+ return this._rsgVersion;
2339
+ }
2090
2340
  dispose() {
2091
2341
  var _a, _b, _c, _d, _e, _f, _g, _h;
2092
2342
  this.plugins.emit('beforeRemoveProgram', { program: this });