Repository: rokucommunity/brighterscript
Pull request #637 (2022-07-11 17:45:36)

Use `util.createLocation`, not `Location.create()`
VSCode's `Location.create()` is slow (they probably do more validation under the hood. We care about raw speed, so use a faster util function. Similar to the `util.createRange()` and `util.createPosition()` functions introduced some time ago.


Patch:
diff --git a/src/Program.spec.ts b/src/Program.spec.ts
index 8f3b7520..8cf3a362 100644
--- a/src/Program.spec.ts
+++ b/src/Program.spec.ts
@@ -1,7 +1,7 @@
 import { assert, expect } from 'chai';
 import * as pick from 'object.pick';
 import * as sinonImport from 'sinon';
-import { CompletionItemKind, Position, Range, Location } from 'vscode-languageserver';
+import { CompletionItemKind, Position, Range } from 'vscode-languageserver';
 import * as fsExtra from 'fs-extra';
 import { DiagnosticMessages } from './DiagnosticMessages';
 import type { BrsFile } from './files/BrsFile';
@@ -232,7 +232,7 @@ describe('Program', () => {
                 ...DiagnosticMessages.duplicateComponentName('Component1'),
                 range: Range.create(1, 17, 1, 27),
                 relatedInformation: [{
-                    location: Location.create(
+                    location: util.createLocation(
                         URI.file(s`${rootDir}/components/component1.xml`).toString(),
                         Range.create(1, 17, 1, 27)
                     ),
@@ -242,7 +242,7 @@ describe('Program', () => {
                 ...DiagnosticMessages.duplicateComponentName('Component1'),
                 range: Range.create(1, 17, 1, 27),
                 relatedInformation: [{
-                    location: Location.create(
+                    location: util.createLocation(
                         URI.file(s`${rootDir}/components/component2.xml`).toString(),
                         Range.create(1, 17, 1, 27)
                     ),
diff --git a/src/Program.ts b/src/Program.ts
index 1751b927..f087dd2c 100644
--- a/src/Program.ts
+++ b/src/Program.ts
@@ -1,8 +1,8 @@
 import * as assert from 'assert';
 import * as fsExtra from 'fs-extra';
 import * as path from 'path';
-import type { CodeAction, CompletionItem, Position, Range, SignatureInformation } from 'vscode-languageserver';
-import { Location, CompletionItemKind } from 'vscode-languageserver';
+import type { CodeAction, CompletionItem, Position, Range, SignatureInformation, Location } from 'vscode-languageserver';
+import { CompletionItemKind } from 'vscode-languageserver';
 import type { BsConfig } from './BsConfig';
 import { Scope } from './Scope';
 import { DiagnosticMessages } from './DiagnosticMessages';
@@ -677,7 +677,7 @@ export class Program {
                         file: xmlFile,
                         relatedInformation: xmlFiles.filter(x => x !== xmlFile).map(x => {
                             return {
-                                location: Location.create(
+                                location: util.createLocation(
                                     URI.file(xmlFile.srcPath ?? xmlFile.srcPath).toString(),
                                     x.componentName.range
                                 ),
diff --git a/src/Scope.ts b/src/Scope.ts
index f8e50f7b..86b6a020 100644
--- a/src/Scope.ts
+++ b/src/Scope.ts
@@ -1,6 +1,6 @@
-import type { CompletionItem, Position, Range } from 'vscode-languageserver';
+import type { CompletionItem, Position, Range, Location } from 'vscode-languageserver';
 import * as path from 'path';
-import { CompletionItemKind, Location } from 'vscode-languageserver';
+import { CompletionItemKind } from 'vscode-languageserver';
 import chalk from 'chalk';
 import type { DiagnosticInfo } from './DiagnosticMessages';
 import { DiagnosticMessages } from './DiagnosticMessages';
@@ -636,7 +636,7 @@ export class Scope {
                         range: param.name.range,
                         relatedInformation: [{
                             message: 'Namespace declared here',
-                            location: Location.create(
+                            location: util.createLocation(
                                 URI.file(namespace.file.srcPath).toString(),
                                 namespace.nameRange
                             )
@@ -657,7 +657,7 @@ export class Scope {
                     range: assignment.name.range,
                     relatedInformation: [{
                         message: 'Namespace declared here',
-                        location: Location.create(
+                        location: util.createLocation(
                             URI.file(namespace.file.srcPath).toString(),
                             namespace.nameRange
                         )
diff --git a/src/bscPlugin/validation/ScopeValidator.ts b/src/bscPlugin/validation/ScopeValidator.ts
index 099669c0..3b149443 100644
--- a/src/bscPlugin/validation/ScopeValidator.ts
+++ b/src/bscPlugin/validation/ScopeValidator.ts
@@ -1,4 +1,3 @@
-import { Location } from 'vscode-languageserver';
 import { URI } from 'vscode-uri';
 import { isBinaryExpression, isBrsFile, isLiteralExpression } from '../../astUtils/reflection';
 import { Cache } from '../../Cache';
@@ -100,7 +99,7 @@ export class ScopeValidator {
                                 range: tokens[tokens.length - 1].range,
                                 relatedInformation: [{
                                     message: 'Enum declared here',
-                                    location: Location.create(
+                                    location: util.createLocation(
                                         URI.file(file.srcPath).toString(),
                                         theEnum.tokens.name.range
                                     )
@@ -143,7 +142,7 @@ export class ScopeValidator {
                     range: duplicateEnumInfo.statement.tokens.name.range,
                     relatedInformation: [{
                         message: 'Enum declared here',
-                        location: Location.create(
+                        location: util.createLocation(
                             URI.file(primaryEnum.file.srcPath).toString(),
                             primaryEnum.statement.tokens.name.range
                         )
diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts
index dc40b424..2ef98fe4 100644
--- a/src/files/BrsFile.ts
+++ b/src/files/BrsFile.ts
@@ -1,7 +1,7 @@
 import type { CodeWithSourceMap } from 'source-map';
 import { SourceNode } from 'source-map';
-import type { CompletionItem, Hover, Position } from 'vscode-languageserver';
-import { CompletionItemKind, SymbolKind, Location, SignatureInformation, ParameterInformation, DocumentSymbol, SymbolInformation, TextEdit } from 'vscode-languageserver';
+import type { CompletionItem, Hover, Position, Location } from 'vscode-languageserver';
+import { CompletionItemKind, SymbolKind, SignatureInformation, ParameterInformation, DocumentSymbol, SymbolInformation, TextEdit } from 'vscode-languageserver';
 import chalk from 'chalk';
 import * as path from 'path';
 import type { Scope } from '../Scope';
@@ -1094,7 +1094,7 @@ export class BrsFile {
                 const namespaceItemStatementHandler = (statement: ClassStatement | FunctionStatement) => {
                     if (!location && statement.name.text.toLowerCase() === endName) {
                         const uri = util.pathToUri(file.srcPath);
-                        location = Location.create(uri, statement.range);
+                        location = util.createLocation(uri, statement.range);
                     }
                 };
 
@@ -1410,7 +1410,7 @@ export class BrsFile {
                 //to only get functions defined in interface methods
                 const callable = scope.getAllCallables().find((c) => c.callable.name.toLowerCase() === textToSearchFor); // eslint-disable-line @typescript-eslint/no-loop-func
                 if (callable) {
-                    results.push(Location.create(util.pathToUri((callable.callable.file as BrsFile).srcPath), callable.callable.functionStatement.range));
+                    results.push(util.createLocation(util.pathToUri((callable.callable.file as BrsFile).srcPath), callable.callable.functionStatement.range));
                 }
             }
             return results;
@@ -1423,7 +1423,7 @@ export class BrsFile {
                 const nameParts = cs.parentClassName.getNameParts();
                 let extendedClass = this.getClassFileLink(nameParts[nameParts.length - 1], nameParts.slice(0, -1).join('.'));
                 if (extendedClass) {
-                    results.push(Location.create(util.pathToUri(extendedClass.file.srcPath), extendedClass.item.range));
+                    results.push(util.createLocation(util.pathToUri(extendedClass.file.srcPath), extendedClass.item.range));
                 }
             }
             return results;
@@ -1448,14 +1448,14 @@ export class BrsFile {
                 //we found a variable declaration with this token text!
                 if (varDeclaration.name.toLowerCase() === textToSearchFor) {
                     const uri = util.pathToUri(this.srcPath);
-                    results.push(Location.create(uri, varDeclaration.nameRange));
+                    results.push(util.createLocation(uri, varDeclaration.nameRange));
                 }
             }
             if (this.tokenFollows(token, TokenKind.Goto)) {
                 for (const label of functionScope.labelStatements) {
                     if (label.name.toLocaleLowerCase() === textToSearchFor) {
                         const uri = util.pathToUri(this.srcPath);
-                        results.push(Location.create(uri, label.nameRange));
+                        results.push(util.createLocation(uri, label.nameRange));
                     }
                 }
             }
@@ -1480,7 +1480,7 @@ export class BrsFile {
                 const statementHandler = (statement: FunctionStatement) => {
                     if (statement.getName(this.parseMode).toLowerCase() === textToSearchFor) {
                         const uri = util.pathToUri(file.srcPath);
-                        results.push(Location.create(uri, statement.range));
+                        results.push(util.createLocation(uri, statement.range));
                     }
                 };
 
@@ -1499,12 +1499,12 @@ export class BrsFile {
         //get class fields and members
         const statementHandler = (statement: MethodStatement) => {
             if (statement.getName(file.parseMode).toLowerCase() === textToSearchFor) {
-                results.push(Location.create(util.pathToUri(file.srcPath), statement.range));
+                results.push(util.createLocation(util.pathToUri(file.srcPath), statement.range));
             }
         };
         const fieldStatementHandler = (statement: FieldStatement) => {
             if (statement.name.text.toLowerCase() === textToSearchFor) {
-                results.push(Location.create(util.pathToUri(file.srcPath), statement.range));
+                results.push(util.createLocation(util.pathToUri(file.srcPath), statement.range));
             }
         };
         file.parser.ast.walk(createVisitor({
@@ -1756,7 +1756,7 @@ export class BrsFile {
                 file.ast.walk(createVisitor({
                     VariableExpression: (e) => {
                         if (e.name.text.toLowerCase() === searchFor) {
-                            locations.push(Location.create(util.pathToUri(file.srcPath), e.range));
+                            locations.push(util.createLocation(util.pathToUri(file.srcPath), e.range));
                         }
                     }
                 }), {
diff --git a/src/util.ts b/src/util.ts
index cb4ef1d1..4c1a812c 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -4,7 +4,7 @@ import type { ParseError } from 'jsonc-parser';
 import { parse as parseJsonc, printParseErrorCode } from 'jsonc-parser';
 import * as path from 'path';
 import { rokuDeploy, DefaultFiles, standardizePath as rokuDeployStandardizePath } from 'roku-deploy';
-import type { Diagnostic, Position, Range } from 'vscode-languageserver';
+import type { Diagnostic, Position, Range, Location } from 'vscode-languageserver';
 import { URI } from 'vscode-uri';
 import * as xml2js from 'xml2js';
 import type { BsConfig } from './BsConfig';
@@ -866,6 +866,16 @@ export class Util {
         return rangeLines.join('\n');
     }
 
+    /**
+     * Helper for creating `Location` objects. Prefer using this function because vscode-languageserver's `Location.create()` is significantly slower at scale
+     */
+    public createLocation(uri: string, range: Range): Location {
+        return {
+            uri: uri,
+            range: range
+        };
+    }
+
     /**
      * Helper for creating `Range` objects. Prefer using this function because vscode-languageserver's `util.createRange()` is significantly slower
      */
diff --git a/src/validators/ClassValidator.ts b/src/validators/ClassValidator.ts
index 3af2a5a3..7e388e2b 100644
--- a/src/validators/ClassValidator.ts
+++ b/src/validators/ClassValidator.ts
@@ -3,7 +3,7 @@ import { DiagnosticMessages } from '../DiagnosticMessages';
 import type { CallExpression } from '../parser/Expression';
 import { ParseMode } from '../parser/Parser';
 import type { ClassStatement, MethodStatement } from '../parser/Statement';
-import { CancellationTokenSource, Location } from 'vscode-languageserver';
+import { CancellationTokenSource } from 'vscode-languageserver';
 import { URI } from 'vscode-uri';
 import util from '../util';
 import { isCallExpression, isClassFieldStatement, isClassMethodStatement, isCustomType } from '../astUtils/reflection';
@@ -102,7 +102,7 @@ export class BsClassValidator {
                     file: classStatement.file,
                     range: classStatement.name.range,
                     relatedInformation: [{
-                        location: Location.create(
+                        location: util.createLocation(
                             URI.file(nonNamespaceClass.file.srcPath).toString(),
                             nonNamespaceClass.name.range
                         ),
@@ -376,7 +376,7 @@ export class BsClassValidator {
                         file: file,
                         range: classStatement.name.range,
                         relatedInformation: [{
-                            location: Location.create(
+                            location: util.createLocation(
                                 URI.file(alreadyDefinedClass.file.srcPath).toString(),
                                 this.classes.get(lowerName).range
                             ),


Test patch:
diff --git a/src/parser/tests/statement/Enum.spec.ts b/src/parser/tests/statement/Enum.spec.ts
index 0a960c07..d7d7a537 100644
--- a/src/parser/tests/statement/Enum.spec.ts
+++ b/src/parser/tests/statement/Enum.spec.ts
@@ -8,7 +8,7 @@ import { EnumStatement, InterfaceStatement } from '../../Statement';
 import { Program } from '../../../Program';
 import { createSandbox } from 'sinon';
 import type { BrsFile } from '../../../files/BrsFile';
-import { Location, CancellationTokenSource, CompletionItemKind } from 'vscode-languageserver-protocol';
+import { CancellationTokenSource, CompletionItemKind } from 'vscode-languageserver-protocol';
 import { WalkMode } from '../../../astUtils/visitors';
 import { isEnumStatement } from '../../../astUtils/reflection';
 import { URI } from 'vscode-uri';
@@ -216,7 +216,7 @@ describe('EnumStatement', () => {
             expectDiagnostics(program, [{
                 ...DiagnosticMessages.duplicateEnumDeclaration('source', 'Direction'),
                 relatedInformation: [{
-                    location: Location.create(
+                    location: util.createLocation(
                         URI.file(s`${rootDir}/source/main.bs`).toString(),
                         util.createRange(1, 21, 1, 30)
                     ),
@@ -240,7 +240,7 @@ describe('EnumStatement', () => {
             expectDiagnostics(program, [{
                 ...DiagnosticMessages.duplicateEnumDeclaration('source', 'Direction'),
                 relatedInformation: [{
-                    location: Location.create(
+                    location: util.createLocation(
                         URI.file(s`${rootDir}/source/lib.bs`).toString(),
                         util.createRange(1, 21, 1, 30)
                     ),