diff --git a/src/BsConfig.ts b/src/BsConfig.ts index dd9a1be4a..e35849b65 100644 --- a/src/BsConfig.ts +++ b/src/BsConfig.ts @@ -186,7 +186,15 @@ export interface BsConfig { * @default true */ sourceMap?: boolean; - + /** + * Excludes empty files from being included in the output. Some Brighterscript files + * are left empty or with only comments after transpilation to Brightscript. + * The default behavior is to write these to disk after transpilation. + * Setting this flag to `true` will prevent empty files being written and will + * remove associated script tags from XML + * @default false + */ + pruneEmptyCodeFiles?: boolean; /** * Allow brighterscript features (classes, interfaces, etc...) to be included in BrightScript (`.brs`) files, and force those files to be transpiled. * @default false diff --git a/src/Program.spec.ts b/src/Program.spec.ts index 5b2eb101e..b072cc928 100644 --- a/src/Program.spec.ts +++ b/src/Program.spec.ts @@ -2159,6 +2159,51 @@ describe('Program', () => { s`${sourceRoot}/source/main.bs` ); }); + + it('does not publish files that are empty', async () => { + let sourceRoot = s`${tempDir}/sourceRootFolder`; + program = new Program({ + rootDir: rootDir, + stagingDir: stagingDir, + sourceRoot: sourceRoot, + sourceMap: true, + pruneEmptyCodeFiles: true + }); + program.setFile('source/types.bs', ` + enum mainstyle + dark = "dark" + light = "light" + end enum + `); + program.setFile('source/main.bs', ` + import "pkg:/source/types.bs" + + sub main() + ? "The night is " + mainstyle.dark + " and full of terror" + end sub + `); + await program.transpile([ + { + src: s`${rootDir}/source/main.bs`, + dest: s`source/main.bs` + }, + { + src: s`${rootDir}/source/types.bs`, + dest: s`source/types.bs` + } + ], stagingDir); + + expect(trimMap( + fsExtra.readFileSync(s`${stagingDir}/source/main.brs`).toString() + )).to.eql(trim` + 'import "pkg:/source/types.bs" + + sub main() + ? "The night is " + "dark" + " and full of terror" + end sub + `); + expect(fsExtra.pathExistsSync(s`${stagingDir}/source/types.brs`)).to.be.false; + }); }); describe('typedef', () => { diff --git a/src/Program.ts b/src/Program.ts index a6e36a3ae..5ffb9e9e1 100644 --- a/src/Program.ts +++ b/src/Program.ts @@ -1213,28 +1213,30 @@ export class Program { //mark this file as processed so we don't process it more than once processedFiles.add(outputPath?.toLowerCase()); - //skip transpiling typedef files - if (isBrsFile(file) && file.isTypedef) { - return; - } + if (!this.options.pruneEmptyCodeFiles || !file.canBePruned) { + //skip transpiling typedef files + if (isBrsFile(file) && file.isTypedef) { + return; + } - const fileTranspileResult = this._getTranspiledFileContents(file, outputPath); + const fileTranspileResult = this._getTranspiledFileContents(file, outputPath); - //make sure the full dir path exists - await fsExtra.ensureDir(path.dirname(outputPath)); + //make sure the full dir path exists + await fsExtra.ensureDir(path.dirname(outputPath)); - if (await fsExtra.pathExists(outputPath)) { - throw new Error(`Error while transpiling "${file.srcPath}". A file already exists at "${outputPath}" and will not be overwritten.`); - } - const writeMapPromise = fileTranspileResult.map ? fsExtra.writeFile(`${outputPath}.map`, fileTranspileResult.map.toString()) : null; - await Promise.all([ - fsExtra.writeFile(outputPath, fileTranspileResult.code), - writeMapPromise - ]); - - if (fileTranspileResult.typedef) { - const typedefPath = outputPath.replace(/\.brs$/i, '.d.bs'); - await fsExtra.writeFile(typedefPath, fileTranspileResult.typedef); + if (await fsExtra.pathExists(outputPath)) { + throw new Error(`Error while transpiling "${file.srcPath}". A file already exists at "${outputPath}" and will not be overwritten.`); + } + const writeMapPromise = fileTranspileResult.map ? fsExtra.writeFile(`${outputPath}.map`, fileTranspileResult.map.toString()) : null; + await Promise.all([ + fsExtra.writeFile(outputPath, fileTranspileResult.code), + writeMapPromise + ]); + + if (fileTranspileResult.typedef) { + const typedefPath = outputPath.replace(/\.brs$/i, '.d.bs'); + await fsExtra.writeFile(typedefPath, fileTranspileResult.typedef); + } } }; diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts index 6cc4ec7e8..cb0845dc0 100644 --- a/src/files/BrsFile.spec.ts +++ b/src/files/BrsFile.spec.ts @@ -182,6 +182,66 @@ describe('BrsFile', () => { }); }); + describe('canBePruned', () => { + it('returns false is target file has contains a function statement', () => { + program.setFile('source/main.brs', ` + sub main() + print \`pkg:\` + end sub + `); + const file = program.getFile('source/main.brs'); + expect(file.canBePruned).to.be.false; + }); + + it('returns false if target file contains a class statement', () => { + program.setFile('source/main.brs', ` + class Animal + public name as string + end class + `); + const file = program.getFile('source/main.brs'); + expect(file.canBePruned).to.be.false; + }); + + it('returns false if target file contains a class statement', () => { + program.setFile('source/main.brs', ` + namespace Vertibrates.Birds + function GetDucks() + end function + end namespace + `); + const file = program.getFile('source/main.brs'); + expect(file.canBePruned).to.be.false; + }); + + it('returns true if target file contains only enum', () => { + program.setFile('source/main.brs', ` + enum Direction + up + down + left + right + end enum + `); + const file = program.getFile('source/main.brs'); + expect(file.canBePruned).to.be.true; + }); + + it('returns true if target file is empty', () => { + program.setFile('source/main.brs', ''); + const file = program.getFile('source/main.brs'); + expect(file.canBePruned).to.be.true; + }); + + it('returns true if target file only has comments', () => { + program.setFile('source/main.brs', ` + ' this is an interesting comment + `); + const file = program.getFile('source/main.brs'); + expect(file.canBePruned).to.be.true; + }); + }); + describe('getScopesForFile', () => { it('finds the scope for the file', () => { let file = program.setFile('source/main.brs', ``); diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts index 9edef7fd8..19c74a567 100644 --- a/src/files/BrsFile.ts +++ b/src/files/BrsFile.ts @@ -77,6 +77,24 @@ export class BrsFile { this.srcPath = value; } + /** + * Will this file result in only comment or whitespace output? If so, it can be excluded from the output if that bsconfig setting is enabled. + */ + public get canBePruned() { + let canPrune = true; + this.ast.walk(createVisitor({ + FunctionStatement: () => { + canPrune = false; + }, + ClassStatement: () => { + canPrune = false; + } + }), { + walkMode: WalkMode.visitStatements + }); + return canPrune; + } + /** * The parseMode used for the parser for this file */ diff --git a/src/files/XmlFile.spec.ts b/src/files/XmlFile.spec.ts index 104c299ec..400b0ce30 100644 --- a/src/files/XmlFile.spec.ts +++ b/src/files/XmlFile.spec.ts @@ -899,6 +899,92 @@ describe('XmlFile', () => { const code = file.transpile().code; expect(code.endsWith(``)).to.be.true; }); + + it('removes script imports if given file is not publishable', () => { + program.options.pruneEmptyCodeFiles = true; + program.setFile(`components/SimpleScene.bs`, ` + enum simplescenetypes + hero + intro + end enum + `); + + testTranspile(trim` + + +