maestro-roku-bsc-plugin
Version:
Visual studio plugin for maestro brightscript development
879 lines (878 loc) • 61 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");
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) {