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

Feat/enum completion #485

Merged
merged 25 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f7dd5bd
adds completions for enum types
georgejecook Jan 15, 2022
a657ccc
add tests to prove there are no enum member collisions
georgejecook Jan 15, 2022
2ce9139
Merge branch 'feat/enum' into feat/enum-completion
georgejecook Jan 15, 2022
d00fae9
Formatting fixes
TwitchBronBron Jan 16, 2022
8a11418
small var naming tweaks
TwitchBronBron Jan 16, 2022
4f98afd
remove unnecessary eslint disable.
TwitchBronBron Jan 16, 2022
73a626b
clean up test a little
TwitchBronBron Jan 16, 2022
5fd98b8
adds validation for enums
georgejecook Jan 16, 2022
6d208c5
Merge branch 'feat/enum-completion' of github.com:rokucommunity/brigh…
georgejecook Jan 16, 2022
9f6bdec
Merge branch 'feat/enum-completion' into feat/enum-validation
georgejecook Jan 16, 2022
4120166
adds completions for enum types
georgejecook Jan 15, 2022
1c9a819
add tests to prove there are no enum member collisions
georgejecook Jan 15, 2022
3e5459c
Formatting fixes
TwitchBronBron Jan 16, 2022
cd84ac8
small var naming tweaks
TwitchBronBron Jan 16, 2022
7b43c03
remove unnecessary eslint disable.
TwitchBronBron Jan 16, 2022
bf76c71
clean up test a little
TwitchBronBron Jan 16, 2022
6a98859
changes namespace.enumStatements to map
georgejecook Jan 16, 2022
775bcdd
Merge branch 'feat/enum' into feat/enum-completion
georgejecook Jan 16, 2022
35bf2ac
changes namespace.enumStatements to map
georgejecook Jan 16, 2022
333e0ab
Merge branch 'feat/enum-validation' into feat/enum-completion
georgejecook Jan 16, 2022
e40710c
fixes broken imports
georgejecook Jan 16, 2022
83495db
uses visitor to ascertain dotted get statements
georgejecook Jan 16, 2022
2619720
fix circular ref issues.
TwitchBronBron Jan 16, 2022
db76662
Merge branch 'feat/enum' of https://github.com/rokucommunity/brighter…
TwitchBronBron Jan 25, 2022
996836e
clean up some tests
TwitchBronBron Feb 1, 2022
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
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/DiagnosticMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,13 @@ export let DiagnosticMessages = {
message: `Value is required for ${expectedType} enum`,
code: 1125,
severity: DiagnosticSeverity.Error
}),
unknownEnumValue: (name: string, enumName: string) => ({
message: `Enum value ${name} is not found in enum ${enumName}`,
code: 1126,
severity: DiagnosticSeverity.Error
})

};

export const DiagnosticCodeMap = {} as Record<keyof (typeof DiagnosticMessages), number>;
Expand Down
109 changes: 109 additions & 0 deletions src/Scope.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,4 +845,113 @@ describe('Scope', () => {
program['scopes']['source'].buildNamespaceLookup();
});
});

