brighterscript 0.66.0-alpha.7 → 0.66.0-alpha.9

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 (121) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/AstValidationSegmenter.d.ts +25 -0
  3. package/dist/AstValidationSegmenter.js +150 -0
  4. package/dist/AstValidationSegmenter.js.map +1 -0
  5. package/dist/DiagnosticMessages.d.ts +6 -1
  6. package/dist/DiagnosticMessages.js +5 -0
  7. package/dist/DiagnosticMessages.js.map +1 -1
  8. package/dist/LanguageServer.js +7 -1
  9. package/dist/LanguageServer.js.map +1 -1
  10. package/dist/Program.d.ts +24 -2
  11. package/dist/Program.js +112 -3
  12. package/dist/Program.js.map +1 -1
  13. package/dist/Scope.d.ts +15 -27
  14. package/dist/Scope.js +127 -150
  15. package/dist/Scope.js.map +1 -1
  16. package/dist/SymbolTable.d.ts +4 -1
  17. package/dist/SymbolTable.js +19 -7
  18. package/dist/SymbolTable.js.map +1 -1
  19. package/dist/XmlScope.d.ts +1 -1
  20. package/dist/XmlScope.js +5 -4
  21. package/dist/XmlScope.js.map +1 -1
  22. package/dist/astUtils/visitors.d.ts +11 -0
  23. package/dist/astUtils/visitors.js +22 -2
  24. package/dist/astUtils/visitors.js.map +1 -1
  25. package/dist/astUtils/visitors.spec.js +51 -0
  26. package/dist/astUtils/visitors.spec.js.map +1 -1
  27. package/dist/bscPlugin/completions/CompletionsProcessor.d.ts +2 -2
  28. package/dist/bscPlugin/completions/CompletionsProcessor.js +76 -31
  29. package/dist/bscPlugin/completions/CompletionsProcessor.js.map +1 -1
  30. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js +147 -3
  31. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js.map +1 -1
  32. package/dist/bscPlugin/hover/HoverProcessor.js +9 -1
  33. package/dist/bscPlugin/hover/HoverProcessor.js.map +1 -1
  34. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.js +4 -1
  35. package/dist/bscPlugin/transpile/BrsFileTranspileProcessor.js.map +1 -1
  36. package/dist/bscPlugin/validation/BrsFileValidator.js +8 -3
  37. package/dist/bscPlugin/validation/BrsFileValidator.js.map +1 -1
  38. package/dist/bscPlugin/validation/BrsFileValidator.spec.js +1 -1
  39. package/dist/bscPlugin/validation/BrsFileValidator.spec.js.map +1 -1
  40. package/dist/bscPlugin/validation/ScopeValidator.d.ts +5 -9
  41. package/dist/bscPlugin/validation/ScopeValidator.js +199 -216
  42. package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
  43. package/dist/bscPlugin/validation/ScopeValidator.spec.js +343 -0
  44. package/dist/bscPlugin/validation/ScopeValidator.spec.js.map +1 -1
  45. package/dist/files/BrsFile.Class.spec.js +201 -0
  46. package/dist/files/BrsFile.Class.spec.js.map +1 -1
  47. package/dist/files/BrsFile.d.ts +25 -2
  48. package/dist/files/BrsFile.js +210 -2
  49. package/dist/files/BrsFile.js.map +1 -1
  50. package/dist/files/BrsFile.spec.js +868 -0
  51. package/dist/files/BrsFile.spec.js.map +1 -1
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.js +2 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/interfaces.d.ts +45 -5
  56. package/dist/interfaces.js +10 -2
  57. package/dist/interfaces.js.map +1 -1
  58. package/dist/lexer/TokenKind.d.ts +1 -0
  59. package/dist/lexer/TokenKind.js +4 -0
  60. package/dist/lexer/TokenKind.js.map +1 -1
  61. package/dist/parser/Expression.d.ts +1 -1
  62. package/dist/parser/Expression.js +20 -10
  63. package/dist/parser/Expression.js.map +1 -1
  64. package/dist/parser/Parser.Class.spec.js +103 -0
  65. package/dist/parser/Parser.Class.spec.js.map +1 -1
  66. package/dist/parser/Parser.js +58 -13
  67. package/dist/parser/Parser.js.map +1 -1
  68. package/dist/parser/Parser.spec.js +212 -0
  69. package/dist/parser/Parser.spec.js.map +1 -1
  70. package/dist/parser/Statement.d.ts +9 -3
  71. package/dist/parser/Statement.js +56 -26
  72. package/dist/parser/Statement.js.map +1 -1
  73. package/dist/parser/tests/expression/NullCoalescenceExpression.spec.js +6 -6
  74. package/dist/parser/tests/expression/UnaryExpression.spec.d.ts +1 -0
  75. package/dist/parser/tests/expression/UnaryExpression.spec.js +52 -0
  76. package/dist/parser/tests/expression/UnaryExpression.spec.js.map +1 -0
  77. package/dist/parser/tests/statement/ConstStatement.spec.js +74 -0
  78. package/dist/parser/tests/statement/ConstStatement.spec.js.map +1 -1
  79. package/dist/parser/tests/statement/Enum.spec.js +9 -0
  80. package/dist/parser/tests/statement/Enum.spec.js.map +1 -1
  81. package/dist/parser/tests/statement/InterfaceStatement.spec.js +8 -0
  82. package/dist/parser/tests/statement/InterfaceStatement.spec.js.map +1 -1
  83. package/dist/roku-types/data.json +18 -51
  84. package/dist/types/BooleanType.d.ts +1 -1
  85. package/dist/types/BscType.d.ts +2 -2
  86. package/dist/types/BscType.js +28 -9
  87. package/dist/types/BscType.js.map +1 -1
  88. package/dist/types/BuiltInInterfaceAdder.js +6 -4
  89. package/dist/types/BuiltInInterfaceAdder.js.map +1 -1
  90. package/dist/types/BuiltInInterfaceAdder.spec.js +7 -0
  91. package/dist/types/BuiltInInterfaceAdder.spec.js.map +1 -1
  92. package/dist/types/ClassType.d.ts +4 -3
  93. package/dist/types/ClassType.js +6 -3
  94. package/dist/types/ClassType.js.map +1 -1
  95. package/dist/types/ClassType.spec.js +5 -3
  96. package/dist/types/ClassType.spec.js.map +1 -1
  97. package/dist/types/ComponentType.d.ts +1 -1
  98. package/dist/types/EnumType.d.ts +1 -1
  99. package/dist/types/EnumType.js +7 -2
  100. package/dist/types/EnumType.js.map +1 -1
  101. package/dist/types/InheritableType.d.ts +7 -4
  102. package/dist/types/InheritableType.js +67 -3
  103. package/dist/types/InheritableType.js.map +1 -1
  104. package/dist/types/InterfaceType.d.ts +4 -3
  105. package/dist/types/InterfaceType.js +4 -4
  106. package/dist/types/InterfaceType.js.map +1 -1
  107. package/dist/types/NamespaceType.d.ts +2 -1
  108. package/dist/types/NamespaceType.js +3 -0
  109. package/dist/types/NamespaceType.js.map +1 -1
  110. package/dist/types/ReferenceType.js +40 -6
  111. package/dist/types/ReferenceType.js.map +1 -1
  112. package/dist/types/TypedFunctionType.js +5 -5
  113. package/dist/types/TypedFunctionType.js.map +1 -1
  114. package/dist/types/UnionType.js +1 -0
  115. package/dist/types/UnionType.js.map +1 -1
  116. package/dist/types/helpers.js +4 -0
  117. package/dist/types/helpers.js.map +1 -1
  118. package/dist/util.d.ts +14 -4
  119. package/dist/util.js +86 -32
  120. package/dist/util.js.map +1 -1
  121. package/package.json +2 -3
