Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
make getFile more flexible.
  • Loading branch information
TwitchBronBron committed Feb 4, 2022
commit 2f9e00484bc20eaed800c71a46efed62d36134e0
12 changes: 5 additions & 7 deletions src/LanguageServer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,11 @@ describe('LanguageServer', () => {

function addScriptFile(name: string, contents: string, extension = 'brs') {
const filePath = s`components/${name}.${extension}`;
program.addOrReplaceFile(filePath, contents);
for (const key in program.files) {
if (key.includes(filePath)) {
const document = TextDocument.create(util.pathToUri(key), 'brightscript', 1, contents);
svr.documents._documents[document.uri] = document;
return document;
}
const file = program.addOrReplaceFile(filePath, contents);
if (file) {
const document = TextDocument.create(util.pathToUri(file.pathAbsolute), 'brightscript', 1, contents);
svr.documents._documents[document.uri] = document;
return document;
}
}

Expand Down
110 changes: 73 additions & 37 deletions src/Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Scope } from './Scope';
import { DiagnosticMessages } from './DiagnosticMessages';
import { BrsFile } from './files/BrsFile';
import { XmlFile } from './files/XmlFile';
import type { BsDiagnostic, File, FileReference, FileObj, BscFile, SemanticToken, AfterFileTranspileEvent } from './interfaces';
import type { BsDiagnostic, File, FileReference, FileObj, BscFile, SemanticToken, AfterFileTranspileEvent, FileLink } from './interfaces';
import { standardizePath as s, util } from './util';
import { XmlScope } from './XmlScope';
import { DiagnosticFilterer } from './DiagnosticFilterer';
Expand Down Expand Up @@ -48,12 +48,6 @@ export interface SignatureInfoObj {
signature: SignatureInformation;
}

export interface FileLink<T> {
item: T;
file: BrsFile;
}


interface PartialStatementInfo {
commaCount: number;
statementType: string;
Expand Down Expand Up @@ -131,11 +125,11 @@ export class Program {
*/
public get bslibPkgPath() {
//if there's an aliased (preferred) version of bslib from roku_modules loaded into the program, use that
if (this.getFileByPkgPath(bslibAliasedRokuModulesPkgPath)) {
if (this.getFile(bslibAliasedRokuModulesPkgPath)) {
return bslibAliasedRokuModulesPkgPath;

//if there's a non-aliased version of bslib from roku_modules, use that
} else if (this.getFileByPkgPath(bslibNonAliasedRokuModulesPkgPath)) {
} else if (this.getFile(bslibNonAliasedRokuModulesPkgPath)) {
return bslibNonAliasedRokuModulesPkgPath;

//default to the embedded version
Expand Down Expand Up @@ -299,10 +293,10 @@ export class Program {
/**
* Determine if the specified file is loaded in this program right now.
* @param filePath
* @param normalizePath should the provided path be normalized before use
*/
public hasFile(filePath: string) {
filePath = s`${filePath}`;
return this.files[filePath] !== undefined;
public hasFile(filePath: string, normalizePath = true) {
return !!this.getFile(filePath, normalizePath);
}

public getPkgPath(...args: any[]): any { //eslint-disable-line
Expand Down Expand Up @@ -338,6 +332,24 @@ export class Program {
return this.getComponent(componentName)?.scope;
}

/**
* Update internal maps with this file reference
*/
private assignFile<T extends BscFile = BscFile>(file: T) {
this.files[file.pathAbsolute.toLowerCase()] = file;
this.pkgMap[file.pkgPath.toLowerCase()] = file;
return file;
}

/**
* Remove this file from internal maps
*/
private unassignFile<T extends BscFile = BscFile>(file: T) {
delete this.files[file.pathAbsolute.toLowerCase()];
delete this.pkgMap[file.pkgPath.toLowerCase()];
return file;
}

/**
* Load a file into the program. If that file already exists, it is replaced.
* If file contents are provided, those are used, Otherwise, the file is loaded from the file system
Expand Down Expand Up @@ -375,18 +387,17 @@ export class Program {
let file: BscFile | undefined;

if (fileExtension === '.brs' || fileExtension === '.bs') {
let brsFile = new BrsFile(srcPath, pkgPath, this);
//add the file to the program
const brsFile = this.assignFile(
new BrsFile(srcPath, pkgPath, this)
);

//add file to the `source` dependency list
if (brsFile.pkgPath.startsWith(startOfSourcePkgPath)) {
this.createSourceScope();
this.dependencyGraph.addDependency('scope:source', brsFile.dependencyGraphKey);
}


//add the file to the program
this.files[srcPath] = brsFile;
this.pkgMap[brsFile.pkgPath.toLowerCase()] = brsFile;
let sourceObj: SourceObj = {
pathAbsolute: srcPath,
source: fileContents
Expand All @@ -410,10 +421,11 @@ export class Program {
//resides in the components folder (Roku will only parse xml files in the components folder)
pkgPath.toLowerCase().startsWith(util.pathSepNormalize(`components/`))
) {
let xmlFile = new XmlFile(srcPath, pkgPath, this);
//add the file to the program
this.files[srcPath] = xmlFile;
this.pkgMap[xmlFile.pkgPath.toLowerCase()] = xmlFile;
const xmlFile = this.assignFile(
new XmlFile(srcPath, pkgPath, this)
);

let sourceObj: SourceObj = {
pathAbsolute: srcPath,
source: fileContents
Expand Down Expand Up @@ -459,6 +471,7 @@ export class Program {
const sourceScope = new Scope('source', this, 'scope:source');
sourceScope.attachDependencyGraph(this.dependencyGraph);
this.addScope(sourceScope);
this.getFileByPathAbsolute('asdf');
}
}

Expand All @@ -467,6 +480,7 @@ export class Program {
* Roku is a case insensitive file system. It is an error to have multiple files
* with the same path with only case being different.
* @param pathAbsolute
* @deprecated use `getFile` instead, which auto-detects the path type
*/
public getFileByPathAbsolute<T extends BrsFile | XmlFile>(pathAbsolute: string) {
pathAbsolute = s`${pathAbsolute}`;
Expand All @@ -490,32 +504,32 @@ export class Program {
/**
* Get a file with the specified (platform-normalized) pkg path.
* If not found, return undefined
* @deprecated use `getFiles` instead, which auto-detects the path type
*/
public getFileByPkgPath<T extends BscFile>(pkgPath: string) {
return this.pkgMap[pkgPath.toLowerCase()] as T;
}

/**
* Remove a set of files from the program
* @param absolutePaths
* @param filePaths can be an array of srcPath or destPath strings
* @param normalizePath should this function repair and standardize the filePaths? Passing false should have a performance boost if you can guarantee your paths are already sanitized
*/
public removeFiles(absolutePaths: string[]) {
for (let pathAbsolute of absolutePaths) {
this.removeFile(pathAbsolute);
public removeFiles(srcPaths: string[], normalizePath = true) {
for (let srcPath of srcPaths) {
this.removeFile(srcPath, normalizePath);
}
}

/**
* Remove a file from the program
* @param pathAbsolute
* @param filePath can be a srcPath, a pkgPath, or a destPath (same as pkgPath but without `pkg:/`)
* @param normalizePath should this function repair and standardize the path? Passing false should have a performance boost if you can guarantee your path is already sanitized
*/
public removeFile(pathAbsolute: string) {
this.logger.debug('Program.removeFile()', pathAbsolute);
if (!path.isAbsolute(pathAbsolute)) {
throw new Error(`Path must be absolute: "${pathAbsolute}"`);
}
public removeFile(filePath: string, normalizePath = true) {
this.logger.debug('Program.removeFile()', filePath);

let file = this.getFile(pathAbsolute);
let file = this.getFile(filePath, normalizePath);
if (file) {
this.plugins.emit('beforeFileDispose', file);

Expand All @@ -530,8 +544,7 @@ export class Program {
this.plugins.emit('afterScopeDispose', scope);
}
//remove the file from the program
delete this.files[file.pathAbsolute];
delete this.pkgMap[file.pkgPath.toLowerCase()];
this.unassignFile(file);

this.dependencyGraph.remove(file.dependencyGraphKey);

Expand Down Expand Up @@ -661,11 +674,21 @@ export class Program {

/**
* Get the file at the given path
* @param pathAbsolute
* @param filePath can be a srcPath or a destPath
* @param normalizePath should this function repair and standardize the path? Passing false should have a performance boost if you can guarantee your path is already sanitized
*/
private getFile<T extends BscFile>(pathAbsolute: string) {
pathAbsolute = s`${pathAbsolute}`;
return this.files[pathAbsolute] as T;
public getFile<T extends BscFile>(filePath: string, normalizePath = true) {
if (typeof filePath !== 'string') {
return undefined;
} else if (path.isAbsolute(filePath)) {
return this.files[
(normalizePath ? util.standardizePath(filePath) : filePath).toLowerCase()
] as T;
} else {
return this.pkgMap[
(normalizePath ? util.standardizePath(filePath) : filePath).toLowerCase()
] as T;
}
}

/**
Expand All @@ -684,6 +707,19 @@ export class Program {
return result;
}

/**
* Get the first found scope for a file.
*/
public getFirstScopeForFile(file: XmlFile | BrsFile): Scope {
for (let key in this.scopes) {
let scope = this.scopes[key];

if (scope.hasFile(file)) {
return scope;
}
}
}

public getStatementsByName(name: string, originFile: BrsFile, namespaceName?: string): FileLink<Statement>[] {
let results = new Map<Statement, FileLink<Statement>>();
const filesSearched = new Set<BrsFile>();
Expand Down
4 changes: 2 additions & 2 deletions src/Scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { CompletionItemKind, Location } from 'vscode-languageserver';
import chalk from 'chalk';
import type { DiagnosticInfo } from './DiagnosticMessages';
import { DiagnosticMessages } from './DiagnosticMessages';
import type { CallableContainer, BsDiagnostic, FileReference, BscFile, CallableContainerMap } from './interfaces';
import type { FileLink, Program } from './Program';
import type { CallableContainer, BsDiagnostic, FileReference, BscFile, CallableContainerMap, FileLink } from './interfaces';
import type { Program } from './Program';
import { BsClassValidator } from './validators/ClassValidator';
import type { NamespaceStatement, Statement, FunctionStatement, ClassStatement } from './parser/Statement';
import type { NewExpression } from './parser/Expression';
Expand Down
4 changes: 2 additions & 2 deletions src/files/BrsFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2810,11 +2810,11 @@ describe('BrsFile', () => {
});

it('call parse when previously skipped', () => {
program.addOrReplaceFile<BrsFile>('source/main.d.bs', `
program.addOrReplaceFile<BrsFile>('source/main.d.bs', `'typedef
sub main()
end sub
`);
const file = program.addOrReplaceFile<BrsFile>('source/main.brs', `
const file = program.addOrReplaceFile<BrsFile>('source/main.brs', `'source
sub main()
end sub
`);
Expand Down
4 changes: 2 additions & 2 deletions src/files/BrsFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import * as path from 'path';
import type { Scope } from '../Scope';
import { DiagnosticCodeMap, diagnosticCodes, DiagnosticMessages } from '../DiagnosticMessages';
import { FunctionScope } from '../FunctionScope';
import type { Callable, CallableArg, CallableParam, CommentFlag, FunctionCall, BsDiagnostic, FileReference } from '../interfaces';
import type { Callable, CallableArg, CallableParam, CommentFlag, FunctionCall, BsDiagnostic, FileReference, FileLink } from '../interfaces';
import type { Token } from '../lexer/Token';
import { Lexer } from '../lexer/Lexer';
import { TokenKind, AllowedLocalIdentifiers, Keywords } from '../lexer/TokenKind';
import { Parser, ParseMode } from '../parser/Parser';
import type { FunctionExpression, VariableExpression, Expression } from '../parser/Expression';
import type { ClassStatement, FunctionStatement, NamespaceStatement, ClassMethodStatement, AssignmentStatement, LibraryStatement, ImportStatement, Statement, ClassFieldStatement } from '../parser/Statement';
import type { FileLink, Program, SignatureInfoObj } from '../Program';
import type { Program, SignatureInfoObj } from '../Program';
import { DynamicType } from '../types/DynamicType';
import { FunctionType } from '../types/FunctionType';
import { VoidType } from '../types/VoidType';
Expand Down
5 changes: 5 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,8 @@ export interface ExpressionInfo {
}

export type DiagnosticCode = number | string;

export interface FileLink<T> {
item: T;
file: BrsFile;
}
50 changes: 50 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ export class Util {
return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory();
}

/**
* Given a pkg path of any kind, transform it to a roku-specific pkg path (i.e. "pkg:/some/path.brs")
*/
public sanitizePkgPath(pkgPath: string) {
pkgPath = pkgPath.replace(/\\/g, '/');
//if there's no protocol, assume it's supposed to start with `pkg:/`
if (!this.startsWithProtocol(pkgPath)) {
pkgPath = 'pkg:/' + pkgPath;
}
return pkgPath;
}

/**
* Determine if the given path starts with a protocol
*/
public startsWithProtocol(path: string) {
return !!/^[-a-z]+:\//i.exec(path);
}

/**
* Given a pkg path of any kind, transform it to a roku-specific pkg path (i.e. "pkg:/some/path.brs")
*/
Expand Down Expand Up @@ -1102,6 +1121,15 @@ export class Util {
} as SGAttribute;
}

/**
* Converts a path into a standardized format (drive letter to lower, remove extra slashes, use single slash type, resolve relative parts, etc...)
*/
public standardizePath(thePath: string) {
return util.driveLetterToLower(
rokuDeploy.standardizePath(thePath)
);
}

/**
* Copy the version of bslib from local node_modules to the staging folder
*/
Expand Down Expand Up @@ -1216,6 +1244,28 @@ export class Util {
return '```' + language + '\n' + code + '\n```';
}

/**
* Gets each part of the dotted get.
* @param expression
* @returns an array of the parts of the dotted get. If not fully a dotted get, then returns undefined
*/
public getAllDottedGetParts(expression: Expression): string[] | undefined {
const parts: string[] = [];
let nextPart = expression;
while (nextPart) {
if (isDottedGetExpression(nextPart)) {
parts.push(nextPart?.name?.text);
nextPart = nextPart.obj;
} else if (isVariableExpression(nextPart)) {
parts.push(nextPart?.name?.text);
break;
} else {
//we found a non-DottedGet expression, so return because this whole operation is invalid.
return undefined;
}
}
return parts.reverse();
}
}

/**
Expand Down