UNPKG

maestro-roku-bsc-plugin

Version:

Visual studio plugin for maestro brightscript development

327 lines 13.6 kB
"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