UNPKG

gulp-structify

Version:

Generates WebGL-compatible Structs and StructBuffers from a template file.

541 lines 25.4 kB
"use strict"; var through2 = require("through2"); var ts_type_info_1 = require("ts-type-info"); function transformFile(file, template) { // destructure src file var className = template.name; var interfaceName = className + "Like"; var decapClassName = decapitalize(className); var properties = normalizeProperties(template.properties); var documentation = template.documentationComment; var arrayType = template.extendsTypes[0].typeArguments[0].text; // Warn that file is autogenerated file.onBeforeWrite = function (writer) { return writer.write("// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n"); }; // Restore function bodies file.functions.filter(function (f) { return f.tsNode; }).forEach(function (func) { var node = func.tsNode; func.onWriteFunctionBody = function (writer) { for (var _i = 0, _a = node.body.statements; _i < _a.length; _i++) { var statement = _a[_i]; writer.newLine().write(statement.getText()); } }; }); // Add interface with properties copied from template file.addInterface({ name: interfaceName, isExported: true, documentationComment: documentation, properties: properties }); processImports(); processTemplate(); processStruct(); processStructBuffer(); function processImports() { // Remove unnecessary imports file.imports.forEach(function (imprt, index, imports) { switch (imprt.moduleSpecifier) { case "gulp-structify/like": imports.splice(index, 1); break; } }); // Add struct, buf, and mixin imports file.addImport({ moduleSpecifier: 'gulp-structify/struct', namedImports: [{ name: 'Struct' }] }); file.addImport({ moduleSpecifier: 'gulp-structify/buffer', namedImports: [{ name: 'StructBuffer' }] }); file.addImport({ moduleSpecifier: 'gulp-structify/mixin', namedImports: [{ name: 'applyMixins' }] }); } function processTemplate() { // Remove template super class template.extendsTypes.length = 0; // Make sure template is exported template.isExported = true; // Process template methods template.methods.filter(function (m) { return m.tsNode; }).forEach(function (method) { var node = method.tsNode; var params = method.parameters; // Restore method bodies method.onWriteFunctionBody = function (writer) { for (var _i = 0, _a = node.body.statements; _i < _a.length; _i++) { var statement = _a[_i]; writer.newLine().write(statement.getText()); } }; // Process @like annotations params.forEach(function (param) { param.decorators.forEach(function (decorator, index, decorators) { if (decorator.name === "like") { decorators.splice(index, 1); // Remove decorator if (param.type.text === className) { param.type.text = interfaceName; // Replace class type with interface type } } }); }); }); // Add constructor template.setConstructor({ documentationComment: documentation, parameters: properties.map(function (prop) { return { name: prop.name, type: prop.type, defaultExpression: "0" }; }), onWriteFunctionBody: function (writer) { for (var _i = 0, properties_1 = properties; _i < properties_1.length; _i++) { var prop = properties_1[_i]; writer.newLine().write("this." + prop.name + " = " + prop.name + ";"); } } }); // create function parameters var other = "other"; var e = "e"; var k = "k"; var p_other = { name: other, type: interfaceName }; var p_k = { name: k, type: 'number' }; var p_e = { name: e, type: 'number' }; // Generate automatic template functions template.addMethod({ name: 'set', parameters: [p_other], returnType: "void", documentationComment: "Sets each component of this " + className + " to that of the other " + className + ".", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_2 = properties; _i < properties_2.length; _i++) { var prop = properties_2[_i]; writer.newLine().write("this." + prop.name + " = " + other + "." + prop.name + ";"); } } }); template.addMethod({ name: 'set$', parameters: properties, returnType: "void", documentationComment: "Sets each component of this " + className + ".", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_3 = properties; _i < properties_3.length; _i++) { var prop = properties_3[_i]; writer.newLine().write("this." + prop.name + " = " + prop.name + ";"); } } }); template.addMethod({ name: 'setScalar', parameters: [p_k], returnType: "void", documentationComment: "Sets each component of this " + className + " to the specified scalar.", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_4 = properties; _i < properties_4.length; _i++) { var prop = properties_4[_i]; writer.newLine().write("this." + prop.name + " = " + k + ";"); } } }); template.addMethod({ name: 'add', parameters: [p_other], returnType: "void", documentationComment: "Adds the other " + className + " to this " + className + " componentwise.", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_5 = properties; _i < properties_5.length; _i++) { var prop = properties_5[_i]; writer.newLine().write("this." + prop.name + " += " + other + "." + prop.name + ";"); } } }); template.addMethod({ name: 'add$', parameters: properties, returnType: "void", documentationComment: "Adds the specified values to this " + className + " componentwise.", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_6 = properties; _i < properties_6.length; _i++) { var prop = properties_6[_i]; writer.newLine().write("this." + prop.name + " += " + prop.name + ";"); } } }); template.addMethod({ name: 'subtract', parameters: [p_other], returnType: "void", documentationComment: "Subtracts the other " + className + " from this " + className + " componentwise.", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_7 = properties; _i < properties_7.length; _i++) { var prop = properties_7[_i]; writer.newLine().write("this." + prop.name + " -= " + other + "." + prop.name + ";"); } } }); template.addMethod({ name: 'subtract$', parameters: properties, returnType: "void", documentationComment: "Subtracts the specified values from this " + className + " componentwise.", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_8 = properties; _i < properties_8.length; _i++) { var prop = properties_8[_i]; writer.newLine().write("this." + prop.name + " -= " + prop.name + ";"); } } }); template.addMethod({ name: 'mulScalar', parameters: [p_k], returnType: "void", documentationComment: "Multiplies each component of this " + className + " by the specified scalar.", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_9 = properties; _i < properties_9.length; _i++) { var prop = properties_9[_i]; writer.newLine().write("this." + prop.name + " *= " + k + ";"); } } }); template.addMethod({ name: 'divScalar', parameters: [p_k], returnType: "void", documentationComment: "Divides each component of this " + className + " by the specified scalar.", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_10 = properties; _i < properties_10.length; _i++) { var prop = properties_10[_i]; writer.newLine().write("this." + prop.name + " /= " + k + ";"); } } }); template.addMethod({ name: 'equals', parameters: [p_other], returnType: "boolean", documentationComment: "Checks if each component of this " + className + " is exactly equal to that of the other " + className + ".", onWriteFunctionBody: function (writer) { var statements = properties.map(function (p) { return "this." + p.name + " === " + other + "." + p.name; }); writer.write("return " + statements.join(" && ") + ";"); } }); template.addMethod({ name: 'equalsScalar', parameters: [p_k], returnType: "boolean", documentationComment: "Checks if each component of this " + className + " is exactly equal to the specified scalar.", onWriteFunctionBody: function (writer) { var statements = properties.map(function (prop) { return "this." + prop.name + " === " + k; }); writer.write("return " + statements.join(" && ") + ";"); } }); template.addMethod({ name: 'epsilonEquals', parameters: [p_other, p_e], returnType: "boolean", documentationComment: "Checks if each component of this " + className + " is approximately equal to that of the other " + className + ".", onWriteFunctionBody: function (writer) { var statements = properties.map(function (prop) { return "Math.abs(this." + prop.name + " - " + other + "." + prop.name + ") <= " + e; }); writer.write("return " + statements.join(" && ") + ";"); } }); template.addMethod({ name: 'epsilonEqualsScalar', parameters: [p_k, p_e], returnType: "boolean", documentationComment: "Checks if each component of this " + className + " is approximately equal to the specified scalar.", onWriteFunctionBody: function (writer) { var statements = properties.map(function (prop) { return "Math.abs(this." + prop.name + " - " + k + ") <= " + e; }); writer.write("return " + statements.join(" && ") + ";"); } }); template.addMethod({ name: 'toString', documentationComment: "Returns a string representation of this " + className + ".", returnType: "string", onWriteFunctionBody: function (writer) { var expressions = properties.map(function (prop) { return prop.name + ": ${this." + prop.name + "}"; }); writer.write("return `{ " + expressions.join(", ") + " }`"); } }); addStaticMethodsToClass(template); } function processStruct() { // Add "Struct" class var struct = file.addClass({ name: className + "Struct", isExported: true, extendsTypes: ["Struct<" + arrayType + ">"], documentationComment: "A " + className + " backed by a " + arrayType + ".", constructorDef: { parameters: [{ name: "data", type: arrayType, defaultExpression: "new " + arrayType + "(" + properties.length + ")" }], documentationComment: "Creates a " + className + " struct backed by the specified data.", onWriteFunctionBody: function (writer) { writer.newLine().write("super(data);"); } } }); struct.onAfterWrite = function (writer) { writer.newLine().write("applyMixins(" + struct.name + ", " + className + ");"); }; // Add accessors properties.forEach(function (p, i) { struct.addMethod({ name: "get " + p.name, documentationComment: p.documentationComment, onWriteFunctionBody: function (writer) { writer.write("return this.data[" + i + "];"); } }); struct.addMethod({ name: "set " + p.name, parameters: [{ name: "value", type: "number" }], documentationComment: p.documentationComment, onWriteFunctionBody: function (writer) { writer.write("this.data[" + i + "] = value;"); } }); }); addStaticMethodsToClass(struct); addMethodPropertiesToClass(struct); } function processStructBuffer() { // Add struct buffer class var buffer = file.addClass({ isExported: true, name: className + "Buffer", documentationComment: "A " + className + " buffer backed by a " + arrayType + ".", extendsTypes: ["StructBuffer<" + arrayType + ">"], }); buffer.onAfterWrite = function (writer) { writer.newLine().write("applyMixins(" + buffer.name + ", " + className + ");"); }; // Add accessors properties.forEach(function (prop, i) { var comment = prop.documentationComment.replace("this " + className, "the current " + className); buffer.addMethod({ name: "get " + prop.name, documentationComment: comment, onWriteFunctionBody: function (writer) { writer.write("return this.data[this.dataPosition + " + i + "];"); } }); buffer.addMethod({ name: "set " + prop.name, parameters: [{ name: "value", type: "number" }], documentationComment: comment, onWriteFunctionBody: function (writer) { writer.write("this.data[this.dataPosition + " + i + "] = value;"); } }); }); // Add methods to class var p_position = { name: "position", type: "number" }; var p_src = { name: "src", type: interfaceName }; var p_dst = { name: "dst", type: interfaceName, isOptional: true }; buffer.addMethod({ name: "structLength", documentationComment: "Gets the number of properties in a " + className + ", namely " + properties.length + ".", onWriteFunctionBody: function (writer) { writer.write("return " + properties.length + ";"); } }); buffer.addMethod({ name: "aget", parameters: [p_position, p_dst], documentationComment: "Gets the components of the " + className + " at the specified position of this buffer.", onWriteFunctionBody: function (writer) { writer.newLine().write("if (" + p_dst.name + " === void 0){ " + p_dst.name + " = new " + className + "()};"); writer.newLine().write("let dataPos = position * this.structLength();"); for (var _i = 0, properties_11 = properties; _i < properties_11.length; _i++) { var prop = properties_11[_i]; writer.newLine().write(p_dst.name + "." + prop.name + " = this.data[dataPos++];"); } writer.newLine().write("return " + p_dst.name + ";"); } }); buffer.addMethod({ name: "rget", parameters: [p_dst], documentationComment: "Gets the components of the current " + className + ", then moves to the next position of this buffer.", onWriteFunctionBody: function (writer) { writer.newLine().write("if (" + p_dst.name + " === void 0){ " + p_dst.name + " = new " + className + "()};"); for (var _i = 0, properties_12 = properties; _i < properties_12.length; _i++) { var prop = properties_12[_i]; writer.newLine().write(p_dst.name + "." + prop.name + " = this.data[this.dataPosition++];"); } writer.newLine().write("return " + p_dst.name + ";"); } }); buffer.addMethod({ name: "aset", parameters: [p_position, p_src], documentationComment: "Sets each component of the " + className + " at the specified position to that of the src " + className + ".", onWriteFunctionBody: function (writer) { writer.newLine().write("let dataPos = position * this.structLength();"); for (var _i = 0, properties_13 = properties; _i < properties_13.length; _i++) { var prop = properties_13[_i]; writer.newLine().write("this.data[dataPos++] = " + p_src.name + "." + prop.name + ";"); } } }); buffer.addMethod({ name: "aset$", parameters: [p_position].concat(properties), documentationComment: "Sets each component of the " + className + " at the specified position.", onWriteFunctionBody: function (writer) { writer.newLine().write("let dataPos = position * this.structLength();"); for (var _i = 0, properties_14 = properties; _i < properties_14.length; _i++) { var prop = properties_14[_i]; writer.newLine().write("this.data[dataPos++] = " + prop.name + ";"); } } }); buffer.addMethod({ name: "rset", parameters: [p_src], documentationComment: "Sets each component of the current " + className + " to that of the src " + className + ", then moves to the next position of this buffer.", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_15 = properties; _i < properties_15.length; _i++) { var prop = properties_15[_i]; writer.newLine().write("this.data[this.dataPosition++] = " + p_src.name + "." + prop.name + ";"); } } }); buffer.addMethod({ name: "rset$", parameters: properties, documentationComment: "Sets each component of the current " + className + ", then moves to the next position of this buffer.", onWriteFunctionBody: function (writer) { for (var _i = 0, properties_16 = properties; _i < properties_16.length; _i++) { var prop = properties_16[_i]; writer.newLine().write("this.data[this.dataPosition++] = " + prop.name + ";"); } } }); buffer.addStaticMethod({ name: "create", parameters: [{ name: "capacity", type: "number" }], documentationComment: "Creates an empty " + className + " buffer with the specified " + className + " capacity.", onWriteFunctionBody: function (writer) { writer.write("return new " + buffer.name + "(new " + arrayType + "(capacity * " + properties.length + "));"); }, }); addMethodPropertiesToClass(buffer); } function addMethodPropertiesToClass(cls) { template.methods.forEach(function (method) { cls.addProperty({ name: method.name, documentationComment: method.documentationComment, type: getMethodType(method) }); }); } function addStaticMethodsToClass(cls) { template.methods .filter(function (method) { var name = method.name; return name.indexOf("set") === 0 && name.indexOf(" ") === -1 && name !== "setScalar" && (name !== "set$" || cls !== template); // not set$ method and template class }) .forEach(function (setter) { var parameters = normalizeMethodParams(setter.parameters); var args = parameters.map(function (p) { return p.name; }); var name = setter.name.substring("set".length); if (name === "" || name === "$") { name = "create" + name; } cls.addStaticMethod({ name: decapitalize(name), parameters: parameters, onWriteFunctionBody: function (writer) { writer.newLine().write("let " + decapClassName + " = new " + cls.name + "();"); writer.newLine().write(decapClassName + "." + setter.name + "(" + args.join(", ") + ");"); writer.newLine().write("return " + decapClassName + ";"); } }); }); } return file; } function normalizeProperties(properties) { return properties.map(function (p) { return { name: p.name, type: p.type.text, documentationComment: p.documentationComment }; }); } // Helper functions: function normalizeMethodParams(params) { return params.map(function (param) { return { name: param.name, type: param.type.node ? param.type.node.text : param.type.text, isOptional: param.isOptional, isRestParameter: param.isRestParameter, defaultExpression: param.defaultExpression ? param.defaultExpression.text : null, }; }); } function getMethodType(method) { var paramsToString = method.parameters.map(function (param) { var name = param.name; var type = param.type.text; var isOptional = (param.isOptional || param.defaultExpression) ? "?" : ""; return "" + name + isOptional + ": " + type; }).join(", "); return "(" + paramsToString + ") => " + method.returnType.text; } function decapitalize(str) { return str.charAt(0).toLowerCase() + str.slice(1); } module.exports = function structify() { return through2.obj(function (file, encoding, callback) { var info = ts_type_info_1.getInfoFromFiles([file.path], { includeTsNodes: true }); var tsFile = info.getFile(function (f) { return f.fileName.indexOf(file.relative) !== -1; }); // If file is valid .ts if (tsFile) { var templateImport_1; tsFile.imports.forEach(function (imprt, index, imports) { switch (imprt.moduleSpecifier) { case "gulp-structify/template": templateImport_1 = imprt.namedImports[0]; imports.splice(index, 1); break; } }); // If file contains Template import if (templateImport_1) { var searchText_1 = (templateImport_1.alias || templateImport_1.name) + "<"; var templateSubclass = tsFile.getClass(function (cls) { return cls.extendsTypes.some(function (extnds) { return extnds.text.indexOf(searchText_1) !== -1; }); }); // If file contains Template subclass if (templateSubclass) { transformFile(tsFile, templateSubclass); file.contents = new Buffer(tsFile.write()); } } callback(null, file); } }); }; //# sourceMappingURL=main.js.map