Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 106 additions & 8 deletions commands/local/generate/codemod.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ module.exports.command = 'codemod <codemod-name>';
module.exports.desc = 'Generate a new codemod file';

module.exports.builder = function builder(yargs) {
yargs.positional('codemod-name', {
describe: 'the name of the codemod to generate',
});
yargs
.positional('codemod-name', {
describe: 'the name of the codemod to generate',
})
.option('type', {
alias: 't',
describe: 'choose the transform type',
choices: ['js', 'hbs'],
default: 'js',
});
};

module.exports.handler = function handler(options) {
function jsHandler(options) {
const fs = require('fs-extra');
const { stripIndent } = require('common-tags');
const importCwd = require('import-cwd');
Expand Down Expand Up @@ -36,7 +43,9 @@ module.exports.handler = function handler(options) {
.join('');
})
.toSource();
}
};

module.exports.type = 'js';
`,
'utf8'
);
Expand All @@ -47,8 +56,7 @@ module.exports.handler = function handler(options) {

const { runTransformTest } = require('codemod-cli');

runTransformTest({
type: 'jscodeshift',
runTransformTest({
name: '${codemodName}',
});
`,
Expand Down Expand Up @@ -86,5 +94,95 @@ module.exports.handler = function handler(options) {
'utf8'
);

generateFixture({ codemodName, fixtureName: 'basic' });
generateFixture({ codemodName, fixtureName: 'basic', type: options.type });
}

function hbsHandler(options) {
const fs = require('fs-extra');
const { stripIndent } = require('common-tags');
const importCwd = require('import-cwd');
const generateFixture = require('./fixture').handler;

let { codemodName } = options;
let projectName = importCwd('./package.json').name;
let codemodDir = `${process.cwd()}/transforms/${codemodName}`;

fs.outputFileSync(
`${codemodDir}/index.js`,
stripIndent`
module.exports = function ({ source /*, path*/ }, { parse, visit }) {
const ast = parse(source);

return visit(ast, (env) => {
let { builders: b } = env.syntax;

return {
MustacheStatement() {
return b.mustache(b.path('wat-wat'));
},
};
});
};

module.exports.type = 'hbs';
`,
'utf8'
);
fs.outputFileSync(
`${codemodDir}/test.js`,
stripIndent`
'use strict';

const { runTransformTest } = require('codemod-cli');

runTransformTest({
name: '${codemodName}',
});
`,
'utf8'
);
fs.outputFileSync(
`${codemodDir}/README.md`,
stripIndent`
# ${codemodName}\n

## Usage

\`\`\`
npx ${projectName} ${codemodName} path/of/files/ or/some**/*glob.hbs

# or

yarn global add ${projectName}
${projectName} ${codemodName} path/of/files/ or/some**/*glob.hbs
\`\`\`

## Local Usage
\`\`\`
node ./bin/cli.js ${codemodName} path/of/files/ or/some**/*glob.hbs
\`\`\`

## Input / Output

<!--FIXTURES_TOC_START-->
<!--FIXTURES_TOC_END-->

<!--FIXTURES_CONTENT_START-->
<!--FIXTURES_CONTENT_END-->
`,
'utf8'
);

generateFixture({ codemodName, fixtureName: 'basic', type: options.type });
}

