maestro-roku-bsc-plugin
Version:
Visual studio plugin for maestro brightscript development
370 lines • 16.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BindingProcessor = void 0;
const brighterscript_1 = require("brighterscript");
const undent_1 = require("undent");
const FileType_1 = require("../files/FileType");
const Diagnostics_1 = require("../utils/Diagnostics");
const Utils_1 = require("../utils/Utils");
const BindingType_1 = require("./BindingType");
const XMLTag_1 = require("./XMLTag");
const RawCodeStatement_1 = require("../utils/RawCodeStatement");
const SGTypes_1 = require("brighterscript/dist/parser/SGTypes");
const Utils_2 = require("../Utils");
// eslint-disable-next-line
const fsExtra = require("fs-extra");
const BrsTranspileState_1 = require("brighterscript/dist/parser/BrsTranspileState");
const source_map_1 = require("source-map");
class BindingProcessor {
constructor(fileMap, fileFactory, config) {
this.fileMap = fileMap;
this.fileFactory = fileFactory;
this.config = config;
}
generateCodeForXMLFile(file, program, entry) {
if (!file || (file.fileType !== FileType_1.FileType.Xml)) {
throw new Error('was given a non-xml file');
}
if (!file.associatedFile && file.vmClassName) {
if (this.config.mvvm.createCodeBehindFilesWhenNeeded) {
if (entry) {
let vmFile = this.fileMap.getFileForClass(file.vmClassName);
if (vmFile) {
let xmlFile = file.bscFile;
// eslint-disable-next-line @typescript-eslint/dot-notation
let dg = program['dependencyGraph'];
dg.addDependency(xmlFile.dependencyGraphKey, vmFile.bscFile.dependencyGraphKey);
xmlFile.ast.component.scripts.push(this.createSGScript(xmlFile.pkgPath.replace('.xml', '.brs')));
fsExtra.outputFileSync(entry.outputPath.replace('.xml', '.brs'), this.getCodeBehindText(file, vmFile.bscFile));
}
else {
console.error('missing vm file ' + file.vmClassName);
}
}
else {
console.error('cannot generated codebehind file transpile without entry');
}
}
}
else {
file.bscFile.parser.invalidateReferences();
this.addFindNodeVarsMethodForFile(file);
if (this.config.mvvm.callCreateNodeVarsInInit) {
this.addInitCreateNodeVarsCall(file.associatedFile.bscFile);
}
if (this.config.mvvm.insertCreateVMMethod) {
this.addVMConstructor(file);
}
if (file.bindings.length > 0) {
this.addBindingMethodsForFile(file);
}
file.associatedFile.bscFile.parser.invalidateReferences();
}
}
getCodeBehindText(file, brsFile) {
let text = (0, undent_1.default) `
'generated by maestro-bsc-plugin
function init()
m_createNodeVars()
end function
${this.getNodeVarMethodText(file)}
function m_createVM()
m.vm = ${file.vmClassName.replace(/\./gim, '_')}()
m.vm.initialize()
mx_initializeBindings()
end function
`;
if (file.bindings.length > 0) {
text += this.getBindingMethodsText(file, brsFile);
}
return text;
}
getBindingMethodsText(file, brsFile) {
let bindings = file.bindings.concat(file.getAllParentBindings());
if (bindings.length > 0) {
//TODO convert to pure AST
let bindingInitStatement = this.getBindingInitMethod(bindings.filter((b) => b.properties.type !== BindingType_1.BindingType.static &&
b.properties.type !== BindingType_1.BindingType.code), file.bscFile);
let staticBindingStatement = this.getStaticBindingsMethod(bindings.filter((b) => b.properties.type === BindingType_1.BindingType.static ||
b.properties.type === BindingType_1.BindingType.code), file.bscFile);
let text = '';
let state = new BrsTranspileState_1.BrsTranspileState(brsFile);
text += new source_map_1.SourceNode(null, null, state.srcPath, bindingInitStatement.transpile(state)).toString() + '\n';
text += new source_map_1.SourceNode(null, null, state.srcPath, staticBindingStatement.transpile(state)).toString() + '\n';
return text;
}
}
createSGScript(uri) {
return new SGTypes_1.SGScript({ text: 'script' }, [(0, brighterscript_1.createSGAttribute)('uri', `pkg:/${uri}`)], { text: '' });
}
/**
* given a file, will load it's xml, identify bindings and clear out binding text.
* @param file - file to parse bindings for
*/
parseBindings(file) {
var _a;
if (!file || file.fileType !== FileType_1.FileType.Xml) {
throw new Error('was given a non-xml file');
}
file.resetBindings();
let xmlFile = file.bscFile;
//we have to reparse the xml each time we do this..
xmlFile.parse(file.bscFile.fileContents);
// eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
(_a = xmlFile.ast.component) === null || _a === void 0 ? void 0 : _a.setAttribute('vm', undefined);
xmlFile.needsTranspiled = true;
file.bindings = this.processElements(file);
}
addNodeVarsMethodForRegularXMLFile(file) {
if (!file || file.fileType !== FileType_1.FileType.Xml) {
throw new Error('was given a non-xml file');
}
if (file.associatedFile) {
file.resetBindings();
//we have to reparse the xml each time we do this..
file.bscFile.parse(file.bscFile.fileContents);
this.processElementsForTagIds(file);
if (file.tagIds.size > 0) {
this.addFindNodeVarsMethodForFile(file);
if (this.config.mvvm.callCreateNodeVarsInInit) {
this.addInitCreateNodeVarsCall(file.associatedFile.bscFile);
}
}
}
}
processElementsForTagIds(file) {
let xmlFile = file.bscFile;
file.componentTag = xmlFile.ast.component;
for (let sgNode of this.getAllChildren(file.componentTag)) {
let id = sgNode.getAttributeValue('id');
if (id) {
file.tagIds.add(id);
}
}
// console.log('got tagids', file.tagIds);
}
addInitCreateNodeVarsCall(file) {
let initFunc = file.parser.references.functionStatements.find((f) => f.name.text.toLowerCase() === 'init');
if (initFunc) {
initFunc.func.body.statements.splice(0, 0, new RawCodeStatement_1.RawCodeStatement((0, undent_1.default) `
m_createNodeVars()
`));
}
if (!initFunc && this.config.mvvm.callCreateNodeVarsInInit) {
console.log('init func was not present in ', file.pkgPath, ' adding init function');
let initFunc = (0, Utils_1.makeASTFunction)((0, undent_1.default) `
function init()
m_createNodeVars()
end function
`);
file.parser.references.functionStatements.push(initFunc);
file.parser.references.functionStatementLookup.set('init', initFunc);
file.parser.ast.statements.push(initFunc);
}
}
getAllChildren(component) {
let result = [];
this.getNodeChildren(component.children, result);
return result;
}
getNodeChildren(node, results = []) {
if (node) {
results.push(node);
if (node.children) {
for (let child of node.children) {
this.getNodeChildren(child, results);
}
}
}
}
processElements(file) {
let xmlFile = file.bscFile;
file.componentTag = xmlFile.ast.component;
const allTags = this.getAllChildren(file.componentTag).map((c) => new XMLTag_1.XMLTag(c, file, false));
let interfaceFields = file.componentTag.api.fields.map((c) => new XMLTag_1.XMLTag(c, file, true));
allTags.push(...interfaceFields);
for (let tag of allTags) {
if (tag.id) {
(tag.isTopTag ? file.fieldIds : file.tagIds).add(tag.id);
}
}
let tagsWithBindings = allTags.filter((t) => t.hasBindings);
return brighterscript_1.util.flatMap(tagsWithBindings, (t) => t.bindings);
}
validateBindings(file) {
if (!file ||
(file.fileType !== FileType_1.FileType.Xml)) {
throw new Error('was given a non-xml file');
}
let errorCount = 0;
try {
let allParentIds = file.getAllParentTagIds();
let allParentFieldIds = file.getAllParentFieldIds();
for (let id of file.fieldIds) {
if (allParentFieldIds.has(id)) {
(0, Diagnostics_1.addXmlBindingParentHasDuplicateField)(file, id, 1);
errorCount++;
}
}
for (let id of file.tagIds) {
if (allParentIds.has(id)) {
(0, Diagnostics_1.addXmlBindingParentHasDuplicateField)(file, id, 1);
errorCount++;
}
}
}
catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
(0, Diagnostics_1.addXmlBindingErrorValidatingBindings)(file, e.message);
errorCount++;
}
if (file.bindings.length > 0) {
// if (!file.associatedFile) {
// addXmlBindingNoCodeBehind(file);
// }
if (!file.vmClassName) {
if (errorCount === 0) {
(0, Diagnostics_1.addXmlBindingNoVMClassDefined)(file);
errorCount++;
}
}
else {
file.bindingClass = this.fileMap.allClasses[file.vmClassName];
if (!file.bindingClass) {
(0, Diagnostics_1.addXmlBindingVMClassNotFound)(file);
errorCount++;
}
else {
for (let binding of file.bindings.filter((b) => b.isValid)) {
binding.validateAgainstClass();
errorCount += binding.isValid ? 0 : 1;
}
let bindingFile = this.fileMap.getFileForClass(file.vmClassName);
if (bindingFile) {
bindingFile.bindingTargetFiles.add(file.bscFile);
}
}
}
}
file.isValid = errorCount === 0;
}
addBindingMethodsForFile(file) {
//TODO - use AST for this.
let associatedMFile = file.associatedFile.bscFile;
let bindings = file.bindings.concat(file.getAllParentBindings());
if (bindings.length > 0) {
//TODO convert to pure AST
let bindingInitStatement = this.getBindingInitMethod(bindings.filter((b) => b.properties.type !== BindingType_1.BindingType.static &&
b.properties.type !== BindingType_1.BindingType.code), file.bscFile);
let staticBindingStatement = this.getStaticBindingsMethod(bindings.filter((b) => b.properties.type === BindingType_1.BindingType.static ||
b.properties.type === BindingType_1.BindingType.code), file.bscFile);
if (bindingInitStatement) {
associatedMFile.parser.statements.push(bindingInitStatement);
file.associatedFile.isASTChanged = true;
}
if (staticBindingStatement) {
associatedMFile.parser.statements.push(staticBindingStatement);
file.associatedFile.isASTChanged = true;
}
}
}
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;
}
getBindingInitMethod(bindings, file) {
let func = (0, Utils_1.makeASTFunction)((0, undent_1.default) `
function m_initBindings()
if m.vm <> invalid
vm = m.vm
end if
end function
`);
if (func) {
let ifStatement = func.func.body.statements[0];
for (let binding of bindings) {
ifStatement.thenBranch.statements.push(new RawCodeStatement_1.RawCodeStatement(binding.getInitText(), file, binding.range));
}
ifStatement.thenBranch.statements.push(new RawCodeStatement_1.RawCodeStatement((0, undent_1.default) `
if vm.onBindingsConfigured <> invalid
vm.onBindingsConfigured()
end if
`));
}
return func;
}
getStaticBindingsMethod(bindings, file) {
let func = (0, Utils_1.makeASTFunction)((0, undent_1.default) `
function m_initStaticBindings()
if m.vm <> invalid
vm = m.vm
end if
end function
`);
if (func) {
let ifStatement = func.func.body.statements[0];
for (let binding of bindings) {
ifStatement.thenBranch.statements.push(new RawCodeStatement_1.RawCodeStatement(binding.getStaticText(), file, binding.range));
}
}
return func;
}
addFindNodeVarsMethodForFile(file) {
var _a, _b;
let createNodeVarsFunction = this.makeASTFunction(this.getNodeVarMethodText(file));
let brsFile = file.associatedFile.bscFile;
if (createNodeVarsFunction && ((_b = (_a = file.associatedFile) === null || _a === void 0 ? void 0 : _a.bscFile) === null || _b === void 0 ? void 0 : _b.parser)) {
brsFile.parser.statements.push(createNodeVarsFunction);
file.associatedFile.isASTChanged = true;
}
}
getNodeVarMethodText(file) {
let tagIds = Array.from(file.getAllParentTagIds().values()).concat(Array.from(file.tagIds.values()));
if (tagIds.length > 0) {
return (0, undent_1.default) `
function m_createNodeVars()
for each id in [ ${tagIds.map((id) => `"${id}"`).join(',')}]
m[id] = m.top.findNode(id)
end for
end function
`;
}
else {
return (0, undent_1.default) `
function m_createNodeVars()
end function
`;
}
}
addVMConstructor(file) {
console.log('addVM ', file.fullPath, file.bscFile === undefined);
console.log('no initialize function, adding one');
let func = (0, Utils_1.makeASTFunction)(this.getVMInitializeText(file));
if (func) {
let vmFile = this.fileMap.getFileForClass(file.vmClassName);
if (vmFile) {
(0, Utils_2.addImport)(file.associatedFile.bscFile, vmFile.bscFile.pkgPath);
file.associatedFile.bscFile.parser.statements.push(func);
file.associatedFile.isASTChanged = true;
}
else {
console.error(`file for vm class ${file.vmClassName} was not found!`);
}
}
}
getVMInitializeText(file) {
return (0, undent_1.default) `
function m_createVM()
m.vm = new ${file.vmClassName}()
m.vm.initialize()
mx_initializeBindings()
end function
`;
}
}
exports.BindingProcessor = BindingProcessor;
//# sourceMappingURL=BindingProcessor.js.map