Skip to content

Commit

Permalink
Class fixes (#33)
Browse files Browse the repository at this point in the history
* better class name handling in typedefs

* prefix typedef classes for extends

* prefix typedef class names for func return types

* fix lint issue.
  • Loading branch information
TwitchBronBron authored Feb 19, 2021
1 parent e88269a commit 6a87c8a
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 3 deletions.
62 changes: 59 additions & 3 deletions src/prefixer/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { buildAst } from '@xml-tools/ast';
import type { RopmOptions } from '../util';
import { util } from '../util';
import * as path from 'path';
import type { BrsFile, Position, Program, XmlFile } from 'brighterscript';
import { createVisitor, isCallExpression, isDottedGetExpression, isDottedSetStatement, isIndexedGetExpression, isIndexedSetStatement, WalkMode } from 'brighterscript';
import type { BrsFile, Position, Program, Range, XmlFile } from 'brighterscript';
import { ParseMode, createVisitor, isCallExpression, isCustomType, isDottedGetExpression, isDottedSetStatement, isIndexedGetExpression, isIndexedSetStatement, WalkMode, util as bsUtil } from 'brighterscript';

export class File {
constructor(
Expand Down Expand Up @@ -94,6 +94,15 @@ export class File {
endOffset: number;
}>;

/**
* Anywhere that a class is used as a type (like in class `extends` or function parameters)
*/
public classReferences = [] as Array<{
fullyQualifiedName: string;
offsetBegin: number;
offsetEnd: number;
}>;

public functionReferences = [] as Array<{
name: string;
offset: number;
Expand Down Expand Up @@ -231,12 +240,31 @@ export class File {
}
}

private addClassRef(className: string, containingNamespace: string | undefined, range: Range) {
//look up the class. If we can find it, use it
const cls = (this.bscFile as BrsFile).getClassFileLink(className, containingNamespace)?.item;

let fullyQualifiedName: string;
if (cls) {
fullyQualifiedName = bsUtil.getFullyQualifiedClassName(cls.getName(ParseMode.BrighterScript), cls.namespaceName?.getName(ParseMode.BrighterScript));
} else {
fullyQualifiedName = bsUtil.getFullyQualifiedClassName(className, containingNamespace);
}

this.classReferences.push({
fullyQualifiedName: fullyQualifiedName,
offsetBegin: this.positionToOffset(range.start),
offsetEnd: this.positionToOffset(range.end)
});
}

/**
* find various items from this file.
*/
public walkAst() {
const file = this.bscFile as BrsFile;
/* eslint-disable @typescript-eslint/naming-convention */
(this.bscFile as BrsFile).parser.ast.walk(createVisitor({
file.parser.ast.walk(createVisitor({
ImportStatement: (stmt) => {
//skip pkg paths, those are collected elsewhere
if (!stmt.filePath.startsWith('pkg:/')) {
Expand Down Expand Up @@ -283,6 +311,34 @@ export class File {
),
endOffset: this.positionToOffset(cls.end.range.end)
});

if (cls.parentClassName) {
this.addClassRef(
cls.parentClassName.getName(ParseMode.BrighterScript),
cls.namespaceName?.getName(ParseMode.BrighterScript),
cls.parentClassName.range
);
}
},
FunctionExpression: (func) => {
const namespaceName = func.namespaceName?.getName(ParseMode.BrighterScript);
//any parameters containing custom types
for (const param of func.parameters) {
if (isCustomType(param.type)) {
this.addClassRef(
param.type.name,
namespaceName,
param.typeToken!.range
);
}
}
if (isCustomType(func.returnType)) {
this.addClassRef(
func.returnType.name,
namespaceName,
func.returnTypeToken!.range
);
}
},
FunctionStatement: (func) => {
this.functionDefinitions.push({
Expand Down
159 changes: 159 additions & 0 deletions src/prefixer/ModuleManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,165 @@ describe('ModuleManager', () => {
});
});

it('prefixes classes used in parameters', async () => {
await testProcess({
'logger:source/lib.d.bs': [
trim`
namespace animals
class Dog
sub new(brother as Dog, sister as animals.Dog, owner as Human)
end sub
end class
end namespace
class Human
end class
`,
trim`
namespace logger.animals
class Dog
sub new(brother as logger.animals.Dog, sister as logger.animals.Dog, owner as logger.Human)
end sub
end class
end namespace
namespace logger
class Human
end class
end namespace
`
]
});
});

it('prefixes classes used in extends', async () => {
await testProcess({
'logger:source/lib.d.bs': [
trim`
namespace animals
class Animal
end class
class Dog extends Animal
end class
class Cat extends animals.Animal
end class
class Warewolf extends Human
end class
end namespace
class Human
end class
class Warecat extends animals.Cat
end class
`,
trim`
namespace logger.animals
class Animal
end class
class Dog extends logger.animals.Animal
end class
class Cat extends logger.animals.Animal
end class
class Warewolf extends logger.Human
end class
end namespace
namespace logger
class Human
end class
end namespace
namespace logger
class Warecat extends logger.animals.Cat
end class
end namespace
`
]
});
});

it('prefixes classes used as return type', async () => {
await testProcess({
'logger:source/lib.d.bs': [
trim`
namespace animals
class Dog
end class
function GetDog1() as Dog
end function
function GetDog2() as animals.Dog
end function
function GetHuman() as Human
end function
end namespace
function GetDog3() as animals.Dog
end function
class Human
end class
`,
trim`
namespace logger.animals
class Dog
end class
function GetDog1() as logger.animals.Dog
end function
function GetDog2() as logger.animals.Dog
end function
function GetHuman() as logger.Human
end function
end namespace
namespace logger
function GetDog3() as logger.animals.Dog
end function
end namespace
namespace logger
class Human
end class
end namespace
`
]
});
});

it('does not prefix other module namespaced class names', async () => {
manager.modules = createProjects(hostDir, hostDir, {
name: 'host',
dependencies: [{
name: 'logger',
_files: {
'source/lib.d.bs': trim`
class Person
sub new(pet as a.Duck)
end sub
sub watchPetForFriend(friendPet as dogs.Poodle)
end sub
end class
`
},
dependencies: [{
name: 'animals',
alias: 'a'
}, {
name: 'dogs'
}]
}]
});

await process();

fsEqual(`${hostDir}/source/roku_modules/logger/lib.d.bs`, `
namespace logger
class Person
sub new(pet as animals_v1.Duck)
end sub
sub watchPetForFriend(friendPet as dogs_v1.Poodle)
end sub
end class
end namespace
`);
});

it('properly handles annotations', async () => {
await testProcess({
'logger:source/lib.d.bs': [
Expand Down
31 changes: 31 additions & 0 deletions src/prefixer/RopmModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,21 @@ export class RopmModule {
const ownComponentNames = this.getDistinctComponentDeclarationNames();
const prefixMapKeys = Object.keys(this.prefixMap);
const prefixMapKeysLower = prefixMapKeys.map(x => x.toLowerCase());

/**
* Get the alias for a namespace. Only returns if it exists and is different than what is given.
*/
const getAlias = (namespace?: string) => {
if (namespace) {
const lowerNamespaceName = namespace.toLowerCase();
const idx = prefixMapKeysLower.indexOf(lowerNamespaceName);
const prefix = this.prefixMap[prefixMapKeys[idx]];
if (prefix && prefix.toLowerCase() !== lowerNamespaceName) {
return prefix;
}
}
};

const nonPrefixedFunctionMap = {
...this.nonPrefixedFunctionMap,
...this.getInterfaceFunctions()
Expand Down Expand Up @@ -362,6 +377,22 @@ export class RopmModule {
}
}

//prefix d.bs class references
for (const ref of file.classReferences) {
const baseNamespace = util.getBaseNamespace(ref.fullyQualifiedName);

const alias = getAlias(baseNamespace);
let fullyQualifiedName: string;
//if we have an alias, this is a class from another module.
if (alias) {
fullyQualifiedName = ref.fullyQualifiedName.replace(/^.*?\./, alias + '.');
} else {
//this is an internal-module class, so append our prefix to it
fullyQualifiedName = `${brighterscriptPrefix}.${ref.fullyQualifiedName}`;
}
file.addEdit(ref.offsetBegin, ref.offsetEnd, fullyQualifiedName);
}

//prefix d.bs namespaces
for (const namespace of file.namespaces) {
file.addEdit(namespace.offset, namespace.offset, brighterscriptPrefix + '.');
Expand Down
10 changes: 10 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,16 @@ export class Util {
}
}

/**
* Get the base namespace from a namespace statement, or undefined if there are no dots
*/
public getBaseNamespace(text: string) {
const parts = text.split('.');
if (parts.length > 1) {
return parts[0];
}
}

}

function mockProgramValidate() {
Expand Down

0 comments on commit 6a87c8a

Please sign in to comment.