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

Support for type definition files #188

Merged
merged 41 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
14d66d6
Support loading d.bs files during file parse.
TwitchBronBron Sep 23, 2020
b60b15f
remove .only, fix ts6133 issue.
TwitchBronBron Sep 23, 2020
eeff9d6
Fix debugging issue.
TwitchBronBron Oct 7, 2020
c6eeb51
Fix broken tests.
TwitchBronBron Oct 7, 2020
069b5e3
Merge branch 'master' into type-definitions
TwitchBronBron Oct 7, 2020
fe52aa2
Preload typedefs during first run.
TwitchBronBron Oct 9, 2020
c3fe922
Renamed TypeDefinition to `typedef` everywhere.
TwitchBronBron Oct 9, 2020
b0498b5
Basic typedef generation (classes not implemented yet)
TwitchBronBron Oct 9, 2020
4be26ae
Fix ts bulid error
TwitchBronBron Oct 9, 2020
19adfb8
Add typdef support for classes.
TwitchBronBron Oct 9, 2020
c4c99ad
Make typedefCache private and standardize the key
TwitchBronBron Oct 9, 2020
84e5e98
Split d.bs file into own file in file map
TwitchBronBron Oct 9, 2020
6b7ba1f
Add generic type param for addReplaceFile.
TwitchBronBron Oct 10, 2020
4409740
Merge branch 'generic-addOrReplaceFile' into type-definitions
TwitchBronBron Oct 10, 2020
db1b9de
Add some unit tests.
TwitchBronBron Oct 10, 2020
297eb5a
Merge branch 'master' into type-definitions
TwitchBronBron Oct 13, 2020
c36e344
Merge branch 'master' into type-definitions
TwitchBronBron Oct 13, 2020
c3cb8bf
fixing bugs
TwitchBronBron Oct 14, 2020
6a2765e
Merge branch 'master' into type-definitions
TwitchBronBron Oct 16, 2020
de2c1d3
fix lint issue
TwitchBronBron Oct 16, 2020
6560fab
Merge branch 'master' into type-definitions
TwitchBronBron Oct 16, 2020
79590e4
Fixed typedefs not knowing they are typedefs
TwitchBronBron Oct 16, 2020
2a8fa83
hide some debug logs
TwitchBronBron Oct 17, 2020
926d7f6
Merge branch 'master' into type-definitions
TwitchBronBron Oct 17, 2020
2baa6fc
add test to verify typings don't mess with script tags
TwitchBronBron Oct 17, 2020
3f543ee
Merge branch 'master' into type-definitions
TwitchBronBron Oct 21, 2020
a56eca9
Remove extraneous log statement
TwitchBronBron Oct 23, 2020
e7997dd
brs file react to d.bs changes
TwitchBronBron Oct 23, 2020
9d0c819
fix class inheritance for typedefs
TwitchBronBron Oct 26, 2020
ba38aba
remove .only
TwitchBronBron Oct 26, 2020
2977a35
Fix parseMode bug for `thing.spec.bs`
TwitchBronBron Oct 27, 2020
21a1ef9
Fix typedef namespace spec
TwitchBronBron Oct 27, 2020
2a674b7
add `emitDefinitions` to bsconfig schema
TwitchBronBron Oct 27, 2020
ba997ad
Adressed PR concerns.
TwitchBronBron Oct 28, 2020
388d0b4
Merge branch 'master' into type-definitions
TwitchBronBron Oct 28, 2020
d577c39
Address PR concerns
TwitchBronBron Oct 29, 2020
6913096
remove cache from brsfile
TwitchBronBron Oct 29, 2020
a1f4240
Removed unnecessary test
TwitchBronBron Oct 29, 2020
a533a03
typedef import statements should be .brs extension
TwitchBronBron Oct 30, 2020
cd18a36
Address PR comments
TwitchBronBron Oct 30, 2020
d943f7d
remove unused method
TwitchBronBron Oct 30, 2020
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: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ module.exports = {
'func-style': 'off',
'function-call-argument-newline': 'off',
'function-paren-newline': 'off',
'getter-return': 'off',
'guard-for-in': 'off',
'id-length': 'off',
'indent': 'off',
Expand Down
9 changes: 7 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"type": "node",
"request": "launch",
// "preLaunchTask": "build",
"smartStep": false,
"smartStep": true,
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"sourceMaps": true,
"args": [
Expand All @@ -36,7 +36,12 @@
],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
"internalConsoleOptions": "openOnSessionStart",
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/typescript/**",
"!**/node_modules/vscode-languageserver/**"
]
}
]
}
5 changes: 5 additions & 0 deletions bsconfig.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@
"type": "boolean",
"default": false
},
"emitDefinitions": {
"description": "Emit type definition files (`d.bs`) during transpile",
"type": "boolean",
"default": true
},
"diagnosticFilters": {
"description": "A collection of filters used to hide diagnostics for certain files",
"type": "array",
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
"moment": "^2.23.0",
"p-settle": "^2.1.0",
"parse-ms": "^2.1.0",
"path-complete-extname": "^1.0.0",
"roku-deploy": "^3.2.3",
"serialize-error": "^7.0.1",
"source-map": "^0.7.3",
Expand Down
6 changes: 6 additions & 0 deletions src/BsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ export interface BsConfig {
*/
emitFullPaths?: boolean;

/**
* Emit type definition files (`d.bs`)
* @default true
*/
emitDefinitions?: boolean;

/**
* A list of filters used to exclude diagnostics from the output
*/
Expand Down
1 change: 0 additions & 1 deletion src/LanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,6 @@ export class LanguageServer {
this.connection.tracer.log(e);
this.sendCriticalFailure(`Critical error parsing/ validating ${filePath}: ${e.message}`);
}
console.log('Validate done');
}

