UNPKG

maestro-roku-bsc-plugin

Version:

Visual studio plugin for maestro brightscript development

879 lines (878 loc) 61 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"); class MaestroPlugin { constructor() { this.name = 'maestroPlugin'; this.isFrameworkAdded = false; this.mFilesToValidate = new Map(); this.dirtyCompFilePaths = new Set(); this.dirtyNodeClassPaths = new Set(); 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; //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.paramStripExceptions = (_h = config.paramStripExceptions) !== null && _h !== void 0 ? _h : ['onKeyEvent']; config.applyStrictToAllClasses = (_j = config.applyStrictToAllClasses) !== null && _j !== void 0 ? _j : true; config.nodeClasses = (_k = config.nodeClasses) !== null && _k !== void 0 ? _k : {}; config.buildTimeImports = (_l = config.buildTimeImports) !== null && _l !== void 0 ? _l : {}; config.nodeClasses.buildForIDE = config.buildForIDE; //legacy support config.nodeClasses.buildForIDE = config.nodeClasses.buildForIDE === undefined ? false : config.nodeClasses.buildForIDE; config.mvvm = (_m = config.mvvm) !== null && _m !== void 0 ? _m : {}; 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 = (_o = config.refelection) !== null && _o !== void 0 ? _o : {}; 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 = (_p = config.extraValidation) !== null && _p !== void 0 ? _p : {}; 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; 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.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) { // console.log('MAESTRO afp-----', file.pathAbsolute); let mFile = this.fileMap.allFiles[file.pathAbsolute]; 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); if (this.shouldParseFile(file)) { this.filesThatNeedParsingInBeforeProgramValidate.set(mFile.fullPath, mFile); } } else { mFile.loadXmlContents(); } } beforeProgramValidate(program) { 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.pathAbsolute); } this.mFilesToValidate.set(file.pkgPath, file); } this.filesThatNeedParsingInBeforeProgramValidate.clear(); } afterFileValidate(file) { // console.log('MAESTRO afv-----', file.pathAbsolute); 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.pathAbsolute]; 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.pathAbsolute); } else { for (let compFile of this.getCompFilesThatHaveFileInScope(file)) { this.dirtyCompFilePaths.add(compFile.fullPath); } } } if ((0, brighterscript_1.isBrsFile)(file) && this.maestroConfig.updateAsFunctionCalls) { this.validateAsXXXCalls(file); } } validateAsXXXCalls(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|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.getStringPathFromDottedGet(value); 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) { // 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.getFileByPathAbsolute(filePath); file.resetDiagnostics(); this.bindingProcessor.validateBindings(file); if (this.maestroConfig.mvvm.insertXmlBindingsEarly && file.isValid) { console.log('adding xml transpiled code for ', file.bscFile.pkgPath); this.bindingProcessor.generateCodeForXMLFile(file, this.program); } // 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.pathAbsolute]; 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.pathAbsolute, 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.pathAbsolute, filter)) { return false; } } } return true; } beforeFileTranspile(event) { var _a, _b, _c, _d, _e, _f; if (!this.shouldParseFile(event.file)) { return; } 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); this.updateObserveCalls(cs, event.file); } 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) { if (this.maestroConfig.updateAsFunctionCalls) { 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|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.getStringPathFromDottedGet(value); name = `mc_get${name.match(regex)[1]}`; callExpression.callee.name.text = name; if (stringPath) { callExpression.args.unshift(stringPath); } 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); } } } } } } } } 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; } getStringPathFromDottedGet(value) { let parts = [this.getPathValuePartAsString(value)]; 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)}`); } root = root.obj; } let joinedParts = parts.reverse().join('.'); return joinedParts === '' ? undefined : (0, brighterscript_1.createStringLiteral)(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) { 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 ((0, brighterscript_1.isVariableExpression)(expr.index)) { return `${expr.index.name.text}`; } } } beforeProgramTranspile(program, entries) { // 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.pathAbsolute]; // eslint-disable-next-line @typescript-eslint/dot-notation if (mFile.isValid) { //it's a binding file this.bindingProcessor.generateCodeForXMLFile(mFile, program, 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); //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 ], null); 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 (!isNodeClass || (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') { //TODO - make this not get dotted get's in function calls let lowerName = ds.name.text.toLowerCase(); if (!fieldMap.has(lowerName) && !funcMap[lowerName] && !this.skips[lowerName]) { 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.pathAbsolute); continue; } for (let f of cs.fields) { 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) { var _a, _b; // eslint-disable-next-line @typescript-eslint/dot-notation let isNodeClass = cs['_isNodeClass'] === true; for (let f of cs.fields) { let annotation = (f.annotations || []).find((a) => a.name.toLowerCase() === 'inject' || a.name.toLowerCase() === 'injectclass' || a.name.toLowerCase() === 'createclass'); if (annotation) { let args = annotation.getArguments(); let wf = f; 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 = (f.annotations || []).find((a) => a.name.toLowerCase() === 'sync'); if (syncAnnotation && args.length < 2) { 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); 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 = ''; } } if (isNodeClass && (((_a = f.accessModifier) === null || _a === void 0 ? void 0 : _a.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) { wf.initialValue = new RawCodeStatement_1.RawCodeStatement(`__m_setTopField("${f.name.text}", mioc_getInstance("${iocKey}"))`, file, f.range); } else if (args.length === 2) { wf.initialValue = new RawCodeStatement_1.RawCodeStatement(`__m_setTopField("${f.name.text}", mioc_getInstance("${iocKey}", "${iocPath}"))`, file, f.range); } } else { //check for observer field in here.. let observerAnnotation = (f.annotations || []).find((a) => a.name.toLowerCase() === 'observer'); if (observerAnnotation || syncAnnotation) { let funcName = 'invalid'; if (observerAnnotation) { let observerArgs = (_b = observerAnnotation === null || observerAnnotation === void 0 ? void 0 : observerAnnotation.getArguments()) !== null && _b !== void 0 ? _b : []; funcName = `m.${observerArgs[0].toLowerCase()}`; //TODO add validation // let observerFunc = members.get((observerArgs[0] as string).toLowerCase()); // if (observerArgs.length > 0) { // let observerFunc = members.get((observerArgs[0] as string).toLowerCase()); // if (isClassMethodStatement(observerFunc)) { // f.numArgs = observerFunc?.func?.parameters?.length; // } // } } // (fieldName as string, instanceName as string, path as string, observedPath as string, observedField as string, callback = invalid as function) if (!iocPath) { file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.noPathForIOCSync)()), { range: cs.range, file: file })]); } else { wf.initialValue = new RawCodeStatement_1.RawCodeStatement(`m._addIOCObserver("${f.name.text}", "${iocKey}", "${iocPath}", "${observePath}", "${observeField}", ${funcName})`, file, f.range); } } else { if (!iocPath) { wf.initialValue = new RawCodeStatement_1.RawCodeStatement(`mioc_getInstance("${iocKey}")`, file, f.range); } else { wf.initialValue = new RawCodeStatement_1.RawCodeStatement(`mioc_getInstance("${iocKey}", "${iocPath}")`, file, f.range); } } } } else if (annotation.name === 'injectClass') { wf.initialValue = new RawCodeStatement_1.RawCodeStatement(`mioc_getClassInstance("${args[0].toString()}")`, file, f.range); } else if (annotation.name === 'createClass') { let instanceArgs = []; for (let i = 1; i < args.length - 1; i++) { if (args[i]) { instanceArgs.push(args[i].toString()); } } if (instanceArgs.length > 0) { wf.initialValue = new RawCodeStatement_1.RawCodeStatement(`mioc_createClassInstance("${args[0].toString()}", [${instanceArgs.join(',')}])`, file, f.range); } else { wf.initialValue = new RawCodeStatement_1.RawCodeStatement(`mioc_createClassInstance("${args[0].toString()}")`, file, f.range); } } wf.equal = (0, brighterscript_1.createToken)(brighterscript_1.TokenKind.Equal, '=', f.range); } } } doExtraValidations(file) { //ensure we have all lookups let scopeNamespaces = new Map(); let classMethodLookup = {}; for (let scope of this.program.getScopesForFile(file)) { let scopeMap = this.getNamespaceLookup(scope); scopeNamespaces = new Map([...Array.from(scopeMap.entries())]); classMethodLookup = Object.assign(Object.assign({}, this.buildClassMethodLookup(scope)), classMethodLookup); } this.validateFunctionCalls(file, scopeNamespaces, classMethodLookup); } validateFunctionCalls(file, nsLookup, methodLookup) { // file.parser.references.importStatements // for now we're only validating classes let importedPkgPaths = file.program.dependencyGraph.getAllDependencies(file.dependencyGraphKey).map((d) => d.replace('.d.bs', '.bs')); importedPkgPaths.push(file.pkgPath.toLowerCase()); for (let cs of file.parser.references.classStatements) { if (cs.parentClassName && this.maestroConfig.extraValidation.doExtraImportValidation) { let name = cs.parentClassName.getName(brighterscript_1.ParseMode.BrighterScript); if (!methodLookup[name]) { // console.log('>> ' + name); file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.unknownSuperClass)(`${name}`)), { range: cs.range, file: file })]); } else { let member = methodLookup[name]; if (typeof member !== 'boolean') { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument if (!this.isImported(member, importedPkgPaths)) { file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.unknownSuperClass)(`${name}`)), { range: cs.range, file: file })]); } } } } cs.walk((0, brighterscript_1.createVisitor)({ NewExpression: (ne) => { if (this.maestroConfig.extraValidation.doExtraImportValidation) { let name = ne.className.getName(brighterscript_1.ParseMode.BrighterScript); if (!methodLookup[name]) { // console.log('>> ' + name); file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.unknownConstructorMethod)(`${name}`, this.name)), { range: ne.range, file: file })]); } else { let member = methodLookup[name]; if (typeof member !== 'boolean') { let numArgs = ne.call.args.length; if (numArgs < member.minArgs || numArgs > member.maxArgs) { file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.wrongConstructorArgs)(`${name}`, numArgs, member.minArgs, member.maxArgs)), { range: ne.range, file: file })]); } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument if (!this.isImported(member, importedPkgPaths)) { file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.unknownConstructorMethod)(`${name}`, this.name)), { range: ne.range, file: file })]); } } } } }, CallExpression: (ce, parent) => { if ((0, brighterscript_1.isNewExpression)(parent)) { return; } let dg = ce.callee; let nameParts = this.getAllDottedGetParts(dg); let name = nameParts.pop(); if (name) { //is a namespace? if (nameParts[0] && nsLookup.has(nameParts[0].toLowerCase())) { //then it must reference something we know let fullPathName = nameParts.join('.').toLowerCase(); let ns = nsLookup.get(fullPathName); if (!ns) { //look it up minus the tail file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.unknownType)(`${fullPathName}.${name}`, this.name)), { range: ce.range, file: file })]); } else if (!ns.functionStatements[name.toLowerCase()] && !ns.classStatements[name.toLowerCase()]) { file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.unknownType)(`${fullPathName}.${name}`, this.name)), { range: ce.range, file: file })]); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument } else { let member = ns.functionStatements[name.toLowerCase()]; if (member) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument if (this.maestroConfig.extraValidation.doExtraImportValidation && !this.isNamespaceImported(ns, importedPkgPaths)) { file.addDiagnostics([Object.assign(Object.assign({}, (0, Diagnostics_1.namespaceNotImported)(`${fullPathName}.${name}`)), { range: ce.range, file: file })]); } let numArgs = ce.args.length; let minArgs = member.func.parameters.filter((p) => !p.defaultValue).length; let maxArgs = member.func.parameters.length; if (numArgs < minArgs || numArgs > maxArgs) {