Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No top level statements #638

Merged
merged 2 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions src/DiagnosticMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,11 @@ export let DiagnosticMessages = {
message: `Circular reference detected between ${Array.isArray(items) ? items.join(' -> ') : ''} in scope '${scopeName}'`,
code: 1132,
severity: DiagnosticSeverity.Error
}),
unexpectedStatementOutsideFunction: () => ({
message: `Unexpected statement found outside of function body`,
code: 1133,
severity: DiagnosticSeverity.Error
})
};

Expand Down
16 changes: 16 additions & 0 deletions src/Program.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ describe('Program', () => {
//if the program didn't get stuck in an infinite loop, this test passes
});

it('flags unsupported statements at root of file', () => {
program.setFile('source/main.brs', `
result = true
print true
createObject("roSGNode", "Rectangle")
`);
program.validate();
expectDiagnostics(program, [{
...DiagnosticMessages.unexpectedStatementOutsideFunction()
}, {
...DiagnosticMessages.unexpectedStatementOutsideFunction()
}, {
...DiagnosticMessages.unexpectedStatementOutsideFunction()
}]);
});

it('only parses xml files as components when file is found within the "components" folder', () => {
expect(Object.keys(program.files).length).to.equal(0);

Expand Down
34 changes: 32 additions & 2 deletions src/bscPlugin/validation/BrsFileValidator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isLiteralExpression } from '../..';
import { isClassStatement, isCommentStatement, isEnumStatement, isFunctionStatement, isImportStatement, isInterfaceStatement, isLibraryStatement, isLiteralExpression, isNamespaceStatement } from '../../astUtils/reflection';
import { DiagnosticMessages } from '../../DiagnosticMessages';
import type { BrsFile } from '../../files/BrsFile';
import type { BsDiagnostic, OnFileValidateEvent } from '../../interfaces';
Expand All @@ -14,9 +14,10 @@ export class BrsFileValidator {

public process() {
this.validateEnumDeclarations();
this.flagTopLevelStatements();
}

public validateEnumDeclarations() {
private validateEnumDeclarations() {
const diagnostics = [] as BsDiagnostic[];
for (const stmt of this.event.file.parser.references.enumStatements) {
const members = stmt.getMembers();
Expand Down Expand Up @@ -93,4 +94,33 @@ export class BrsFileValidator {
}
}
}

/**
* Find statements defined at the top level (or inside a namespace body) that are not allowed to be there
*/
private flagTopLevelStatements() {
const statements = [...this.event.file.ast.statements];
while (statements.length > 0) {
const statement = statements.pop();
if (isNamespaceStatement(statement)) {
statements.push(...statement.body.statements);
} else {
//only allow these statement types
if (
!isFunctionStatement(statement) &&
!isClassStatement(statement) &&
!isEnumStatement(statement) &&
!isInterfaceStatement(statement) &&
!isCommentStatement(statement) &&
!isLibraryStatement(statement) &&
!isImportStatement(statement)
) {
this.event.file.addDiagnostic({
...DiagnosticMessages.unexpectedStatementOutsideFunction(),
range: statement.range
});
}
}
}
}
}
141 changes: 77 additions & 64 deletions src/files/BrsFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2019,52 +2019,59 @@ describe('BrsFile', () => {

it('transpiles if statement keywords as provided', () => {
const code = `
If True Then
Print True
Else If True Then
print True
Else If False Then
Print False
Else
Print False
End If
sub main()
If True Then
Print True
Else If True Then
print True
Else If False Then
Print False
Else
Print False
End If
end sub
`;
testTranspile(code);
testTranspile(code.toLowerCase());
testTranspile(code.toUpperCase());
});

it('does not transpile `then` tokens', () => {
const code = `
if true
print true
else if true
print false
end if
`;
testTranspile(code);
testTranspile(`
sub main()
if true
print true
else if true
print false
end if
end sub
`);
});

it('honors spacing between multi-word tokens', () => {
testTranspile(`
if true
print true
elseif true
print false
endif
sub main()
if true
print true
elseif true
print false
endif
end sub
`);
});

it('handles when only some of the statements have `then`', () => {
testTranspile(`
if true
else if true then
else if true
else if true then
if true then
return true
sub main()
if true
else if true then
else if true
else if true then
if true then
return true
end if
end if
end if
end sub
`);
});

Expand Down Expand Up @@ -2467,51 +2474,57 @@ describe('BrsFile', () => {

it('handles empty if block', () => {
testTranspile(`
if true then
end if
if true then
else
print "else"
end if
if true then
else if true then
print "else"
end if
if true then
else if true then
print "elseif"
else
print "else"
end if
sub main()
if true then
end if
if true then
else
print "else"
end if
if true then
else if true then
print "else"
end if
if true then
else if true then
print "elseif"
else
print "else"
end if
end sub
`);
});

it('handles empty elseif block', () => {
testTranspile(`
if true then
print "if"
else if true then
end if
if true then
print "if"
else if true then
else if true then
end if
sub main()
if true then
print "if"
else if true then
end if
if true then
print "if"
else if true then
else if true then
end if
end sub
`);
});

it('handles empty else block', () => {
testTranspile(`
if true then
print "if"
else
end if
if true then
print "if"
else if true then
print "elseif"
else
end if
sub main()
if true then
print "if"
else
end if
if true then
print "if"
else if true then
print "elseif"
else
end if
end sub
`);
});

Expand Down
11 changes: 9 additions & 2 deletions src/files/BrsFile.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { CodeWithSourceMap } from 'source-map';
import { SourceNode } from 'source-map';
import type { CompletionItem, Hover, Position, Location } from 'vscode-languageserver';
import type { CompletionItem, Hover, Position, Location, Diagnostic } 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';
import { DiagnosticCodeMap, diagnosticCodes, DiagnosticMessages } from '../DiagnosticMessages';
import { FunctionScope } from '../FunctionScope';
import type { Callable, CallableArg, CallableParam, CommentFlag, FunctionCall, BsDiagnostic, FileReference, FileLink } from '../interfaces';
import type { Callable, CallableArg, CallableParam, CommentFlag, FunctionCall, BsDiagnostic, FileReference, FileLink, BscFile } from '../interfaces';
import type { Token } from '../lexer/Token';
import { Lexer } from '../lexer/Lexer';
import { TokenKind, AllowedLocalIdentifiers, Keywords } from '../lexer/TokenKind';
Expand Down Expand Up @@ -101,6 +101,13 @@ export class BrsFile {
return [...this.diagnostics];
}

public addDiagnostic(diagnostic: Diagnostic & { file?: BscFile }) {
if (!diagnostic.file) {
diagnostic.file = this;
}
this.diagnostics.push(diagnostic as any);
}

public addDiagnostics(diagnostics: BsDiagnostic[]) {
this.diagnostics.push(...diagnostics);
}
Expand Down
38 changes: 20 additions & 18 deletions src/files/tests/optionalChaning.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,24 +73,26 @@ describe('optional chaining', () => {

it('transpiles various use cases', () => {
testTranspile(`
obj = {}
arr = []
print arr?.["0"]
print arr?.value
print obj?.[0]
print obj?.getName()?.first?.second
print createObject("roByteArray")?.value
print createObject("roByteArray")?["0"]
print createObject("roList")?.value
print createObject("roList")?["0"]
print createObject("roXmlList")?["0"]
print createObject("roDateTime")?.value
print createObject("roDateTime")?.GetTimeZoneOffset
print createObject("roSGNode", "Node")?[0]
print obj?.first?.second
print obj?.first?.second
print obj.b.xmlThing?@someAttr
print obj.b.localFunc?()
sub main()
obj = {}
arr = []
print arr?.["0"]
print arr?.value
print obj?.[0]
print obj?.getName()?.first?.second
print createObject("roByteArray")?.value
print createObject("roByteArray")?["0"]
print createObject("roList")?.value
print createObject("roList")?["0"]
print createObject("roXmlList")?["0"]
print createObject("roDateTime")?.value
print createObject("roDateTime")?.GetTimeZoneOffset
print createObject("roSGNode", "Node")?[0]
print obj?.first?.second
print obj?.first?.second
print obj.b.xmlThing?@someAttr
print obj.b.localFunc?()
end sub
`);
});
});
Loading