Skip to content

Commit 81aa853

Browse files
Performance boost by lifting some global lookups
1 parent 2fbc1e1 commit 81aa853

File tree

3 files changed

+66
-29
lines changed

3 files changed

+66
-29
lines changed

.vscode/tasks.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@
1212
"label": "npm: build",
1313
"detail": "tsc"
1414
},
15+
{
16+
"label": "watch",
17+
"type": "npm",
18+
"script": "watch",
19+
"presentation": {
20+
"group": "watch"
21+
},
22+
"isBackground": true,
23+
"problemMatcher": [
24+
"$tsc-watch"
25+
]
26+
},
1527
{
1628
"type": "shell",
1729
"label": "test",

src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BsConfig, Program, DiagnosticSeverity } from 'brighterscript';
1+
import { BsConfig, Program, DiagnosticSeverity, CompilerPlugin } from 'brighterscript';
22
import Linter from './Linter';
33
import CheckUsage from './plugins/checkUsage';
44
import CodeStyle from './plugins/codeStyle';
@@ -71,9 +71,10 @@ export interface BsLintRules {
7171

7272
export { Linter };
7373

74-
export default function factory() {
74+
export default function factory(): CompilerPlugin {
7575
const contextMap = new WeakMap<Program, PluginWrapperContext>();
7676
return {
77+
name: 'bslint',
7778
afterProgramCreate: (program: Program) => {
7879
const context = createContext(program);
7980
contextMap.set(program, context);

src/plugins/trackCodeFlow/varTracking.ts

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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';
22
import { LintState, StatementInfo, NarrowingInfo, VarInfo, VarRestriction } from '.';
33
import { PluginContext } from '../../util';
44

@@ -52,7 +52,7 @@ export function createVarLinter(
5252
args.set(name.toLowerCase(), { name: name, range: p.name.range, isParam: true, isUnsafe: false, isUsed: false });
5353
});
5454

55-
if (isClassMethodStatement(fun.functionStatement)) {
55+
if (isMethodStatement(fun.functionStatement)) {
5656
args.set('super', { name: 'super', range: null, isParam: true, isUnsafe: false, isUsed: true });
5757
}
5858

@@ -379,47 +379,48 @@ export function runDeferredValidation(
379379
files: BscFile[],
380380
callables: CallableContainerMap
381381
) {
382-
const globals = lintContext.globals;
383-
382+
const topLevelVars = buildTopLevelVars(scope, lintContext.globals);
384383
const diagnostics: BsDiagnostic[] = [];
385384
files.forEach((file) => {
386385
const deferred = deferredValidation.get(file.pathAbsolute);
387386
if (deferred) {
388-
deferredVarLinter(scope, file, callables, globals, deferred, diagnostics);
387+
deferredVarLinter(scope, file, callables, topLevelVars, deferred, diagnostics);
389388
}
390389
});
391390
return diagnostics;
392391
}
393392

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+
394416
function deferredVarLinter(
395417
scope: Scope,
396418
file: BscFile,
397419
callables: CallableContainerMap,
398-
globals: string[],
420+
toplevel: Set<string>,
399421
deferred: ValidationInfo[],
400422
diagnostics: BsDiagnostic[]
401423
) {
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-
423424
deferred.forEach(({ kind, name, local, range, namespace }) => {
424425
const key = name?.toLowerCase();
425426
let hasCallable = key ? callables.has(key) || toplevel.has(key) : false;
@@ -448,3 +449,26 @@ function deferredVarLinter(
448449
}
449450
});
450451
}
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

Comments
 (0)