Skip to content

Commit

Permalink
support on-demand parse for typedef-shadowed files (#237)
Browse files Browse the repository at this point in the history
  • Loading branch information
TwitchBronBron committed Nov 1, 2020
1 parent a32651e commit abae186
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 34 deletions.
51 changes: 46 additions & 5 deletions src/files/BrsFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2134,18 +2134,19 @@ describe('BrsFile', () => {

describe('type definitions', () => {
it('only exposes defined functions even if source has more', async () => {
await program.addOrReplaceFile('source/main.d.bs', `
//parse the .brs file first so it doesn't know about the typedef
await program.addOrReplaceFile<BrsFile>('source/main.brs', `
sub main()
end sub
sub speak()
end sub
`);

const file = await program.addOrReplaceFile<BrsFile>('source/main.brs', `
await program.addOrReplaceFile('source/main.d.bs', `
sub main()
end sub
sub speak()
end sub
`);
expect(file.parser.references.functionStatements).to.be.empty;

const sourceScope = program.getScopeByName('source');
const functionNames = sourceScope.getAllCallables().map(x => x.callable.name);
expect(functionNames).to.include('main');
Expand Down Expand Up @@ -2429,6 +2430,46 @@ describe('BrsFile', () => {
});
});

describe('parser getter', () => {
it('recreates the parser when missing', async () => {
const file = await program.addOrReplaceFile<BrsFile>('source/main.brs', `
sub main()
end sub
`);
const parser = file['_parser'];
//clear the private _parser instance
file['_parser'] = undefined;

//force the file to get a new instance of parser
const newParser = file.parser;

expect(newParser).to.exist.and.to.not.equal(parser);

//reference shouldn't change in subsequent accesses
expect(file.parser).to.equal(newParser);
});

it('call parse when previously skipped', async () => {
await program.addOrReplaceFile<BrsFile>('source/main.d.bs', `
sub main()
end sub
`);
const file = await program.addOrReplaceFile<BrsFile>('source/main.brs', `
sub main()
end sub
`);
//no functions should be found since the parser was skipped
expect(file['_parser']).to.not.exist;

const stub = sinon.stub(file, 'parse').callThrough();

//`file.parser` is a getter, so that should force the parse to occur
expect(file.parser.references.functionStatements).to.be.lengthOf(1);
expect(stub.called).to.be.true;
//parse should have been called
});
});

describe('Plugins', () => {
it('can load a plugin which transforms the AST', async () => {
program.plugins = new PluginInterface(
Expand Down
56 changes: 30 additions & 26 deletions src/files/BrsFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,23 @@ export class BrsFile {
}
}

public parser = new Parser();
public get parser() {
if (!this._parser) {
//remove the typedef file (if it exists)
this.hasTypedef = false;
this.typedefFile = undefined;

//reset the deferred
this.parseDeferred = new Deferred();
//parse the file (it should parse fully since there's no linked typedef
this.parse(this.fileContents);

//re-link the typedef (if it exists...which it should)
this.resolveTypdef();
}
return this._parser;
}
private _parser: Parser;

public fileContents: string;

Expand Down Expand Up @@ -188,13 +204,6 @@ export class BrsFile {
//event that fires anytime a dependency changes
this.unsubscribeFromDependencyGraph = this.program.dependencyGraph.onchange(this.dependencyGraphKey, () => {
this.resolveTypdef();

//if there is no typedef file, and this file hasn't been parsed yet, parse it now
//(solves issue when typedef gets deleted and this file had skipped parsing)
if (!this.hasTypedef && this.wasParseSkipped) {
this.parseDeferred = new Deferred();
this.parse(this.fileContents);
}
});

const dependencies = this.ownScriptImports.filter(x => !!x.pkgPath).map(x => x.pkgPath.toLowerCase());
Expand All @@ -208,11 +217,6 @@ export class BrsFile {
dependencyGraph.addOrReplace(this.dependencyGraphKey, dependencies);
}

/**
* Was parsing skipped because the file has a typedef?
*/
private wasParseSkipped = false;

/**
* Calculate the AST for this file
* @param fileContents
Expand All @@ -226,7 +230,6 @@ export class BrsFile {

//if we have a typedef file, skip parsing this file
if (this.hasTypedef) {
this.wasParseSkipped = true;
this.parseDeferred.resolve();
return;
}
Expand Down Expand Up @@ -260,7 +263,7 @@ export class BrsFile {
let tokens = preprocessor.processedTokens.length > 0 ? preprocessor.processedTokens : lexer.tokens;

this.program.logger.time(LogLevel.debug, ['parser.parse', chalk.green(this.pathAbsolute)], () => {
this.parser.parse(tokens, {
this._parser = Parser.parse(tokens, {
mode: this.parseMode,
logger: this.program.logger
});
Expand All @@ -270,7 +273,7 @@ export class BrsFile {
this.diagnostics.push(
...lexer.diagnostics as BsDiagnostic[],
...preprocessor.diagnostics as BsDiagnostic[],
...this.parser.diagnostics as BsDiagnostic[]
...this._parser.diagnostics as BsDiagnostic[]
);

//notify AST ready
Expand All @@ -289,14 +292,13 @@ export class BrsFile {
diagnostic.file = this;
}
} catch (e) {
this.parser = new Parser();
this._parser = new Parser();
this.diagnostics.push({
file: this,
range: util.createRange(0, 0, 0, Number.MAX_VALUE),
...DiagnosticMessages.genericParserMessage('Critical error parsing file: ' + JSON.stringify(serializeError(e)))
});
}
this.wasParseSkipped = false;
this.parseDeferred.resolve();
}

Expand All @@ -318,8 +320,8 @@ export class BrsFile {
}

let statements = [
...this.parser.references.libraryStatements,
...this.parser.references.importStatements
...this._parser.references.libraryStatements,
...this._parser.references.importStatements
];
for (let result of statements) {
//register import statements
Expand Down Expand Up @@ -652,7 +654,7 @@ export class BrsFile {
private findFunctionCalls() {
this.functionCalls = [];
//for every function in the file
for (let func of this.parser.references.functionExpressions) {
for (let func of this._parser.references.functionExpressions) {
//for all function calls in this function
for (let expression of func.callExpressions) {

Expand Down Expand Up @@ -891,8 +893,9 @@ export class BrsFile {

//consume tokens backwards until we find someting other than a dot or an identifier
let tokens = [];
for (let i = this.parser.tokens.indexOf(currentToken); i >= 0; i--) {
currentToken = this.parser.tokens[i];
const parser = this.parser;
for (let i = parser.tokens.indexOf(currentToken); i >= 0; i--) {
currentToken = parser.tokens[i];
if (identifierAndDotKinds.includes(currentToken.kind)) {
tokens.unshift(currentToken.text);
} else {
Expand Down Expand Up @@ -932,8 +935,9 @@ export class BrsFile {
}

public getPreviousToken(token: Token) {
let idx = this.parser.tokens.indexOf(token);
return this.parser.tokens[idx - 1];
const parser = this.parser;
let idx = parser.tokens.indexOf(token);
return parser.tokens[idx - 1];
}

/**
Expand Down Expand Up @@ -1093,7 +1097,7 @@ export class BrsFile {
}

public dispose() {
this.parser?.dispose();
this._parser?.dispose();
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/globalCallables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ import { InterfaceType } from './types/InterfaceType';
import { ObjectType } from './types/ObjectType';
import { StringType } from './types/StringType';
import { VoidType } from './types/VoidType';
import { Parser } from './parser';
import util from './util';

export let globalFile = new BrsFile('global', 'global', null);
globalFile.parser = new Parser();
globalFile.parser.parse([]);
globalFile.parse('');

let mathFunctions = [{
name: 'Abs',
Expand Down

0 comments on commit abae186

Please sign in to comment.