diff --git a/src/cli.ts b/src/cli.ts index ed73b9b..779f0a9 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -7,6 +7,28 @@ import * as path from 'path'; import * as prettier from 'prettier'; import { run } from '.'; +import { CompilationOptions } from './compiler'; + +function resolveGlobs(globPatterns: string[]): string[] { + const files: string[] = []; + function addFile(file: string) { + file = path.resolve(file); + if (files.indexOf(file) === -1) { + files.push(file); + } + } + globPatterns.forEach(pattern => { + if (/[{}*?+\[\]]/.test(pattern)) { + // Smells like globs + glob.sync(pattern, {}).forEach(file => { + addFile(file); + }); + } else { + addFile(pattern); + } + }); + return files; +} program .version('1.0.0') @@ -20,38 +42,57 @@ program .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) + .option('--ignore-prettier-errors', 'Ignore (but warn about) errors in Prettier', false) + .option('--keep-original-files', 'Keep original files', false) + .option('--keep-temporary-files', 'Keep temporary files', false) .usage('[options] ') - .command('* ') - .action(globPattern => { - if (!globPattern) { - throw new Error('You must provide a file name or glob pattern to transform'); + .command('* [glob/filename...]') + .action((globPatterns: string[]) => { + 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 compilationOptions: CompilationOptions = { + ignorePrettierErrors: !!program.ignorePrettierErrors, + }; + const files = resolveGlobs(globPatterns); + if (!files.length) { + throw new Error('Nothing to do. You must provide file names or glob patterns to transform.'); } - const files = glob.sync(globPattern, {}); - for (const file of files) { - const filePath = path.resolve(file); + let errors = false; + for (const filePath of files) { + console.log(`Transforming ${filePath}...`); const newPath = filePath.replace(/\.jsx?$/, '.tsx'); - + const temporaryPath = filePath.replace(/\.jsx?$/, `_js2ts_${+new Date()}.tsx`); try { - fs.renameSync(filePath, 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.copyFileSync(filePath, temporaryPath); + const result = run(temporaryPath, prettierOptions, compilationOptions); fs.writeFileSync(newPath, result); + if (!program.keepOriginalFiles) { + fs.unlinkSync(filePath); + } } catch (error) { - console.warn(`Failed to convert ${file}`); + console.warn(`Failed to convert ${filePath}`); console.warn(error); + errors = true; + } + if (!program.keepTemporaryFiles) { + if (fs.existsSync(temporaryPath)) { + fs.unlinkSync(temporaryPath); + } } } + if (errors) { + process.exit(1); + } }); program.parse(process.argv); diff --git a/src/compiler.ts b/src/compiler.ts index a13ebac..9afc378 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -8,6 +8,16 @@ import * as detectIndent from 'detect-indent'; import { TransformFactoryFactory } from '.'; +export interface CompilationOptions { + ignorePrettierErrors: boolean; +} + +const DEFAULT_COMPILATION_OPTIONS: CompilationOptions = { + ignorePrettierErrors: false, +}; + +export { DEFAULT_COMPILATION_OPTIONS }; + /** * Compile and return result TypeScript * @param filePath Path to file to compile @@ -16,6 +26,7 @@ export function compile( filePath: string, factoryFactories: TransformFactoryFactory[], incomingPrettierOptions: prettier.Options = {}, + compilationOptions: CompilationOptions = DEFAULT_COMPILATION_OPTIONS, ) { const compilerOptions: ts.CompilerOptions = { target: ts.ScriptTarget.ES2017, @@ -56,7 +67,16 @@ export function compile( const inputSource = fs.readFileSync(filePath, 'utf-8'); const prettierOptions = getPrettierOptions(filePath, inputSource, incomingPrettierOptions); - return prettier.format(printed, incomingPrettierOptions); + try { + return prettier.format(printed, prettierOptions); + } catch (prettierError) { + if (compilationOptions.ignorePrettierErrors) { + console.warn(`Prettier failed for ${filePath} (ignorePrettierErrors is on):`); + console.warn(prettierError); + return printed; + } + throw prettierError; + } } /** @@ -76,7 +96,7 @@ export function getPrettierOptions(filePath: string, source: string, options: pr const semi = getUseOfSemi(source); const quotations = getQuotation(source); - _.defaults(options, { + _.defaults(Object.assign({}, options), { tabWidth: indentAmount, useTabs: indentType && indentType === 'tab', printWidth: sourceWidth, diff --git a/src/index.ts b/src/index.ts index 831dbd9..5c26c27 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import * as ts from 'typescript'; import * as prettier from 'prettier'; -import { compile } from './compiler'; +import { compile, CompilationOptions, DEFAULT_COMPILATION_OPTIONS } from './compiler'; import { reactJSMakePropsAndStateInterfaceTransformFactoryFactory } from './transforms/react-js-make-props-and-state-transform'; import { reactRemovePropTypesAssignmentTransformFactoryFactory } from './transforms/react-remove-prop-types-assignment-transform'; import { reactMovePropTypesToClassTransformFactoryFactory } from './transforms/react-move-prop-types-to-class-transform'; @@ -37,6 +37,10 @@ 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, prettierOptions: prettier.Options = {}): string { - return compile(filePath, allTransforms, prettierOptions); +export function run( + filePath: string, + prettierOptions: prettier.Options = {}, + compilationOptions: CompilationOptions = DEFAULT_COMPILATION_OPTIONS, +): string { + return compile(filePath, allTransforms, prettierOptions, compilationOptions); }