rocket-translator
Version:
Translate your HTML files to React.js and Vue.js
1,842 lines (1,662 loc) • 47.7 kB
JavaScript
const defineErrors = require("../ErrorManagement"); //Define Error Management Globals
const globalList = require("../../const/Globals");
const events = require("../../const/Events");
const {parse} = require("babylon");
defineErrors();
/**
* State Management Base Class
*
* Class that manage HTML String and get all data from these
*
* @class
*/
class StateManagement {
constructor() {
//Initialize Data arrays
this._states = new Array();
this._computed = new Array();
this._methods = new Array();
this._components = new Array();
this._watchers = new Array();
this._props = new Array();
this._handleInputs = false;
this._conditionals = new Array();
this._loops = new Array();
this._lifecycle = new Array();
this._regExpToMatchState = /^\w*\s*-\s*state$/g; //RegExp to get State With Declaration
this._regExpToMatchStateWithValue = /^\w*\s*-\s*state\s*-\s*.*$/g; //RegExp to get State With Value
this._regExpToMatchComputed = /^\w*\s*-\s*computed/g; //RegExp to get Computed Methods
this._regExpToMatchProps = /^\w*\s*-\s*prop/g; //RegExp to get Props
this.componentsContent = new Array();
this._styles = undefined;
}
//--------------- Public Methods -----------------
set styles(css) {
this._styles = css;
}
get styles() {
if (this._styles) {
if (global.RocketTranslator.mode === "react")
return `<style jsx>{\`${this._styles}\`}</style>`;
}
return "";
}
/**
* Get HTML String
*
* Get Get All Data From HTML
*
* @public
* @param {string} html
*/
getHTMLString(html){
this._setDataFromHTML(html); //Call Method to Get Data from HTML String
}
/**
* Get Js Data
*
* Get Js Methods from the Js File and Set to Methods Contents
*
* @public
* @param {Array} JSParsed JS Content Array From JS Parser
*/
getJsData(JSParsed) {
if (JSParsed.length > 0) {
this._filterJS(JSParsed).forEach(e => {
//Map Methods
this.methods.forEach((method, i) => {
if (/^async/.test(e.name)) {
if (e.name === `async ${method.name}`) {
this._methods[i] = {
content: e.content,
name: e.name
};
}
} else {
if (e.name === method.name)
this._methods[i].content = e.content;
}
});
//Map Computed
this.computed.forEach(({name}, i) => {
if (/^async/.test(e.name)) {
if (e.name === `async ${name}`) {
this._computed[i] = {
content: e.content,
name: e.name
};
}
} else {
if (e.name === name)
this._computed[i].content = e.content;
}
});
});
}
/*Error Handle*/
this.methods.forEach(({content, name}) => {
if(!content) new global.UndefinedMethodError(name);
});
this.computed.forEach(({content, name}) => {
if(!content) new global.UndefinedComputedError(name);
});
}
/**
* Set Vars
*
* Get Vars from JS Parsed and set the value in the corresponding state
*
* @public
* @param {Array} VarsArray
*/
setVarsToStatesContent(VarsArray){
if (VarsArray.length > 0) {
//Map Vars Array
this.states.forEach((state, i) => {
if (typeof state === "object") {
const {value:val} = state;
//If match replace the corresponding state
if (VarsArray.length > 0) {
VarsArray.forEach(({name, value}) => {
if (val.var) {
if (val.var === name) {
this._states[i] = {
key:state.key,
value:value
};
}
else new global.MissingVarError(state.key, val.var);
}
});
} else {
if (typeof val === "object" && val.var)
new global.MissingVarError(state.key, val.var);
}
}
});
}
}
/**
* Set States
*
* Get States from JS Parsed and set to Component States
*
* @public
* @param {Array} statesArray Array with all states from JS Parser
*/
set statesFromJS(statesArray){
this._states = new Array();
this._computed = new Array();
this._methods = new Array();
this._components = new Array();
this._watchers = new Array();
this._props = new Array();
this._handleInputs = false;
this._conditionals = new Array();
this._loops = new Array();
this._lifecycle = new Array();
if (statesArray) {
this._states = statesArray;
}
}
/**
* Setter Watcher
*
* Get State Watchers from JS Parsed and set to Component Watchers
*
* @public
* @param {Array} watchersArray
*/
set watchers(watchersArray){
if (watchersArray) {
watchersArray.forEach(({name}) => {
var count = 0;
this.states.forEach(state => {
const key = typeof state === "object" ? state.key : state;
if (key === name) count++;
});
this.props.forEach(prop => {
if (prop === name) count++;
});
if (count === 0) {
new global.UndefinedStateError({type:"watcher", name});
}
});
this._watchers = watchersArray;
}
}
/**
* Getter Watchers
*
* @public
*
* @return {Array}
*/
get watchers(){
return this._watchers;
}
/**
* Lifecycle Setter
*
* @public
* @param {Array} lifecycles
* @param {String} type
*
* @return {Object}
*/
setLifecycle(lifecycles) {
if (lifecycles.length > 0) {
const jsFiltered = this._filterJS(lifecycles);
this._lifecycle = jsFiltered.map(({name, content}) => {
if (global.RocketTranslator.mode === "react") {
switch(name) {
case "unmounted":
new global.LifecycleIsNotUsed(name, "Angular and Vue");
break;
case "beforeMount":
name = "componentWillMount";
break;
case "mounted":
name = "componentDidMount";
break;
case "beforeUpdate":
name = "componentWillUpdate";
break;
case "updated":
name = "componentDidUpdate";
break;
case "checkUpdate":
name = "componentShouldUpdate";
break;
case "beforeUnmount":
name = "componentWillUnmount";
break;
default: break;
}
} else if (global.RocketTranslator.mode === "veact") {
switch(name) {
case "checkUpdate":
new global.LifecycleIsNotUsed(name, "Angular and React");
break;
case "beforeMount":
case "mounted":
case "beforeUpdate":
case "updated":
break;
case "beforeUnmount":
name = "beforeDestroy";
break;
case "unmounted":
name = "destroyed";
break;
default: break;
}
} else if (global.RocketTranslator.mode === "aeact") {
switch(name) {
case "beforeMount":
case "beforeUpdate":
case "beforeUnmount":
new global.LifecycleIsNotUsed(name, "Vue and React");
break;
case "mounted":
name = "ngOnInit";
break;
case "updated":
name = "ngOnChanges";
break;
case "unmounted":
name = "ngOnDestroy";
break;
case "checkUpdate":
name = "ngDoCheck";
break;
default: break;
}
}
return {
name,
content
};
});
}
}
/**
* Lifecycle Getter
*
* @public
* @return {Array}
*/
get lifecycle() {
return this._lifecycle;
}
/**
* Components Setter
*
* @public
* @param {string} html
*/
set components(html){
const _matchComponents = html.match(/<([A-Z]\w*).*\/>/g); //Match Components
if (Array.isArray(_matchComponents)) {
_matchComponents.forEach(e => {
const name = e.match(/[A-Z]\w*/g)[0]; //Get Component Name
this._components.push({name, type: "internal"});
});
this._components.forEach(({name}) => {
this._components = this._components.filter(({name:innerName}) => name !== innerName);
});
}
const splitComponentWithContent = html.split("<component ");
splitComponentWithContent.forEach((e, i) => {
if (i > 0) {
const name = e.match(/component-name\s*=\s*('|")\w*/)[0].replace(/component-name\s*=\s*("|')/, "");
const content = e.replace(/.*>(\r\n|\n|\r)/, "").split(/(\r\n|\n|\r)*\t*<\/component>/)[0];
this._components.push({name, type: "internal"});
this.componentsContent.push({
name,
content
});
}
});
this.componentsContent.forEach(({name}) => {
var duplicates = 0;
this.componentsContent.forEach(ev => {
if (name === ev.name) duplicates++;
});
if (duplicates > 1) {
new global.DuplicateComponentError(name);
}
});
}
set externalComponents(data) {
this._components.concat(data);
}
/**
* Components Getter
*
* @public
*
* @return {Array}
*/
get components() {
return this._components;
}
/**
* Computed Setter
*
* @public
* @param {array} dataArray Array With All Data
*/
set computed(dataArray){
var _computedArray = []; //Declare Empty Array
//Map Array to get computed methods
dataArray.forEach(e => {
const _computedMatched = e.match(this._regExpToMatchComputed);
//If Match push to empty array
if (Array.isArray(_computedMatched)) {
//This must match something like: {Name - computed}
_computedArray.push(_computedMatched[0]);
}
});
//If have matched computed push to Component Computed
if (_computedArray.length > 0) {
var computedList = ["1234"];
_computedArray = _computedArray.filter(e => {
var duplicate = false;
computedList.forEach(ev => {
if (e.name === ev) duplicate = true;
else computedList.push(e.name);
});
if (!duplicate) return e;
});
_computedArray.forEach(e => {
this._computed.push({
name:e.match(/^\w*/g)[0],
content:null
});
});
}
}
/**
* Computed Getter
*
* @public
*
* @return {Array}
*/
get computed() {
return this._computed;
}
/**
* State In Bind Attributes Setter
*
* @public
* @param {String} html
*/
set statesInBindAttributes(html) {
html.split(/:(?=\w*=)/).forEach((bindAttr, i) => {
if (i > 0) {
const withTypeRegExp = /^\w*=("|')(\w*(\s*-\s*\w*)*)("|')/;
if (withTypeRegExp.test(bindAttr)) {
const attrib = bindAttr.match(withTypeRegExp)[0];
if (/state/.test(attrib)) {
const stateName = attrib.replace(/'/g, "\"").split(/="/)[1].replace(/\s*-.*$/, "");
this._states.push(stateName);
}
}
}
});
}
/**
* States In Bars Setter
*
* @public
* @param {array} dataArray
*/
set statesInBars(dataArray){
/*
Capture State Without Value and push to Empty Array
*/
var _stateArray = []; //Declare Empty Array to State With Declaration: {name - state}
dataArray.forEach(e => {
const _matched = e.match(this._regExpToMatchState);
if (Array.isArray(_matched)) {
_stateArray.push(_matched[0]);
}
});
/*
Capture State With Value and Instance and push to Empty Array
*/
var _stateWithValueArray = []; //Declare Empty Array to State With Value: {name - state - someValue}
dataArray.forEach(e => {
const _matched = e.match(this._regExpToMatchStateWithValue);
if (Array.isArray(_matched)) {
_stateWithValueArray.push(_matched[0]);
}
});
/*
* If a state is defined on Javascript filter declaration without value
*/
_stateArray = _stateArray.filter(e => {
var stateIsDefined = false;
this._states.forEach(state => {
var stateName = typeof state === "object" ? state.key : state;
//If Match e with state name
if (new RegExp(`^${stateName}\\s*-\\s*state`).test(e))
stateIsDefined = true;
});
return !stateIsDefined;
});
_stateWithValueArray.forEach(e => {
this._states.forEach(state => {
var stateName = typeof state === "object" ? state.key : state;
//If Match e with state name
if (new RegExp(`^${stateName}\\s*-\\s*state\\s*-\\s*.*`).test(e))
new global.StateAlreadyDefinedError(stateName);
});
});
//If State With Declaration, Map and Push to Component States
if (_stateArray.length > 0){
_stateArray.forEach(e => {
const _stateName = e.match(/^\w*/g)[0]; //Get State Name
this._states.push(_stateName);
});
}
//If State With Value, Map and Push to Component States
if (_stateWithValueArray) {
_stateWithValueArray.forEach(e => {
const _getKey = e.match(/^\w*/);
const value = this._defineTypeFromString(e.match(/(\w*|\{.*\}|\[.*\]|('|")\w*(\s*\w*)*('|"))$/)[0]); //Set Value
const key = _getKey[0]; //Set Key
this._states.push({key, value });
});
}
}
/**
* States Getter
*
* @public
* @return {Array}
*/
get states() {
return this._states;
}
/**
* Methods Setter
*
* Map and get all HTML events attr like onclick, onsubmit, etc.
*
* @public
* @param {string} html HTML String
*/
set methods(html) {
const methodRegExp = new RegExp(`on(${events.join("|")})=("|')\\w*\\(.*\\)("|')`, "g"),
methodWithoutParamsRegExp = new RegExp(`on(${events.join("|")})=("|')\\w*("|')`, "g");
var haveMethods = methodRegExp.test(html),
haveMethodsWithoutParams = methodWithoutParamsRegExp.test(html);
const getDuplicates = () => {
//Array to push each method if don't is duplicate
var duplicateList = ["1234"];
//Methods Errors Handle
this._methods = this._methods.filter(method => {
let duplicate = false;
duplicateList.forEach(methodNameToCompare => {
if (method.name === methodNameToCompare)
duplicate = true;
else
duplicateList.push(method.name);
});
if (!duplicate) return method;
});
};
if (haveMethods) {
html.match(methodRegExp).forEach(e => {
const split = e.split("=");
const name = split[1].match(/\w*(?=\()/)[0];
this._methods.push({
name, /*Get Method Name*/
content:null
});
});
getDuplicates();
} else if (haveMethodsWithoutParams) {
html.match(methodWithoutParamsRegExp).forEach(e => {
const name = e.replace(/\w*\s*=\s*('|")/, "").match(/\w*(?="|')/)[0];
this._methods.push({
name, /*Get Method Name*/
content:null
});
});
getDuplicates();
}
}
/**
* Methods Getter
*
* @public
* @return {Array}
*/
get methods() {
return this._methods;
}
/**
* Props In Bind Attributes Setter
*
* @public
* @param {String} html
*/
set propsInBindAttributes(html) {
html.split(/:(?=\w*=)/).forEach((bindAttr, i) => {
if (i > 0) {
const regExpToMatch = /^\w*=("|')(\w*(\s*-\s*\w*)*)("|')/;
if (regExpToMatch.test(bindAttr)) {
const attrib = bindAttr.match(regExpToMatch)[0];
if (/prop/.test(attrib)) {
const propName = attrib.replace(/'/g, "\"").split(/="/)[1].replace(/\s*-.*$/, "");
this._props.push(propName);
}
}
}
});
}
/**
* Props In Bars
*
* @public
* @param {Array} dataArray
*/
set propsInBars(dataArray) {
//Map Array
dataArray.forEach(e => {
//If Match Add Prop Name to Props
if (e.match(this._regExpToMatchProps)) {
this._props.push(e.replace(/\s-\s\w*/g, ""));
}
});
}
/**
* Props Getter
*
* @public
*
* @return {Array}
*/
get props() {
return this._props;
}
/**
* Inputs Setter
*
* Get Input, Textarea and Option Tags from HTML String
*
* @public
* @param {string} html
*/
set inputs(html) {
//Match Tags
const inputs = html.match(/<(input|select|textarea).*(\/>|>)/g);
if (Array.isArray(inputs)) {
//Map Matches Tags
inputs.forEach(e => {
//If the tag have the attr "name" set an input handler
const name = e.match(/name=('|")\w*('|")/g);
if (Array.isArray(name)) {
const stateKey = name[0].match(/('|")\w*(?="|')/)[0].slice(1); //Get the name value to declare a state
if (!stateKey)
new global.UndefinedInputNameError(e.split(/\r\n|\n|\r/)[0]);
this._handleInputs = true;
this._states.push(stateKey); //push to states
}
else {
if (!global.RocketTranslator.ignoreInputName) new global.ExpectedAttributeError(e.split(/\r\n|\n|\r/)[0], "name");
}
});
}
}
/**
* Handle Inputs Getter
*
* @public
* @return {Array}
*/
get handleInputs() {
return this._handleInputs;
}
/**
* Conditionals Validator
*
* @description Validate that conditions are a state or prop
*
* @public
* @param {String} html
*/
conditionalsValidator(html){
//Function to get tag condition
const getCond = data => {
const tagRegExp = /\s*tag=('|")\w*(-\w*)*("|')/;
data = data.replace(tagRegExp, "");
const dataCond = data.match(/cond=('|").*('|")/g);
return dataCond[0].replace(/cond=('|")/, "").replace(/('|")$/, "");
};
const condTagsArray = html.split(/<if |<else-if /);
condTagsArray
.forEach((e, i) => {
if (i > 0) {
const cond = getCond(e);
const condDefined = cond.match(/^\w*/)[0];
const matchState = this.isState(condDefined);
if (!matchState) new global.UndefinedStateError({type:"conditional", name:condDefined, condition: cond});
}
});
}
/**
* Conditionals Getter
*
* @public
*
* @return {Array}
*/
get conditionals() {
return this._conditionals;
}
/**
* Loops Validator
*
* @description Validate that loop are a state or prop
*
* @public
* @param {String} html
*/
loopsValidator(html){
const loopsTagsArray = html.split(/<for /);
loopsTagsArray
.forEach((e, i) => {
if (i > 0) {
const valueAndState = e.match(/val=('|").*(?=('|")>)/)[0];
const stateToMap = valueAndState.replace(/^.*in /, "").replace(/('|").*/, "");
const matchState = this.isState(stateToMap);
if (!matchState) new global.UndefinedStateError({type:"loop", name:stateToMap});
}
});
}
/**
* Loops Getter
*
* @public
*
* @return {Array}
*/
get loops() {
return this._loops;
}
//------------------------------------------------------------------------------
/*Internal Methods*/
/**
* JSON Prettify
*
* Convert an Object to String and add new lines and indents to code beauty
*
* @protected
* @param {Object} json
*
* @return {String}
*/
_JSONPrettify(json){
Object.entries(json).forEach(e => {
const name = e[0];
const value = e[1];
if (value === undefined)
json[name] = null;
})
var jsonToString = JSON.stringify(json); //Convert to String
const quoteMatch = jsonToString.match(/"\w*"(?=:)/g); //Get Object keys
quoteMatch.forEach(e => {
//Add indents and delete quotes in state keys
jsonToString = jsonToString.replace(e, `${e.slice(1, e.length-1)}`);
});
//Filter Globals
this._globals.forEach(glob => {
jsonToString = jsonToString.replace(new RegExp(`('|")${glob}('|")`), glob);
});
return jsonToString.replace(/\{/g, "{\n")
.replace(/,(?=(\t)*\w*:)/g, ",\n")
.replace(/}/g, "\n}");
}
/**
* Set Data From HTML
*
* Get All Data From HTML
*
* @private
* @param {string} html
*/
_setDataFromHTML(html){
this.components = html; //Get Components
html = html
.split("<component ")
.map((e, i) => {
if (i > 0) {
const name = e.match(/name=('|")\w*/)[0].slice(6);
const splitted = e.split("</component>");
const tag = splitted[0].split(/\r\n|\n|\r/)[0];
return tag.replace(/name=('|")\w*('|")/, name).replace(">", "/>") + splitted[1];
}
return e;
})
.join("<");
/*
* Get all data that was be declared with "{Name - Type}" format.
*/
const _getBarsSyntax = html
.split("{")
.map((e, i) => {
if (i > 0) {
const match = e.match(/.*(?=\})/g); //Get All that continue with "}"
if(Array.isArray(match))
return match[0];
new global.ExpectedTokenError(e);
}
}).filter(a => a);
//Handle Error
_getBarsSyntax.forEach(e => {
if (/^\w*$/.test(e))
return;
if (!/\w*\s*-\s*(state|prop|computed)/.test(e))
new global.UndefinedTypeError(e);
});
if (_getBarsSyntax) {
this.statesInBars = _getBarsSyntax; //Get States
this.computed = _getBarsSyntax; //Get Computed Methods
this.propsInBars = _getBarsSyntax; //Get Props
}
this.statesInBindAttributes = html;
this.propsInBindAttributes = html;
this.inputs = html; //Get Inputs, Textarea and Options
this.conditionalsValidator(html); //Get conditionals Data
this.loopsValidator(html); //Get Loops Data
this.methods = html; //Call Method to Get Data from HTML String
}
/**
* Filter Javascript
*
* Get an Object's Array with JS Data and return with Vue or React Syntax
*
* @protected
* @param {Array} JsArray
*
* @return {Array}
*/
_filterJS(JsArray){
//Watch if have Content
if (JsArray.length > 0) {
//Map JS Content
const JsonArray = JsArray.map(({content, name}) => {
if (/^async/.test(content))
name = `async ${name}`;
const params = content.match(/\(.*\)|(\w*(?=\s*=>))/)[0];
content = content.replace(/.*(?={)/, "");
var data = content; //Asign content to var data
/*
Map exist state to asign the state declaration to data
Example: If var 'name' is a state
On React was be: 'this.state.name'
and on Vue was be: 'this.name'
*/
data = this._expressionsFilter(content);
if (global.RocketTranslator.mode === "react")
data = this._setStateFilter(data);
data = data.replace(/\.RocketStates\.|\.RocketProps\./g, ".");
return {
name,
content:params + data
.split("\n")
.map((es, i) => {
if (es && i > 0 && es != /^}(\s|\t)*$/) return `${es}\n`;
else return `${es}\n`;
})
.join("")
.replace(/(\n|\r)$/g, "")
};
});
return JsonArray;
}
}
/**
* Set State Filter
*
* Filter the state value assignament and change: 'this.state.name = "Value";'
* with: 'this.setState({name: "Value"});'
*
* Executes only when translate React
*
* @private
* @param {String} data
*
* @return {String}
*/
_setStateFilter(data) {
const statesToChange = data.match(/this\.state\.\w*\s*=.*/g);
if(statesToChange === null)
return data;
statesToChange.forEach(states => {
const value = states.match(/=.*$/)[0].replace(/=\s*/, "").replace(/;/, "");
const state = states.replace(/this\.state\./, "").replace(/\s*=.*/, "");
const setStateString = `this.setState({${state}: ${value}});`;
data = data.replace(states, setStateString);
});
return data;
}
/**
* Parse JS
*
* Parse Javascript with Babylon parser
*
* @param {String} code
*
* return {Object}
*/
_parseJS(code) {
const {tokens} = parse(code);
const parsed = tokens.map(({type: tokenType, start, value}, i) => {
const matchVar = new RegExp(`(var|let|const)\\s*${value}`).exec(code);
const matchFuntion = new RegExp(`${value}\\s*\\(.*\\)`).exec(code);
const matchParam = new RegExp(`\\(\\s*(\\w*\\s*,\\s*)*${value}(\\w*\\s*,\\s*)*\\)\\s*{|\\(\\s*(\\w*\\s*,\\s*)*${value}(\\w*\\s*,\\s*)*\\)\\s*=>\\s*{|\\s*${value}\\s*=>`).exec(code);
const objectParam = new RegExp(`^${value}\\s*:`).exec(code.slice(start));
var type = tokenType.label;
if (matchVar !== null)
type = matchVar[1];
else if (matchFuntion !== null)
type = "function";
else if (matchParam !== null || objectParam !== null)
type = "param";
if (value === "States") {
// eslint-disable-next-line prefer-destructuring
value = tokens[i+2].value;
type = "state";
delete tokens[i+2];
}
else if (value == "Props") {
// eslint-disable-next-line prefer-destructuring
value = tokens[i+2].value;
type = "prop";
delete tokens[i+2];
}
return {
type,
position: start,
value
};
});
const vars = parsed.filter(({type}) => /var|let|const|param/.test(type));
const data = parsed.filter(({type}) => /function|name|state|prop/.test(type));
return {
vars,
data
};
}
/**
* Expressions Filter
*
* Filter States and Props vars with corresponding caller
*
* @private
* @param {String} code
*
* @return {String}
*/
_expressionsFilter(code) {
//Filter States
var filtered = code;
const {data} = this._parseJS(code);
const isReact = global.RocketTranslator.mode === "react";
const stateReplacemment = isReact ? "this.state." : "this.";
const propReplacemment = isReact ? "this.props." : "this.";
data.reverse().forEach(({value, position}) => {
var isState = false;
const isMethod = this.isMethod(value);
const isProp = this.isProp(value);
var isStateInObject = false;
const init = filtered.slice(0, position);
const rest = filtered.slice(position);
for (let i = 0; i <= this._states.length; i++) {
const state = this._states[i];
const name = typeof state === "object" ? state.key : state;
if (value === name) {
isState = true;
isStateInObject = new RegExp(`\\n(\\t*|(\\s\\s)*)${state}\\s*,|(\\{\\s*|,\\s*)${state}\\s*(,|\\})`).test(filtered);
break;
}
}
if (isStateInObject) {
filtered = `${init}${value}: ${stateReplacemment}${rest}`;
return;
}
if (isState) {
filtered = `${init}${stateReplacemment}${rest}`;
return;
}
if (isProp) {
filtered = `${init}${propReplacemment}${rest}`;
return;
}
if (isMethod) {
filtered = `${init}this.${rest}`;
return;
}
});
return filtered;
}
/**
* Globals Getter
*
* Getter that return the global list
*
* @private
* @return {Array}
*/
get _globals () {
if (!global.RocketGlobal)
return globalList;
const {defineGlobals} = new Function(`return {${global.RocketGlobals}}`)();
return globalList.concat(defineGlobals());
}
/**
* Define Type From String
*
* Get String Value and Parse that
*
* @param {string} string String Value
*
* @return {any}
*/
_defineTypeFromString(string){
const _isString = /^("|').*('|")$/.test(string);
const _isDigit = /^\d*$/.test(string);
const _isBoolean = /(true|false)$/g.test(string);
const _isArray = /^\[.*\]$/.test(string);
const _isObject = /^\{(\r|\n)*((\t*).*(\r|\n*))*\}/g.test(string);
const _isNull = /null$/g.test(string);
const _isUndefined = /undefined$/g.test(string);
const _isNaN = /NaN$/g.test(string);
const _isInfinity = /Infinity$/g.test(string);
if (_isDigit)
return parseFloat(string);
if (_isNull)
return null;
if (_isNaN)
return NaN;
if (_isInfinity)
return Infinity;
if (_isUndefined)
return undefined;
if (_isBoolean)
return this._BooleanParser(string);
if(_isArray)
return this._ArrayAndObjectParser(_isArray[0]);
if (_isObject)
return this._ArrayAndObjectParser(_isObject[0]);
if(_isString)
return string.replace(/("|')/g, ""); //String Value
/*is Var*/
return {var:string};
}
/**
* Boolean Parser
*
* Parse Boolean String
*
* @param {string} string String Value
*
* @return {Boolean}
*/
_BooleanParser(string){
if (string === "true")
return true;
return false;
}
/**
* Array And Object Parser
*
* Parse Array, Object and Define Type
*
* @param {string} string String Value
*
* @return {Array}
*/
_ArrayAndObjectParser(string){
return JSON.parse(string);
}
//------------------JSX Methods--------------------//
/**
* Parse HTML
*
* @description Parse HTML and generate a JSX Linear AST
* @private
* @param {String} code
*
* @return {Array}
*/
_parseHTML(code) {
//TO DO implement Splitter /<(?=\w*(?!.*>.*(?!{).*})|\/\w*(?!.*>.*(?!{).*}))/
return code.split(/</).filter(e => e).map((line) => {
const tag = line.match(/\/*\w*(-\w*)*/)[0].replace("/", "");
var content = line.match(/>.*/)[0].replace(">", "");
var attr = line.match(/.*>/)[0].replace(/>/, "");
attr = attr.split(/\s(?=:*\w*=('|"))/)
.filter(e => /\w*=("|')/.test(e))
.map(e => {
const isBind = e[0] === ":";
if (isBind) e = e.slice(1);
const splitted = e.split("=");
const name = splitted[0];
splitted.shift();
const value = splitted.join("=").replace(/^("|')|('|").*$/g, "");
return {
name,
value,
isBind
};
});
var mode;
var type;
//Parse Content
if (/\w*(\s*-\s*\w*)*/.test(content)) {
const matches = content.match(/{\w*(\s*-\s*.*)*}/g);
if (Array.isArray(matches)) {
matches.forEach(e => {
const name = e.match(/^\{\w*/)[0].slice(1);
const matchType = e.match(/\s*-\s*\w*/);
const type = Array.isArray(matchType) ? matchType[0].replace(/\s*-\s*/, "") : null;
if (global.RocketTranslator.mode === "react") {
if (type === "prop")
content = content.replace(e, `{this.props.${name}}`);
else if (type === "state")
content = content.replace(e, `{this.state.${name}}`);
else if (type === "computed")
content = content.replace(e, `{${name}}`);
}
else {
if (type !== null)
content = content.replace(e, `{this.${name}}`);
}
});
}
}
if (tag[0].toUpperCase() === tag[0])
type = "component";
else if (tag === "if" || tag === "else" || tag === "else-if")
type = "conditional";
else if (tag === "for")
type = "loop";
else
type = "normal";
if (/^\/\w*(-\w*)*>/.test(line))
mode = "close";
else if (/\w*.*\/>/.test(line))
mode = "single";
else if (/^\w*/.test(line))
mode = "open";
return {
tag,
mode,
type,
content,
attr
};
});
}
/**
* Generate JSX
*
* @description Generate JSX tags from linear AST
* @private
* @param {Array} parsedCode
*
* @return {Array}
*/
_generateJSXAst(parsedCode) {
var parts = [];
var tree = [];
var condID = -1;
const parseAttributes = attr => {
const parseMathOps = (operator, value) => {
const name = value.split(/\s*(-|\*|\/|\+)\s*/)[0];
return `{() => this.setState({${name}: this.state.${value}})}`;
};
return attr.map(({name, value, isBind}) => {
if (name.startsWith("on")) {
const valueName = value.replace(/\+|-|\*|\/|State\.|Props\./g, "").match(/\w*/)[0];
name = `on${ this._generateJSXEventName(name.slice(2))}`;
if (/\w*\+\+/.test(value) || /\+\+\w*/.test(value)) { //Increment
if (!this.isState(valueName) && !this.isProp(valueName))
new global.UndefinedStateError({type: "function", name: valueName, content: value});
const stateName = value.replace("++", "");
value = `{() => this.setState({${stateName}: this.state.${stateName} + 1})}`;
}
else if (/\w*--/.test(value) || /--\w*/.test(value)) { //Decrement
if (!this.isState(valueName) && !this.isProp(valueName))
new global.UndefinedStateError({type: "function", name: valueName, content: value});
const stateName = value.replace("--", "");
value = `{() => this.setState({${stateName}: this.state.${stateName} - 1})}`;
}
else if (/\w*\s*\+\s*\d*/.test(value)) {//Math Operation: Add
if (!this.isState(valueName) && !this.isProp(valueName))
new global.UndefinedStateError({type: "function", name: valueName, content: value});
value = parseMathOps("+", value);
}
else if (/\w*\s*-\s*\d*/.test(value)) {//Math Operation: Sust
if (!this.isState(valueName) && !this.isProp(valueName))
new global.UndefinedStateError({type: "function", name: valueName, content: value});
value = parseMathOps("-", value);
}
else if (/\w*\s*\*\s*\d*/.test(value)) {//Math Operation: Mult
if (!this.isState(valueName) && !this.isProp(valueName))
new global.UndefinedStateError({type: "function", name: valueName, content: value});
value = parseMathOps("*", value);
}
else if (/\w*\s*\/\s*\d*/.test(value)) {//Math Operation: Div
if (!this.isState(valueName) && !this.isProp(valueName))
new global.UndefinedStateError({type: "function", name: valueName, content: value});
value = parseMathOps("/", value);
}
else if (/\w*\s*=\s*.*/.test(value)) {//Assing Value
if (!this.isState(valueName) && !this.isProp(valueName))
new global.UndefinedStateError({type: "function", name: valueName, content: value});
value = `{() => this.setState({${value.replace(/\s*=\s*.*/, "")}: this.state.${value.replace(/\w*\s*=\s*/, "")}})}`;
}
else {
if (!this.isMethod(valueName))
new global.UndefinedMethodError(valueName);
value = `${(value.endsWith(")") ? "{() => this." : "{this.") + value }}`;
}
} else {
if (isBind) {
if (this.isState(value) || /RocketStates\.\w*/.test(value))
value = `{this.state.${value.replace("RocketStates.", "")}}`;
else if (this.isProp(value) || /RocketProps\.\w*/.test(value))
value = `{this.props.${value.replace("RocketProps.", "")}}`;
else
value = `{${value}}`;
}
else
value = `"${value}"`;
}
return `${name}=${value}`;
}).join(" ");
};
parsedCode.forEach(({type, mode, tag, content, attr}, i) => {
const isOpen = mode === "open";
if (i === 0) {
parts.push({tag, tree: Object.assign([], tree).join(","), content: `\n<${tag}>@content@${this.styles}\n</${tag}>`, type: "root"});
tree.push(tag);
} else {
if (isOpen) {
if (type === "loop") {
var state;
var toMap;
attr.forEach(({name, value}) => {
if (name === "val") {
state = value.match(/in\s*\w*/)[0].replace(/in\s*/, "");
this.states.forEach(k => {
const stateName = typeof k === "object" ? k.key : k;
var mode = global.RocketTranslator.mode === "react" ? "this.state." : "this.";
if (state === stateName) {
state = state.replace(stateName, `${mode}${stateName}`);
}
});
this.props.forEach(prop => {
var mode = global.RocketTranslator.mode === "react" ? "this.props." : "this.";
if (state === prop) {
state = state.replace(prop, `${mode}${prop}`);
}
});
toMap = value.match(/.*(?=\s*in)/)[0];
}
});
parts.push({tag, tree: Object.assign([], tree).join(","), content:`const loop${i} = ${state}.map(${toMap} => (@content@));\n`, type:"loop"});
}
else if (type === "conditional") {
var condition;
var newTag = tag.replace("-", " ");
condID++;
if (tag !== "else") {
attr.forEach(({name, value}) => {
if (name === "cond") {
this.states.forEach(state => {
const stateName = typeof state === "object" ? state.key : state;
var mode = global.RocketTranslator.mode === "react" ? "this.state." : "this.";
value = value.replace(stateName, `${mode}${stateName}`);
});
this.props.forEach(prop => {
var mode = global.RocketTranslator.mode === "react" ? "this.props." : "this.";
value = value.replace(prop, `${mode}${prop}`);
});
condition = ` (${value})`;
}
});
}
else
condition = "";
parts.push({tag, tree: Object.assign([], tree).join(","), content:`${newTag}${condition} { conditional${condID} = @content@}`, type:"conditional"});
}
else {
if (tag === "a" && global.RocketTranslator.allowSSR) {
var href;
for (let i = 0; i < attr.length; i++) {
const {name} = attr[i];
if (name === "href") {
href = parseAttributes([attr[i]]);
delete attr[i];
break;
}
}
parts.push({tag: "Link", tree: Object.assign([], tree).join(","), type: "content", content: `\n<Link${href ? ` ${href}` : ""} prefetch>@content@\n</Link>@content@`});
tree.push("Link");
var existsLink = false;
for (let i = 0; i < this._components.length; i++) {
const {name, path} = this._components[i];
if (name === "Link" && path === "next/link") {
existsLink = true;
break;
}
}
if (!existsLink)
this._components.push({name: "Link", type: "external", path: "next/link"})
}
const attributes = parseAttributes(attr);
parts.push({tag, tree: Object.assign([], tree).join(","), type: "content", content: `\n<${tag}${attributes ? ` ${attributes}` : ""}>${content ? content : "@content@\n"}</${tag}>`});
}
tree.push(tag);
}
else if (mode === "single") {
const attributes = parseAttributes(attr);
parts.push({tag, tree: Object.assign([], tree).join(","), type, content: `\n<${tag}${attributes ? ` ${attributes}` : ""}/>`});
}
else {
const next = parsedCode[i+1];
if (next) {
if (mode === "close" && (next.type === "conditional" && next.mode === "close")) {
condID--;
}
}
tree.pop();
parts.push({tag, type: "close", content:"", tree: Object.assign([], tree).join(",")});
if (tag === "a" && global.RocketTranslator.allowSSR) {
tree.pop();
parts.push({tag: "Link", type: "close", content:"", tree: Object.assign([], tree).join(",")});
}
}
}
});
return parts;
}
/**
* Generate JSX Event Name
*
* @description Take HTML event name and return in JSX
* @private
* @param {String} name
*
* @return {String}
*/
_generateJSXEventName(name) {
var splitted;
switch (name) {
//3
case "dblclick":
case "canplay":
case "popstate":
case "keydown":
case "keypress":
case "keyup":
splitted = name.split("");
splitted[0] = splitted[0].toUpperCase();
splitted[2] = splitted[2].toUpperCase();
return splitted.join("");
//4
case "hashchange":
case "loadstart":
case "dragend":
case "dragenter":
case "dragleave":
case "dragover":
case "dragstart":
case "pagehide":
case "pageshow":
case "ratechange":
case "timeupdate":
splitted = name.split("");
splitted[0] = splitted[0].toUpperCase();
splitted[3] = splitted[3].toUpperCase();
return splitted.join("");
//5
case "afterprint":
case "focusin":
case "focusout":
case "mousedown":
case "mouseenter":
case "mouseleave":
case "mousemove":
case "mouseover":
case "mouseout":
case "mouseup":
case "touchcancel":
case "touchend":
case "touchmove":
case "touchstart":
splitted = name.split("");
splitted[0] = splitted[0].toUpperCase();
splitted[4] = splitted[4].toUpperCase();
return splitted.join("");
//6
case "volumechange":
case "beforeprint":
case "beforeunload":
case "loadeddata":
case "loadedmetadata":
splitted = name.split("");
splitted[0] = splitted[0].toUpperCase();
splitted[5] = splitted[5].toUpperCase();
return splitted.join("");
//7
case "contextmenu":
case "pointerdown":
case "pointermove":
case "pointerup":
case "pointercancel":
case "pointerenter":
case "pointerleave":
case "pointerover":
case "pointerout":
splitted = name.split("");
splitted[0] = splitted[0].toUpperCase();
splitted[6] = splitted[6].toUpperCase();
return splitted.join("");
//8
case "durationchange":
return "DurationChange";
//9
case "animationend":
case "animationiteration":
case "animationstart":
splitted = name.split("");
splitted[0] = splitted[0].toUpperCase();
splitted[8] = splitted[8].toUpperCase();
return splitted.join("");
//11
case "compositionend":
case "compositionstart":
case "compositionupdate":
splitted = name.split("");
splitted[0] = splitted[0].toUpperCase();
splitted[10] = splitted[10].toUpperCase();
return splitted.join("");
case "canplaythrough":
return "CanPlayThrough";
case "gotpointercapture":
return "GotPointerCapture";
case "lostpointercapture":
return "LostPointerCapture";
case "transitionend":
return "TransitionEnd";
default:
return name[0].toUpperCase() + name.slice(1);
}
}
/**
* JSX Generator
*
* @description Take linear AST and generate final JSX Tree
* @private
* @param {Array} JSXArray
*
* @return {Object<HTML: String, conditionals: Array, loops: Array>}
*/
_JSXGenerator(JSXArray) {
//Define empty data for set Components
var main;
JSXArray.forEach(({tree, content, type, tag}, i) => {
if (i === 0)
main = content;
if (type === "loop") {
this._loops.push(this._generateJSXLoops(JSXArray, i, {tree, content, type, tag}));
} else if (type === "conditional") {
this._conditionals.push(this._generateJSXConditional(JSXArray, i, {tree, content, type, tag}, this._conditionals));
} else {
const init = JSXArray[i+1];
var part = "";
if (init) {
if (init.type === "loop") {
part = `{${init.content.match(/const\s*\w*/)[0].replace(/const\s*/, "")}}`;
}
else if (init.type === "conditional") {
if (init.tag === "if")
part = `\n{${init.content.match(/\w*(?=\s*= @)/)[0]}}`;
else
part = "";
}
else {
if (init.content)
part = init.content;
}
} else {
var altInit = JSXArray[i];
part = altInit.content;
}
if (part) {
if (!/@content@/.test(part) && !/<a>/.test(part))
part = `${part}@content@`;
main = main.replace("@content@", part);
}
}
});
return {
loops: this.loops.filter(e => e),
conditionals: this.conditionals.filter(e => e),
html: main.replace("@content@", "")
};
}
/**
* Generate JSX Loops
*
* @description Generate loop to JSX Generator
* @private
*
* @param {Array} JSXArray
* @param {Int} LoopIndex
* @param {Object} LoopData
*
* @return {String}
*/
_generateJSXLoops(JSXArray, LoopIndex, LoopData) {
const init = JSXArray.slice(LoopIndex);
const {tree} = LoopData;
var loop;
if (init) {
for (let i = 0; i < init.length; i++) {
const {tree:newTree, tag:newTag, type:newType} = init[i];
if (tree === newTree && newType === "close" && newTag === "for") {
loop = this._JSXGenerator(init.slice(0, i - 1)).html;
break;
}
}
}
const countInit = LoopIndex + 1;
var countEnd;
for (let i = 0; i < init.length; i++) {
const newData = init[i];
const {tree:newTree, tag:newTag, type:newType} = newData;
if (tree === newTree && newType === "close" && newTag === "for") {
countEnd = i - 1;
break;
}
}
JSXArray.splice(countInit, countEnd - 1);
return loop;
}
/**
* Generate JSX Conditional
*
* @description Generate conditional to JSX Generator
* @private
*
* @param {Array} JSXArray
* @param {Int} CondIndex
* @param {Object} CondData
* @param {Array} conditional Ref to main conditional array
*/
_generateJSXConditional(JSXArray, CondIndex, CondData, conditionals) {
const init = JSXArray.slice(CondIndex + 1);
const {tree, content, tag} = CondData;
var cond;
const actualCond = JSXArray[CondIndex];
if (actualCond.tag === "if" && actualCond.type === "conditional") {
const varName = actualCond.content.match(/\w*(?=\s*=\s*@)/)[0];
conditionals.push(`var ${varName};`);
}
if (init) {
for (let a = 0; a < init.length; a++) {
const {tree:newTree, tag:newTag, type:newType} = init[a];
if (tree === newTree && newType === "close" && newTag === tag) {
cond = `${this._JSXGenerator(init.slice(0, a)).html} `;
if (cond.startsWith("if") || cond.startsWith("else"))
cond = cond.match(/{\s\w*\s/)[0].slice(1);
else if (/loop/.test(cond))
cond = cond.match(/^const \w*\s/)[0].replace("const ", "");
break;
}
}
conditionals.push(content.replace("@content@", cond));
}
var countInit = CondIndex + 1;
var countEnd;
for (let a = 0; a < init.length; a++) {
const newData = init[a];
const {tree:newTree, tag:newTag, type:newType} = newData;
if (tree === newTree && newType === "close" && newTag === tag) {
countEnd = a;
break;
}
}
JSXArray.splice(countInit, countEnd);
}
/**
* Generate JSX
*
* @description JSX Generator Interface
* @protected
* @param {String} HTML
*
* return {Object}
*/
_generateJSX(HTML) {
return this._JSXGenerator(this._generateJSXAst(this._parseHTML(HTML)));
}
/**
* Is State
*
* @description Get a "name" and return if exist in states Array
*
* @public
* @param {String} name
*
* @return {Boolean}
*/
isState(name) {
var isState = false;
for (let i = 0; i < this._states.length; i++) {
const state = this._states[i];
const stateName = typeof state === "object" ? state.key : state;
if (name === stateName) {
isState = true;
break;
}
}
return isState;
}
/**
* Is Prop
*
* @description Get a "name" and return if exist in Props Array
*
* @public
* @param {String} name
*
* @return {Boolean}
*/
isProp(name) {
var isProp = false;
for (let i = 0; i < this._props.length; i++) {
const prop = this._props[i];
if (name === prop) {
isProp = true;
break;
}
}
return isProp;
}
/**
* Is Method
*
* @description Get a "name" and return if exist in Methods Array
*
* @public
* @param {String} name
*
* @return {Boolean}
*/
isMethod(name) {
var isMethod = false;
for (let i = 0; i < this._methods.length; i++) {
const methodName = this._methods[i].name;
if (name === methodName.match(/\w*$/)[0]) {
isMethod = true;
break;
}
}
return isMethod;
}
/**
* Is Computed
*
* @description Get a "name" and return if exist in Computeds Array
*
* @public
* @param {String} name
*
* @return {Boolean}
*/
isComputed(name) {
var isComputed = false;
for (let i = 0; i < this._computed.length; i++) {
const computedName = this._computed[i].name;
if (name === computedName) {
isComputed = true;
break;
}
}
return isComputed;
}
/**
* Is Watcher
*
* @description Get a "name" and return if exist in Watcher Array
*
* @public
* @param {String} name
*
* @return {Boolean}
*/
isWatcher(name) {
var isWatcher = false;
for (let i = 0; i < this._watchers.length; i++) {
const watcherName = this._watchers[i].name;
if (name === watcherName) {
isWatcher = true;
break;
}
}
return isWatcher;
}
}
module.exports = StateManagement;