@@ -5,13 +5,14 @@ const vscode_uri_1 = require("vscode-uri");
5
5
  const reflection_1 = require("../../astUtils/reflection");
6
6
  const Cache_1 = require("../../Cache");
7
7
  const DiagnosticMessages_1 = require("../../DiagnosticMessages");
8
+ const interfaces_1 = require("../../interfaces");
8
9
  const SymbolTable_1 = require("../../SymbolTable");
9
10
  const util_1 = require("../../util");
10
11
  const roku_types_1 = require("../../roku-types");
11
12
  const Expression_1 = require("../../parser/Expression");
12
- const Parser_1 = require("../../parser/Parser");
13
- const TokenKind_1 = require("../../lexer/TokenKind");
14
13
  const visitors_1 = require("../../astUtils/visitors");
14
+ const AstValidationSegmenter_1 = require("../../AstValidationSegmenter");
15
+ const TokenKind_1 = require("../../lexer/TokenKind");
15
16
  /**
16
17
  * The lower-case names of all platform-included scenegraph nodes
17
18
  */
@@ -25,12 +26,14 @@ const platformComponentNames = roku_types_1.components ? new Set(Object.values(r
25
26
  */
26
27
  class ScopeValidator {
27
28
  constructor() {
28
- this.expressionsByFile = new Cache_1.Cache();
29
29
  this.onceCache = new Cache_1.Cache();
30
30
  this.multiScopeCache = new Cache_1.Cache();
31
31
  }
32
32
  processEvent(event) {
33
33
  this.event = event;
34
+ if (this.event.program.globalScope === this.event.scope) {
35
+ return;
36
+ }
34
37
  this.walkFiles();
35
38
  this.detectDuplicateEnums();
36
39
  }
@@ -42,11 +45,32 @@ class ScopeValidator {
42
45
  walkFiles() {
43
46
  this.event.scope.enumerateOwnFiles((file) => {
44
47
  if ((0, reflection_1.isBrsFile)(file)) {
45
- this.iterateFileExpressions(file);
46
- this.validateCreateObjectCalls(file);
47
- file.ast.walk((0, visitors_1.createVisitor)({
48
+ const hasChangeInfo = this.event.changedFiles && this.event.changedSymbols;
49
+ let thisFileRequiresChangedSymbol = false;
50
+ for (let requiredSymbol of file.requiredSymbols) {
51
+ const changeSymbolSetForFlag = this.event.changedSymbols.get(requiredSymbol.flags);
52
+ if (util_1.default.setContainsUnresolvedSymbol(changeSymbolSetForFlag, requiredSymbol)) {
53
+ thisFileRequiresChangedSymbol = true;
54
+ }
55
+ }
56
+ const thisFileHasChanges = this.event.changedFiles.includes(file);
57
+ if (hasChangeInfo && !thisFileRequiresChangedSymbol && !thisFileHasChanges) {
58
+ // this file does not require a symbol that has changed, and this file has not changed
59
+ return;
60
+ }
61
+ if (thisFileHasChanges) {
62
+ this.event.scope.clearAstSegmentDiagnosticsByFile(file);
63
+ }
64
+ const validationVisitor = (0, visitors_1.createVisitor)({
65
+ VariableExpression: (varExpr) => {
66
+ this.validateVariableAndDottedGetExpressions(file, varExpr);
67
+ },
68
+ DottedGetExpression: (dottedGet) => {
69
+ this.validateVariableAndDottedGetExpressions(file, dottedGet);
70
+ },
48
71
  CallExpression: (functionCall) => {
49
72
  this.validateFunctionCall(file, functionCall);
73
+ this.validateCreateObjectCall(file, functionCall);
50
74
  },
51
75
  ReturnStatement: (returnStatement) => {
52
76
  this.validateReturnStatement(file, returnStatement);
@@ -60,29 +84,21 @@ class ScopeValidator {
60
84
  UnaryExpression: (unaryExpr) => {
61
85
  this.validateUnaryExpression(file, unaryExpr);
62
86
  }
63
- }), {
64
- walkMode: visitors_1.WalkMode.visitAllRecursive
65
87
  });
88
+ const segmentsToWalkForValidation = (thisFileHasChanges || !hasChangeInfo)
89
+ ? file.validationSegmenter.segmentsForValidation // validate everything in the file
90
+ : file.getValidationSegments(this.event.changedSymbols); // validate only what's needed in the file
91
+ for (const segment of segmentsToWalkForValidation) {
92
+ this.currentSegmentBeingValidated = segment;
93
+ this.event.scope.clearAstSegmentDiagnostics(segment);
94
+ segment.walk(validationVisitor, {
95
+ walkMode: AstValidationSegmenter_1.InsideSegmentWalkMode
96
+ });
97
+ file.markSegmentAsValidated(segment);
98
+ }
66
99
  }
67
100
  });
68
101
  }
69
- checkIfUsedAsTypeExpression(expression) {
70
- //TODO: this is much faster than node.findAncestor(), but may need to be updated for "complicated" type expressions
71
- if ((0, reflection_1.isTypeExpression)(expression) ||
72
- (0, reflection_1.isTypeExpression)(expression.parent) ||
73
- (0, reflection_1.isTypedArrayExpression)(expression) ||
74
- (0, reflection_1.isTypedArrayExpression)(expression.parent)) {
75
- return true;
76
- }
77
- if ((0, reflection_1.isBinaryExpression)(expression.parent)) {
78
- let currentExpr = expression.parent;
79
- while ((0, reflection_1.isBinaryExpression)(currentExpr) && currentExpr.operator.kind === TokenKind_1.TokenKind.Or) {
80
- currentExpr = currentExpr.parent;
81
- }
82
- return (0, reflection_1.isTypeExpression)(currentExpr) || (0, reflection_1.isTypedArrayExpression)(currentExpr);
83
- }
84
- return false;
85
- }
86
102
  isTypeKnown(exprType) {
87
103
  let isKnownType = exprType === null || exprType === void 0 ? void 0 : exprType.isResolvable();
88
104
  return isKnownType;
@@ -90,136 +106,21 @@ class ScopeValidator {
90
106
  /**
91
107
  * If this is the lhs of an assignment, we don't need to flag it as unresolved
92
108
  */
93
- ignoreUnresolvedAssignmentLHS(expression, exprType) {
109
+ ignoreUnresolvedAssignmentLHS(expression, exprType, definingNode) {
110
+ var _a, _b;
94
111
  if (!(0, reflection_1.isVariableExpression)(expression)) {
95
112
  return false;
96
113
  }
97
- const assignmentAncestor = expression === null || expression === void 0 ? void 0 : expression.findAncestor(reflection_1.isAssignmentStatement);
98
- return (assignmentAncestor === null || assignmentAncestor === void 0 ? void 0 : assignmentAncestor.name) === (expression === null || expression === void 0 ? void 0 : expression.name) && (0, reflection_1.isUnionType)(exprType); // the left hand side is not a union, which means it was never assigned
99
- }
100
- iterateFileExpressions(file) {
101
- var _a, _b, _c;
102
- const { scope } = this.event;
103
- //build an expression collection ONCE per file
104
- const expressionInfos = this.expressionsByFile.getOrAdd(file, () => {
105
- var _a, _b;
106
- const result = [];
107
- const expressions = [...file.parser.references.expressions];
108
- for (let expression of expressions) {
109
- if (!expression) {
110
- continue;
111
- }
112
- //walk left-to-right on every expression, only keep the ones that start with VariableExpression, and then keep subsequent DottedGet parts
113
- const parts = util_1.default.getDottedGetPath(expression);
114
- if (parts.length > 0) {
115
- result.push({
116
- parts: parts,
117
- expression: expression,
118
- isUsedAsType: this.checkIfUsedAsTypeExpression(expression),
119
- enclosingNamespaceNameLower: (_b = (_a = expression.findAncestor(reflection_1.isNamespaceStatement)) === null || _a === void 0 ? void 0 : _a.getName(Parser_1.ParseMode.BrighterScript)) === null || _b === void 0 ? void 0 : _b.toLowerCase()
120
- });
121
- }
122
- }
123
- return result;
124
- });
125
- outer: for (const info of expressionInfos) {
126
- const firstNamespacePart = info.parts[0].name.text;
127
- const firstNamespacePartLower = firstNamespacePart === null || firstNamespacePart === void 0 ? void 0 : firstNamespacePart.toLowerCase();
128
- //get the namespace container (accounting for namespace-relative as well)
129
- const namespaceContainer = scope.getNamespace(firstNamespacePartLower, info.enclosingNamespaceNameLower);
130
- let symbolType = SymbolTable_1.SymbolTypeFlag.runtime;
131
- let oppositeSymbolType = SymbolTable_1.SymbolTypeFlag.typetime;
132
- if (info.isUsedAsType) {
133
- // This is used in a TypeExpression - only look up types from SymbolTable
134
- symbolType = SymbolTable_1.SymbolTypeFlag.typetime;
135
- oppositeSymbolType = SymbolTable_1.SymbolTypeFlag.runtime;
136
- }
137
- // Do a complete type check on all DottedGet and Variable expressions
138
- // this will create a diagnostic if an invalid member is accessed
139
- const typeChain = [];
140
- let exprType = info.expression.getType({
141
- flags: symbolType,
142
- typeChain: typeChain
143
- });
144
- if (!this.isTypeKnown(exprType) && !this.ignoreUnresolvedAssignmentLHS(info.expression, exprType)) {
145
- if ((_a = info.expression.getType({ flags: oppositeSymbolType })) === null || _a === void 0 ? void 0 : _a.isResolvable()) {
146
- const oppoSiteTypeChain = [];
147
- const invalidlyUsedResolvedType = info.expression.getType({ flags: oppositeSymbolType, typeChain: oppoSiteTypeChain });
148
- const typeChainScan = util_1.default.processTypeChain(oppoSiteTypeChain);
149
- if (info.isUsedAsType) {
150
- this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.itemCannotBeUsedAsType(typeChainScan.fullChainName)), { range: info.expression.range, file: file }));
151
- }
152
- else {
153
- this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.itemCannotBeUsedAsVariable(invalidlyUsedResolvedType.toString())), { range: info.expression.range, file: file }));
154
- }
155
- continue;
156
- }
157
- const typeChainScan = util_1.default.processTypeChain(typeChain);
158
- this.addMultiScopeDiagnostic(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.cannotFindName(typeChainScan.itemName, typeChainScan.fullNameOfItem)), { range: typeChainScan.range }));
159
- //skip to the next expression
160
- continue;
161
- }
162
- const enumStatement = scope.getEnum(firstNamespacePartLower, info.enclosingNamespaceNameLower);
163
- //if this isn't a namespace, skip it
164
- if (!namespaceContainer && !enumStatement) {
165
- continue;
166
- }
167
- //catch unknown namespace items
168
- let entityName = firstNamespacePart;
169
- let entityNameLower = firstNamespacePart.toLowerCase();
170
- for (let i = 1; i < info.parts.length; i++) {
171
- const part = info.parts[i];
172
- entityName += '.' + part.name.text;
173
- entityNameLower += '.' + part.name.text.toLowerCase();
174
- //if this is an enum member, stop validating here to prevent errors further down the chain
175
- if (scope.getEnumMemberFileLink(entityName, info.enclosingNamespaceNameLower)) {
176
- break;
177
- }
178
- if (!scope.getEnumMap().has(entityNameLower) &&
179
- !scope.getClassMap().has(entityNameLower) &&
180
- !scope.getInterfaceMap().has(entityNameLower) &&
181
- !scope.getConstMap().has(entityNameLower) &&
182
- !scope.getCallableByName(entityNameLower) &&
183
- !scope.getNamespace(entityNameLower, info.enclosingNamespaceNameLower)) {
184
- //if this looks like an enum, provide a nicer error message
185
- const theEnum = (_b = this.getEnum(scope, entityNameLower)) === null || _b === void 0 ? void 0 : _b.item;
186
- if (theEnum) {
187
- this.addMultiScopeDiagnostic(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.unknownEnumValue((_c = part.name.text) === null || _c === void 0 ? void 0 : _c.split('.').pop(), theEnum.fullName)), { range: part.name.range, relatedInformation: [{
188
- message: 'Enum declared here',
189
- location: util_1.default.createLocation(vscode_uri_1.URI.file(file.srcPath).toString(), theEnum.tokens.name.range)
190
- }] }));
191
- }
192
- else {
193
- this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.cannotFindName(part.name.text, entityName)), { range: part.name.range, file: file }));
194
- }
195
- //no need to add another diagnostic for future unknown items
196
- continue outer;
197
- }
198
- }
199
- //if the full expression is just an enum name, this is an illegal statement because enums don't exist at runtime
200
- if (!info.isUsedAsType && enumStatement && info.parts.length === 1) {
201
- this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.itemCannotBeUsedAsVariable('enum')), { range: info.expression.range, file: file }));
202
- }
203
- //if the full expression is a namespace path, this is an illegal statement because namespaces don't exist at runtme
204
- if (scope.getNamespace(entityNameLower, info.enclosingNamespaceNameLower)) {
205
- this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.itemCannotBeUsedAsVariable('namespace')), { range: info.expression.range, file: file }));
206
- }
114
+ let assignmentAncestor;
115
+ if ((0, reflection_1.isAssignmentStatement)(definingNode) && definingNode.equals.kind === TokenKind_1.TokenKind.Equal) {
116
+ // this symbol was defined in a "normal" assignment (eg. not a compound assignment)
117
+ assignmentAncestor = definingNode;
118
+ return ((_a = assignmentAncestor === null || assignmentAncestor === void 0 ? void 0 : assignmentAncestor.name) === null || _a === void 0 ? void 0 : _a.text.toLowerCase()) === ((_b = expression === null || expression === void 0 ? void 0 : expression.name) === null || _b === void 0 ? void 0 : _b.text.toLowerCase());
207
119
  }
208
- }
209
- /**
210
- * Given a string optionally separated by dots, find an enum related to it.
211
- * For example, all of these would return the enum: `SomeNamespace.SomeEnum.SomeMember`, SomeEnum.SomeMember, `SomeEnum`
212
- */
213
- getEnum(scope, name) {
214
- //look for the enum directly
215
- let result = scope.getEnumMap().get(name);
216
- //assume we've been given the enum.member syntax, so pop the member and try again
217
- if (!result) {
218
- const parts = name.split('.');
219
- parts.pop();
220
- result = scope.getEnumMap().get(parts.join('.'));
221
- }
222
- return result;
120
+ else {
121
+ assignmentAncestor = expression === null || expression === void 0 ? void 0 : expression.findAncestor(reflection_1.isAssignmentStatement);
122
+ }
123
+ return (assignmentAncestor === null || assignmentAncestor === void 0 ? void 0 : assignmentAncestor.name) === (expression === null || expression === void 0 ? void 0 : expression.name) && (0, reflection_1.isUnionType)(exprType);
223
124
  }
224
125
  /**
225
126
  * Flag duplicate enums
@@ -260,7 +161,7 @@ class ScopeValidator {
260
161
  diagnostics.push(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.duplicateEnumDeclaration(this.event.scope.name, fullName)), { file: duplicateEnumInfo.file, range: duplicateEnumInfo.statement.tokens.name.range, relatedInformation: [{
261
162
  message: 'Enum declared here',
262
163
  location: util_1.default.createLocation(vscode_uri_1.URI.file(primaryEnum.file.srcPath).toString(), primaryEnum.statement.tokens.name.range)
263
- }] }));
164
+ }], origin: interfaces_1.DiagnosticOrigin.Scope }));
264
165
  }
265
166
  }
266
167
  this.event.scope.addDiagnostics(diagnostics);
@@ -271,64 +172,60 @@ class ScopeValidator {
271
172
  * what these calls are supposed to look like, and this is a very common thing for brs devs to do, so just
272
173
  * do this manually for now.
273
174
  */
