Skip to content

Instantly share code, notes, and snippets.

@selvagsz
Last active September 15, 2020 09:57
Show Gist options
  • Save selvagsz/9ffb7e3ab19b0dccce0d3bd1fd7436a5 to your computer and use it in GitHub Desktop.
Save selvagsz/9ffb7e3ab19b0dccce0d3bd1fd7436a5 to your computer and use it in GitHub Desktop.
CodeMods that I'd used

CodeMods

List of jscodeshift transformers that I cooked to address some chore tasks

/*
Changes
import TableRowComponent from 'tradegecko/components/table-row';
import TableRowComponent from 'tradegecko/components/table-row';
export default TableRowComponent.extend({});
to
import TableRowComponent from 'tradegecko/components/table-row';
import TableRowComponent from 'tradegecko/components/table-row';
export default TableRowComponent.extend({});
*/
export default function transformer(file, api) {
const j = api.jscodeshift;
return j(file.source)
.find(j.ImportDeclaration)
.at(-1)
.forEach((path) => {
path.insertAfter('\n');
})
.toSource()
.replace(/\n\s*\n\s*\n/, '\n\n');
}
/*
Adds the `module` & `test` qunit imports
import { module, test } from 'ember-qunit';
||
\/
import { module, test } from 'qunit';
import { module, test, moduleForHelper } from 'ember-qunit';
||
\/
import { module, test } from 'qunit';
import { moduleForHelper } from 'ember-qunit';
*/
const getQunitImportDeclaration = j =>
j.importDeclaration(
[j.importSpecifier(j.identifier('module')), j.importSpecifier(j.identifier('test'))],
j.literal('qunit')
);
export default function transformer(file, api) {
const j = api.jscodeshift;
let qunitImports = j(file.source)
.find(j.ImportDeclaration)
.filter(path => {
return path.value.source.value === 'qunit';
});
if (qunitImports.length) {
return;
}
let emberQunitImports = j(file.source)
.find(j.ImportDeclaration)
.filter(path => {
return path.value.source.value === 'ember-qunit';
});
if (emberQunitImports.length) {
return emberQunitImports
.forEach(path => {
let otherImportSpecifiers = path.value.specifiers.filter(specifier => {
return !['module', 'test'].includes(specifier.imported.name);
});
if (otherImportSpecifiers.length) {
j(path).replaceWith(j.importDeclaration(otherImportSpecifiers, j.literal('ember-qunit')));
path.parentPath.value.unshift(getQunitImportDeclaration(j));
} else {
j(path).replaceWith(getQunitImportDeclaration(j));
}
})
.toSource();
}
return j(file.source)
.find(j.Program)
.forEach(path => {
path.value.body.unshift(getQunitImportDeclaration(j));
})
.toSource();
}
/*
Adds `sinon` import if being using in the file
*/
const getSinonImportDeclaration = j =>
j.importDeclaration([j.importDefaultSpecifier(j.identifier('sinon'))], j.literal('sinon'));
export default function transformer(file, api) {
const j = api.jscodeshift;
let sinonUsages = j(file.source).find(j.Identifier, { name: 'sinon' }).length;
if (sinonUsages) {
return j(file.source)
.find(j.Program)
.forEach(path => {
path.value.body.unshift(getSinonImportDeclaration(j));
})
.toSource();
}
}
// Super custom codemod that I never care to do any clean ups. But gets the job done
const computedExts = [
'add',
'append',
'boundEqual',
'convert',
'defaultTo',
'divide',
'divideRounded',
'findBy',
'groupBy',
'humanize',
'includes',
'isEvery',
'isNone',
'isThis',
'lookup',
'multiply',
'multiplyRounded',
'nearestOfType',
'parseNumber',
'prefix',
'rejectBy',
'subtract',
'suffix',
'sumBy',
'uniqSumBy',
'getBy',
'slice',
];
const EMBER_COMPUTED_IMPORT_PATH = '@ember/object/computed';
const COMPUTED_EXT_IMPORT_PATH = 'tradegecko/utils/computed-ext';
const findDefaultImportDeclarations = function(collection, j, importPath) {
return collection.find(j.ImportDeclaration).filter((path) => {
return path.value.source.value === importPath;
});
};
const contructDefaultImportDeclaration = (j, identifier, path) =>
j.importDeclaration([j.importDefaultSpecifier(j.identifier(identifier))], j.literal(path));
const addImportStmts = function(j, computedNamedImports) {
const extSpecifiers = [];
const defaultSpecifiers = [];
computedNamedImports.forEach((cp) => {
const isExtCP = computedExts.includes(cp);
let importPath = isExtCP ? COMPUTED_EXT_IMPORT_PATH : EMBER_COMPUTED_IMPORT_PATH;
const computedExtNode = findDefaultImportDeclarations(this, j, importPath);
if (computedExtNode.size()) {
let computedExtNodePath = computedExtNode.paths()[0];
if (
!j(computedExtNodePath.value.specifiers)
.find(j.Identifier, { name: cp })
.size()
) {
computedExtNodePath.value.specifiers.push(j.importSpecifier(j.identifier(cp)));
}
} else {
isExtCP ? extSpecifiers.push(cp) : defaultSpecifiers.push(cp);
}
});
if (extSpecifiers.length) {
this.find(j.Program).forEach((nodePath) => {
nodePath.value.body.splice(
this.find(j.ImportDeclaration).size(),
0,
contructDefaultImportDeclaration(j, `{ ${extSpecifiers.join(', ')} }`, COMPUTED_EXT_IMPORT_PATH)
);
});
}
if (defaultSpecifiers.length) {
this.find(j.Program).forEach((nodePath) => {
nodePath.value.body.splice(
this.find(j.ImportDeclaration).size(),
0,
contructDefaultImportDeclaration(j, `{ ${defaultSpecifiers.join(', ')} }`, EMBER_COMPUTED_IMPORT_PATH)
);
});
}
return this;
};
module.exports = function transformer(file, api) {
const computedNamedImports = [];
const j = api.jscodeshift;
const ast = j(file.source);
j.registerMethods({
addImportStmts: addImportStmts.bind(ast, j, computedNamedImports),
});
const computedVariableDeclarations = (path) => path.value.declarations[0].init && path.value.declarations[0].init.name === 'computed';
const getNamedComputeds = (path) =>
j(path.value.declarations[0])
.find(j.Property)
.forEach((path) => {
computedNamedImports.push(path.value.value.name);
});
ast.find(j.MemberExpression, { object: { name: 'computed' } }).forEach((path) => {
let computedFnName = path.value.property.name;
computedNamedImports.push(computedFnName);
path.replace(j.identifier(computedFnName));
});
return ast
.find(j.VariableDeclaration)
.filter(computedVariableDeclarations)
.forEach(getNamedComputeds)
.remove()
.addImportStmts()
.toSource({ quote: 'single' })
.replace(/\n\nimport/, '\nimport')
}
// Remove "decaffeinate" comments
export default function transformer(file, api) {
const j = api.jscodeshift;
return j(file.source)
.find(j.Comment)
.forEach(path => {
if (path.value.value.includes('decaffeinate suggestions')) {
j(path).replaceWith('');
}
})
.toSource();
}
export default function transformer(file, api) {
const j = api.jscodeshift;
return j(file.source)
.find(j.CallExpression)
.filter(path => path.value.callee.name === 'test')
.find(j.BlockStatement)
.forEach(path => {
let returnStatement = path.value.body.pop()
path.value.body.push(j.expressionStatement(
returnStatement.argument
));
})
.toSource();
}
/*
Adds `assert` params to the `test` callbacks & replaces global asserts
test('Foo Bar Baz', function() {
ok(someBlah, 'blha blah')
notOk(someBlah, 'blha blah')
equal(someBlah, 'blah', 'blha blah')
notEqual(someBlah, 'blah', 'blha blah')
});
||
\/
test('Foo Bar Baz', function(assert) {
assert.ok(someBlah, 'blha blah')
assert.notOk(someBlah, 'blha blah')
assert.equal(someBlah, 'blah', 'blha blah')
assert.equal(someBlah, 'blah', 'blha blah')
});
*/
const QUNIT_ASSERTIONS = ['equal', 'ok', 'deepEqual', 'notOk', 'notEqual'];
export default function transformer(file, api) {
const j = api.jscodeshift;
return j(file.source)
.find(j.CallExpression, { callee: { name: 'test' } })
.find(j.FunctionExpression)
.filter(path => {
return path.parentPath.parentPath.value.callee.name === 'test';
})
.forEach(path => {
path.value.params = ['assert'];
})
.find(j.BlockStatement)
.find(j.CallExpression, { callee: { type: 'Identifier' } })
.find(j.Identifier)
.forEach(path => {
if (QUNIT_ASSERTIONS.includes(path.value.name)) {
path.value.name = 'assert.' + path.value.name.replace('assert.', '');
}
})
.toSource();
}
// Removes `'use strict'`
export default function transformer(file, api) {
const j = api.jscodeshift;
return j(file.source)
.find(j.ExpressionStatement)
.filter(path => path.value.expression.value === 'use strict')
.forEach(path => {
j(path).replaceWith('');
})
.toSource();
}
/*
Migrates qunit's setup/teardown hooks to beforeEach/afterEach
module('foo', {
setup() {
},
teardown() {
}
})
||
\/
module('foo', {
beforeEach() {
},
afterEach() {
}
})
*/
const QUNIT_HOOKS = ['setup', 'teardown'];
export default function transformer(file, api) {
const j = api.jscodeshift;
return j(file.source)
.find(j.ObjectExpression)
.find(j.Property, { key: j.Identifier })
.filter(path => QUNIT_HOOKS.indexOf(path.value.key.name) !== -1)
.forEach(path => {
let newHookName = path.value.key.name === 'setup' ? 'beforeEach' : 'afterEach';
path.value.key.name = newHookName;
})
.toSource();
}
// uses ember-template-recast
// steps to run
// npm i -g ember-template-recast
// ember-template-recast path/to/templates/directory -t transform.js
module.exports = function({ source, path }, { parse, visit }) {
const ast = parse(source);
return visit(ast, env => {
let b = env.syntax.builders;
const ctaBtnTypeHashPair = (pair) => pair.key === "btnType" && pair.value.value === "cta";
const isAppearanceHashPair = (pair) => pair.key === "appearance";
return {
MustacheStatement(a) {
const syntax = env.syntax;
if (a.path.original === "tg-button") {
let ctaBtnType = a.hash.pairs.find(ctaBtnTypeHashPair);
if (!!ctaBtnType) {
let appearanceHashPair = a.hash.pairs.find(isAppearanceHashPair);
if (appearanceHashPair) {
let appearanceValue = appearanceHashPair.value.value;
// if secondary, remove the `appearance`
if (appearanceValue === "secondary") {
a.hash.pairs = a.hash.pairs.filter((pair) => pair !== appearanceHashPair);
}
} else {
// add `primary` appearance
a.hash.pairs.unshift(
syntax.builders.pair("appearance", syntax.builders.literal("StringLiteral", "primary"))
);
}
// remove `btnType="cta"`
a.hash.pairs = a.hash.pairs.filter((pair) => pair !== ctaBtnType);
}
}
return a;
}
};
});
};
/*
Replaces global ENV usage with module
export default Component.extend({
init() {
this._super(...arguments)
if (Ember.ENV.testing) {}
}
})
becomes
||
\/
import ENV from 'env';
export default Component.extend({
init() {
this._super(...arguments)
if (ENV.testing) {}
}
})
*/
const isEmberENV = (p) => p.value.name === 'Ember' && p.parent.value.property.name === 'ENV';
const findDefaultImportDeclarations = function(j, importPath) {
return this.find(j.ImportDeclaration).filter((path) => {
return path.value.source.value === importPath;
});
};
const findIdentifiers = function(j, name) {
return this.find(j.Identifier, { name });
};
const contructDefaultImportDeclaration = ({ j, identifier, path }) =>
j.importDeclaration([j.importDefaultSpecifier(j.identifier(identifier))], j.literal(path));
const addDefaultImport = function(j, identifier, path) {
return this.find(j.Program).forEach((nodePath) => {
nodePath.value.body.unshift(contructDefaultImportDeclaration({ j, identifier, path }));
});
};
export default function transformer(file, api) {
const j = api.jscodeshift;
let ast = j(file.source);
j.registerMethods({
findIdentifiers: findIdentifiers.bind(ast, j),
addDefaultImport: addDefaultImport.bind(ast, j),
findDefaultImportDeclarations: findDefaultImportDeclarations.bind(ast, j),
});
const emberEnvIdentifers = ast.findIdentifiers('Ember').filter(isEmberENV);
if (emberEnvIdentifers.size() > 0 && !ast.findDefaultImportDeclarations('env').size()) {
ast.addDefaultImport('ENV', 'env');
}
return emberEnvIdentifers
.forEach((path) => {
j(path.parent).replaceWith(j.identifier('ENV'));
})
.toSource({ quote: 'single' });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment