Repository: prettier/prettier-eslint
Pull request #59 (2017-03-29 17:49:15)

add prettierLast option
Adds a new prettierLast option (see #58).

As recommended in that issue, I'll also be sumitting PRs to add this to the `prettier-eslint-cli` and `prettier-atom`, assuming y'all are happy with this first PR.


Patch:
diff --git a/README.md b/README.md
index bc95ca0..1bd0dcf 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,15 @@ with the `eslintPath` option.
 
 This is basically the same as `eslintPath` except for the `prettier` module.
 
+#### prettierLast (?Boolean)
+
+By default, `prettier-eslint` will run `prettier` first, then `eslint --fix`. This is great if you want to use `prettier`,
+but override some of the styles you don't like using `eslint --fix`.
+
+An alternative approach is to use different tools for different concerns. If you provide `pretterLast: true`, it will
+run `eslint --fix` first, then `prettier`. This allows you to use `eslint` to look for bugs and/or bad practices, and use
+`prettier` to enforce code style.
+
 ### throws
 
 `prettier-eslint` will propagate errors when either `prettier` or `eslint` fails for one reason or another. In addition
diff --git a/src/index.js b/src/index.js
index a9a5bd2..c413c34 100644
--- a/src/index.js
+++ b/src/index.js
@@ -29,8 +29,9 @@ module.exports = format
  * @param {Object} options.prettierOptions - the options to pass for
  *  formatting with `prettier`. If not provided, prettier-eslint will attempt
  *  to create the options based on the eslintConfig
- * @param {Boolean} options.logLevel - the level for the logs
+ * @param {String} options.logLevel - the level for the logs
  *  (error, warn, info, debug, trace)
+ * @param {Boolean} options.prettierLast - Run Prettier Last
  * @return {String} - the formatted string
  */
 function format(options) {
@@ -44,6 +45,7 @@ function format(options) {
     eslintPath = getModulePath(filePath, 'eslint'),
     prettierPath = getModulePath(filePath, 'prettier'),
     prettierOptions,
+    prettierLast,
   } = options
 
   const eslintConfig = defaultEslintConfig(
@@ -66,83 +68,93 @@ function format(options) {
       eslintConfig: formattingOptions.eslint,
       prettierOptions: formattingOptions.prettier,
       logLevel,
+      prettierLast,
     }),
   )
 
-  logger.debug('calling prettier on text')
-  logger.trace(
-    stripIndent`
-      prettier input:
+  const prettify = createPrettify(formattingOptions.prettier, prettierPath)
+  const eslintFix = createEslintFix(formattingOptions.eslint, eslintPath)
 
-      ${indentString(text, 2)}
-    `,
-  )
-  const pretty = prettify(text, formattingOptions.prettier, prettierPath)
-  logger.trace(
-    stripIndent`
-      prettier output (eslint input):
-
-      ${indentString(pretty, 2)}
-    `,
-  )
-  const eslintFixed = eslintFix(pretty, formattingOptions.eslint, eslintPath)
-  logger.trace(
-    stripIndent`
-      eslint --fix output (final formatted result):
+  if (prettierLast) {
+    return prettify(eslintFix(text))
+  }
 
-      ${indentString(pretty, 2)}
-    `,
-  )
-  return eslintFixed
+  return eslintFix(prettify(text))
 }
 
-function prettify(text, formatOptions, prettierPath) {
-  let prettier
-  try {
-    logger.trace(`requiring prettier module at "${prettierPath}"`)
-    prettier = require(prettierPath)
-  } catch (error) {
-    logger.error(
-      oneLine`
+function createPrettify(formatOptions, prettierPath) {
+  return function prettify(text) {
+    logger.debug('calling prettier on text')
+    logger.trace(
+      stripIndent`
+      prettier input:
+
+      ${indentString(text, 2)}
+    `,
+    )
+    let prettier
+    try {
+      logger.trace(`requiring prettier module at "${prettierPath}"`)
+      prettier = require(prettierPath)
+    } catch (error) {
+      logger.error(
+        oneLine`
         There was trouble getting prettier.
         Is "prettierPath: ${prettierPath}"
         a correct path to the prettier module?
       `,
-    )
-    throw error
-  }
-  try {
-    logger.trace(`calling prettier.format with the text and prettierOptions`)
-    const output = prettier.format(text, formatOptions)
-    logger.trace('prettier: output === input', output === text)
-    return output
-  } catch (error) {
-    logger.error('prettier formatting failed due to a prettier error')
-    throw error
+      )
+      throw error
+    }
+    try {
+      logger.trace(`calling prettier.format with the text and prettierOptions`)
+      const output = prettier.format(text, formatOptions)
+      logger.trace('prettier: output === input', output === text)
+      logger.trace(
+        stripIndent`
+        prettier output:
+
+        ${indentString(output, 2)}
+      `,
+      )
+      return output
+    } catch (error) {
+      logger.error('prettier formatting failed due to a prettier error')
+      throw error
+    }
   }
 }
 
-function eslintFix(text, eslintConfig, eslintPath) {
-  const eslint = getESLintCLIEngine(eslintPath, eslintConfig)
-  try {
-    logger.trace(`calling eslint.executeOnText with the text`)
-    const report = eslint.executeOnText(text)
-    logger.trace(
-      `executeOnText returned the following report:`,
-      prettyFormat(report),
-    )
-    // default the output to text because if there's nothing
-    // to fix, eslint doesn't provide `output`
-    const [{output = text}] = report.results
-    logger.trace('eslint --fix: output === input', output === text)
-    // NOTE: We're ignoring linting errors/warnings here and
-    // defaulting to the given text if there are any
-    // because all we're trying to do is fix what we can.
-    // We don't care about what we can't
-    return output
-  } catch (error) {
-    logger.error('eslint fix failed due to an eslint error')
-    throw error
+function createEslintFix(eslintConfig, eslintPath) {
+  return function eslintFix(text) {
+    const eslint = getESLintCLIEngine(eslintPath, eslintConfig)
+    try {
+      logger.trace(`calling eslint.executeOnText with the text`)
+      const report = eslint.executeOnText(text)
+      logger.trace(
+        `executeOnText returned the following report:`,
+        prettyFormat(report),
+      )
+      // default the output to text because if there's nothing
+      // to fix, eslint doesn't provide `output`
+      const [{output = text}] = report.results
+      logger.trace('eslint --fix: output === input', output === text)
+      // NOTE: We're ignoring linting errors/warnings here and
+      // defaulting to the given text if there are any
+      // because all we're trying to do is fix what we can.
+      // We don't care about what we can't
+      logger.trace(
+        stripIndent`
+        eslint --fix output:
+
+        ${indentString(output, 2)}
+      `,
+      )
+      return output
+    } catch (error) {
+      logger.error('eslint fix failed due to an eslint error')
+      throw error
+    }
   }
 }
 
@@ -189,7 +201,7 @@ function getConfig(filePath, eslintPath) {
   } catch (error) {
     // is this noisy? Try setting options.disableLog to false
     logger.debug('Unable to find config')
-    return { rules: {} }
+    return {rules: {}}
   }
 }
 
diff --git a/src/utils.js b/src/utils.js
index 14705d9..263dc06 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -35,24 +35,24 @@ function getRelevantESLintConfig(eslintConfig) {
   ]
 
   logger.debug('reducing eslint rules down to relevant rules only')
-  const relevantRules = Object.keys(rules).reduce((
-    rulesAccumulator,
-    ruleName,
-  ) => {
-    if (rulesThatWillNeverBeFixable.indexOf(ruleName) === -1) {
-      logger.trace(
-        `adding to relevant rules:`,
-        JSON.stringify({[ruleName]: rules[ruleName]}),
-      )
-      rulesAccumulator[ruleName] = rules[ruleName]
-    } else {
-      logger.trace(
-        `omitting from relevant rules:`,
-        JSON.stringify({[ruleName]: rules[ruleName]}),
-      )
-    }
-    return rulesAccumulator
-  }, {})
+  const relevantRules = Object.keys(rules).reduce(
+    (rulesAccumulator, ruleName) => {
+      if (rulesThatWillNeverBeFixable.indexOf(ruleName) === -1) {
+        logger.trace(
+          `adding to relevant rules:`,
+          JSON.stringify({[ruleName]: rules[ruleName]}),
+        )
+        rulesAccumulator[ruleName] = rules[ruleName]
+      } else {
+        logger.trace(
+          `omitting from relevant rules:`,
+          JSON.stringify({[ruleName]: rules[ruleName]}),
+        )
+      }
+      return rulesAccumulator
+    },
+    {},
+  )
 
   return {
     // defaults
@@ -80,13 +80,16 @@ function getPrettierOptionsFromESLintRules(eslintConfig, prettierOptions) {
     bracketSpacing: getBraketSpacing,
   }
 
-  return Object.keys(optionGetters).reduce((options, key) => {
-    const givenOption = prettierOptions[key]
-    const optionIsGiven = prettierOptions[key] !== undefined
-    const getter = optionGetters[key]
-    options[key] = optionIsGiven ? givenOption : getter(rules)
-    return options
-  }, {})
+  return Object.keys(optionGetters).reduce(
+    (options, key) => {
+      const givenOption = prettierOptions[key]
+      const optionIsGiven = prettierOptions[key] !== undefined
+      const getter = optionGetters[key]
+      options[key] = optionIsGiven ? givenOption : getter(rules)
+      return options
+    },
+    {},
+  )
 }
 
 function getPrintWidth(rules) {


Test patch:
diff --git a/src/index.test.js b/src/index.test.js
index 7f32206..8822886 100644
--- a/src/index.test.js
+++ b/src/index.test.js
@@ -33,6 +33,15 @@ const tests = [
     },
     output: 'const { foo } = bar',
   },
+  {
+    title: 'with prettierLast: true',
+    input: {
+      text: defaultInputText(),
+      filePath: path.resolve('./mock/default-config'),
+      prettierLast: true,
+    },
+    output: prettierLastOutput(),
+  },
   {
     title: 'with a filePath and no config',
     input: {
@@ -295,3 +304,12 @@ function defaultOutput() {
     }
   `
 }
+
+function prettierLastOutput() {
+  return `
+    function foo() {
+      // stuff
+      console.log('Hello world!', and, stuff);
+    }
+  `
+}
diff --git a/src/utils.test.js b/src/utils.test.js
index c246b0e..2491a54 100644
--- a/src/utils.test.js
+++ b/src/utils.test.js
@@ -172,9 +172,12 @@ getPrettierOptionsFromESLintRulesTests.forEach(({rules, options}, index) => {
 })
 
 test('if prettierOptions are provided, those are preferred', () => {
-  const {prettier} = getOptionsForFormatting({rules: {quotes: [2, 'single']}}, {
-    singleQuote: false,
-  })
+  const {prettier} = getOptionsForFormatting(
+    {rules: {quotes: [2, 'single']}},
+    {
+      singleQuote: false,
+    },
+  )
   expect(prettier).toMatchObject({singleQuote: false})
 })