Skip to content

Commit

Permalink
Add diagnostic for unknown file reference in import (#139)
Browse files Browse the repository at this point in the history
* diagnostic for missing files in import statements.
dedicated import spec

* finish doc comment
  • Loading branch information
TwitchBronBron committed Jul 8, 2020
1 parent 19e14bd commit 4c0333c
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 217 deletions.
176 changes: 0 additions & 176 deletions src/Program.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -854,182 +854,6 @@ describe('Program', () => {
});
});

describe('import statements', () => {
it('still transpiles import statements if found at bottom of file', async () => {
await program.addOrReplaceFile('components/ChildScene.xml', `
<?xml version="1.0" encoding="utf-8" ?>
<component name="ChildScene" extends="Scene">
<script type="text/brighterscript" uri="pkg:/source/lib.bs" />
</component>
`);

await program.addOrReplaceFile('source/lib.bs', `
function toLower(strVal as string)
return StringToLower(strVal)
end function
'this import is purposefully at the bottom just to prove the transpile still works
import "stringOps.bs"
`);

await program.addOrReplaceFile('source/stringOps.bs', `
function StringToLower(strVal as string)
return true
end function
`);
let files = Object.keys(program.files).map(x => program.getFileByPathAbsolute(x)).filter(x => !!x).map(x => {
return {
src: x.pathAbsolute,
dest: x.pkgPath
};
});
await program.transpile(files, stagingFolderPath);
expect(
fsExtra.readFileSync(`${stagingFolderPath}/components/ChildScene.xml`).toString()
).to.equal(`
<?xml version="1.0" encoding="utf-8" ?>
<component name="ChildScene" extends="Scene">
<script type="text/brightscript" uri="pkg:/source/lib.brs" />
<script type="text/brightscript" uri="pkg:/source/stringOps.brs" />
<script type="text/brightscript" uri="pkg:/source/bslib.brs" />
</component>
`);
});

it('finds function loaded in by import multiple levels deep', async () => {
//create child component
let component = await program.addOrReplaceFile('components/ChildScene.xml', `
<?xml version="1.0" encoding="utf-8" ?>
<component name="ChildScene" extends="ParentScene">
<script type="text/brighterscript" uri="pkg:/source/lib.bs" />
</component>
`);
await program.addOrReplaceFile('source/lib.bs', `
import "stringOps.bs"
function toLower(strVal as string)
return StringToLower(strVal)
end function
`);
await program.addOrReplaceFile('source/stringOps.bs', `
import "intOps.bs"
function StringToLower(strVal as string)
return isInt(strVal)
end function
`);
await program.addOrReplaceFile('source/intOps.bs', `
function isInt(strVal as dynamic)
return true
end function
`);
await program.validate();
expect(program.getDiagnostics().map(x => x.message)[0]).to.not.exist;
expect(
(component as XmlFile).getAvailableScriptImports().sort()
).to.eql([
s`source/intOps.bs`,
s`source/lib.bs`,
s`source/stringOps.bs`
]);
});

it('supports importing brs files', async () => {
//create child component
let component = await program.addOrReplaceFile('components/ChildScene.xml', `
<?xml version="1.0" encoding="utf-8" ?>
<component name="ChildScene" extends="ParentScene">
<script type="text/brighterscript" uri="pkg:/source/lib.bs" />
</component>
`);
await program.addOrReplaceFile('source/lib.bs', `
import "stringOps.brs"
function toLower(strVal as string)
return StringToLower(strVal)
end function
`);
await program.addOrReplaceFile('source/stringOps.brs', `
function StringToLower(strVal as string)
return lcase(strVal)
end function
`);
await program.validate();
expect(program.getDiagnostics().map(x => x.message)[0]).to.not.exist;
expect(
(component as XmlFile).getAvailableScriptImports()
).to.eql([
s`source/lib.bs`,
s`source/stringOps.brs`
]);
});

it('detects when dependency contents have changed', async () => {
//create child component
await program.addOrReplaceFile('components/ChildScene.xml', `
<?xml version="1.0" encoding="utf-8" ?>
<component name="ChildScene" extends="ParentScene">
<script type="text/brighterscript" uri="lib.bs" />
</component>
`);
await program.addOrReplaceFile('components/lib.bs', `
import "animalActions.bs"
function init1(strVal as string)
Waddle()
end function
`);
//add the empty dependency
await program.addOrReplaceFile('components/animalActions.bs', ``);

//there should be an error because that function doesn't exist
await program.validate();

expect(program.getDiagnostics().map(x => x.message)).to.eql([
DiagnosticMessages.callToUnknownFunction('Waddle', s`components/ChildScene.xml`).message
]);

//change the dependency to now contain the file. the scope should re-validate
await program.addOrReplaceFile('components/animalActions.bs', `
sub Waddle()
print "Waddling"
end sub
`);

//validate again
await program.validate();

//the error should be gone
expect(program.getDiagnostics()).to.be.empty;

});

it('adds brs imports to xml file during transpile', async () => {
//create child component
let component = await program.addOrReplaceFile({ src: s`${rootDir}/components/ChildScene.xml`, dest: 'components/ChildScene.xml' }, `
<?xml version="1.0" encoding="utf-8" ?>
<component name="ChildScene" extends="ParentScene">
<script type="text/brightscript" uri="pkg:/source/lib.bs" />
</component>
`);
await program.addOrReplaceFile({ src: s`${rootDir}/source/lib.bs`, dest: 'source/lib.bs' }, `
import "stringOps.brs"
function toLower(strVal as string)
return StringToLower(strVal)
end function
`);
await program.addOrReplaceFile({ src: s`${rootDir}/source/stringOps.brs`, dest: 'source/stringOps.brs' }, `
function StringToLower(strVal as string)
return isInt(strVal)
end function
`);
await program.validate();
expect(component.transpile().code).to.equal(`
<?xml version="1.0" encoding="utf-8" ?>
<component name="ChildScene" extends="ParentScene">
<script type="text/brightscript" uri="pkg:/source/lib.brs" />
<script type="text/brightscript" uri="pkg:/source/stringOps.brs" />
<script type="text/brightscript" uri="pkg:/source/bslib.brs" />
</component>
`);
});
});