describe('buildEnumLookup', () => {

it('builds enum lookup', () => {
const sourceScope = program.getScopeByName('source');
//eslint-disable-next-line @typescript-eslint/no-floating-promises
program.addOrReplaceFile('source/main.bs', `
enum foo
bar1
bar2
end enum

namespace test
function fooFace2()
end function

class fooClass2
end class

enum foo2
bar2_1
bar2_2
end enum
end namespace

function fooFace()
end function

class fooClass
end class

enum foo3
bar3_1
bar3_2
end enum
`);
// program.validate();
let lookup = sourceScope.enumLookup;

expect(
[...lookup.keys()]
).to.eql([
'foo',
'foo.bar1',
'foo.bar2',
'test.foo2',
'test.foo2.bar2_1',
'test.foo2.bar2_2',
'foo3',
'foo3.bar3_1',
'foo3.bar3_2'
]);
});
});
describe('enums', () => {
it('gets enum completions', () => {
//eslint-disable-next-line @typescript-eslint/no-floating-promises
program.addOrReplaceFile('source/main.bs', `
enum foo
bar1
bar2
end enum

sub Main()
g1 = foo.bar1
g2 = test.foo2.bar2_1
g3 = test.foo2.bar2_1
g4 = test.nested.foo3.bar3_1
b1 = foo.bad1
b2 = test.foo2.bad2
b4 = test.nested.foo3.bad3
'unknown namespace
b3 = test.foo3.bar3_1
end sub

namespace test
function fooFace2()
end function

class fooClass2
end class

enum foo2
bar2_1
bar2_2
end enum
end namespace

function fooFace()
end function

class fooClass
end class

namespace test.nested
enum foo3
bar3_1
bar3_2
end enum
end namespace
`);
program.validate();
expectDiagnostics(program, [
DiagnosticMessages.unknownEnumValue('bad1', 'foo'),
DiagnosticMessages.unknownEnumValue('bad2', 'test.foo2'),
DiagnosticMessages.unknownEnumValue('bad3', 'test.nested.foo3')
]);
});
});
});
119 changes: 115 additions & 4 deletions src/Scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ import { DiagnosticMessages } from './DiagnosticMessages';
import type { CallableContainer, BsDiagnostic, FileReference, BscFile, CallableContainerMap } from './interfaces';
import type { FileLink, Program } from './Program';
import { BsClassValidator } from './validators/ClassValidator';
import type { NamespaceStatement, Statement, FunctionStatement, ClassStatement } from './parser/Statement';
import type { NewExpression } from './parser/Expression';
import type { NamespaceStatement, Statement, FunctionStatement, ClassStatement, EnumStatement } from './parser/Statement';
import type { DottedGetExpression, NewExpression } from './parser/Expression';
import { ParseMode } from './parser/Parser';
import { standardizePath as s, util } from './util';
import { globalCallableMap } from './globalCallables';
import { Cache } from './Cache';
import { URI } from 'vscode-uri';
import { LogLevel } from './Logger';
import { isBrsFile, isClassStatement, isFunctionStatement, isFunctionType, isXmlFile, isCustomType, isClassMethodStatement } from './astUtils/reflection';
import type { BrsFile } from './files/BrsFile';
import type { DependencyGraph, DependencyChangedEvent } from './DependencyGraph';
import { isBrsFile, isClassMethodStatement, isClassStatement, isCustomType, isDottedGetExpression, isEnumStatement, isFunctionStatement, isFunctionType, isVariableExpression, isXmlFile } from './astUtils/reflection';
import { createVisitor, WalkMode } from './astUtils/visitors';

/**
* A class to keep track of all declarations within a given scope (like source scope, component scope)
Expand Down Expand Up @@ -53,6 +54,12 @@ export class Scope {
public get namespaceLookup() {
return this.cache.getOrAdd('namespaceLookup', () => this.buildNamespaceLookup());
}
/**
* A dictionary of enums, indexed by the lower case full name of each enum.
*/
public get enumLookup() {
return this.cache.getOrAdd('enumLookup', () => this.buildEnumLookup());
}

/**
* Get the class with the specified name.
Expand Down Expand Up @@ -113,6 +120,28 @@ export class Scope {
});
}

/**
* A dictionary of all enums in this scope. This includes namespaced enums always with their full name.
* The key is stored in lower case
*/
public getEnumMap(): Map<string, FileLink<EnumStatement>> {
return this.cache.getOrAdd('enumMap', () => {
const map = new Map<string, FileLink<EnumStatement>>();
this.enumerateBrsFiles((file) => {
if (isBrsFile(file)) {
for (let enumStmt of file.parser.references.enumStatements) {
const lowerEnumName = enumStmt.fullName.toLowerCase();
//only track enums with a defined name (i.e. exclude nameless malformed enums)
if (lowerEnumName) {
map.set(lowerEnumName, { item: enumStmt, file: file });
}
}
}
});
return map;
});
}

