Skip to content

Instantly share code, notes, and snippets.

@zhdanovme
Last active March 15, 2016 09:17
Show Gist options
  • Select an option

  • Save zhdanovme/67d600822edae510f1ab to your computer and use it in GitHub Desktop.

Select an option

Save zhdanovme/67d600822edae510f1ab to your computer and use it in GitHub Desktop.
recast-xhh.js
#!/usr/bin/env node
// Запускать с уровня где лежит папка hh.sites.main
// Usage: ./recast-xhh.js HH/Foo
'use strict';
var recast = require('recast');
var glob = require('glob');
var fs = require('fs');
var cp = require('child_process');
var exec = require('child_process').exec;
var builder = recast.types.builders;
var STATIC_JS_PATH = './hh.sites.main/webapp-static/js/';
var processFile = process.argv[2] + '.js';
if (processFile) {
console.log('Processing only ' + processFile);
} else {
return;
}
var RESERVED_WORDS = [
'Array',
'Date',
'Infinity',
'Math',
'NaN',
'Number',
'String',
'Object'
];
var files = glob.sync(STATIC_JS_PATH + '@(jsx|HHC)/*.js');
var depModules = files.map(function(filepath) {
return filepath.replace(STATIC_JS_PATH, '').replace('.js', '');
});
var depModuleVariables = {};
for (var i in depModules) {
var depModule = depModules[i];
depModuleVariables[depModule] = depModule.replace('jsx', '').replace('HHC', '').split('/').join('');
}
var depModuleLegacyVariables = {};
for (var i in depModules) {
var depModule = depModules[i];
depModuleLegacyVariables[depModule] = 'jsx.' + depModuleVariables[depModule];
}
var jquery = 'Libraries/jquery';
depModules.push(jquery);
depModuleVariables[jquery] = '$';
depModuleLegacyVariables[jquery] = '$';
var underscore = 'Libraries/underscore';
depModules.push(underscore);
depModuleVariables[underscore] = '_';
depModuleLegacyVariables[underscore] = '_';
var raphael = 'Libraries/raphael';
depModules.push(raphael);
depModuleVariables[raphael] = 'Raphael';
depModuleLegacyVariables[raphael] = 'Raphael';
var requireModule = 'require';
depModules.push(requireModule);
depModuleVariables[requireModule] = 'require';
depModuleLegacyVariables[requireModule] = 'require';
var depRenameList = {};
depModules.forEach(function(module) {
depRenameList[module] = module.replace('jsx/', 'Utils/');
});
var b = recast.types.builders;
function escape(str) {
return str.replace(/[-[\]{}()*+?.,\\/^$|#\s]/g, '\\$&');
}
function searchVarExp(str) {
return new RegExp('[^\\d^\\w]' + escape(str) + '[\.\(\s\:\[\,]', 'g');
}
glob(STATIC_JS_PATH + '!(Libraries|External|Legacy|Tests)/**/*.js', {}, function(error, files) {
files.forEach(function(filepath) {
fs.readFile(filepath, function(error, data) {
if (processFile && filepath.indexOf(processFile) === -1) {
return;
}
console.log('OPENING:' + filepath);
var code = data.toString();
code = code.replace(/Components\.build\(\s+/, 'Components.build(');
var ast = recast.parse(code);
var firstFunction = ast.program.body[0];
if (firstFunction && firstFunction.expression) {
firstFunction = firstFunction.expression;
} else {
return;
}
if (!(firstFunction.callee &&
firstFunction.callee.name === 'define' &&
firstFunction.arguments !== undefined)) {
return;
}
var defineArguments = firstFunction.arguments;
var defineCallback;
defineArguments.forEach(function(arg, index) {
if (arg.type === 'FunctionExpression') {
defineCallback = defineArguments[index];
}
});
var componentsBuildArgument;
var buildPath;
// var toNewFormat = false;
var oldStructure;
recast.types.visit(ast, {
visitCallExpression: function(path) {
if (componentsBuildArgument) {
return false;
}
var hasBuild;
try {
hasBuild = path.node.callee.property.name === 'build';
} catch (ignore) {
this.traverse(path);
return false;
}
if (hasBuild !== true) {
this.traverse(path);
return false;
}
buildPath = path;
componentsBuildArgument = path.node.arguments[0];
foundBuild();
return false;
}
});
function foundBuild() {
var splitedPath = filepath.split(/[^\w]/);
var fileName = splitedPath[splitedPath.length - 2];
var notInCode = code.match(new RegExp('\\s'+fileName)) === null;
var constructorName = (notInCode && RESERVED_WORDS.indexOf(fileName) === -1) ? fileName : 'Constructor';
if (componentsBuildArgument.type === 'ObjectExpression') {
console.log('OBJECT FOUND:' + filepath);
return false;
}
// Build в новый формат
var dummy = recast.parse('b({\n defaults: {},\n create: function(element, params) {\n return new '+constructorName+'(element, params);\n }\n});');
var dummyObject = dummy.program.body[0].expression.arguments[0];
buildPath.node.arguments[0] = dummyObject;
//dummyObject.properties[1].value = componentsBuildArgument;
var createFunction;
var createViaVar;
var createViaPath;
//var createViaNamedFunc;
// В аргументе функция?
if (componentsBuildArgument.type === 'FunctionExpression') {
createFunction = componentsBuildArgument;
// В аргументе переменная?
} else if (componentsBuildArgument.type === 'Identifier') {
// Ищем переменную
recast.types.visit(ast, {
visitIdentifier: function(path) {
var node = path.node;
if (node.name === componentsBuildArgument.name) {
if (path.parentPath.value.init && path.parentPath.value.init.type === 'FunctionExpression') {
createFunction = path.parentPath.value.init;
//createViaVar = true;
//createViaPath = path;
}
return false;
}
this.traverse(path);
}
});
// Ищем именованую функцию
if (createFunction === undefined) {
recast.types.visit(ast, {
visitFunctionDeclaration: function(path) {
var node = path.node;
if (node.id.name === componentsBuildArgument.name) {
createFunction = node;
//createViaNamedFunc = true;
//createViaPath = path;
return false;
}
this.traverse(path);
}
});
}
}
if (createFunction === undefined) {
console.log('NOT FOUND:' + filepath);
return false;
}
// Вешаем аргументы конструктору
createFunction.params = [builder.identifier('element'), builder.identifier('params')];
// Переносим defaults
var defaultsObject;
recast.types.visit(createFunction, {
visitAssignmentExpression: function(path) {
var node = path.node;
if (node.left.property && node.left.property.name === 'defaults') {
defaultsObject = node.right;
var functionBody = path.parentPath.parentPath.value;
var defaultExpr = path.parentPath.value;
functionBody.splice(functionBody.indexOf(defaultExpr), 1);
return false;
}
this.traverse(path);
}
});
if (defaultsObject) {
dummyObject.properties[0].value = defaultsObject;
} else {
dummyObject.properties.splice(0, 1);
}
// Отрезаем use strict из кода компонента
createFunction.body.body.forEach(function(func, index) {
if (func.expression && func.expression.value === 'use strict') {
createFunction.body.body.splice(index, 1);
}
});
var noInit;
// Если у нас только this.init - переносим весь его код в конструктор
if (createFunction.body.body.length === 1 && createFunction.body.body[0].expression.left.property.original.name === 'init') {
createFunction.body.body = createFunction.body.body[0].expression.right.body.body;
noInit = true;
}
// Считаем на каком уровне имеем код\
// var initIndentRegexp = code.match(/\ +(?=this\.init)/);
// var initIndent = initIndentRegexp && initIndentRegexp[0].length;
// oldStructure = code.match(/define\(\s+/);
var functionConstructor
if (componentsBuildArgument.type === 'FunctionExpression') {
// var createProperty = dummyObject.properties.length === 2 ? dummyObject.properties[1] : dummyObject.properties[0];
functionConstructor = recast.parse("function "+constructorName+"(element, params) {};\n\n").program.body[0];
functionConstructor.body.body = createFunction.body.body;
defineCallback.body.body.unshift(functionConstructor);
} else {
var createProperty = dummyObject.properties.length === 2 ? dummyObject.properties[1] : dummyObject.properties[0];
createProperty.value.body.body[0].argument.callee.name = componentsBuildArgument.name;
functionConstructor = createFunction;
}
//!!!!!!!!
// Если переменная - то надо переименовать конструктор
// if ((createViaNamedFunc || createViaVar) && oldStructure) {
// var createProperty = dummyObject.properties.length === 2 ? dummyObject.properties[1] : dummyObject.properties[0];
// createFunction.id = null;
// createProperty.value = createFunction;
// if (createViaNamedFunc) {
// var defineBodyCallback = createViaPath.parentPath.value;
// }
// if (createViaVar) {
// var defineBodyCallback = createViaPath.parentPath.parentPath.parentPath.parentPath.value;
// }
// defineBodyCallback.splice(defineBodyCallback.indexOf(createViaPath.node),1);
// }
// if (oldStructure && initIndent === 12) {
// toNewFormat = true;
// }
// Проставляем use strict
defineCallback.body.body.forEach(function(line, index) {
if (line.expression && line.expression.value === 'use strict') {
defineCallback.body.body.splice(index, 1);
}
});
var useStrictDummy = recast.parse('\'use strict\';\n\n');
var useStrict = useStrictDummy.program.body[0];
defineCallback.body.body.unshift(useStrict);
// Добавляем сопутствующий код
var dummyThis = recast.parse('(function() {\n this.params = params;\n this.element = element;\n this.$element = $(element);\n this.init(element, params);\n})');
var dummyThisArr = dummyThis.program.body[0].expression.body.body;
var createFunctionCode = recast.print(functionConstructor).code;
function findUsage(str) {
return (createFunctionCode.indexOf(str) >= 0 &&
createFunctionCode.indexOf(str + ' =') === -1 &&
createFunctionCode.indexOf(str + '=') === -1);
}
if (findUsage('this.params')) {
createFunction.body.body.unshift(dummyThisArr[0]);
}
if (findUsage('this.element')) {
createFunction.body.body.unshift(dummyThisArr[1]);
}
if (findUsage('this.$element')) {
createFunction.body.body.unshift(dummyThisArr[2]);
}
if (!noInit) {
createFunction.body.body.push(dummyThisArr[3]);
}
return false;
}
var resultCode = recast.print(ast, {
quote: 'single',
//reuseWhitespace: false,
tabWidth: 4
}).code;
if (oldStructure) {
resultCode = resultCode.replace(/\n\ {4}/g, '\n');
resultCode = resultCode.replace(/\n\],\s+function/, '\n], function');
resultCode = resultCode.replace(/define\(\s+/, 'define(');
resultCode = resultCode.replace(/\n\}\n\)/, '\n})');
}
var code = resultCode;
code = code.split('jsx/CallBacks').join('jsx/Callbacks');
code = code.split('jsx.CallBacks').join('jsx.Callbacks');
var ast = recast.parse(code);
var firstFunction = ast.program.body[0];
if (firstFunction && firstFunction.expression) {
firstFunction = firstFunction.expression;
} else {
return;
}
if (!(firstFunction.callee &&
firstFunction.callee.name === 'define' &&
firstFunction.arguments !== undefined)) {
return;
}
var defineArguments = firstFunction.arguments;
// Отрезаем имена в именных модулях
defineArguments.forEach(function(argument, index) {
if (argument.type === 'Literal') {
defineArguments.splice(index, 1);
}
});
if (defineArguments[0].type === 'FunctionExpression') {
defineArguments[1] = defineArguments[0];
defineArguments[0] = b.arrayExpression([]);
}
var dependencies = defineArguments[0].elements;
var defineCallback = defineArguments[1];
var defineCallbackArguments = defineCallback.params;
var bodyCode = recast.print(defineCallback.body).code;
var argsArray = defineCallbackArguments.map(function(param) {
return param.name;
});
var depsArray = [];
var currentLegacyVariable = {};
dependencies.forEach(function(dependency, index) {
var value = dependency.value;
depsArray.push(value);
currentLegacyVariable[value] = argsArray[index];
});
// Убираем дубликаты в зависимостях
dependencies.forEach(function(dependency, index) {
var value = dependency.value;
if (depsArray.indexOf(value) !== index) {
dependencies.splice(index, 1);
depsArray.splice(index, 1);
if (argsArray[index]) {
argsArray.splice(index, 1);
defineCallbackArguments.splice(index, 1);
}
}
});
// Ищем пропущенные или лишние зависимости
depModules.forEach(function(depModule) {
var legacyVar = depModuleLegacyVariables[depModule];
var currentVar = currentLegacyVariable[depModule];
var hasVarInCode =
(currentVar && (bodyCode.match(searchVarExp(currentVar)) !== null)) ||
(legacyVar && (bodyCode.match(searchVarExp(legacyVar)) !== null));
var theSameModule = filepath.indexOf(depModule) >= 0;
var hasDep = depsArray.indexOf(depModule) !== -1;
var depIndex = depsArray.indexOf(depModule);
// Добавляем пропущенную зависимость
if (hasVarInCode && !hasDep && !theSameModule) {
depsArray.push(depModule);
dependencies.push(b.literal(depModule));
console.log('ADDED DEP ' + depModule + ':' + filepath);
}
// Убираем лишнюю зависимость
if (!hasVarInCode && hasDep) {
var indexToDelete = depsArray.indexOf(depModule);
depsArray.splice(indexToDelete, 1);
dependencies.splice(indexToDelete, 1);
if (depIndex >= 0 && argsArray[depIndex]) {
argsArray.splice(depIndex, 1);
defineCallbackArguments.splice(depIndex, 1);
}
console.log('REMOVED DEP ' + depModule + ':' + filepath);
}
});
// добавляем переменные
for (var i in depModules) {
var depModule = depModules[i];
var depIndex = depsArray.indexOf(depModule);
if (depIndex >= 0 && argsArray[depIndex] === undefined) {
defineCallbackArguments[depIndex] = b.identifier(depModuleVariables[depModule]);
console.log('ADDED VAR ' + depModuleVariables[depModule] + ':' + filepath);
}
}
// Сортируем зависимости
var order = ['require', 'Libraries', 'HHC', 'Utils', 'jsx'];
dependencies.forEach(function(dep, index) {
dep._oldIndex = index;
});
dependencies.sort(function(a, b) {
// Зависимости без переменной - всегда в конец
if (defineCallbackArguments[a._oldIndex] === undefined) {
return 2;
}
if (defineCallbackArguments[a._oldIndex] === undefined && defineCallbackArguments[b._oldIndex] === undefined) {
return 0;
}
if (defineCallbackArguments[b._oldIndex] === undefined) {
return -1;
}
var aSpace = a.value.split('/')[0];
var bSpace = b.value.split('/')[0];
return (order.indexOf(aSpace) === -1 ? order.length : order.indexOf(aSpace)) -
(order.indexOf(bSpace) === -1 ? order.length : order.indexOf(bSpace));
});
var newDefineCallbackArguments = [];
dependencies.forEach(function(dep, index) {
if (defineCallback.params[dep._oldIndex] !== undefined) {
newDefineCallbackArguments[index] = defineCallback.params[dep._oldIndex];
}
});
defineCallback.params = newDefineCallbackArguments;
defineCallbackArguments = defineCallback.params;
if (dependencies.length === 0) {
defineArguments.splice(0, 1);
}
// заменяем HH на window
recast.types.visit(ast, {
visitIdentifier: function(path) {
var node = path.node;
if (node.name === 'HH') {
node.name = 'window';
}
this.traverse(path);
}
});
var preRenameCode = recast.print(ast, {
quote: 'single',
reuseWhitespace: false
}).code;
var resultCode = preRenameCode;
// переименовываем легаси переменные
depModules.forEach(function(depModule) {
var currentVar = currentLegacyVariable[depModule];
var legacyVar = depModuleLegacyVariables[depModule];
var hasVarInCode =
(currentVar && (resultCode.match(searchVarExp(currentVar)) !== null)) ||
(legacyVar && (resultCode.match(searchVarExp(legacyVar)) !== null));
if (hasVarInCode) {
if (currentVar) {
resultCode = resultCode.replace(new RegExp('([^\\w^\\d])' + escape(currentVar) + '(?![\\d\\w])', 'g'), '$1' + depModuleVariables[depModule]);
}
if (legacyVar) {
resultCode = resultCode.replace(new RegExp('([^\\w^\\d])' + escape(legacyVar) + '(?![\\d\\w])', 'g'), '$1' + depModuleVariables[depModule]);
}
if (preRenameCode !== resultCode) {
console.log('REMOVED OLD VARS:' + currentVar + ' ' + legacyVar + ' ' + filepath);
}
}
});
resultCode = resultCode.split('Libraries/').join('');
// Переименовываем jsx -> Utils
Object.keys(depRenameList).forEach(function(dep) {
resultCode = resultCode.split(dep).join(depRenameList[dep]);
});
// форматируем завимости
if (!resultCode.match(/define\(\[\n/)) {
var toReplace = resultCode.match(/define\(\[[^\]]+/);
if (toReplace) {
var newString = toReplace[0].split(', ').join(',').replace(/('[\w/]+')/g, "\n $1") + '\n';
resultCode = resultCode.split(toReplace[0]).join(newString);
}
}
fs.writeFile(filepath, resultCode);
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment