From bcdf02a17ef06042adc0dd242ceabe9b4334682b Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Sat, 27 Feb 2021 07:15:31 -0500 Subject: [PATCH] bslib ropm support (#334) * Add support for `bslib` installing from ropm. * remove local install * auto-detect bslib from program. remove config option. * update readme. * simplify program logic and fix tests * fix duplicate bslib import * address PR concerns. * remove redundant path standardize --- README.md | 16 ++++++++++ bslib.brs | 47 ------------------------------ package-lock.json | 5 ++++ package.json | 1 + src/Program.spec.ts | 14 +++++++-- src/Program.ts | 26 +++++++++++------ src/Scope.ts | 5 ++++ src/files/XmlFile.spec.ts | 61 ++++++++++++++++++++++++++++++++++++++- src/files/XmlFile.ts | 17 +++++++---- src/util.spec.ts | 7 +++++ src/util.ts | 25 ++++++++++++++++ 11 files changed, 159 insertions(+), 65 deletions(-) delete mode 100644 bslib.brs diff --git a/README.md b/README.md index 7fa822db5..b31541876 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,22 @@ end sub The primary motivation for this feature was to provide a stopgap measure to hide incorrectly-thrown errors on legitimate BrightScript code due to parser bugs. This is still a new project and it is likely to be missing support for certain BrightScript syntaxes. It is recommended that you only use these comments when absolutely necessary. + +## ropm support +In order for BrighterScript-transpiled projects to work as ropm modules, they need a reference to [bslib](https://github.com/rokucommunity/bslib/blob/master/source/bslib.brs) (the BrightScript runtime library for BrighterScript features) in their package. As `ropm` and `brighterscript` become more popular, this could result in many duplicate copies of `bslib.brs`. + +To encourage reducing code duplication, BrighterScript has built-in support for loading `bslib` from [ropm](https://github.com/rokucommunity/ropm). Here's how it works: +1. if your program does not use ropm, or _does_ use ropm but does not directly reference bslib, then the BrighterScript compiler will copy bslib to `"pkg:/source/bslib.brs"` at transpile-time. +2. if your program uses ropm and has installed `bslib` as a dependency, then the BrighterScript compiler will _not_ emit a copy of bslib at `pkg:/source/bslib.brs`, and will instead use the path to the version from ropm `pkg:/source/roku_modules/bslib/bslib.brs`. + + + +### Installing bslib in your ropm-enabled project +bslib is actually published to npm under the name [@rokucommunity/bslib](https://npmjs.com/package/@rokucommunity/bslib). However, to keep the bslib function names short, BrighterScript requires that you install @rokucommunity/bslib with the `bslib` alias. Here's the command to do that using the ropm CLI. +```bash +ropm install bslib@npm:@rokucommunity/bslib +``` + ## Language Server Protocol This project also contributes a class that aligns with Microsoft's [Language Server Protocol](https://microsoft.github.io/language-server-protocol/), which makes it easy to integrate `BrightScript` and `BrighterScript` with any IDE that supports the protocol. We won't go into more detail here, but you can use the `LanguageServer` class from this project to integrate into your IDE. The [vscode-BrightScript-language](https://github.com/rokucommunity/vscode-BrightScript-language) extension uses this LanguageServer class to bring `BrightScript` and `BrighterScript` language support to Visual Studio Code. diff --git a/bslib.brs b/bslib.brs deleted file mode 100644 index 3222d2b9d..000000000 --- a/bslib.brs +++ /dev/null @@ -1,47 +0,0 @@ -' Utility functions for the BrighterScript language - -' -' Convert a value into a string. For non-primitive values, this will return the type instead of its value in most cases. -' @param dynamic value - the value to be turned into a string. -' @return string - the string representation of `value` -' -function bslib_toString(value) - valueType = type(value) - 'if the var supports `toStr()` - if valueType = "" then - return valueType - else if value = invalid then - return "" - else if GetInterface(value, "ifToStr") <> invalid then - return value.toStr() - else if valueType = "roSGNode" - return "Node(" + value.subType() + ")" - end if - 'TODO add class name for objects once we have reflection implemented - - 'when all else fails, just return the type - return "<" + valueType + ">" -end function - -' -' Simple ternary function. Given a condition, return the -' consequent if true, and the alternate if false -' -function bslib_ternary(condition, consequent, alternate) - if condition then - return consequent - else - return alternate - end if -end function - -' -' Return consequent if consequent is not invalid, otherwise return alternate -' -function bslib_coalesce(consequent, alternate) - if consequent <> invalid then - return consequent - else - return alternate - end if -end function \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f35f4ecc6..13f957907 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1029,6 +1029,11 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "bslib": { + "version": "npm:@rokucommunity/bslib@0.1.1", + "resolved": "https://registry.npmjs.org/@rokucommunity/bslib/-/bslib-0.1.1.tgz", + "integrity": "sha512-2ox6EUL+UTtccTbD4dbVjZK3QHa0PHCqpoKMF8lZz9ayzzEP3iVPF8KZR6hOi6bxsIcbGXVjqmtCVkpC4P9SrA==" + }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", diff --git a/package.json b/package.json index 73b00a6c5..e08c8d221 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "dependencies": { "@xml-tools/parser": "^1.0.7", "array-flat-polyfill": "^1.0.1", + "bslib": "npm:@rokucommunity/bslib@^0.1.1", "chalk": "^2.4.2", "chokidar": "^3.0.2", "clear": "^0.1.0", diff --git a/src/Program.spec.ts b/src/Program.spec.ts index 64cc56391..9a1460b0c 100644 --- a/src/Program.spec.ts +++ b/src/Program.spec.ts @@ -1638,6 +1638,18 @@ describe('Program', () => { }); describe('transpile', () => { + it('copies bslib.brs when no ropm version was found', async () => { + await program.transpile([], stagingFolderPath); + expect(fsExtra.pathExistsSync(`${stagingFolderPath}/source/bslib.brs`)).to.be.true; + }); + + it('does not copy bslib.brs when found in roku_modules', async () => { + program.addOrReplaceFile('source/roku_modules/bslib/bslib.brs', ''); + await program.transpile([], stagingFolderPath); + expect(fsExtra.pathExistsSync(`${stagingFolderPath}/source/bslib.brs`)).to.be.false; + expect(fsExtra.pathExistsSync(`${stagingFolderPath}/source/roku_modules/bslib/bslib.brs`)).to.be.true; + }); + it('transpiles in-memory-only files', async () => { program.addOrReplaceFile('source/logger.bs', trim` sub logInfo() @@ -2241,7 +2253,5 @@ describe('Program', () => { expect(signatureHelp[0].index, `failed on col ${col}`).to.equal(2); } }); - - }); }); diff --git a/src/Program.ts b/src/Program.ts index e3abea12d..02a015e8d 100644 --- a/src/Program.ts +++ b/src/Program.ts @@ -26,6 +26,7 @@ import { ParseMode } from './parser'; import { TokenKind } from './lexer'; import { BscPlugin } from './bscPlugin/BscPlugin'; const startOfSourcePkgPath = `source${path.sep}`; +const bslibRokuModulesPkgPath = s`source/roku_modules/bslib/bslib.brs`; export interface SourceObj { pathAbsolute: string; @@ -121,6 +122,18 @@ export class Program { */ private diagnostics = [] as BsDiagnostic[]; + /** + * The path to bslib.brs (the BrightScript runtime for certain BrighterScript features) + */ + public get bslibPkgPath() { + //if there's a version of bslib from roku_modules loaded into the program, use that + if (this.getFileByPkgPath(bslibRokuModulesPkgPath)) { + return bslibRokuModulesPkgPath; + } else { + return `source${path.sep}bslib.brs`; + } + } + /** * A map of every file loaded into this program, indexed by its original file location */ @@ -1141,15 +1154,10 @@ export class Program { this.plugins.emit('afterFileTranspile', entry); }); - //copy the brighterscript stdlib to the output directory - promises.push( - fsExtra.ensureDir(s`${stagingFolderPath}/source`).then(() => { - return fsExtra.copyFile( - s`${__dirname}/../bslib.brs`, - s`${stagingFolderPath}/source/bslib.brs` - ); - }) - ); + //if there's no bslib file already loaded into the program, copy it to the staging directory + if (!this.getFileByPkgPath(bslibRokuModulesPkgPath) && !this.getFileByPkgPath(s`source/bslib.brs`)) { + promises.push(util.copyBslibToStaging(stagingFolderPath)); + } await Promise.all(promises); this.plugins.emit('afterProgramTranspile', this, entries); diff --git a/src/Scope.ts b/src/Scope.ts index 095cd3a51..30f58e29b 100644 --- a/src/Scope.ts +++ b/src/Scope.ts @@ -1,4 +1,5 @@ import type { CodeAction, CompletionItem, Position, Range } from 'vscode-languageserver'; +import * as path from 'path'; import { CompletionItemKind, Location } from 'vscode-languageserver'; import chalk from 'chalk'; import type { DiagnosticInfo } from './DiagnosticMessages'; @@ -835,6 +836,10 @@ export class Scope { let referencedFile = this.getFileByRelativePath(scriptImport.pkgPath); //if we can't find the file if (!referencedFile) { + //skip the default bslib file, it will exist at transpile time but should not show up in the program during validation cycle + if (scriptImport.pkgPath === `source${path.sep}bslib.brs`) { + continue; + } let dInfo: DiagnosticInfo; if (scriptImport.text.trim().length === 0) { dInfo = DiagnosticMessages.scriptSrcCannotBeEmpty(); diff --git a/src/files/XmlFile.spec.ts b/src/files/XmlFile.spec.ts index 78ca30cdf..2c4c2a4c1 100644 --- a/src/files/XmlFile.spec.ts +++ b/src/files/XmlFile.spec.ts @@ -11,7 +11,7 @@ import { BrsFile } from './BrsFile'; import { XmlFile } from './XmlFile'; import util, { standardizePath as s } from '../util'; import { getTestTranspile } from './BrsFile.spec'; -import { expectCodeActions, trim, trimMap } from '../testHelpers.spec'; +import { expectCodeActions, expectZeroDiagnostics, trim, trimMap } from '../testHelpers.spec'; import { URI } from 'vscode-uri'; describe('XmlFile', () => { @@ -626,6 +626,65 @@ describe('XmlFile', () => { }); describe('transpile', () => { + it('includes bslib script', () => { + testTranspile(trim` + + + + `, trim` + + +