diff --git a/src/Program.spec.ts b/src/Program.spec.ts index 08f49eac2..831800787 100644 --- a/src/Program.spec.ts +++ b/src/Program.spec.ts @@ -1717,6 +1717,51 @@ describe('Program', () => { describe('transpile', () => { + it('detects and transpiles files added between beforeProgramTranspile and afterProgramTranspile', async () => { + program.setFile('source/main.bs', trim` + sub main() + print "hello world" + end sub + `); + program.plugins.add({ + name: 'TestPlugin', + beforeFileTranspile: (event) => { + if (isBrsFile(event.file)) { + //add lib1 + if (event.outputPath.endsWith('main.brs')) { + event.program.setFile('source/lib1.bs', ` + sub lib1() + end sub + `); + } + //add lib2 (this should happen during the next cycle of "catch missing files" cycle + if (event.outputPath.endsWith('main.brs')) { + //add another file + event.program.setFile('source/lib2.bs', ` + sub lib2() + end sub + `); + } + } + } + }); + await program.transpile([], stagingFolderPath); + //our new files should exist + expect( + fsExtra.readFileSync(`${stagingFolderPath}/source/lib1.brs`).toString() + ).to.eql(trim` + sub lib1() + end sub + `); + //our changes should be there + expect( + fsExtra.readFileSync(`${stagingFolderPath}/source/lib2.brs`).toString() + ).to.eql(trim` + sub lib2() + end sub + `); + }); + it('sets needsTranspiled=true when there is at least one edit', async () => { program.setFile('source/main.brs', trim` sub main() diff --git a/src/Program.ts b/src/Program.ts index 088ca7d5d..d0d9788c1 100644 --- a/src/Program.ts +++ b/src/Program.ts @@ -1267,6 +1267,7 @@ export class Program { const editor = new AstEditor(); this.plugins.emit('beforeFileTranspile', { + program: this, file: file, outputPath: outputPath, editor: editor @@ -1288,6 +1289,7 @@ export class Program { } const event: AfterFileTranspileEvent = { + program: this, file: file, outputPath: outputPath, editor: editor, @@ -1316,7 +1318,7 @@ export class Program { return collection; }, {}); - const entries = Object.values(this.files).map(file => { + const getOutputPath = (file: BscFile) => { let filePathObj = mappedFileEntries[s`${file.pathAbsolute}`]; if (!filePathObj) { //this file has been added in-memory, from a plugin, for example @@ -1330,24 +1332,20 @@ export class Program { let outputPath = filePathObj.dest.replace(/\.bs$/gi, '.brs'); //prepend the staging folder path outputPath = s`${stagingFolderPath}/${outputPath}`; - return { - file: file, - outputPath: outputPath - }; - }); + return outputPath; + }; - const astEditor = new AstEditor(); + const processedFiles = new Set(); - this.plugins.emit('beforeProgramTranspile', this, entries, astEditor); + const transpileFile = async (file: BscFile, outputPath?: string) => { + //mark this file as processed so we don't do it again + processedFiles.add(file); - const promises = entries.map(async (entry) => { //skip transpiling typedef files - if (isBrsFile(entry.file) && entry.file.isTypedef) { + if (isBrsFile(file) && file.isTypedef) { return; } - const { file, outputPath } = entry; - const fileTranspileResult = this._getTranspiledFileContents(file, outputPath); //make sure the full dir path exists @@ -1366,14 +1364,48 @@ export class Program { const typedefPath = outputPath.replace(/\.brs$/i, '.d.bs'); await fsExtra.writeFile(typedefPath, fileTranspileResult.typedef); } + }; + + const entries = Object.values(this.files).map(file => { + return { + file: file, + outputPath: getOutputPath(file) + }; + }); + + const astEditor = new AstEditor(); + + this.plugins.emit('beforeProgramTranspile', this, entries, astEditor); + + let promises = entries.map(async (entry) => { + return transpileFile(entry.file, entry.outputPath); }); //if there's no bslib file already loaded into the program, copy it to the staging directory - if (!this.getFileByPkgPath(bslibAliasedRokuModulesPkgPath) && !this.getFileByPkgPath(s`source/bslib.brs`)) { + if (!this.getFile(bslibAliasedRokuModulesPkgPath) && !this.getFile(s`source/bslib.brs`)) { promises.push(util.copyBslibToStaging(stagingFolderPath)); } await Promise.all(promises); + //transpile any new files that plugins added since the start of this transpile process + do { + promises = []; + for (const key in this.files) { + const file = this.files[key]; + //this is a new file + if (!processedFiles.has(file)) { + promises.push( + transpileFile(file, getOutputPath(file)) + ); + } + } + if (promises.length > 0) { + this.logger.info(`Transpiling ${promises.length} new files`); + await Promise.all(promises); + } + } + while (promises.length > 0); + this.plugins.emit('afterProgramTranspile', this, entries, astEditor); astEditor.undoAll(); } diff --git a/src/interfaces.ts b/src/interfaces.ts index d97161fbe..dd1d82261 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -265,6 +265,7 @@ export interface OnScopeValidateEvent { export type Editor = Pick; export interface BeforeFileTranspileEvent { + program: Program; file: TFile; outputPath: string; /** @@ -276,6 +277,10 @@ export interface BeforeFileTranspileEvent { } export interface AfterFileTranspileEvent { + /** + * The program this event was triggered for + */ + program: Program; file: TFile; outputPath: string; /**