Skip to content

Commit

Permalink
Added more class member compiler checks.
Browse files Browse the repository at this point in the history
  • Loading branch information
TwitchBronBron committed Apr 12, 2020
1 parent fbf6536 commit 7bf33ef
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 16 deletions.
11 changes: 8 additions & 3 deletions src/DiagnosticMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ export let DiagnosticMessages = {
code: 1026,
severity: DiagnosticSeverity.Error
}),
missingOverrideKeyword: (methodName: string, ancestorClassName: string) => ({
message: `Method '${methodName}' has no override keyword but is declared in ancestor class class '${ancestorClassName}'`,
missingOverrideKeyword: (ancestorClassName: string) => ({
message: `Method has no override keyword but is declared in ancestor class '${ancestorClassName}'`,
code: 1027,
severity: DiagnosticSeverity.Error
}),
Expand Down Expand Up @@ -506,9 +506,14 @@ export let DiagnosticMessages = {
severity: DiagnosticSeverity.Error
}),
memberAlreadyExistsInParentClass: (memberType: string, parentClassName: string) => ({
message: `A ${memberType} with this name already exists in inherited class "${parentClassName}"`,
message: `A ${memberType} with this name already exists in inherited class '${parentClassName}'`,
code: 1098,
severity: DiagnosticSeverity.Error
}),
classChildMemberDifferentMemberTypeThanAncestor: (memberType: string, parentMemberType: string, parentClassName: string) => ({
message: `Class member is a ${memberType} here but a ${parentMemberType} in ancestor class '${parentClassName}'`,
code: 1099,
severity: DiagnosticSeverity.Error
})
};

Expand Down
32 changes: 25 additions & 7 deletions src/files/BrsFile.Class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,37 +190,55 @@ describe('BrsFile BrighterScript classes', () => {
}]);
});

it.skip('detects overridden property name in child class', async () => {
it('detects mismatched member type in child class', async () => {
(await program.addOrReplaceFile({ src: `${rootDir}/source/main.brs`, dest: 'source/main.brs' }, `
class Animal
public name
end class
class Duck
class Duck extends Animal
public function name()
return "Donald"
end function
end class
`) as BrsFile);
await program.validate();
expect(
program.getDiagnostics().map(x => x.message).sort()[0]
).to.eql(
DiagnosticMessages.classChildMemberDifferentMemberTypeThanAncestor('method', 'field', 'Animal').message
);
});

it('detects overridden property name in child class', async () => {
(await program.addOrReplaceFile({ src: `${rootDir}/source/main.brs`, dest: 'source/main.brs' }, `
class Animal
public name
end class
class Duck extends Animal
public name
end class
`) as BrsFile);
await program.validate();
let diagnostics = program.getDiagnostics().map(x => x.message);
expect(diagnostics).to.eql([
DiagnosticMessages.memberAlreadyExistsInParentClass('property', 'Animal').message
DiagnosticMessages.memberAlreadyExistsInParentClass('field', 'Animal').message
]);
});

it.skip('detects overridden methods without override keyword', async () => {
it('detects overridden methods without override keyword', async () => {
(await program.addOrReplaceFile({ src: `${rootDir}/source/main.brs`, dest: 'source/main.brs' }, `
class Animal
sub speak()
end sub
end class
class Duck
class Duck extends Animal
sub speak()
end sub
end class
`) as BrsFile);
await program.validate();
expect(program.getDiagnostics()[0]).to.exist.and.to.include({
...DiagnosticMessages.missingOverrideKeyword('speak', 'Animal'),
location: Range.create(6, 20, 6, 25)
...DiagnosticMessages.missingOverrideKeyword('Animal')
});
});

Expand Down
64 changes: 58 additions & 6 deletions src/validators/ClassValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,48 @@ export class BsClassValidator {
});
}

//handle collisions in parent
let ancestorClass = classStatement.parentClass;
while (ancestorClass) {
//if the ancestor has the same named field as
if (ancestorClass.memberMap[lowerMemberName]) {
let memberType = member instanceof ClassFieldStatement ? 'field' : 'method';
let ancestorAndMember = this.getAncestorMember(classStatement, lowerMemberName);
if (ancestorAndMember) {
let ancestorMemberType = ancestorAndMember.member instanceof ClassFieldStatement ? 'field' : 'method';

//mismatched member type (field/method in child, opposite in parent)
if (memberType !== ancestorMemberType) {
this.diagnostics.push({
...DiagnosticMessages.classChildMemberDifferentMemberTypeThanAncestor(
memberType,
ancestorMemberType,
ancestorAndMember.classStatement.name.text
),
file: classStatement.file,
range: member.range
});
}

//child field has same name as parent
if (member instanceof ClassFieldStatement) {
this.diagnostics.push({
...DiagnosticMessages.memberAlreadyExistsInParentClass(
memberType,
ancestorAndMember.classStatement.name.text
),
file: classStatement.file,
range: member.range
});
}

//child method missing the override keyword
if (member instanceof ClassMethodStatement && !member.overrides) {
this.diagnostics.push({
...DiagnosticMessages.missingOverrideKeyword(
ancestorAndMember.classStatement.name.text
),
file: classStatement.file,
range: member.range
});
}
ancestorClass = classStatement.parentClass;
}

if (member instanceof ClassMethodStatement) {
methods[lowerMemberName] = member;

Expand All @@ -59,6 +93,24 @@ export class BsClassValidator {
}
}

/**
* Get the closest member with the specified name (case-insensitive)
*/
private getAncestorMember(classStatement: AugmentedClassStatement, memberName: string) {
let lowerMemberName = memberName.toLowerCase();
let ancestor = classStatement.parentClass;
while (ancestor) {
let member = ancestor.memberMap[lowerMemberName];
if (member) {
return {
member: member,
classStatement: ancestor
};
}
ancestor = classStatement.parentClass;
}
}

private cleanUp() {
//unlink all classes from their parents so it doesn't mess up the next scope
for (let key in this.classes) {
Expand Down

0 comments on commit 7bf33ef

Please sign in to comment.