From 0ef45c45f6cc5e8b3c0ae9759101fe383fa20fe7 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 21 Jan 2018 10:58:23 -0800 Subject: [PATCH 1/4] Pretty print using prettier --- .vscode/launch.json | 6 +- package.json | 8 +- src/cli.ts | 10 + src/compiler.ts | 77 +- src/untyped-modules.d.ts | 1 + .../advanced/output.tsx | 10 +- .../empty-empty/output.tsx | 3 +- .../multiple/output.tsx | 8 +- .../simple/output.tsx | 4 +- test/end-to-end/basic/output.tsx | 12 +- .../output.tsx | 29 +- .../initial-state-and-proprypes/output.tsx | 21 +- .../end-to-end/multiple-components/output.tsx | 41 +- test/end-to-end/non-react/output.tsx | 9 +- .../stateless-arrow-function/output.tsx | 4 +- test/end-to-end/stateless-function/output.tsx | 4 +- .../multiple-components/output.tsx | 39 +- .../non-react/output.tsx | 15 +- .../propless-stateless/output.tsx | 12 +- .../set-state-advanced/output.tsx | 55 +- .../set-state-only/output.tsx | 19 +- .../state-in-class-member/output.tsx | 15 +- .../state-in-constructor/output.tsx | 21 +- .../static-proptypes-getter-simple/output.tsx | 23 +- .../static-proptypes-many-props/output.tsx | 174 +- .../static-proptypes-simple/output.tsx | 19 +- .../multiple-components/output.tsx | 33 +- .../simple/output.tsx | 21 +- .../functional-components/output.tsx | 5 +- .../multiple/output.tsx | 21 +- .../simple/output.tsx | 14 +- .../from-prop-types/output.tsx | 5 +- .../from-react-multi-named-import/output.tsx | 9 +- .../from-react-simple/output.tsx | 5 +- .../getter/output.tsx | 14 +- .../multiple-components/output.tsx | 28 +- .../multiple/output.tsx | 14 +- .../simple/output.tsx | 14 +- .../empty-prop/output.tsx | 5 +- .../multiple-components/output.tsx | 12 +- .../stateless-arrow-function/output.tsx | 6 +- .../stateless-function-many-props/output.tsx | 162 +- .../stateless-function/output.tsx | 6 +- .../stateless-propless/output.tsx | 4 +- test/{runner.ts => transformers.test.ts} | 24 +- tsconfig.json | 2 +- yarn-error.log | 2867 +++++++++++++++++ yarn.lock | 12 + 48 files changed, 3459 insertions(+), 463 deletions(-) create mode 100644 src/untyped-modules.d.ts rename test/{runner.ts => transformers.test.ts} (78%) create mode 100644 yarn-error.log diff --git a/.vscode/launch.json b/.vscode/launch.json index 23883b1..fff8a01 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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": [], diff --git a/package.json b/package.json index 4a3110f..af75515 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "transform": { ".ts": "/node_modules/ts-jest/preprocessor.js" }, - "testRegex": "test/runner.ts", + "testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", "moduleFileExtensions": ["ts", "js"] }, "lint-staged": { @@ -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" diff --git a/src/cli.ts b/src/cli.ts index 6cc2031..a52e8da 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -9,6 +9,16 @@ import { run } from '.'; program .version('1.0.0') + .option('--arrow-parens ', '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 ', 'The line length where Prettier will try wrap.', 80) + .option('--prose-wrap 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 ', 'Number of spaces per indentation level.', 2) + .option('--trailing-comma ', 'Print trailing commas wherever possible when multi-line.', 'none') + .option('--use-tabs', 'Indent with tabs instead of spaces.', false) .usage('[options] ') .command('* ') .action(globPattern => { diff --git a/src/compiler.ts b/src/compiler.ts index acfce10..3db11ec 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,5 +1,10 @@ +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 '.'; @@ -7,7 +12,11 @@ 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, @@ -42,5 +51,69 @@ 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) { + 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'; } diff --git a/src/untyped-modules.d.ts b/src/untyped-modules.d.ts new file mode 100644 index 0000000..f187021 --- /dev/null +++ b/src/untyped-modules.d.ts @@ -0,0 +1 @@ +declare module 'dedent'; diff --git a/test/collapse-intersection-interfaces-transform/advanced/output.tsx b/test/collapse-intersection-interfaces-transform/advanced/output.tsx index a272d5a..991f604 100644 --- a/test/collapse-intersection-interfaces-transform/advanced/output.tsx +++ b/test/collapse-intersection-interfaces-transform/advanced/output.tsx @@ -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 }; diff --git a/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx b/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx index ad4c7b6..579668f 100644 --- a/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx +++ b/test/collapse-intersection-interfaces-transform/empty-empty/output.tsx @@ -1,2 +1 @@ -type Foo = { -}; +type Foo = {}; diff --git a/test/collapse-intersection-interfaces-transform/multiple/output.tsx b/test/collapse-intersection-interfaces-transform/multiple/output.tsx index cd722f1..1239f9f 100644 --- a/test/collapse-intersection-interfaces-transform/multiple/output.tsx +++ b/test/collapse-intersection-interfaces-transform/multiple/output.tsx @@ -1,8 +1,8 @@ type Foo = { - foo: string; - bar: number; + foo: string, + bar: number }; type Bar = { - foo: number; - bar: string; + foo: number, + bar: string }; diff --git a/test/collapse-intersection-interfaces-transform/simple/output.tsx b/test/collapse-intersection-interfaces-transform/simple/output.tsx index 97d43d9..6e82625 100644 --- a/test/collapse-intersection-interfaces-transform/simple/output.tsx +++ b/test/collapse-intersection-interfaces-transform/simple/output.tsx @@ -1,4 +1,4 @@ type Foo = { - foo: string; - bar: number; + foo: string, + bar: number }; diff --git a/test/end-to-end/basic/output.tsx b/test/end-to-end/basic/output.tsx index b2d81de..9698ad8 100644 --- a/test/end-to-end/basic/output.tsx +++ b/test/end-to-end/basic/output.tsx @@ -1,8 +1,6 @@ -import * as React from 'react'; -export default class MyComponent extends React.Component<{ - }, { - }> { - render() { - return
; - } +import * as React from "react"; +export default class MyComponent extends React.Component<{}, {}> { + render() { + return
; + } } diff --git a/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx b/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx index b120653..5df9c4f 100644 --- a/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx +++ b/test/end-to-end/initial-state-and-proprypes-and-set-state/output.tsx @@ -1,18 +1,21 @@ -import * as React from 'react'; +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 { - state = { foo: 1, bar: 'str' }; - render() { - return
; - } - otherFn() { - this.setState({ dynamicState: 42 }); - } +export default class MyComponent extends React.Component< + MyComponentProps, + MyComponentState +> { + state = { foo: 1, bar: "str" }; + render() { + return
; + } + otherFn() { + this.setState({ dynamicState: 42 }); + } } diff --git a/test/end-to-end/initial-state-and-proprypes/output.tsx b/test/end-to-end/initial-state-and-proprypes/output.tsx index 93ed422..3cdbf2b 100644 --- a/test/end-to-end/initial-state-and-proprypes/output.tsx +++ b/test/end-to-end/initial-state-and-proprypes/output.tsx @@ -1,14 +1,17 @@ -import * as React from 'react'; +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 { - state = { foo: 1, bar: 'str' }; - render() { - return
; - } +export default class MyComponent extends React.Component< + MyComponentProps, + MyComponentState +> { + state = { foo: 1, bar: "str" }; + render() { + return
; + } } diff --git a/test/end-to-end/multiple-components/output.tsx b/test/end-to-end/multiple-components/output.tsx index 4154dd3..b7b22cd 100644 --- a/test/end-to-end/multiple-components/output.tsx +++ b/test/end-to-end/multiple-components/output.tsx @@ -1,34 +1,35 @@ type HelloProps = { - message?: string; + message?: string }; const Hello: React.SFC = ({ message }) => { - return
hello {message}
; + return
hello {message}
; }; type HeyProps = { - message?: string; + message?: string }; const Hey: React.SFC = ({ name }) => { - return
hey, {name}
; + return
hey, {name}
; }; type MyComponentState = { - foo: number; - bar: number; + foo: number, + bar: number }; -export default class MyComponent extends React.Component<{ - }, MyComponentState> { - render() { - return