maestro-roku-bsc-plugin
Version:
Visual studio plugin for maestro brightscript development
912 lines • 71.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MaestroPlugin = void 0;
const brighterscript_1 = require("brighterscript");
const ProjectFileMap_1 = require("./lib/files/ProjectFileMap");
const BindingProcessor_1 = require("./lib/binding/BindingProcessor");
const minimatch = require("minimatch");
const FileType_1 = require("./lib/files/FileType");
const ImportProcessor_1 = require("./lib/importSupport/ImportProcessor");
const ReflectionUtil_1 = require("./lib/reflection/ReflectionUtil");
const FileFactory_1 = require("./lib/utils/FileFactory");
const NodeClassUtil_1 = require("./lib/node-classes/NodeClassUtil");
const RawCodeStatement_1 = require("./lib/utils/RawCodeStatement");
const Diagnostics_1 = require("./lib/utils/Diagnostics");
const Utils_1 = require("./lib/utils/Utils");
const SGApi_1 = require("./SGApi");
const DynamicType_1 = require("brighterscript/dist/types/DynamicType");
const BrsTranspileState_1 = require("brighterscript/dist/parser/BrsTranspileState");
const Utils_2 = require("./lib/Utils");
class MaestroPlugin {
constructor() {
this.name = 'maestroPlugin';
this.isFrameworkAdded = false;
this.mFilesToValidate = new Map();
this.dirtyCompFilePaths = new Set();
this.dirtyNodeClassPaths = new Set();
this.filesThatNeedAddingBeforeProgramValidate = new Map();
this.filesThatNeedParsingInBeforeProgramValidate = new Map();
this.skips = {
'__classname': true,
'addreplace': true,
'lookup': true,
'lookupci': true,
'doesexist': true,
'delete': true,
'clear': true,
'keys': true,
'items': true,
'setmodecasesensitive': true,
'append': true,
'count': true
};
}
getConfig(config) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
//ignore roku modules by default
config = (_a = config === null || config === void 0 ? void 0 : config.maestro) !== null && _a !== void 0 ? _a : {
buildTimeImports: {}
};
config.excludeFilters = (_b = config.excludeFilters) !== null && _b !== void 0 ? _b : ['**/roku_modules/**/*'];
config.addFrameworkFiles = (_c = config.addFrameworkFiles) !== null && _c !== void 0 ? _c : true;
config.stripParamTypes = (_d = config.stripParamTypes) !== null && _d !== void 0 ? _d : true;
config.processXMLFiles = (_e = config.processXMLFiles) !== null && _e !== void 0 ? _e : true;
config.updateAsFunctionCalls = (_f = config.updateAsFunctionCalls) !== null && _f !== void 0 ? _f : true;
config.updateObserveCalls = (_g = config.updateObserveCalls) !== null && _g !== void 0 ? _g : true;
config.allowNotificationAnnotations = (_h = config.allowNotificationAnnotations) !== null && _h !== void 0 ? _h : true;
config.paramStripExceptions = (_j = config.paramStripExceptions) !== null && _j !== void 0 ? _j : ['onKeyEvent'];
config.applyStrictToAllClasses = (_k = config.applyStrictToAllClasses) !== null && _k !== void 0 ? _k : true;
config.nodeClasses = (_l = config.nodeClasses) !== null && _l !== void 0 ? _l : {};
config.buildTimeImports = (_m = config.buildTimeImports) !== null && _m !== void 0 ? _m : {};
config.nodeClasses.buildForIDE = config.buildForIDE; //legacy support
config.nodeClasses.buildForIDE = config.nodeClasses.buildForIDE === undefined ? false : config.nodeClasses.buildForIDE;
config.mvvm = (_o = config.mvvm) !== null && _o !== void 0 ? _o : {};
config.mvvm.insertXmlBindingsEarly = config.mvvm.insertXmlBindingsEarly === undefined ? false : config.mvvm.insertXmlBindingsEarly;
config.mvvm.createCodeBehindFilesWhenNeeded = config.mvvm.createCodeBehindFilesWhenNeeded === undefined ? true : config.mvvm.createCodeBehindFilesWhenNeeded;
config.mvvm.insertCreateVMMethod = config.mvvm.insertCreateVMMethod === undefined ? true : config.mvvm.insertCreateVMMethod;
config.mvvm.callCreateVMMethodInInit = config.mvvm.callCreateVMMethodInInit === undefined ? true : config.mvvm.callCreateVMMethodInInit;
config.mvvm.callCreateNodeVarsInInit = config.mvvm.callCreateNodeVarsInInit === undefined ? true : config.mvvm.callCreateNodeVarsInInit;
config.reflection = (_p = config.refelection) !== null && _p !== void 0 ? _p : {};
config.reflection.generateReflectionFunctions = config.reflection.generateReflectionFunctions === undefined ? true : config.reflection.generateReflectionFunctions;
config.reflection.excludeFilters = config.reflection.excludeFilters === undefined ? ['**/roku_modules/**/*', '**/*.spec.bs'] : config.reflection.excludeFilters;
config.extraValidation = (_q = config.extraValidation) !== null && _q !== void 0 ? _q : {};
config.extraValidation.doExtraValidation = config.extraValidation.doExtraValidation === undefined ? true : config.extraValidation.doExtraValidation;
config.extraValidation.doExtraImportValidation = config.extraValidation.doExtraImportValidation === undefined ? false : config.extraValidation.doExtraImportValidation;
config.extraValidation.excludeFilters = config.extraValidation.excludeFilters === undefined ? [] : config.extraValidation.excludeFilters;
config.transpileAsNodeAsAny = (_r = config.transpileAsNodeAsAny) !== null && _r !== void 0 ? _r : false;
config.validateAnnotations = (_s = config.validateAnnotations) !== null && _s !== void 0 ? _s : true;
return config;
}
afterProgramCreate(program) {
this.program = program;
if (!this.fileMap) {
this.fileMap = new ProjectFileMap_1.ProjectFileMap();
this.fileFactory = new FileFactory_1.FileFactory(program);
this.maestroConfig = this.getConfig(program.options);
this.defaultAnnotations = this.getDefaultAnnotations();
this.bindingProcessor = new BindingProcessor_1.BindingProcessor(this.fileMap, this.fileFactory, this.maestroConfig);
this.reflectionUtil = new ReflectionUtil_1.default(this.fileMap, program, this.maestroConfig);
this.importProcessor = new ImportProcessor_1.default(this.maestroConfig);
this.nodeClassUtil = new NodeClassUtil_1.default(this.fileMap, this.fileFactory);
}
// console.log('MAESTRO apc-----');
if (!this.isFrameworkAdded) {
this.fileFactory.addFrameworkFiles();
this.isFrameworkAdded = true;
}
}
afterFileParse(file) {
// afp(file: (BrsFile | XmlFile)): void {
// console.log('MAESTRO afp-----', file.srcPath);
let mFile = this.fileMap.allFiles[file.srcPath];
if (!mFile) {
mFile = this.fileMap.createFile(file);
}
mFile.bscFile = file;
if (file.pkgPath.startsWith('components/maestro/generated')) {
// eslint-disable-next-line @typescript-eslint/dot-notation
file['diagnostics'] = [];
return;
}
if ((0, brighterscript_1.isBrsFile)(file)) {
this.importProcessor.processDynamicImports(file, this.program);
// this.reflectionUtil.addFile(file);
this.filesThatNeedAddingBeforeProgramValidate.set(mFile.fullPath, mFile);
if (this.shouldParseFile(file)) {
this.filesThatNeedParsingInBeforeProgramValidate.set(mFile.fullPath, mFile);
}
}
else {
mFile.loadXmlContents();
}
}
ncValidation(program) {
for (let [, mFile] of this.filesThatNeedAddingBeforeProgramValidate) {
let file = mFile.bscFile;
this.reflectionUtil.addFile(file);
}
this.filesThatNeedAddingBeforeProgramValidate.clear();
for (let [, mFile] of this.filesThatNeedParsingInBeforeProgramValidate) {
let file = mFile.bscFile;
this.nodeClassUtil.addFile(file, mFile);
for (let nc of [...mFile.nodeClasses.values()]) {
nc.generateCode(this.fileFactory, this.program, this.fileMap, this.maestroConfig.nodeClasses.buildForIDE);
}
if (this.maestroConfig.nodeClasses.buildForIDE) {
if (this.maestroConfig.nodeClasses.generateTestUtils) {
this.nodeClassUtil.generateTestCode(this.program);
}
}
if (mFile.nodeClasses.size > 0) {
this.dirtyNodeClassPaths.add(file.srcPath);
}
this.mFilesToValidate.set(file.pkgPath, file);
}
this.filesThatNeedParsingInBeforeProgramValidate.clear();
}
afterFileValidate(file) {
// console.log('MAESTRO afv-----', file.srcPath);
if (!this.shouldParseFile(file)) {
return;
}
if (file.pkgPath.startsWith('components/maestro/generated')) {
// eslint-disable-next-line @typescript-eslint/dot-notation
file['diagnostics'] = [];
return;
}
// console.log('MAESTRO running stf.....');
let compFile = this.fileMap.allFiles[file.srcPath];
if (this.maestroConfig.processXMLFiles) {
if ((compFile === null || compFile === void 0 ? void 0 : compFile.fileType) === FileType_1.FileType.Xml && (compFile === null || compFile === void 0 ? void 0 : compFile.vmClassName)) {
this.bindingProcessor.parseBindings(compFile);
this.dirtyCompFilePaths.add(file.srcPath);
}
else {
for (let compFile of this.getCompFilesThatHaveFileInScope(file)) {
this.dirtyCompFilePaths.add(compFile.fullPath);
}
}
}
if ((0, brighterscript_1.isBrsFile)(file)) {
if (this.maestroConfig.updateAsFunctionCalls) {
this.validateAsXXXCalls(file);
}
if (this.maestroConfig.validateAnnotations) {
this.validateAnnotations(file);
}
}
}
getDefaultAnnotations() {
var _a;
let annotations = (_a = this.defaultAnnotations) !== null && _a !== void 0 ? _a : Utils_1.defaultAnnotations;
let set = new Set([...annotations].map(value => value.toLowerCase()));
if (this.maestroConfig.customAnnotations) {
for (let ca of this.maestroConfig.customAnnotations) {
set.add(ca.toLowerCase());
}
}
return set;
}
validateAnnotations(file) {
if (this.maestroConfig.updateAsFunctionCalls && this.shouldDoExtraValidationsOnFile(file)) {
this.checkAnnotations(file, this.findAnnotations(file.parser.references.classStatements));
this.checkAnnotations(file, this.findAnnotations(file.parser.references.namespaceStatements));
}
if (this.maestroConfig.allowNotificationAnnotations) {
this.checkNotificationAnnotations(file);
}
}
checkNotificationAnnotations(file) {
let classes = file.parser.references.classStatements;
for (let cs of classes) {
for (let method of cs.methods) {
let annotation = (method.annotations || []).find((a) => a.name.toLowerCase() === 'onnotification');
if (!annotation) {
continue;
}
let classAnnotations = this.findAnnotations(method.parent) || [];
if (!classAnnotations.find((a) => a.name.toLowerCase() === 'node' || a.name.toLowerCase() === 'task')) {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.onNotificationNotSupported)()), { range: cs.range, file: file })]);
continue;
}
if (method.name.text.toLowerCase() === 'new') {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.onNotificationConstructorError)()), { range: method.range, file: file })]);
continue;
}
if (method.func.parameters.length !== 1) {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.onNotificationWrongParameter)()), { range: method.range, file: file })]);
continue;
}
else if (method.func.parameters.length === 1) {
if (method.func.parameters[0].type.toString() !== 'mc.notification') {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.onNotificationWrongParameter)()), { range: method.range, file: file })]);
continue;
}
}
}
}
}
findAnnotations(obj) {
const foundOccurrences = [];
const visitedObjects = new Set();
function search(obj) {
if (visitedObjects.has(obj)) {
return; // Skip if object has already been visited
}
visitedObjects.add(obj);
if (typeof obj === 'object' && obj !== null) {
if ('annotations' in obj) {
foundOccurrences.push(...obj.annotations);
}
for (const key in obj) {
if ((Object.prototype.hasOwnProperty.call(obj, key))) {
search(obj[key]);
}
}
}
}
if (typeof obj === 'object') {
search(obj);
}
return foundOccurrences;
}
checkAnnotations(file, annotations) {
if (annotations) {
for (let annotation of annotations) {
let annotationName = annotation.name.toLowerCase();
if (!this.defaultAnnotations.has(annotationName)) {
(0, Diagnostics_1.addWrongAnnotation)(file, annotation.name, annotation.range.start.line, annotation.range.start.character);
}
if (this.maestroConfig.allowNotificationAnnotations) {
if (annotationName === 'onnotification') {
let args = annotation.getArguments();
if (args.length !== 1) {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.noNameForNotification)()), { range: annotation.range, file: file })]);
}
}
}
else {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.notificationAnnotationDisabled)()), { range: annotation.range, file: file })]);
}
}
}
}
validateAsXXXCalls(file) {
let state = new BrsTranspileState_1.BrsTranspileState(file);
if (this.maestroConfig.updateAsFunctionCalls && this.shouldDoExtraValidationsOnFile(file)) {
for (let functionScope of file.functionScopes) {
// event.file.functionCalls
for (let callExpression of functionScope.func.callExpressions) {
let regex = /as(Any|Array|AA|Boolean|Float|Integer|Long|Node|Point|String)/i;
if ((0, brighterscript_1.isVariableExpression)(callExpression.callee) && (0, brighterscript_1.isExpression)(callExpression.args[0])) {
let name = callExpression.callee.name.text;
if (regex.test(name)) {
try {
let value = callExpression.args[0];
this.getRawTextFromDottedGet(value, state);
this.getRootValue(value);
}
catch (error) {
if (error.message === 'unsupportedValue') {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.noCallsInAsXXXAllowed)(error.functionName)), { range: error.range, file: file })]);
}
else {
console.error('could not update asXXX function call, due to unexpected error', error);
}
}
}
}
}
}
}
}
afterProgramValidate(program) {
this.ncValidation(program);
// console.log('MAESTRO bpv-----');
if (this.maestroConfig.processXMLFiles) {
for (let filePath of [...this.dirtyCompFilePaths.values()]) {
// console.time('Validate bindings');
let file = this.fileMap.allFiles[filePath];
file.bscFile = this.program.getFile(filePath);
file.resetDiagnostics();
this.bindingProcessor.validateBindings(file);
// console.timeEnd('Validate bindings');
}
}
if (!this.maestroConfig.nodeClasses.buildForIDE) {
console.time('Build node classes');
for (let nc of Object.values(this.fileMap.nodeClasses)) {
nc.generateCode(this.fileFactory, this.program, this.fileMap, false);
}
if (this.maestroConfig.nodeClasses.generateTestUtils) {
this.nodeClassUtil.generateTestCode(this.program);
}
console.timeEnd('Build node classes');
}
this.dirtyCompFilePaths.clear();
this.afterProgramValidate2(program);
}
//note this is moved because of changes in how the bsc plugin system worked
//TODO - revisit and see if there's some other tidying up we can do in here
afterProgramValidate2(program) {
for (let f of Object.values(this.program.files)) {
if (f.pkgPath.startsWith('components/maestro/generated')) {
f.diagnostics = [];
if ((0, brighterscript_1.isXmlFile)(f)) {
let s = f.program.getScopeByName(f.pkgPath);
// eslint-disable-next-line @typescript-eslint/dot-notation
s['diagnostics'] = [];
}
}
}
let runtimeFile = this.program.getFile('source/roku_modules/maestro/reflection/Reflection.brs');
if (runtimeFile) {
// eslint-disable-next-line @typescript-eslint/dot-notation
runtimeFile['diagnostics'] = [];
}
if (this.maestroConfig.extraValidation.doExtraValidation) {
console.time('Do additional validations');
for (let f of [...this.mFilesToValidate.values()]) {
let mFile = this.fileMap.allFiles[f.srcPath];
if (mFile && this.shouldDoExtraValidationsOnFile(f)) {
this.checkMReferences(mFile);
this.doExtraValidations(f);
}
}
console.timeEnd('Do additional validations');
this.mFilesToValidate.clear();
console.time('Validate node classes');
for (let filePath of [...this.dirtyNodeClassPaths.values()]) {
for (let nc of this.fileMap.nodeClassesByPath[filePath]) {
if (this.shouldDoExtraValidationsOnFile(nc.file)) {
nc.validate();
nc.validateBaseComponent(this.fileMap);
}
}
}
console.timeEnd('Validate node classes');
}
this.dirtyNodeClassPaths.clear();
}
getCompFilesThatHaveFileInScope(file) {
let compFiles = [];
let lowerPath = file.pkgPath.toLowerCase();
for (let compFile of Object.values(this.fileMap.allFiles).filter((f) => f.fileType === FileType_1.FileType.Xml && f.vmClassName)) {
let xmlFile = compFile.bscFile;
if (xmlFile.getAllDependencies().includes(lowerPath)) {
compFiles.push(compFile);
}
}
return compFiles;
}
shouldParseFile(file) {
if (this.maestroConfig.excludeFilters) {
for (let filter of [...this.maestroConfig.excludeFilters, '**/components/maestro/generated/*']) {
if (minimatch(file.srcPath, filter)) {
return false;
}
}
}
return true;
}
shouldDoExtraValidationsOnFile(file) {
if (!this.maestroConfig.extraValidation.doExtraValidation) {
return false;
}
if (!this.shouldParseFile(file)) {
return false;
}
if (this.maestroConfig.extraValidation.excludeFilters) {
for (let filter of [...this.maestroConfig.extraValidation.excludeFilters, '**/components/maestro/generated/*']) {
if (minimatch(file.srcPath, filter)) {
return false;
}
}
}
return true;
}
beforeFileTranspile(event) {
var _a, _b, _c, _d, _e, _f;
if (!this.shouldParseFile(event.file)) {
return;
}
if (this.maestroConfig.processXMLFiles && this.dirtyCompFilePaths.has(event.file.srcPath)) {
// console.time('Validate bindings');
let file = this.fileMap.allFiles[event.file.srcPath];
if (this.maestroConfig.mvvm.insertXmlBindingsEarly && file.isValid) {
// console.log('adding xml transpiled code for ', file.bscFile.pkgPath);
this.bindingProcessor.generateCodeForXMLFile(file, this.program, event.editor);
}
// console.timeEnd('Validate bindings');
}
if ((0, brighterscript_1.isBrsFile)(event.file)) {
let classes = event.file.parser.references.classStatements;
for (let cs of classes) {
//do class updates in here
let fieldMap = (0, Utils_1.getAllFields)(this.fileMap, cs);
let id = (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.Identifier, '__classname', cs.range);
// eslint-disable-next-line @typescript-eslint/dot-notation
if (!fieldMap.has('__classname')) {
let p = (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.Public, 'public', cs.range);
let a = (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.As, 'as', cs.range);
let s = (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.String, 'string', cs.range);
let classNameStatement = new brighterscript_1.ClassFieldStatement(p, id, a, s, (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.Equal, '=', cs.range), (0, brighterscript_1.createStringLiteral)('"' + cs.getName(brighterscript_1.ParseMode.BrighterScript), cs.range));
cs.body.push(classNameStatement);
cs.fields.push(classNameStatement);
cs.memberMap.__className = classNameStatement;
}
else {
//this is more complicated, have to add this to the constructor
let s = new RawCodeStatement_1.RawCodeStatement(`m.__className = "${cs.getName(brighterscript_1.ParseMode.BrighterScript)}"`, event.file, cs.range);
let constructor = cs.memberMap.new;
if (constructor) {
constructor.func.body.statements.push(s);
}
else {
//have to create a constructor, with same args as parent..
// console.log('TBD: create a constructor to inject for ', cs.name.text);
}
}
let allClassAnnotations = (0, Utils_1.getAllAnnotations)(this.fileMap, cs);
// eslint-disable-next-line @typescript-eslint/dot-notation
if (allClassAnnotations['usesetfield']) {
this.updateFieldSets(cs);
}
this.injectIOCCode(cs, event.file, event.editor);
this.updateObserveCalls(cs, event.file);
if (this.maestroConfig.allowNotificationAnnotations) {
this.updateNotificationAnnotation(cs, event);
}
}
if (this.maestroConfig.stripParamTypes) {
for (let fs of event.file.parser.references.functionExpressions) {
if (fs.returnType && !(0, brighterscript_1.isVoidType)(fs.returnType) && !(0, brighterscript_1.isDynamicType)(fs.returnType)) {
const name = (_c = (_b = (_a = fs.functionStatement) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.text) !== null && _c !== void 0 ? _c : (_f = (_e = (_d = fs.parentFunction) === null || _d === void 0 ? void 0 : _d.functionStatement) === null || _e === void 0 ? void 0 : _e.name) === null || _f === void 0 ? void 0 : _f.text;
if (!this.maestroConfig.paramStripExceptions.includes(name)) {
fs.returnType = new DynamicType_1.DynamicType();
}
}
for (let param of fs.parameters) {
param.asToken = null;
}
}
}
this.updateAsFunctionCalls(event.file);
this.autoInjectNamespaceFunctionCalls(event.file);
}
}
updateAsFunctionCalls(file) {
let state = new BrsTranspileState_1.BrsTranspileState(file);
if (this.maestroConfig.updateAsFunctionCalls) {
let transpileAsNodeAsAny = this.maestroConfig.transpileAsNodeAsAny;
for (let functionScope of file.functionScopes) {
// event.file.functionCalls
for (let callExpression of functionScope.func.callExpressions) {
let regex = /^as(Any|Array|AA|Boolean|Number|Float|Long|Integer|Node|Point|String)/i;
if ((0, brighterscript_1.isVariableExpression)(callExpression.callee) && (0, brighterscript_1.isExpression)(callExpression.args[0])) {
let name = callExpression.callee.name.text;
if (regex.test(name)) {
try {
let value = callExpression.args.shift();
let stringPath = this.getRawTextFromDottedGet(value, state);
name = `mc_get${name.match(regex)[1]}`;
if (transpileAsNodeAsAny && name === 'mc_getNode') {
name = 'mc_getAny';
}
callExpression.callee.name.text = name;
if (stringPath) {
//FIXME do not use a raw code expression here
let rawCode = new RawCodeStatement_1.RawCodeExpression(`"${stringPath}"`, file, value.range);
callExpression.args.unshift(rawCode);
}
else {
callExpression.args.unshift((0, brighterscript_1.createInvalidLiteral)());
}
let rootValue = this.getRootValue(value);
callExpression.args.unshift(rootValue);
}
catch (error) {
if (error.message !== 'unsupportedValue') {
console.error('could not update asXXX function call, due to unexpected error', error);
}
}
}
}
}
}
}
}
autoInjectNamespaceFunctionCalls(file) {
if (this.maestroConfig.updateAsFunctionCalls && Object.keys(this.fileMap.allAutoInjectedNamespaceMethods).length > 0) {
for (let functionScope of file.functionScopes) {
// event.file.functionCalls
for (let callExpression of functionScope.func.callExpressions) {
//1. get the namespace
//2. check if all params are called
//3. if tag
//4. get defaults for missing params
//5. add m
try {
let dg = callExpression.callee;
let parts = this.getAllDottedGetParts(dg);
if (parts.length > 1) {
let fullPathName = this.getAllDottedGetParts(dg).join('.');
let nsFunc = this.fileMap.allAutoInjectedNamespaceMethods[fullPathName];
//is a namespace?
if ((0, brighterscript_1.isFunctionStatement)(nsFunc) && callExpression.args.length < nsFunc.func.parameters.length) {
for (let i = callExpression.args.length; i < nsFunc.func.parameters.length - 1; i++) {
let param = nsFunc.func.parameters[i];
if (param.defaultValue) {
callExpression.args.push(param.defaultValue);
}
else {
callExpression.args.push((0, brighterscript_1.createInvalidLiteral)());
}
}
callExpression.args.push((0, brighterscript_1.createVariableExpression)('m'));
}
}
}
catch (error) {
if (error.message !== 'unsupportedValue') {
console.error('could not update asXXX function call, due to unexpected error', error);
}
}
}
}
}
}
updateObserveCalls(cs, file) {
if (this.maestroConfig.updateObserveCalls) {
for (let method of cs.methods) {
// event.file.functionCalls
for (let callExpression of method.func.callExpressions) {
let regex = /^(observe|unobserve)$/i;
if ((0, brighterscript_1.isDottedGetExpression)(callExpression.callee) && (0, brighterscript_1.isDottedGetExpression)(callExpression.args[0])) {
let name = callExpression.callee.name.text;
if (regex.test(name)) {
try {
let arg0 = callExpression.args[0];
let functionName = arg0.name.text;
arg0 = callExpression.args.shift();
callExpression.args.unshift((0, brighterscript_1.createStringLiteral)(functionName));
callExpression.args.unshift(arg0.obj);
callExpression.callee.name.text = `${name.match(regex)[1]}NodeField`;
}
catch (error) {
if (error.message !== 'unsupportedValue') {
console.error('could not update asXXX function call, due to unexpected error', error);
}
}
}
}
}
}
}
}
updateNotificationAnnotation(cs, event) {
var _a, _b;
for (let method of cs.methods) {
let annotation = (method.annotations || []).find((a) => a.name.toLowerCase() === 'onnotification');
if (annotation && method.name.text.toLowerCase() !== 'new') {
let args = annotation.getArguments();
if (args.length === 1) {
let initializeMethod = cs.memberMap.initialize;
// eslint-disable-next-line
let observeStatement = new RawCodeStatement_1.RawCodeStatement(`m.observeNotification("${args[0].toString()}", m.${(_b = (_a = method === null || method === void 0 ? void 0 : method.func.functionStatement) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.text})`, event.file, cs.range);
if ((0, brighterscript_1.isMethodStatement)(initializeMethod)) {
initializeMethod.func.body.statements.push(observeStatement);
}
else {
let constructor = cs.memberMap.new;
if (constructor) {
constructor.func.body.statements.push(observeStatement);
}
}
}
}
}
}
getRootValue(value) {
let root;
if ((0, brighterscript_1.isDottedGetExpression)(value) || (0, brighterscript_1.isIndexedGetExpression)(value)) {
root = value.obj;
while (root.obj) {
root = root.obj;
}
}
else {
root = value;
}
return root;
}
getRawTextFromDottedGet(value, state) {
let parts = [this.getPathValuePartAsString(value, state)];
let root;
root = value.obj;
while (root) {
if ((0, brighterscript_1.isCallExpression)(root) || (0, brighterscript_1.isCallfuncExpression)(root)) {
throw this.getWrongAsXXXFunctionPartError(root);
}
if (root.obj) {
parts.push(`${this.getPathValuePartAsString(root, state)}`);
}
root = root.obj;
}
let joinedParts = parts.reverse().join('.');
return joinedParts === '' ? undefined : joinedParts;
}
getWrongAsXXXFunctionPartError(expr) {
let error = new Error('unsupportedValue');
// eslint-disable-next-line @typescript-eslint/dot-notation
error['range'] = expr.range;
if ((0, brighterscript_1.isCallExpression)(expr)) {
if ((0, brighterscript_1.isDottedGetExpression)(expr.callee)) {
// eslint-disable-next-line @typescript-eslint/dot-notation
error['functionName'] = expr.callee.name.text;
}
else {
// eslint-disable-next-line @typescript-eslint/dot-notation
error['functionName'] = '#unknown#';
}
}
else if ((0, brighterscript_1.isCallfuncExpression)(expr)) {
// eslint-disable-next-line @typescript-eslint/dot-notation
error['functionName'] = expr.methodName.text;
}
return error;
}
getPathValuePartAsString(expr, state) {
if ((0, brighterscript_1.isCallExpression)(expr) || (0, brighterscript_1.isCallfuncExpression)(expr)) {
throw this.getWrongAsXXXFunctionPartError(expr);
}
if (!expr) {
return undefined;
}
if ((0, brighterscript_1.isDottedGetExpression)(expr)) {
return expr.name.text;
}
else if ((0, brighterscript_1.isIndexedGetExpression)(expr)) {
if ((0, brighterscript_1.isLiteralExpression)(expr.index)) {
return `${expr.index.token.text.replace(/^"/, '').replace(/"$/, '')}`;
// } else if (isVariableExpression(expr.index)) {
// return expr.index.name.text;
}
else {
return `" + rokucommunity_bslib_toString(${expr.index.transpile(state).join('')}) + "`;
// return `\${m.text}}`;
}
}
}
beforeProgramTranspile(program, entries, editor) {
// console.log('++++++', this.maestroConfig.processXMLFiles);
if (!this.maestroConfig.mvvm.insertXmlBindingsEarly && this.maestroConfig.processXMLFiles) {
console.time('Inject bindings into xml files');
for (let entry of entries) {
if ((0, brighterscript_1.isXmlFile)(entry.file)) {
let mFile = this.fileMap.allFiles[entry.file.srcPath];
// eslint-disable-next-line @typescript-eslint/dot-notation
if (mFile.isValid) {
//it's a binding file
this.bindingProcessor.generateCodeForXMLFile(mFile, program, editor, entry);
// console.log('generating code for bindings ', entry.file.pkgPath);
//it's a binding file
}
else if (mFile.bindings.length === 0 && this.shouldParseFile(entry.file)) {
//check if we should add bindings to this anyhow)
// console.log('getting ids for regular xml file ', entry.file.pkgPath);
this.bindingProcessor.addNodeVarsMethodForRegularXMLFile(mFile, editor);
//check if we should add bindings to this anyhow)
}
else {
// console.log('not passing file through binding processor', entry.file.pkgPath);
}
}
}
console.timeEnd('Inject bindings into xml files');
}
//do some nodeclass transformations
for (let nc of Object.values(this.fileMap.nodeClasses)) {
nc.replacePublicMFieldRefs(this.fileMap);
}
}
beforePublish(builder, files) {
console.time('Update reflection runtime file');
this.reflectionUtil.updateRuntimeFile();
console.timeEnd('Update reflection runtime file');
}
updateFieldSets(cs) {
let fieldMap = (0, Utils_1.getAllFields)(this.fileMap, cs, brighterscript_1.TokenKind.Public);
cs.walk((0, brighterscript_1.createVisitor)({
DottedSetStatement: (ds) => {
var _a, _b;
if ((0, brighterscript_1.isVariableExpression)(ds.obj) && ((_b = (_a = ds.obj) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.text) === 'm') {
let lowerName = ds.name.text.toLowerCase();
if (fieldMap.has(lowerName)) {
let callE = new brighterscript_1.CallExpression(new brighterscript_1.DottedGetExpression(ds.obj, (0, brighterscript_1.createIdentifier)('setField', ds.range), (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.Dot, '.', ds.range)), (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.LeftParen, '(', ds.range), (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.RightParen, ')', ds.range), [
(0, brighterscript_1.createStringLiteral)(`"${ds.name.text}"`, ds.range),
ds.value
]);
let callS = new brighterscript_1.ExpressionStatement(callE);
return callS;
}
}
}
}), { walkMode: brighterscript_1.WalkMode.visitAllRecursive });
// cs.walk(createVisitor{
// }, { walkMode: WalkMode.visitAllRecursive });
}
checkMReferences(file) {
for (let cs of file.bscFile.parser.references.classStatements) {
// eslint-disable-next-line @typescript-eslint/dot-notation
if (!this.maestroConfig.applyStrictToAllClasses && !(0, Utils_1.getAllAnnotations)(this.fileMap, cs)['strict']) {
continue;
}
// eslint-disable-next-line @typescript-eslint/dot-notation
// let isNodeClass = cs['_isNodeClass'];
let fieldMap = (0, Utils_1.getAllFields)(this.fileMap, cs);
let funcMap = file.getAllFuncs(cs);
cs.walk((0, brighterscript_1.createVisitor)({
DottedSetStatement: (ds) => {
var _a, _b;
if ((0, brighterscript_1.isVariableExpression)(ds.obj) && ((_b = (_a = ds.obj) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.text) === 'm') {
let lowerName = ds.name.text.toLowerCase();
if (!fieldMap.has(lowerName) && !this.skips[lowerName]) {
if (lowerName !== 'top' && lowerName !== 'global') {
(0, Diagnostics_1.addClassFieldsNotFoundOnSetOrGet)(file, `${ds.obj.name.text}.${ds.name.text} `, cs.name.text, ds.range);
}
}
}
},
DottedGetExpression: (ds) => {
var _a;
if ((0, brighterscript_1.isVariableExpression)(ds.obj) && ((_a = ds === null || ds === void 0 ? void 0 : ds.obj) === null || _a === void 0 ? void 0 : _a.name.text) === 'm') {
let lowerName = ds.name.text.toLowerCase();
//TODO - make this not get dotted get's in function calls
if (!fieldMap.has(lowerName) && !funcMap[lowerName] && !this.skips[lowerName]) {
if (lowerName !== 'top' && lowerName !== 'global') {
// if (!isNodeClass && (lowerName !== 'top' && lowerName !== 'global')) {
(0, Diagnostics_1.addClassFieldsNotFoundOnSetOrGet)(file, `${ds.obj.name.text}.${ds.name.text} `, cs.name.text, ds.range);
}
}
}
}
}), { walkMode: brighterscript_1.WalkMode.visitAllRecursive });
}
}
afterScopeValidate(scope, files, callables) {
var _a, _b;
if (!((_b = (_a = this.maestroConfig) === null || _a === void 0 ? void 0 : _a.extraValidation) === null || _b === void 0 ? void 0 : _b.doExtraValidation)) {
return;
}
//validate the ioc calls
let classMap = scope.getClassMap();
for (let mapItem of [...classMap.values()]) {
let cs = mapItem.item;
let file = mapItem.file;
if (file.pkgPath.startsWith('components/maestro/generated')) {
continue;
}
if (!this.shouldDoExtraValidationsOnFile(file)) {
// console.log('skipping validation on ', file.srcPath);
continue;
}
for (let f of cs.fields) {
let onNotificationAnnotation = (f.annotations || []).find((a) => a.name === 'onNotification');
if (onNotificationAnnotation) {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.onNotificationFieldError)()), { range: cs.range, file: file })]);
}
let annotation = (f.annotations || []).find((a) => a.name === 'inject' || a.name === 'injectClass' || a.name === 'createClass');
if (annotation) {
let args = annotation.getArguments();
if (args.length === 0 || args[0].toString().trim() === '') {
(0, Diagnostics_1.addIOCWrongArgs)(file, `${f.name.text}`, cs.name.text, f.range);
}
else if (annotation.name === 'inject') {
if (args.length === 0 || args.length > 2) {
(0, Diagnostics_1.addIOCNoTypeSupplied)(file, `${f.name.text} `, cs.name.text, f.range);
}
}
else if (annotation.name === 'injectClass') {
if (args.length !== 1) {
(0, Diagnostics_1.addIOCWrongArgs)(file, `${f.name.text} `, cs.name.text, f.range);
}
else {
let targetClass = classMap.get(args[0].toString().toLowerCase());
if (!targetClass) {
(0, Diagnostics_1.IOCClassNotInScope)(file, args[0].toString(), `${f.name.text} `, cs.name.text, f.range);
}
}
}
else if (annotation.name === 'createClass') {
if (args.length < 1) {
(0, Diagnostics_1.addIOCWrongArgs)(file, `${cs.name.text}.${f.name.text} `, cs.name.text, f.range);
}
else {
let targetClass = classMap.get(args[0].toString().toLowerCase());
if (!targetClass) {
(0, Diagnostics_1.IOCClassNotInScope)(file, args[0].toString(), `${f.name.text}`, cs.name.text, f.range);
} //
// TODO - check constructor arg length
//
}
}
}
}
}
}
injectIOCCode(cs, file, astEditor) {
var _a, _b, _c, _d, _e;
// eslint-disable-next-line @typescript-eslint/dot-notation
let isNodeClass = cs['_isNodeClass'] === true;
let state = new BrsTranspileState_1.BrsTranspileState(file);
for (let field of cs.fields) {
let annotation = (field.annotations || []).find((a) => a.name.toLowerCase() === 'inject' || a.name.toLowerCase() === 'injectclass' || a.name.toLowerCase() === 'createclass');
if (annotation) {
let args = annotation.getArguments();
if (annotation.name === 'inject') {
if (args.length < 1) {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.noPathForInject)()), { range: cs.range, file: file })]);
continue;
}
let syncAnnotation = (field.annotations || []).find((a) => a.name.toLowerCase() === 'sync');
if (syncAnnotation && args.length < 2 && !args[0].includes('.')) {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.noPathForIOCSync)()), { range: cs.range, file: file })]);
continue;
}
let args1 = args[0].toString().split('.');
let iocKey;
let iocPath;
let observeField;
let observePath;
if (args1.length > 1) {
iocKey = args1.splice(0, 1);
iocPath = args1.join('.');
}
else {
iocKey = args1;
iocPath = args.length === 2 ? args[1].toString() : undefined;
}
if (syncAnnotation && !iocPath) {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.noPathForIOCSync)()), { range: cs.range, file: file })]);
continue;
}
if (iocPath) {
let observeParts = iocPath.split('.');
if (observeParts.length > 1) {
observeField = observeParts.pop();
observePath = observeParts.join('.');
}
else {
observeField = observeParts[0];
observePath = '';
}
}
let defaultValue;
try {
if (field === null || field === void 0 ? void 0 : field.initialValue) {
defaultValue = (_c = (_b = (_a = field === null || field === void 0 ? void 0 : field.initialValue) === null || _a === void 0 ? void 0 : _a.transpile(state)) === null || _b === void 0 ? void 0 : _b.join('')) !== null && _c !== void 0 ? _c : 'invalid';
}
else if (field.as) {
defaultValue = (0, Utils_2.typeToValueString)(field.type);
}
}
catch (error) {
console.error(error);
}
if (!defaultValue) {
defaultValue = 'invalid';
}
if (isNodeClass && (((_d = field.accessModifier) === null || _d === void 0 ? void 0 : _d.kind) === brighterscript_1.TokenKind.Public)) {
if (syncAnnotation) {
file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.noPathForIOCSync)()), { range: cs.range, file: file })]);
continue;
}
if (args.length === 1) {
astEditor.setProperty(field, 'initialValue', new RawCodeStatement_1.RawCodeStatement(`__m_setTopField("${field.name.text}", mioc_getInstance("${iocKey}", invalid, ${defaultValue}))`, file, field.range));
}
else if (args.length === 2) {
astEditor.setProperty(field, 'initialValue', new RawCodeStatement_1.RawCodeStatement(`__m_setTopField("${field.name.text}", mioc_getInstance("${iocKey}", "${iocPath}", ${defaultValue}))`, file, field.range));
}
}
el