module.exports.handler = function handler(options) {
switch (options.type) {
case 'js':
return jsHandler(options);
case 'hbs':
return hbsHandler(options);
default:
throw new Error(`Unknown type: "${options.type}"`);
}
};
7 changes: 5 additions & 2 deletions commands/local/generate/fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ module.exports.builder = function builder(yargs) {

module.exports.handler = function handler(options) {
const fs = require('fs-extra');
const { getTransformType } = require('../../../src/transform-support');

let { codemodName, fixtureName } = options;
let codemodDir = `${process.cwd()}/transforms/${codemodName}`;
let fixturePath = `${codemodDir}/__testfixtures__/${fixtureName}`;

fs.outputFileSync(`${fixturePath}.input.js`, '');
fs.outputFileSync(`${fixturePath}.output.js`, '');
let transformType = getTransformType(codemodDir);

fs.outputFileSync(`${fixturePath}.input.${transformType}`, '');
fs.outputFileSync(`${fixturePath}.output.${transformType}`, '');
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@babel/parser": "^7.6.0",
"chalk": "^2.4.2",
"common-tags": "^1.8.0",
"ember-template-recast": "^4.1.4",
"execa": "^2.0.4",
"fs-extra": "^8.1.0",
"globby": "^10.0.1",
Expand Down
52 changes: 49 additions & 3 deletions src/bin-support.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
'use strict';

const DEFAULT_EXTENSIONS = 'js,ts';
const DEFAULT_JS_EXTENSIONS = 'js,ts';

async function runTransform(binRoot, transformName, args, extensions = DEFAULT_EXTENSIONS) {
async function runJsTransform(root, transformName, args, extensions = DEFAULT_JS_EXTENSIONS) {
const globby = require('globby');
const execa = require('execa');
const chalk = require('chalk');
const path = require('path');
const { parseTransformArgs } = require('./options-support');
const { getTransformPath } = require('./transform-support');

let { paths, options } = parseTransformArgs(args);

try {
let foundPaths = await globby(paths, {
expandDirectories: { extensions: extensions.split(',') },
});
let transformPath = path.join(binRoot, '..', 'transforms', transformName, 'index.js');
let transformPath = getTransformPath(root, transformName);

let jscodeshiftPkg = require('jscodeshift/package');
let jscodeshiftPath = path.dirname(require.resolve('jscodeshift/package'));
Expand All @@ -37,6 +38,51 @@ async function runTransform(binRoot, transformName, args, extensions = DEFAULT_E
}
}

async function runTemplateTransform(root, transformName, args) {
const execa = require('execa');
const chalk = require('chalk');
const { parseTransformArgs } = require('./options-support');
const { getTransformPath } = require('./transform-support');

let { paths, options } = parseTransformArgs(args);

try {
let transformPath = getTransformPath(root, transformName);
let binOptions = ['-t', transformPath, ...paths];

return execa('ember-template-recast', binOptions, {
stdio: 'inherit',
preferLocal: true,
env: {
CODEMOD_CLI_ARGS: JSON.stringify(options),
},
});
} catch (error) {
console.error(chalk.red(error.stack)); // eslint-disable-line no-console
process.exitCode = 1;

throw error;
}
}

async function runTransform(binRoot, transformName, args, extensions) {
const { getTransformType, getTransformPath } = require('./transform-support');
const path = require('path');

let root = path.join(binRoot, '..');
let transformPath = getTransformPath(root, transformName);
let type = getTransformType(transformPath);

switch (type) {
case 'js':
return runJsTransform(root, transformName, args, extensions);
case 'hbs':
return runTemplateTransform(root, transformName, args);
default:
throw new Error(`Unknown type passed to runTransform: "${type}"`);
}
}

module.exports = {
runTransform,
};
33 changes: 13 additions & 20 deletions src/test-support.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,12 @@

/* global it, describe, beforeEach, afterEach */

const { runInlineTest } = require('jscodeshift/dist/testUtils');
const fs = require('fs-extra');
const path = require('path');
const globby = require('globby');
const { transformDetails } = require('./test-support/utils');

function transformDetails(options) {
let root = process.cwd() + `/transforms/${options.name}/`;

return {
name: options.name,
root,
transformPath: root + 'index',
fixtureDir: root + '__testfixtures__/',
};
}

function jscodeshiftTest(options) {
function testRunner(options, runTest) {
let details = transformDetails(options);

let transform = require(details.transformPath);
Expand Down Expand Up @@ -49,18 +38,16 @@ function jscodeshiftTest(options) {
});

it('transforms correctly', function() {
runInlineTest(
runTest(
transform,
{},
{ path: testInputPath, source: fs.readFileSync(inputPath, 'utf8') },
fs.readFileSync(outputPath, 'utf8')
);
});

it('is idempotent', function() {
runInlineTest(
runTest(
transform,
{},
{ path: testInputPath, source: fs.readFileSync(outputPath, 'utf8') },
fs.readFileSync(outputPath, 'utf8')
);
Expand All @@ -71,9 +58,15 @@ function jscodeshiftTest(options) {
}

function runTransformTest(options) {
switch (options.type) {
case 'jscodeshift':
return jscodeshiftTest(options);
let details = transformDetails(options);

switch (details.transformType) {
case 'js':
return testRunner(options, require('./test-support/jscodeshift'));
case 'hbs':
return testRunner(options, require('./test-support/template'));
default:
throw new Error(`Unknown type of transform: "${details.transformType}"`);
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/test-support/jscodeshift.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

const { runInlineTest } = require('jscodeshift/dist/testUtils');

module.exports = function runJsTest(transform, input, expectedOutput) {
return runInlineTest(transform, {}, input, expectedOutput);
};
23 changes: 23 additions & 0 deletions src/test-support/template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that this is (or could be) basically the same as the jscodeshift one but we run a different function for actually testing (jscodeshift runs runInlineTest and this one runs runTemplateTest).

I would prefer to refactor these together to avoid the duplication that exists right now, and to also ensure that both support the same feature set. At the moment, I think this file is missing the ability to test passing different command line options (that the template transform can access with getOptions).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, done.


/* global expect */

const { transform, parse } = require('ember-template-recast');

module.exports = function runTemplateTest(plugin, { path: pluginPath, source }, expectedOutput) {
const code = plugin(
{
path: pluginPath,
source,
},
{
parse,
visit(ast, callback) {
const results = transform(ast, callback);
return results && results.code;
},
}
);

expect(code || '').toEqual(expectedOutput);
};
22 changes: 22 additions & 0 deletions src/test-support/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

function transformDetails(options) {
const { getTransformType, getTransformPath } = require('../transform-support');
const path = require('path');

let transformPath = getTransformPath(process.cwd(), options.name);
let root = path.dirname(transformPath);
let transformType = getTransformType(transformPath);

return {
name: options.name,
root,
transformPath,
transformType,
fixtureDir: path.join(root, '__testfixtures__/'),
};
}

module.exports = {
transformDetails,
};
12 changes: 12 additions & 0 deletions src/transform-support.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
'use strict';

function getTransformPath(root, transformName) {
const path = require('path');

return require.resolve(path.join(root, 'transforms', transformName));
}

function getTransformType(transformPath) {
return require(transformPath).type || 'js'; // fallback to 'js' if `type` export does not exist
}

function getJSCodeshiftParser(api) {
try {
let parser = require('recast/parsers/typescript');
Expand All @@ -19,4 +29,6 @@ module.exports = {
jscodeshift: {
getParser: getJSCodeshiftParser,
},
getTransformType,
getTransformPath,
};
Loading