274
- validateCreateObjectCalls(file) {
275
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
276
- const diagnostics = [];
277
- for (const call of file.functionCalls) {
278
- //skip non CreateObject function calls
279
- if (((_a = call.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== 'createobject' || !(0, reflection_1.isLiteralExpression)((_b = call === null || call === void 0 ? void 0 : call.args[0]) === null || _b === void 0 ? void 0 : _b.expression)) {
280
- continue;
175
+ validateCreateObjectCall(file, call) {
176
+ var _a, _b, _c, _d, _e, _f;
177
+ //skip non CreateObject function calls
178
+ const callName = (_a = util_1.default.getAllDottedGetPartsAsString(call.callee)) === null || _a === void 0 ? void 0 : _a.toLowerCase();
179
+ if (callName !== 'createobject' || !(0, reflection_1.isLiteralExpression)(call === null || call === void 0 ? void 0 : call.args[0])) {
180
+ return;
181
+ }
182
+ const firstParamToken = (_b = call === null || call === void 0 ? void 0 : call.args[0]) === null || _b === void 0 ? void 0 : _b.token;
183
+ const firstParamStringValue = (_c = firstParamToken === null || firstParamToken === void 0 ? void 0 : firstParamToken.text) === null || _c === void 0 ? void 0 : _c.replace(/"/g, '');
184
+ //if this is a `createObject('roSGNode'` call, only support known sg node types
185
+ if ((firstParamStringValue === null || firstParamStringValue === void 0 ? void 0 : firstParamStringValue.toLowerCase()) === 'rosgnode' && (0, reflection_1.isLiteralExpression)(call === null || call === void 0 ? void 0 : call.args[1])) {
186
+ const componentName = (_d = call === null || call === void 0 ? void 0 : call.args[1]) === null || _d === void 0 ? void 0 : _d.token;
187
+ //don't validate any components with a colon in their name (probably component libraries, but regular components can have them too).
188
+ if ((_e = componentName === null || componentName === void 0 ? void 0 : componentName.text) === null || _e === void 0 ? void 0 : _e.includes(':')) {
189
+ return;
281
190
  }
282
- const firstParamToken = (_d = (_c = call === null || call === void 0 ? void 0 : call.args[0]) === null || _c === void 0 ? void 0 : _c.expression) === null || _d === void 0 ? void 0 : _d.token;
283
- const firstParamStringValue = (_e = firstParamToken === null || firstParamToken === void 0 ? void 0 : firstParamToken.text) === null || _e === void 0 ? void 0 : _e.replace(/"/g, '');
284
- //if this is a `createObject('roSGNode'` call, only support known sg node types
285
- if ((firstParamStringValue === null || firstParamStringValue === void 0 ? void 0 : firstParamStringValue.toLowerCase()) === 'rosgnode' && (0, reflection_1.isLiteralExpression)((_f = call === null || call === void 0 ? void 0 : call.args[1]) === null || _f === void 0 ? void 0 : _f.expression)) {
286
- const componentName = (_h = (_g = call === null || call === void 0 ? void 0 : call.args[1]) === null || _g === void 0 ? void 0 : _g.expression) === null || _h === void 0 ? void 0 : _h.token;
287
- //don't validate any components with a colon in their name (probably component libraries, but regular components can have them too).
288
- if ((_j = componentName === null || componentName === void 0 ? void 0 : componentName.text) === null || _j === void 0 ? void 0 : _j.includes(':')) {
289
- continue;
290
- }
291
- //add diagnostic for unknown components
292
- const unquotedComponentName = (_k = componentName === null || componentName === void 0 ? void 0 : componentName.text) === null || _k === void 0 ? void 0 : _k.replace(/"/g, '');
293
- if (unquotedComponentName && !platformNodeNames.has(unquotedComponentName.toLowerCase()) && !this.event.program.getComponent(unquotedComponentName)) {
294
- this.addDiagnosticOnce(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.unknownRoSGNode(unquotedComponentName)), { range: componentName.range }));
295
- }
296
- else if ((call === null || call === void 0 ? void 0 : call.args.length) !== 2) {
297
- // roSgNode should only ever have 2 args in `createObject`
298
- this.addDiagnosticOnce(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.mismatchCreateObjectArgumentCount(firstParamStringValue, [2], call === null || call === void 0 ? void 0 : call.args.length)), { range: call.range }));
299
- }
191
+ //add diagnostic for unknown components
192
+ const unquotedComponentName = (_f = componentName === null || componentName === void 0 ? void 0 : componentName.text) === null || _f === void 0 ? void 0 : _f.replace(/"/g, '');
193
+ if (unquotedComponentName && !platformNodeNames.has(unquotedComponentName.toLowerCase()) && !this.event.program.getComponent(unquotedComponentName)) {
194
+ this.addDiagnosticOnce(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.unknownRoSGNode(unquotedComponentName)), { range: componentName.range }));
300
195
  }
301
- else if (!platformComponentNames.has(firstParamStringValue.toLowerCase())) {
302
- this.addDiagnosticOnce(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.unknownBrightScriptComponent(firstParamStringValue)), { range: firstParamToken.range }));
196
+ else if ((call === null || call === void 0 ? void 0 : call.args.length) !== 2) {
197
+ // roSgNode should only ever have 2 args in `createObject`
198
+ this.addDiagnosticOnce(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.mismatchCreateObjectArgumentCount(firstParamStringValue, [2], call === null || call === void 0 ? void 0 : call.args.length)), { range: call.range }));
303
199
  }
304
- else {
305
- // This is valid brightscript component
306
- // Test for invalid arg counts
307
- const brightScriptComponent = roku_types_1.components[firstParamStringValue.toLowerCase()];
308
- // Valid arg counts for createObject are 1+ number of args for constructor
309
- let validArgCounts = brightScriptComponent.constructors.map(cnstr => cnstr.params.length + 1);
310
- if (validArgCounts.length === 0) {
311
- // no constructors for this component, so createObject only takes 1 arg
312
- validArgCounts = [1];
313
- }
314
- if (!validArgCounts.includes(call === null || call === void 0 ? void 0 : call.args.length)) {
315
- // Incorrect number of arguments included in `createObject()`
316
- this.addDiagnosticOnce(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.mismatchCreateObjectArgumentCount(firstParamStringValue, validArgCounts, call === null || call === void 0 ? void 0 : call.args.length)), { range: call.range }));
317
- }
318
- // Test for deprecation
319
- if (brightScriptComponent.isDeprecated) {
320
- this.addDiagnosticOnce(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.deprecatedBrightScriptComponent(firstParamStringValue, brightScriptComponent.deprecatedDescription)), { range: call.range }));
321
- }
200
+ }
201
+ else if (!platformComponentNames.has(firstParamStringValue.toLowerCase())) {
202
+ this.addDiagnosticOnce(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.unknownBrightScriptComponent(firstParamStringValue)), { range: firstParamToken.range }));
203
+ }
204
+ else {
205
+ // This is valid brightscript component
206
+ // Test for invalid arg counts
207
+ const brightScriptComponent = roku_types_1.components[firstParamStringValue.toLowerCase()];
208
+ // Valid arg counts for createObject are 1+ number of args for constructor
209
+ let validArgCounts = brightScriptComponent.constructors.map(cnstr => cnstr.params.length + 1);
210
+ if (validArgCounts.length === 0) {
211
+ // no constructors for this component, so createObject only takes 1 arg
212
+ validArgCounts = [1];
213
+ }
214
+ if (!validArgCounts.includes(call === null || call === void 0 ? void 0 : call.args.length)) {
215
+ // Incorrect number of arguments included in `createObject()`
216
+ this.addDiagnosticOnce(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.mismatchCreateObjectArgumentCount(firstParamStringValue, validArgCounts, call === null || call === void 0 ? void 0 : call.args.length)), { range: call.range }));
217
+ }
218
+ // Test for deprecation
219
+ if (brightScriptComponent.isDeprecated) {
220
+ this.addDiagnosticOnce(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.deprecatedBrightScriptComponent(firstParamStringValue, brightScriptComponent.deprecatedDescription)), { range: call.range }));
322
221
  }
