|
1 |
| -import { BscFile, FunctionExpression, BsDiagnostic, Range, isForStatement, isForEachStatement, isIfStatement, isAssignmentStatement, isNamespaceStatement, NamespaceStatement, Expression, isVariableExpression, isBinaryExpression, TokenKind, Scope, CallableContainerMap, DiagnosticSeverity, isLiteralInvalid, isWhileStatement, isClassMethodStatement, isBrsFile, isCatchStatement, isLabelStatement, isGotoStatement, NamespacedVariableNameExpression, ParseMode } from 'brighterscript'; |
| 1 | +import { BscFile, FunctionExpression, BsDiagnostic, Range, isForStatement, isForEachStatement, isIfStatement, isAssignmentStatement, isNamespaceStatement, NamespaceStatement, Expression, isVariableExpression, isBinaryExpression, TokenKind, Scope, CallableContainerMap, DiagnosticSeverity, isLiteralInvalid, isWhileStatement, isCatchStatement, isLabelStatement, isGotoStatement, NamespacedVariableNameExpression, ParseMode, util, isMethodStatement } from 'brighterscript'; |
2 | 2 | import { LintState, StatementInfo, NarrowingInfo, VarInfo, VarRestriction } from '.';
|
3 | 3 | import { PluginContext } from '../../util';
|
4 | 4 |
|
@@ -52,7 +52,7 @@ export function createVarLinter(
|
52 | 52 | args.set(name.toLowerCase(), { name: name, range: p.name.range, isParam: true, isUnsafe: false, isUsed: false });
|
53 | 53 | });
|
54 | 54 |
|
55 |
| - if (isClassMethodStatement(fun.functionStatement)) { |
| 55 | + if (isMethodStatement(fun.functionStatement)) { |
56 | 56 | args.set('super', { name: 'super', range: null, isParam: true, isUnsafe: false, isUsed: true });
|
57 | 57 | }
|
58 | 58 |
|
@@ -379,47 +379,48 @@ export function runDeferredValidation(
|
379 | 379 | files: BscFile[],
|
380 | 380 | callables: CallableContainerMap
|
381 | 381 | ) {
|
382 |
| - const globals = lintContext.globals; |
383 |
| - |
| 382 | + const topLevelVars = buildTopLevelVars(scope, lintContext.globals); |
384 | 383 | const diagnostics: BsDiagnostic[] = [];
|
385 | 384 | files.forEach((file) => {
|
386 | 385 | const deferred = deferredValidation.get(file.pathAbsolute);
|
387 | 386 | if (deferred) {
|
388 |
| - deferredVarLinter(scope, file, callables, globals, deferred, diagnostics); |
| 387 | + deferredVarLinter(scope, file, callables, topLevelVars, deferred, diagnostics); |
389 | 388 | }
|
390 | 389 | });
|
391 | 390 | return diagnostics;
|
392 | 391 | }
|
393 | 392 |
|
| 393 | +/** |
| 394 | + * Get a list of all top level variables available in the scope |
| 395 | + */ |
| 396 | +function buildTopLevelVars(scope: Scope, globals: string[]) { |
| 397 | + // lookups for namespaces, classes, enums, etc... |
| 398 | + // to add them to the topLevel so that they don't get marked as unused. |
| 399 | + const toplevel = new Set<string>(globals); |
| 400 | + |
| 401 | + for (const namespace of scope.getAllNamespaceStatements()) { |
| 402 | + toplevel.add(getRootNamespaceName(namespace).toLowerCase()); // keep root of namespace |
| 403 | + } |
| 404 | + for (const [, cls] of scope.getClassMap()) { |
| 405 | + toplevel.add(cls.item.name.text.toLowerCase()); |
| 406 | + } |
| 407 | + for (const [, enm] of scope.getEnumMap()) { |
| 408 | + toplevel.add(enm.item.name.toLowerCase()); |
| 409 | + } |
| 410 | + for (const [, cnst] of scope.getConstMap()) { |
| 411 | + toplevel.add(cnst.item.name.toLowerCase()); |
| 412 | + } |
| 413 | + return toplevel; |
| 414 | +} |
| 415 | + |
394 | 416 | function deferredVarLinter(
|
395 | 417 | scope: Scope,
|
396 | 418 | file: BscFile,
|
397 | 419 | callables: CallableContainerMap,
|
398 |
| - globals: string[], |
| 420 | + toplevel: Set<string>, |
399 | 421 | deferred: ValidationInfo[],
|
400 | 422 | diagnostics: BsDiagnostic[]
|
401 | 423 | ) {
|
402 |
| - // lookups for namespaces, classes, and enums |
403 |
| - // to add them to the topLevel so that they don't get marked as unused. |
404 |
| - const toplevel = new Set<string>(globals); |
405 |
| - scope.getAllNamespaceStatements().forEach(ns => { |
406 |
| - toplevel.add(ns.name.toLowerCase().split('.')[0]); // keep root of namespace |
407 |
| - }); |
408 |
| - scope.getClassMap().forEach(cls => { |
409 |
| - toplevel.add(cls.item.name.text.toLowerCase()); |
410 |
| - }); |
411 |
| - scope.getEnumMap().forEach(enm => { |
412 |
| - toplevel.add(enm.item.name.toLowerCase()); |
413 |
| - }); |
414 |
| - scope.getConstMap().forEach(cnt => { |
415 |
| - toplevel.add(cnt.item.name.toLowerCase()); |
416 |
| - }); |
417 |
| - if (isBrsFile(file)) { |
418 |
| - file.parser.references.classStatements.forEach(cls => { |
419 |
| - toplevel.add(cls.name.text.toLowerCase()); |
420 |
| - }); |
421 |
| - } |
422 |
| - |
423 | 424 | deferred.forEach(({ kind, name, local, range, namespace }) => {
|
424 | 425 | const key = name?.toLowerCase();
|
425 | 426 | let hasCallable = key ? callables.has(key) || toplevel.has(key) : false;
|
@@ -448,3 +449,26 @@ function deferredVarLinter(
|
448 | 449 | }
|
449 | 450 | });
|
450 | 451 | }
|
| 452 | + |
| 453 | +/** |
| 454 | + * Get the leftmost part of the namespace name. (i.e. `alpha` from `alpha.beta.charlie`) by walking |
| 455 | + * up the namespace chain until we get to the very topmost namespace. Then grabbing the leftmost token's name. |
| 456 | + * |
| 457 | + */ |
| 458 | +export function getRootNamespaceName(namespace: NamespaceStatement) { |
| 459 | + // there are more concise ways to accomplish this, but this is a hot function so it's been optimized. |
| 460 | + while (true) { |
| 461 | + const parent = namespace.parent?.parent as NamespaceStatement; |
| 462 | + if (isNamespaceStatement(parent)) { |
| 463 | + namespace = parent; |
| 464 | + } else { |
| 465 | + break; |
| 466 | + } |
| 467 | + } |
| 468 | + const result = util.getDottedGetPath(namespace.nameExpression)[0]?.name?.text; |
| 469 | + // const name = namespace.getName(ParseMode.BrighterScript).toLowerCase(); |
| 470 | + // if (name.includes('imigx')) { |
| 471 | + // console.log([name, result]); |
| 472 | + // } |
| 473 | + return result; |
| 474 | +} |
0 commit comments