describe('xml inheritance', () => {
it('handles parent-child attach and detach', async () => {
//create parent component
Expand Down
56 changes: 54 additions & 2 deletions src/Scope.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { EventEmitter } from 'eventemitter3';
import { CompletionItem, CompletionItemKind, Location, Position, Range } from 'vscode-languageserver';
import chalk from 'chalk';
import { DiagnosticMessages } from './DiagnosticMessages';
import { DiagnosticMessages, DiagnosticInfo } from './DiagnosticMessages';
import { BrsFile } from './files/BrsFile';
import { XmlFile } from './files/XmlFile';
import { CallableContainer, BsDiagnostic } from './interfaces';
import { CallableContainer, BsDiagnostic, FileReference } from './interfaces';
import { Program } from './Program';
import { BsClassValidator } from './validators/ClassValidator';
import { NamespaceStatement, ParseMode, Statement, NewExpression, FunctionStatement } from './parser';
Expand Down Expand Up @@ -358,6 +358,9 @@ export class Scope {
//find all duplicate function declarations
this.diagnosticFindDuplicateFunctionDeclarations(callableContainerMap);

//detect missing and incorrect-case script imports
this.diagnosticValidateScriptImportPaths();

//enforce a series of checks on the bodies of class methods
this.validateClasses();

Expand Down Expand Up @@ -665,6 +668,55 @@ export class Scope {
}
}

/**
* Get the list of all script imports for this scope
*/
private getScriptImports() {
let result = [] as FileReference[];
let files = this.getFiles();
for (let file of files) {
if (file instanceof BrsFile) {
result.push(...file.ownScriptImports);
} else if (file instanceof XmlFile) {
result.push(...file.scriptTagImports);
}
}
return result;
}

/**
* Verify that all of the scripts ipmorted by each file in this scope actually exist
*/
private diagnosticValidateScriptImportPaths() {
let scriptImports = this.getScriptImports();
//verify every script import
for (let scriptImport of scriptImports) {
let referencedFile = this.getFileByRelativePath(scriptImport.pkgPath);
//if we can't find the file
if (!referencedFile) {
let dInfo: DiagnosticInfo;
if (scriptImport.text.trim().length === 0) {
dInfo = DiagnosticMessages.scriptSrcCannotBeEmpty();
} else {
dInfo = DiagnosticMessages.referencedFileDoesNotExist();
}

this.diagnostics.push({
...dInfo,
range: scriptImport.filePathRange,
file: scriptImport.sourceFile
});
//if the character casing of the script import path does not match that of the actual path
} else if (scriptImport.pkgPath !== referencedFile.pkgPath) {
this.diagnostics.push({
...DiagnosticMessages.scriptImportCaseMismatch(referencedFile.pkgPath),
range: scriptImport.filePathRange,
file: scriptImport.sourceFile
});
}
}
}

/**
* Find the file with the specified relative path
* @param relativePath
Expand Down
37 changes: 1 addition & 36 deletions src/XmlScope.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Location, Position, Range } from 'vscode-languageserver';

import { Scope } from './Scope';
import { DiagnosticInfo, DiagnosticMessages } from './DiagnosticMessages';
import { DiagnosticMessages } from './DiagnosticMessages';
import { BrsFile } from './files/BrsFile';
import { XmlFile } from './files/XmlFile';
import { FileReference } from './interfaces';
Expand Down Expand Up @@ -43,9 +43,6 @@ export class XmlScope extends Scope {
//detect when the child imports a script that its ancestor also imports
this.diagnosticDetectDuplicateAncestorScriptImports();

//detect script imports to files that are not loaded in this scope
this.diagnosticValidateScriptImportPaths();

(this as any).isValidated = true;
}
}
Expand Down Expand Up @@ -119,36 +116,4 @@ export class XmlScope extends Scope {
}
return results;
}

/**
* Verify that all of the scripts ipmorted by
*/
private diagnosticValidateScriptImportPaths() {
//verify every script import
for (let scriptImport of this.xmlFile.scriptTagImports) {
let referencedFile = this.getFileByRelativePath(scriptImport.pkgPath);
//if we can't find the file
if (!referencedFile) {
let dInfo: DiagnosticInfo;
if (scriptImport.text.trim().length === 0) {
dInfo = DiagnosticMessages.scriptSrcCannotBeEmpty();
} else {
dInfo = DiagnosticMessages.referencedFileDoesNotExist();
}

this.diagnostics.push({
...dInfo,
range: scriptImport.filePathRange,
file: this.xmlFile
});
//if the character casing of the script import path does not match that of the actual path
} else if (scriptImport.pkgPath !== referencedFile.pkgPath) {
this.diagnostics.push({
...DiagnosticMessages.scriptImportCaseMismatch(referencedFile.pkgPath),
range: scriptImport.filePathRange,
file: this.xmlFile
});
}
}
}
}
2 changes: 1 addition & 1 deletion src/files/BrsFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export class BrsFile {
filePathRange: result.filePathToken.range,
pkgPath: util.getPkgPathFromTarget(this.pkgPath, result.filePath),
sourceFile: this,
text: ''
text: result.filePathToken?.text
});
}

Expand Down
Loading

0 comments on commit 4c0333c

Please sign in to comment.