323
222
  }
324
- this.event.scope.addDiagnostics(diagnostics);
325
223
  }
326
224
  /**
327
225
  * Detect calls to functions with the incorrect number of parameters, or wrong types of arguments
328
226
  */
329
227
  validateFunctionCall(file, expression) {
330
228
  var _a, _b;
331
- const diagnostics = [];
332
229
  const getTypeOptions = { flags: SymbolTable_1.SymbolTypeFlag.runtime };
333
230
  let funcType = (_a = expression === null || expression === void 0 ? void 0 : expression.callee) === null || _a === void 0 ? void 0 : _a.getType(getTypeOptions);
334
231
  if ((funcType === null || funcType === void 0 ? void 0 : funcType.isResolvable()) && (0, reflection_1.isClassType)(funcType)) {
@@ -355,7 +252,7 @@ class ScopeValidator {
355
252
  let expCallArgCount = expression.args.length;
356
253
  if (expCallArgCount > maxParams || expCallArgCount < minParams) {
357
254
  let minMaxParamsText = minParams === maxParams ? maxParams : `${minParams}-${maxParams}`;
358
- diagnostics.push(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.mismatchArgumentCount(minMaxParamsText, expCallArgCount)), { range: expression.callee.range,
255
+ this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.mismatchArgumentCount(minMaxParamsText, expCallArgCount)), { range: expression.callee.range,
359
256
  //TODO detect end of expression call
360
257
  file: file }));
361
258
  }
@@ -376,14 +273,12 @@ class ScopeValidator {
376
273
  paramIndex++;
377
274
  }
378
275
  }
