Skip to content

Commit 95f308b

Browse files
authored
Pretty print using prettier (lyft#25)
* Pretty print using prettier * Remove yarn log * Use CLI input for prettier config * pass config in test
1 parent 1204fea commit 95f308b

File tree

49 files changed

+374
-248
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+374
-248
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
node_modules
22
dist
33
.DS_Store
4-
coverage/
4+
coverage/
5+
.log

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
test/
22
dist/
3+
!test/transformers.test.ts

.vscode/launch.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
"env": {
88
"NODE_ENV": "test"
99
},
10-
"externalConsole": false,
10+
"console": "internalConsole",
1111
"name": "Run Tests",
12-
"outDir": "${workspaceRoot}/dist",
13-
"preLaunchTask": "compile",
12+
"outFiles": ["${workspaceRoot}/dist"],
13+
"preLaunchTask": "tsc",
1414
"program": "${workspaceRoot}/node_modules/.bin/jest",
1515
"request": "launch",
1616
"runtimeArgs": [],

package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"transform": {
2323
".ts": "<rootDir>/node_modules/ts-jest/preprocessor.js"
2424
},
25-
"testRegex": "test/runner.ts",
25+
"testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
2626
"moduleFileExtensions": ["ts", "js"]
2727
},
2828
"lint-staged": {
@@ -34,22 +34,26 @@
3434
"dependencies": {
3535
"chalk": "^1.1.3",
3636
"commander": "^2.10.0",
37+
"detect-indent": "^5.0.0",
3738
"glob": "^7.1.2",
3839
"lodash": "^4.17.4",
40+
"prettier": "^1.10.2",
3941
"typescript": "^2.6.2"
4042
},
4143
"devDependencies": {
4244
"@types/chalk": "^0.4.31",
4345
"@types/commander": "^2.9.1",
46+
"@types/detect-indent": "^5.0.0",
4447
"@types/glob": "^5.0.30",
4548
"@types/jest": "^20.0.2",
4649
"@types/lodash": "^4.14.93",
4750
"@types/node": "^8.0.2",
51+
"@types/prettier": "^1.10.0",
4852
"@types/react": "^15.0.31",
53+
"dedent": "^0.7.0",
4954
"husky": "^0.14.3",
5055
"jest": "^20.0.4",
5156
"lint-staged": "^6.0.1",
52-
"prettier": "^1.10.2",
5357
"ts-jest": "^20.0.6",
5458
"ts-node": "^3.1.0",
5559
"tslint": "^5.2.0"

src/cli.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,22 @@ import * as program from 'commander';
44
import * as glob from 'glob';
55
import * as fs from 'fs';
66
import * as path from 'path';
7+
import * as prettier from 'prettier';
78

89
import { run } from '.';
910

1011
program
1112
.version('1.0.0')
13+
.option('--arrow-parens <avoid|always>', 'Include parentheses around a sole arrow function parameter.', 'avoid')
14+
.option('--no-bracket-spacing', 'Do not print spaces between brackets.', false)
15+
.option('--jsx-bracket-same-line', 'Put > on the last line instead of at a new line.', false)
16+
.option('--print-width <int>', 'The line length where Prettier will try wrap.', 80)
17+
.option('--prose-wrap <always|never|preserve> How to wrap prose. (markdown)', 'preserve')
18+
.option('--no-semi', 'Do not print semicolons, except at the beginning of lines which may need them', false)
19+
.option('--single-quote', 'Use single quotes instead of double quotes.', false)
20+
.option('--tab-width <int>', 'Number of spaces per indentation level.', 2)
21+
.option('--trailing-comma <none|es5|all>', 'Print trailing commas wherever possible when multi-line.', 'none')
22+
.option('--use-tabs', 'Indent with tabs instead of spaces.', false)
1223
.usage('[options] <filename or glob>')
1324
.command('* <glob>')
1425
.action(globPattern => {
@@ -22,7 +33,19 @@ program
2233

2334
try {
2435
fs.renameSync(filePath, newPath);
25-
const result = run(newPath);
36+
const prettierOptions: prettier.Options = {
37+
arrowParens: program.arrowParens,
38+
bracketSpacing: !program.noBracketSpacing,
39+
jsxBracketSameLine: !!program.jsxBracketSameLine,
40+
printWidth: parseInt(program.printWidth, 10),
41+
proseWrap: program.proseWrap,
42+
semi: !program.noSemi,
43+
singleQuote: !!program.singleQuote,
44+
tabWidth: parseInt(program.tabWidth, 10),
45+
trailingComma: program.trailingComma,
46+
useTabs: !!program.useTabs,
47+
};
48+
const result = run(newPath, prettierOptions);
2649
fs.writeFileSync(newPath, result);
2750
} catch (error) {
2851
console.warn(`Failed to convert ${file}`);

src/compiler.ts

+76-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1+
import * as os from 'os';
2+
import * as fs from 'fs';
13
import * as ts from 'typescript';
24
import * as chalk from 'chalk';
5+
import * as _ from 'lodash';
6+
import * as prettier from 'prettier';
7+
import * as detectIndent from 'detect-indent';
38

49
import { TransformFactoryFactory } from '.';
510

611
/**
712
* Compile and return result TypeScript
813
* @param filePath Path to file to compile
914
*/
10-
export function compile(filePath: string, factoryFactories: TransformFactoryFactory[]) {
15+
export function compile(
16+
filePath: string,
17+
factoryFactories: TransformFactoryFactory[],
18+
incomingPrettierOptions: prettier.Options = {},
19+
) {
1120
const compilerOptions: ts.CompilerOptions = {
1221
target: ts.ScriptTarget.ES2017,
1322
module: ts.ModuleKind.ES2015,
@@ -42,5 +51,70 @@ export function compile(filePath: string, factoryFactories: TransformFactoryFact
4251
const printer = ts.createPrinter();
4352

4453
// TODO: fix the index 0 access... What if program have multiple source files?
45-
return printer.printNode(ts.EmitHint.SourceFile, result.transformed[0], sourceFiles[0]);
54+
const printed = printer.printNode(ts.EmitHint.SourceFile, result.transformed[0], sourceFiles[0]);
55+
56+
const inputSource = fs.readFileSync(filePath, 'utf-8');
57+
const prettierOptions = getPrettierOptions(filePath, inputSource, incomingPrettierOptions);
58+
59+
return prettier.format(printed, incomingPrettierOptions);
60+
}
61+
62+
/**
63+
* Get Prettier options based on style of a JavaScript
64+
* @param filePath Path to source file
65+
* @param source Body of a JavaScript
66+
* @param options Existing prettier option
67+
*/
68+
export function getPrettierOptions(filePath: string, source: string, options: prettier.Options): prettier.Options {
69+
const resolvedOptions = prettier.resolveConfig.sync(filePath);
70+
if (resolvedOptions) {
71+
_.defaults(resolvedOptions, options);
72+
return resolvedOptions;
73+
}
74+
const { amount: indentAmount, type: indentType } = detectIndent(source);
75+
const sourceWidth = getCodeWidth(source, 80);
76+
const semi = getUseOfSemi(source);
77+
const quotations = getQuotation(source);
78+
79+
_.defaults(options, {
80+
tabWidth: indentAmount,
81+
useTabs: indentType && indentType === 'tab',
82+
printWidth: sourceWidth,
83+
semi,
84+
singleQuote: quotations === 'single',
85+
});
86+
87+
return options;
88+
}
89+
90+
/**
91+
* Given body of a source file, return its code width
92+
* @param source
93+
*/
94+
function getCodeWidth(source: string, defaultWidth: number): number {
95+
return source.split(os.EOL).reduce((result, line) => Math.max(result, line.length), defaultWidth);
96+
}
97+
98+
/**
99+
* Detect if a source file is using semicolon
100+
* @todo: use an actual parser. This is not a proper implementation
101+
* @param source
102+
* @return true if code is using semicolons
103+
*/
104+
function getUseOfSemi(source: string): boolean {
105+
return source.indexOf(';') !== -1;
106+
}
107+
108+
/**
109+
* Detect if a source file is using single quotes or double quotes
110+
* @todo use an actual parser. This is not a proper implementation
111+
* @param source
112+
*/
113+
function getQuotation(source: string): 'single' | 'double' {
114+
const numberOfSingleQuotes = (source.match(/\'/g) || []).length;
115+
const numberOfDoubleQuotes = (source.match(/\"/g) || []).length;
116+
if (numberOfSingleQuotes > numberOfDoubleQuotes) {
117+
return 'single';
118+
}
119+
return 'double';
46120
}

src/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as ts from 'typescript';
2+
import * as prettier from 'prettier';
23

34
import { compile } from './compiler';
45
import { reactJSMakePropsAndStateInterfaceTransformFactoryFactory } from './transforms/react-js-make-props-and-state-transform';
@@ -36,6 +37,6 @@ export type TransformFactoryFactory = (typeChecker: ts.TypeChecker) => ts.Transf
3637
* Run React JavaScript to TypeScript transform for file at `filePath`
3738
* @param filePath
3839
*/
39-
export function run(filePath: string): string {
40-
return compile(filePath, allTransforms);
40+
export function run(filePath: string, prettierOptions: prettier.Options = {}): string {
41+
return compile(filePath, allTransforms, prettierOptions);
4142
}

src/untyped-modules.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module 'dedent';
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
type Foo = {
2-
foo: string;
3-
stuff: boolean;
4-
other: () => void;
5-
bar: number;
6-
[key: string]: number;
2+
foo: string,
3+
stuff: boolean,
4+
other: () => void,
5+
bar: number,
6+
[key: string]: number,
77
};
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
type Foo = {
2-
};
1+
type Foo = {};
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
type Foo = {
2-
foo: string;
3-
bar: number;
2+
foo: string,
3+
bar: number,
44
};
55
type Bar = {
6-
foo: number;
7-
bar: string;
6+
foo: number,
7+
bar: string,
88
};
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
type Foo = {
2-
foo: string;
3-
bar: number;
2+
foo: string,
3+
bar: number,
44
};

test/end-to-end/basic/output.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import * as React from 'react';
2-
export default class MyComponent extends React.Component<{
3-
}, {
4-
}> {
2+
export default class MyComponent extends React.Component<{}, {}> {
53
render() {
64
return <div />;
75
}

test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as React from 'react';
22
type MyComponentProps = {
3-
baz: string;
3+
baz: string,
44
};
55
type MyComponentState = {
6-
dynamicState: number;
7-
foo: number;
8-
bar: string;
6+
dynamicState: number,
7+
foo: number,
8+
bar: string,
99
};
1010
export default class MyComponent extends React.Component<MyComponentProps, MyComponentState> {
1111
state = { foo: 1, bar: 'str' };

test/end-to-end/initial-state-and-proprypes/output.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as React from 'react';
22
type MyComponentProps = {
3-
baz: string;
3+
baz: string,
44
};
55
type MyComponentState = {
6-
foo: number;
7-
bar: string;
6+
foo: number,
7+
bar: string,
88
};
99
export default class MyComponent extends React.Component<MyComponentProps, MyComponentState> {
1010
state = { foo: 1, bar: 'str' };

test/end-to-end/multiple-components/output.tsx

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,31 @@
11
type HelloProps = {
2-
message?: string;
2+
message?: string,
33
};
44
const Hello: React.SFC<HelloProps> = ({ message }) => {
55
return <div>hello {message}</div>;
66
};
77
type HeyProps = {
8-
message?: string;
8+
message?: string,
99
};
1010
const Hey: React.SFC<HeyProps> = ({ name }) => {
1111
return <div>hey, {name}</div>;
1212
};
1313
type MyComponentState = {
14-
foo: number;
15-
bar: number;
14+
foo: number,
15+
bar: number,
1616
};
17-
export default class MyComponent extends React.Component<{
18-
}, MyComponentState> {
17+
export default class MyComponent extends React.Component<{}, MyComponentState> {
1918
render() {
20-
return <button onClick={this.onclick.bind(this)}/>;
19+
return <button onClick={this.onclick.bind(this)} />;
2120
}
2221
onclick() {
2322
this.setState({ foo: 1, bar: 2 });
2423
}
2524
}
2625
type AnotherComponentProps = {
27-
foo: string;
26+
foo: string,
2827
};
29-
export class AnotherComponent extends React.Component<AnotherComponentProps, {
30-
}> {
28+
export class AnotherComponent extends React.Component<AnotherComponentProps, {}> {
3129
render() {
3230
return <div />;
3331
}

test/end-to-end/non-react/output.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@ class Foo {
44
}
55
}
66
class Bar extends Foo {
7-
baz() {
8-
}
7+
baz() {}
98
}

test/end-to-end/stateless-arrow-function/output.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
type HelloProps = {
2-
message?: string;
2+
message?: string,
33
};
44
const Hello: React.SFC<HelloProps> = ({ message }) => {
55
return <div>hello {message}</div>;

test/end-to-end/stateless-function/output.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
type HelloProps = {
2-
message?: string;
2+
message?: string,
33
};
44
export const Hello: React.SFC<HelloProps> = ({ message }) => {
55
return <div>hello {message}</div>;

test/react-js-make-props-and-state-transform/multiple-components/output.tsx

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
import * as React from 'react';
2-
type MyComponentState = { foo: number; bar: number; };
3-
export default class MyComponent extends React.Component<{
4-
}, MyComponentState> {
2+
type MyComponentState = { foo: number, bar: number };
3+
export default class MyComponent extends React.Component<{}, MyComponentState> {
54
render() {
6-
return <button onClick={this.onclick.bind(this)}/>;
5+
return <button onClick={this.onclick.bind(this)} />;
76
}
87
onclick() {
98
this.setState({ foo: 1, bar: 2 });
109
}
1110
}
1211
type AnotherComponentProps = {
13-
foo: string;
12+
foo: string,
1413
};
15-
export class AnotherComponent extends React.Component<AnotherComponentProps, {
16-
}> {
14+
export class AnotherComponent extends React.Component<AnotherComponentProps, {}> {
1715
static propTypes = {
1816
foo: React.PropTypes.string.isRequired,
1917
};

0 commit comments

Comments
 (0)