Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile Plugins written in Typescript #57

Closed
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
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,17 @@ $ codemod --find-babel-config --plugin ./my-plugin.js src/
This requires that all babel plugins and presets be installed locally and are listed in your `.babelrc` file. `babel-codemod` uses `babel-register` under the hood too accomplish this and all `.babelrc` [lookup rules apply](https://babeljs.io/docs/usage/babelrc/#lookup-behavior).

### Transpiling using TypeScript
`babel-codemod` supports plugins written in TypeScript.

There is currently an [open issue](https://github.com/square/babel-codemod/issues/51) for supporting plugins written in typescript. In the interim, you can take the same approach using `--require` along with `ts-node/register`.
Unlike babel transpilation described above, this feature is off by default and **requires that TypeScript be installed locally**. This feature uses `ts-node/register` under the hood so [all rules](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) regarding existence and placement of `tsconfig.json` apply.

To opt-in to TypeScript transpilation of your plugins, simply pass the `--transpile-ts-plugins` option.

For example:

```sh
# Run a local plugin written with TypeScript.
$ codemod --require ts-node/register --plugin ./my-plugin.ts src/
$ codemod --transpile-ts-plugins --plugin ./my-plugin.ts src/
```

## Contributing
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
"recast": "^0.13.0",
"resolve": "^1.5.0",
"tmp": "^0.0.33",
"whatwg-url": "^6.4.0"
"whatwg-url": "^6.4.0",
"ts-node": "^4.1.0"
},
"engines": {
"node": ">=6.0.0"
Expand Down
38 changes: 33 additions & 5 deletions src/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,26 @@ export default class Options {
readonly requires: Array<string>,
readonly transpilePlugins: boolean,
readonly findBabelConfig: boolean,
readonly transpilePluginsWithTypescript: boolean,
readonly ignore: PathPredicate,
readonly stdio: boolean,
readonly help: boolean,
readonly dry: boolean
) {}
) {
const defaultFileSystemExtensions = new Set(['.js']);
if (transpilePluginsWithTypescript) {
{
defaultFileSystemExtensions.add('.ts');
}
}

private pluginLoader = new PluginLoader([
new FileSystemResolver(),
new PackageResolver()
]);
this.pluginLoader = new PluginLoader([
new FileSystemResolver(defaultFileSystemExtensions),
new PackageResolver()
]);
}

private pluginLoader: PluginLoader;

private remotePluginLoader = new PluginLoader([
new AstExplorerResolver(),
Expand Down Expand Up @@ -109,6 +119,18 @@ export default class Options {
}
}

loadTypescriptTranspile() {
try {
require.resolve('typescript');
} catch (e) {
throw new Error(
'Typescript is not installed locally. You must installed Typescript locally in order to transpile plugins written in Typescript'
);
}

require('ts-node').register();
}

async getPlugin(name: string): Promise<Plugin | null> {
for (let plugin of await this.getPlugins()) {
if (plugin.declaredName === name || plugin.inferredName === name) {
Expand Down Expand Up @@ -164,6 +186,7 @@ export default class Options {
let requires: Array<string> = [];
let findBabelConfig = false;
let transpilePlugins = true;
let transpilePluginsWithTypescript = false;
let stdio = false;
let help = false;
let dry = false;
Expand Down Expand Up @@ -222,6 +245,10 @@ export default class Options {
findBabelConfig = arg === '--find-babel-config';
break;

case '--transpile-ts-plugins':
transpilePluginsWithTypescript = true;
break;

case '--extensions':
i++;
extensions = new Set(
Expand Down Expand Up @@ -267,6 +294,7 @@ export default class Options {
requires,
transpilePlugins,
findBabelConfig,
transpilePluginsWithTypescript,
ignore,
stdio,
help,
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ OPTIONS
--[no-]transpile-plugins Transpile plugins to enable future syntax (default: on).
--[no-]find-babel-config Run plugins through babel plugins/presets specified in local
.babelrc file instead of babel-preset-env (default: off).
--transpile-ts-plugins Transpile plugins to enable typescript syntax. Requires typescript to be installed local to project. (default: off).
-s, --stdio Read source from stdin and print to stdout.
-h, --help Show this help message.
-d, --dry Run plugins without modifying files on disk.
Expand Down Expand Up @@ -79,8 +80,14 @@ export default async function run(
return 0;
}

if(options.transpilePluginsWithTypescript){
options.loadTypescriptTranspile();
}


options.loadBabelTranspile();


options.loadRequires();

let plugins = await options.getBabelPlugins();
Expand Down
83 changes: 83 additions & 0 deletions test/cli/CLITest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,87 @@ describe('CLI', function() {
await server.stop();
}
});

it('can load plugins written in Typescript', async function() {
let afile = await createTemporaryFile('a-file.js', '3 + 4;');
let { status, stdout, stderr } = await runCodemodCLI([
afile,
'-p',
plugin('typescript/increment-typescript', '.ts'),
'--transpile-ts-plugins'
]);

deepEqual(
{ status, stdout, stderr },
{
status: 0,
stdout: `${afile}\n1 file(s), 1 modified, 0 errors\n`,
stderr: ''
}
);
strictEqual(await readFile(afile, 'utf8'), '4 + 5;');
});

it('can load plugins written in Typescript without ts extension', async function() {
let afile = await createTemporaryFile('a-file.js', '3 + 4;');
let { status, stdout, stderr } = await runCodemodCLI([
afile,
'-p',
plugin('typescript/increment-typescript', ''),
'--transpile-ts-plugins'
]);

deepEqual(
{ status, stdout, stderr },
{
status: 0,
stdout: `${afile}\n1 file(s), 1 modified, 0 errors\n`,
stderr: ''
}
);
strictEqual(await readFile(afile, 'utf8'), '4 + 5;');
});

it('can load plugins with multiple files written in Typescript', async function() {
let afile = await createTemporaryFile('a-file.js', '3 + 4;');
let { status, stdout, stderr } = await runCodemodCLI([
afile,
'-p',
plugin('typescript/increment-export-default-multiple/index', '.ts'),
'--transpile-ts-plugins'
]);

deepEqual(
{ status, stdout, stderr },
{
status: 0,
stdout: `${afile}\n1 file(s), 1 modified, 0 errors\n`,
stderr: ''
}
);
strictEqual(await readFile(afile, 'utf8'), '4 + 5;');
});

it('can load plugins with multiple files written in Typescript and Javascript', async function() {
let afile = await createTemporaryFile('a-file.js', '3 + 4;');
let { status, stdout, stderr } = await runCodemodCLI([
afile,
'-p',
plugin(
'typescript/increment-export-default-multiple/increment-export-index',
'.ts'
),
'--transpile-ts-plugins'
]);

deepEqual(
{ status, stdout, stderr },
{
status: 0,
stdout: `${afile}\n1 file(s), 1 modified, 0 errors\n`,
stderr: ''
}
);
strictEqual(await readFile(afile, 'utf8'), '4 + 5;');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function incrementValue(x) {
return x + 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function incrementValue(x: number): number {
return x + 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { incrementValue } from './increment-export-default-js';

/* tslint:disable */
export default function() {
return {
visitor: {
NumericLiteral(path) {
let value: number = path.node.value;
path.node.value = incrementValue(value);
}
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {incrementValue} from './increment-export-default';

/* tslint:disable */
export default function() {
return {
visitor: {
NumericLiteral(path) {
let value: number = path.node.value;
path.node.value = incrementValue(value);
}
}
};
}
15 changes: 15 additions & 0 deletions test/fixtures/plugin/typescript/increment-typescript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function incrementValue(x: number): number {
return x + 1;
}

/* tslint:disable */
export default function() {
return {
visitor: {
NumericLiteral(path) {
let value: number = path.node.value;
path.node.value = incrementValue(value);
}
}
};
}
4 changes: 2 additions & 2 deletions test/helpers/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { join } from 'path';

export default function plugin(name: string): string {
return join(__dirname, `../fixtures/plugin/${name}.js`);
export default function plugin(name: string, ext: string = '.js'): string {
return join(__dirname, `../fixtures/plugin/${name}${ext}`);
}
21 changes: 12 additions & 9 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": false,
"sourceMap": true,
"strictNullChecks": true,
"declaration": true
}
}
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": false,
"sourceMap": true,
"strictNullChecks": true,
"declaration": true
},
"exclude": [
"./test/fixtures/**/*.ts"
]
}
Loading