379
- this.event.scope.addDiagnostics(diagnostics);
380
276
  }
381
277
  /**
382
278
  * Detect return statements with incompatible types vs. declared return type
383
279
  */
384
280
  validateReturnStatement(file, returnStmt) {
385
281
  var _a;
386
- const diagnostics = [];
387
282
  const getTypeOptions = { flags: SymbolTable_1.SymbolTypeFlag.runtime };
388
283
  let funcType = returnStmt.findAncestor(reflection_1.isFunctionExpression).getType({ flags: SymbolTable_1.SymbolTypeFlag.typetime });
389
284
  if ((0, reflection_1.isTypedFunctionType)(funcType)) {
@@ -393,14 +288,12 @@ class ScopeValidator {
393
288
  this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.returnTypeMismatch(actualReturnType.toString(), funcType.returnType.toString(), compatibilityData)), { range: returnStmt.value.range, file: file }));
394
289
  }
395
290
  }
396
- this.event.scope.addDiagnostics(diagnostics);
397
291
  }
398
292
  /**
399
293
  * Detect return statements with incompatible types vs. declared return type
400
294
  */
401
295
  validateDottedSetStatement(file, dottedSetStmt) {
402
296
  var _a, _b, _c;
403
- const diagnostics = [];
404
297
  const getTypeOpts = { flags: SymbolTable_1.SymbolTypeFlag.runtime };
405
298
  const expectedLHSType = (_b = (_a = dottedSetStmt === null || dottedSetStmt === void 0 ? void 0 : dottedSetStmt.obj) === null || _a === void 0 ? void 0 : _a.getType(getTypeOpts)) === null || _b === void 0 ? void 0 : _b.getMemberType(dottedSetStmt.name.text, getTypeOpts);
406
299
  const actualRHSType = (_c = dottedSetStmt === null || dottedSetStmt === void 0 ? void 0 : dottedSetStmt.value) === null || _c === void 0 ? void 0 : _c.getType(getTypeOpts);
@@ -408,15 +301,13 @@ class ScopeValidator {
408
301
  if ((expectedLHSType === null || expectedLHSType === void 0 ? void 0 : expectedLHSType.isResolvable()) && !(expectedLHSType === null || expectedLHSType === void 0 ? void 0 : expectedLHSType.isTypeCompatible(actualRHSType, compatibilityData))) {
409
302
  this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.assignmentTypeMismatch(actualRHSType.toString(), expectedLHSType.toString(), compatibilityData)), { range: dottedSetStmt.range, file: file }));
410
303
  }
411
- this.event.scope.addDiagnostics(diagnostics);
412
304
  }
413
305
  /**
414
306
  * Detect invalid use of a binary operator
415
307
  */
416
308
  validateBinaryExpression(file, binaryExpr) {
417
- const diagnostics = [];
418
309
  const getTypeOpts = { flags: SymbolTable_1.SymbolTypeFlag.runtime };
419
- if (this.checkIfUsedAsTypeExpression(binaryExpr)) {
310
+ if (util_1.default.isInTypeExpression(binaryExpr)) {
420
311
  return;
421
312
  }
422
313
  let leftType = binaryExpr.left.getType(getTypeOpts);
@@ -455,13 +346,11 @@ class ScopeValidator {
455
346
  // if the result was dynamic, that means there wasn't a valid operation
456
347
  this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.operatorTypeMismatch(binaryExpr.operator.text, leftType.toString(), rightType.toString())), { range: binaryExpr.range, file: file }));
457
348
  }
458
- this.event.scope.addDiagnostics(diagnostics);
459
349
  }
460
350
  /**
461
351
  * Detect invalid use of a Unary operator
462
352
  */
463
353
  validateUnaryExpression(file, unaryExpr) {
464
- const diagnostics = [];
465
354
  const getTypeOpts = { flags: SymbolTable_1.SymbolTypeFlag.runtime };
466
355
  let rightType = unaryExpr.right.getType(getTypeOpts);
467
356
  if (!rightType.isResolvable()) {
@@ -475,11 +364,9 @@ class ScopeValidator {
475
364
  if ((0, reflection_1.isUnionType)(rightTypeToTest)) {
476
365
  // TODO: it is possible to validate based on innerTypes, but more complicated
477
366
  // Because you need to verify each combination of types
478
- return;
479
367
  }
480
368
  else if ((0, reflection_1.isDynamicType)(rightTypeToTest) || (0, reflection_1.isObjectType)(rightTypeToTest)) {
481
369
  // operand is basically "any" type... ignore;
482
- return;
483
370
  }
484
371
  else if ((0, reflection_1.isPrimitiveType)(rightType)) {
485
372
  const opResult = util_1.default.unaryOperatorResultType(unaryExpr.operator, rightTypeToTest);
@@ -491,7 +378,82 @@ class ScopeValidator {
491
378
  // rhs is not a primitive, so no binary operator is allowed
492
379
  this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.operatorTypeMismatch(unaryExpr.operator.text, rightType.toString())), { range: unaryExpr.range, file: file }));
493
380
  }
494
- this.event.scope.addDiagnostics(diagnostics);
381
+ }
382
+ validateVariableAndDottedGetExpressions(file, expression) {
383
+ var _a, _b;
384
+ if ((0, reflection_1.isDottedGetExpression)(expression.parent)) {
385
+ // We validate dottedGetExpressions at the top-most level
386
+ return;
387
+ }
388
+ if ((0, reflection_1.isVariableExpression)(expression)) {
389
+ if ((0, reflection_1.isAssignmentStatement)(expression.parent) && expression.parent.name === expression.name) {
390
+ // Don't validate LHS of assignments
391
+ return;
392
+ }
393
+ else if ((0, reflection_1.isNamespaceStatement)(expression.parent)) {
394
+ return;
395
+ }
396
+ }
397
+ let symbolType = SymbolTable_1.SymbolTypeFlag.runtime;
398
+ let oppositeSymbolType = SymbolTable_1.SymbolTypeFlag.typetime;
399
+ const isUsedAsType = util_1.default.isInTypeExpression(expression);
400
+ if (isUsedAsType) {
401
+ // This is used in a TypeExpression - only look up types from SymbolTable
402
+ symbolType = SymbolTable_1.SymbolTypeFlag.typetime;
403
+ oppositeSymbolType = SymbolTable_1.SymbolTypeFlag.runtime;
404
+ }
405
+ // Do a complete type check on all DottedGet and Variable expressions
406
+ // this will create a diagnostic if an invalid member is accessed
407
+ const typeChain = [];
408
+ const typeData = {};
409
+ let exprType = expression.getType({
410
+ flags: symbolType,
411
+ typeChain: typeChain,
412
+ data: typeData
413
+ });
414
+ const shouldIgnoreLHS = this.ignoreUnresolvedAssignmentLHS(expression, exprType, typeData === null || typeData === void 0 ? void 0 : typeData.definingNode);
415
+ if (!this.isTypeKnown(exprType) && !shouldIgnoreLHS) {
416
+ if ((_a = expression.getType({ flags: oppositeSymbolType })) === null || _a === void 0 ? void 0 : _a.isResolvable()) {
417
+ const oppoSiteTypeChain = [];
418
+ const invalidlyUsedResolvedType = expression.getType({ flags: oppositeSymbolType, typeChain: oppoSiteTypeChain });
419
+ const typeChainScan = util_1.default.processTypeChain(oppoSiteTypeChain);
420
+ if (isUsedAsType) {
421
+ this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.itemCannotBeUsedAsType(typeChainScan.fullChainName)), { range: expression.range, file: file }));
422
+ }
423
+ else {
424
+ this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.itemCannotBeUsedAsVariable(invalidlyUsedResolvedType.toString())), { range: expression.range, file: file }));
425
+ }
426
+ }
427
+ else {
428
+ const typeChainScan = util_1.default.processTypeChain(typeChain);
429
+ this.addMultiScopeDiagnostic(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.cannotFindName(typeChainScan.itemName, typeChainScan.fullNameOfItem)), { range: typeChainScan.range }));
430
+ }
431
+ }
432
+ if (isUsedAsType) {
433
+ return;
434
+ }
435
+ const lastTypeInfo = typeChain[typeChain.length - 1];
436
+ const parentTypeInfo = typeChain[typeChain.length - 2];
437
+ if ((0, reflection_1.isNamespaceType)(exprType)) {
438
+ this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.itemCannotBeUsedAsVariable('namespace')), { range: expression.range, file: file }));
439
+ }
440
+ else if ((0, reflection_1.isEnumType)(exprType)) {
441
+ const enumStatement = this.event.scope.getEnum(util_1.default.getAllDottedGetPartsAsString(expression));
442
+ if (enumStatement) {
443
+ // there's an enum with this name
444
+ this.addMultiScopeDiagnostic(Object.assign(Object.assign({}, DiagnosticMessages_1.DiagnosticMessages.itemCannotBeUsedAsVariable('enum')), { range: expression.range, file: file }));
445
+ }
446
+ }
447
+ else if ((0, reflection_1.isDynamicType)(exprType) && (0, reflection_1.isEnumType)(parentTypeInfo === null || parentTypeInfo === void 0 ? void 0 : parentTypeInfo.type) && (0, reflection_1.isDottedGetExpression)(expression)) {
448
+ const enumFileLink = this.event.scope.getEnumFileLink(util_1.default.getAllDottedGetPartsAsString(expression.obj));
449
+ const typeChainScanForParent = util_1.default.processTypeChain(typeChain.slice(0, -1));
450
+ if (enumFileLink) {
451
+ this.addMultiScopeDiagnostic(Object.assign(Object.assign({ file: file }, DiagnosticMessages_1.DiagnosticMessages.unknownEnumValue(lastTypeInfo === null || lastTypeInfo === void 0 ? void 0 : lastTypeInfo.name, typeChainScanForParent.fullChainName)), { range: lastTypeInfo === null || lastTypeInfo === void 0 ? void 0 : lastTypeInfo.range, relatedInformation: [{
452
+ message: 'Enum declared here',
453
+ location: util_1.default.createLocation(vscode_uri_1.URI.file(enumFileLink === null || enumFileLink === void 0 ? void 0 : enumFileLink.file.srcPath).toString(), (_b = enumFileLink === null || enumFileLink === void 0 ? void 0 : enumFileLink.item) === null || _b === void 0 ? void 0 : _b.tokens.name.range)
454
+ }] }));
455
+ }
456
+ }
495
457
  }
