Skip to content
This repository was archived by the owner on Sep 21, 2019. It is now read-only.

Pretty print using prettier #25

Merged
merged 4 commits into from
Jan 21, 2018
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules
dist
.DS_Store
coverage/
coverage/
.log
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
test/
dist/
!test/transformers.test.ts
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
"env": {
"NODE_ENV": "test"
},
"externalConsole": false,
"console": "internalConsole",
"name": "Run Tests",
"outDir": "${workspaceRoot}/dist",
"preLaunchTask": "compile",
"outFiles": ["${workspaceRoot}/dist"],
"preLaunchTask": "tsc",
"program": "${workspaceRoot}/node_modules/.bin/jest",
"request": "launch",
"runtimeArgs": [],
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"transform": {
".ts": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "test/runner.ts",
"testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "js"]
},
"lint-staged": {
Expand All @@ -34,22 +34,26 @@
"dependencies": {
"chalk": "^1.1.3",
"commander": "^2.10.0",
"detect-indent": "^5.0.0",
"glob": "^7.1.2",
"lodash": "^4.17.4",
"prettier": "^1.10.2",
"typescript": "^2.6.2"
},
"devDependencies": {
"@types/chalk": "^0.4.31",
"@types/commander": "^2.9.1",
"@types/detect-indent": "^5.0.0",
"@types/glob": "^5.0.30",
"@types/jest": "^20.0.2",
"@types/lodash": "^4.14.93",
"@types/node": "^8.0.2",
"@types/prettier": "^1.10.0",
"@types/react": "^15.0.31",
"dedent": "^0.7.0",
"husky": "^0.14.3",
"jest": "^20.0.4",
"lint-staged": "^6.0.1",
"prettier": "^1.10.2",
"ts-jest": "^20.0.6",
"ts-node": "^3.1.0",
"tslint": "^5.2.0"
Expand Down
25 changes: 24 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,22 @@ import * as program from 'commander';
import * as glob from 'glob';
import * as fs from 'fs';
import * as path from 'path';
import * as prettier from 'prettier';

import { run } from '.';

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

try {
fs.renameSync(filePath, newPath);
const result = run(newPath);
const prettierOptions: prettier.Options = {
arrowParens: program.arrowParens,
bracketSpacing: !program.noBracketSpacing,
jsxBracketSameLine: !!program.jsxBracketSameLine,
printWidth: parseInt(program.printWidth, 10),
proseWrap: program.proseWrap,
semi: !program.noSemi,
singleQuote: !!program.singleQuote,
tabWidth: parseInt(program.tabWidth, 10),
trailingComma: program.trailingComma,
useTabs: !!program.useTabs,
};
const result = run(newPath, prettierOptions);
fs.writeFileSync(newPath, result);
} catch (error) {
console.warn(`Failed to convert ${file}`);
Expand Down
78 changes: 76 additions & 2 deletions src/compiler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import * as os from 'os';
import * as fs from 'fs';
import * as ts from 'typescript';
import * as chalk from 'chalk';
import * as _ from 'lodash';
import * as prettier from 'prettier';
import * as detectIndent from 'detect-indent';

import { TransformFactoryFactory } from '.';

/**
* Compile and return result TypeScript
* @param filePath Path to file to compile
*/
export function compile(filePath: string, factoryFactories: TransformFactoryFactory[]) {
export function compile(
filePath: string,
factoryFactories: TransformFactoryFactory[],
incomingPrettierOptions: prettier.Options = {},
) {
const compilerOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ES2017,
module: ts.ModuleKind.ES2015,
Expand Down Expand Up @@ -42,5 +51,70 @@ export function compile(filePath: string, factoryFactories: TransformFactoryFact
const printer = ts.createPrinter();

// TODO: fix the index 0 access... What if program have multiple source files?
return printer.printNode(ts.EmitHint.SourceFile, result.transformed[0], sourceFiles[0]);
const printed = printer.printNode(ts.EmitHint.SourceFile, result.transformed[0], sourceFiles[0]);

const inputSource = fs.readFileSync(filePath, 'utf-8');
const prettierOptions = getPrettierOptions(filePath, inputSource, incomingPrettierOptions);

return prettier.format(printed, incomingPrettierOptions);
}

/**
* Get Prettier options based on style of a JavaScript
* @param filePath Path to source file
* @param source Body of a JavaScript
* @param options Existing prettier option
*/
export function getPrettierOptions(filePath: string, source: string, options: prettier.Options): prettier.Options {
const resolvedOptions = prettier.resolveConfig.sync(filePath);
if (resolvedOptions) {
_.defaults(resolvedOptions, options);
return resolvedOptions;
}
const { amount: indentAmount, type: indentType } = detectIndent(source);
const sourceWidth = getCodeWidth(source, 80);
const semi = getUseOfSemi(source);
const quotations = getQuotation(source);

_.defaults(options, {
tabWidth: indentAmount,
useTabs: indentType && indentType === 'tab',
printWidth: sourceWidth,
semi,
singleQuote: quotations === 'single',
});

return options;
}

/**
* Given body of a source file, return its code width
* @param source
*/
function getCodeWidth(source: string, defaultWidth: number): number {
return source.split(os.EOL).reduce((result, line) => Math.max(result, line.length), defaultWidth);
}

/**
* Detect if a source file is using semicolon
* @todo: use an actual parser. This is not a proper implementation
* @param source
* @return true if code is using semicolons
*/
function getUseOfSemi(source: string): boolean {
return source.indexOf(';') !== -1;
}

/**
* Detect if a source file is using single quotes or double quotes
* @todo use an actual parser. This is not a proper implementation
* @param source
*/
function getQuotation(source: string): 'single' | 'double' {
const numberOfSingleQuotes = (source.match(/\'/g) || []).length;
const numberOfDoubleQuotes = (source.match(/\"/g) || []).length;
if (numberOfSingleQuotes > numberOfDoubleQuotes) {
return 'single';
}
return 'double';
}
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as ts from 'typescript';
import * as prettier from 'prettier';

import { compile } from './compiler';
import { reactJSMakePropsAndStateInterfaceTransformFactoryFactory } from './transforms/react-js-make-props-and-state-transform';
Expand Down Expand Up @@ -36,6 +37,6 @@ export type TransformFactoryFactory = (typeChecker: ts.TypeChecker) => ts.Transf
* Run React JavaScript to TypeScript transform for file at `filePath`
* @param filePath
*/
export function run(filePath: string): string {
return compile(filePath, allTransforms);
export function run(filePath: string, prettierOptions: prettier.Options = {}): string {
return compile(filePath, allTransforms, prettierOptions);
}
1 change: 1 addition & 0 deletions src/untyped-modules.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'dedent';
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type Foo = {
foo: string;
stuff: boolean;
other: () => void;
bar: number;
[key: string]: number;
foo: string,
stuff: boolean,
other: () => void,
bar: number,
[key: string]: number,
};
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
type Foo = {
};
type Foo = {};
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
type Foo = {
foo: string;
bar: number;
foo: string,
bar: number,
};
type Bar = {
foo: number;
bar: string;
foo: number,
bar: string,
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type Foo = {
foo: string;
bar: number;
foo: string,
bar: number,
};
4 changes: 1 addition & 3 deletions test/end-to-end/basic/output.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import * as React from 'react';
export default class MyComponent extends React.Component<{
}, {
}> {
export default class MyComponent extends React.Component<{}, {}> {
render() {
return <div />;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as React from 'react';
type MyComponentProps = {
baz: string;
baz: string,
};
type MyComponentState = {
dynamicState: number;
foo: number;
bar: string;
dynamicState: number,
foo: number,
bar: string,
};
export default class MyComponent extends React.Component<MyComponentProps, MyComponentState> {
state = { foo: 1, bar: 'str' };
Expand Down
6 changes: 3 additions & 3 deletions test/end-to-end/initial-state-and-proprypes/output.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as React from 'react';
type MyComponentProps = {
baz: string;
baz: string,
};
type MyComponentState = {
foo: number;
bar: string;
foo: number,
bar: string,
};
export default class MyComponent extends React.Component<MyComponentProps, MyComponentState> {
state = { foo: 1, bar: 'str' };
Expand Down
18 changes: 8 additions & 10 deletions test/end-to-end/multiple-components/output.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
type HelloProps = {
message?: string;
message?: string,
};
const Hello: React.SFC<HelloProps> = ({ message }) => {
return <div>hello {message}</div>;
};
type HeyProps = {
message?: string;
message?: string,
};
const Hey: React.SFC<HeyProps> = ({ name }) => {
return <div>hey, {name}</div>;
};
type MyComponentState = {
foo: number;
bar: number;
foo: number,
bar: number,
};
export default class MyComponent extends React.Component<{
}, MyComponentState> {
export default class MyComponent extends React.Component<{}, MyComponentState> {
render() {
return <button onClick={this.onclick.bind(this)}/>;
return <button onClick={this.onclick.bind(this)} />;
}
onclick() {
this.setState({ foo: 1, bar: 2 });
}
}
type AnotherComponentProps = {
foo: string;
foo: string,
};
export class AnotherComponent extends React.Component<AnotherComponentProps, {
}> {
export class AnotherComponent extends React.Component<AnotherComponentProps, {}> {
render() {
return <div />;
}
Expand Down
3 changes: 1 addition & 2 deletions test/end-to-end/non-react/output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ class Foo {
}
}
class Bar extends Foo {
baz() {
}
baz() {}
}
2 changes: 1 addition & 1 deletion test/end-to-end/stateless-arrow-function/output.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type HelloProps = {
message?: string;
message?: string,
};
const Hello: React.SFC<HelloProps> = ({ message }) => {
return <div>hello {message}</div>;
Expand Down
2 changes: 1 addition & 1 deletion test/end-to-end/stateless-function/output.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type HelloProps = {
message?: string;
message?: string,
};
export const Hello: React.SFC<HelloProps> = ({ message }) => {
return <div>hello {message}</div>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import * as React from 'react';
type MyComponentState = { foo: number; bar: number; };
export default class MyComponent extends React.Component<{
}, MyComponentState> {
type MyComponentState = { foo: number, bar: number };
export default class MyComponent extends React.Component<{}, MyComponentState> {
render() {
return <button onClick={this.onclick.bind(this)}/>;
return <button onClick={this.onclick.bind(this)} />;
}
onclick() {
this.setState({ foo: 1, bar: 2 });
}
}
type AnotherComponentProps = {
foo: string;
foo: string,
};
export class AnotherComponent extends React.Component<AnotherComponentProps, {
}> {
export class AnotherComponent extends React.Component<AnotherComponentProps, {}> {
static propTypes = {
foo: React.PropTypes.string.isRequired,
};
Expand Down
Loading