\n`
- dir = path.getString()
+ dir = path
if dir.len() > 0 and dir.right(1) <> "/"
dir = dir + "/"
end if
diff --git a/playlet-lib/src/components/Web/WebServer/WebServer.bs b/playlet-lib/src/components/Web/WebServer/WebServer.bs
index 3e38f15ba..60c7061b1 100644
--- a/playlet-lib/src/components/Web/WebServer/WebServer.bs
+++ b/playlet-lib/src/components/Web/WebServer/WebServer.bs
@@ -6,7 +6,7 @@ function Init()
m.top.functionName = "WebServerLoop"
end function
-function StartServer(_unused as dynamic) as boolean
+function StartServer() as boolean
if m.top.isRunning = true
return true
end if
@@ -15,7 +15,7 @@ function StartServer(_unused as dynamic) as boolean
return true
end function
-function StopServer(_unused as dynamic) as void
+function StopServer() as void
if m.top.isRunning = false
return
end if
diff --git a/playlet-lib/src/components/Web/WebServer/WebSockets/WebSocketConnection.bs b/playlet-lib/src/components/Web/WebServer/WebSockets/WebSocketConnection.bs
index c360b046b..52aa20f63 100644
--- a/playlet-lib/src/components/Web/WebServer/WebSockets/WebSocketConnection.bs
+++ b/playlet-lib/src/components/Web/WebServer/WebSockets/WebSocketConnection.bs
@@ -7,11 +7,13 @@ namespace Web
const BUFSIZE = 524288
class WebSocketConnection
+ public id = invalid
+ public state = invalid
+
private server
private socket
private frame
- public id = invalid
- public state = invalid
+ private buffer
function new(server as WebSocketServer, socket as object)
m.server = server
diff --git a/playlet-lib/src/components/Web/WebServer/WebSockets/WebSocketFrame.bs b/playlet-lib/src/components/Web/WebServer/WebSockets/WebSocketFrame.bs
index 79fc72c11..51c68f53e 100644
--- a/playlet-lib/src/components/Web/WebServer/WebSockets/WebSocketFrame.bs
+++ b/playlet-lib/src/components/Web/WebServer/WebSockets/WebSocketFrame.bs
@@ -13,6 +13,7 @@ namespace Web
private index
private length
+ private buffer
function new()
m.payload = CreateObject("roByteArray")
diff --git a/playlet-lib/src/components/parts/AutoBind/AutoBind.part.bs b/playlet-lib/src/components/parts/AutoBind/AutoBind.part.bs
index 51176e34a..6c2a4088d 100644
--- a/playlet-lib/src/components/parts/AutoBind/AutoBind.part.bs
+++ b/playlet-lib/src/components/parts/AutoBind/AutoBind.part.bs
@@ -25,7 +25,7 @@ function AutoBindSceneGraph() as void
end function
' Bind the node fields, to be called with interface function
-function BindNodeFieldsAndProps(_unused as dynamic) as void
+function BindNodeFieldsAndProps() as void
node = m.top
bindings = node.bindings
if bindings = invalid
@@ -67,7 +67,7 @@ end function
' Create scoped fields (m.fieldName) for all fields in this node
' To be called after BindNodeFieldsAndProps
-function BindScopeFields(_unused as dynamic)
+function BindScopeFields()
types = m.top.getFieldTypes()
for each key in types
if m.DoesExist(key)
@@ -84,10 +84,10 @@ function BindScopeFields(_unused as dynamic)
end function
' Bind a single node. This for nodes created dynamically, and all its dependencies are expected to be bound already
-function BindNode(_unused as dynamic)
+function BindNode()
RemoveNodeFromGlobalAutoBind()
- BindNodeFieldsAndProps(invalid)
- BindScopeFields(invalid)
+ BindNodeFieldsAndProps()
+ BindScopeFields()
m.top.binding_done = true
end function
diff --git a/playlet-lib/src/source/AsyncTask/AsyncTask.bs b/playlet-lib/src/source/AsyncTask/AsyncTask.bs
index dd8d48fdf..92ee59364 100644
--- a/playlet-lib/src/source/AsyncTask/AsyncTask.bs
+++ b/playlet-lib/src/source/AsyncTask/AsyncTask.bs
@@ -2,6 +2,10 @@ import "pkg:/source/utils/Types.bs"
namespace AsyncTask
+ interface AsyncTaskComponent extends roSGNodeTask
+ input as object
+ end interface
+
function Start(taskName as string, input as object, callback = invalid as object) as object
task = Create(taskName, input, callback)
task.control = "RUN"
@@ -10,7 +14,7 @@ namespace AsyncTask
function Create(taskName as string, input as object, callback = invalid as object) as object
id = StrI(rnd(2147483647), 36)
- task = createObject("roSGNode", taskName)
+ task = createObject("roSGNode", taskName) as AsyncTask.AsyncTaskComponent
task.id = id
task.input = input
diff --git a/playlet-lib/src/source/QrCode/QRBitBuffer.bs b/playlet-lib/src/source/QrCode/QRBitBuffer.bs
index 438b8de24..0bc4f4d7a 100644
--- a/playlet-lib/src/source/QrCode/QRBitBuffer.bs
+++ b/playlet-lib/src/source/QrCode/QRBitBuffer.bs
@@ -1,5 +1,9 @@
namespace QrCode
class QRBitBuffer
+
+ private buffer as roArray
+ private numberOfBits as integer
+
function new()
m.buffer = []
m.numberOfBits = 0
diff --git a/playlet-lib/src/source/QrCode/QRCode.bs b/playlet-lib/src/source/QrCode/QRCode.bs
index fa92afe08..6b7527d95 100644
--- a/playlet-lib/src/source/QrCode/QRCode.bs
+++ b/playlet-lib/src/source/QrCode/QRCode.bs
@@ -13,6 +13,24 @@ namespace QrCode
class QRCode
+ private PAD0 as integer
+ private PAD1 as integer
+
+ private dataNodes as roArray
+ private dataBytes as roArray
+
+ private modules as roArray
+ private moduleCount as integer
+
+ private typeNumber as integer
+ private errorCorrectionLevel as string
+
+ private math as QRMath
+ private util as QRUtil
+ private rsBlock as QRRSBlock
+
+ public status as string
+
function new()
m.PAD0 = &HEC
m.PAD1 = &H11
@@ -378,15 +396,15 @@ namespace QrCode
end for
end function
- public function addData(data as string, qrType = QrCode.QrMode.Byte as QrCode.QrMode) as boolean
+ public function addData(data as string, qrType = QrMode.Byte as QrMode) as boolean
- if qrType = QrCode.QrMode.AlphaNumeric
+ if qrType = QrMode.AlphaNumeric
qrData = new QRData_Alphanumeric()
- else if qrType = QrCode.QrMode.Numeric
+ else if qrType = QrMode.Numeric
qrData = new QRData_Numeric()
- else if qrType = QrCode.QrMode.Byte
+ else if qrType = QrMode.Byte
qrData = new QRData_Byte()
- else if qrType = QrCode.QrMode.Kanji
+ else if qrType = QrMode.Kanji
' NOTE: Kanji not supported - removed because it increases the code size significantly.
' Currently there's no use case for it, since we're mostly doing a QRCode for a url.
' If we need to support Kanji, we'll add it back.
diff --git a/playlet-lib/src/source/QrCode/QRCode.exports.bs b/playlet-lib/src/source/QrCode/QRCode.exports.bs
index 4fbf659b7..7d84e480b 100644
--- a/playlet-lib/src/source/QrCode/QRCode.exports.bs
+++ b/playlet-lib/src/source/QrCode/QRCode.exports.bs
@@ -24,46 +24,4 @@ namespace QrCode
}
end function
- function QRMaskFunctions() as object
- result = {}
-
- result.PATTERN000 = function(i as dynamic, j as dynamic) as boolean
- return (i + j) mod 2 = 0
- end function
-
- result.PATTERN001 = function(i as dynamic, _j as dynamic) as boolean
- return i mod 2 = 0
- end function
-
- result.PATTERN010 = function(_i as dynamic, j as dynamic) as boolean
- return j mod 3 = 0
- end function
-
- result.PATTERN011 = function(i as dynamic, j as dynamic) as boolean
- return (i + j) mod 3 = 0
- end function
-
- result.PATTERN100 = function(i as dynamic, j as dynamic) as boolean
- return ((i \ 2) + (j \ 3)) mod 2 = 0
- end function
-
- result.PATTERN101 = function(i as dynamic, j as dynamic) as boolean
- return ((i * j) mod 2) + ((i * j) mod 3) = 0
- end function
-
- result.PATTERN110 = function(i as dynamic, j as dynamic) as boolean
- return (((i * j) mod 2) + ((i * j) mod 3)) mod 2 = 0
- end function
-
- result.PATTERN111 = function(i as dynamic, j as dynamic) as boolean
- return (((i * j) mod 3) + ((i + j) mod 2)) mod 2 = 0
- end function
-
- return result
- end function
-
- function QRMaskPatterns() as object
- return QRMaskFunctions().keys()
- end function
-
end namespace
diff --git a/playlet-lib/src/source/QrCode/QRData_AlphaNumeric.bs b/playlet-lib/src/source/QrCode/QRData_AlphaNumeric.bs
index 8a9c8ed3a..cf8c82a00 100644
--- a/playlet-lib/src/source/QrCode/QRData_AlphaNumeric.bs
+++ b/playlet-lib/src/source/QrCode/QRData_AlphaNumeric.bs
@@ -2,8 +2,13 @@ namespace QrCode
class QRData_AlphaNumeric
+ private mode as integer
+ private data as string
+ private length as integer
+ private CHAR_TABLE as roAssociativeArray
+
function new()
- m.mode = QrCode.QRMode.AlphaNumeric
+ m.mode = QRMode.AlphaNumeric
m.CHAR_TABLE = {
"0": 0
diff --git a/playlet-lib/src/source/QrCode/QRData_Byte.bs b/playlet-lib/src/source/QrCode/QRData_Byte.bs
index 5ebd7984e..419e9ec4f 100644
--- a/playlet-lib/src/source/QrCode/QRData_Byte.bs
+++ b/playlet-lib/src/source/QrCode/QRData_Byte.bs
@@ -2,8 +2,12 @@ namespace QrCode
class QRData_Byte
+ private mode as QRMode
+ private bytes as roByteArray
+ private length as integer
+
function new()
- m.mode = QrCode.QRMode.Byte
+ m.mode = QRMode.Byte
end function
public function setData(data as string)
diff --git a/playlet-lib/src/source/QrCode/QRData_Numeric.bs b/playlet-lib/src/source/QrCode/QRData_Numeric.bs
index 4baa4ef76..d383eb720 100644
--- a/playlet-lib/src/source/QrCode/QRData_Numeric.bs
+++ b/playlet-lib/src/source/QrCode/QRData_Numeric.bs
@@ -2,8 +2,14 @@ namespace QrCode
class QRData_Numeric
+ private mode as QRMode
+ private data as string
+ private length as integer
+
+ private CHAR_TABLE as roAssociativeArray
+
function new()
- m.mode = QrCode.QRMode.Numeric
+ m.mode = QRMode.Numeric
m.CHAR_TABLE = {
"0": 0
diff --git a/playlet-lib/src/source/QrCode/QRMaskFunctions.bs b/playlet-lib/src/source/QrCode/QRMaskFunctions.bs
new file mode 100644
index 000000000..d088a6163
--- /dev/null
+++ b/playlet-lib/src/source/QrCode/QRMaskFunctions.bs
@@ -0,0 +1,43 @@
+namespace QrCode
+
+ function QRMaskPatterns() as string[]
+ return ["PATTERN000", "PATTERN001", "PATTERN010", "PATTERN011", "PATTERN100", "PATTERN101", "PATTERN110", "PATTERN111"]
+ end function
+
+ class QRMaskFunctions
+
+ function PATTERN000(i as dynamic, j as dynamic) as boolean
+ return (i + j) mod 2 = 0
+ end function
+
+ function PATTERN001(i as dynamic, _j as dynamic) as boolean
+ return i mod 2 = 0
+ end function
+
+ function PATTERN010(_i as dynamic, j as dynamic) as boolean
+ return j mod 3 = 0
+ end function
+
+ function PATTERN011(i as dynamic, j as dynamic) as boolean
+ return (i + j) mod 3 = 0
+ end function
+
+ function PATTERN100(i as dynamic, j as dynamic) as boolean
+ return ((i \ 2) + (j \ 3)) mod 2 = 0
+ end function
+
+ function PATTERN101(i as dynamic, j as dynamic) as boolean
+ return ((i * j) mod 2) + ((i * j) mod 3) = 0
+ end function
+
+ function PATTERN110(i as dynamic, j as dynamic) as boolean
+ return (((i * j) mod 2) + ((i * j) mod 3)) mod 2 = 0
+ end function
+
+ function PATTERN111(i as dynamic, j as dynamic) as boolean
+ return (((i * j) mod 3) + ((i + j) mod 2)) mod 2 = 0
+ end function
+
+ end class
+
+end namespace
diff --git a/playlet-lib/src/source/QrCode/QRMath.bs b/playlet-lib/src/source/QrCode/QRMath.bs
index 8b94294d2..53270949a 100644
--- a/playlet-lib/src/source/QrCode/QRMath.bs
+++ b/playlet-lib/src/source/QrCode/QRMath.bs
@@ -1,6 +1,8 @@
namespace QrCode
class QRMath
+ private EXP_TABLE as roArray
+ private LOG_TABLE as roArray
function new()
EXP_TABLE = createObject("roArray", 256, false)
diff --git a/playlet-lib/src/source/QrCode/QRPolynomial.bs b/playlet-lib/src/source/QrCode/QRPolynomial.bs
index 74bf1b6c3..b442a4374 100644
--- a/playlet-lib/src/source/QrCode/QRPolynomial.bs
+++ b/playlet-lib/src/source/QrCode/QRPolynomial.bs
@@ -1,6 +1,10 @@
namespace QrCode
class QRPolynomial
+
+ private math as QRMath
+ private _num as roArray
+
function new(math as QRMath, num as object, shift as integer)
if type(num) <> "roArray"
throw `Invalid num ${num}/${shift}`
diff --git a/playlet-lib/src/source/QrCode/QRRSBlock.bs b/playlet-lib/src/source/QrCode/QRRSBlock.bs
index ec715b58d..9bed1fc36 100644
--- a/playlet-lib/src/source/QrCode/QRRSBlock.bs
+++ b/playlet-lib/src/source/QrCode/QRRSBlock.bs
@@ -2,6 +2,9 @@ namespace QrCode
class QRRSBlock
+ private ERROR_CORRECTION_LEVEL_OFFSETS as roAssociativeArray
+ private RS_BLOCK_TABLE as roArray
+
function new()
m.ERROR_CORRECTION_LEVEL_OFFSETS = {
L: 0
diff --git a/playlet-lib/src/source/QrCode/QRUtil.bs b/playlet-lib/src/source/QrCode/QRUtil.bs
index e13a88be9..0da3de80c 100644
--- a/playlet-lib/src/source/QrCode/QRUtil.bs
+++ b/playlet-lib/src/source/QrCode/QRUtil.bs
@@ -1,7 +1,18 @@
+import "QRMaskFunctions.bs"
+
namespace QrCode
class QRUtil
+ private math as QRMath
+ private PATTERN_POSITION_TABLE as roArray
+
+ private G15 as integer
+ private G18 as integer
+ private G15_MASK as integer
+ private G15_BCHDigit as integer
+ private G18_BCHDigit as integer
+
function new(math as QRMath)
m.math = math
m.PATTERN_POSITION_TABLE = [
@@ -53,7 +64,6 @@ namespace QrCode
m.G15_BCHDigit = m.getBCHDigit(m.G15)
m.G18_BCHDigit = m.getBCHDigit(m.G18)
-
end function
function getBCHDigit(data as integer) as integer
@@ -87,21 +97,21 @@ namespace QrCode
end function
function getMaskFunction(maskPattern as integer) as function
- masks = QRMaskFunctions()
+ masks = new QRMaskFunctions()
patterns = QRMaskPatterns()
maskId = patterns[maskPattern]
if maskId = invalid or masks[maskId] = invalid
- ' print "Invalid maskPattern", maskPattern
- return function(_i as dynamic, _j as dynamic) as boolean
- ' print "Invalid maskPattern used"
- return false
- end function
+ return m.InvalidMaskFunction
end if
return masks[maskId]
end function
+ function InvalidMaskFunction(_i as dynamic, _j as dynamic) as boolean
+ return false
+ end function
+
function getErrorCorrectPolynomial(errorCorrectLength as integer) as object
a = new QRPolynomial(m.math, [1], 0)
for i = 0 to errorCorrectLength - 1 step 1
@@ -113,35 +123,35 @@ namespace QrCode
function getLengthInBits(mode as integer, typeNumber as integer) as integer
if 1 <= typeNumber and typeNumber < 10
' 1 - 9
- if mode = QrCode.QrMode.Numeric
+ if mode = QrMode.Numeric
return 10
end if
- if mode = QrCode.QrMode.AlphaNumeric
+ if mode = QrMode.AlphaNumeric
return 9
end if
- if mode = QrCode.QrMode.Byte
+ if mode = QrMode.Byte
return 8
end if
else if typeNumber < 27
' 10 - 26
- if mode = QrCode.QrMode.Numeric
+ if mode = QrMode.Numeric
return 12
end if
- if mode = QrCode.QrMode.AlphaNumeric
+ if mode = QrMode.AlphaNumeric
return 11
end if
- if mode = QrCode.QrMode.Byte
+ if mode = QrMode.Byte
return 16
end if
else if typeNumber < 41
' 27 - 40
- if mode = QrCode.QrMode.Numeric
+ if mode = QrMode.Numeric
return 14
end if
- if mode = QrCode.QrMode.AlphaNumeric
+ if mode = QrMode.AlphaNumeric
return 13
end if
- if mode = QrCode.QrMode.Byte
+ if mode = QrMode.Byte
return 16
end if
end if
diff --git a/playlet-lib/src/source/QrCode/QrImage.bs b/playlet-lib/src/source/QrCode/QrImage.bs
index 83960be56..69ff3b88f 100644
--- a/playlet-lib/src/source/QrCode/QrImage.bs
+++ b/playlet-lib/src/source/QrCode/QrImage.bs
@@ -1,14 +1,14 @@
+import "pkg:/source/utils/CryptoUtils.bs"
+import "QRCode.bs"
+
' DISABLE_CACHE will force the generation of the QRCode every time.
' Only use this for debugging purposes.
#const DISABLE_CACHE = false
-import "pkg:/source/utils/CryptoUtils.bs"
-import "QRCode.bs"
-
namespace QrCode
class QRImage
- function Generate(text as string, size as integer, padding as integer, mode = QrCode.QrMode.Byte as QrCode.QrMode) as string
+ function Generate(text as string, size as integer, padding as integer, mode = QrMode.Byte as QrMode) as string
fileName = m.GetFileName(text, mode, size, padding)
#if DEBUG
@@ -22,7 +22,7 @@ namespace QrCode
#end if
#end if
- qr = new QrCode.QRCode()
+ qr = new QRCode()
qr.addData(text, mode)
qr.make()
@@ -33,7 +33,7 @@ namespace QrCode
return ""
end function
- function GetFileName(text as string, mode as QrCode.QrMode, size as integer, padding as integer) as string
+ function GetFileName(text as string, mode as QrMode, size as integer, padding as integer) as string
key = `v1-${text}-${mode}-${size}-${padding}`
key = CryptoUtils.GetMd5(key)
return "cachefs:/" + key + ".png"
diff --git a/playlet-lib/src/source/services/ApplicationInfo.bs b/playlet-lib/src/source/services/ApplicationInfo.bs
index 846257cd0..565478cda 100644
--- a/playlet-lib/src/source/services/ApplicationInfo.bs
+++ b/playlet-lib/src/source/services/ApplicationInfo.bs
@@ -1,9 +1,15 @@
import "pkg:/source/utils/StringUtils.bs"
class ApplicationInfo
- public node as object
- public roDeviceInfo as object
- public roAppInfo as object
+ public node as roSGNodeApplicationInfo
+ public roDeviceInfo as roDeviceInfo
+ public roAppInfo as roAppInfo
+
+ private ipAddress as string
+ private countryCode as string
+ private appInfo as roAssociativeArray
+ private deviceInfo as roAssociativeArray
+ private libManifestValues as roAssociativeArray
function new(node as object)
m.node = node
@@ -29,7 +35,7 @@ class ApplicationInfo
return m.countryCode
end function
- function GetAppInfo() as object
+ function GetAppInfo() as roAssociativeArray
if m.appInfo = invalid
m.appInfo = {
id: m.roAppInfo.GetID()
@@ -61,7 +67,7 @@ class ApplicationInfo
return m.appInfo
end function
- function GetDeviceInfo() as object
+ function GetDeviceInfo() as roAssociativeArray
if m.deviceInfo = invalid
m.deviceInfo = {
model: m.roDeviceInfo.GetModel()
diff --git a/playlet-lib/src/source/services/HttpClient.bs b/playlet-lib/src/source/services/HttpClient.bs
index f5f733454..6e990995d 100644
--- a/playlet-lib/src/source/services/HttpClient.bs
+++ b/playlet-lib/src/source/services/HttpClient.bs
@@ -1,10 +1,3 @@
-' DISABLE_CACHE will disable all caching for all requests.
-' Only use this for debugging purposes.
-#const DISABLE_CACHE = false
-' NETWORK_THROTTLE will add a random delay to all requests to simulate network latency.
-' Only use this for debugging purposes.
-#const NETWORK_THROTTLE = false
-
import "pkg:/source/utils/CryptoUtils.bs"
import "pkg:/source/utils/ErrorUtils.bs"
import "pkg:/source/utils/Locale.bs"
@@ -13,6 +6,13 @@ import "pkg:/source/utils/StringUtils.bs"
import "pkg:/source/utils/Types.bs"
import "pkg:/source/utils/UrlUtils.bs"
+' DISABLE_CACHE will disable all caching for all requests.
+' Only use this for debugging purposes.
+#const DISABLE_CACHE = false
+' NETWORK_THROTTLE will add a random delay to all requests to simulate network latency.
+' Only use this for debugging purposes.
+#const NETWORK_THROTTLE = false
+
namespace HttpClient
const COMPRESSION_ENABLED = true
@@ -121,7 +121,29 @@ namespace HttpClient
class HttpRequest
- public urlTransfer as object
+ public urlTransfer as roUrlTransfer
+
+ public _cache as object
+ public _noCache as boolean
+ public _sent as boolean
+ public _cacheLocation as string
+ public _expireSeconds as integer
+ public _method as string
+ public _headers as roAssociativeArray
+ public _fileSystem as roFileSystem
+
+ private _timeoutSeconds as integer
+ private _tryCount as integer
+ private _url as string
+ private _queryParams as roAssociativeArray
+ private _pathParams as roAssociativeArray
+ private _body as string
+ private _retryCallback as function
+ private _useHttp2 as boolean
+ private _cancellation as object
+ private _timer as roTimeSpan
+ private _cancelled as boolean
+ private _fullUrl as string
function new()
m._timeoutSeconds = 30
@@ -297,6 +319,8 @@ namespace HttpClient
' Note: Calling Send() only without Await() will not guarentee a request will be sent.
' If a Task finishes before the request is sent, the request might be dropped.
+ ' TODO:P0 remove invalid attribute
+ @invalid
function Send() as HttpRequest
if m.urlTransfer <> invalid or m._cache <> invalid
return m
@@ -568,9 +592,15 @@ namespace HttpClient
class HttpResponse
public request as HttpRequest
- public event as object
+ public event as roUrlEvent
+
+ private _statusCode as integer
+ private _text as string
+ private _json as object
+ private _headers as object
+ private _errorMessage as string
- function new(request as HttpRequest, event as object)
+ function new(request as HttpRequest, event as roUrlEvent)
m.request = request
m.event = event
end function
diff --git a/tools/bs-plugins/Classes/RawCodeStatement.ts b/tools/bs-plugins/Classes/RawCodeStatement.ts
index 4f3052e25..9890a00da 100644
--- a/tools/bs-plugins/Classes/RawCodeStatement.ts
+++ b/tools/bs-plugins/Classes/RawCodeStatement.ts
@@ -5,8 +5,9 @@ import type {
WalkVisitor
} from 'brighterscript';
import {
+ AstNodeKind,
Range,
- Statement
+ Statement,
} from 'brighterscript';
import { SourceNode } from 'source-map';
@@ -22,6 +23,9 @@ export class RawCodeStatement extends Statement {
super();
}
+ public readonly kind = AstNodeKind.ExpressionStatement;
+ public readonly location: undefined;
+
public transpile(state: BrsTranspileState) {
//indent every line with the current transpile indent level (except the first line, because that's pre-indented by bsc)
let source = this.source.replace(/\r?\n/g, (match, newline) => {
@@ -31,7 +35,7 @@ export class RawCodeStatement extends Statement {
return [new SourceNode(
this.range.start.line + 1,
this.range.start.character,
- this.sourceFile ? this.sourceFile.pathAbsolute : state.srcPath,
+ this.sourceFile ? this.sourceFile.srcPath : state.srcPath,
source
)];
}
diff --git a/tools/bs-plugins/asynctask-plugin.ts b/tools/bs-plugins/asynctask-plugin.ts
index fe64104eb..f6b3c99b9 100644
--- a/tools/bs-plugins/asynctask-plugin.ts
+++ b/tools/bs-plugins/asynctask-plugin.ts
@@ -1,77 +1,84 @@
// This plugin generates a task component for each function annotated with @asynctask
import {
- CompilerPlugin,
+ AfterFileAddEvent,
+ BeforeProgramValidateEvent,
+ BrsFile,
BscFile,
- isBrsFile,
- WalkMode,
+ CompilerPlugin,
createVisitor,
- Program,
- FunctionStatement,
DiagnosticSeverity,
+ FunctionStatement,
+ isBrsFile,
+ ParseMode,
+ Program,
+ WalkMode,
+ XmlFile,
} from 'brighterscript';
export class AsyncTaskPlugin implements CompilerPlugin {
public name = 'AsyncTaskPlugin';
- afterFileParse(file: BscFile) {
- if (!isBrsFile(file)) {
- return
+ afterFileAdd(event: AfterFileAddEvent) {
+ if (!isBrsFile(event.file)) {
+ return;
}
- const program = file.program
-
- file.ast.walk(createVisitor({
- FunctionExpression: (func) => {
- if (!this.isAsyncTask(func.functionStatement)) {
- return
+ const program = event.program;
+ event.file.ast.walk(createVisitor({
+ FunctionStatement: (funcStmt) => {
+ if (!this.isAsyncTask(funcStmt)) {
+ return;
}
- const functionName = func.functionStatement!.name.text
- const hasParams = func.functionStatement!.func.parameters.length > 0
- const taskName = `${functionName}_AsyncTask`
+ const functionName = this.getFunctionName(funcStmt);
+ const hasParams = funcStmt.func.parameters.length > 0;
+ const taskName = this.getTaskName(funcStmt);
+
+ const bsFileContent = this.generateBsTask(functionName, hasParams, event.file);
+ const bsFilePath = `components/AsyncTask/generated/${taskName}.bs`;
- const bs = this.generateBsTask(functionName, hasParams, file)
- const bsFile = `components/AsyncTask/generated/${taskName}.bs`
+ const xmlFileContent = this.generateXmlTask(taskName);
+ const xmlFilePath = `components/AsyncTask/generated/${taskName}.xml`;
- const xml = this.generateXmlTask(taskName, bsFile)
- const xmlFile = `components/AsyncTask/generated/${taskName}.xml`
+ program.diagnostics.clearByFilter({ file: event.file, tag: this.name });
- if (program.hasFile(xmlFile)) {
- const currentContent = program.getFile(xmlFile).fileContents
- if (currentContent !== xml) {
- file.addDiagnostics([{
- file: file,
- range: func.range,
- message: `AsyncTaskPlugin: file ${xmlFile} already exists`,
+ if (program.hasFile(xmlFilePath)) {
+ const currentContent = (program.getFile(xmlFilePath) as XmlFile).fileContents;
+ if (currentContent !== xmlFileContent) {
+ program.diagnostics.register({
+ file: event.file,
+ range: funcStmt.tokens.name.location.range,
+ message: `AsyncTaskPlugin: file ${xmlFilePath} already exists`,
severity: DiagnosticSeverity.Error,
code: 'ASYNC_TASK_FILE_EXISTS',
- }]);
+ }, { tags: [this.name] });
}
}
- file.program.setFile(xmlFile, xml)
-
- if (program.hasFile(bsFile)) {
- const currentContent = program.getFile(bsFile).fileContents
- if (currentContent !== bs) {
- file.addDiagnostics([{
- file: file,
- range: func.range,
- message: `AsyncTaskPlugin: file ${bsFile} already exists`,
+
+ if (program.hasFile(bsFilePath)) {
+ const currentContent = (program.getFile(bsFilePath) as BrsFile).fileContents;
+ if (currentContent !== bsFileContent) {
+ program.diagnostics.register({
+ file: event.file,
+ range: funcStmt.tokens.name.location.range,
+ message: `AsyncTaskPlugin: file ${bsFilePath} already exists`,
severity: DiagnosticSeverity.Error,
code: 'ASYNC_TASK_FILE_EXISTS',
- }]);
+ }, { tags: [this.name] });
}
}
- file.program.setFile(bsFile, bs)
- },
+
+ event.program.setFile(xmlFilePath, xmlFileContent);
+ event.program.setFile(bsFilePath, bsFileContent);
+ }
}), {
- walkMode: WalkMode.visitExpressionsRecursive
+ walkMode: WalkMode.visitStatements
});
}
- beforeProgramValidate(program: Program) {
- this.generateTaskListEnum(program);
+ beforeProgramValidate(event: BeforeProgramValidateEvent) {
+ this.generateTaskListEnum(event.program);
}
isAsyncTask(functionStatement: FunctionStatement | undefined) {
@@ -88,9 +95,17 @@ export class AsyncTaskPlugin implements CompilerPlugin {
return false
}
+ getFunctionName(funcStmt: FunctionStatement) {
+ return funcStmt.getName(ParseMode.BrightScript);
+ }
+
+ getTaskName(funcStmt: FunctionStatement) {
+ return this.getFunctionName(funcStmt) + '_AsyncTask';
+ }
+
generateBsTask(functionName: string, hasInput: boolean, file: BscFile): string {
return `
-import "pkg:/${file.pkgPath}"
+import "pkg:/${file.destPath}"
import "pkg:/source/utils/ErrorUtils.bs"
function Init()
@@ -127,7 +142,7 @@ end function
`
}
- generateXmlTask(taskName: string, bsFile: string): string {
+ generateXmlTask(taskName: string): string {
return `
@@ -148,22 +163,22 @@ end function
}
file.ast.walk(createVisitor({
- FunctionExpression: (func) => {
- if (!this.isAsyncTask(func.functionStatement)) {
+ FunctionStatement: (funcStmt) => {
+ if (!this.isAsyncTask(funcStmt)) {
return
}
- acc.add(func.functionStatement!.name.text)
+ acc.add(funcStmt)
},
}), {
- walkMode: WalkMode.visitExpressionsRecursive
+ walkMode: WalkMode.visitStatements
});
return acc
- }, new Set());
+ }, new Set());
const enumItems = Array.from(asyncTasks).map((task) => {
- return `${task} = "${task}_AsyncTask"`
+ return `${this.getFunctionName(task)} = "${this.getTaskName(task)}"`
})
const content = 'enum Tasks\n ' + enumItems.join('\n ') + '\nend enum\n';
diff --git a/tools/bs-plugins/bindings-plugin.ts b/tools/bs-plugins/bindings-plugin.ts
index d3b699bca..1bf7ab61e 100644
--- a/tools/bs-plugins/bindings-plugin.ts
+++ b/tools/bs-plugins/bindings-plugin.ts
@@ -1,17 +1,19 @@
// Plugin to generate node bindings for AutoBind components
import {
- BeforeFileTranspileEvent,
- BscFile,
+ AfterProvideFileEvent,
+ BeforePrepareFileEvent,
CompilerPlugin,
+ ParseMode,
XmlFile,
+ createSGScript,
isBrsFile,
isXmlFile,
isXmlScope,
util
} from "brighterscript";
import path from "path";
-import { SGNode, SGScript } from "brighterscript/dist/parser/SGTypes";
+import { SGNode } from "brighterscript/dist/parser/SGTypes";
import { RawCodeStatement } from "./Classes/RawCodeStatement";
@@ -39,7 +41,12 @@ export class BindingsPlugin implements CompilerPlugin {
private bindingsForFile: { [key: string]: Bindings } = {};
- afterFileParse(file: BscFile) {
+ afterProvideFile(event: AfterProvideFileEvent) {
+ if (event.files.length !== 1) {
+ return;
+ }
+
+ const file = event.files[0];
if (!isXmlFile(file)) {
return;
}
@@ -57,22 +64,19 @@ export class BindingsPlugin implements CompilerPlugin {
program.setFile(bindingsFilePath, bindings_script_template);
}
- const scriptTag = new SGScript();
- scriptTag.type = "text/brightscript"
- scriptTag.uri = util.getRokuPkgPath(bindingsFilePath);
- if (!file.ast.component!.scripts) {
- file.ast.component!.scripts = [];
- }
- file.ast.component!.scripts.push(scriptTag);
+ const scriptTag = createSGScript({
+ type: "text/brightscript",
+ uri: util.sanitizePkgPath(bindingsFilePath)
+ });
+ file.ast.componentElement!.addChild(scriptTag);
this.deleteBindingsInFields(file);
- this.deleteBindingsInChildPropsNode(file.ast.component!.children);
-
+ this.deleteBindingsInChildPropsNode(file.ast.componentElement!.childrenElement);
file.parser.invalidateReferences();
}
- beforeFileTranspile(event: BeforeFileTranspileEvent) {
- if (!isBrsFile(event.file) || !event.file.pkgPath.endsWith('_bindings.bs')) {
+ beforePrepareFile(event: BeforePrepareFileEvent) {
+ if (!isBrsFile(event.file) || !event.file.pkgPath.endsWith('_bindings.brs')) {
return
}
@@ -92,7 +96,7 @@ export class BindingsPlugin implements CompilerPlugin {
}
const bindingsFunction = event.file.callables[0];
- if (bindingsFunction.functionStatement.name.text !== bindings_function_name) {
+ if (bindingsFunction.functionStatement.getName(ParseMode.BrightScript) !== bindings_function_name) {
throw new Error(`Bindings file ${event.file.pkgPath} should have exactly one function named ${bindings_function_name}`);
}
@@ -125,7 +129,7 @@ export class BindingsPlugin implements CompilerPlugin {
}
getBindingsForXmlFile(xmlFile: XmlFile): Bindings {
- const isAutoBindComponent = !!xmlFile.ast.component?.api?.fields?.find((field) => {
+ const isAutoBindComponent = !!xmlFile.ast.componentElement?.interfaceElement?.fields?.find((field) => {
return field.id === 'binding_done';
}) && xmlFile.componentName.text !== 'AutoBind';
@@ -133,16 +137,16 @@ export class BindingsPlugin implements CompilerPlugin {
return { isAutoBindComponent, fields: {}, childProps: {} };
}
- const fields = xmlFile.ast.component?.api?.fields?.filter((field) => {
- return field.attributes.find((attr) => attr.key.text === 'bind');
+ const fields = xmlFile.ast.componentElement?.interfaceElement?.fields?.filter((field) => {
+ return field.attributes.find((attr) => attr.key === 'bind');
});
const childProps: ChildProps = {};
- this.getChildBindings(childProps, xmlFile.ast.component?.children);
+ this.getChildBindings(childProps, xmlFile.ast.componentElement?.childrenElement);
const bindingFields = fields?.reduce((acc, field) => {
- const bindAttr = field.attributes.find((attr) => attr.key.text === 'bind');
- acc[field.id] = bindAttr!.value.text;
+ const bindAttr = field.attributes.find((attr) => attr.key === 'bind');
+ acc[field.id] = bindAttr!.value!;
return acc;
}, {} as { [key: string]: string }) || {};
@@ -154,30 +158,30 @@ export class BindingsPlugin implements CompilerPlugin {
return;
}
if (node.id) {
- const props = node.attributes.filter((attr) => attr.value.text.startsWith('bind:'));
+ const props = node.attributes.filter((attr) => attr.value!.startsWith('bind:'));
if (props.length) {
if (!bindings[node.id]) {
bindings[node.id] = {};
}
for (let i = 0; i < props.length; i++) {
const prop = props[i];
- bindings[node.id][prop.key.text] = prop.value.text.replace('bind:', '');
+ bindings[node.id][prop.key] = prop.value!.replace('bind:', '');
}
}
}
- if (node.children) {
- for (let i = 0; i < node.children.length; i++) {
- const child = node.children[i];
- this.getChildBindings(bindings, child);
- }
+ for (let i = 0; i < node.elements.length; i++) {
+ const child = node.elements[i];
+ this.getChildBindings(bindings, child);
}
}
deleteBindingsInFields(xmlFile: XmlFile) {
- xmlFile.ast.component!.api!.fields.forEach((field) => {
- field.attributes = field.attributes.filter((attr) => {
- return attr.key.text !== 'bind';
- });
+ const fields = xmlFile.ast.componentElement?.interfaceElement?.fields;
+ if (!fields) {
+ return;
+ }
+ fields.forEach((field) => {
+ field.removeAttribute('bind');
});
}
@@ -185,15 +189,18 @@ export class BindingsPlugin implements CompilerPlugin {
if (!node) {
return;
}
- node.attributes = node.attributes.filter((attr) => {
- return !attr.value.text.startsWith('bind:');
- });
- if (node.children) {
- for (let i = 0; i < node.children.length; i++) {
- const child = node.children[i];
- this.deleteBindingsInChildPropsNode(child);
+ if (node.attributes) {
+ for (let i = node.attributes.length - 1; i >= 0; i--) {
+ const attr = node.attributes[i];
+ if (attr.value?.startsWith('bind:')) {
+ node.removeAttribute(attr.key);
+ }
}
}
+ for (let i = 0; i < node.elements.length; i++) {
+ const child = node.elements[i];
+ this.deleteBindingsInChildPropsNode(child);
+ }
}
}
diff --git a/tools/bs-plugins/component-includes-plugin.ts b/tools/bs-plugins/component-includes-plugin.ts
index 20009359c..8ba97e621 100644
--- a/tools/bs-plugins/component-includes-plugin.ts
+++ b/tools/bs-plugins/component-includes-plugin.ts
@@ -1,15 +1,16 @@
// Plugin to allow including other components in a component
import {
- BscFile,
+ AfterProvideFileEvent,
CompilerPlugin,
DiagnosticSeverity,
Program,
XmlFile,
+ createSGScript,
isXmlFile,
util,
} from 'brighterscript';
-import { SGChildren, SGComponent, SGInterface, SGScript } from 'brighterscript/dist/parser/SGTypes';
+import { SGChildren, SGComponent } from 'brighterscript/dist/parser/SGTypes';
import { globSync } from 'glob';
import fs from 'fs-extra'
import path from 'path';
@@ -17,16 +18,24 @@ import path from 'path';
export class ComponentIncludesPlugin implements CompilerPlugin {
public name = 'ComponentIncludesPlugin';
- afterFileParse(file: BscFile) {
+ afterProvideFile(event: AfterProvideFileEvent) {
+ if (event.files.length !== 1) {
+ return;
+ }
+
+ const file = event.files[0];
if (!isXmlFile(file)) {
return;
}
const program = file.program;
- const component = file.parser.ast.component;
+ program.diagnostics.clearByFilter({ file: file, tag: this.name });
+
+ const component = file.parser.ast.componentElement;
if (!component) {
return;
}
+
const includes = this.getIncludes(component);
if (includes.length === 0) {
return;
@@ -37,33 +46,39 @@ export class ComponentIncludesPlugin implements CompilerPlugin {
const includePaths = globSync(`**/${include.name}.part.xml`, { cwd: rootDir });
if (includePaths.length === 0) {
- file.addDiagnostics([{
+ program.diagnostics.register({
file: file,
range: include.range!,
message: `Could not find include: ${include.name}`,
severity: DiagnosticSeverity.Error,
code: 'INCLUDE_NOT_FOUND',
- }]);
+ }, { tags: [this.name] });
return;
}
+
if (includePaths.length > 1) {
- file.addDiagnostics([{
+ program.diagnostics.register({
file: file,
range: include.range!,
- message: `Found multiple include files for include: ${include}`,
+ message: `Found multiple include files for include: ${include.name}`,
severity: DiagnosticSeverity.Error,
code: 'MULTIPLE_INCLUDES_FOUND',
- }]);
+ }, { tags: [this.name] });
return;
}
const includePath = includePaths[0];
const srcPath = path.join(rootDir, includePath);
- const xmlFile = new XmlFile(srcPath, includePath, program);
+
+ const xmlFile = new XmlFile({
+ srcPath: srcPath,
+ program: program,
+ destPath: includePath,
+ });
const includeFileContents = fs.readFileSync(srcPath, 'utf8');
xmlFile.parse(includeFileContents);
- const includeComponent = xmlFile.ast.component;
+ const includeComponent = xmlFile.ast.componentElement;
if (!includeComponent) {
return;
}
@@ -74,15 +89,17 @@ export class ComponentIncludesPlugin implements CompilerPlugin {
this.addChildren(component, includeComponent);
});
- component.attributes = component.attributes.filter((attr) => {
- return attr.key.text !== 'includes';
- });
-
+ component.removeAttribute('includes');
file.parser.invalidateReferences();
}
addScripts(component: SGComponent, includeComponent: SGComponent, includePath: string, program: Program) {
- component.scripts.push(...includeComponent.scripts);
+ const includeScripts = includeComponent.scriptElements;
+ for (let i = 0; i < includeScripts.length; i++) {
+ const script = includeScripts[i];
+ component.addChild(script);
+ }
+
if (program.options.autoImportComponentScript) {
const possibleCodeBehindPkgPaths = [
includePath.replace('.xml', '.bs'),
@@ -91,68 +108,97 @@ export class ComponentIncludesPlugin implements CompilerPlugin {
possibleCodeBehindPkgPaths.forEach((scriptPkgPath) => {
const scriptFilePath = path.join(program.options.rootDir!, scriptPkgPath);
if (fs.existsSync(scriptFilePath)) {
- const scriptTag = new SGScript();
- scriptTag.type = "text/brightscript"
- scriptTag.uri = util.getRokuPkgPath(scriptPkgPath);
-
- component.scripts.push(scriptTag);
+ const scriptTag = createSGScript({
+ type: "text/brightscript",
+ uri: util.sanitizePkgPath(scriptPkgPath),
+ })
+ component.addChild(scriptTag);
}
});
}
}
addFields(component: SGComponent, includeComponent: SGComponent) {
- if (!includeComponent.api) {
+ const interfaceElement = includeComponent.interfaceElement;
+ if (!interfaceElement) {
return;
}
- if (!includeComponent.api.fields || includeComponent.api.fields.length === 0) {
+
+ const fields = interfaceElement.fields;
+ if (!fields || fields.length === 0) {
return;
}
- if (!component.api) {
- component.api = new SGInterface({ text: 'interface' }, [])
+ for (let i = 0; i < fields.length; i++) {
+ const field = fields[i];
+ let alwaysNotify: boolean | undefined = undefined;
+ if (field.alwaysNotify) {
+ alwaysNotify = field.alwaysNotify === 'true';
+ }
+ component.setInterfaceField(field.id, field.type, field.onChange, alwaysNotify, field.alias);
}
- component.api.fields.push(...includeComponent.api.fields);
}
addFunctions(component: SGComponent, includeComponent: SGComponent) {
- if (!includeComponent.api) {
+ const interfaceElement = includeComponent.interfaceElement;
+ if (!interfaceElement) {
return;
}
- if (!includeComponent.api.functions || includeComponent.api.functions.length === 0) {
+
+ const functions = interfaceElement.functions;
+ if (!functions || functions.length === 0) {
return;
}
- if (!component.api) {
- component.api = new SGInterface({ text: 'interface' }, [])
+
+ for (let i = 0; i < functions.length; i++) {
+ const func = functions[i];
+ component.setInterfaceFunction(func.name);
}
- component.api.functions.push(...includeComponent.api.functions);
}
addChildren(component: SGComponent, includeComponent: SGComponent) {
- if (!includeComponent.children) {
+ const includeChildren = includeComponent.childrenElement;
+ if (!includeChildren) {
return;
}
- if (!includeComponent.children.children || includeComponent.children.children.length === 0) {
+
+ if (includeChildren.elements.length === 0) {
return;
}
- if (!component.children) {
- component.children = new SGChildren({ text: 'children' }, [])
+
+ let componentChildren = component.childrenElement;
+ if (!componentChildren) {
+ componentChildren = new SGChildren({
+ startTagOpen: { text: '<' },
+ startTagName: { text: 'children' },
+ startTagClose: { text: '>' },
+ elements: [],
+ endTagOpen: { text: '' },
+ endTagName: { text: 'children' },
+ endTagClose: { text: '>' }
+ });
+ component.addChild(componentChildren);
+ }
+ for (let i = 0; i < includeChildren.elements.length; i++) {
+ const child = includeChildren.elements[i];
+ componentChildren.elements.push(child);
}
- component.children.children.push(...includeComponent.children.children);
}
-
getIncludes(component?: SGComponent) {
if (!component || !component.attributes) {
return [];
}
return component.attributes.filter((attr) => {
- return attr.key.text === 'includes';
+ return attr.key === 'includes';
}).map((attr) => {
- return attr.value.text.split(',').map((item) => {
+ if (!attr.value) {
+ return [];
+ }
+ return attr.value.split(',').map((item) => {
return {
name: item.trim(),
- range: attr.value.range,
+ range: attr.tokens.value?.location?.range,
}
});
}).flat();
diff --git a/tools/bs-plugins/image-gen-plugin.ts b/tools/bs-plugins/image-gen-plugin.ts
index 55e17ed99..2b967787a 100644
--- a/tools/bs-plugins/image-gen-plugin.ts
+++ b/tools/bs-plugins/image-gen-plugin.ts
@@ -1,7 +1,9 @@
// This plugin converts svg files to png/jpeg files
import {
- CompilerPlugin, FileObj, ProgramBuilder,
+ BeforeBuildProgramEvent,
+ BscFile,
+ CompilerPlugin,
} from 'brighterscript';
import { readFileSync, writeFileSync } from 'fs';
import { existsSync } from 'fs-extra';
@@ -9,6 +11,7 @@ import { globSync } from 'glob';
import md5 from 'crypto-js/md5';
import { join as joinPath } from 'path';
import json5 from 'json5';
+import { AssetFile } from 'brighterscript/dist/files/AssetFile';
const shell = require('shelljs');
const META_EXT = '.meta.json5';
@@ -16,13 +19,13 @@ const META_EXT = '.meta.json5';
export class ImageGenPlugin implements CompilerPlugin {
public name = 'ImageGenPlugin';
- beforePrepublish(builder: ProgramBuilder, files: FileObj[]) {
- // Force generate flag
+ beforeBuildProgram(event: BeforeBuildProgramEvent) {
+ const options = event.program.options;
// @ts-ignore
- const forceGenerateImages = !!builder.options.forceGenerateImages;
+ const forceGenerateImages = !!options.forceGenerateImages;
// Debug flag
// @ts-ignore
- const debug = !!builder.options.debug;
+ const debug = !!options.debug;
if (debug && !forceGenerateImages) {
// Since the plugin does a lot of scanning of files, it is fine not to run it in debug mode
@@ -30,8 +33,7 @@ export class ImageGenPlugin implements CompilerPlugin {
return;
}
- const rootDir = builder.options.rootDir!;
-
+ const rootDir = options.rootDir!;
const svgFiles = globSync(`**/*.svg`, { cwd: rootDir });
svgFiles.forEach((svgFile) => {
@@ -47,7 +49,7 @@ export class ImageGenPlugin implements CompilerPlugin {
const meta = json5.parse(readFileSync(metafile, 'utf8'));
- this.generateImages(svgFile, meta, metafile, rootDir, files, forceGenerateImages);
+ this.generateImages(svgFile, meta, metafile, rootDir, event.files, forceGenerateImages);
});
}
@@ -65,7 +67,7 @@ export class ImageGenPlugin implements CompilerPlugin {
writeFileSync(metafile, json5.stringify(meta, null, 2));
}
- generateImages(svgFile: string, meta: any, metafile: string, rootDir: string, files: FileObj[], forceGenerateImages: boolean) {
+ generateImages(svgFile: string, meta: any, metafile: string, rootDir: string, files: BscFile[], forceGenerateImages: boolean) {
let metaChanged = false;
const inputHash = this.checkFileHash(joinPath(rootDir, svgFile), meta.inputHash);
if (!inputHash.valid) {
@@ -86,7 +88,13 @@ export class ImageGenPlugin implements CompilerPlugin {
this.generateImage(svgFile, output, rootDir);
outputHash = this.checkFileHash(outputFilePath, output.outputHash);
- files.push({ src: outputFilePath, dest: output.outputFilePath })
+ const file = new AssetFile({
+ srcPath: outputFilePath,
+ destPath: outputFilePath.replace(rootDir, ''),
+ data: () => readFileSync(outputFilePath),
+ });
+
+ files.push(file)
output.outputHash = outputHash.hash;
metaChanged = true;
diff --git a/tools/bs-plugins/json-yaml-plugin.ts b/tools/bs-plugins/json-yaml-plugin.ts
index f63b136ea..27e7523fb 100644
--- a/tools/bs-plugins/json-yaml-plugin.ts
+++ b/tools/bs-plugins/json-yaml-plugin.ts
@@ -1,10 +1,9 @@
// This plugin converts json5 and yaml files to json files
import {
- CompilerPlugin, FileObj, ProgramBuilder, util,
+ CompilerPlugin,
+ ProvideFileEvent,
} from 'brighterscript';
-import path from 'path';
-import fs from 'fs-extra'
import json5 from 'json5';
import YAML from "yaml";
@@ -14,28 +13,40 @@ const yamlExtensions = ['.yaml', '.yml'];
export class JsonYamlPlugin implements CompilerPlugin {
public name = 'JsonYamlPlugin';
- afterPrepublish(builder: ProgramBuilder, files: FileObj[]) {
- const jsonFiles = files
- .filter((file) => jsonExtensions.includes(path.extname(file.dest)))
- .map((file) => path.join(builder.options.stagingDir!, file.dest));
+ provideFile(event: ProvideFileEvent) {
+ if (jsonExtensions.includes(event.srcExtension)) {
+ this.handleJson(event);
+ } else if (yamlExtensions.includes(event.srcExtension)) {
+ this.handleYaml(event);
+ }
+ }
- const yamlFiles = files
- .filter((file) => yamlExtensions.includes(path.extname(file.dest)))
- .map((file) => path.join(builder.options.stagingDir!, file.dest));
+ handleJson(event: ProvideFileEvent) {
+ let contents = event.data.value.toString();
+ const json = json5.parse(contents);
+ contents = JSON.stringify(json);
- jsonFiles.forEach((filePath) => {
- let contents = fs.readFileSync(filePath, 'utf8');
- const json = json5.parse(contents);
- contents = JSON.stringify(json);
- fs.writeFileSync(filePath, contents);
+ const file = event.fileFactory.AssetFile({
+ srcPath: event.srcPath,
+ destPath: event.destPath,
+ data: contents,
});
- yamlFiles.forEach((filePath) => {
- let contents = fs.readFileSync(filePath, 'utf8');
- const yaml = YAML.parse(contents);
- contents = JSON.stringify(yaml);
- fs.writeFileSync(filePath, contents);
+ event.files.push(file);
+ }
+
+ handleYaml(event: ProvideFileEvent) {
+ let contents = event.data.value.toString();
+ const yaml = YAML.parse(contents);
+ contents = JSON.stringify(yaml);
+
+ const file = event.fileFactory.AssetFile({
+ srcPath: event.srcPath,
+ destPath: event.destPath,
+ data: contents,
});
+
+ event.files.push(file);
}
}
diff --git a/tools/bs-plugins/locale-validation-plugin.ts b/tools/bs-plugins/locale-validation-plugin.ts
index 48416a3ce..b305631e0 100644
--- a/tools/bs-plugins/locale-validation-plugin.ts
+++ b/tools/bs-plugins/locale-validation-plugin.ts
@@ -7,12 +7,16 @@
// - For that reason, only certain attributes (like "text" and "title") are allowed to have localized values
import {
+ AfterFileValidateEvent,
+ AfterProgramValidateEvent,
+ BeforeProgramValidateEvent,
BrsFile,
- BscFile,
CompilerPlugin,
DiagnosticSeverity,
EnumStatement,
+ LiteralExpression,
Program,
+ Range,
WalkMode,
XmlFile,
createVisitor,
@@ -33,8 +37,8 @@ export class LocaleValidationPlugin implements CompilerPlugin {
private enums: { file: BrsFile, enumStatement: EnumStatement }[] = [];
private localeValues: string[] = [];
- beforeProgramValidate(program: Program) {
- this.enums = this.getEnumsWithLocaleAnnotation(program);
+ beforeProgramValidate(event: BeforeProgramValidateEvent) {
+ this.enums = this.getEnumsWithLocaleAnnotation(event.program);
if (this.enums.length === 0) {
this.localeValues = [];
return;
@@ -43,7 +47,10 @@ export class LocaleValidationPlugin implements CompilerPlugin {
this.localeValues = this.getLocaleValues(this.enums);
}
- afterFileValidate(file: BscFile) {
+ afterFileValidate(event: AfterFileValidateEvent) {
+ const file = event.file;
+ const program = event.program;
+
if (!isXmlFile(file)) {
return;
}
@@ -52,81 +59,81 @@ export class LocaleValidationPlugin implements CompilerPlugin {
return;
}
- if (!file.ast.component) {
+ if (!file.ast.componentElement) {
return;
}
+ program.diagnostics.clearByFilter({ file: file, tag: this.name });
+
const localeValues = this.localeValues;
- const component = file.ast.component;
+ const componentElement = file.ast.componentElement;
+ const fields = componentElement.interfaceElement?.fields;
+ const children = componentElement.childrenElement;
- if (component.api && component.api.fields) {
- component.api.fields.forEach((field) => {
+ if (fields) {
+ fields.forEach((field) => {
const value = field.value;
if (value && localeValues.includes(value)) {
const id = field.id;
if (!allowedXmlAttributes.includes(id)) {
- file.addDiagnostics([{
+ program.diagnostics.register({
file: file,
- range: field.range!,
+ range: field.attributes.find((attr) => attr.key === 'value')?.tokens.value?.location?.range || field.tokens.startTagName.location!.range,
message: `Locale value found in xml component "${value}" but the attribute "${id}" is not allowed to be localized.`,
severity: DiagnosticSeverity.Error,
code: 'LOCALE_VALUE_IN_XML',
- }]);
+ }, { tags: [this.name] });
}
}
});
}
- if (component.children) {
- component.children.children.forEach((child) => {
- this.validateSgNode(child, localeValues, file);
+ if (children) {
+ children.elements.forEach((child) => {
+ this.validateSgNode(program, child, localeValues, file);
});
}
}
- validateSgNode(node: SGNode, localeValues: string[], file: XmlFile) {
+ validateSgNode(program: Program, node: SGNode, localeValues: string[], file: XmlFile) {
node.attributes.forEach((attribute) => {
- const value = attribute.value.text;
+ const value = attribute.value;
if (value && localeValues.includes(value)) {
- const key = attribute.key.text;
+ const key = attribute.key;
if (!allowedXmlAttributes.includes(key)) {
- file.addDiagnostics([{
+ program.diagnostics.register({
file: file,
- range: attribute.value.range!,
+ range: attribute.tokens.value!.location!.range,
message: `Locale value found in xml component: "${value}" but the attribute "${key}" is not allowed to be localized.`,
severity: DiagnosticSeverity.Error,
code: 'LOCALE_VALUE_IN_XML',
- }]);
+ });
}
}
});
- if (!node.children) {
- return;
- }
- node.children.forEach((child) => {
- this.validateSgNode(child, localeValues, file);
+ node.elements.forEach((child) => {
+ this.validateSgNode(program, child, localeValues, file);
});
}
- afterProgramValidate(program: Program) {
+ afterProgramValidate(event: AfterProgramValidateEvent) {
if (this.enums.length === 0 || this.localeValues.length === 0) {
return;
}
+ const program = event.program;
const uniqueLocaleValues = Array.from(new Set(this.localeValues));
if (uniqueLocaleValues.length !== this.localeValues.length) {
const duplicates = this.localeValues.filter((value, index) => this.localeValues.indexOf(value) !== index);
- program.addDiagnostics([{
+
+ program.diagnostics.register({
file: this.enums[0].file,
- range: {
- start: { line: 0, character: 0 },
- end: { line: 0, character: 0 }
- },
+ range: Range.create(0, 0, 0, 0),
message: `Duplicate values in locale enums: ${duplicates.join(', ')}`,
severity: DiagnosticSeverity.Error,
code: 'LOCALE_DUPLICATE_VALUES',
- }]);
+ });
}
this.validateEnglishTranslations(program, this.localeValues, this.enums[0].file);
@@ -135,8 +142,11 @@ export class LocaleValidationPlugin implements CompilerPlugin {
const translationFiles = globSync(`locale/**/translations.ts`, { cwd: rootDir });
translationFiles.forEach((translationFile) => {
const srcPath = pathJoin(rootDir, translationFile);
- const pkgPath = pathJoin('pkg:/', translationFile);
- const xmlFile = new XmlFile(srcPath, pkgPath, program);
+ const xmlFile = new XmlFile({
+ srcPath: srcPath,
+ destPath: translationFile,
+ program: program,
+ });
this.validateTranslations(xmlFile, program, this.localeValues, this.enums[0].file);
});
}
@@ -149,24 +159,25 @@ export class LocaleValidationPlugin implements CompilerPlugin {
const missingKeys = Object.keys(translations).filter((key) => !localeValues.includes(key));
if (missingKeys.length > 0) {
- program.addDiagnostics([{
+
+ program.diagnostics.register({
file: file,
- range: {
- start: { line: 0, character: 0 },
- end: { line: 0, character: 0 }
- },
- message: `Missing keys in enum from ${translationFile.srcPath}: ${missingKeys.join(', ')}`,
+ range: Range.create(0, 0, 0, 0),
+ message: `Missing keys in locale enum from ${translationFile.srcPath}: ${missingKeys.join(', ')}`,
severity: DiagnosticSeverity.Error,
code: 'LOCALE_MISSING_ENUM_KEYS',
- }]);
+ });
}
}
validateEnglishTranslations(program: Program, localeValues: string[], file: BrsFile) {
const translationFile = 'locale/en_US/translations.ts';
const srcPath = pathJoin(program.options.rootDir!, translationFile);
- const pkgPath = pathJoin('pkg:/', translationFile);
- const xmlFile = new XmlFile(srcPath, pkgPath, program);
+ const xmlFile = new XmlFile({
+ srcPath: srcPath,
+ destPath: translationFile,
+ program: program,
+ });
const englishTranslations = this.loadTranslationsFile(program, xmlFile);
if (!englishTranslations) {
@@ -177,30 +188,25 @@ export class LocaleValidationPlugin implements CompilerPlugin {
const mismatchedKeys = Object.keys(englishTranslations).filter((key) => englishTranslations[key] !== key);
if (mismatchedKeys.length > 0) {
const mismatchedTranslations = mismatchedKeys.map((key) => `${key}=${englishTranslations[key]}`);
- program.addDiagnostics([{
+
+ program.diagnostics.register({
file: file,
- range: {
- start: { line: 0, character: 0 },
- end: { line: 0, character: 0 }
- },
+ range: Range.create(0, 0, 0, 0),
message: `Mismatched translations in en_US: ${mismatchedTranslations.join(', ')}`,
severity: DiagnosticSeverity.Error,
code: 'LOCALE_MISMATCHED_EN_TRANSLATIONS',
- }]);
+ });
}
const missingLocaleValues = localeValues.filter((value) => !englishTranslations[value]);
if (missingLocaleValues.length > 0) {
- program.addDiagnostics([{
+ program.diagnostics.register({
file: file,
- range: {
- start: { line: 0, character: 0 },
- end: { line: 0, character: 0 }
- },
+ range: Range.create(0, 0, 0, 0),
message: `Missing translations in en_US: ${missingLocaleValues.join(', ')}`,
severity: DiagnosticSeverity.Error,
code: 'LOCALE_MISSING_TRANSLATIONS',
- }]);
+ });
const xml = missingLocaleValues.reduce((acc, value) => {
acc += `
@@ -230,16 +236,13 @@ export class LocaleValidationPlugin implements CompilerPlugin {
const translation = message.translation[0];
if (acc[source]) {
- program.addDiagnostics([{
+ program.diagnostics.register({
file: translationFile,
- range: {
- start: { line: 0, character: 0 },
- end: { line: 0, character: 0 }
- },
+ range: Range.create(0, 0, 0, 0),
message: `Duplicate translation key: ${source}`,
severity: DiagnosticSeverity.Error,
code: 'LOCALE_DUPLICATE_TRANSLATION_KEY',
- }]);
+ });
}
acc[source] = translation;
@@ -264,15 +267,15 @@ export class LocaleValidationPlugin implements CompilerPlugin {
return enums.reduce((acc, e) => {
e.enumStatement.walk(createVisitor({
EnumMemberStatement: (enumMemberStatement) => {
- const value = enumMemberStatement.getValue();
+ const value = (enumMemberStatement.value as LiteralExpression).tokens.value.text;
if (!value.startsWith('"') || !value.endsWith('"')) {
- e.file.addDiagnostics([{
+ e.file.program.diagnostics.register({
file: e.file,
- range: enumMemberStatement.range,
+ range: enumMemberStatement.tokens.name.location.range,
message: `Locale value should be a string literal`,
severity: DiagnosticSeverity.Error,
code: 'LOCALE_VALUE_NOT_STRING_LITERAL',
- }]);
+ });
return;
}
acc.push(value.slice(1, -1));
@@ -289,8 +292,8 @@ export class LocaleValidationPlugin implements CompilerPlugin {
if (!isBrsFile(file)) {
return acc;
}
- if (file.fileContents.includes('@locale')) {
- program.logger.info('break');
+ if (!file.fileContents.includes('@locale')) {
+ return acc;
}
file.ast.walk(createVisitor({
EnumStatement: (enumStatement) => {
diff --git a/tools/bs-plugins/logger-plugin.ts b/tools/bs-plugins/logger-plugin.ts
index 54cd79434..bd77dd3ee 100644
--- a/tools/bs-plugins/logger-plugin.ts
+++ b/tools/bs-plugins/logger-plugin.ts
@@ -1,15 +1,18 @@
// This plugin generates logging functions based on the usage of the LogError, LogWarn, LogInfo, and LogDebug functions.
import {
- AstEditor,
- BeforeFileTranspileEvent,
- BscFile,
+ BeforeBuildProgramEvent,
+ BeforePrepareFileEvent,
+ BrsFile,
+ CallExpression,
CompilerPlugin,
+ DottedGetExpression,
+ ParseMode,
Program,
SourceLiteralExpression,
TokenKind,
- TranspileObj,
WalkMode,
+ createIdentifier,
createToken,
createVisitor,
isBrsFile,
@@ -48,48 +51,49 @@ export class LoggerPlugin implements CompilerPlugin {
private hasLogger = false;
- beforeProgramTranspile(program: Program, entries: TranspileObj[], editor: AstEditor) {
- this.hasLogger = program.hasFile(loggingFilePath);
+ beforeBuildProgram(event: BeforeBuildProgramEvent) {
+ this.hasLogger = event.program.hasFile(loggingFilePath);
if (!this.hasLogger) {
return;
}
const usedLogFunctions = new Map();
+ const visitor = createVisitor({
+ ExpressionStatement: (statement) => {
+ const expression = statement.expression as CallExpression;
+ if (!expression) {
+ return;
+ }
+ const callee = expression.callee as DottedGetExpression;
+ if (!callee || !callee.getName) {
+ return;
+ }
+ const funcName = callee.getName(ParseMode.BrightScript);
+ if (funcName && logFunctionKeys.includes(funcName)) {
+ const argCount = expression.args.length + 1; // +1 for the file path
+ const newFuncName = `${funcName}${argCount}`;
+ usedLogFunctions.set(newFuncName, {
+ name: funcName,
+ argCount: argCount,
+ });
+ }
+ },
+ });
- for (const entry of entries) {
- if (!isBrsFile(entry.file)) {
+ for (const file of event.files) {
+ if (!isBrsFile(file)) {
continue;
}
- const visitor = createVisitor({
- ExpressionStatement: (statement) => {
- // @ts-ignore
- const funcName = statement.expression.callee?.name?.text;
- if (funcName && logFunctionKeys.includes(funcName)) {
- // @ts-ignore
- const argCount = statement.expression.args.length + 1; // +1 for the file path
- const newFuncName = `${funcName}${argCount}`;
- usedLogFunctions.set(newFuncName, {
- name: funcName,
- argCount: argCount,
- });
- }
- },
+ file.ast.walk(visitor, {
+ walkMode: WalkMode.visitStatementsRecursive
});
-
- for (const func of entry.file.parser.references.functionExpressions) {
- func.body.walk(visitor, {
- walkMode: WalkMode.visitStatements
- });
- }
}
- // @ts-ignore
- const isDebug = !!program.options.debug;
- this.generateLoggingFile(program, usedLogFunctions, isDebug);
+ this.generateLoggingFile(event.program, usedLogFunctions);
}
- beforeFileTranspile(event: BeforeFileTranspileEvent) {
+ beforePrepareFile(event: BeforePrepareFileEvent) {
if (!this.hasLogger) {
return;
}
@@ -103,40 +107,47 @@ export class LoggerPlugin implements CompilerPlugin {
const visitor = createVisitor({
ExpressionStatement: (statement) => {
- // @ts-ignore
- const funcName = statement.expression.callee?.name?.text;
+ const expression = statement.expression as CallExpression;
+ if (!expression) {
+ return;
+ }
+ const callee = expression.callee as DottedGetExpression;
+ if (!callee || !callee.getName) {
+ return;
+ }
+ const funcName = callee.getName(ParseMode.BrightScript);
if (funcName && logFunctionKeys.includes(funcName)) {
- // @ts-ignore
- const args = statement.expression.args;
+ const args = expression.args;
if (isDebug) {
- const t = createToken(TokenKind.SourceLocationLiteral, '', statement.expression.range);
- let sourceExpression = new SourceLiteralExpression(t);
+ const t = createToken(TokenKind.SourceLocationLiteral, '', statement.expression.location);
+ const sourceExpression = new SourceLiteralExpression({ value: t });
event.editor.addToArray(args, 0, sourceExpression);
} else {
- const fileName = path.basename(event.file.pkgPath);
- const line = statement.range!.start.line;
- const t = createToken(TokenKind.StringLiteral, `\"[${fileName}:${line}]\"`, statement.expression.range);
- let sourceExpression = new SourceLiteralExpression(t);
+ const fileName = path.basename(event.file.srcPath);
+ const line = statement.location!.range.start.line + 1; // range.start.line is 0-based
+ const t = createToken(TokenKind.StringLiteral, `\"[${fileName}:${line}]\"`, statement.expression.location);
+ const sourceExpression = new SourceLiteralExpression({ value: t });
event.editor.addToArray(args, 0, sourceExpression);
}
const newFuncName = `${funcName}${args.length}`;
+ const newFuncIdentifier = createIdentifier(newFuncName, callee.location);
event.program.logger.info(this.name, `Replacing ${funcName} with ${newFuncName}`);
- // @ts-ignore
- event.editor.setProperty(statement.expression.callee?.name, 'text', `${newFuncName}`)
+ event.editor.setProperty(callee.tokens, 'name', newFuncIdentifier);
}
},
});
- for (const func of event.file.parser.references.functionExpressions) {
- func.body.walk(visitor, {
- walkMode: WalkMode.visitStatements
- });
- }
+ event.file.ast.walk(visitor, {
+ walkMode: WalkMode.visitStatementsRecursive
+ });
}
- generateLoggingFile(program: Program, usedLogFunctions: Map, isDebug: boolean) {
- const file = program.getFile(loggingFilePath);
+ generateLoggingFile(program: Program, usedLogFunctions: Map) {
+ // @ts-ignore
+ const isDebug = !!program.options.debug;
+
+ const file = program.getFile(loggingFilePath) as BrsFile;
let content = file.fileContents;
content += '\n\' Start of auto-generated functions\n'
@@ -145,7 +156,7 @@ export class LoggerPlugin implements CompilerPlugin {
});
content += '\n\' End of auto-generated functions\n'
- program.setFile(loggingFilePath, content)
+ file.parse(content);
}
generateLoggingFunction(newFunctionName: string, level: string, argCount: number, isDebug: boolean) {
diff --git a/tools/bs-plugins/manifest-edit-plugin.ts b/tools/bs-plugins/manifest-edit-plugin.ts
index 37bfe8386..fb61e7f31 100644
--- a/tools/bs-plugins/manifest-edit-plugin.ts
+++ b/tools/bs-plugins/manifest-edit-plugin.ts
@@ -8,10 +8,11 @@
// - This is only done when --debug is set
import {
+ AfterBuildProgramEvent,
+ BeforeProgramCreateEvent,
BeforeProgramDisposeEvent,
CompilerPlugin,
Program,
- ProgramBuilder
} from 'brighterscript';
import path from 'path';
import fs from 'fs';
@@ -22,7 +23,8 @@ export class ManifestEditPlugin implements CompilerPlugin {
private originalManifestContent?: string;
- beforeProgramCreate(builder: ProgramBuilder) {
+ beforeProgramCreate(event: BeforeProgramCreateEvent) {
+ const builder = event.builder;
const manifestPath = path.join(builder.options.rootDir!, "manifest")
let originalManifestContent = fs.readFileSync(manifestPath, { encoding: 'utf8', flag: 'r' })
@@ -52,8 +54,8 @@ export class ManifestEditPlugin implements CompilerPlugin {
fs.writeFileSync(manifestPath, manifestContent)
}
- afterPublish(builder: ProgramBuilder) {
- this.restoreManifest(builder.program!);
+ afterBuildProgram(event: AfterBuildProgramEvent) {
+ this.restoreManifest(event.program);
}
beforeProgramDispose(event: BeforeProgramDisposeEvent) {
diff --git a/tools/bs-plugins/oninit-plugin.ts b/tools/bs-plugins/oninit-plugin.ts
index 56a567a94..c38edae30 100644
--- a/tools/bs-plugins/oninit-plugin.ts
+++ b/tools/bs-plugins/oninit-plugin.ts
@@ -1,16 +1,16 @@
// This plugin takes functions annotated with @oninit and adds them to the component's Init function
import {
+ AfterFileValidateEvent,
AnnotationExpression,
- BeforeFileTranspileEvent,
+ BeforePrepareFileEvent,
+ BeforeScopeValidateEvent,
BrsFile,
- BscFile,
Callable,
- CallableContainerMap,
CompilerPlugin,
DiagnosticSeverity,
FunctionStatement,
- Scope,
+ ParseMode,
XmlScope,
isBrsFile,
isXmlScope,
@@ -28,7 +28,8 @@ import { RawCodeStatement } from './Classes/RawCodeStatement';
export class OnInitPlugin implements CompilerPlugin {
public name = 'OnInitPlugin';
- afterScopeValidate(scope: Scope, files: BscFile[], callables: CallableContainerMap) {
+ afterScopeValidate(event: BeforeScopeValidateEvent) {
+ const scope = event.scope;
if (!isXmlScope(scope)) {
return;
}
@@ -38,21 +39,25 @@ export class OnInitPlugin implements CompilerPlugin {
return;
}
+ const program = event.program;
+ program.diagnostics.clearByFilter({ file: scope.xmlFile, tag: this.name });
+
const initFunction = this.getInitCallableInScope(scope);
if (!initFunction) {
onInitCallables.forEach((onInitCallable) => {
- scope.xmlFile.addDiagnostics([{
+ program.diagnostics.register({
file: scope.xmlFile,
- range: onInitCallable.annotation!.range,
- message: `function ${onInitCallable.callable.functionStatement.name.text} with @oninit annotation is included in ${scope.name}, but no Init function was found in the component.`,
+ range: onInitCallable.annotation!.location!.range,
+ message: `function ${onInitCallable.callable.functionStatement.getName(ParseMode.BrightScript)} with @oninit annotation is included in ${scope.name}, but no Init function was found in the component.`,
severity: DiagnosticSeverity.Error,
- code: 1818
- }]);
+ code: "ONINIT_NO_INIT_FUNCTION"
+ });
});
}
}
- afterFileValidate(file: BscFile) {
+ afterFileValidate(event: AfterFileValidateEvent) {
+ const file = event.file;
if (!isBrsFile(file)) {
return
}
@@ -71,34 +76,47 @@ export class OnInitPlugin implements CompilerPlugin {
return;
}
+ program.diagnostics.clearByFilter({ file: file, tag: this.name });
+
const initFunction = this.getInitCallableInFile(file);
if (initFunction && scopes.length > 1 && onInitCallables.length > 0) {
- file.addDiagnostics([{
+ program.diagnostics.register({
file,
- range: initFunction.functionStatement!.func.range,
+ range: initFunction.functionStatement.location!.range,
message: `Init function will call @oninit functions, but is included in multiple scopes.`,
severity: DiagnosticSeverity.Error,
- code: 1819
- }]);
+ code: "ONINIT_INIT_FUNCTION_MULTIPLE_SCOPES"
+ });
}
for (let i = 0; i < onInitCallables.length; i++) {
const onInitCallable = onInitCallables[i];
- const functionName = onInitCallable.callable.functionStatement?.name.text;
+ const functionName = onInitCallable.callable.functionStatement.getName(ParseMode.BrightScript);
if (!functionName) {
- file.addDiagnostics([{
+ program.diagnostics.register({
file: onInitCallable.callable.file,
- range: onInitCallable.annotation!.range,
+ range: onInitCallable.annotation!.location!.range,
message: `function with @oninit annotation must have a name`,
severity: DiagnosticSeverity.Error,
- code: 1820
- }]);
+ code: "ONINIT_FUNCTION_NO_NAME"
+ });
+ }
+
+ const paramCount = onInitCallable.callable.functionStatement.func.parameters.length;
+ if (paramCount > 0) {
+ program.diagnostics.register({
+ file: onInitCallable.callable.file,
+ range: onInitCallable.annotation!.location!.range,
+ message: `function ${functionName} with @oninit annotation must not have parameters`,
+ severity: DiagnosticSeverity.Error,
+ code: "ONINIT_FUNCTION_PARAMETERS"
+ });
}
}
}
- beforeFileTranspile(event: BeforeFileTranspileEvent) {
+ beforePrepareFile(event: BeforePrepareFileEvent) {
if (!isBrsFile(event.file)) {
return
}
@@ -138,7 +156,7 @@ export class OnInitPlugin implements CompilerPlugin {
for (let i = 0; i < onInitCallables.length; i++) {
const onInitCallable = onInitCallables[i];
- const functionName = onInitCallable.callable.functionStatement?.name.text;
+ const functionName = onInitCallable.callable.functionStatement.getName(ParseMode.BrightScript);
if (!functionName) {
continue;
}
@@ -171,13 +189,13 @@ export class OnInitPlugin implements CompilerPlugin {
getInitCallableInFile(file: BrsFile): Callable | undefined {
return file.callables.find((callable) => {
- return callable.functionStatement?.name.text === "Init";
+ return callable.functionStatement.getName(ParseMode.BrightScript) === "Init";
});
}
getInitCallableInScope(scope: XmlScope): Callable | undefined {
return scope.getOwnCallables().find((callable) => {
- return callable.callable.functionStatement?.name.text === "Init";
+ return callable.callable.functionStatement.getName(ParseMode.BrightScript) === "Init";
})?.callable;
}
diff --git a/tools/bs-plugins/roku-types-plugin.ts b/tools/bs-plugins/roku-types-plugin.ts
new file mode 100644
index 000000000..02d467ba0
--- /dev/null
+++ b/tools/bs-plugins/roku-types-plugin.ts
@@ -0,0 +1,45 @@
+// This plugin overrides Roku data types used by validation and completion features
+
+import {
+ BeforeProgramCreateEvent,
+ CompilerPlugin,
+} from 'brighterscript';
+
+import { components } from 'brighterscript/dist/roku-types/index';
+
+export class RokuTypesPlugin implements CompilerPlugin {
+ public name = 'RokuTypesPlugin';
+
+ beforeProgramCreate(event: BeforeProgramCreateEvent) {
+ if (components.roappmemorymonitor.interfaces.find(i => i.name === 'ifSetMessagePort')) {
+ event.builder.logger.error('ifSetMessagePort already exists on roAppMemoryMonitor');
+ } else {
+ components.roappmemorymonitor.interfaces.push({
+ "name": "ifSetMessagePort",
+ "url": "https://developer.roku.com/docs/references/brightscript/interfaces/ifsetmessageport.md"
+ });
+ }
+
+ if (components.rosystemlog.interfaces.find(i => i.name === 'ifSetMessagePort')) {
+ event.builder.logger.error('ifSetMessagePort already exists on roSystemLog');
+ } else {
+ components.rosystemlog.interfaces.push({
+ "name": "ifSetMessagePort",
+ "url": "https://developer.roku.com/docs/references/brightscript/interfaces/ifsetmessageport.md"
+ });
+ }
+
+ if (components.rostreamsocket.interfaces.find(i => i.name === 'ifSocketOption')) {
+ event.builder.logger.error('ifSocketOption already exists on roStreamSocket');
+ } else {
+ components.rostreamsocket.interfaces.push({
+ "name": "ifSocketOption",
+ "url": "https://developer.roku.com/docs/references/brightscript/interfaces/ifsocketoption.md"
+ });
+ }
+ }
+}
+
+export default () => {
+ return new RokuTypesPlugin();
+};
diff --git a/tools/bs-plugins/track-transpiled-plugin.ts b/tools/bs-plugins/track-transpiled-plugin.ts
index f9f63483b..a55e23c7f 100644
--- a/tools/bs-plugins/track-transpiled-plugin.ts
+++ b/tools/bs-plugins/track-transpiled-plugin.ts
@@ -11,7 +11,8 @@
// Can be caught by inspecting the diff in the transpiled files.
import {
- CompilerPlugin, FileObj, ProgramBuilder,
+ AfterBuildProgramEvent,
+ CompilerPlugin,
} from 'brighterscript';
import { globSync } from "glob";
import path from 'path';
@@ -20,14 +21,14 @@ import fs from 'fs-extra'
export class TrackTranspiledPlugin implements CompilerPlugin {
public name = 'TrackTranspiledPlugin';
- afterPublish(builder: ProgramBuilder, files: FileObj[]) {
+ afterBuildProgram(event: AfterBuildProgramEvent) {
// @ts-ignore
- if (!builder.options.testMode) {
+ if (!event.program.options.testMode) {
return;
}
- const stagingDir = builder.options.stagingDir!;
- const rootDir = builder.rootDir;
+ const stagingDir = event.program.options.stagingDir!;
+ const rootDir = event.program.options.rootDir!;
const transpiledFolders = globSync('**/*.transpiled', { cwd: rootDir });
for (let i = 0; i < transpiledFolders.length; i++) {
diff --git a/tools/bs-plugins/type-gen-plugin.ts b/tools/bs-plugins/type-gen-plugin.ts
index 538c7ca2c..28a44ab3f 100644
--- a/tools/bs-plugins/type-gen-plugin.ts
+++ b/tools/bs-plugins/type-gen-plugin.ts
@@ -1,5 +1,5 @@
// This plugin generates a file containing functions for type checking and type casting.
-import { CompilerPlugin, Program, SourceObj } from "brighterscript";
+import { BeforeProvideFileEvent, CompilerPlugin } from "brighterscript";
const types = [
{ name: 'Bool', interface: 'ifBoolean', type: 'boolean', defaultValue: 'false' },
@@ -38,18 +38,13 @@ end function`;
export class TypeGenPlugin implements CompilerPlugin {
public name = 'TypeGenPlugin';
- program: Program | undefined;
-
- afterProgramCreate(program: Program) {
- this.program = program;
- }
-
- beforeFileParse(source: SourceObj) {
- if (!source.srcPath.endsWith(typesFilePath)) {
+ beforeProvideFile(event: BeforeProvideFileEvent) {
+ if (!event.srcPath.endsWith(typesFilePath)) {
return;
}
- if (source.source.includes(generatedCodeHeader)) {
+ let content = event.data.value.toString();
+ if (content.includes(generatedCodeHeader)) {
return;
}
@@ -62,8 +57,15 @@ export class TypeGenPlugin implements CompilerPlugin {
}
});
- const fileContents = source.source + generatedCode;
- this.program!.setFile(source.srcPath, fileContents)
+ content += generatedCode;
+
+ const file = event.fileFactory.BrsFile({
+ srcPath: event.srcPath,
+ destPath: event.destPath,
+ });
+ file.fileContents = content;
+
+ event.files.push(file);
}
}
diff --git a/tools/bs-plugins/validation-plugin.ts b/tools/bs-plugins/validation-plugin.ts
index 05a80acdf..2d30cdf29 100644
--- a/tools/bs-plugins/validation-plugin.ts
+++ b/tools/bs-plugins/validation-plugin.ts
@@ -2,10 +2,11 @@
// Rules are defined in the `validation` property of the `bsconfig.json` file.
import {
+ BeforeProgramValidateEvent,
BscFile,
CompilerPlugin,
+ DiagnosticSeverity,
OnFileValidateEvent,
- Program,
Range,
isBrsFile
} from 'brighterscript';
@@ -20,21 +21,21 @@ type Validation = {
export class ValidationPlugin implements CompilerPlugin {
public name = 'ValidationPlugin';
- private validation?: Validation[];
-
- beforeProgramValidate(program: Program) {
- // @ts-ignore
- this.validation = program.options.validation as Validation[] | undefined;
- }
-
onFileValidate(event: OnFileValidateEvent) {
const file = event.file;
+ if (!isBrsFile(file)) {
+ return;
+ }
- if (!this.validation || !isBrsFile(file)) {
+ // @ts-ignore
+ const validation = event.program.options.validation as Validation[] | undefined;
+ if (!validation) {
return;
}
- this.validation.forEach((validation) => {
+ event.program.diagnostics.clearByFilter({ file: file, tag: this.name });
+
+ validation.forEach((validation) => {
const regex = new RegExp(validation.regex, validation.regexFlags);
const fileContents = file.fileContents;
@@ -42,12 +43,14 @@ export class ValidationPlugin implements CompilerPlugin {
while ((match = regex.exec(fileContents)) !== null) {
const line = fileContents.substring(0, match.index).split('\n').length - 1;
const column = match.index - fileContents.lastIndexOf('\n', match.index) - 1;
- file.diagnostics.push({
- code: validation.code,
- message: validation.message,
+
+ event.program.diagnostics.register({
file: file,
- range: Range.create(line, column, line, column + match[0].length)
- });
+ range: Range.create(line, column, line, column + match[0].length),
+ message: validation.message,
+ code: validation.code,
+ severity: DiagnosticSeverity.Error
+ }, { tags: [this.name] });
}
});
}
diff --git a/tools/bs-plugins/web-server-plugin.ts b/tools/bs-plugins/web-server-plugin.ts
index 8b3574b97..4b57e6145 100644
--- a/tools/bs-plugins/web-server-plugin.ts
+++ b/tools/bs-plugins/web-server-plugin.ts
@@ -2,15 +2,15 @@
// associated functions with the router.
import {
- BeforeFileTranspileEvent,
- BscFile,
+ BeforePrepareFileEvent,
ClassStatement,
CompilerPlugin,
+ DiagnosticSeverity,
FunctionExpression,
FunctionStatement,
MethodStatement,
OnFileValidateEvent,
- TokenKind,
+ ParseMode,
WalkMode,
createVisitor,
isBrsFile
@@ -23,11 +23,14 @@ const httpRouterBaseClass = 'HttpRouter';
export class WebServerPlugin implements CompilerPlugin {
public name = 'WebServerPlugin';
- onFileValidate(event: OnFileValidateEvent) {
+ onFileValidate(event: OnFileValidateEvent) {
if (!isBrsFile(event.file)) {
return;
}
+ const program = event.program;
+ program.diagnostics.clearByFilter({ file: event.file, tag: this.name });
+
// Make sure each class that inherits from HttpRouter has a constructor
event.file.ast.walk(createVisitor({
ClassStatement: (classStmt) => {
@@ -35,20 +38,20 @@ export class WebServerPlugin implements CompilerPlugin {
return;
}
- const parentClass = classStmt.parentClassName.expression.name.text;
+ const parentClass = classStmt.parentClassName.getName();
if (parentClass !== httpRouterBaseClass) {
return;
}
const classConstructor = this.getClassConstructor(classStmt);
if (!classConstructor) {
- event.file.addDiagnostics([{
+ program.diagnostics.register({
file: event.file,
- range: classStmt.name.range,
- message: `Class ${classStmt.name.text} extends ${httpRouterBaseClass} and must have a constructor`,
- severity: 1,
+ range: classStmt.tokens.name.location.range,
+ message: `Class ${classStmt.tokens.name.text} extends ${httpRouterBaseClass} and must have a constructor`,
+ severity: DiagnosticSeverity.Error,
code: 'HTTP_ROUTER_NO_CONSTRUCTOR',
- }]);
+ }, { tags: [this.name] });
}
},
}), {
@@ -56,7 +59,7 @@ export class WebServerPlugin implements CompilerPlugin {
});
}
- beforeFileTranspile(event: BeforeFileTranspileEvent) {
+ beforePrepareFile(event: BeforePrepareFileEvent) {
if (!isBrsFile(event.file)) {
return;
}
@@ -84,7 +87,7 @@ export class WebServerPlugin implements CompilerPlugin {
const classConstructor = this.getClassConstructor(classStmt) as MethodStatement;
const method = routeInfo.method === 'ALL' ? '*' : routeInfo.method;
- const stmt = new RawCodeStatement(`m.routes.push({ method: "${method}", path: "${routeInfo.route}", router: m, func: "${func.functionStatement?.name.text}" })`)
+ const stmt = new RawCodeStatement(`m.routes.push({ method: "${method}", path: "${routeInfo.route}", router: m, func: "${func.functionStatement!.getName(ParseMode.BrighterScript)}" })`)
event.editor.arrayPush(classConstructor.func.body.statements, stmt);
},
}), {
@@ -98,7 +101,7 @@ export class WebServerPlugin implements CompilerPlugin {
return false;
}
- const parentClass = classStmt.parentClassName.expression.name.text;
+ const parentClass = classStmt.parentClassName.getName();
return parentClass === httpRouterBaseClass;
}
@@ -140,8 +143,7 @@ export class WebServerPlugin implements CompilerPlugin {
getClassConstructor(classStmt: ClassStatement) {
return classStmt.body.find((stmt) => {
const methodStmt = stmt as MethodStatement;
- // @ts-ignore
- if (methodStmt.name?.kind === TokenKind.New) {
+ if (methodStmt.tokens.name.text === 'new') {
return stmt;
}
});