diff --git a/src/core/lib/Code.mjs b/src/core/lib/Code.mjs new file mode 100644 index 00000000..ee55dfaf --- /dev/null +++ b/src/core/lib/Code.mjs @@ -0,0 +1,31 @@ +/** + * Code functions. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + */ + +/** + * This tries to rename variable names in a code snippet according to a function. + * + * @param {string} input + * @param {function} replacer - this function will be fed the token which should be renamed. + * @returns {string} + */ +export function replaceVariableNames(input, replacer) { + const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig; + + return input.replace(tokenRegex, (...args) => { + const match = args[0], + quotes = args[1]; + + if (!quotes) { + return match; + } else { + return replacer(match); + } + }); +} diff --git a/src/core/operations/GenericCodeBeautify.mjs b/src/core/operations/GenericCodeBeautify.mjs new file mode 100644 index 00000000..5078bebc --- /dev/null +++ b/src/core/operations/GenericCodeBeautify.mjs @@ -0,0 +1,161 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Generic Code Beautify operation + */ +class GenericCodeBeautify extends Operation { + + /** + * GenericCodeBeautify constructor + */ + constructor() { + super(); + + this.name = "Generic Code Beautify"; + this.module = "Code"; + this.description = "Attempts to pretty print C-style languages such as C, C++, C#, Java, PHP, JavaScript etc.

This will not do a perfect job, and the resulting code may not work any more. This operation is designed purely to make obfuscated or minified code more easy to read and understand.

Things which will not work properly:"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const preservedTokens = []; + let code = input, + t = 0, + m; + + // Remove strings + const sstrings = /'([^'\\]|\\.)*'/g; + while ((m = sstrings.exec(code))) { + code = preserveToken(code, m, t++); + sstrings.lastIndex = m.index; + } + + const dstrings = /"([^"\\]|\\.)*"/g; + while ((m = dstrings.exec(code))) { + code = preserveToken(code, m, t++); + dstrings.lastIndex = m.index; + } + + // Remove comments + const scomments = /\/\/[^\n\r]*/g; + while ((m = scomments.exec(code))) { + code = preserveToken(code, m, t++); + scomments.lastIndex = m.index; + } + + const mcomments = /\/\*[\s\S]*?\*\//gm; + while ((m = mcomments.exec(code))) { + code = preserveToken(code, m, t++); + mcomments.lastIndex = m.index; + } + + const hcomments = /(^|\n)#[^\n\r#]+/g; + while ((m = hcomments.exec(code))) { + code = preserveToken(code, m, t++); + hcomments.lastIndex = m.index; + } + + // Remove regexes + const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi; + while ((m = regexes.exec(code))) { + code = preserveToken(code, m, t++); + regexes.lastIndex = m.index; + } + + code = code + // Create newlines after ; + .replace(/;/g, ";\n") + // Create newlines after { and around } + .replace(/{/g, "{\n") + .replace(/}/g, "\n}\n") + // Remove carriage returns + .replace(/\r/g, "") + // Remove all indentation + .replace(/^\s+/g, "") + .replace(/\n\s+/g, "\n") + // Remove trailing spaces + .replace(/\s*$/g, "") + .replace(/\n{/g, "{"); + + // Indent + let i = 0, + level = 0, + indent; + while (i < code.length) { + switch (code[i]) { + case "{": + level++; + break; + case "\n": + if (i+1 >= code.length) break; + + if (code[i+1] === "}") level--; + indent = (level >= 0) ? Array(level*4+1).join(" ") : ""; + + code = code.substring(0, i+1) + indent + code.substring(i+1); + if (level > 0) i += level*4; + break; + } + i++; + } + + code = code + // Add strategic spaces + .replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ") + .replace(/\s*<([=]?)\s*/g, " <$1 ") + .replace(/\s*>([=]?)\s*/g, " >$1 ") + .replace(/([^+])\+([^+=])/g, "$1 + $2") + .replace(/([^-])-([^-=])/g, "$1 - $2") + .replace(/([^*])\*([^*=])/g, "$1 * $2") + .replace(/([^/])\/([^/=])/g, "$1 / $2") + .replace(/\s*,\s*/g, ", ") + .replace(/\s*{/g, " {") + .replace(/}\n/g, "}\n\n") + // Hacky horribleness + .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3") + .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3") + .replace(/else\s*\n([^{])/gim, "else\n $1") + .replace(/else\s+([^{])/gim, "else $1") + // Remove strategic spaces + .replace(/\s+;/g, ";") + .replace(/\{\s+\}/g, "{}") + .replace(/\[\s+\]/g, "[]") + .replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1"); + + // Replace preserved tokens + const ptokens = /###preservedToken(\d+)###/g; + while ((m = ptokens.exec(code))) { + const ti = parseInt(m[1], 10); + code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length); + ptokens.lastIndex = m.index; + } + + return code; + + /** + * Replaces a matched token with a placeholder value. + */ + function preserveToken(str, match, t) { + preservedTokens[t] = match[0]; + return str.substring(0, match.index) + + "###preservedToken" + t + "###" + + str.substring(match.index + match[0].length); + } + } + +} + +export default GenericCodeBeautify; diff --git a/src/core/operations/SyntaxHighlighter.mjs b/src/core/operations/SyntaxHighlighter.mjs new file mode 100644 index 00000000..bde6fbde --- /dev/null +++ b/src/core/operations/SyntaxHighlighter.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import hljs from "highlight.js"; + +/** + * Syntax highlighter operation + */ +class SyntaxHighlighter extends Operation { + + /** + * SyntaxHighlighter constructor + */ + constructor() { + super(); + + this.name = "Syntax highlighter"; + this.module = "Code"; + this.description = "Adds syntax highlighting to a range of source code languages. Note that this will not indent the code. Use one of the 'Beautify' operations for that."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Language", + "type": "option", + "value": ["auto detect"].concat(hljs.listLanguages()) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const language = args[0]; + + if (language === "auto detect") { + return hljs.highlightAuto(input).value; + } + + return hljs.highlight(language, input, true).value; + } + + /** + * Highlight Syntax highlighter + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Syntax highlighter in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default SyntaxHighlighter; diff --git a/src/core/operations/ToCamelCase.mjs b/src/core/operations/ToCamelCase.mjs new file mode 100644 index 00000000..5c9fa939 --- /dev/null +++ b/src/core/operations/ToCamelCase.mjs @@ -0,0 +1,53 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import { camelCase } from "lodash"; +import Operation from "../Operation"; +import { replaceVariableNames } from "../lib/Code"; + +/** + * To Camel case operation + */ +class ToCamelCase extends Operation { + + /** + * ToCamelCase constructor + */ + constructor() { + super(); + + this.name = "To Camel case"; + this.module = "Code"; + this.description = "Converts the input string to camel case.\n

\nCamel case is all lower case except letters after word boundaries which are uppercase.\n

\ne.g. thisIsCamelCase\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Attempt to be context aware", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, camelCase); + } else { + return camelCase(input); + } + } + +} + +export default ToCamelCase; diff --git a/src/core/operations/ToKebabCase.mjs b/src/core/operations/ToKebabCase.mjs new file mode 100644 index 00000000..5e62460e --- /dev/null +++ b/src/core/operations/ToKebabCase.mjs @@ -0,0 +1,53 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import { kebabCase } from "lodash"; +import Operation from "../Operation"; +import { replaceVariableNames } from "../lib/Code"; + +/** + * To Kebab case operation + */ +class ToKebabCase extends Operation { + + /** + * ToKebabCase constructor + */ + constructor() { + super(); + + this.name = "To Kebab case"; + this.module = "Code"; + this.description = "Converts the input string to kebab case.\n

\nKebab case is all lower case with dashes as word boundaries.\n

\ne.g. this-is-kebab-case\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Attempt to be context aware", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, kebabCase); + } else { + return kebabCase(input); + } + } + +} + +export default ToKebabCase; diff --git a/src/core/operations/ToSnakeCase.mjs b/src/core/operations/ToSnakeCase.mjs new file mode 100644 index 00000000..0c138272 --- /dev/null +++ b/src/core/operations/ToSnakeCase.mjs @@ -0,0 +1,52 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import { snakeCase } from "lodash"; +import Operation from "../Operation"; +import { replaceVariableNames } from "../lib/Code"; + +/** + * To Snake case operation + */ +class ToSnakeCase extends Operation { + + /** + * ToSnakeCase constructor + */ + constructor() { + super(); + + this.name = "To Snake case"; + this.module = "Code"; + this.description = "Converts the input string to snake case.\n

\nSnake case is all lower case with underscores as word boundaries.\n

\ne.g. this_is_snake_case\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Attempt to be context aware", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, snakeCase); + } else { + return snakeCase(input); + } + } +} + +export default ToSnakeCase;