maestro-roku-bsc-plugin
Version:
Visual studio plugin for maestro brightscript development
636 lines (633 loc) • 28.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeClass = exports.NodeField = exports.NodeClassType = void 0;
const brighterscript_1 = require("brighterscript");
const Utils_1 = require("../Utils");
const Diagnostics_1 = require("../utils/Diagnostics");
const RawCodeStatement_1 = require("../utils/RawCodeStatement");
const Utils_2 = require("../utils/Utils");
// eslint-disable-next-line
const path = require('path');
var NodeClassType;
(function (NodeClassType) {
NodeClassType[NodeClassType["none"] = 0] = "none";
NodeClassType[NodeClassType["node"] = 1] = "node";
NodeClassType[NodeClassType["task"] = 2] = "task";
})(NodeClassType = exports.NodeClassType || (exports.NodeClassType = {}));
class NodeField {
constructor(file, classStatement, field, fieldType, observerAnnotation, alwaysNotify, debounce, isPossibleClassType = false, isRootOnlyObserver = false) {
this.file = file;
this.classStatement = classStatement;
this.field = field;
this.fieldType = fieldType;
this.observerAnnotation = observerAnnotation;
this.alwaysNotify = alwaysNotify;
this.debounce = debounce;
this.isPossibleClassType = isPossibleClassType;
this.isRootOnlyObserver = isRootOnlyObserver;
this.name = field.name.text;
this.type = isPossibleClassType ? 'assocarray' : fieldType;
this.classType = isPossibleClassType ? fieldType : '';
this.value = (0, Utils_1.expressionToString)(this.field.initialValue);
this.callback = observerAnnotation === null || observerAnnotation === void 0 ? void 0 : observerAnnotation.getArguments()[0];
}
getObserverStatementText() {
return `
m.top.observeField("${this.name}", "on_${this.name}")`;
}
getInterfaceText() {
let text = `
<field id="${this.name}" type="${this.type}" `;
if (this.alwaysNotify) {
text += ` alwaysNotify="${this.alwaysNotify}" `;
}
text += '/>';
return text;
}
getCallBackFunctionText(code) {
let text = `
function on_${this.name}(event)
`;
if (this.isRootOnlyObserver) {
text += `v = event.getData()
if type(v) <> "roSGNode" or not v.isSameNode(m._p_${this.name})
m._p_${this.name} = v
${code}
end if
`;
}
else {
text += code + `
`;
}
text += `end function
`;
return text;
}
getCallbackStatement() {
return this.getCallBackFunctionText(`m.${this.callback}(${this.numArgs === 1 ? 'event.getData()' : ''})`);
}
getDebouncedCallbackStatement() {
return this.getCallBackFunctionText(`addCallback("${this.callback}")`);
}
getLazyCallbackStatement() {
return this.getCallBackFunctionText(`_getVM().${this.callback}(${this.numArgs === 1 ? 'event.getData()' : ''})`);
}
getLazyDebouncedCallbackStatement() {
return this.getCallBackFunctionText(`addCallback("${this.callback}")`);
}
}
exports.NodeField = NodeField;
class NodeClass {
constructor(type, file, classStatement, name, extendsName, annotation, fileMap, isLazy, observersWaitInit) {
this.type = type;
this.file = file;
this.classStatement = classStatement;
this.name = name;
this.extendsName = extendsName;
this.annotation = annotation;
this.fileMap = fileMap;
this.isLazy = isLazy;
this.observersWaitInit = observersWaitInit;
this.nodeFields = [];
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
this.classMemberFilter = (m) => (0, brighterscript_1.isClassMethodStatement)(m) && (!m.accessModifier || m.accessModifier.kind === brighterscript_1.TokenKind.Public) && m.name.text !== 'new';
this.knownFieldTypes = {
'integer': true,
'longinteger': true,
'float': true,
'string': true,
'boolean': true,
'vector2d': true,
'color': true,
'time': true,
'uri': true,
'node': true,
'floatarray': true,
'intarray': true,
'boolarray': true,
'stringarray': true,
'vector2darray': true,
'colorarray': true,
'timearray': true,
'nodearray': true,
'assocarray': true,
'array': true,
'rect2d': true
};
this.generatedNodeName = this.name.replace(/[^a-zA-Z0-9]/g, '_');
this.bsPath = path.join('components', 'maestro', 'generated', `${this.generatedNodeName}.bs`);
this.xmlPath = path.join('components', 'maestro', 'generated', `${this.generatedNodeName}.xml`);
this.nodeFields = this.getNodeFields(this.file, this.classStatement, fileMap);
}
resetDiagnostics() {
if (this.xmlFile) {
this.xmlFile.diagnostics = [];
}
if (this.brsFile) {
this.brsFile.diagnostics = [];
}
}
getDebounceFunction(isLazy) {
return `
function addCallback(funcName)
m.pendingCallbacks[funcName] = true
if (m.pendingCallbacks.count() = 1)
mc.tasks.observeNodeField(m.global.tick, "fire", onTick, "none", true)
end if
end function
function onTick()
for each funcName in m.pendingCallbacks
${isLazy ? `_getVM()
m.[funcName]()`
: 'm.[funcName]()'}
end for
m.pendingCallbacks = {}
end function
`;
}
getNodeTaskBrsCode(nodeFile) {
let text = `
function init()
m.top.functionName = "exec"
end function
function exec()
instance = __${nodeFile.classStatement.getName(brighterscript_1.ParseMode.BrightScript)}_builder()
instance.delete("top")
instance.delete("global")
top = m.top
m.append(instance)
m.__isVMCreated = true
m.new()
m.top = top
try
m.top.output = m.execute(m.top.args)
catch error
m.log.error("error occurred executing task", mc_dv(m.top), error)
m.top.output = {isOk:false, data: error, message: mc_getString(error, "message")}
end try
end function
`;
return text;
}
makeFunction(name, args, bodyText) {
let funcText = `
function ${name}(${args})
${bodyText}
end function
`;
return funcText;
// this.brsFile.parser.statements.push(makeASTFunction(funcText));
}
getNodeBrsCode(members) {
let text = '';
for (let member of members.filter(this.classMemberFilter)) {
let params = member.func.parameters;
if (params.length) {
let args = `${params.map((p) => p.name.text).join(',')}`;
text += this.makeFunction(member.name.text, this.getWrapperCallFuncParams(params), `
return m.${member.name.text}(${args})`);
}
else {
text += this.makeFunction(member.name.text, 'dummy = invalid', `
return m.${member.name.text}()`);
}
}
return text;
}
getWrapperCallFuncParams(params) {
return `${params.map((p) => {
let defaultValue = (0, Utils_1.expressionToValue)(p.defaultValue);
if (typeof defaultValue === 'string') {
defaultValue = `"${defaultValue}"`;
}
return p.name.text + ' = ' + (defaultValue !== undefined ? defaultValue : 'invalid');
}).join(',')}`;
}
getLazyNodeBrsCode(nodeFile, members) {
let body = `
if m.__isVMCreated = invalid
instance = __${nodeFile.classStatement.getName(brighterscript_1.ParseMode.BrightScript)}_builder()
instance.delete("top")
instance.delete("global")
top = m.top
m.append(instance)
m.__isVMCreated = true
m.new()
m.top = top
`;
if (this.observersWaitInit) {
body += `m.isWiringObserversOnInit = true\n`;
}
else {
body += `m_wireUpObservers()\n`;
}
body += ` end if
return m
`;
let text = this.makeFunction('_getVM', '', body);
for (let member of members.filter(this.classMemberFilter)) {
let params = member.func.parameters;
if (params.length) {
let args = `${params.map((p) => p.name.text).join(',')}`;
text += this.makeFunction(member.name.text, this.getWrapperCallFuncParams(params), `
return _getVM().${member.name.text}(${args})`);
}
else {
text += this.makeFunction(member.name.text, 'dummy = invalid', `
return _getVM().${member.name.text}()`);
}
}
return text;
}
getNodeTaskFileXmlText(nodeFile) {
return `<?xml version="1.0" encoding="UTF-8" ?>
<component
name="${nodeFile.name}"
extends="${nodeFile.extendsName}">
<interface>
<field id="args" type="assocarray"/>
<field id="output" type="assocarray"/>
<function name="exec"/>
</interface>
<children>
</children>
</component>
`;
}
getNodeFileXmlText(nodeFile, members, program) {
let text = `<?xml version="1.0" encoding="UTF-8" ?>
<component
name="${nodeFile.name}"
extends="${nodeFile.extendsName}">
<interface>
`;
for (let member of nodeFile.nodeFields) {
if (!this.getFieldInParents(member.name, program)) {
text += member.getInterfaceText();
}
}
for (let member of members.filter(this.classMemberFilter)) {
if (!this.getFunctionInParents(member.name.text, program)) {
text += `
<function name="${member.name.text}"/>`;
}
}
text += `
</interface>
<children>
</children>
</component>
`;
return text;
}
getFieldInParents(name, program) {
var _a, _b, _c, _d;
let comp = program.getComponent(this.extendsName.toLowerCase());
while (comp) {
if (comp.file.parser.ast.component.api.getField(name)) {
return true;
}
comp = program.getComponent((_d = (_c = (_b = (_a = comp.file.parser) === null || _a === void 0 ? void 0 : _a.references) === null || _b === void 0 ? void 0 : _b.extends) === null || _c === void 0 ? void 0 : _c.text) === null || _d === void 0 ? void 0 : _d.toLowerCase());
}
return false;
}
getFunctionInParents(name, program) {
var _a, _b, _c, _d;
let comp = program.getComponent(this.extendsName.toLowerCase());
while (comp) {
if (comp.file.parser.ast.component.api.getFunction(name)) {
return true;
}
comp = program.getComponent((_d = (_c = (_b = (_a = comp.file.parser) === null || _a === void 0 ? void 0 : _a.references) === null || _b === void 0 ? void 0 : _b.extends) === null || _c === void 0 ? void 0 : _c.text) === null || _d === void 0 ? void 0 : _d.toLowerCase());
}
return false;
}
generateCode(fileFactory, program, fileMap, isIDEBuild) {
let members = this.type === NodeClassType.task ? [] : [...this.getClassMembers(this.classStatement, fileMap).values()];
// if (!isIDEBuild) {
// console.log('Generating node class', this.name, 'isIDEBuild?', isIDEBuild
// );
// }
if (!isIDEBuild) {
//update node fields, in case of them being present in base classes
this.nodeFields = this.getNodeFields(this.file, this.classStatement, fileMap);
let source = `import "${(0, Utils_1.sanitizePkgPath)(this.file.pkgPath)}"\n`;
let initBody = ``;
let otherFunctionsText = ``;
let observerBody = ``;
let hasDebounce = false;
if (this.type === NodeClassType.node) {
for (let member of this.nodeFields) {
if (member.isEnum) {
initBody += `m.top.${member.name} = ${(0, Utils_1.expressionToString)(this.getEnumInitialValue(member.field))}\n`;
}
else {
initBody += `m.top.${member.name} = ${member.value}\n`;
}
if (member.isRootOnlyObserver) {
initBody += `m._p_${member.name} = invalid\n`;
}
}
if (!this.isLazy) {
initBody += `
instance = __${this.classStatement.getName(brighterscript_1.ParseMode.BrightScript)}_builder()
instance.delete("top")
instance.delete("global")
top = m.top
m.append(instance)
m.__isVMCreated = true
m.new()
m.top = top
`;
if (this.observersWaitInit) {
initBody += `m.isWiringObserversOnInit = true\n`;
}
else {
initBody += `m_wireUpObservers()\n`;
}
}
for (let field of this.nodeFields.filter((f) => f.observerAnnotation)) {
observerBody += field.getObserverStatementText() + `\n`;
hasDebounce = hasDebounce || field.debounce;
if (this.isLazy) {
otherFunctionsText += field.debounce ? field.getLazyDebouncedCallbackStatement() : field.getLazyCallbackStatement();
}
else {
otherFunctionsText += field.debounce ? field.getDebouncedCallbackStatement() : field.getCallbackStatement();
}
}
if (hasDebounce || this.observersWaitInit) {
initBody += `
m.pendingCallbacks = {}
`;
}
source += this.makeFunction('init', '', initBody);
source += otherFunctionsText;
source += this.makeFunction('m_wireUpObservers', '', observerBody);
if (hasDebounce || this.observersWaitInit) {
source = `import "pkg:/source/roku_modules/mc/Tasks.brs"
` + source;
source += this.getDebounceFunction(this.isLazy);
}
}
source += `function __m_setTopField(field, value)
if m.top.doesExist(field)
m.top[field] = value
end if
return value
end function`;
if (this.type === NodeClassType.task) {
source += this.getNodeTaskBrsCode(this);
}
else if (this.isLazy) {
source += this.getLazyNodeBrsCode(this, members);
}
else {
source += this.getNodeBrsCode(members);
}
this.brsFile = fileFactory.addFile(this.bsPath, source);
this.brsFile.parser.invalidateReferences();
}
let xmlText = this.type === NodeClassType.task ? this.getNodeTaskFileXmlText(this) : this.getNodeFileXmlText(this, members, program);
this.xmlFile = fileFactory.addFile(this.xmlPath, xmlText);
this.xmlFile.parser.invalidateReferences();
}
getClassMembers(classStatement, fileMap) {
let results = new Map();
if (classStatement) {
let classes = this.getClassHierarchy(classStatement.getName(brighterscript_1.ParseMode.BrighterScript), fileMap);
for (let cs of classes) {
let fields = cs === null || cs === void 0 ? void 0 : cs.fields;
let methods = cs === null || cs === void 0 ? void 0 : cs.methods;
for (let member of [...fields, ...methods]) {
if (!results.has(member.name.text.toLowerCase())) {
results.set(member.name.text.toLowerCase(), member);
}
}
}
}
return results;
}
getClassFields(classStatement, fileMap) {
let results = new Map();
if (classStatement) {
let classes = this.getClassHierarchy(classStatement.getName(brighterscript_1.ParseMode.BrighterScript), fileMap);
for (let cs of classes) {
let fields = cs === null || cs === void 0 ? void 0 : cs.fields;
for (let member of [...fields]) {
if (!results.has(member.name.text.toLowerCase())) {
results.set(member.name.text.toLowerCase(), { cs: cs, f: member });
}
}
}
}
return results;
}
getClassHierarchy(className, fileMap) {
var _a;
let items = [];
let parent = fileMap.allClasses[className];
while (parent) {
items.push(parent);
parent = fileMap.allClasses[(_a = parent.parentClassName) === null || _a === void 0 ? void 0 : _a.getName(brighterscript_1.ParseMode.BrighterScript)];
}
return items;
}
validateBaseComponent(fileMap) {
var _a, _b;
let comp = this.file.program.getComponent(this.extendsName.toLowerCase());
if (!(((_b = (_a = comp === null || comp === void 0 ? void 0 : comp.file) === null || _a === void 0 ? void 0 : _a.componentName) === null || _b === void 0 ? void 0 : _b.text) === this.extendsName || fileMap.validComps.has(this.extendsName) || fileMap.nodeClasses[this.extendsName])) {
(0, Diagnostics_1.addNodeClassNoExtendNodeFound)(this.file, this.name, this.extendsName, this.annotation.range.start.line, this.annotation.range.start.character);
}
}
validate() {
for (let field of this.nodeFields.filter((f) => f.classStatement === this.classStatement)) {
if (field.observerAnnotation) {
let observerArgs = field.observerAnnotation.getArguments();
let observerFunc = this.classStatement.methods.find((m) => m.name.text === observerArgs[0]);
if ((observerArgs === null || observerArgs === void 0 ? void 0 : observerArgs.length) !== 1) {
(0, Diagnostics_1.addNodeClassCallbackNotDefined)(this.file, field.name, field.observerAnnotation.range.start.line, field.observerAnnotation.range.start.character);
}
else if (!observerFunc) {
(0, Diagnostics_1.addNodeClassCallbackNotFound)(this.file, field.name, observerArgs[0], this.classStatement.getName(brighterscript_1.ParseMode.BrighterScript), field.observerAnnotation.range.start.line, field.observerAnnotation.range.start.character);
}
else if (field.numArgs > 1) {
(0, Diagnostics_1.addNodeClassCallbackWrongParams)(this.file, field.name, observerArgs[0], this.classStatement.getName(brighterscript_1.ParseMode.BrighterScript), field.observerAnnotation.range.start.line, field.observerAnnotation.range.start.character);
}
}
if (field.isPossibleClassType && !this.fileMap.allClassNames.has(field.fieldType)) {
(0, Diagnostics_1.addNodeClassUnknownClassType)(this.file, field.name, field.classType, this.classStatement.getName(brighterscript_1.ParseMode.BrighterScript), field.field.range.start.line, field.field.range.start.character);
}
}
for (let method of this.classStatement.methods.filter(this.classMemberFilter)) {
if (method.func.parameters.length > 5) {
(0, Diagnostics_1.addTooManyPublicParams)(this.file, method.name.text, this.classStatement.getName(brighterscript_1.ParseMode.BrighterScript), method.name.range.start.line, method.name.range.start.character);
}
}
}
replacePublicMFieldRefs(fileMap) {
let allTopFields = (0, Utils_2.getAllFields)(fileMap, this.classStatement, brighterscript_1.TokenKind.Public);
allTopFields.set('id', true);
allTopFields.delete('__classname');
let logVisitor = (0, brighterscript_1.createVisitor)({
DottedGetExpression: (de) => {
if ((0, brighterscript_1.isVariableExpression)(de.obj) && de.obj.name.text === 'm' && allTopFields.get(de.name.text.toLowerCase())) {
try {
// eslint-disable-next-line
de['obj'] = new RawCodeStatement_1.RawCodeStatement(`m.top`, this.file, de.range);
}
catch (e) {
console.log(`Error updating m.public field to dotted get: ${this.file.pkgPath} ${e.getMessage()}`);
}
}
},
DottedSetStatement: (ds) => {
if ((0, brighterscript_1.isVariableExpression)(ds.obj) && ds.obj.name.text === 'm' && allTopFields.get(ds.name.text.toLowerCase())) {
try {
// eslint-disable-next-line
ds['obj'] = new RawCodeStatement_1.RawCodeStatement(`m.top`, this.file, ds.range);
}
catch (e) {
console.log(`Error updating m.public field to dotted get: ${this.file.pkgPath} ${e.getMessage()}`);
}
}
}
});
this.classStatement.walk(logVisitor, { walkMode: brighterscript_1.WalkMode.visitAllRecursive });
}
getNodeFields(file, cs, fileMap) {
var _a, _b, _c, _d, _e, _f, _g;
let fields = [];
let members = new Map();
if (this.type !== NodeClassType.task) {
members = this.getClassMembers(this.classStatement, fileMap);
fields = [...this.getClassFields(this.classStatement, fileMap).values()];
}
let nodeFields = [];
for (let bf of fields.filter((bf) => (!bf.f.accessModifier || bf.f.accessModifier.kind === brighterscript_1.TokenKind.Public) && bf.f.name.text.toLocaleLowerCase() !== '__classname')) {
let field = bf.f;
let fieldType = this.getFieldType(field);
let isEnum = false;
if (!fieldType) {
fieldType = this.getEnumTypeFromField(field);
if (fieldType) {
isEnum = true;
}
else {
(0, Diagnostics_1.addNodeClassFieldNoFieldType)(file, field.name.text, field.range.start.line, field.range.start.character);
continue;
}
}
let debounce = ((_a = field.annotations) === null || _a === void 0 ? void 0 : _a.find((a) => a.name.toLowerCase() === 'debounce')) !== undefined;
let observerAnnotation = (_b = field.annotations) === null || _b === void 0 ? void 0 : _b.find((a) => a.name.toLowerCase() === 'observer');
let rootOnly = (_c = field.annotations) === null || _c === void 0 ? void 0 : _c.find((a) => a.name.toLowerCase() === 'rootonly');
let alwaysNotify = ((_d = field.annotations) === null || _d === void 0 ? void 0 : _d.find((a) => a.name.toLowerCase() === 'alwaysnotify')) !== undefined;
let observerArgs = (_e = observerAnnotation === null || observerAnnotation === void 0 ? void 0 : observerAnnotation.getArguments()) !== null && _e !== void 0 ? _e : [];
let isRootOnly = rootOnly !== undefined || ((observerArgs.length > 2 && observerArgs[1] === true));
let f = new NodeField(file, bf.cs, field, fieldType, observerAnnotation, alwaysNotify, debounce, !this.knownFieldTypes[fieldType.toLowerCase()], isRootOnly);
f.isEnum = isEnum;
if (observerArgs.length > 0) {
let observerFunc = members.get(observerArgs[0].toLowerCase());
if ((0, brighterscript_1.isClassMethodStatement)(observerFunc)) {
f.numArgs = (_g = (_f = observerFunc === null || observerFunc === void 0 ? void 0 : observerFunc.func) === null || _f === void 0 ? void 0 : _f.parameters) === null || _g === void 0 ? void 0 : _g.length;
}
}
nodeFields.push(f);
}
return nodeFields;
}
getFieldType(field) {
let fieldType;
if (field.type) {
fieldType = field.type.text.toLowerCase();
if (fieldType === 'mc.types.assocarray') {
fieldType = 'assocarray';
}
else if (fieldType === 'mc.types.node') {
fieldType = 'node';
}
else if (fieldType === 'mc.types.array') {
fieldType = 'array';
}
else if (this.getInterfaceFromFieldType(fieldType)) {
fieldType = 'assocarray';
}
else if (!this.knownFieldTypes[fieldType]) {
// keep original case
fieldType = field.type.text;
}
// console.log('fieldType', fieldType);
}
else if ((0, brighterscript_1.isLiteralExpression)(field.initialValue)) {
fieldType = field.initialValue.type.toTypeString();
}
else if ((0, brighterscript_1.isAALiteralExpression)(field.initialValue)) {
fieldType = 'assocarray';
}
else if ((0, brighterscript_1.isArrayLiteralExpression)(field.initialValue)) {
fieldType = 'array';
}
else if ((0, brighterscript_1.isUnaryExpression)(field.initialValue) && (0, brighterscript_1.isLiteralNumber)(field.initialValue.right)) {
if ((0, brighterscript_1.isIntegerType)(field.initialValue.right.type) || (0, brighterscript_1.isLongIntegerType)(field.initialValue.right.type)) {
fieldType = 'integer';
}
else {
fieldType = 'float';
}
}
else if ((0, brighterscript_1.isNewExpression)(field.initialValue)) {
fieldType = 'assocarray';
}
return fieldType === 'invalid' ? undefined : fieldType;
}
getEnumFromField(field) {
var _a, _b;
if ((0, brighterscript_1.isDottedGetExpression)(field.initialValue)) {
let enumMap = (_a = this.file.program.getFirstScopeForFile(this.file)) === null || _a === void 0 ? void 0 : _a.getEnumMap();
let parts = (0, Utils_1.getAllDottedGetParts)(field.initialValue);
let namePart = parts.pop();
let enumStatement = (_b = enumMap.get(parts.join('.').toLowerCase())) === null || _b === void 0 ? void 0 : _b.item;
if ((0, brighterscript_1.isEnumStatement)(enumStatement)) {
return enumStatement.body.find((value) => value.name.toLowerCase() === namePart.toLowerCase());
}
}
return undefined;
}
getInterfaceFromFieldType(fieldType) {
var _a, _b;
let interfaceMap = (_a = this.file.program.getFirstScopeForFile(this.file)) === null || _a === void 0 ? void 0 : _a.getInterfaceMap();
return (_b = interfaceMap.get(fieldType.toLowerCase())) === null || _b === void 0 ? void 0 : _b.item;
}
getEnumTypeFromField(field) {
let enumValue = this.getEnumFromField(field);
if ((0, brighterscript_1.isEnumMemberStatement)(enumValue)) {
if ((0, brighterscript_1.isLiteralExpression)(enumValue.value)) {
return enumValue.value.type.toTypeString();
}
else if ((0, brighterscript_1.isAALiteralExpression)(enumValue.value)) {
return 'assocarray';
}
else if ((0, brighterscript_1.isArrayLiteralExpression)(enumValue.value)) {
return 'array';
}
else if ((0, brighterscript_1.isUnaryExpression)(enumValue.value) && (0, brighterscript_1.isLiteralNumber)(enumValue.value.right)) {
if ((0, brighterscript_1.isIntegerType)(enumValue.value.right.type) || (0, brighterscript_1.isLongIntegerType)(enumValue.value.right.type)) {
return 'integer';
}
else {
return 'float';
}
}
return undefined;
}
}
getEnumInitialValue(field) {
let enumValue = this.getEnumFromField(field);
if ((0, brighterscript_1.isEnumMemberStatement)(enumValue)) {
return enumValue.value;
}
}
}
exports.NodeClass = NodeClass;
//# sourceMappingURL=NodeClass.js.map