Skip to content

Commit

Permalink
Use recast for parsing and formatting (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
ganemone authored Nov 15, 2018
1 parent c41be8e commit c306a97
Show file tree
Hide file tree
Showing 21 changed files with 254 additions and 98 deletions.
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
extends: ['eslint-config-fusion'],
env: {
'node': true
}
};
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ WORKDIR /dubstep-core
COPY . .

RUN yarn
RUN yarn lint
RUN yarn test
9 changes: 2 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,15 +301,10 @@ visitJsImport(
```js
import {generateJs} from '@dubstep/core';

generateJs = (path: NodePath, options: GenerateJsObject) => string;
type GenerateJsOptions = ?{
formatter: ?('babel' | 'prettier'),
formatterOptions: ?Object,
};
generateJs = (path: NodePath) => string;
```
Converts a Program `NodePath` into a Javascript code string. The default formatter is `prettier`. The `formatterOptions` object should be [prettier options map](https://prettier.io/docs/en/options.html) or a [babel generator options map](https://babeljs.io/docs/en/next/babel-generator.html#options) depending on which `formatter` is specified.
Converts a Program `NodePath` into a Javascript code string.
A `NodePath` can be obtained from `withJsFile`, `withJsFiles` or `parseJs`.
##### insertJsAfter
Expand Down
17 changes: 12 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dev": "npm-watch | grep --invert-match nodemon",
"build": "babel src -d dist -q",
"test": "jest",
"lint": "eslint src/",
"cover": "jest --coverage",
"flow": "flow check || true",
"prepare": "babel src -d dist -s"
Expand All @@ -39,21 +40,18 @@
}
},
"dependencies": {
"@babel/generator": "^7.1.5",
"@babel/parser": "^7.1.5",
"@babel/template": "^7.1.2",
"@babel/traverse": "^7.1.5",
"@babel/types": "^7.1.5",
"eslint": "^5.9.0",
"eslint-config-prettier": "^3.1.0",
"eslint-plugin-prettier": "^3.0.0",
"execa": "^1.0.0",
"flow-coverage-report": "^0.6.0",
"fs-extra": "^7.0.1",
"globby": "^8.0.1",
"ini": "^1.3.5",
"isomorphic-git": "^0.39.3",
"prettier": "^1.15.1"
"prettier": "^1.15.1",
"recast": "^0.16.0"
},
"devDependencies": {
"@babel/cli": "7.1.5",
Expand All @@ -65,6 +63,15 @@
"babel-eslint": "10.0.1",
"babel-jest": "23.6.0",
"babel-preset-flow": "6.23.0",
"eslint": "^5.9.0",
"eslint-config-fusion": "^4.0.0",
"eslint-config-prettier": "^3.1.0",
"eslint-plugin-cup": "^2.0.0",
"eslint-plugin-flowtype": "^3.2.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jest": "^22.0.0",
"eslint-plugin-prettier": "^3.0.0",
"eslint-plugin-react": "^7.11.1",
"flow-bin": "0.86.0",
"jest": "23.6.0",
"markdown-it": "^8.4.2",
Expand Down
1 change: 1 addition & 0 deletions src/core/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ THE SOFTWARE.
import * as API from './index.js';
import markdown from 'markdown-it';
import fs from 'fs';

const {Stepper, step, StepperError} = API;

test('test', () => {
Expand Down
22 changes: 14 additions & 8 deletions src/utils/ensure-js-imports.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,51 +30,57 @@ test('ensureJsImports', () => {
const path = parseJs('');
const vars = ensureJsImports(path, `import foo, {bar} from 'bar';`);
const code = generateJs(path);
expect(code.trim()).toEqual(`import foo, { bar } from 'bar';`);
expect(code.trim()).toMatchInlineSnapshot(`"import foo, {bar} from 'bar';"`);
expect(vars).toEqual([{default: 'foo', bar: 'bar'}]);
});

test('ensureJsImports after', () => {
const path = parseJs(`import 'x';`);
const vars = ensureJsImports(path, `import foo, {bar} from 'bar';`);
const code = generateJs(path);
expect(code.trim()).toEqual(`import 'x';\nimport foo, { bar } from 'bar';`);
expect(code.trim()).toMatchInlineSnapshot(
`"import 'x';import foo, {bar} from 'bar';"`
);
expect(vars).toEqual([{default: 'foo', bar: 'bar'}]);
});

test('ensureJsImports before', () => {
const path = parseJs(`const a = 1;`);
const vars = ensureJsImports(path, `import foo, {bar} from 'bar';`);
const code = generateJs(path);
expect(code.trim()).toEqual(`import foo, { bar } from 'bar';\nconst a = 1;`);
expect(code.trim()).toMatchInlineSnapshot(
`"import foo, {bar} from 'bar';const a = 1;"`
);
expect(vars).toEqual([{default: 'foo', bar: 'bar'}]);
});

test('merge', () => {
const path = parseJs(`import {x} from 'foo'`);
const vars = ensureJsImports(path, `import foo, {bar} from 'foo';`);
const code = generateJs(path);
expect(code.trim()).toEqual(`import foo, { x, bar } from 'foo';`);
expect(code.trim()).toMatchInlineSnapshot(
`"import foo, { x, bar } from 'foo';"`
);
expect(vars).toEqual([{default: 'foo', x: 'x', bar: 'bar'}]);
});

test('retain default', () => {
const path = parseJs(`import foo from 'foo'`);
const vars = ensureJsImports(path, `import wildcard from 'foo';`);
const code = generateJs(path);
expect(code.trim()).toEqual(`import foo from 'foo';`);
expect(code.trim()).toMatchInlineSnapshot(`"import foo from 'foo'"`);
expect(vars).toEqual([{default: 'foo'}]);
});

test('multiple', () => {
const path = parseJs(`import foo from 'foo';import bar from 'bar'`);
const vars = ensureJsImports(
path,
`import wildcard from 'foo';import another, {x} from 'bar';`,
`import wildcard from 'foo';import another, {x} from 'bar';`
);
const code = generateJs(path);
expect(code.trim()).toEqual(
`import foo from 'foo';\nimport bar, { x } from 'bar';`,
expect(code.trim()).toMatchInlineSnapshot(
`"import foo from 'foo';import bar, { x } from 'bar';"`
);
expect(vars).toEqual([{default: 'foo'}, {default: 'bar', x: 'x'}]);
});
28 changes: 3 additions & 25 deletions src/utils/generate-js.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,8 @@ THE SOFTWARE.
@flow
*/

import generate from '@babel/generator';
import prettier from 'prettier';
import {writeFile} from './write-file.js';
import recast from 'recast';

export type GenerateJsOptions = ?{
formatter: ?('babel' | 'prettier'),
formatterOptions: ?Object,
};

export const generateJs = (path: NodePath, options: GenerateJsOptions) => {
const formatter = options ? options.formatter : 'babel';
const formatterOptions = options ? options.formatterOptions : {};
switch (formatter) {
case 'babel':
const generated = generate(path.parent, formatterOptions);
return generated.code;
case 'prettier':
default:
// placeholder is needed to prevent prettier from short-circuiting before the parser runs
return prettier.format('__placeholder__', {
parser() {
return path.node;
},
...formatterOptions,
});
}
export const generateJs = (path: NodePath) => {
return recast.print(path.parent).code;
};
2 changes: 1 addition & 1 deletion src/utils/insert-js-after.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const insertJsAfter = (
path: NodePath,
target: string,
code: string,
wildcards: Array<string> = [],
wildcards: Array<string> = []
) => {
return replaceJs(path, target, `${target}\n${code}`, wildcards);
};
2 changes: 1 addition & 1 deletion src/utils/insert-js-before.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const insertJsBefore = (
path: NodePath,
target: string,
code: string,
wildcards: Array<string> = [],
wildcards: Array<string> = []
) => {
return replaceJs(path, target, `${code}\n${target}`, wildcards);
};
61 changes: 33 additions & 28 deletions src/utils/parse-js.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ THE SOFTWARE.
import traverse from '@babel/traverse';
import NodePath from '@babel/traverse/lib/path';
import {parse} from '@babel/parser';
import {readFile} from './read-file.js';
import generate from '@babel/generator';
import recast from 'recast';

export type ParserOptions = ?{mode: ?('typescript' | 'flow')};

Expand All @@ -36,32 +35,38 @@ export const parseJs = (code: string, options: ParserOptions) => {
? ['typescript']
: ['flow', 'flowComments'];

const ast = parse(code, {
sourceType: 'unambiguous',
plugins: [
...typeSystem,
'jsx',
'doExpressions',
'objectRestSpread',
['decorators', {decoratorsBeforeExport: false}],
'classProperties',
'classPrivateProperties',
'classPrivateMethods',
'exportDefaultFrom',
'exportNamespaceFrom',
'asyncGenerators',
'functionBind',
'functionSent',
'dynamicImport',
'numericSeparator',
'optionalChaining',
'importMeta',
'bigInt',
'optionalCatchBinding',
'throwExpressions',
['pipelineOperator', {proposal: 'minimal'}],
'nullishCoalescingOperator',
],
const ast = recast.parse(code, {
parser: {
parse(source) {
return parse(source, {
sourceType: 'unambiguous',
plugins: [
...typeSystem,
'jsx',
'doExpressions',
'objectRestSpread',
['decorators', {decoratorsBeforeExport: false}],
'classProperties',
'classPrivateProperties',
'classPrivateMethods',
'exportDefaultFrom',
'exportNamespaceFrom',
'asyncGenerators',
'functionBind',
'functionSent',
'dynamicImport',
'numericSeparator',
'optionalChaining',
'importMeta',
'bigInt',
'optionalCatchBinding',
'throwExpressions',
['pipelineOperator', {proposal: 'minimal'}],
'nullishCoalescingOperator',
],
});
},
},
});

// ensure `path` has correct type to keep flow.js happy
Expand Down
1 change: 0 additions & 1 deletion src/utils/read-file.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ THE SOFTWARE.
@flow
*/

import util from 'util';
import fse from 'fs-extra';
import {writeFile} from './write-file.js';
import {readFile} from './read-file.js';
Expand Down
4 changes: 2 additions & 2 deletions src/utils/remove-js-imports.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ test('removeJsImports dependent statements', () => {
c.toString();
function x(a) {
a();
}`,
}`
);
removeJsImports(path, `import a, {b} from 'c';`);
expect(generateJs(path).trim()).toEqual('function x(a) {\n a();\n}');
Expand All @@ -75,7 +75,7 @@ test('removeJsImports namespace dependent statements', () => {
foo(a.test());
function x(a) {
a();
}`,
}`
);
removeJsImports(path, `import * as a from 'c';`);
expect(generateJs(path).trim()).toEqual('function x(a) {\n a();\n}');
Expand Down
2 changes: 1 addition & 1 deletion src/utils/replace-js.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const replaceJs = (
path: NodePath,
source: string,
target: string,
wildcards: Array<string> = [],
wildcards: Array<string> = []
): boolean => {
const sourcePath = parseJs(source);
const sourceNode = sourcePath.node.body[0];
Expand Down
6 changes: 3 additions & 3 deletions src/utils/visit-js-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@ import {
export const visitJsImport = (
path: NodePath,
source: string,
handler: (path: NodePath, refPaths: Array<NodePath>) => Boolean | void,
handler: (path: NodePath, refPaths: Array<NodePath>) => Boolean | void
) => {
const sourcePath = parseJs(source);
const sourceNode = sourcePath.node.body[0];
if (sourceNode.type !== 'ImportDeclaration') {
throw new Error(
`Expected source with type ImportDeclaration. Received: ${
sourceNode.type
}`,
}`
);
}
const specifiers = sourceNode.specifiers;
if (specifiers.length !== 1) {
throw new Error(
`Expected exactly one import specifier. Received: ${specifiers.length}`,
`Expected exactly one import specifier. Received: ${specifiers.length}`
);
}
const sourceSpecifier = specifiers[0];
Expand Down
10 changes: 5 additions & 5 deletions src/utils/visit-js-import.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ test('visitJsImport named', () => {
expect(refPaths).toHaveLength(2);
});
visitJsImport(path, `import {a} from 'c'`, handler);
expect(handler).toBeCalledTimes(1);
expect(handler).toHaveBeenCalledTimes(1);
});

test('visitJsImport no references', () => {
Expand All @@ -49,7 +49,7 @@ test('visitJsImport no references', () => {
expect(refPaths).toHaveLength(0);
});
visitJsImport(path, `import {a} from 'c'`, handler);
expect(handler).toBeCalledTimes(1);
expect(handler).toHaveBeenCalledTimes(1);
});

test('visitJsImport no binding', () => {
Expand All @@ -60,7 +60,7 @@ test('visitJsImport no binding', () => {
expect(refPaths).toHaveLength(0);
});
visitJsImport(path, `import {a} from 'c'`, handler);
expect(handler).toBeCalledTimes(0);
expect(handler).toHaveBeenCalledTimes(0);
});

test('visitJsImport namespace', () => {
Expand All @@ -77,7 +77,7 @@ test('visitJsImport namespace', () => {
expect(refPaths).toHaveLength(2);
});
visitJsImport(path, `import * as b from 'c'`, handler);
expect(handler).toBeCalledTimes(1);
expect(handler).toHaveBeenCalledTimes(1);
});

test('visitJsImport default', () => {
Expand All @@ -94,7 +94,7 @@ test('visitJsImport default', () => {
expect(refPaths).toHaveLength(2);
});
visitJsImport(path, `import b from 'c'`, handler);
expect(handler).toBeCalledTimes(1);
expect(handler).toHaveBeenCalledTimes(1);
});

test('visitJsImport validation', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/with-ignore-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {readFile} from '../utils/read-file.js';
import {writeFile} from '../utils/write-file.js';

export type IgnoreFileMutation = (
data: Array<string>,
data: Array<string>
) => Promise<?Array<string>>;

export const withIgnoreFile = async (file: string, fn: IgnoreFileMutation) => {
Expand Down
Loading

0 comments on commit c306a97

Please sign in to comment.