private async validateAll() {
Expand Down
10 changes: 7 additions & 3 deletions src/Program.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ describe('Program', () => {

//resolve lib.brs from memory instead of going to disk
program.fileResolvers.push((pathAbsolute) => {
if (pathAbsolute === s`${rootDir}/source/lib.brs`) {
if (
pathAbsolute === s`${rootDir}/source/lib.brs` ||
pathAbsolute === s`${rootDir}/source/lib.d.bs`
) {
return `'comment`;
}
});
Expand Down Expand Up @@ -736,7 +739,7 @@ describe('Program', () => {

describe('getCompletions', () => {
it('should include first-level namespace names for brighterscript files', async () => {
await program.addOrReplaceFile({ src: `${rootDir}/source/main.bs`, dest: 'source/main.brs' }, `
await program.addOrReplaceFile('source/main.bs', `
namespace NameA.NameB.NameC
sub DoSomething()
end sub
Expand All @@ -754,7 +757,7 @@ describe('Program', () => {
});

it('resolves completions for namespaces with next namespace part for brighterscript file', async () => {
await program.addOrReplaceFile({ src: `${rootDir}/source/main.bs`, dest: 'source/main.brs' }, `
const file = await program.addOrReplaceFile({ src: `${rootDir}/source/main.bs`, dest: 'source/main.brs' }, `
namespace NameA.NameB.NameC
sub DoSomething()
end sub
Expand All @@ -763,6 +766,7 @@ describe('Program', () => {
NameA.
end sub
`);
await file.isReady();
let completions = (await program.getCompletions(`${rootDir}/source/main.bs`, Position.create(6, 26))).map(x => x.label);
expect(completions).to.include('NameB');
expect(completions).not.to.include('NameA');
Expand Down
65 changes: 37 additions & 28 deletions src/Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import type { ManifestValue } from './preprocessor/Manifest';
import { parseManifest } from './preprocessor/Manifest';
import { URI } from 'vscode-uri';
import PluginInterface from './PluginInterface';
import { isXmlFile } from './astUtils/reflection';
import { isBrsFile, isXmlFile } from './astUtils/reflection';
const startOfSourcePkgPath = `source${path.sep}`;

export interface SourceObj {
pathAbsolute: string;
source: string;
definitions?: string;
}

export interface TranspileObj {
Expand Down Expand Up @@ -111,7 +112,7 @@ export class Program {
private diagnostics = [] as BsDiagnostic[];

/**
* A map of every file loaded into this program
* A map of every file loaded into this program, indexed by its original file location
*/
public files = {} as Record<string, BscFile>;
private pkgMap = {} as Record<string, BscFile>;
Expand Down Expand Up @@ -297,63 +298,61 @@ export class Program {
public async addOrReplaceFile<T extends BscFile>(fileEntry: FileObj, fileContents?: string): Promise<T>;
public async addOrReplaceFile<T extends BscFile>(fileParam: FileObj | string, fileContents?: string): Promise<T> {
assert.ok(fileParam, 'fileEntry is required');
let pathAbsolute: string;
let srcPath: string;
let pkgPath: string;
if (typeof fileParam === 'string') {
pathAbsolute = s`${this.options.rootDir}/${fileParam}`;
srcPath = s`${this.options.rootDir}/${fileParam}`;
pkgPath = s`${fileParam}`;
} else {
pathAbsolute = s`${fileParam.src}`;
srcPath = s`${fileParam.src}`;
pkgPath = s`${fileParam.dest}`;
}
let file = await this.logger.time(LogLevel.debug, ['Program.addOrReplaceFile()', chalk.green(pathAbsolute)], async () => {
let file = await this.logger.time(LogLevel.debug, ['Program.addOrReplaceFile()', chalk.green(srcPath)], async () => {

assert.ok(pathAbsolute, 'fileEntry.src is required');
assert.ok(srcPath, 'fileEntry.src is required');
assert.ok(pkgPath, 'fileEntry.dest is required');

//if the file is already loaded, remove it
if (this.hasFile(pathAbsolute)) {
this.removeFile(pathAbsolute);
if (this.hasFile(srcPath)) {
this.removeFile(srcPath);
}
let fileExtension = path.extname(pathAbsolute).toLowerCase();
let fileExtension = path.extname(srcPath).toLowerCase();
let file: BscFile | undefined;

//load the file contents by file path if not provided
let getFileContents = async () => {
if (fileContents === undefined) {
return this.getFileContents(pathAbsolute);
return this.getFileContents(srcPath);
} else {
return fileContents;
}
};
//get the extension of the file

if (fileExtension === '.brs' || fileExtension === '.bs') {
let brsFile = new BrsFile(pathAbsolute, pkgPath, this);
let brsFile = new BrsFile(srcPath, pkgPath, this);

//add file to the `source` dependency list
if (brsFile.pkgPath.startsWith(startOfSourcePkgPath)) {
this.createSourceScope();
this.dependencyGraph.addDependency('scope:source', brsFile.dependencyGraphKey);
}


//add the file to the program
this.files[pathAbsolute] = brsFile;
this.files[srcPath] = brsFile;
this.pkgMap[brsFile.pkgPath.toLowerCase()] = brsFile;
let fileContents: SourceObj = {
pathAbsolute: pathAbsolute,
pathAbsolute: srcPath,
source: await getFileContents()
};
this.plugins.emit('beforeFileParse', fileContents);

this.logger.time(LogLevel.info, ['parse', chalk.green(pathAbsolute)], () => {
this.logger.time(LogLevel.info, ['parse', chalk.green(srcPath)], () => {
brsFile.parse(fileContents.source);
});
file = brsFile;

this.dependencyGraph.addOrReplace(
brsFile.dependencyGraphKey,
brsFile.ownScriptImports.filter(x => !!x.pkgPath).map(x => x.pkgPath.toLowerCase())
);
brsFile.attachDependencyGraph(this.dependencyGraph);

this.plugins.emit('afterFileValidate', brsFile);
} else if (
Expand All @@ -362,11 +361,11 @@ export class Program {
//resides in the components folder (Roku will only parse xml files in the components folder)
pkgPath.toLowerCase().startsWith(util.pathSepNormalize(`components/`))
) {
let xmlFile = new XmlFile(pathAbsolute, pkgPath, this);
let xmlFile = new XmlFile(srcPath, pkgPath, this);
//add the file to the program
this.files[pathAbsolute] = xmlFile;
this.files[srcPath] = xmlFile;
let fileContents: SourceObj = {
pathAbsolute: pathAbsolute,
pathAbsolute: srcPath,
source: await getFileContents()
};
this.plugins.emit('beforeFileParse', fileContents);
Expand Down Expand Up @@ -418,11 +417,11 @@ export class Program {
* with the same path with only case being different.
* @param pathAbsolute
*/
public getFileByPathAbsolute(pathAbsolute: string) {
public getFileByPathAbsolute<T extends BrsFile | XmlFile>(pathAbsolute: string) {
pathAbsolute = s`${pathAbsolute}`;
for (let filePath in this.files) {
if (filePath.toLowerCase() === pathAbsolute.toLowerCase()) {
return this.files[filePath];
return this.files[filePath] as T;
}
}
}
Expand Down Expand Up @@ -460,7 +459,10 @@ export class Program {
* @param pathAbsolute
*/
public removeFile(pathAbsolute: string) {
pathAbsolute = s`${pathAbsolute}`;
if (!path.isAbsolute(pathAbsolute)) {
throw new Error(`Path must be absolute: "${pathAbsolute}"`);
}

let file = this.getFile(pathAbsolute);
if (file) {
this.plugins.emit('beforeFileDispose', file);
Expand All @@ -476,7 +478,7 @@ export class Program {
this.plugins.emit('afterScopeDispose', scope);
}
//remove the file from the program
delete this.files[pathAbsolute];
delete this.files[file.pathAbsolute];
delete this.pkgMap[file.pkgPath.toLowerCase()];

this.dependencyGraph.remove(file.dependencyGraphKey);
Expand Down Expand Up @@ -792,6 +794,13 @@ export class Program {
fsExtra.writeFile(outputPath, result.code),
writeMapPromise
]);

if (isBrsFile(file) && this.options.emitDefinitions !== false) {
const typedef = file.getTypedef();
const typedefPath = outputPath.replace(/\.brs$/i, '.d.bs');
await fsExtra.writeFile(typedefPath, typedef);
}

this.plugins.emit('afterFileTranspile', entry);
});

Expand Down Expand Up @@ -843,4 +852,4 @@ export class Program {
}
}

export type FileResolver = (pathAbsolute: string) => string | undefined | Thenable<string | undefined>;
export type FileResolver = (pathAbsolute: string) => string | undefined | Thenable<string | undefined> | void;
63 changes: 48 additions & 15 deletions src/ProgramBuilder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import { Range } from '.';
import { DiagnosticSeverity } from './astUtils';
import { BrsFile } from './files/BrsFile';

let tmpPath = s`${process.cwd()}/.tmp`;
let rootDir = s`${tmpPath}/rootDir`;
let stagingFolderPath = s`${tmpPath}/staging`;

describe('ProgramBuilder', () => {

let tmpPath = s`${process.cwd()}/.tmp`;
let rootDir = s`${tmpPath}/rootDir`;
let stagingFolderPath = s`${tmpPath}/staging`;

beforeEach(() => {
fsExtra.ensureDirSync(tmpPath);
fsExtra.ensureDirSync(rootDir);
fsExtra.emptyDirSync(tmpPath);
});
afterEach(() => {
Expand All @@ -28,13 +29,13 @@ describe('ProgramBuilder', () => {
});

let builder: ProgramBuilder;
let b: any;
beforeEach(async () => {
builder = new ProgramBuilder();
b = builder;
b.options = await util.normalizeAndResolveConfig(undefined);
b.program = new Program(b.options);
b.logger = new Logger();
builder.options = await util.normalizeAndResolveConfig({
rootDir: rootDir
});
builder.program = new Program(builder.options);
builder.logger = new Logger();
});

afterEach(() => {
Expand All @@ -54,13 +55,45 @@ describe('ProgramBuilder', () => {
dest: 'file.xml'
}]));

b.program = {
addOrReplaceFile: () => { }
};
let stub = sinon.stub(b.program, 'addOrReplaceFile');
await b.loadAllFilesAST();
let stub = sinon.stub(builder.program, 'addOrReplaceFile');
await builder['loadAllFilesAST']();
expect(stub.getCalls()).to.be.lengthOf(3);
});

it('loads all type definitions first', async () => {
const requestedFiles = [] as string[];
builder.program.fileResolvers.push((filePath) => {
requestedFiles.push(s(filePath));
});
fsExtra.outputFileSync(s`${rootDir}/source/main.brs`, '');
fsExtra.outputFileSync(s`${rootDir}/source/main.d.bs`, '');
fsExtra.outputFileSync(s`${rootDir}/source/lib.d.bs`, '');
fsExtra.outputFileSync(s`${rootDir}/source/lib.brs`, '');
const stub = sinon.stub(builder.program, 'addOrReplaceFile');
await builder['loadAllFilesAST']();
const srcPaths = stub.getCalls().map(x => x.args[0].src);
//the d files should be first
expect(srcPaths.indexOf(s`${rootDir}/source/main.d.bs`)).within(0, 1);
expect(srcPaths.indexOf(s`${rootDir}/source/lib.d.bs`)).within(0, 1);
//the non-d files should be last
expect(srcPaths.indexOf(s`${rootDir}/source/main.brs`)).within(2, 3);
expect(srcPaths.indexOf(s`${rootDir}/source/lib.brs`)).within(2, 3);

//the d files should NOT be requested from the FS
expect(requestedFiles).not.to.include(s`${rootDir}/source/lib.d.bs`);
expect(requestedFiles).not.to.include(s`${rootDir}/source/main.d.bs`);
});

it('does not load non-existent type definition file', async () => {
const requestedFiles = [] as string[];
builder.program.fileResolvers.push((filePath) => {
requestedFiles.push(s(filePath));
});
fsExtra.outputFileSync(s`${rootDir}/source/main.brs`, '');
await builder['loadAllFilesAST']();
//the d file should not be requested because `loadAllFilesAST` knows it doesn't exist
expect(requestedFiles).not.to.include(s`${rootDir}/source/main.d.bs`);
});
});

describe('run', () => {
Expand Down
Loading