496
458
  /**
497
459
  * Adds a diagnostic to the first scope for this key. Prevents duplicate diagnostics
@@ -499,12 +461,26 @@ class ScopeValidator {
499
461
  */
500
462
  addDiagnosticOnce(diagnostic) {
501
463
  this.onceCache.getOrAdd(`${diagnostic.code}-${diagnostic.message}-${util_1.default.rangeToString(diagnostic.range)}`, () => {
502
- this.event.scope.addDiagnostics([diagnostic]);
464
+ const diagnosticWithOrigin = Object.assign({}, diagnostic);
465
+ if (!diagnosticWithOrigin.origin) {
466
+ // diagnostic does not have origin.
467
+ // set the origin to the current astSegment
468
+ diagnosticWithOrigin.origin = interfaces_1.DiagnosticOrigin.ASTSegment;
469
+ diagnosticWithOrigin.astSegment = this.currentSegmentBeingValidated;
470
+ }
471
+ this.event.scope.addDiagnostics([diagnosticWithOrigin]);
503
472
  return true;
504
473
  });
505
474
  }
506
475
  addDiagnostic(diagnostic) {
507
- this.event.scope.addDiagnostics([diagnostic]);
476
+ const diagnosticWithOrigin = Object.assign({}, diagnostic);
477
+ if (!diagnosticWithOrigin.origin) {
478
+ // diagnostic does not have origin.
479
+ // set the origin to the current astSegment
480
+ diagnosticWithOrigin.origin = interfaces_1.DiagnosticOrigin.ASTSegment;
481
+ diagnosticWithOrigin.astSegment = this.currentSegmentBeingValidated;
482
+ }
483
+ this.event.scope.addDiagnostics([diagnosticWithOrigin]);
508
484
  }
509
485
  /**
510
486
  * Add a diagnostic (to the first scope) that will have `relatedInformation` for each affected scope
@@ -515,8 +491,15 @@ class ScopeValidator {
515
491
  if (!diagnostic.relatedInformation) {
516
492
  diagnostic.relatedInformation = [];
517
493
  }
518
- this.addDiagnostic(diagnostic);
519
- return diagnostic;
494
+ const diagnosticWithOrigin = Object.assign({}, diagnostic);
495
+ if (!diagnosticWithOrigin.origin) {
496
+ // diagnostic does not have origin.
497
+ // set the origin to the current astSegment
498
+ diagnosticWithOrigin.origin = interfaces_1.DiagnosticOrigin.ASTSegment;
499
+ diagnosticWithOrigin.astSegment = this.currentSegmentBeingValidated;
500
+ }
501
+ this.addDiagnostic(diagnosticWithOrigin);
502
+ return diagnosticWithOrigin;
520
503
  });
521
504
  if ((0, reflection_1.isXmlScope)(this.event.scope) && ((_b = this.event.scope.xmlFile) === null || _b === void 0 ? void 0 : _b.srcPath)) {
522
505
  diagnostic.relatedInformation.push({