/**
* The list of diagnostics found specifically for this scope. Individual file diagnostics are stored on the files themselves.
*/
Expand Down Expand Up @@ -360,7 +389,8 @@ export class Scope {
namespaces: new Map<string, NamespaceContainer>(),
classStatements: {},
functionStatements: {},
statements: []
statements: [],
enumStatements: new Map<string, EnumStatement>()
});
}
}
Expand All @@ -371,6 +401,8 @@ export class Scope {
ns.classStatements[statement.name.text.toLowerCase()] = statement;
} else if (isFunctionStatement(statement) && statement.name) {
ns.functionStatements[statement.name.text.toLowerCase()] = statement;
} else if (isEnumStatement(statement) && statement.fullName) {
ns.enumStatements.set(statement.fullName.toLowerCase(), statement);
}
}
}
Expand All @@ -391,6 +423,35 @@ export class Scope {
return namespaceLookup;
}

public buildEnumLookup() {
let lookup = new Map<string, EnumContainer>();
this.enumerateBrsFiles((file) => {
for (let [key, es] of file.parser.references.enumStatementLookup) {
if (!lookup.has(key)) {
lookup.set(key, {
file: file,
fullName: key,
nameRange: es.range,
lastPartName: es.name,
statement: es
});
for (const ems of es.getMembers()) {
const fullMemberName = `${key}.${ems.name.toLowerCase()}`;
lookup.set(fullMemberName, {
file: file,
fullName: fullMemberName,
nameRange: ems.range,
lastPartName: ems.name,
statement: es
});
}
}
}
});
return lookup;
}


public getAllNamespaceStatements() {
let result = [] as NamespaceStatement[];
this.enumerateBrsFiles((file) => {
Expand Down Expand Up @@ -467,6 +528,7 @@ export class Scope {
this.diagnosticDetectFunctionCollisions(file);
this.detectVariableNamespaceCollisions(file);
this.diagnosticDetectInvalidFunctionExpressionTypes(file);
this.detectUnknownEnumMembers(file);
});
}

Expand Down Expand Up @@ -1001,6 +1063,46 @@ export class Scope {
}
return items;
}

private detectUnknownEnumMembers(file: BrsFile) {
if (!isBrsFile(file)) {
return;
}
file.parser.ast.walk(createVisitor({
DottedGetExpression: (dge) => {
let nameParts = this.getAllDottedGetParts(dge);
let name = nameParts.pop();
let parentPath = nameParts.join('.');
let ec = this.enumLookup.get(parentPath);
if (ec && !this.enumLookup.has(`${parentPath}.${name}`)) {
this.diagnostics.push({
file: file,
...DiagnosticMessages.unknownEnumValue(name, ec.fullName),
range: dge.range,
relatedInformation: [{
message: 'Enum declared here',
location: Location.create(
URI.file(ec.file.pathAbsolute).toString(),
ec.statement.range
)
}]
});

}
}
}), { walkMode: WalkMode.visitAllRecursive });
}

private getAllDottedGetParts(dg: DottedGetExpression) {
let parts = [dg?.name?.text];
let nextPart = dg.obj;
while (isDottedGetExpression(nextPart) || isVariableExpression(nextPart)) {
parts.push(nextPart?.name?.text);
nextPart = isDottedGetExpression(nextPart) ? nextPart.obj : undefined;
}
return parts.reverse();
}

}

interface NamespaceContainer {
Expand All @@ -1011,9 +1113,18 @@ interface NamespaceContainer {
statements: Statement[];
classStatements: Record<string, ClassStatement>;
functionStatements: Record<string, FunctionStatement>;
enumStatements: Map<string, EnumStatement>;
namespaces: Map<string, NamespaceContainer>;
}

interface EnumContainer {
file: BscFile;
fullName: string;
nameRange: Range;
lastPartName: string;
statement: EnumStatement;
}

interface AugmentedNewExpression extends NewExpression {
file: BscFile;
}
Loading