maestro-roku-bsc-plugin
Version:
Visual studio plugin for maestro brightscript development
327 lines • 13.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAllDottedGetParts = exports.sanitizePkgPath = exports.addImport = exports.createImportStatement = exports.expressionToValue = exports.expressionToString = exports.typeToValueString = exports.standardizePath = exports.getTokenText = exports.createVarExpression = exports.createIfStatement = exports.sanitizeBsJsonString = exports.changeClassMethodBody = exports.addOverriddenMethod = exports.changeFunctionBody = exports.getFunctionBody = exports.makeASTFunction = exports.pad = exports.addSetItems = exports.getRegexMatchValue = exports.getRegexMatchesValues = exports.spliceString = void 0;
const brighterscript_1 = require("brighterscript");
const rokuDeploy = require("roku-deploy");
const Utils_1 = require("./utils/Utils");
function spliceString(str, index, count, add) {
// We cannot pass negative indexes directly to the 2nd slicing operation.
if (index < 0) {
index = str.length + index;
if (index < 0) {
index = 0;
}
}
return str.slice(0, index) + (add || '') + str.slice(index + count);
}
exports.spliceString = spliceString;
function getRegexMatchesValues(input, regex, groupIndex) {
let values = [];
let matches;
// eslint-disable-next-line
while (matches = regex.exec(input)) {
values.push(matches[groupIndex]);
}
return values;
}
exports.getRegexMatchesValues = getRegexMatchesValues;
function getRegexMatchValue(input, regex, groupIndex) {
let matches;
// eslint-disable-next-line
while (matches = regex.exec(input)) {
if (matches.length > groupIndex) {
return matches[groupIndex];
}
}
return null;
}
exports.getRegexMatchValue = getRegexMatchValue;
function addSetItems(setA, setB) {
for (const elem of setB) {
setA.add(elem);
}
}
exports.addSetItems = addSetItems;
function pad(pad, str, padLeft) {
if (typeof str === 'undefined') {
return pad;
}
if (padLeft) {
return (pad + str).slice(-pad.length);
}
else {
return (str + pad).substring(0, pad.length);
}
}
exports.pad = pad;
function makeASTFunction(source) {
let tokens = brighterscript_1.Lexer.scan(source).tokens;
let { statements } = brighterscript_1.Parser.parse(tokens, { mode: brighterscript_1.ParseMode.BrighterScript });
if (statements && statements.length > 0) {
return statements[0];
}
return undefined;
}
exports.makeASTFunction = makeASTFunction;
function getFunctionBody(source) {
let funcStatement = makeASTFunction(source);
return funcStatement ? funcStatement.func.body.statements : [];
}
exports.getFunctionBody = getFunctionBody;
function changeFunctionBody(statement, source) {
let statements = statement.func.body.statements;
statements.splice(0, statements.length);
let newStatements = (typeof source === 'string') ? getFunctionBody(source) : source;
for (let newStatement of newStatements) {
statements.push(newStatement);
}
}
exports.changeFunctionBody = changeFunctionBody;
function addOverriddenMethod(target, name, source) {
let statement = makeASTFunction(`
class wrapper
override function ${name}()
${source}
end function
end class
`);
if ((0, brighterscript_1.isClassStatement)(statement)) {
let classStatement = statement;
target.body.push(classStatement.methods[0]);
return true;
}
return false;
}
exports.addOverriddenMethod = addOverriddenMethod;
function changeClassMethodBody(target, name, source) {
let method = target.methods.find((m) => m.name.text === name);
if ((0, brighterscript_1.isClassMethodStatement)(method)) {
changeFunctionBody(method, source);
return true;
}
return false;
}
exports.changeClassMethodBody = changeClassMethodBody;
function sanitizeBsJsonString(text) {
return `"${text ? text.replace(/"/g, '\'') : ''}"`;
}
exports.sanitizeBsJsonString = sanitizeBsJsonString;
function createIfStatement(condition, statements) {
let ifToken = (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.If, 'if', brighterscript_1.Range.create(1, 1, 1, 999999));
let thenBranch = new brighterscript_1.Block(statements, brighterscript_1.Range.create(1, 1, 1, 1));
return new brighterscript_1.IfStatement({ if: ifToken, then: (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.Then, '', brighterscript_1.Range.create(1, 1, 1, 999999)) }, condition, thenBranch);
}
exports.createIfStatement = createIfStatement;
function createVarExpression(varName, operator, value) {
let variable = (0, brighterscript_1.createVariableExpression)(varName, brighterscript_1.Range.create(1, 1, 1, 999999));
let v = (0, brighterscript_1.createStringLiteral)(value, brighterscript_1.Range.create(1, 1, 1, 999999));
let t = (0, brighterscript_1.createToken)(operator, getTokenText(operator), brighterscript_1.Range.create(1, 1, 1, 999999));
return new brighterscript_1.BinaryExpression(variable, t, v);
}
exports.createVarExpression = createVarExpression;
function getTokenText(operator) {
switch (operator) {
case brighterscript_1.TokenKind.Equal:
return '=';
case brighterscript_1.TokenKind.Plus:
return '+';
case brighterscript_1.TokenKind.Minus:
return '-';
case brighterscript_1.TokenKind.Less:
return '<';
case brighterscript_1.TokenKind.Greater:
return '>';
default:
return '';
}
}
exports.getTokenText = getTokenText;
/**
* A tagged template literal function for standardizing the path. This has to be defined as standalone function since it's a tagged template literal function,
* we can't use `object.tag` syntax.
*/
function standardizePath(stringParts, ...expressions) {
let result = [];
for (let i = 0; i < stringParts.length; i++) {
result.push(stringParts[i], expressions[i]);
}
return driveLetterToLower(rokuDeploy.standardizePath(result.join('')));
}
exports.standardizePath = standardizePath;
function driveLetterToLower(fullPath) {
if (fullPath) {
let firstCharCode = fullPath.charCodeAt(0);
if (
//is upper case A-Z
firstCharCode >= 65 && firstCharCode <= 90 &&
//next char is colon
fullPath[1] === ':') {
fullPath = fullPath[0].toLowerCase() + fullPath.substring(1);
}
}
return fullPath;
}
function typeToValueString(typeToken) {
switch (typeToken.kind) {
case brighterscript_1.TokenKind.Boolean:
return 'false';
case brighterscript_1.TokenKind.Integer:
return '0';
case brighterscript_1.TokenKind.Float:
return '0.0';
case brighterscript_1.TokenKind.Double:
return '0.0';
case brighterscript_1.TokenKind.LongInteger:
return '0';
case brighterscript_1.TokenKind.Object:
return '{}';
case brighterscript_1.TokenKind.String:
return '""';
default:
switch (typeToken.text.toLowerCase()) {
case 'mc.types.array':
case 'sc.types.array':
return '[]';
case 'mc.types.assocarray':
case 'sc.types.assocarray':
return '{}';
//TODO look up an enum using this type
default:
return 'invalid';
}
}
}
exports.typeToValueString = typeToValueString;
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
function expressionToString(expr) {
if (!expr) {
return 'invalid';
}
if ((0, brighterscript_1.isUnaryExpression)(expr) && (0, brighterscript_1.isLiteralNumber)(expr.right)) {
return numberExpressionToValue(expr.right, expr.operator.text).toString();
}
if ((0, brighterscript_1.isLiteralString)(expr)) {
//remove leading and trailing quotes
return `"${expr.token.text.replace(/^"/, '').replace(/"$/, '')}"`;
}
if ((0, brighterscript_1.isLiteralNumber)(expr)) {
return numberExpressionToValue(expr).toString();
}
if ((0, brighterscript_1.isLiteralBoolean)(expr)) {
return expr.token.text.toLowerCase() === 'true' ? 'true' : 'false';
}
if ((0, brighterscript_1.isArrayLiteralExpression)(expr)) {
return `[${expr.elements
.filter(e => !(0, brighterscript_1.isCommentStatement)(e))
.map(e => expressionToString(e))}]`;
}
if ((0, brighterscript_1.isAALiteralExpression)(expr)) {
let text = `{${expr.elements.reduce((acc, e) => {
if (!(0, brighterscript_1.isCommentStatement)(e)) {
const sep = acc === '' ? '' : ', ';
acc += `${sep}${e.keyToken.text}: ${expressionToString(e.value)}`;
}
return acc;
}, '')}}`;
return text;
}
return 'invalid';
}
exports.expressionToString = expressionToString;
function expressionToValue(expr) {
if (!expr) {
return undefined;
}
if ((0, brighterscript_1.isEnumMemberStatement)(expr)) {
return expressionToValue(expr.value);
}
if ((0, brighterscript_1.isUnaryExpression)(expr) && (0, brighterscript_1.isLiteralNumber)(expr.right)) {
return numberExpressionToValue(expr.right, expr.operator.text);
}
if ((0, brighterscript_1.isLiteralString)(expr)) {
//remove leading and trailing quotes
return expr.token.text.replace(/^"/, '').replace(/"$/, '');
}
if ((0, brighterscript_1.isLiteralNumber)(expr)) {
return numberExpressionToValue(expr);
}
if ((0, brighterscript_1.isLiteralBoolean)(expr)) {
return expr.token.text.toLowerCase() === 'true';
}
if ((0, brighterscript_1.isArrayLiteralExpression)(expr)) {
return expr.elements
.filter(e => !(0, brighterscript_1.isCommentStatement)(e))
.map(e => expressionToValue(e));
}
if ((0, brighterscript_1.isAALiteralExpression)(expr)) {
return expr.elements.reduce((acc, e) => {
if (!(0, brighterscript_1.isCommentStatement)(e)) {
acc[e.keyToken.text] = expressionToValue(e.value);
}
return acc;
}, {});
}
return undefined;
}
exports.expressionToValue = expressionToValue;
function numberExpressionToValue(expr, operator = '') {
if ((0, brighterscript_1.isIntegerType)(expr.type) || (0, brighterscript_1.isLongIntegerType)(expr.type)) {
return parseInt(operator + expr.token.text);
}
else {
return parseFloat(operator + expr.token.text);
}
}
function createImportStatement(pkgPath, range) {
let importToken = (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.Import, 'import', range);
let filePathToken = (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.SourceFilePathLiteral, `"${sanitizePkgPath(pkgPath)}"`, range);
return new brighterscript_1.ImportStatement(importToken, filePathToken);
}
exports.createImportStatement = createImportStatement;
function addImport(file, pkgPath, astEditor) {
//TODO - not sure how to do this; ask Bron
let existingImports = file.parser.ast.statements.find((el) => (0, brighterscript_1.isImportStatement)(el) && el.filePath === pkgPath);
if (!existingImports) {
let importStatement = createImportStatement(pkgPath, (0, Utils_1.createRange)(brighterscript_1.Position.create(1, 1)));
file.parser.ast.statements = [importStatement, ...file.parser.ast.statements];
file.parser.invalidateReferences();
const scriptImport = {
//TODO remove this eventually once the file api lands, pkgPath was deprecated on imports
pkgPath: pkgPath,
destPath: pkgPath,
text: `import "${sanitizePkgPath(pkgPath)}"`,
sourceFile: file
};
file.ownScriptImports.push(scriptImport);
//re-attach the dependency graph which tells the xml file this has changed
// eslint-disable-next-line @typescript-eslint/dot-notation
let dg = file.program['dependencyGraph'];
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
file.attachDependencyGraph(dg);
}
}
exports.addImport = addImport;
/**
* Handles replacing windows `\` with `/`. Also handles v0->v1 compatibility by prepending `pkg:/` if missing
* @param pkgPath the full pkgPath of a file (with or without `pkg:/`). This MUST NOT be a relative path
*/
function sanitizePkgPath(pkgPath) {
pkgPath = pkgPath.replace(/[\\/]+/g, '/');
if (!pkgPath.startsWith('pkg:/')) {
pkgPath = `pkg:/${pkgPath}`;
}
return pkgPath;
}
exports.sanitizePkgPath = sanitizePkgPath;
function getAllDottedGetParts(dg) {
var _a, _b;
let parts = [(_a = dg === null || dg === void 0 ? void 0 : dg.name) === null || _a === void 0 ? void 0 : _a.text];
let nextPart = dg.obj;
while ((0, brighterscript_1.isDottedGetExpression)(nextPart) || (0, brighterscript_1.isVariableExpression)(nextPart)) {
parts.push((_b = nextPart === null || nextPart === void 0 ? void 0 : nextPart.name) === null || _b === void 0 ? void 0 : _b.text);
nextPart = (0, brighterscript_1.isDottedGetExpression)(nextPart) ? nextPart.obj : undefined;
}
return parts.reverse();
}
exports.getAllDottedGetParts = getAllDottedGetParts;
//# sourceMappingURL=Utils.js.map