UNPKG

maestro-roku-bsc-plugin

Version:

Visual studio plugin for maestro brightscript development

912 lines 71.2 kB
"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