Repository: prettier/prettier-atom
Pull request #165 (2017-05-12 15:24:07)

Refactor
This is a substantial refactor of the codebase. We had started to amass too many files in the root of the src directory, and the helpers file had become very incohesive. Now, if a certain function is only used once and does not interface with atom or the atom TextEditor object, it stays near the function calling it either in the same file or as a sibling file. Each high-level operation or concern is given its own folder so that the root of src is much cleaner and easier to read.

Other changes of note:
- moved away from the strict `actual`/`expected` syntax for tests as this was unusual for people trying to contribute to the project, and I'm not 100% sold on it personally anyway
- refactored certain functions to take a more FP approach and avoid local state wherever possible
- removed flow coverage of test files: flow seems to have terrible jest support, so we were having to put `$FlowFixMe` annotations everytime we used `mockImplementation`. That noise was probably hurting more than flow was helping.


Patch:
diff --git a/decls/index.js b/decls/index.js
index d18fe4a..ba211ce 100644
--- a/decls/index.js
+++ b/decls/index.js
@@ -15,7 +15,6 @@ declare type TextEditor = {
   getLastCursor: () => { getScopeDescriptor: () => string },
   getSelectedText: () => string,
   getSelectedBufferRanges: () => Ranges,
-  // getTextInRange: () => string,
   getTextInBufferRange: () => string,
   setCursorScreenPosition: (point: Point) => Point,
   setTextInBufferRange: (bufferRange: Range, text: string) => Range,
@@ -36,6 +35,8 @@ declare type Atom$Disposable = any;
 declare type Atom$View = any;
 declare type Atom$Workspace = any;
 declare type Atom$Command = { name: string, displayName: string };
+declare type Atom$Notifications$Options = { detail?: ?string, dismissable?: ?boolean };
+declare type Atom$Tooltips$Options = { title?: string };
 declare var atom: {
   commands: {
     dispatch: (view: Atom$View, commandName: string) => void,
@@ -46,11 +47,12 @@ declare var atom: {
     set: (key: string) => any,
   },
   notifications: {
-    addError: (message: string, options?: { detail?: string, dismissable?: boolean }) => void,
-    addInfo: (message: string, options?: { detail?: string, dismissable?: boolean }) => void,
+    addError: (message: string, options?: Atom$Notifications$Options) => void,
+    addInfo: (message: string, options?: Atom$Notifications$Options) => void,
+    addWarning: (message: string, options?: Atom$Notifications$Options) => void,
   },
   tooltips: {
-    add: (target: HTMLElement, options?: { title?: string }) => Atom$Disposable
+    add: (target: HTMLElement, options?: Atom$Tooltips$Options) => Atom$Disposable,
   },
   views: {
     getView: Atom$Workspace => Atom$View,
diff --git a/dist/atomInterface/index.js b/dist/atomInterface/index.js
new file mode 100644
index 0000000..68f7ae1
--- /dev/null
+++ b/dist/atomInterface/index.js
@@ -0,0 +1,123 @@
+'use strict';
+
+// constants
+var LINTER_LINT_COMMAND = 'linter:lint';
+
+// local helpers
+var getConfigOption = function getConfigOption(key) {
+  return atom.config.get('prettier-atom.' + key);
+};
+
+var setConfigOption = function setConfigOption(key, value) {
+  return atom.config.set('prettier-atom.' + key, value);
+};
+
+var isLinterLintCommandDefined = function isLinterLintCommandDefined(editor) {
+  return atom.commands.findCommands({ target: atom.views.getView(editor) }).some(function (command) {
+    return command.name === LINTER_LINT_COMMAND;
+  });
+};
+
+// public
+var isLinterEslintAutofixEnabled = function isLinterEslintAutofixEnabled() {
+  return atom.config.get('linter-eslint.fixOnSave');
+};
+
+var shouldUseEslint = function shouldUseEslint() {
+  return getConfigOption('useEslint');
+};
+
+var shouldUseEditorConfig = function shouldUseEditorConfig() {
+  return getConfigOption('useEditorConfig');
+};
+
+var shouldDisplayErrors = function shouldDisplayErrors() {
+  return !getConfigOption('silenceErrors');
+};
+
+var isFormatOnSaveEnabled = function isFormatOnSaveEnabled() {
+  return getConfigOption('formatOnSaveOptions.enabled');
+};
+
+var shouldRespectEslintignore = function shouldRespectEslintignore() {
+  return getConfigOption('formatOnSaveOptions.respectEslintignore');
+};
+
+var getScopes = function getScopes() {
+  return getConfigOption('formatOnSaveOptions.scopes');
+};
+
+var getWhitelistedGlobs = function getWhitelistedGlobs() {
+  return getConfigOption('formatOnSaveOptions.whitelistedGlobs');
+};
+
+var getExcludedGlobs = function getExcludedGlobs() {
+  return getConfigOption('formatOnSaveOptions.excludedGlobs');
+};
+
+var toggleFormatOnSave = function toggleFormatOnSave() {
+  return setConfigOption('formatOnSaveOptions.enabled', !isFormatOnSaveEnabled());
+};
+
+var getAtomTabLength = function getAtomTabLength(editor) {
+  return atom.config.get('editor.tabLength', { scope: editor.getLastCursor().getScopeDescriptor() });
+};
+
+var getPrettierOptions = function getPrettierOptions() {
+  return getConfigOption('prettierOptions');
+};
+
+var getPrettierEslintOptions = function getPrettierEslintOptions() {
+  return getConfigOption('prettierEslintOptions');
+};
+
+var getAtomVersion = function getAtomVersion() {
+  return atom.getVersion();
+};
+
+var getPrettierAtomConfig = function getPrettierAtomConfig() {
+  return atom.config.get('prettier-atom');
+};
+
+var addTooltip = function addTooltip(element, options) {
+  return atom.tooltips.add(element, options);
+};
+
+var addInfoNotification = function addInfoNotification(message, options) {
+  return atom.notifications.addInfo(message, options);
+};
+
+var addWarningNotification = function addWarningNotification(message, options) {
+  return atom.notifications.addWarning(message, options);
+};
+
+var addErrorNotification = function addErrorNotification(message, options) {
+  return atom.notifications.addError(message, options);
+};
+
+var runLinter = function runLinter(editor) {
+  return isLinterLintCommandDefined(editor) && atom.commands.dispatch(atom.views.getView(editor), LINTER_LINT_COMMAND);
+};
+
+module.exports = {
+  addErrorNotification: addErrorNotification,
+  addInfoNotification: addInfoNotification,
+  addTooltip: addTooltip,
+  addWarningNotification: addWarningNotification,
+  getAtomTabLength: getAtomTabLength,
+  getAtomVersion: getAtomVersion,
+  getExcludedGlobs: getExcludedGlobs,
+  getPrettierAtomConfig: getPrettierAtomConfig,
+  getPrettierEslintOptions: getPrettierEslintOptions,
+  getPrettierOptions: getPrettierOptions,
+  getScopes: getScopes,
+  getWhitelistedGlobs: getWhitelistedGlobs,
+  isFormatOnSaveEnabled: isFormatOnSaveEnabled,
+  isLinterEslintAutofixEnabled: isLinterEslintAutofixEnabled,
+  runLinter: runLinter,
+  shouldDisplayErrors: shouldDisplayErrors,
+  shouldRespectEslintignore: shouldRespectEslintignore,
+  shouldUseEditorConfig: shouldUseEditorConfig,
+  shouldUseEslint: shouldUseEslint,
+  toggleFormatOnSave: toggleFormatOnSave
+};
\ No newline at end of file
diff --git a/dist/config-schema.json b/dist/config-schema.json
index a38a403..c392bd2 100644
--- a/dist/config-schema.json
+++ b/dist/config-schema.json
@@ -15,7 +15,7 @@
   },
   "useEditorConfig": {
     "title": "EditorConfig Integration",
-    "description": "Use [EditorConfig](http://editorconfig.org/) configuration to load project-based settings for prettier formatting <br />Next options will be overriden: **Use tabs**, **Tab width**, **Print width**",
+    "description": "Use [EditorConfig](http://editorconfig.org/) configuration to load project-based settings for prettier formatting <br />Takes precedence over your settings. The following settings will be overriden if they are found in your .editorconfig: **Use tabs**, **Tab width**, **Print width**",
     "type": "boolean",
     "default": true,
     "order": 3
diff --git a/dist/displayDebugInfo.js b/dist/displayDebugInfo.js
deleted file mode 100644
index 95bc05d..0000000
--- a/dist/displayDebugInfo.js
+++ /dev/null
@@ -1,15 +0,0 @@
-'use strict';
-
-var _require = require('./helpers'),
-    getDebugInfo = _require.getDebugInfo;
-
-var displayDebugInfo = function displayDebugInfo() {
-  var info = getDebugInfo();
-  var details = ['Atom version: ' + info.atomVersion, 'prettier-atom version: ' + info.prettierAtomVersion, 'prettier version: ' + info.prettierVersion, 'prettier-eslint version: ' + info.prettierESLintVersion, 'prettier-atom configuration: ' + JSON.stringify(info.prettierAtomConfig, null, 2)].join('\n');
-  atom.notifications.addInfo('prettier-atom: details on current install', {
-    detail: details,
-    dismissable: true
-  });
-};
-
-module.exports = displayDebugInfo;
\ No newline at end of file
diff --git a/dist/displayDebugInfo/index.js b/dist/displayDebugInfo/index.js
new file mode 100644
index 0000000..ffd873e
--- /dev/null
+++ b/dist/displayDebugInfo/index.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var readPkg = require('read-pkg');
+var path = require('path');
+
+var _require = require('../atomInterface'),
+    getAtomVersion = _require.getAtomVersion,
+    getPrettierAtomConfig = _require.getPrettierAtomConfig,
+    addInfoNotification = _require.addInfoNotification;
+
+var getDepPath = function getDepPath(dep) {
+  return path.join(__dirname, '..', '..', 'node_modules', dep);
+};
+
+var getDebugInfo = function getDebugInfo() {
+  return ('\nAtom version: ' + getAtomVersion() + '\nprettier-atom version: ' + readPkg.sync().version + '\nprettier version: ' + readPkg.sync(getDepPath('prettier')).version + '\nprettier-eslint version: ' + readPkg.sync(getDepPath('prettier-eslint')).version + '\nprettier-atom configuration: ' + JSON.stringify(getPrettierAtomConfig(), null, 2) + '\n').trim();
+};
+
+var displayDebugInfo = function displayDebugInfo() {
+  return addInfoNotification('prettier-atom: details on current install', {
+    detail: getDebugInfo(),
+    dismissable: true
+  });
+};
+
+module.exports = displayDebugInfo;
\ No newline at end of file
diff --git a/dist/editorInterface/index.js b/dist/editorInterface/index.js
new file mode 100644
index 0000000..6ac1d80
--- /dev/null
+++ b/dist/editorInterface/index.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var EMBEDDED_SCOPES = ['text.html.vue', 'text.html.basic'];
+
+var getBufferRange = function getBufferRange(editor) {
+  return editor.getBuffer().getRange();
+};
+
+var getCurrentScope = function getCurrentScope(editor) {
+  return editor.getGrammar().scopeName;
+};
+
+var isCurrentScopeEmbeddedScope = function isCurrentScopeEmbeddedScope(editor) {
+  return EMBEDDED_SCOPES.includes(getCurrentScope(editor));
+};
+
+var getCurrentFilePath = function getCurrentFilePath(editor) {
+  return editor.buffer.file ? editor.buffer.file.path : undefined;
+};
+
+module.exports = {
+  getBufferRange: getBufferRange,
+  isCurrentScopeEmbeddedScope: isCurrentScopeEmbeddedScope,
+  getCurrentScope: getCurrentScope,
+  getCurrentFilePath: getCurrentFilePath
+};
\ No newline at end of file
diff --git a/dist/executePrettier.js b/dist/executePrettier.js
deleted file mode 100644
index 3e0e911..0000000
--- a/dist/executePrettier.js
+++ /dev/null
@@ -1,94 +0,0 @@
-'use strict';
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-var prettierEslint = require('prettier-eslint');
-
-var _require = require('loophole'),
-    allowUnsafeNewFunction = _require.allowUnsafeNewFunction;
-
-var _require2 = require('./helpers'),
-    getCurrentFilePath = _require2.getCurrentFilePath,
-    getPrettier = _require2.getPrettier,
-    runLinter = _require2.runLinter;
-
-var _require3 = require('./options'),
-    getPrettierOptions = _require3.getPrettierOptions,
-    getPrettierEslintOptions = _require3.getPrettierEslintOptions,
-    shouldUseEslint = _require3.shouldUseEslint,
-    shouldDisplayErrors = _require3.shouldDisplayErrors;
-
-var EMBEDDED_JS_REGEX = /<script\b[^>]*>([\s\S]*?)(?=<\/script>)/gi;
-
-var displayError = function displayError(error) {
-  atom.notifications.addError('prettier-atom failed!', {
-    detail: error,
-    stack: error.stack,
-    dismissable: true
-  });
-};
-
-var handleError = function handleError(error) {
-  if (shouldDisplayErrors()) displayError(error);
-  return false;
-};
-
-var executePrettier = function executePrettier(editor, text) {
-  try {
-    var filePath = getCurrentFilePath(editor);
-    var prettierOptions = getPrettierOptions(editor);
-    if (shouldUseEslint()) {
-      return allowUnsafeNewFunction(function () {
-        return prettierEslint(_extends({}, getPrettierEslintOptions(), {
-          text: text,
-          filePath: filePath,
-          fallbackPrettierOptions: prettierOptions
-        }));
-      });
-    }
-
-    var prettier = getPrettier(filePath);
-
-    return prettier.format(text, prettierOptions);
-  } catch (error) {
-    return handleError(error);
-  }
-};
-
-var executePrettierOnBufferRange = function executePrettierOnBufferRange(editor, bufferRange) {
-  var cursorPositionPriorToFormat = editor.getCursorScreenPosition();
-  var textToTransform = editor.getTextInBufferRange(bufferRange);
-  var transformed = executePrettier(editor, textToTransform);
-
-  var isTextUnchanged = transformed === textToTransform;
-  if (!transformed || isTextUnchanged) return;
-
-  editor.setTextInBufferRange(bufferRange, transformed);
-  editor.setCursorScreenPosition(cursorPositionPriorToFormat);
-  runLinter(editor);
-};
-
-var executePrettierOnEmbeddedScripts = function executePrettierOnEmbeddedScripts(editor) {
-  return editor.backwardsScanInBufferRange(EMBEDDED_JS_REGEX, editor.getBuffer().getRange(), function (iter) {
-    var _iter$range = iter.range,
-        start = _iter$range.start,
-        end = _iter$range.end;
-
-    // Skip formatting when <script> and </script> on the same line
-
-    if (start.row === end.row) return;
-
-    // Create new range with start row advanced by 1,
-    // since we cannot use look-behind on variable-length starting
-    // <script ...> tag
-    var startModified = [start.row + 1, start.column];
-    var bufferRange = new iter.range.constructor(startModified, end);
-
-    executePrettierOnBufferRange(editor, bufferRange);
-  });
-};
-
-module.exports = {
-  executePrettierOnBufferRange: executePrettierOnBufferRange,
-  executePrettierOnEmbeddedScripts: executePrettierOnEmbeddedScripts
-};
\ No newline at end of file
diff --git a/dist/executePrettier/buildEditorConfigOptions.js b/dist/executePrettier/buildEditorConfigOptions.js
new file mode 100644
index 0000000..be8b19f
--- /dev/null
+++ b/dist/executePrettier/buildEditorConfigOptions.js
@@ -0,0 +1,41 @@
+'use strict';
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _ = require('lodash/fp');
+var editorconfig = require('editorconfig');
+
+var maxLineLengthIsNumber = _.flow(_.get('max_line_length'), _.isNumber);
+
+var indentStyleIsTab = _.conforms({ indent_style: _.isEqual('tab') });
+
+var indentSizeIsTab = _.conforms({ indent_size: _.isEqual('tab') });
+
+var tabWidthIsNumber = _.conforms({ tab_width: _.isNumber });
+
+var shouldUseTabWidth = _.overEvery([tabWidthIsNumber, _.overSome([indentSizeIsTab, indentStyleIsTab])]);
+
+var indentSizeIsNumber = _.conforms({ indent_size: _.isNumber });
+
+var indentStyleIsSpace = _.conforms({ indent_style: _.isEqual('space') });
+
+var getPrintWidth = _.cond([[maxLineLengthIsNumber, function (opts) {
+  return { printWidth: opts.max_line_length };
+}]]);
+
+var getTabWidth = _.cond([[shouldUseTabWidth, function (opts) {
+  return { tabWidth: opts.tab_width };
+}], [indentSizeIsNumber, function (opts) {
+  return { tabWidth: opts.indent_size };
+}]]);
+
+var getUseTabs = _.cond([[indentStyleIsTab, _.constant({ useTabs: true })], [indentStyleIsSpace, _.constant({ useTabs: false })]]);
+
+var mapEditorConfigOptions = function mapEditorConfigOptions() {
+  var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+  return _extends({}, getPrintWidth(opts), getTabWidth(opts), getUseTabs(opts));
+};
+
+var buildEditorConfigOptions = _.flow(editorconfig.parseSync, mapEditorConfigOptions);
+
+module.exports = buildEditorConfigOptions;
\ No newline at end of file
diff --git a/dist/executePrettier/buildPrettierEslintOptions.js b/dist/executePrettier/buildPrettierEslintOptions.js
new file mode 100644
index 0000000..88da4c8
--- /dev/null
+++ b/dist/executePrettier/buildPrettierEslintOptions.js
@@ -0,0 +1,22 @@
+'use strict';
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var buildPrettierOptions = require('./buildPrettierOptions');
+
+var _require = require('../atomInterface'),
+    getPrettierEslintOptions = _require.getPrettierEslintOptions;
+
+var _require2 = require('../editorInterface'),
+    getCurrentFilePath = _require2.getCurrentFilePath;
+
+var buildPrettierEslintOptions = function buildPrettierEslintOptions(editor, text) {
+  return _extends({
+    text: text
+  }, getPrettierEslintOptions(), {
+    fallbackPrettierOptions: buildPrettierOptions(editor),
+    filePath: getCurrentFilePath(editor)
+  });
+};
+
+module.exports = buildPrettierEslintOptions;
\ No newline at end of file
diff --git a/dist/executePrettier/buildPrettierOptions.js b/dist/executePrettier/buildPrettierOptions.js
new file mode 100644
index 0000000..2f4408a
--- /dev/null
+++ b/dist/executePrettier/buildPrettierOptions.js
@@ -0,0 +1,33 @@
+'use strict';
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _ = require('lodash/fp');
+
+var _require = require('../editorInterface'),
+    getCurrentFilePath = _require.getCurrentFilePath;
+
+var _require2 = require('../atomInterface'),
+    shouldUseEditorConfig = _require2.shouldUseEditorConfig,
+    getPrettierOptions = _require2.getPrettierOptions,
+    getAtomTabLength = _require2.getAtomTabLength;
+
+var buildEditorConfigOptions = require('./buildEditorConfigOptions');
+
+var isDefined = _.negate(_.isNil);
+
+var isAppropriateToBuildEditorConfigOptions = _.overEvery([isDefined, shouldUseEditorConfig]);
+
+var buildEditorConfigOptionsIfAppropriate = _.flow(getCurrentFilePath, _.cond([[isAppropriateToBuildEditorConfigOptions, buildEditorConfigOptions]]));
+
+var buildPrettierOptions = function buildPrettierOptions(editor) {
+  var optionsFromSettings = getPrettierOptions();
+
+  if (optionsFromSettings.tabWidth === 'auto') {
+    optionsFromSettings.tabWidth = getAtomTabLength(editor);
+  }
+
+  return _extends({}, optionsFromSettings, buildEditorConfigOptionsIfAppropriate(editor));
+};
+
+module.exports = buildPrettierOptions;
\ No newline at end of file
diff --git a/dist/executePrettier/executePrettierOnBufferRange.js b/dist/executePrettier/executePrettierOnBufferRange.js
new file mode 100644
index 0000000..a172884
--- /dev/null
+++ b/dist/executePrettier/executePrettierOnBufferRange.js
@@ -0,0 +1,48 @@
+'use strict';
+
+var prettierEslint = require('prettier-eslint');
+
+var _require = require('loophole'),
+    allowUnsafeNewFunction = _require.allowUnsafeNewFunction;
+
+var _require2 = require('../atomInterface'),
+    shouldUseEslint = _require2.shouldUseEslint,
+    runLinter = _require2.runLinter;
+
+var getPrettierInstance = require('./getPrettierInstance');
+var buildPrettierEslintOptions = require('./buildPrettierEslintOptions');
+var buildPrettierOptions = require('./buildPrettierOptions');
+var handleError = require('./handleError');
+
+var executePrettier = function executePrettier(editor, text) {
+  return getPrettierInstance(editor).format(text, buildPrettierOptions(editor));
+};
+
+var executePrettierEslint = function executePrettierEslint(editor, text) {
+  return allowUnsafeNewFunction(function () {
+    return prettierEslint(buildPrettierEslintOptions(editor, text));
+  });
+};
+
+var executePrettierOrPrettierEslint = function executePrettierOrPrettierEslint(editor, text) {
+  try {
+    return shouldUseEslint() ? executePrettierEslint(editor, text) : executePrettier(editor, text);
+  } catch (error) {
+    return handleError(error);
+  }
+};
+
+var executePrettierOnBufferRange = function executePrettierOnBufferRange(editor, bufferRange) {
+  var cursorPositionPriorToFormat = editor.getCursorScreenPosition();
+  var textToTransform = editor.getTextInBufferRange(bufferRange);
+  var transformed = executePrettierOrPrettierEslint(editor, textToTransform);
+
+  var isTextUnchanged = transformed === textToTransform;
+  if (!transformed || isTextUnchanged) return;
+
+  editor.setTextInBufferRange(bufferRange, transformed);
+  editor.setCursorScreenPosition(cursorPositionPriorToFormat);
+  runLinter(editor);
+};
+
+module.exports = executePrettierOnBufferRange;
\ No newline at end of file
diff --git a/dist/executePrettier/executePrettierOnEmbeddedScripts.js b/dist/executePrettier/executePrettierOnEmbeddedScripts.js
new file mode 100644
index 0000000..c03b35d
--- /dev/null
+++ b/dist/executePrettier/executePrettierOnEmbeddedScripts.js
@@ -0,0 +1,27 @@
+'use strict';
+
+var executePrettierOnBufferRange = require('./executePrettierOnBufferRange');
+
+var EMBEDDED_JS_REGEX = /<script\b[^>]*>([\s\S]*?)(?=<\/script>)/gi;
+
+var executePrettierOnEmbeddedScripts = function executePrettierOnEmbeddedScripts(editor) {
+  return editor.backwardsScanInBufferRange(EMBEDDED_JS_REGEX, editor.getBuffer().getRange(), function (iter) {
+    var _iter$range = iter.range,
+        start = _iter$range.start,
+        end = _iter$range.end;
+
+    // Skip formatting when <script> and </script> on the same line
+
+    if (start.row === end.row) return;
+
+    // Create new range with start row advanced by 1,
+    // since we cannot use look-behind on variable-length starting
+    // <script ...> tag
+    var startModified = [start.row + 1, start.column];
+    var bufferRange = new iter.range.constructor(startModified, end);
+
+    executePrettierOnBufferRange(editor, bufferRange);
+  });
+};
+
+module.exports = executePrettierOnEmbeddedScripts;
\ No newline at end of file
diff --git a/dist/executePrettier/getPrettierInstance.js b/dist/executePrettier/getPrettierInstance.js
new file mode 100644
index 0000000..f2a16f3
--- /dev/null
+++ b/dist/executePrettier/getPrettierInstance.js
@@ -0,0 +1,40 @@
+'use strict';
+
+var _ = require('lodash/fp');
+var path = require('path');
+var bundledPrettier = require('prettier');
+
+var _require = require('../editorInterface'),
+    getCurrentFilePath = _require.getCurrentFilePath;
+
+var _require2 = require('../helpers'),
+    findCachedFromFilePath = _require2.findCachedFromFilePath;
+
+var PRETTIER_INDEX_PATH = path.join('node_modules', 'prettier', 'index.js');
+
+var getLocalPrettierPath = function getLocalPrettierPath(filePath) {
+  return findCachedFromFilePath(filePath, PRETTIER_INDEX_PATH);
+};
+
+// const getLocalPrettierPath = (filePath: ?FilePath): ?FilePath => {
+//   if (!filePath) return null;
+//
+//   const dirPath = getDirFromFilePath(filePath);
+//
+//   return dirPath ? findCached(dirPath, PRETTIER_INDEX_PATH) : null;
+// };
+
+// charypar: This is currently the best way to use local prettier instance.
+// Using the CLI introduces a noticeable delay and there is currently no
+// way to use prettier as a long-running process for formatting files as needed
+//
+// See https://github.com/prettier/prettier/issues/918
+var requireWithFallbackToBundledPrettier = function requireWithFallbackToBundledPrettier(prettierPackagePath
+// $$FlowFixMe
+) {
+  return prettierPackagePath ? require(prettierPackagePath) : bundledPrettier;
+}; // eslint-disable-line
+
+var getPrettierInstance = _.flow(getCurrentFilePath, getLocalPrettierPath, requireWithFallbackToBundledPrettier);
+
+module.exports = getPrettierInstance;
\ No newline at end of file
diff --git a/dist/executePrettier/handleError.js b/dist/executePrettier/handleError.js
new file mode 100644
index 0000000..776dedd
--- /dev/null
+++ b/dist/executePrettier/handleError.js
@@ -0,0 +1,19 @@
+'use strict';
+
+var _ = require('lodash/fp');
+
+var _require = require('../atomInterface'),
+    shouldDisplayErrors = _require.shouldDisplayErrors,
+    addErrorNotification = _require.addErrorNotification;
+
+var displayError = function displayError(error) {
+  return addErrorNotification('prettier-atom failed!', {
+    detail: error.toString(),
+    stack: error.stack,
+    dismissable: true
+  });
+};
+
+var handleError = _.flow(_.cond([[shouldDisplayErrors, displayError]]), _.stubFalse);
+
+module.exports = handleError;
\ No newline at end of file
diff --git a/dist/executePrettier/index.js b/dist/executePrettier/index.js
new file mode 100644
index 0000000..76ca0bd
--- /dev/null
+++ b/dist/executePrettier/index.js
@@ -0,0 +1,9 @@
+'use strict';
+
+var executePrettierOnBufferRange = require('./executePrettierOnBufferRange');
+var executePrettierOnEmbeddedScripts = require('./executePrettierOnEmbeddedScripts.js');
+
+module.exports = {
+  executePrettierOnBufferRange: executePrettierOnBufferRange,
+  executePrettierOnEmbeddedScripts: executePrettierOnEmbeddedScripts
+};
\ No newline at end of file
diff --git a/dist/format.js b/dist/format.js
deleted file mode 100644
index 3a3fbc8..0000000
--- a/dist/format.js
+++ /dev/null
@@ -1,26 +0,0 @@
-'use strict';
-
-var _require = require('./executePrettier'),
-    executePrettierOnBufferRange = _require.executePrettierOnBufferRange,
-    executePrettierOnEmbeddedScripts = _require.executePrettierOnEmbeddedScripts;
-
-var _require2 = require('./helpers'),
-    isCurrentScopeEmbeddedScope = _require2.isCurrentScopeEmbeddedScope;
-
-var formatSelectedBufferRanges = function formatSelectedBufferRanges(editor) {
-  return editor.getSelectedBufferRanges().forEach(function (bufferRange) {
-    return executePrettierOnBufferRange(editor, bufferRange);
-  });
-};
-
-var format = function format(editor) {
-  if (editor.getSelectedText()) {
-    formatSelectedBufferRanges(editor);
-  } else if (isCurrentScopeEmbeddedScope(editor)) {
-    executePrettierOnEmbeddedScripts(editor);
-  } else {
-    executePrettierOnBufferRange(editor, editor.getBuffer().getRange());
-  }
-};
-
-module.exports = format;
\ No newline at end of file
diff --git a/dist/formatOnSave.js b/dist/formatOnSave.js
deleted file mode 100644
index f9d0c72..0000000
--- a/dist/formatOnSave.js
+++ /dev/null
@@ -1,44 +0,0 @@
-'use strict';
-
-var _require = require('./executePrettier'),
-    executePrettierOnBufferRange = _require.executePrettierOnBufferRange,
-    executePrettierOnEmbeddedScripts = _require.executePrettierOnEmbeddedScripts;
-
-var _require2 = require('./helpers'),
-    getCurrentFilePath = _require2.getCurrentFilePath,
-    isCurrentScopeEmbeddedScope = _require2.isCurrentScopeEmbeddedScope,
-    isFilePathEslintignored = _require2.isFilePathEslintignored,
-    isFilePathExcluded = _require2.isFilePathExcluded,
-    isInScope = _require2.isInScope,
-    isFilePathWhitelisted = _require2.isFilePathWhitelisted;
-
-var _require3 = require('./options'),
-    isFormatOnSaveEnabled = _require3.isFormatOnSaveEnabled,
-    isWhitelistProvided = _require3.isWhitelistProvided,
-    shouldRespectEslintignore = _require3.shouldRespectEslintignore;
-
-var formatOnSaveIfAppropriate = function formatOnSaveIfAppropriate(editor) {
-  if (!isFormatOnSaveEnabled()) return;
-
-  var filePath = getCurrentFilePath(editor);
-
-  if (!filePath) return;
-
-  if (filePath && isWhitelistProvided() && !isFilePathWhitelisted(filePath)) {
-    return;
-  }
-
-  if (filePath && !isFilePathWhitelisted(filePath)) {
-    if (!isInScope(editor)) return;
-    if (filePath && isFilePathExcluded(filePath)) return;
-  }
-  if (filePath && shouldRespectEslintignore() && isFilePathEslintignored(filePath)) return;
-
-  if (isCurrentScopeEmbeddedScope(editor)) {
-    executePrettierOnEmbeddedScripts(editor);
-  } else {
-    executePrettierOnBufferRange(editor, editor.getBuffer().getRange());
-  }
-};
-
-module.exports = formatOnSaveIfAppropriate;
\ No newline at end of file
diff --git a/dist/formatOnSave/index.js b/dist/formatOnSave/index.js
new file mode 100644
index 0000000..8e77340
--- /dev/null
+++ b/dist/formatOnSave/index.js
@@ -0,0 +1,17 @@
+'use strict';
+
+var _require = require('../editorInterface'),
+    isCurrentScopeEmbeddedScope = _require.isCurrentScopeEmbeddedScope,
+    getBufferRange = _require.getBufferRange;
+
+var _require2 = require('../executePrettier'),
+    executePrettierOnBufferRange = _require2.executePrettierOnBufferRange,
+    executePrettierOnEmbeddedScripts = _require2.executePrettierOnEmbeddedScripts;
+
+var shouldFormatOnSave = require('./shouldFormatOnSave');
+
+var formatOnSaveIfAppropriate = function formatOnSaveIfAppropriate(editor) {
+  return shouldFormatOnSave(editor) && (isCurrentScopeEmbeddedScope(editor) ? executePrettierOnEmbeddedScripts(editor) : executePrettierOnBufferRange(editor, getBufferRange(editor)));
+};
+
+module.exports = formatOnSaveIfAppropriate;
\ No newline at end of file
diff --git a/dist/formatOnSave/isFilePathEslintIgnored.js b/dist/formatOnSave/isFilePathEslintIgnored.js
new file mode 100644
index 0000000..f1c3650
--- /dev/null
+++ b/dist/formatOnSave/isFilePathEslintIgnored.js
@@ -0,0 +1,34 @@
+'use strict';
+
+var _ = require('lodash/fp');
+var path = require('path');
+var fs = require('fs');
+
+var _require = require('../helpers'),
+    findCachedFromFilePath = _require.findCachedFromFilePath,
+    getDirFromFilePath = _require.getDirFromFilePath,
+    someGlobsMatchFilePath = _require.someGlobsMatchFilePath;
+
+var LINE_SEPERATOR_REGEX = /(\r|\n|\r\n)/;
+
+var getNearestEslintignorePath = function getNearestEslintignorePath(filePath) {
+  return findCachedFromFilePath(filePath, '.eslintignore');
+};
+
+var safeRelativePath = _.curry(function (from, to) {
+  return !!from && !!to ? path.relative(from, to) : undefined;
+});
+
+var getFilePathRelativeToEslintignore = function getFilePathRelativeToEslintignore(filePath) {
+  return _.flow(getNearestEslintignorePath, getDirFromFilePath, safeRelativePath(_, filePath))(filePath);
+};
+
+var getLinesFromFilePath = function getLinesFromFilePath(filePath) {
+  return !!filePath && filePath.length > 0 ? fs.readFileSync(filePath, 'utf8').split(LINE_SEPERATOR_REGEX) : [];
+};
+
+var getIgnoredGlobsFromNearestEslintIgnore = _.flow(getNearestEslintignorePath, getLinesFromFilePath);
+
+var isFilePathEslintignored = _.flow(_.over([getIgnoredGlobsFromNearestEslintIgnore, getFilePathRelativeToEslintignore]), _.spread(someGlobsMatchFilePath));
+
+module.exports = isFilePathEslintignored;
\ No newline at end of file
diff --git a/dist/formatOnSave/shouldFormatOnSave.js b/dist/formatOnSave/shouldFormatOnSave.js
new file mode 100644
index 0000000..a1b138a
--- /dev/null
+++ b/dist/formatOnSave/shouldFormatOnSave.js
@@ -0,0 +1,42 @@
+'use strict';
+
+var _ = require('lodash/fp');
+
+var _require = require('../helpers'),
+    someGlobsMatchFilePath = _require.someGlobsMatchFilePath;
+
+var _require2 = require('../editorInterface'),
+    getCurrentFilePath = _require2.getCurrentFilePath,
+    getCurrentScope = _require2.getCurrentScope;
+
+var _require3 = require('../atomInterface'),
+    isFormatOnSaveEnabled = _require3.isFormatOnSaveEnabled,
+    getScopes = _require3.getScopes,
+    getExcludedGlobs = _require3.getExcludedGlobs,
+    getWhitelistedGlobs = _require3.getWhitelistedGlobs;
+
+var isFilePathEslintignored = require('./isFilePathEslintIgnored');
+
+var hasFilePath = function hasFilePath(editor) {
+  return !!getCurrentFilePath(editor);
+};
+
+var isInScope = function isInScope(editor) {
+  return getScopes().includes(getCurrentScope(editor));
+};
+
+var filePathDoesNotMatchBlacklistGlobs = _.flow(getCurrentFilePath, function (filePath) {
+  return _.negate(someGlobsMatchFilePath)(getExcludedGlobs(), filePath);
+});
+
+var noWhitelistGlobsPresent = _.flow(getWhitelistedGlobs, _.isEmpty);
+
+var isFilePathWhitelisted = _.flow(getCurrentFilePath, function (filePath) {
+  return someGlobsMatchFilePath(getWhitelistedGlobs(), filePath);
+});
+
+var isFilePathNotEslintignored = _.flow(getCurrentFilePath, _.negate(isFilePathEslintignored));
+
+var shouldFormatOnSave = _.overEvery([isFormatOnSaveEnabled, hasFilePath, isInScope, _.overSome([isFilePathWhitelisted, _.overEvery([noWhitelistGlobsPresent, filePathDoesNotMatchBlacklistGlobs])]), isFilePathNotEslintignored]);
+
+module.exports = shouldFormatOnSave;
\ No newline at end of file
diff --git a/dist/helpers.js b/dist/helpers.js
deleted file mode 100644
index e03106d..0000000
--- a/dist/helpers.js
+++ /dev/null
@@ -1,159 +0,0 @@
-'use strict';
-
-var _require = require('atom-linter'),
-    findCached = _require.findCached;
-
-var fs = require('fs');
-var ignore = require('ignore');
-var path = require('path');
-var bundledPrettier = require('prettier');
-var readPkg = require('read-pkg');
-
-// constants
-var LINE_SEPERATOR_REGEX = /(\r|\n|\r\n)/;
-var EMBEDDED_SCOPES = ['text.html.vue', 'text.html.basic'];
-var LINTER_LINT_COMMAND = 'linter:lint';
-
-// local helpers
-var getCurrentScope = function getCurrentScope(editor) {
-  return editor.getGrammar().scopeName;
-};
-
-// robwise: my apologies for this one, but I love function composition and want to use one that is Facebook
-// flow inferrable. See https://drboolean.gitbooks.io/mostly-adequate-guide/ch5.html
-var flow = function flow(func) {
-  for (var _len = arguments.length, funcs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
-    funcs[_key - 1] = arguments[_key];
-  }
-
-  return function () {
-    return funcs.length ? flow.apply(undefined, funcs)(func.apply(undefined, arguments)) : func.apply(undefined, arguments);
-  };
-};
-
-var getDirFromFilePath = function getDirFromFilePath(filePath) {
-  return path.parse(filePath).dir;
-};
-
-var getNearestEslintignorePath = function getNearestEslintignorePath(filePath) {
-  return findCached(getDirFromFilePath(filePath), '.eslintignore');
-};
-
-var getLocalPrettierPath = function getLocalPrettierPath(filePath) {
-  if (!filePath) return null;
-
-  var indexPath = path.join('node_modules', 'prettier', 'index.js');
-  var dirPath = getDirFromFilePath(filePath);
-
-  return dirPath ? findCached(dirPath, indexPath) : null;
-};
-
-var getPrettier = function getPrettier(filePath) {
-  var prettierPath = getLocalPrettierPath(filePath);
-
-  // charypar: This is currently the best way to use local prettier instance.
-  // Using the CLI introduces a noticeable delay and there is currently no
-  // way to use prettier as a long-running process for formatting files as needed
-  //
-  // See https://github.com/prettier/prettier/issues/918
-  //
-  // $FlowFixMe when possible, don't use dynamic require
-  return prettierPath ? require(prettierPath) : bundledPrettier; // eslint-disable-line
-};
-
-var getFilePathRelativeToEslintignore = function getFilePathRelativeToEslintignore(filePath) {
-  var nearestEslintignorePath = getNearestEslintignorePath(filePath);
-
-  if (!nearestEslintignorePath) return undefined;
-
-  var eslintignoreDir = getDirFromFilePath(nearestEslintignorePath);
-
-  return path.relative(eslintignoreDir, filePath);
-};
-
-var getLinesFromFilePath = function getLinesFromFilePath(filePath) {
-  return fs.readFileSync(filePath, 'utf8').split(LINE_SEPERATOR_REGEX);
-};
-
-var getIgnoredGlobsFromNearestEslintIgnore = flow(getNearestEslintignorePath, function (maybePath) {
-  return maybePath ? getLinesFromFilePath(maybePath) : [];
-});
-
-var someGlobsMatchFilePath = function someGlobsMatchFilePath(globs, filePath) {
-  return ignore().add(globs).ignores(filePath);
-};
-
-var isLinterLintCommandDefined = function isLinterLintCommandDefined(editor) {
-  return atom.commands.findCommands({ target: atom.views.getView(editor) }).some(function (command) {
-    return command.name === LINTER_LINT_COMMAND;
-  });
-};
-
-var getDepPath = function getDepPath(dep) {
-  return path.join(__dirname, '../node_modules', dep);
-};
-
-// public helpers
-var getConfigOption = function getConfigOption(key) {
-  return atom.config.get('prettier-atom.' + key);
-};
-
-var setConfigOption = function setConfigOption(key, value) {
-  return atom.config.set('prettier-atom.' + key, value);
-};
-
-var getCurrentFilePath = function getCurrentFilePath(editor) {
-  return editor.buffer.file ? editor.buffer.file.path : undefined;
-};
-
-var isInScope = function isInScope(editor) {
-  return getConfigOption('formatOnSaveOptions.scopes').includes(getCurrentScope(editor));
-};
-
-var isCurrentScopeEmbeddedScope = function isCurrentScopeEmbeddedScope(editor) {
-  return EMBEDDED_SCOPES.includes(getCurrentScope(editor));
-};
-
-var isFilePathEslintignored = function isFilePathEslintignored(filePath) {
-  var filePathRelativeToEslintignore = getFilePathRelativeToEslintignore(filePath);
-
-  if (!filePathRelativeToEslintignore) return false;
-
-  return someGlobsMatchFilePath(getIgnoredGlobsFromNearestEslintIgnore(filePath), filePathRelativeToEslintignore);
-};
-
-var isFilePathExcluded = function isFilePathExcluded(filePath) {
-  return someGlobsMatchFilePath(getConfigOption('formatOnSaveOptions.excludedGlobs'), filePath);
-};
-
-var isFilePathWhitelisted = function isFilePathWhitelisted(filePath) {
-  return someGlobsMatchFilePath(getConfigOption('formatOnSaveOptions.whitelistedGlobs'), filePath);
-};
-
-var runLinter = function runLinter(editor) {
-  return isLinterLintCommandDefined(editor) ? atom.commands.dispatch(atom.views.getView(editor), LINTER_LINT_COMMAND) : undefined;
-};
-
-var getDebugInfo = function getDebugInfo() {
-  return {
-    atomVersion: atom.getVersion(),
-    prettierAtomVersion: readPkg.sync().version,
-    prettierVersion: readPkg.sync(getDepPath('prettier')).version,
-    prettierESLintVersion: readPkg.sync(getDepPath('prettier-eslint')).version,
-    prettierAtomConfig: atom.config.get('prettier-atom')
-  };
-};
-
-module.exports = {
-  getConfigOption: getConfigOption,
-  setConfigOption: setConfigOption,
-  getCurrentFilePath: getCurrentFilePath,
-  getPrettier: getPrettier,
-  isInScope: isInScope,
-  isCurrentScopeEmbeddedScope: isCurrentScopeEmbeddedScope,
-  isFilePathEslintignored: isFilePathEslintignored,
-  isFilePathExcluded: isFilePathExcluded,
-  isFilePathWhitelisted: isFilePathWhitelisted,
-  runLinter: runLinter,
-  getDebugInfo: getDebugInfo
-};
\ No newline at end of file
diff --git a/dist/helpers/index.js b/dist/helpers/index.js
new file mode 100644
index 0000000..b7e76e4
--- /dev/null
+++ b/dist/helpers/index.js
@@ -0,0 +1,35 @@
+'use strict';
+
+var _ = require('lodash/fp');
+var path = require('path');
+var ignore = require('ignore');
+
+var _require = require('atom-linter'),
+    findCached = _require.findCached;
+
+var isPresent = function isPresent(target) {
+  return !!target && (typeof target.length === 'undefined' || target.length > 0);
+};
+
+var safePathParse = function safePathParse(filePath) {
+  return typeof filePath === 'string' && filePath.length > 0 ? path.parse(filePath) : undefined;
+};
+
+var getDirFromFilePath = _.flow(safePathParse, _.get('dir'));
+
+var someGlobsMatchFilePath = function someGlobsMatchFilePath(globs, filePath) {
+  return isPresent(filePath) && ignore().add(globs).ignores(filePath);
+};
+
+var findCachedFromFilePath = function findCachedFromFilePath(filePath, name) {
+  return _.flow(getDirFromFilePath, function (dirPath) {
+    return isPresent(dirPath) ? findCached(dirPath, name) : undefined;
+  })(filePath);
+};
+
+module.exports = {
+  isPresent: isPresent,
+  getDirFromFilePath: getDirFromFilePath,
+  someGlobsMatchFilePath: someGlobsMatchFilePath,
+  findCachedFromFilePath: findCachedFromFilePath
+};
\ No newline at end of file
diff --git a/dist/main.js b/dist/main.js
index e27c30b..253bb01 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -26,7 +26,7 @@ var tileElement = null;
 
 // HACK: lazy load most of the code we need for performance
 var lazyFormat = function lazyFormat() {
-  if (!format) format = require('./format'); // eslint-disable-line global-require
+  if (!format) format = require('./manualFormat'); // eslint-disable-line global-require
 
   var editor = atom.workspace.getActiveTextEditor();
   if (editor) format(editor);
@@ -61,7 +61,7 @@ var lazyDisplayDebugInfo = function lazyDisplayDebugInfo() {
 var lazyToggleFormatOnSave = function lazyToggleFormatOnSave() {
   if (!toggleFormatOnSave) {
     // eslint-disable-next-line global-require
-    toggleFormatOnSave = require('./toggleFormatOnSave');
+    toggleFormatOnSave = require('./atomInterface').toggleFormatOnSave;
   }
   toggleFormatOnSave();
 };
@@ -112,7 +112,7 @@ var activate = function activate() {
   }));
 
   // HACK: an Atom bug seems to be causing old configuration settings to linger for some users
-  //       https://github.com/jlongster/prettier-atom/issues/72
+  //       https://github.com/prettier/prettier-atom/issues/72
   atom.config.unset('prettier-atom.singleQuote');
   atom.config.unset('prettier-atom.trailingComma');
 };
diff --git a/dist/manualFormat/index.js b/dist/manualFormat/index.js
new file mode 100644
index 0000000..fa0e95a
--- /dev/null
+++ b/dist/manualFormat/index.js
@@ -0,0 +1,29 @@
+'use strict';
+
+var _ = require('lodash/fp');
+
+var _require = require('../executePrettier'),
+    executePrettierOnBufferRange = _require.executePrettierOnBufferRange,
+    executePrettierOnEmbeddedScripts = _require.executePrettierOnEmbeddedScripts;
+
+var _require2 = require('../editorInterface'),
+    getBufferRange = _require2.getBufferRange,
+    isCurrentScopeEmbeddedScope = _require2.isCurrentScopeEmbeddedScope;
+
+var hasSelectedText = function hasSelectedText(editor) {
+  return !!editor.getSelectedText();
+};
+
+var formatSelectedBufferRanges = function formatSelectedBufferRanges(editor) {
+  return editor.getSelectedBufferRanges().forEach(function (bufferRange) {
+    return executePrettierOnBufferRange(editor, bufferRange);
+  });
+};
+
+var executePrettierOnCurrentBufferRange = function executePrettierOnCurrentBufferRange(editor) {
+  return executePrettierOnBufferRange(editor, getBufferRange(editor));
+};
+
+var format = _.cond([[hasSelectedText, formatSelectedBufferRanges], [isCurrentScopeEmbeddedScope, executePrettierOnEmbeddedScripts], [_.stubTrue, executePrettierOnCurrentBufferRange]]);
+
+module.exports = format;
\ No newline at end of file
diff --git a/dist/options.js b/dist/options.js
deleted file mode 100644
index 8376b63..0000000
--- a/dist/options.js
+++ /dev/null
@@ -1,102 +0,0 @@
-'use strict';
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-var editorconfig = require('editorconfig');
-
-var _require = require('./helpers'),
-    getCurrentFilePath = _require.getCurrentFilePath,
-    getConfigOption = _require.getConfigOption;
-
-var getPrettierOption = function getPrettierOption(key) {
-  return getConfigOption('prettierOptions.' + key);
-};
-
-var getPrettierEslintOption = function getPrettierEslintOption(key) {
-  return getConfigOption('prettierEslintOptions.' + key);
-};
-
-var shouldUseEslint = function shouldUseEslint() {
-  return getConfigOption('useEslint');
-};
-
-var shouldDisplayErrors = function shouldDisplayErrors() {
-  return !getConfigOption('silenceErrors');
-};
-
-var getAtomTabLength = function getAtomTabLength(editor) {
-  return atom.config.get('editor.tabLength', { scope: editor.getLastCursor().getScopeDescriptor() });
-};
-
-var useAtomTabLengthIfAuto = function useAtomTabLengthIfAuto(editor, tabLength) {
-  return tabLength === 'auto' ? getAtomTabLength(editor) : Number(tabLength);
-};
-
-var mapEditorConfigOptions = function mapEditorConfigOptions(options) {
-  var indentStyle = options.indent_style,
-      tabWidth = options.tab_width,
-      printWidth = options.max_line_length;
-
-  return _extends({}, tabWidth ? { tabWidth: tabWidth } : null, printWidth ? { printWidth: printWidth } : null, indentStyle ? { useTabs: indentStyle === 'tab' } : null);
-};
-
-var getEditorConfigOptions = function getEditorConfigOptions(file) {
-  var options = editorconfig.parseSync(file);
-  return options ? mapEditorConfigOptions(options) : null;
-};
-
-var getPrettierOptions = function getPrettierOptions(editor) {
-  var filePath = getCurrentFilePath(editor);
-  var optionsFromSettings = {
-    printWidth: getPrettierOption('printWidth'),
-    tabWidth: useAtomTabLengthIfAuto(editor, getPrettierOption('tabWidth')),
-    parser: getPrettierOption('parser'),
-    singleQuote: getPrettierOption('singleQuote'),
-    trailingComma: getPrettierOption('trailingComma'),
-    bracketSpacing: getPrettierOption('bracketSpacing'),
-    semi: getPrettierOption('semi'),
-    useTabs: getPrettierOption('useTabs'),
-    jsxBracketSameLine: getPrettierOption('jsxBracketSameLine')
-  };
-  if (!filePath || !getConfigOption('useEditorConfig')) {
-    return optionsFromSettings;
-  }
-  return _extends({}, optionsFromSettings, getEditorConfigOptions(filePath));
-};
-
-var getPrettierEslintOptions = function getPrettierEslintOptions() {
-  return {
-    prettierLast: getPrettierEslintOption('prettierLast')
-  };
-};
-
-var isFormatOnSaveEnabled = function isFormatOnSaveEnabled() {
-  return getConfigOption('formatOnSaveOptions.enabled');
-};
-
-var shouldRespectEslintignore = function shouldRespectEslintignore() {
-  return getConfigOption('formatOnSaveOptions.respectEslintignore');
-};
-
-var isLinterEslintAutofixEnabled = function isLinterEslintAutofixEnabled() {
-  return atom.config.get('linter-eslint.fixOnSave');
-};
-
-var isWhitelistProvided = function isWhitelistProvided() {
-  return getConfigOption('formatOnSaveOptions.whitelistedGlobs').length > 0;
-};
-
-module.exports = {
-  getConfigOption: getConfigOption,
-  getEditorConfigOptions: getEditorConfigOptions,
-  getPrettierOption: getPrettierOption,
-  getPrettierOptions: getPrettierOptions,
-  getPrettierEslintOption: getPrettierEslintOption,
-  getPrettierEslintOptions: getPrettierEslintOptions,
-  shouldUseEslint: shouldUseEslint,
-  shouldDisplayErrors: shouldDisplayErrors,
-  isWhitelistProvided: isWhitelistProvided,
-  isFormatOnSaveEnabled: isFormatOnSaveEnabled,
-  isLinterEslintAutofixEnabled: isLinterEslintAutofixEnabled,
-  shouldRespectEslintignore: shouldRespectEslintignore
-};
\ No newline at end of file
diff --git a/dist/statusTile.js b/dist/statusTile.js
deleted file mode 100644
index e8fa86f..0000000
--- a/dist/statusTile.js
+++ /dev/null
@@ -1,48 +0,0 @@
-'use strict';
-
-var _require = require('./helpers'),
-    getConfigOption = _require.getConfigOption;
-
-var toggleFormatOnSave = require('./toggleFormatOnSave');
-
-var tooltip = null;
-
-var getFormatOnSaveStatus = function getFormatOnSaveStatus() {
-  return getConfigOption('formatOnSaveOptions.enabled') ? 'enabled' : 'disabled';
-};
-
-var createStatusTile = function createStatusTile() {
-  var element = document.createElement('div');
-  element.classList.add('prettier-atom-status-tile');
-  element.classList.add('inline-block');
-  element.dataset.formatOnSave = getFormatOnSaveStatus();
-  element.addEventListener('click', toggleFormatOnSave);
-
-  element.appendChild(document.createTextNode('Prettier'));
-
-  return element;
-};
-
-var disposeTooltip = function disposeTooltip() {
-  if (tooltip) {
-    tooltip.dispose();
-  }
-};
-
-var updateStatusTile = function updateStatusTile(disposable, element) {
-  // eslint-disable-next-line no-param-reassign
-  element.dataset.formatOnSave = getFormatOnSaveStatus();
-
-  disposeTooltip();
-
-  tooltip = atom.tooltips.add(element, { title: 'Format on Save: ' + getFormatOnSaveStatus() });
-  disposable.add(tooltip);
-
-  return tooltip;
-};
-
-module.exports = {
-  createStatusTile: createStatusTile,
-  updateStatusTile: updateStatusTile,
-  disposeTooltip: disposeTooltip
-};
\ No newline at end of file
diff --git a/dist/statusTile/getFormatOnSaveStatus.js b/dist/statusTile/getFormatOnSaveStatus.js
new file mode 100644
index 0000000..76d9bf1
--- /dev/null
+++ b/dist/statusTile/getFormatOnSaveStatus.js
@@ -0,0 +1,10 @@
+'use strict';
+
+var _require = require('../atomInterface'),
+    isFormatOnSaveEnabled = _require.isFormatOnSaveEnabled;
+
+var getFormatOnSaveStatus = function getFormatOnSaveStatus() {
+  return isFormatOnSaveEnabled() ? 'enabled' : 'disabled';
+};
+
+module.exports = getFormatOnSaveStatus;
\ No newline at end of file
diff --git a/dist/statusTile/index.js b/dist/statusTile/index.js
new file mode 100644
index 0000000..6bbfc73
--- /dev/null
+++ b/dist/statusTile/index.js
@@ -0,0 +1,13 @@
+'use strict';
+
+var createStatusTile = require('./createStatusTile');
+var updateStatusTile = require('./updateStatusTile');
+
+var _require = require('./tooltip'),
+    disposeTooltip = _require.disposeTooltip;
+
+module.exports = {
+  createStatusTile: createStatusTile,
+  updateStatusTile: updateStatusTile,
+  disposeTooltip: disposeTooltip
+};
\ No newline at end of file
diff --git a/dist/statusTile/tooltip.js b/dist/statusTile/tooltip.js
new file mode 100644
index 0000000..5faa3df
--- /dev/null
+++ b/dist/statusTile/tooltip.js
@@ -0,0 +1,16 @@
+"use strict";
+
+var tooltip = null;
+
+var disposeTooltip = function disposeTooltip() {
+  return tooltip && tooltip.dispose;
+};
+
+var setTooltip = function setTooltip(nextTooltip) {
+  tooltip = nextTooltip;
+};
+
+module.exports = {
+  disposeTooltip: disposeTooltip,
+  setTooltip: setTooltip
+};
\ No newline at end of file
diff --git a/dist/toggleFormatOnSave.js b/dist/toggleFormatOnSave.js
deleted file mode 100644
index c7c0c3b..0000000
--- a/dist/toggleFormatOnSave.js
+++ /dev/null
@@ -1,13 +0,0 @@
-'use strict';
-
-var _require = require('./helpers'),
-    getConfigOption = _require.getConfigOption,
-    setConfigOption = _require.setConfigOption;
-
-var FORMAT_ON_SAVE = 'formatOnSaveOptions.enabled';
-
-var toggleFormatOnSave = function toggleFormatOnSave() {
-  setConfigOption(FORMAT_ON_SAVE, !getConfigOption(FORMAT_ON_SAVE));
-};
-
-module.exports = toggleFormatOnSave;
\ No newline at end of file
diff --git a/dist/warnAboutLinterEslintFixOnSave.js b/dist/warnAboutLinterEslintFixOnSave/index.js
similarity index 59%
rename from dist/warnAboutLinterEslintFixOnSave.js
rename to dist/warnAboutLinterEslintFixOnSave/index.js
index a81b730..fe1ccc5 100644
--- a/dist/warnAboutLinterEslintFixOnSave.js
+++ b/dist/warnAboutLinterEslintFixOnSave/index.js
@@ -1,16 +1,15 @@
 'use strict';
 
-var _require = require('./options'),
+var _require = require('../atomInterface'),
     shouldUseEslint = _require.shouldUseEslint,
-    isLinterEslintAutofixEnabled = _require.isLinterEslintAutofixEnabled;
+    isLinterEslintAutofixEnabled = _require.isLinterEslintAutofixEnabled,
+    addWarningNotification = _require.addWarningNotification;
 
-var message = "prettier-atom: linter-eslint's `Fix on Save` feature is currently enabled. " + 'We recommend disabling this feature when using the ESlint integration from prettier-atom';
+var MESSAGE = "prettier-atom: linter-eslint's `Fix on Save` feature is currently enabled. " + 'We recommend disabling this feature when using the ESlint integration from prettier-atom';
+var OPTIONS = { dismissable: true };
 
-var options = { dismissable: true };
-
-// $FlowFixMe
 var displayWarning = function displayWarning() {
-  return atom.notifications.addWarning(message, options);
+  return addWarningNotification(MESSAGE, OPTIONS);
 };
 
 var warnAboutLinterEslintFixOnSave = function warnAboutLinterEslintFixOnSave() {
diff --git a/flow-typed/npm/all-contributors-cli_vx.x.x.js b/flow-typed/npm/all-contributors-cli_vx.x.x.js
index 960ad29..31fa310 100644
--- a/flow-typed/npm/all-contributors-cli_vx.x.x.js
+++ b/flow-typed/npm/all-contributors-cli_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 0d3ed9b513d5df315918b3ec13a96c0b
-// flow-typed version: <<STUB>>/all-contributors-cli_v^4.3.0/flow_v0.45.0
+// flow-typed signature: 1cf902cb8b1ef18278379c9489760e7b
+// flow-typed version: <<STUB>>/all-contributors-cli_v^4.3.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/atom-linter_vx.x.x.js b/flow-typed/npm/atom-linter_vx.x.x.js
index 5c34a82..73a9ea1 100644
--- a/flow-typed/npm/atom-linter_vx.x.x.js
+++ b/flow-typed/npm/atom-linter_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: d3d5b7268414381884fef30a1e6a17e1
-// flow-typed version: <<STUB>>/atom-linter_v^10.0.0/flow_v0.45.0
+// flow-typed signature: 1e1acbbb8240239b13aa3dfeb094e679
+// flow-typed version: <<STUB>>/atom-linter_v^10.0.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/babel-cli_vx.x.x.js b/flow-typed/npm/babel-cli_vx.x.x.js
index d2818a0..6298f44 100644
--- a/flow-typed/npm/babel-cli_vx.x.x.js
+++ b/flow-typed/npm/babel-cli_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 7dcc07278092a6c783d4d57546bdecca
-// flow-typed version: <<STUB>>/babel-cli_v^6.24.1/flow_v0.45.0
+// flow-typed signature: 8615cf6c39596ff7ba27c4a0377fe299
+// flow-typed version: <<STUB>>/babel-cli_v^6.24.1/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/babel-eslint_vx.x.x.js b/flow-typed/npm/babel-eslint_vx.x.x.js
index ee80170..cc4f6c8 100644
--- a/flow-typed/npm/babel-eslint_vx.x.x.js
+++ b/flow-typed/npm/babel-eslint_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 400b5eeff3ce041e148d7e2cae9c2efe
-// flow-typed version: <<STUB>>/babel-eslint_v^7.2.3/flow_v0.45.0
+// flow-typed signature: f3c3dd3c4bea413962ad456458ec0804
+// flow-typed version: <<STUB>>/babel-eslint_v^7.2.3/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/babel-jest_vx.x.x.js b/flow-typed/npm/babel-jest_vx.x.x.js
index c0436c0..c774f2f 100644
--- a/flow-typed/npm/babel-jest_vx.x.x.js
+++ b/flow-typed/npm/babel-jest_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 3ec4cd10ba0077d5e87e4b37a0183ba6
-// flow-typed version: <<STUB>>/babel-jest_v^19.0.0/flow_v0.45.0
+// flow-typed signature: 20074fa081acc2b719f9da852e3b236c
+// flow-typed version: <<STUB>>/babel-jest_v^20.0.1/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/babel-plugin-transform-flow-strip-types_vx.x.x.js b/flow-typed/npm/babel-plugin-transform-flow-strip-types_vx.x.x.js
index 6bb5ca7..2719a26 100644
--- a/flow-typed/npm/babel-plugin-transform-flow-strip-types_vx.x.x.js
+++ b/flow-typed/npm/babel-plugin-transform-flow-strip-types_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 32d4874973842e2f3cdc07adc9ee227e
-// flow-typed version: <<STUB>>/babel-plugin-transform-flow-strip-types_v^6.22.0/flow_v0.45.0
+// flow-typed signature: fa3129952614c64f55776c233b5f3b10
+// flow-typed version: <<STUB>>/babel-plugin-transform-flow-strip-types_v^6.22.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/babel-preset-env_vx.x.x.js b/flow-typed/npm/babel-preset-env_vx.x.x.js
index fb25f21..ca1a90b 100644
--- a/flow-typed/npm/babel-preset-env_vx.x.x.js
+++ b/flow-typed/npm/babel-preset-env_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: adbecc6086da14b2aee993ba427ca523
-// flow-typed version: <<STUB>>/babel-preset-env_v^1.4.0/flow_v0.45.0
+// flow-typed signature: f12875965303032e2f229e46f5734514
+// flow-typed version: <<STUB>>/babel-preset-env_v^1.4.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/babel-preset-stage-2_vx.x.x.js b/flow-typed/npm/babel-preset-stage-2_vx.x.x.js
index 3ade0f7..7d5af6f 100644
--- a/flow-typed/npm/babel-preset-stage-2_vx.x.x.js
+++ b/flow-typed/npm/babel-preset-stage-2_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 10c5d726cb691e68af0f599655484ed4
-// flow-typed version: <<STUB>>/babel-preset-stage-2_v^6.24.1/flow_v0.45.0
+// flow-typed signature: bd74ec965239b139d1c3401b8d43e236
+// flow-typed version: <<STUB>>/babel-preset-stage-2_v^6.24.1/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/codecov_vx.x.x.js b/flow-typed/npm/codecov_vx.x.x.js
index aa9765c..742a43a 100644
--- a/flow-typed/npm/codecov_vx.x.x.js
+++ b/flow-typed/npm/codecov_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 1c9ef5e9540f311f2530a1fefe0d9b25
-// flow-typed version: <<STUB>>/codecov_v^2.1.0/flow_v0.45.0
+// flow-typed signature: dd6af16a725a56ea9f01c127e1ed1e1c
+// flow-typed version: <<STUB>>/codecov_v^2.2.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
@@ -30,6 +30,10 @@ declare module 'codecov/lib/detect' {
   declare module.exports: any;
 }
 
+declare module 'codecov/lib/git' {
+  declare module.exports: any;
+}
+
 declare module 'codecov/lib/offline' {
   declare module.exports: any;
 }
@@ -90,6 +94,10 @@ declare module 'codecov/test/detect' {
   declare module.exports: any;
 }
 
+declare module 'codecov/test/git' {
+  declare module.exports: any;
+}
+
 declare module 'codecov/test/index' {
   declare module.exports: any;
 }
@@ -167,6 +175,9 @@ declare module 'codecov/lib/codecov.js' {
 declare module 'codecov/lib/detect.js' {
   declare module.exports: $Exports<'codecov/lib/detect'>;
 }
+declare module 'codecov/lib/git.js' {
+  declare module.exports: $Exports<'codecov/lib/git'>;
+}
 declare module 'codecov/lib/offline.js' {
   declare module.exports: $Exports<'codecov/lib/offline'>;
 }
@@ -212,6 +223,9 @@ declare module 'codecov/lib/services/wercker.js' {
 declare module 'codecov/test/detect.js' {
   declare module.exports: $Exports<'codecov/test/detect'>;
 }
+declare module 'codecov/test/git.js' {
+  declare module.exports: $Exports<'codecov/test/git'>;
+}
 declare module 'codecov/test/index.js' {
   declare module.exports: $Exports<'codecov/test/index'>;
 }
diff --git a/flow-typed/npm/commitizen_vx.x.x.js b/flow-typed/npm/commitizen_vx.x.x.js
index bb90f8d..343c23f 100644
--- a/flow-typed/npm/commitizen_vx.x.x.js
+++ b/flow-typed/npm/commitizen_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 4f19934b47e40c099632234849088084
-// flow-typed version: <<STUB>>/commitizen_v^2.9.6/flow_v0.45.0
+// flow-typed signature: 6c0a8ca4057e210744d42f99625bb38d
+// flow-typed version: <<STUB>>/commitizen_v^2.9.6/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/cz-conventional-changelog_vx.x.x.js b/flow-typed/npm/cz-conventional-changelog_vx.x.x.js
index 71faf46..fc54259 100644
--- a/flow-typed/npm/cz-conventional-changelog_vx.x.x.js
+++ b/flow-typed/npm/cz-conventional-changelog_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: cbba2ec18f2cd69acff7e575ac139a90
-// flow-typed version: <<STUB>>/cz-conventional-changelog_v^2.0.0/flow_v0.45.0
+// flow-typed signature: 3d1b5fb71fb040f7941a6d3864ae80c9
+// flow-typed version: <<STUB>>/cz-conventional-changelog_v^2.0.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/editorconfig_vx.x.x.js b/flow-typed/npm/editorconfig_vx.x.x.js
index f36acd4..06c4408 100644
--- a/flow-typed/npm/editorconfig_vx.x.x.js
+++ b/flow-typed/npm/editorconfig_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 51bc365c01ccba1a7e27632be057b34f
-// flow-typed version: <<STUB>>/editorconfig_v^0.13.2/flow_v0.45.0
+// flow-typed signature: b4df39241bab6cb645ea65e8ac23e85c
+// flow-typed version: <<STUB>>/editorconfig_v^0.13.2/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/eslint-config-airbnb-base_vx.x.x.js b/flow-typed/npm/eslint-config-airbnb-base_vx.x.x.js
index 65c2c97..bc3fd67 100644
--- a/flow-typed/npm/eslint-config-airbnb-base_vx.x.x.js
+++ b/flow-typed/npm/eslint-config-airbnb-base_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 81d6aabf129611c0ebcbe3c826556094
-// flow-typed version: <<STUB>>/eslint-config-airbnb-base_v^11.1.3/flow_v0.45.0
+// flow-typed signature: 0cc11fa6f77759e261fb83e62233633d
+// flow-typed version: <<STUB>>/eslint-config-airbnb-base_v^11.1.3/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/eslint-plugin-flowtype_vx.x.x.js b/flow-typed/npm/eslint-plugin-flowtype_vx.x.x.js
index 431b554..4363ea0 100644
--- a/flow-typed/npm/eslint-plugin-flowtype_vx.x.x.js
+++ b/flow-typed/npm/eslint-plugin-flowtype_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 59e233ea7b80f5120a0e46bf350dc45d
-// flow-typed version: <<STUB>>/eslint-plugin-flowtype_v^2.32.1/flow_v0.45.0
+// flow-typed signature: 63a651b3d4f7baab56bae360bb0b2a70
+// flow-typed version: <<STUB>>/eslint-plugin-flowtype_v^2.32.1/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/eslint-plugin-import_vx.x.x.js b/flow-typed/npm/eslint-plugin-import_vx.x.x.js
index c594f2a..9c6beab 100644
--- a/flow-typed/npm/eslint-plugin-import_vx.x.x.js
+++ b/flow-typed/npm/eslint-plugin-import_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 771f77e95193004b6da3e62d8c296e44
-// flow-typed version: <<STUB>>/eslint-plugin-import_v^2.2.0/flow_v0.45.0
+// flow-typed signature: fdc069572f193f76e11f3ff6be5fde49
+// flow-typed version: <<STUB>>/eslint-plugin-import_v^2.2.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/eslint-plugin-jest_vx.x.x.js b/flow-typed/npm/eslint-plugin-jest_vx.x.x.js
index 80af702..2a83d31 100644
--- a/flow-typed/npm/eslint-plugin-jest_vx.x.x.js
+++ b/flow-typed/npm/eslint-plugin-jest_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 0645c43bd22e63156eb284bb11fcdf79
-// flow-typed version: <<STUB>>/eslint-plugin-jest_v^19.0.1/flow_v0.45.0
+// flow-typed signature: a2cd0c5e7ead9ce7aa9a6e24ba263573
+// flow-typed version: <<STUB>>/eslint-plugin-jest_v^20.0.1/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
@@ -42,6 +42,10 @@ declare module 'eslint-plugin-jest/build/rules/types' {
   declare module.exports: any;
 }
 
+declare module 'eslint-plugin-jest/build/rules/valid-expect' {
+  declare module.exports: any;
+}
+
 // Filename aliases
 declare module 'eslint-plugin-jest/build/index.js' {
   declare module.exports: $Exports<'eslint-plugin-jest/build/index'>;
@@ -58,3 +62,6 @@ declare module 'eslint-plugin-jest/build/rules/no-identical-title.js' {
 declare module 'eslint-plugin-jest/build/rules/types.js' {
   declare module.exports: $Exports<'eslint-plugin-jest/build/rules/types'>;
 }
+declare module 'eslint-plugin-jest/build/rules/valid-expect.js' {
+  declare module.exports: $Exports<'eslint-plugin-jest/build/rules/valid-expect'>;
+}
diff --git a/flow-typed/npm/eslint_vx.x.x.js b/flow-typed/npm/eslint_vx.x.x.js
index 274b6d5..753d677 100644
--- a/flow-typed/npm/eslint_vx.x.x.js
+++ b/flow-typed/npm/eslint_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 17a62aab0a546e8ae7bd20789aa0b4fb
-// flow-typed version: <<STUB>>/eslint_v^3.19.0/flow_v0.45.0
+// flow-typed signature: 9354e7847cf55f354b2ca85525e8513a
+// flow-typed version: <<STUB>>/eslint_v^3.19.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/flow-typed_vx.x.x.js b/flow-typed/npm/flow-typed_vx.x.x.js
index 8335190..80d4d09 100644
--- a/flow-typed/npm/flow-typed_vx.x.x.js
+++ b/flow-typed/npm/flow-typed_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 4c649379826caaac45f9cdb5c4c4569a
-// flow-typed version: <<STUB>>/flow-typed_v^2.1.2/flow_v0.45.0
+// flow-typed signature: 8898603ed592b58f9cb89fd30a371a09
+// flow-typed version: <<STUB>>/flow-typed_v^2.1.2/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/husky_vx.x.x.js b/flow-typed/npm/husky_vx.x.x.js
index 6f7e414..2f2b8cf 100644
--- a/flow-typed/npm/husky_vx.x.x.js
+++ b/flow-typed/npm/husky_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 666cc265d22fa33634591c45fe2a1dfd
-// flow-typed version: <<STUB>>/husky_v^0.13.3/flow_v0.45.0
+// flow-typed signature: 10d49e849df9e54bb7e1f92e14967412
+// flow-typed version: <<STUB>>/husky_v^0.13.3/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/ignore_vx.x.x.js b/flow-typed/npm/ignore_vx.x.x.js
index 2c10434..1f3edf1 100644
--- a/flow-typed/npm/ignore_vx.x.x.js
+++ b/flow-typed/npm/ignore_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 2738cdacd4dc418b2c4aa8dea5c2d4fb
-// flow-typed version: <<STUB>>/ignore_v^3.2.7/flow_v0.45.0
+// flow-typed signature: 8af680d9fcb80e8d237cd1d2cc0ad080
+// flow-typed version: <<STUB>>/ignore_v^3.3.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/jest-cli_vx.x.x.js b/flow-typed/npm/jest-cli_vx.x.x.js
index ef36faa..0a01483 100644
--- a/flow-typed/npm/jest-cli_vx.x.x.js
+++ b/flow-typed/npm/jest-cli_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 5773b44d957908b4555e47cc94cce785
-// flow-typed version: <<STUB>>/jest-cli_v^19.0.2/flow_v0.45.0
+// flow-typed signature: bb1040fa213b918469f24813774c746e
+// flow-typed version: <<STUB>>/jest-cli_v^20.0.1/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
@@ -38,6 +38,10 @@ declare module 'jest-cli/build/cli/index' {
   declare module.exports: any;
 }
 
+declare module 'jest-cli/build/cli/runCLI' {
+  declare module.exports: any;
+}
+
 declare module 'jest-cli/build/constants' {
   declare module.exports: any;
 }
@@ -54,15 +58,11 @@ declare module 'jest-cli/build/lib/BufferedConsole' {
   declare module.exports: any;
 }
 
-declare module 'jest-cli/build/lib/buildTestPathPatternInfo' {
-  declare module.exports: any;
-}
-
 declare module 'jest-cli/build/lib/colorize' {
   declare module.exports: any;
 }
 
-declare module 'jest-cli/build/lib/createHasteContext' {
+declare module 'jest-cli/build/lib/createContext' {
   declare module.exports: any;
 }
 
@@ -74,6 +74,14 @@ declare module 'jest-cli/build/lib/getMaxWorkers' {
   declare module.exports: any;
 }
 
+declare module 'jest-cli/build/lib/getTestPathPattern' {
+  declare module.exports: any;
+}
+
+declare module 'jest-cli/build/lib/handleDeprecationWarnings' {
+  declare module.exports: any;
+}
+
 declare module 'jest-cli/build/lib/highlight' {
   declare module.exports: any;
 }
@@ -86,7 +94,7 @@ declare module 'jest-cli/build/lib/logDebugMessages' {
   declare module.exports: any;
 }
 
-declare module 'jest-cli/build/lib/promisify' {
+declare module 'jest-cli/build/lib/patternModeHelpers' {
   declare module.exports: any;
 }
 
@@ -94,7 +102,7 @@ declare module 'jest-cli/build/lib/Prompt' {
   declare module.exports: any;
 }
 
-declare module 'jest-cli/build/lib/setState' {
+declare module 'jest-cli/build/lib/scrollList' {
   declare module.exports: any;
 }
 
@@ -102,10 +110,26 @@ declare module 'jest-cli/build/lib/terminalUtils' {
   declare module.exports: any;
 }
 
+declare module 'jest-cli/build/lib/updateArgv' {
+  declare module.exports: any;
+}
+
+declare module 'jest-cli/build/lib/validatePattern' {
+  declare module.exports: any;
+}
+
+declare module 'jest-cli/build/PatternPrompt' {
+  declare module.exports: any;
+}
+
 declare module 'jest-cli/build/preRunMessage' {
   declare module.exports: any;
 }
 
+declare module 'jest-cli/build/ReporterDispatcher' {
+  declare module.exports: any;
+}
+
 declare module 'jest-cli/build/reporters/BaseReporter' {
   declare module.exports: any;
 }
@@ -114,6 +138,10 @@ declare module 'jest-cli/build/reporters/CoverageReporter' {
   declare module.exports: any;
 }
 
+declare module 'jest-cli/build/reporters/CoverageWorker' {
+  declare module.exports: any;
+}
+
 declare module 'jest-cli/build/reporters/DefaultReporter' {
   declare module.exports: any;
 }
@@ -170,6 +198,10 @@ declare module 'jest-cli/build/TestRunner' {
   declare module.exports: any;
 }
 
+declare module 'jest-cli/build/TestSequencer' {
+  declare module.exports: any;
+}
+
 declare module 'jest-cli/build/TestWatcher' {
   declare module.exports: any;
 }
@@ -195,6 +227,9 @@ declare module 'jest-cli/build/cli/getJest.js' {
 declare module 'jest-cli/build/cli/index.js' {
   declare module.exports: $Exports<'jest-cli/build/cli/index'>;
 }
+declare module 'jest-cli/build/cli/runCLI.js' {
+  declare module.exports: $Exports<'jest-cli/build/cli/runCLI'>;
+}
 declare module 'jest-cli/build/constants.js' {
   declare module.exports: $Exports<'jest-cli/build/constants'>;
 }
@@ -207,14 +242,11 @@ declare module 'jest-cli/build/jest.js' {
 declare module 'jest-cli/build/lib/BufferedConsole.js' {
   declare module.exports: $Exports<'jest-cli/build/lib/BufferedConsole'>;
 }
-declare module 'jest-cli/build/lib/buildTestPathPatternInfo.js' {
-  declare module.exports: $Exports<'jest-cli/build/lib/buildTestPathPatternInfo'>;
-}
 declare module 'jest-cli/build/lib/colorize.js' {
   declare module.exports: $Exports<'jest-cli/build/lib/colorize'>;
 }
-declare module 'jest-cli/build/lib/createHasteContext.js' {
-  declare module.exports: $Exports<'jest-cli/build/lib/createHasteContext'>;
+declare module 'jest-cli/build/lib/createContext.js' {
+  declare module.exports: $Exports<'jest-cli/build/lib/createContext'>;
 }
 declare module 'jest-cli/build/lib/formatTestNameByPattern.js' {
   declare module.exports: $Exports<'jest-cli/build/lib/formatTestNameByPattern'>;
@@ -222,6 +254,12 @@ declare module 'jest-cli/build/lib/formatTestNameByPattern.js' {
 declare module 'jest-cli/build/lib/getMaxWorkers.js' {
   declare module.exports: $Exports<'jest-cli/build/lib/getMaxWorkers'>;
 }
+declare module 'jest-cli/build/lib/getTestPathPattern.js' {
+  declare module.exports: $Exports<'jest-cli/build/lib/getTestPathPattern'>;
+}
+declare module 'jest-cli/build/lib/handleDeprecationWarnings.js' {
+  declare module.exports: $Exports<'jest-cli/build/lib/handleDeprecationWarnings'>;
+}
 declare module 'jest-cli/build/lib/highlight.js' {
   declare module.exports: $Exports<'jest-cli/build/lib/highlight'>;
 }
@@ -231,27 +269,42 @@ declare module 'jest-cli/build/lib/isValidPath.js' {
 declare module 'jest-cli/build/lib/logDebugMessages.js' {
   declare module.exports: $Exports<'jest-cli/build/lib/logDebugMessages'>;
 }
-declare module 'jest-cli/build/lib/promisify.js' {
-  declare module.exports: $Exports<'jest-cli/build/lib/promisify'>;
+declare module 'jest-cli/build/lib/patternModeHelpers.js' {
+  declare module.exports: $Exports<'jest-cli/build/lib/patternModeHelpers'>;
 }
 declare module 'jest-cli/build/lib/Prompt.js' {
   declare module.exports: $Exports<'jest-cli/build/lib/Prompt'>;
 }
-declare module 'jest-cli/build/lib/setState.js' {
-  declare module.exports: $Exports<'jest-cli/build/lib/setState'>;
+declare module 'jest-cli/build/lib/scrollList.js' {
+  declare module.exports: $Exports<'jest-cli/build/lib/scrollList'>;
 }
 declare module 'jest-cli/build/lib/terminalUtils.js' {
   declare module.exports: $Exports<'jest-cli/build/lib/terminalUtils'>;
 }
+declare module 'jest-cli/build/lib/updateArgv.js' {
+  declare module.exports: $Exports<'jest-cli/build/lib/updateArgv'>;
+}
+declare module 'jest-cli/build/lib/validatePattern.js' {
+  declare module.exports: $Exports<'jest-cli/build/lib/validatePattern'>;
+}
+declare module 'jest-cli/build/PatternPrompt.js' {
+  declare module.exports: $Exports<'jest-cli/build/PatternPrompt'>;
+}
 declare module 'jest-cli/build/preRunMessage.js' {
   declare module.exports: $Exports<'jest-cli/build/preRunMessage'>;
 }
+declare module 'jest-cli/build/ReporterDispatcher.js' {
+  declare module.exports: $Exports<'jest-cli/build/ReporterDispatcher'>;
+}
 declare module 'jest-cli/build/reporters/BaseReporter.js' {
   declare module.exports: $Exports<'jest-cli/build/reporters/BaseReporter'>;
 }
 declare module 'jest-cli/build/reporters/CoverageReporter.js' {
   declare module.exports: $Exports<'jest-cli/build/reporters/CoverageReporter'>;
 }
+declare module 'jest-cli/build/reporters/CoverageWorker.js' {
+  declare module.exports: $Exports<'jest-cli/build/reporters/CoverageWorker'>;
+}
 declare module 'jest-cli/build/reporters/DefaultReporter.js' {
   declare module.exports: $Exports<'jest-cli/build/reporters/DefaultReporter'>;
 }
@@ -294,6 +347,9 @@ declare module 'jest-cli/build/TestPathPatternPrompt.js' {
 declare module 'jest-cli/build/TestRunner.js' {
   declare module.exports: $Exports<'jest-cli/build/TestRunner'>;
 }
+declare module 'jest-cli/build/TestSequencer.js' {
+  declare module.exports: $Exports<'jest-cli/build/TestSequencer'>;
+}
 declare module 'jest-cli/build/TestWatcher.js' {
   declare module.exports: $Exports<'jest-cli/build/TestWatcher'>;
 }
diff --git a/flow-typed/npm/lodash_v4.x.x.js b/flow-typed/npm/lodash_v4.x.x.js
new file mode 100644
index 0000000..ce14747
--- /dev/null
+++ b/flow-typed/npm/lodash_v4.x.x.js
@@ -0,0 +1,514 @@
+// flow-typed signature: a7d253bb465906a2d746884b23c74257
+// flow-typed version: df11ef698b/lodash_v4.x.x/flow_>=v0.38.x
+
+declare module 'lodash' {
+  declare type TemplateSettings = {
+    escape?: RegExp,
+    evaluate?: RegExp,
+    imports?: Object,
+    interpolate?: RegExp,
+    variable?: string,
+  };
+
+  declare type TruncateOptions = {
+    length?: number,
+    omission?: string,
+    separator?: RegExp|string,
+  };
+
+  declare type DebounceOptions = {
+    leading?: bool,
+    maxWait?: number,
+    trailing?: bool,
+  };
+
+  declare type ThrottleOptions = {
+    leading?: bool,
+    trailing?: bool,
+  };
+
+  declare type NestedArray<T> = Array<Array<T>>;
+
+  declare type matchesIterateeShorthand = Object;
+  declare type matchesPropertyIterateeShorthand = [string, any];
+  declare type propertyIterateeShorthand = string;
+
+  declare type OPredicate<A, O> =
+    | ((value: A, key: string, object: O) => any)
+    | matchesIterateeShorthand
+    | matchesPropertyIterateeShorthand
+    | propertyIterateeShorthand;
+
+  declare type OIterateeWithResult<V, O, R> = Object|string|((value: V, key: string, object: O) => R);
+  declare type OIteratee<O> = OIterateeWithResult<any, O, any>;
+  declare type OFlatMapIteratee<T, U> = OIterateeWithResult<any, T, Array<U>>;
+
+  declare type Predicate<T> =
+    | ((value: T, index: number, array: Array<T>) => any)
+    | matchesIterateeShorthand
+    | matchesPropertyIterateeShorthand
+    | propertyIterateeShorthand;
+
+  declare type _ValueOnlyIteratee<T> = (value: T) => mixed;
+  declare type ValueOnlyIteratee<T> = _ValueOnlyIteratee<T>|string;
+  declare type _Iteratee<T> = (item: T, index: number, array: ?Array<T>) => mixed;
+  declare type Iteratee<T> = _Iteratee<T>|Object|string;
+  declare type FlatMapIteratee<T, U> = ((item: T, index: number, array: ?Array<T>) => Array<U>)|Object|string;
+  declare type Comparator<T> = (item: T, item2: T) => bool;
+
+  declare type MapIterator<T,U> =
+    | ((item: T, index: number, array: Array<T>) => U)
+    | propertyIterateeShorthand;
+
+  declare type OMapIterator<T,O,U> =
+    | ((item: T, key: string, object: O) => U)
+    | propertyIterateeShorthand;
+
+  declare class Lodash {
+    // Array
+    chunk<T>(array: ?Array<T>, size?: number): Array<Array<T>>;
+    compact<T,N:?T>(array: Array<N>): Array<T>;
+    concat<T>(base: Array<T>, ...elements: Array<any>): Array<T|any>;
+    difference<T>(array: ?Array<T>, values?: Array<T>): Array<T>;
+    differenceBy<T>(array: ?Array<T>, values: Array<T>, iteratee: ValueOnlyIteratee<T>): T[];
+    differenceWith<T>(array: T[], values: T[], comparator?: Comparator<T>): T[];
+    drop<T>(array: ?Array<T>, n?: number): Array<T>;
+    dropRight<T>(array: ?Array<T>, n?: number): Array<T>;
+    dropRightWhile<T>(array: ?Array<T>, predicate?: Predicate<T>): Array<T>;
+    dropWhile<T>(array: ?Array<T>, predicate?: Predicate<T>): Array<T>;
+    fill<T, U>(array: ?Array<T>, value: U, start?: number, end?: number): Array<T|U>;
+    findIndex<T>(array: ?Array<T>, predicate?: Predicate<T>): number;
+    findLastIndex<T>(array: ?Array<T>, predicate?: Predicate<T>): number;
+    // alias of _.head
+    first<T>(array: ?Array<T>): T;
+    flatten<T,X>(array: Array<Array<T>|X>): Array<T|X>;
+    flattenDeep<T>(array: any[]): Array<T>;
+    flattenDepth(array: any[], depth?: number): any[];
+    fromPairs<T>(pairs: Array<T>): Object;
+    head<T>(array: ?Array<T>): T;
+    indexOf<T>(array: ?Array<T>, value: T, fromIndex?: number): number;
+    initial<T>(array: ?Array<T>): Array<T>;
+    intersection<T>(...arrays: Array<Array<T>>): Array<T>;
+    //Workaround until (...parameter: T, parameter2: U) works
+    intersectionBy<T>(a1: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    intersectionBy<T>(a1: Array<T>, a2: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    intersectionBy<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    intersectionBy<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, a4: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    //Workaround until (...parameter: T, parameter2: U) works
+    intersectionWith<T>(a1: Array<T>, comparator: Comparator<T>): Array<T>;
+    intersectionWith<T>(a1: Array<T>, a2: Array<T>, comparator: Comparator<T>): Array<T>;
+    intersectionWith<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, comparator: Comparator<T>): Array<T>;
+    intersectionWith<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, a4: Array<T>, comparator: Comparator<T>): Array<T>;
+    join<T>(array: ?Array<T>, separator?: string): string;
+    last<T>(array: ?Array<T>): T;
+    lastIndexOf<T>(array: ?Array<T>, value: T, fromIndex?: number): number;
+    nth<T>(array: T[], n?: number): T;
+    pull<T>(array: ?Array<T>, ...values?: Array<T>): Array<T>;
+    pullAll<T>(array: ?Array<T>, values: Array<T>): Array<T>;
+    pullAllBy<T>(array: ?Array<T>, values: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    pullAllWith<T>(array?: T[], values: T[], comparator?: Function): T[];
+    pullAt<T>(array: ?Array<T>, ...indexed?: Array<number>): Array<T>;
+    pullAt<T>(array: ?Array<T>, indexed?: Array<number>): Array<T>;
+    remove<T>(array: ?Array<T>, predicate?: Predicate<T>): Array<T>;
+    reverse<T>(array: ?Array<T>): Array<T>;
+    slice<T>(array: ?Array<T>, start?: number, end?: number): Array<T>;
+    sortedIndex<T>(array: ?Array<T>, value: T): number;
+    sortedIndexBy<T>(array: ?Array<T>, value: T, iteratee?: ValueOnlyIteratee<T>): number;
+    sortedIndexOf<T>(array: ?Array<T>, value: T): number;
+    sortedLastIndex<T>(array: ?Array<T>, value: T): number;
+    sortedLastIndexBy<T>(array: ?Array<T>, value: T, iteratee?: ValueOnlyIteratee<T>): number;
+    sortedLastIndexOf<T>(array: ?Array<T>, value: T): number;
+    sortedUniq<T>(array: ?Array<T>): Array<T>;
+    sortedUniqBy<T>(array: ?Array<T>, iteratee?: (value: T) => mixed): Array<T>;
+    tail<T>(array: ?Array<T>): Array<T>;
+    take<T>(array: ?Array<T>, n?: number): Array<T>;
+    takeRight<T>(array: ?Array<T>, n?: number): Array<T>;
+    takeRightWhile<T>(array: ?Array<T>, predicate?: Predicate<T>): Array<T>;
+    takeWhile<T>(array: ?Array<T>, predicate?: Predicate<T>): Array<T>;
+    union<T>(...arrays?: Array<Array<T>>): Array<T>;
+    //Workaround until (...parameter: T, parameter2: U) works
+    unionBy<T>(a1: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    unionBy<T>(a1: Array<T>, a2: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    unionBy<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    unionBy<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, a4: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    //Workaround until (...parameter: T, parameter2: U) works
+    unionWith<T>(a1: Array<T>, comparator?: Comparator<T>): Array<T>;
+    unionWith<T>(a1: Array<T>, a2: Array<T>, comparator?: Comparator<T>): Array<T>;
+    unionWith<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, comparator?: Comparator<T>): Array<T>;
+    unionWith<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, a4: Array<T>, comparator?: Comparator<T>): Array<T>;
+    uniq<T>(array: ?Array<T>): Array<T>;
+    uniqBy<T>(array: ?Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    uniqWith<T>(array: ?Array<T>, comparator?: Comparator<T>): Array<T>;
+    unzip<T>(array: ?Array<T>): Array<T>;
+    unzipWith<T>(array: ?Array<T>, iteratee?: Iteratee<T>): Array<T>;
+    without<T>(array: ?Array<T>, ...values?: Array<T>): Array<T>;
+    xor<T>(...array: Array<Array<T>>): Array<T>;
+    //Workaround until (...parameter: T, parameter2: U) works
+    xorBy<T>(a1: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    xorBy<T>(a1: Array<T>, a2: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    xorBy<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    xorBy<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, a4: Array<T>, iteratee?: ValueOnlyIteratee<T>): Array<T>;
+    //Workaround until (...parameter: T, parameter2: U) works
+    xorWith<T>(a1: Array<T>, comparator?: Comparator<T>): Array<T>;
+    xorWith<T>(a1: Array<T>, a2: Array<T>, comparator?: Comparator<T>): Array<T>;
+    xorWith<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, comparator?: Comparator<T>): Array<T>;
+    xorWith<T>(a1: Array<T>, a2: Array<T>, a3: Array<T>, a4: Array<T>, comparator?: Comparator<T>): Array<T>;
+    zip<A, B>(a1: A[], a2: B[]): Array<[A, B]>;
+    zip<A, B, C>(a1: A[], a2: B[], a3: C[]): Array<[A, B, C]>;
+    zip<A, B, C, D>(a1: A[], a2: B[], a3: C[], a4: D[]): Array<[A, B, C, D]>;
+    zip<A, B, C, D, E>(a1: A[], a2: B[], a3: C[], a4: D[], a5: E[]): Array<[A, B, C, D, E]>;
+
+    zipObject(props?: Array<any>, values?: Array<any>): Object;
+    zipObjectDeep(props?: any[], values?: any): Object;
+    //Workaround until (...parameter: T, parameter2: U) works
+    zipWith<T>(a1: NestedArray<T>, iteratee?: Iteratee<T>): Array<T>;
+    zipWith<T>(a1: NestedArray<T>, a2: NestedArray<T>, iteratee?: Iteratee<T>): Array<T>;
+    zipWith<T>(a1: NestedArray<T>, a2: NestedArray<T>, a3: NestedArray<T>, iteratee?: Iteratee<T>): Array<T>;
+    zipWith<T>(a1: NestedArray<T>, a2: NestedArray<T>, a3: NestedArray<T>, a4: NestedArray<T>, iteratee?: Iteratee<T>): Array<T>;
+
+    // Collection
+    countBy<T>(array: ?Array<T>, iteratee?: ValueOnlyIteratee<T>): Object;
+    countBy<T: Object>(object: T, iteratee?: ValueOnlyIteratee<T>): Object;
+    // alias of _.forEach
+    each<T>(array: ?Array<T>, iteratee?: Iteratee<T>): Array<T>;
+    each<T: Object>(object: T, iteratee?: OIteratee<T>): T;
+    // alias of _.forEachRight
+    eachRight<T>(array: ?Array<T>, iteratee?: Iteratee<T>): Array<T>;
+    eachRight<T: Object>(object: T, iteratee?: OIteratee<T>): T;
+    every<T>(array: ?Array<T>, iteratee?: Iteratee<T>): bool;
+    every<T: Object>(object: T, iteratee?: OIteratee<T>): bool;
+    filter<T>(array: ?Array<T>, predicate?: Predicate<T>): Array<T>;
+    filter<A, T: {[id: string]: A}>(object: T, predicate?: OPredicate<A, T>): Array<A>;
+    find<T>(array: ?Array<T>, predicate?: Predicate<T>): T|void;
+    find<V, A, T: {[id: string]: A}>(object: T, predicate?: OPredicate<A, T>): V;
+    findLast<T>(array: ?Array<T>, predicate?: Predicate<T>): T|void;
+    findLast<V, A, T: {[id: string]: A}>(object: T, predicate?: OPredicate<A, T>): V;
+    flatMap<T, U>(array: ?Array<T>, iteratee?: FlatMapIteratee<T, U>): Array<U>;
+    flatMap<T: Object, U>(object: T, iteratee?: OFlatMapIteratee<T, U>): Array<U>;
+    flatMapDeep<T, U>(array: ?Array<T>, iteratee?: FlatMapIteratee<T, U>): Array<U>;
+    flatMapDeep<T: Object, U>(object: T, iteratee?: OFlatMapIteratee<T, U>): Array<U>;
+    flatMapDepth<T, U>(array: ?Array<T>, iteratee?: FlatMapIteratee<T, U>, depth?: number): Array<U>;
+    flatMapDepth<T: Object, U>(object: T, iteratee?: OFlatMapIteratee<T, U>, depth?: number): Array<U>;
+    forEach<T>(array: ?Array<T>, iteratee?: Iteratee<T>): Array<T>;
+    forEach<T: Object>(object: T, iteratee?: OIteratee<T>): T;
+    forEachRight<T>(array: ?Array<T>, iteratee?: Iteratee<T>): Array<T>;
+    forEachRight<T: Object>(object: T, iteratee?: OIteratee<T>): T;
+    groupBy<V, T>(array: ?Array<T>, iteratee?: ValueOnlyIteratee<T>): {[key: V]: ?Array<T>};
+    groupBy<V, A, T: {[id: string]: A}>(object: T, iteratee?: ValueOnlyIteratee<A>): {[key: V]: ?Array<A>};
+    includes<T>(array: ?Array<T>, value: T, fromIndex?: number): bool;
+    includes<T: Object>(object: T, value: any, fromIndex?: number): bool;
+    includes(str: string, value: string, fromIndex?: number): bool;
+    invokeMap<T>(array: ?Array<T>, path: ((value: T) => Array<string>|string)|Array<string>|string, ...args?: Array<any>): Array<any>;
+    invokeMap<T: Object>(object: T, path: ((value: any) => Array<string>|string)|Array<string>|string, ...args?: Array<any>): Array<any>;
+    keyBy<T, V>(array: ?Array<T>, iteratee?: ValueOnlyIteratee<T>): {[key: V]: ?T};
+    keyBy<V, A, T: {[id: string]: A}>(object: T, iteratee?: ValueOnlyIteratee<A>): {[key: V]: ?A};
+    map<T, U>(array: ?Array<T>, iteratee?: MapIterator<T, U>): Array<U>;
+    map<V, T: Object, U>(object: ?T, iteratee?: OMapIterator<V, T, U>): Array<U>;
+    map(str: ?string, iteratee?: (char: string, index: number, str: string) => any): string;
+    orderBy<T>(array: ?Array<T>, iteratees?: Array<Iteratee<T>>|string, orders?: Array<'asc'|'desc'>|string): Array<T>;
+    orderBy<V, T: Object>(object: T, iteratees?: Array<OIteratee<*>>|string, orders?: Array<'asc'|'desc'>|string): Array<V>;
+    partition<T>(array: ?Array<T>, predicate?: Predicate<T>): NestedArray<T>;
+    partition<V, A, T: {[id: string]: A}>(object: T, predicate?: OPredicate<A, T>): NestedArray<V>;
+    reduce<T, U>(array: ?Array<T>, iteratee?: (accumulator: U, value: T, index: number, array: ?Array<T>) => U, accumulator?: U): U;
+    reduce<T: Object, U>(object: T, iteratee?: (accumulator: U, value: any, key: string, object: T) => U, accumulator?: U): U;
+    reduceRight<T, U>(array: ?Array<T>, iteratee?: (accumulator: U, value: T, index: number, array: ?Array<T>) => U, accumulator?: U): U;
+    reduceRight<T: Object, U>(object: T, iteratee?: (accumulator: U, value: any, key: string, object: T) => U, accumulator?: U): U;
+    reject<T>(array: ?Array<T>, predicate?: Predicate<T>): Array<T>;
+    reject<V: Object, A, T: {[id: string]: A}>(object: T, predicate?: OPredicate<A, T>): Array<V>;
+    sample<T>(array: ?Array<T>): T;
+    sample<V, T: Object>(object: T): V;
+    sampleSize<T>(array: ?Array<T>, n?: number): Array<T>;
+    sampleSize<V, T: Object>(object: T, n?: number): Array<V>;
+    shuffle<T>(array: ?Array<T>): Array<T>;
+    shuffle<V, T: Object>(object: T): Array<V>;
+    size(collection: Array<any>|Object): number;
+    some<T>(array: ?Array<T>, predicate?: Predicate<T>): bool;
+    some<A, T: {[id: string]: A}>(object?: ?T, predicate?: OPredicate<A, T>): bool;
+    sortBy<T>(array: ?Array<T>, ...iteratees?: Array<Iteratee<T>>): Array<T>;
+    sortBy<T>(array: ?Array<T>, iteratees?: Array<Iteratee<T>>): Array<T>;
+    sortBy<V, T: Object>(object: T, ...iteratees?: Array<OIteratee<T>>): Array<V>;
+    sortBy<V, T: Object>(object: T, iteratees?: Array<OIteratee<T>>): Array<V>;
+
+    // Date
+    now(): number;
+
+    // Function
+    after(n: number, fn: Function): Function;
+    ary(func: Function, n?: number): Function;
+    before(n: number, fn: Function): Function;
+    bind(func: Function, thisArg: any, ...partials: Array<any>): Function;
+    bindKey(obj: Object, key: string, ...partials: Array<any>): Function;
+    curry(func: Function, arity?: number): Function;
+    curryRight(func: Function, arity?: number): Function;
+    debounce(func: Function, wait?: number, options?: DebounceOptions): Function;
+    defer(func: Function, ...args?: Array<any>): number;
+    delay(func: Function, wait: number, ...args?: Array<any>): number;
+    flip(func: Function): Function;
+    memoize(func: Function, resolver?: Function): Function;
+    negate(predicate: Function): Function;
+    once(func: Function): Function;
+    overArgs(func: Function, ...transforms: Array<Function>): Function;
+    overArgs(func: Function, transforms: Array<Function>): Function;
+    partial(func: Function, ...partials: any[]): Function;
+    partialRight(func: Function, ...partials: Array<any>): Function;
+    partialRight(func: Function, partials: Array<any>): Function;
+    rearg(func: Function, ...indexes: Array<number>): Function;
+    rearg(func: Function, indexes: Array<number>): Function;
+    rest(func: Function, start?: number): Function;
+    spread(func: Function): Function;
+    throttle(func: Function, wait?: number, options?: ThrottleOptions): Function;
+    unary(func: Function): Function;
+    wrap(value: any, wrapper: Function): Function;
+
+    // Lang
+    castArray(value: *): any[];
+    clone<T>(value: T): T;
+    cloneDeep<T>(value: T): T;
+    cloneDeepWith<T, U>(value: T, customizer?: ?(value: T, key: number|string, object: T, stack: any) => U): U;
+    cloneWith<T, U>(value: T, customizer?: ?(value: T, key: number|string, object: T, stack: any) => U): U;
+    conformsTo<T:{[key:string]:mixed}>(source: T, predicates: T&{[key:string]:(x:any)=>boolean}): boolean;
+    eq(value: any, other: any): bool;
+    gt(value: any, other: any): bool;
+    gte(value: any, other: any): bool;
+    isArguments(value: any): bool;
+    isArray(value: any): bool;
+    isArrayBuffer(value: any): bool;
+    isArrayLike(value: any): bool;
+    isArrayLikeObject(value: any): bool;
+    isBoolean(value: any): bool;
+    isBuffer(value: any): bool;
+    isDate(value: any): bool;
+    isElement(value: any): bool;
+    isEmpty(value: any): bool;
+    isEqual(value: any, other: any): bool;
+    isEqualWith<T, U>(value: T, other: U, customizer?: (objValue: any, otherValue: any, key: number|string, object: T, other: U, stack: any) => bool|void): bool;
+    isError(value: any): bool;
+    isFinite(value: any): bool;
+    isFunction(value: Function): true;
+    isFunction(value: number|string|void|null|Object): false;
+    isInteger(value: any): bool;
+    isLength(value: any): bool;
+    isMap(value: any): bool;
+    isMatch(object?: ?Object, source: Object): bool;
+    isMatchWith<T: Object, U: Object>(object: T, source: U, customizer?: (objValue: any, srcValue: any, key: number|string, object: T, source: U) => bool|void): bool;
+    isNaN(value: any): bool;
+    isNative(value: any): bool;
+    isNil(value: any): bool;
+    isNull(value: any): bool;
+    isNumber(value: any): bool;
+    isObject(value: any): bool;
+    isObjectLike(value: any): bool;
+    isPlainObject(value: any): bool;
+    isRegExp(value: any): bool;
+    isSafeInteger(value: any): bool;
+    isSet(value: any): bool;
+    isString(value: string): true;
+    isString(value: number|Function|void|null|Object|Array<any>): false;
+    isSymbol(value: any): bool;
+    isTypedArray(value: any): bool;
+    isUndefined(value: any): bool;
+    isWeakMap(value: any): bool;
+    isWeakSet(value: any): bool;
+    lt(value: any, other: any): bool;
+    lte(value: any, other: any): bool;
+    toArray(value: any): Array<any>;
+    toFinite(value: any): number;
+    toInteger(value: any): number;
+    toLength(value: any): number;
+    toNumber(value: any): number;
+    toPlainObject(value: any): Object;
+    toSafeInteger(value: any): number;
+    toString(value: any): string;
+
+    // Math
+    add(augend: number, addend: number): number;
+    ceil(number: number, precision?: number): number;
+    divide(dividend: number, divisor: number): number;
+    floor(number: number, precision?: number): number;
+    max<T>(array: ?Array<T>): T;
+    maxBy<T>(array: ?Array<T>, iteratee?: Iteratee<T>): T;
+    mean(array: Array<*>): number;
+    meanBy<T>(array: Array<T>, iteratee?: Iteratee<T>): number;
+    min<T>(array: ?Array<T>): T;
+    minBy<T>(array: ?Array<T>, iteratee?: Iteratee<T>): T;
+    multiply(multiplier: number, multiplicand: number): number;
+    round(number: number, precision?: number): number;
+    subtract(minuend: number, subtrahend: number): number;
+    sum(array: Array<*>): number;
+    sumBy<T>(array: Array<T>, iteratee?: Iteratee<T>): number;
+
+    // number
+    clamp(number: number, lower?: number, upper: number): number;
+    inRange(number: number, start?: number, end: number): bool;
+    random(lower?: number, upper?: number, floating?: bool): number;
+
+    // Object
+    assign(object?: ?Object, ...sources?: Array<Object>): Object;
+    assignIn<A, B>(a: A, b: B): A & B;
+    assignIn<A, B, C>(a: A, b: B, c: C): A & B & C;
+    assignIn<A, B, C, D>(a: A, b: B, c: C, d: D): A & B & C & D;
+    assignIn<A, B, C, D, E>(a: A, b: B, c: C, d: D, e: E): A & B & C & D & E;
+    assignInWith<T: Object, A: Object>(object: T, s1: A, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A) => any|void): Object;
+    assignInWith<T: Object, A: Object, B: Object>(object: T, s1: A, s2: B, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B) => any|void): Object;
+    assignInWith<T: Object, A: Object, B: Object, C: Object>(object: T, s1: A, s2: B, s3: C, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C) => any|void): Object;
+    assignInWith<T: Object, A: Object, B: Object, C: Object, D: Object>(object: T, s1: A, s2: B, s3: C, s4: D, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C|D) => any|void): Object;
+    assignWith<T: Object, A: Object>(object: T, s1: A, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A) => any|void): Object;
+    assignWith<T: Object, A: Object, B: Object>(object: T, s1: A, s2: B, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B) => any|void): Object;
+    assignWith<T: Object, A: Object, B: Object, C: Object>(object: T, s1: A, s2: B, s3: C, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C) => any|void): Object;
+    assignWith<T: Object, A: Object, B: Object, C: Object, D: Object>(object: T, s1: A, s2: B, s3: C, s4: D, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C|D) => any|void): Object;
+    at(object?: ?Object, ...paths: Array<string>): Array<any>;
+    at(object?: ?Object, paths: Array<string>): Array<any>;
+    create<T>(prototype: T, properties?: Object): $Supertype<T>;
+    defaults(object?: ?Object, ...sources?: Array<Object>): Object;
+    defaultsDeep(object?: ?Object, ...sources?: Array<Object>): Object;
+    // alias for _.toPairs
+    entries(object?: ?Object): NestedArray<any>;
+    // alias for _.toPairsIn
+    entriesIn(object?: ?Object): NestedArray<any>;
+    // alias for _.assignIn
+    extend<A, B>(a: A, b: B): A & B;
+    extend<A, B, C>(a: A, b: B, c: C): A & B & C;
+    extend<A, B, C, D>(a: A, b: B, c: C, d: D): A & B & C & D;
+    extend<A, B, C, D, E>(a: A, b: B, c: C, d: D, e: E): A & B & C & D & E;
+    // alias for _.assignInWith
+    extendWith<T: Object, A: Object>(object: T, s1: A, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A) => any|void): Object;
+    extendWith<T: Object, A: Object, B: Object>(object: T, s1: A, s2: B, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B) => any|void): Object;
+    extendWith<T: Object, A: Object, B: Object, C: Object>(object: T, s1: A, s2: B, s3: C, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C) => any|void): Object;
+    extendWith<T: Object, A: Object, B: Object, C: Object, D: Object>(object: T, s1: A, s2: B, s3: C, s4: D, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C|D) => any|void): Object;
+    findKey<A, T: {[id: string]: A}>(object?: ?T, predicate?: OPredicate<A, T>): string|void;
+    findLastKey<A, T: {[id: string]: A}>(object?: ?T, predicate?: OPredicate<A, T>): string|void;
+    forIn(object?: ?Object, iteratee?: OIteratee<*>): Object;
+    forInRight(object?: ?Object, iteratee?: OIteratee<*>): Object;
+    forOwn(object?: ?Object, iteratee?: OIteratee<*>): Object;
+    forOwnRight(object?: ?Object, iteratee?: OIteratee<*>): Object;
+    functions(object?: ?Object): Array<string>;
+    functionsIn(object?: ?Object): Array<string>;
+    get(object?: ?Object, path?: ?Array<string>|string, defaultValue?: any): any;
+    has(object?: ?Object, path?: ?Array<string>|string): bool;
+    hasIn(object?: ?Object, path?: ?Array<string>|string): bool;
+    invert(object?: ?Object, multiVal?: bool): Object;
+    invertBy(object: ?Object, iteratee?: Function): Object;
+    invoke(object?: ?Object, path?: ?Array<string>|string, ...args?: Array<any>): any;
+    keys(object?: ?Object): Array<string>;
+    keysIn(object?: ?Object): Array<string>;
+    mapKeys(object?: ?Object, iteratee?: OIteratee<*>): Object;
+    mapValues(object?: ?Object, iteratee?: OIteratee<*>): Object;
+    merge(object?: ?Object, ...sources?: Array<?Object>): Object;
+    mergeWith<T: Object, A: Object>(object: T, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A) => any|void): Object;
+    mergeWith<T: Object, A: Object, B: Object>(object: T, s1: A, s2: B, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B) => any|void): Object;
+    mergeWith<T: Object, A: Object, B: Object, C: Object>(object: T, s1: A, s2: B, s3: C, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C) => any|void): Object;
+    mergeWith<T: Object, A: Object, B: Object, C: Object, D: Object>(object: T, s1: A, s2: B, s3: C, s4: D, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C|D) => any|void): Object;
+    omit(object?: ?Object, ...props: Array<string>): Object;
+    omit(object?: ?Object, props: Array<string>): Object;
+    omitBy<A, T: {[id: string]: A}>(object?: ?T, predicate?: OPredicate<A, T>): Object;
+    pick(object?: ?Object, ...props: Array<string>): Object;
+    pick(object?: ?Object, props: Array<string>): Object;
+    pickBy<A, T: {[id: string]: A}>(object?: ?T, predicate?: OPredicate<A, T>): Object;
+    result(object?: ?Object, path?: ?Array<string>|string, defaultValue?: any): any;
+    set(object?: ?Object, path?: ?Array<string>|string, value: any): Object;
+    setWith<T>(object: T, path?: ?Array<string>|string, value: any, customizer?: (nsValue: any, key: string, nsObject: T) => any): Object;
+    toPairs(object?: ?Object|Array<*>): NestedArray<any>;
+    toPairsIn(object?: ?Object): NestedArray<any>;
+    transform(collection: Object|Array<any>, iteratee?: OIteratee<*>, accumulator?: any): any;
+    unset(object?: ?Object, path?: ?Array<string>|string): bool;
+    update(object: Object, path: string[]|string, updater: Function): Object;
+    updateWith(object: Object, path: string[]|string, updater: Function, customizer?: Function): Object;
+    values(object?: ?Object): Array<any>;
+    valuesIn(object?: ?Object): Array<any>;
+
+    // Seq
+    // harder to read, but this is _()
+    (value: any): any;
+    chain<T>(value: T): any;
+    tap<T>(value: T, interceptor: (value:T)=>any): T;
+    thru<T1,T2>(value: T1, interceptor: (value:T1)=>T2): T2;
+    // TODO: _.prototype.*
+
+    // String
+    camelCase(string?: ?string): string;
+    capitalize(string?: string): string;
+    deburr(string?: string): string;
+    endsWith(string?: string, target?: string, position?: number): bool;
+    escape(string?: string): string;
+    escapeRegExp(string?: string): string;
+    kebabCase(string?: string): string;
+    lowerCase(string?: string): string;
+    lowerFirst(string?: string): string;
+    pad(string?: string, length?: number, chars?: string): string;
+    padEnd(string?: string, length?: number, chars?: string): string;
+    padStart(string?: string, length?: number, chars?: string): string;
+    parseInt(string: string, radix?: number): number;
+    repeat(string?: string, n?: number): string;
+    replace(string?: string, pattern: RegExp|string, replacement: ((string: string) => string)|string): string;
+    snakeCase(string?: string): string;
+    split(string?: string, separator: RegExp|string, limit?: number): Array<string>;
+    startCase(string?: string): string;
+    startsWith(string?: string, target?: string, position?: number): bool;
+    template(string?: string, options?: TemplateSettings): Function;
+    toLower(string?: string): string;
+    toUpper(string?: string): string;
+    trim(string?: string, chars?: string): string;
+    trimEnd(string?: string, chars?: string): string;
+    trimStart(string?: string, chars?: string): string;
+    truncate(string?: string, options?: TruncateOptions): string;
+    unescape(string?: string): string;
+    upperCase(string?: string): string;
+    upperFirst(string?: string): string;
+    words(string?: string, pattern?: RegExp|string): Array<string>;
+
+    // Util
+    attempt(func: Function): any;
+    bindAll(object?: ?Object, methodNames: Array<string>): Object;
+    bindAll(object?: ?Object, ...methodNames: Array<string>): Object;
+    cond(pairs: NestedArray<Function>): Function;
+    conforms(source: Object): Function;
+    constant<T>(value: T): () => T;
+    defaultTo<T1:string|boolean|Object,T2>(value: T1, default: T2): T1;
+    // NaN is a number instead of its own type, otherwise it would behave like null/void
+    defaultTo<T1:number,T2>(value: T1, default: T2): T1|T2;
+    defaultTo<T1:void|null,T2>(value: T1, default: T2): T2;
+    flow(...funcs?: Array<Function>): Function;
+    flow(funcs?: Array<Function>): Function;
+    flowRight(...funcs?: Array<Function>): Function;
+    flowRight(funcs?: Array<Function>): Function;
+    identity<T>(value: T): T;
+    iteratee(func?: any): Function;
+    matches(source: Object): Function;
+    matchesProperty(path?: ?Array<string>|string, srcValue: any): Function;
+    method(path?: ?Array<string>|string, ...args?: Array<any>): Function;
+    methodOf(object?: ?Object, ...args?: Array<any>): Function;
+    mixin<T: Function|Object>(object?: T, source: Object, options?: { chain: bool }): T;
+    noConflict(): Lodash;
+    noop(): void;
+    nthArg(n?: number): Function;
+    over(...iteratees: Array<Function>): Function;
+    over(iteratees: Array<Function>): Function;
+    overEvery(...predicates: Array<Function>): Function;
+    overEvery(predicates: Array<Function>): Function;
+    overSome(...predicates: Array<Function>): Function;
+    overSome(predicates: Array<Function>): Function;
+    property(path?: ?Array<string>|string): Function;
+    propertyOf(object?: ?Object): Function;
+    range(start: number, end: number, step?: number): Array<number>;
+    range(end: number, step?: number): Array<number>;
+    rangeRight(start: number, end: number, step?: number): Array<number>;
+    rangeRight(end: number, step?: number): Array<number>;
+    runInContext(context?: Object): Function;
+
+    stubArray(): Array<*>;
+    stubFalse(): false;
+    stubObject(): {};
+    stubString(): '';
+    stubTrue(): true;
+    times(n: number, ...rest: Array<void>): Array<number>;
+    times<T>(n: number, iteratee: ((i: number) => T)): Array<T>;
+    toPath(value: any): Array<string>;
+    uniqueId(prefix?: string): string;
+
+    // Properties
+    VERSION: string;
+    templateSettings: TemplateSettings;
+  }
+
+  declare var exports: Lodash;
+}
diff --git a/flow-typed/npm/loophole_vx.x.x.js b/flow-typed/npm/loophole_vx.x.x.js
index 6dd7524..e12d95b 100644
--- a/flow-typed/npm/loophole_vx.x.x.js
+++ b/flow-typed/npm/loophole_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: b7cca345e64a4bc95f3bae3c27dbc46c
-// flow-typed version: <<STUB>>/loophole_v^1.1.0/flow_v0.45.0
+// flow-typed signature: 0677696c7be1ff3c0235d1018b74add1
+// flow-typed version: <<STUB>>/loophole_v^1.1.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/nps_vx.x.x.js b/flow-typed/npm/nps_vx.x.x.js
index b3abaac..8ffc3bd 100644
--- a/flow-typed/npm/nps_vx.x.x.js
+++ b/flow-typed/npm/nps_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 0561b4112944f4357964e5df94c74819
-// flow-typed version: <<STUB>>/nps_v^5.1.0/flow_v0.45.0
+// flow-typed signature: fcc05bb730d059436e1a2ed7972a2606
+// flow-typed version: <<STUB>>/nps_v^5.1.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/opt-cli_vx.x.x.js b/flow-typed/npm/opt-cli_vx.x.x.js
index f99c0b8..c5e85d4 100644
--- a/flow-typed/npm/opt-cli_vx.x.x.js
+++ b/flow-typed/npm/opt-cli_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 8b963f51948c9e52fb8d6bed3005bd9b
-// flow-typed version: <<STUB>>/opt-cli_v^1.5.1/flow_v0.45.0
+// flow-typed signature: 62ba169ba4bcb4a0f583909f1e2e6747
+// flow-typed version: <<STUB>>/opt-cli_v^1.5.1/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/prettier-eslint-cli_vx.x.x.js b/flow-typed/npm/prettier-eslint-cli_vx.x.x.js
index b7f6ee4..9a8bcc6 100644
--- a/flow-typed/npm/prettier-eslint-cli_vx.x.x.js
+++ b/flow-typed/npm/prettier-eslint-cli_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 6905b83bfbbed1f83e434432424d40ec
-// flow-typed version: <<STUB>>/prettier-eslint-cli_v^3.4.2/flow_v0.45.0
+// flow-typed signature: dd165d565dc2d2481cd19a2d03a1b2b5
+// flow-typed version: <<STUB>>/prettier-eslint-cli_v^3.4.3/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/prettier-eslint_vx.x.x.js b/flow-typed/npm/prettier-eslint_vx.x.x.js
index a17486d..944bfa2 100644
--- a/flow-typed/npm/prettier-eslint_vx.x.x.js
+++ b/flow-typed/npm/prettier-eslint_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 76db6a712f9a1872e062ccfc6e7f1dc9
-// flow-typed version: <<STUB>>/prettier-eslint_v^6.1.2/flow_v0.45.0
+// flow-typed signature: 88a1e84645fe6b59d8fabe139cc44b49
+// flow-typed version: <<STUB>>/prettier-eslint_v^6.2.1/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/prettier_vx.x.x.js b/flow-typed/npm/prettier_vx.x.x.js
index c14811c..e774eb9 100644
--- a/flow-typed/npm/prettier_vx.x.x.js
+++ b/flow-typed/npm/prettier_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 2f3434a9f45cd9a7e7b22dd9523dbf9d
-// flow-typed version: <<STUB>>/prettier_v^1.3.0/flow_v0.45.0
+// flow-typed signature: 84628f0cf0bfca399fcd1d50c371e23f
+// flow-typed version: <<STUB>>/prettier_v^1.3.1/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/read-pkg_vx.x.x.js b/flow-typed/npm/read-pkg_vx.x.x.js
index 4d10ac4..e141337 100644
--- a/flow-typed/npm/read-pkg_vx.x.x.js
+++ b/flow-typed/npm/read-pkg_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: d6a857663265ca76523f0c1119fb1ddd
-// flow-typed version: <<STUB>>/read-pkg_v^2.0.0/flow_v0.45.0
+// flow-typed signature: e02afd5db0e6af1f4e185c54dce46c76
+// flow-typed version: <<STUB>>/read-pkg_v^2.0.0/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/flow-typed/npm/rimraf_vx.x.x.js b/flow-typed/npm/rimraf_vx.x.x.js
index dfbb25d..5405000 100644
--- a/flow-typed/npm/rimraf_vx.x.x.js
+++ b/flow-typed/npm/rimraf_vx.x.x.js
@@ -1,5 +1,5 @@
-// flow-typed signature: 148b6bc172b12d8111a1aeaa8b4955a9
-// flow-typed version: <<STUB>>/rimraf_v^2.6.1/flow_v0.45.0
+// flow-typed signature: 475d59277bfba3e04a313defb589ae39
+// flow-typed version: <<STUB>>/rimraf_v^2.6.1/flow_v0.46.0
 
 /**
  * This is an autogenerated libdef stub for:
diff --git a/package.json b/package.json
index 9bb180c..ea2e91f 100644
--- a/package.json
+++ b/package.json
@@ -23,35 +23,36 @@
   "dependencies": {
     "atom-linter": "^10.0.0",
     "editorconfig": "^0.13.2",
-    "ignore": "^3.2.7",
+    "ignore": "^3.3.0",
+    "lodash": "^4.17.4",
     "loophole": "^1.1.0",
-    "prettier": "^1.3.0",
-    "prettier-eslint": "^6.1.2",
+    "prettier": "^1.3.1",
+    "prettier-eslint": "^6.2.1",
     "read-pkg": "^2.0.0"
   },
   "devDependencies": {
     "all-contributors-cli": "^4.3.0",
     "babel-cli": "^6.24.1",
     "babel-eslint": "^7.2.3",
-    "babel-jest": "^19.0.0",
+    "babel-jest": "^20.0.1",
     "babel-plugin-transform-flow-strip-types": "^6.22.0",
     "babel-preset-env": "^1.4.0",
     "babel-preset-stage-2": "^6.24.1",
-    "codecov": "^2.1.0",
+    "codecov": "^2.2.0",
     "commitizen": "^2.9.6",
     "cz-conventional-changelog": "^2.0.0",
     "eslint": "^3.19.0",
     "eslint-config-airbnb-base": "^11.1.3",
     "eslint-plugin-flowtype": "^2.32.1",
     "eslint-plugin-import": "^2.2.0",
-    "eslint-plugin-jest": "^19.0.1",
-    "flow-bin": "^0.45.0",
+    "eslint-plugin-jest": "^20.0.1",
+    "flow-bin": "^0.46.0",
     "flow-typed": "^2.1.2",
     "husky": "^0.13.3",
-    "jest-cli": "^19.0.2",
+    "jest-cli": "^20.0.1",
     "nps": "^5.1.0",
     "opt-cli": "^1.5.1",
-    "prettier-eslint-cli": "^3.4.2",
+    "prettier-eslint-cli": "^3.4.3",
     "rimraf": "^2.6.1"
   },
   "scripts": {
diff --git a/src/atomInterface/index.js b/src/atomInterface/index.js
new file mode 100644
index 0000000..85d8d9d
--- /dev/null
+++ b/src/atomInterface/index.js
@@ -0,0 +1,85 @@
+// @flow
+
+// constants
+const LINTER_LINT_COMMAND = 'linter:lint';
+
+// local helpers
+const getConfigOption = (key: string) => atom.config.get(`prettier-atom.${key}`);
+
+const setConfigOption = (key: string, value: any) => atom.config.set(`prettier-atom.${key}`, value);
+
+const isLinterLintCommandDefined = (editor: TextEditor) =>
+  atom.commands
+    .findCommands({ target: atom.views.getView(editor) })
+    .some(command => command.name === LINTER_LINT_COMMAND);
+
+// public
+const isLinterEslintAutofixEnabled = () => atom.config.get('linter-eslint.fixOnSave');
+
+const shouldUseEslint = () => getConfigOption('useEslint');
+
+const shouldUseEditorConfig = () => getConfigOption('useEditorConfig');
+
+const shouldDisplayErrors = () => !getConfigOption('silenceErrors');
+
+const isFormatOnSaveEnabled = () => getConfigOption('formatOnSaveOptions.enabled');
+
+const shouldRespectEslintignore = () => getConfigOption('formatOnSaveOptions.respectEslintignore');
+
+const getScopes = () => getConfigOption('formatOnSaveOptions.scopes');
+
+const getWhitelistedGlobs = () => getConfigOption('formatOnSaveOptions.whitelistedGlobs');
+
+const getExcludedGlobs = () => getConfigOption('formatOnSaveOptions.excludedGlobs');
+
+const toggleFormatOnSave = () => setConfigOption('formatOnSaveOptions.enabled', !isFormatOnSaveEnabled());
+
+const getAtomTabLength = (editor: TextEditor) =>
+  atom.config.get('editor.tabLength', { scope: editor.getLastCursor().getScopeDescriptor() });
+
+const getPrettierOptions = () => getConfigOption('prettierOptions');
+
+const getPrettierEslintOptions = () => getConfigOption('prettierEslintOptions');
+
+const getAtomVersion = () => atom.getVersion();
+
+const getPrettierAtomConfig = () => atom.config.get('prettier-atom');
+
+const addTooltip = (element: HTMLElement, options: Atom$Tooltips$Options) =>
+  atom.tooltips.add(element, options);
+
+const addInfoNotification = (message: string, options?: Atom$Notifications$Options) =>
+  atom.notifications.addInfo(message, options);
+
+const addWarningNotification = (message: string, options?: Atom$Notifications$Options) =>
+  atom.notifications.addWarning(message, options);
+
+const addErrorNotification = (message: string, options?: Atom$Notifications$Options) =>
+  atom.notifications.addError(message, options);
+
+const runLinter = (editor: TextEditor) =>
+  isLinterLintCommandDefined(editor) &&
+  atom.commands.dispatch(atom.views.getView(editor), LINTER_LINT_COMMAND);
+
+module.exports = {
+  addErrorNotification,
+  addInfoNotification,
+  addTooltip,
+  addWarningNotification,
+  getAtomTabLength,
+  getAtomVersion,
+  getExcludedGlobs,
+  getPrettierAtomConfig,
+  getPrettierEslintOptions,
+  getPrettierOptions,
+  getScopes,
+  getWhitelistedGlobs,
+  isFormatOnSaveEnabled,
+  isLinterEslintAutofixEnabled,
+  runLinter,
+  shouldDisplayErrors,
+  shouldRespectEslintignore,
+  shouldUseEditorConfig,
+  shouldUseEslint,
+  toggleFormatOnSave,
+};
diff --git a/src/config-schema.json b/src/config-schema.json
index a38a403..c392bd2 100644
--- a/src/config-schema.json
+++ b/src/config-schema.json
@@ -15,7 +15,7 @@
   },
   "useEditorConfig": {
     "title": "EditorConfig Integration",
-    "description": "Use [EditorConfig](http://editorconfig.org/) configuration to load project-based settings for prettier formatting <br />Next options will be overriden: **Use tabs**, **Tab width**, **Print width**",
+    "description": "Use [EditorConfig](http://editorconfig.org/) configuration to load project-based settings for prettier formatting <br />Takes precedence over your settings. The following settings will be overriden if they are found in your .editorconfig: **Use tabs**, **Tab width**, **Print width**",
     "type": "boolean",
     "default": true,
     "order": 3
diff --git a/src/displayDebugInfo.js b/src/displayDebugInfo.js
deleted file mode 100644
index 7daa03e..0000000
--- a/src/displayDebugInfo.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// @flow
-const { getDebugInfo } = require('./helpers');
-
-const displayDebugInfo = () => {
-  const info = getDebugInfo();
-  const details = [
-    `Atom version: ${info.atomVersion}`,
-    `prettier-atom version: ${info.prettierAtomVersion}`,
-    `prettier version: ${info.prettierVersion}`,
-    `prettier-eslint version: ${info.prettierESLintVersion}`,
-    `prettier-atom configuration: ${JSON.stringify(info.prettierAtomConfig, null, 2)}`,
-  ].join('\n');
-  atom.notifications.addInfo('prettier-atom: details on current install', {
-    detail: details,
-    dismissable: true,
-  });
-};
-
-module.exports = displayDebugInfo;
diff --git a/src/displayDebugInfo/index.js b/src/displayDebugInfo/index.js
new file mode 100644
index 0000000..29dc3ac
--- /dev/null
+++ b/src/displayDebugInfo/index.js
@@ -0,0 +1,23 @@
+// @flow
+const readPkg = require('read-pkg');
+const path = require('path');
+const { getAtomVersion, getPrettierAtomConfig, addInfoNotification } = require('../atomInterface');
+
+const getDepPath = (dep: string) => path.join(__dirname, '..', '..', 'node_modules', dep);
+
+const getDebugInfo = () =>
+  `
+Atom version: ${getAtomVersion()}
+prettier-atom version: ${readPkg.sync().version}
+prettier version: ${readPkg.sync(getDepPath('prettier')).version}
+prettier-eslint version: ${readPkg.sync(getDepPath('prettier-eslint')).version}
+prettier-atom configuration: ${JSON.stringify(getPrettierAtomConfig(), null, 2)}
+`.trim();
+
+const displayDebugInfo = () =>
+  addInfoNotification('prettier-atom: details on current install', {
+    detail: getDebugInfo(),
+    dismissable: true,
+  });
+
+module.exports = displayDebugInfo;
diff --git a/src/editorInterface/index.js b/src/editorInterface/index.js
new file mode 100644
index 0000000..74b6c0f
--- /dev/null
+++ b/src/editorInterface/index.js
@@ -0,0 +1,17 @@
+// @flow
+const EMBEDDED_SCOPES = ['text.html.vue', 'text.html.basic'];
+
+const getBufferRange = (editor: TextEditor) => editor.getBuffer().getRange();
+
+const getCurrentScope = (editor: TextEditor) => editor.getGrammar().scopeName;
+
+const isCurrentScopeEmbeddedScope = (editor: TextEditor) => EMBEDDED_SCOPES.includes(getCurrentScope(editor));
+
+const getCurrentFilePath = (editor: TextEditor) => (editor.buffer.file ? editor.buffer.file.path : undefined);
+
+module.exports = {
+  getBufferRange,
+  isCurrentScopeEmbeddedScope,
+  getCurrentScope,
+  getCurrentFilePath,
+};
diff --git a/src/executePrettier.js b/src/executePrettier.js
deleted file mode 100644
index 4e01f2e..0000000
--- a/src/executePrettier.js
+++ /dev/null
@@ -1,84 +0,0 @@
-// @flow
-const prettierEslint = require('prettier-eslint');
-const { allowUnsafeNewFunction } = require('loophole');
-
-const { getCurrentFilePath, getPrettier, runLinter } = require('./helpers');
-
-const {
-  getPrettierOptions,
-  getPrettierEslintOptions,
-  shouldUseEslint,
-  shouldDisplayErrors,
-} = require('./options');
-
-const EMBEDDED_JS_REGEX = /<script\b[^>]*>([\s\S]*?)(?=<\/script>)/gi;
-
-const displayError = (error) => {
-  atom.notifications.addError('prettier-atom failed!', {
-    detail: error,
-    stack: error.stack,
-    dismissable: true,
-  });
-};
-
-const handleError = (error) => {
-  if (shouldDisplayErrors()) displayError(error);
-  return false;
-};
-
-const executePrettier = (editor, text) => {
-  try {
-    const filePath = getCurrentFilePath(editor);
-    const prettierOptions = getPrettierOptions(editor);
-    if (shouldUseEslint()) {
-      return allowUnsafeNewFunction(() =>
-        prettierEslint({
-          ...getPrettierEslintOptions(),
-          text,
-          filePath,
-          fallbackPrettierOptions: prettierOptions,
-        }),
-      );
-    }
-
-    const prettier = getPrettier(filePath);
-
-    return prettier.format(text, prettierOptions);
-  } catch (error) {
-    return handleError(error);
-  }
-};
-
-const executePrettierOnBufferRange = (editor: TextEditor, bufferRange: Range) => {
-  const cursorPositionPriorToFormat = editor.getCursorScreenPosition();
-  const textToTransform = editor.getTextInBufferRange(bufferRange);
-  const transformed = executePrettier(editor, textToTransform);
-
-  const isTextUnchanged = transformed === textToTransform;
-  if (!transformed || isTextUnchanged) return;
-
-  editor.setTextInBufferRange(bufferRange, transformed);
-  editor.setCursorScreenPosition(cursorPositionPriorToFormat);
-  runLinter(editor);
-};
-
-const executePrettierOnEmbeddedScripts = (editor: TextEditor) =>
-  editor.backwardsScanInBufferRange(EMBEDDED_JS_REGEX, editor.getBuffer().getRange(), (iter) => {
-    const { start, end } = iter.range;
-
-    // Skip formatting when <script> and </script> on the same line
-    if (start.row === end.row) return;
-
-    // Create new range with start row advanced by 1,
-    // since we cannot use look-behind on variable-length starting
-    // <script ...> tag
-    const startModified = [start.row + 1, start.column];
-    const bufferRange = new iter.range.constructor(startModified, end);
-
-    executePrettierOnBufferRange(editor, bufferRange);
-  });
-
-module.exports = {
-  executePrettierOnBufferRange,
-  executePrettierOnEmbeddedScripts,
-};
diff --git a/src/executePrettier/buildEditorConfigOptions.js b/src/executePrettier/buildEditorConfigOptions.js
new file mode 100644
index 0000000..defa881
--- /dev/null
+++ b/src/executePrettier/buildEditorConfigOptions.js
@@ -0,0 +1,60 @@
+// @flow
+const _ = require('lodash/fp');
+const editorconfig = require('editorconfig');
+
+type RawOpts = {
+  indent_style?: 'tab' | 'space',
+  indent_size?: 'tab' | number,
+  tab_width?: number,
+  max_line_length?: number | 'off',
+};
+
+type TranslatedOpts = {
+  tabWidth?: number,
+  printWidth?: number,
+  useTabs?: boolean,
+};
+
+const maxLineLengthIsNumber: (opts: RawOpts) => boolean = _.flow(_.get('max_line_length'), _.isNumber);
+
+const indentStyleIsTab: (opts: RawOpts) => boolean = _.conforms({ indent_style: _.isEqual('tab') });
+
+const indentSizeIsTab: (opts: RawOpts) => boolean = _.conforms({ indent_size: _.isEqual('tab') });
+
+const tabWidthIsNumber: (opts: RawOpts) => boolean = _.conforms({ tab_width: _.isNumber });
+
+const shouldUseTabWidth: (opts: RawOpts) => boolean = _.overEvery([
+  tabWidthIsNumber,
+  _.overSome([indentSizeIsTab, indentStyleIsTab]),
+]);
+
+const indentSizeIsNumber: (opts: RawOpts) => boolean = _.conforms({ indent_size: _.isNumber });
+
+const indentStyleIsSpace: (opts: RawOpts) => boolean = _.conforms({ indent_style: _.isEqual('space') });
+
+const getPrintWidth: (opts: RawOpts) => ?{ printWidth: number } = _.cond([
+  [maxLineLengthIsNumber, opts => ({ printWidth: opts.max_line_length })],
+]);
+
+const getTabWidth: (opts: RawOpts) => ?{ tabWidth: number } = _.cond([
+  [shouldUseTabWidth, opts => ({ tabWidth: opts.tab_width })],
+  [indentSizeIsNumber, opts => ({ tabWidth: opts.indent_size })],
+]);
+
+const getUseTabs: (opts: RawOpts) => ?{ useTabs: boolean } = _.cond([
+  [indentStyleIsTab, _.constant({ useTabs: true })],
+  [indentStyleIsSpace, _.constant({ useTabs: false })],
+]);
+
+const mapEditorConfigOptions = (opts?: RawOpts = {}) => ({
+  ...getPrintWidth(opts),
+  ...getTabWidth(opts),
+  ...getUseTabs(opts),
+});
+
+const buildEditorConfigOptions: (file: FilePath) => TranslatedOpts = _.flow(
+  editorconfig.parseSync,
+  mapEditorConfigOptions,
+);
+
+module.exports = buildEditorConfigOptions;
diff --git a/src/executePrettier/buildPrettierEslintOptions.js b/src/executePrettier/buildPrettierEslintOptions.js
new file mode 100644
index 0000000..02bbf86
--- /dev/null
+++ b/src/executePrettier/buildPrettierEslintOptions.js
@@ -0,0 +1,13 @@
+// @flow
+const buildPrettierOptions = require('./buildPrettierOptions');
+const { getPrettierEslintOptions } = require('../atomInterface');
+const { getCurrentFilePath } = require('../editorInterface');
+
+const buildPrettierEslintOptions = (editor: TextEditor, text: string) => ({
+  text,
+  ...getPrettierEslintOptions(),
+  fallbackPrettierOptions: buildPrettierOptions(editor),
+  filePath: getCurrentFilePath(editor),
+});
+
+module.exports = buildPrettierEslintOptions;
diff --git a/src/executePrettier/buildPrettierOptions.js b/src/executePrettier/buildPrettierOptions.js
new file mode 100644
index 0000000..7d35128
--- /dev/null
+++ b/src/executePrettier/buildPrettierOptions.js
@@ -0,0 +1,32 @@
+// @flow
+const _ = require('lodash/fp');
+const { getCurrentFilePath } = require('../editorInterface');
+const { shouldUseEditorConfig, getPrettierOptions, getAtomTabLength } = require('../atomInterface');
+const buildEditorConfigOptions = require('./buildEditorConfigOptions');
+
+const isDefined: (x: any) => boolean = _.negate(_.isNil);
+
+const isAppropriateToBuildEditorConfigOptions: (filePath: FilePath) => boolean = _.overEvery([
+  isDefined,
+  shouldUseEditorConfig,
+]);
+
+const buildEditorConfigOptionsIfAppropriate: (editor: TextEditor) => ?{} = _.flow(
+  getCurrentFilePath,
+  _.cond([[isAppropriateToBuildEditorConfigOptions, buildEditorConfigOptions]]),
+);
+
+const buildPrettierOptions = (editor: TextEditor) => {
+  const optionsFromSettings = getPrettierOptions();
+
+  if (optionsFromSettings.tabWidth === 'auto') {
+    optionsFromSettings.tabWidth = getAtomTabLength(editor);
+  }
+
+  return {
+    ...optionsFromSettings,
+    ...buildEditorConfigOptionsIfAppropriate(editor),
+  };
+};
+
+module.exports = buildPrettierOptions;
diff --git a/src/executePrettier/executePrettierOnBufferRange.js b/src/executePrettier/executePrettierOnBufferRange.js
new file mode 100644
index 0000000..602e425
--- /dev/null
+++ b/src/executePrettier/executePrettierOnBufferRange.js
@@ -0,0 +1,38 @@
+// @flow
+const prettierEslint = require('prettier-eslint');
+const { allowUnsafeNewFunction } = require('loophole');
+
+const { shouldUseEslint, runLinter } = require('../atomInterface');
+const getPrettierInstance = require('./getPrettierInstance');
+const buildPrettierEslintOptions = require('./buildPrettierEslintOptions');
+const buildPrettierOptions = require('./buildPrettierOptions');
+const handleError = require('./handleError');
+
+const executePrettier = (editor: TextEditor, text: string) =>
+  getPrettierInstance(editor).format(text, buildPrettierOptions(editor));
+
+const executePrettierEslint = (editor: TextEditor, text: string) =>
+  allowUnsafeNewFunction(() => prettierEslint(buildPrettierEslintOptions(editor, text)));
+
+const executePrettierOrPrettierEslint = (editor: TextEditor, text: string) => {
+  try {
+    return shouldUseEslint() ? executePrettierEslint(editor, text) : executePrettier(editor, text);
+  } catch (error) {
+    return handleError(error);
+  }
+};
+
+const executePrettierOnBufferRange = (editor: TextEditor, bufferRange: Range) => {
+  const cursorPositionPriorToFormat = editor.getCursorScreenPosition();
+  const textToTransform = editor.getTextInBufferRange(bufferRange);
+  const transformed = executePrettierOrPrettierEslint(editor, textToTransform);
+
+  const isTextUnchanged = transformed === textToTransform;
+  if (!transformed || isTextUnchanged) return;
+
+  editor.setTextInBufferRange(bufferRange, transformed);
+  editor.setCursorScreenPosition(cursorPositionPriorToFormat);
+  runLinter(editor);
+};
+
+module.exports = executePrettierOnBufferRange;
diff --git a/src/executePrettier/executePrettierOnEmbeddedScripts.js b/src/executePrettier/executePrettierOnEmbeddedScripts.js
new file mode 100644
index 0000000..28a5cd5
--- /dev/null
+++ b/src/executePrettier/executePrettierOnEmbeddedScripts.js
@@ -0,0 +1,22 @@
+// @flow
+const executePrettierOnBufferRange = require('./executePrettierOnBufferRange');
+
+const EMBEDDED_JS_REGEX = /<script\b[^>]*>([\s\S]*?)(?=<\/script>)/gi;
+
+const executePrettierOnEmbeddedScripts = (editor: TextEditor) =>
+  editor.backwardsScanInBufferRange(EMBEDDED_JS_REGEX, editor.getBuffer().getRange(), (iter) => {
+    const { start, end } = iter.range;
+
+    // Skip formatting when <script> and </script> on the same line
+    if (start.row === end.row) return;
+
+    // Create new range with start row advanced by 1,
+    // since we cannot use look-behind on variable-length starting
+    // <script ...> tag
+    const startModified = [start.row + 1, start.column];
+    const bufferRange = new iter.range.constructor(startModified, end);
+
+    executePrettierOnBufferRange(editor, bufferRange);
+  });
+
+module.exports = executePrettierOnEmbeddedScripts;
diff --git a/src/executePrettier/getPrettierInstance.js b/src/executePrettier/getPrettierInstance.js
new file mode 100644
index 0000000..81de153
--- /dev/null
+++ b/src/executePrettier/getPrettierInstance.js
@@ -0,0 +1,36 @@
+// @flow
+const _ = require('lodash/fp');
+const path = require('path');
+const bundledPrettier = require('prettier');
+const { getCurrentFilePath } = require('../editorInterface');
+const { findCachedFromFilePath } = require('../helpers');
+
+const PRETTIER_INDEX_PATH = path.join('node_modules', 'prettier', 'index.js');
+
+const getLocalPrettierPath = (filePath: ?FilePath): ?FilePath =>
+  findCachedFromFilePath(filePath, PRETTIER_INDEX_PATH);
+
+// const getLocalPrettierPath = (filePath: ?FilePath): ?FilePath => {
+//   if (!filePath) return null;
+//
+//   const dirPath = getDirFromFilePath(filePath);
+//
+//   return dirPath ? findCached(dirPath, PRETTIER_INDEX_PATH) : null;
+// };
+
+// charypar: This is currently the best way to use local prettier instance.
+// Using the CLI introduces a noticeable delay and there is currently no
+// way to use prettier as a long-running process for formatting files as needed
+//
+// See https://github.com/prettier/prettier/issues/918
+const requireWithFallbackToBundledPrettier = (prettierPackagePath: ?string): typeof bundledPrettier =>
+  // $$FlowFixMe
+  prettierPackagePath ? require(prettierPackagePath) : bundledPrettier; // eslint-disable-line
+
+const getPrettierInstance: (editor: TextEditor) => typeof bundledPrettier = _.flow(
+  getCurrentFilePath,
+  getLocalPrettierPath,
+  requireWithFallbackToBundledPrettier,
+);
+
+module.exports = getPrettierInstance;
diff --git a/src/executePrettier/handleError.js b/src/executePrettier/handleError.js
new file mode 100644
index 0000000..e56236e
--- /dev/null
+++ b/src/executePrettier/handleError.js
@@ -0,0 +1,17 @@
+// @flow
+const _ = require('lodash/fp');
+const { shouldDisplayErrors, addErrorNotification } = require('../atomInterface');
+
+const displayError = (error: Error) =>
+  addErrorNotification('prettier-atom failed!', {
+    detail: error.toString(),
+    stack: error.stack,
+    dismissable: true,
+  });
+
+const handleError: (error: Error) => false = _.flow(
+  _.cond([[shouldDisplayErrors, displayError]]),
+  _.stubFalse,
+);
+
+module.exports = handleError;
diff --git a/src/executePrettier/index.js b/src/executePrettier/index.js
new file mode 100644
index 0000000..3db081d
--- /dev/null
+++ b/src/executePrettier/index.js
@@ -0,0 +1,8 @@
+// @flow
+const executePrettierOnBufferRange = require('./executePrettierOnBufferRange');
+const executePrettierOnEmbeddedScripts = require('./executePrettierOnEmbeddedScripts.js');
+
+module.exports = {
+  executePrettierOnBufferRange,
+  executePrettierOnEmbeddedScripts,
+};
diff --git a/src/format.js b/src/format.js
deleted file mode 100644
index 277de03..0000000
--- a/src/format.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// @flow
-const { executePrettierOnBufferRange, executePrettierOnEmbeddedScripts } = require('./executePrettier');
-const { isCurrentScopeEmbeddedScope } = require('./helpers');
-
-const formatSelectedBufferRanges = (editor: TextEditor) =>
-  editor.getSelectedBufferRanges().forEach(bufferRange => executePrettierOnBufferRange(editor, bufferRange));
-
-const format = (editor: TextEditor) => {
-  if (editor.getSelectedText()) {
-    formatSelectedBufferRanges(editor);
-  } else if (isCurrentScopeEmbeddedScope(editor)) {
-    executePrettierOnEmbeddedScripts(editor);
-  } else {
-    executePrettierOnBufferRange(editor, editor.getBuffer().getRange());
-  }
-};
-
-module.exports = format;
diff --git a/src/formatOnSave.js b/src/formatOnSave.js
deleted file mode 100644
index 49db2d8..0000000
--- a/src/formatOnSave.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// @flow
-const { executePrettierOnBufferRange, executePrettierOnEmbeddedScripts } = require('./executePrettier');
-const {
-  getCurrentFilePath,
-  isCurrentScopeEmbeddedScope,
-  isFilePathEslintignored,
-  isFilePathExcluded,
-  isInScope,
-  isFilePathWhitelisted,
-} = require('./helpers');
-
-const { isFormatOnSaveEnabled, isWhitelistProvided, shouldRespectEslintignore } = require('./options');
-
-const formatOnSaveIfAppropriate = (editor: TextEditor) => {
-  if (!isFormatOnSaveEnabled()) return;
-
-  const filePath = getCurrentFilePath(editor);
-
-  if (!filePath) return;
-
-  if (filePath && isWhitelistProvided() && !isFilePathWhitelisted(filePath)) {
-    return;
-  }
-
-  if (filePath && !isFilePathWhitelisted(filePath)) {
-    if (!isInScope(editor)) return;
-    if (filePath && isFilePathExcluded(filePath)) return;
-  }
-  if (filePath && shouldRespectEslintignore() && isFilePathEslintignored(filePath)) return;
-
-  if (isCurrentScopeEmbeddedScope(editor)) {
-    executePrettierOnEmbeddedScripts(editor);
-  } else {
-    executePrettierOnBufferRange(editor, editor.getBuffer().getRange());
-  }
-};
-
-module.exports = formatOnSaveIfAppropriate;
diff --git a/src/formatOnSave/index.js b/src/formatOnSave/index.js
new file mode 100644
index 0000000..aaf8418
--- /dev/null
+++ b/src/formatOnSave/index.js
@@ -0,0 +1,12 @@
+// @flow
+const { isCurrentScopeEmbeddedScope, getBufferRange } = require('../editorInterface');
+const { executePrettierOnBufferRange, executePrettierOnEmbeddedScripts } = require('../executePrettier');
+const shouldFormatOnSave = require('./shouldFormatOnSave');
+
+const formatOnSaveIfAppropriate = (editor: TextEditor) =>
+  shouldFormatOnSave(editor) &&
+  (isCurrentScopeEmbeddedScope(editor)
+    ? executePrettierOnEmbeddedScripts(editor)
+    : executePrettierOnBufferRange(editor, getBufferRange(editor)));
+
+module.exports = formatOnSaveIfAppropriate;
diff --git a/src/formatOnSave/isFilePathEslintIgnored.js b/src/formatOnSave/isFilePathEslintIgnored.js
new file mode 100644
index 0000000..3818d1d
--- /dev/null
+++ b/src/formatOnSave/isFilePathEslintIgnored.js
@@ -0,0 +1,32 @@
+// @flow
+const _ = require('lodash/fp');
+const path = require('path');
+const fs = require('fs');
+const { findCachedFromFilePath, getDirFromFilePath, someGlobsMatchFilePath } = require('../helpers');
+
+const LINE_SEPERATOR_REGEX = /(\r|\n|\r\n)/;
+
+const getNearestEslintignorePath = (filePath: FilePath): ?FilePath =>
+  findCachedFromFilePath(filePath, '.eslintignore');
+
+const safeRelativePath = _.curry(
+  (from: ?FilePath, to: ?FilePath): ?FilePath => (!!from && !!to ? path.relative(from, to) : undefined),
+);
+
+const getFilePathRelativeToEslintignore = (filePath: FilePath): ?FilePath =>
+  _.flow(getNearestEslintignorePath, getDirFromFilePath, safeRelativePath(_, filePath))(filePath);
+
+const getLinesFromFilePath = (filePath: ?FilePath) =>
+  !!filePath && filePath.length > 0 ? fs.readFileSync(filePath, 'utf8').split(LINE_SEPERATOR_REGEX) : [];
+
+const getIgnoredGlobsFromNearestEslintIgnore: (filePath: FilePath) => Globs = _.flow(
+  getNearestEslintignorePath,
+  getLinesFromFilePath,
+);
+
+const isFilePathEslintignored: (filePath: FilePath) => boolean = _.flow(
+  _.over([getIgnoredGlobsFromNearestEslintIgnore, getFilePathRelativeToEslintignore]),
+  _.spread(someGlobsMatchFilePath),
+);
+
+module.exports = isFilePathEslintignored;
diff --git a/src/formatOnSave/shouldFormatOnSave.js b/src/formatOnSave/shouldFormatOnSave.js
new file mode 100644
index 0000000..f424358
--- /dev/null
+++ b/src/formatOnSave/shouldFormatOnSave.js
@@ -0,0 +1,47 @@
+// @flow
+const _ = require('lodash/fp');
+const { someGlobsMatchFilePath } = require('../helpers');
+const { getCurrentFilePath, getCurrentScope } = require('../editorInterface');
+const {
+  isFormatOnSaveEnabled,
+  getScopes,
+  getExcludedGlobs,
+  getWhitelistedGlobs,
+} = require('../atomInterface');
+const isFilePathEslintignored = require('./isFilePathEslintIgnored');
+
+const hasFilePath = (editor: TextEditor) => !!getCurrentFilePath(editor);
+
+const isInScope = (editor: TextEditor) => getScopes().includes(getCurrentScope(editor));
+
+const filePathDoesNotMatchBlacklistGlobs: (
+  editor: TextEditor,
+) => boolean = _.flow(getCurrentFilePath, (filePath: FilePath) =>
+  _.negate(someGlobsMatchFilePath)(getExcludedGlobs(), filePath),
+);
+
+const noWhitelistGlobsPresent: () => boolean = _.flow(getWhitelistedGlobs, _.isEmpty);
+
+const isFilePathWhitelisted: (
+  editor: TextEditor,
+) => boolean = _.flow(getCurrentFilePath, (filePath: FilePath) =>
+  someGlobsMatchFilePath(getWhitelistedGlobs(), filePath),
+);
+
+const isFilePathNotEslintignored: (editor: TextEditor) => boolean = _.flow(
+  getCurrentFilePath,
+  _.negate(isFilePathEslintignored),
+);
+
+const shouldFormatOnSave: (editor: TextEditor) => boolean = _.overEvery([
+  isFormatOnSaveEnabled,
+  hasFilePath,
+  isInScope,
+  _.overSome([
+    isFilePathWhitelisted,
+    _.overEvery([noWhitelistGlobsPresent, filePathDoesNotMatchBlacklistGlobs]),
+  ]),
+  isFilePathNotEslintignored,
+]);
+
+module.exports = shouldFormatOnSave;
diff --git a/src/helpers.js b/src/helpers.js
deleted file mode 100644
index 6d8a9ac..0000000
--- a/src/helpers.js
+++ /dev/null
@@ -1,130 +0,0 @@
-// @flow
-const { findCached } = require('atom-linter');
-const fs = require('fs');
-const ignore = require('ignore');
-const path = require('path');
-const bundledPrettier = require('prettier');
-const readPkg = require('read-pkg');
-
-// constants
-const LINE_SEPERATOR_REGEX = /(\r|\n|\r\n)/;
-const EMBEDDED_SCOPES = ['text.html.vue', 'text.html.basic'];
-const LINTER_LINT_COMMAND = 'linter:lint';
-
-// local helpers
-const getCurrentScope = (editor: TextEditor) => editor.getGrammar().scopeName;
-
-// robwise: my apologies for this one, but I love function composition and want to use one that is Facebook
-// flow inferrable. See https://drboolean.gitbooks.io/mostly-adequate-guide/ch5.html
-const flow = (func: Function, ...funcs: Array<Function>) => (...args) =>
-  funcs.length ? flow(...funcs)(func(...args)) : func(...args);
-
-const getDirFromFilePath = (filePath: FilePath): FilePath => path.parse(filePath).dir;
-
-const getNearestEslintignorePath = (filePath: FilePath): ?FilePath =>
-  findCached(getDirFromFilePath(filePath), '.eslintignore');
-
-const getLocalPrettierPath = (filePath: ?FilePath): ?FilePath => {
-  if (!filePath) return null;
-
-  const indexPath = path.join('node_modules', 'prettier', 'index.js');
-  const dirPath = getDirFromFilePath(filePath);
-
-  return dirPath ? findCached(dirPath, indexPath) : null;
-};
-
-const getPrettier = (filePath: ?FilePath) => {
-  const prettierPath = getLocalPrettierPath(filePath);
-
-  // charypar: This is currently the best way to use local prettier instance.
-  // Using the CLI introduces a noticeable delay and there is currently no
-  // way to use prettier as a long-running process for formatting files as needed
-  //
-  // See https://github.com/prettier/prettier/issues/918
-  //
-  // $FlowFixMe when possible, don't use dynamic require
-  return prettierPath ? require(prettierPath) : bundledPrettier; // eslint-disable-line
-};
-
-const getFilePathRelativeToEslintignore = (filePath: FilePath): ?FilePath => {
-  const nearestEslintignorePath = getNearestEslintignorePath(filePath);
-
-  if (!nearestEslintignorePath) return undefined;
-
-  const eslintignoreDir = getDirFromFilePath(nearestEslintignorePath);
-
-  return path.relative(eslintignoreDir, filePath);
-};
-
-const getLinesFromFilePath = (filePath: FilePath) =>
-  fs.readFileSync(filePath, 'utf8').split(LINE_SEPERATOR_REGEX);
-
-const getIgnoredGlobsFromNearestEslintIgnore: (filePath: FilePath) => Globs = flow(
-  getNearestEslintignorePath,
-  maybePath => (maybePath ? getLinesFromFilePath(maybePath) : []),
-);
-
-const someGlobsMatchFilePath = (globs: Globs, filePath: FilePath) => ignore().add(globs).ignores(filePath);
-
-const isLinterLintCommandDefined = (editor: TextEditor) =>
-  atom.commands
-    .findCommands({ target: atom.views.getView(editor) })
-    .some(command => command.name === LINTER_LINT_COMMAND);
-
-const getDepPath = (dep: string) => path.join(__dirname, '../node_modules', dep);
-
-// public helpers
-const getConfigOption = (key: string) => atom.config.get(`prettier-atom.${key}`);
-
-const setConfigOption = (key: string, value: any) => atom.config.set(`prettier-atom.${key}`, value);
-
-const getCurrentFilePath = (editor: TextEditor) => (editor.buffer.file ? editor.buffer.file.path : undefined);
-
-const isInScope = (editor: TextEditor) =>
-  getConfigOption('formatOnSaveOptions.scopes').includes(getCurrentScope(editor));
-
-const isCurrentScopeEmbeddedScope = (editor: TextEditor) => EMBEDDED_SCOPES.includes(getCurrentScope(editor));
-
-const isFilePathEslintignored = (filePath: FilePath) => {
-  const filePathRelativeToEslintignore = getFilePathRelativeToEslintignore(filePath);
-
-  if (!filePathRelativeToEslintignore) return false;
-
-  return someGlobsMatchFilePath(
-    getIgnoredGlobsFromNearestEslintIgnore(filePath),
-    filePathRelativeToEslintignore,
-  );
-};
-
-const isFilePathExcluded = (filePath: FilePath) =>
-  someGlobsMatchFilePath(getConfigOption('formatOnSaveOptions.excludedGlobs'), filePath);
-
-const isFilePathWhitelisted = (filePath: FilePath) =>
-  someGlobsMatchFilePath(getConfigOption('formatOnSaveOptions.whitelistedGlobs'), filePath);
-
-const runLinter = (editor: TextEditor) =>
-  isLinterLintCommandDefined(editor)
-    ? atom.commands.dispatch(atom.views.getView(editor), LINTER_LINT_COMMAND)
-    : undefined;
-
-const getDebugInfo = () => ({
-  atomVersion: atom.getVersion(),
-  prettierAtomVersion: readPkg.sync().version,
-  prettierVersion: readPkg.sync(getDepPath('prettier')).version,
-  prettierESLintVersion: readPkg.sync(getDepPath('prettier-eslint')).version,
-  prettierAtomConfig: atom.config.get('prettier-atom'),
-});
-
-module.exports = {
-  getConfigOption,
-  setConfigOption,
-  getCurrentFilePath,
-  getPrettier,
-  isInScope,
-  isCurrentScopeEmbeddedScope,
-  isFilePathEslintignored,
-  isFilePathExcluded,
-  isFilePathWhitelisted,
-  runLinter,
-  getDebugInfo,
-};
diff --git a/src/helpers/index.js b/src/helpers/index.js
new file mode 100644
index 0000000..adda013
--- /dev/null
+++ b/src/helpers/index.js
@@ -0,0 +1,29 @@
+// @flow
+const _ = require('lodash/fp');
+const path = require('path');
+const ignore = require('ignore');
+const { findCached } = require('atom-linter');
+
+const isPresent = (target: any): boolean =>
+  !!target && (typeof target.length === 'undefined' || target.length > 0);
+
+const safePathParse = (filePath: FilePath) =>
+  typeof filePath === 'string' && filePath.length > 0 ? path.parse(filePath) : undefined;
+
+const getDirFromFilePath: (filePath: ?FilePath) => ?FilePath = _.flow(safePathParse, _.get('dir'));
+
+const someGlobsMatchFilePath = (globs: Globs, filePath: ?FilePath) =>
+  isPresent(filePath) && ignore().add(globs).ignores(filePath);
+
+const findCachedFromFilePath = (filePath: ?FilePath, name: string | Array<string>): ?FilePath =>
+  _.flow(
+    getDirFromFilePath,
+    (dirPath: FilePath): ?FilePath => (isPresent(dirPath) ? findCached(dirPath, name) : undefined),
+  )(filePath);
+
+module.exports = {
+  isPresent,
+  getDirFromFilePath,
+  someGlobsMatchFilePath,
+  findCachedFromFilePath,
+};
diff --git a/src/main.js b/src/main.js
index fa27827..101b0e9 100644
--- a/src/main.js
+++ b/src/main.js
@@ -16,7 +16,7 @@ let tileElement = null;
 
 // HACK: lazy load most of the code we need for performance
 const lazyFormat = () => {
-  if (!format) format = require('./format'); // eslint-disable-line global-require
+  if (!format) format = require('./manualFormat'); // eslint-disable-line global-require
 
   const editor = atom.workspace.getActiveTextEditor();
   if (editor) format(editor);
@@ -51,7 +51,7 @@ const lazyDisplayDebugInfo = () => {
 const lazyToggleFormatOnSave = () => {
   if (!toggleFormatOnSave) {
     // eslint-disable-next-line global-require
-    toggleFormatOnSave = require('./toggleFormatOnSave');
+    toggleFormatOnSave = require('./atomInterface').toggleFormatOnSave;
   }
   toggleFormatOnSave();
 };
@@ -109,7 +109,7 @@ const activate = () => {
   );
 
   // HACK: an Atom bug seems to be causing old configuration settings to linger for some users
-  //       https://github.com/jlongster/prettier-atom/issues/72
+  //       https://github.com/prettier/prettier-atom/issues/72
   atom.config.unset('prettier-atom.singleQuote');
   atom.config.unset('prettier-atom.trailingComma');
 };
diff --git a/src/manualFormat/index.js b/src/manualFormat/index.js
new file mode 100644
index 0000000..2114332
--- /dev/null
+++ b/src/manualFormat/index.js
@@ -0,0 +1,20 @@
+// @flow
+const _ = require('lodash/fp');
+const { executePrettierOnBufferRange, executePrettierOnEmbeddedScripts } = require('../executePrettier');
+const { getBufferRange, isCurrentScopeEmbeddedScope } = require('../editorInterface');
+
+const hasSelectedText = (editor: TextEditor) => !!editor.getSelectedText();
+
+const formatSelectedBufferRanges = (editor: TextEditor) =>
+  editor.getSelectedBufferRanges().forEach(bufferRange => executePrettierOnBufferRange(editor, bufferRange));
+
+const executePrettierOnCurrentBufferRange = (editor: TextEditor) =>
+  executePrettierOnBufferRange(editor, getBufferRange(editor));
+
+const format: (editor: TextEditor) => void = _.cond([
+  [hasSelectedText, formatSelectedBufferRanges],
+  [isCurrentScopeEmbeddedScope, executePrettierOnEmbeddedScripts],
+  [_.stubTrue, executePrettierOnCurrentBufferRange],
+]);
+
+module.exports = format;
diff --git a/src/options.js b/src/options.js
deleted file mode 100644
index 1ba1dd0..0000000
--- a/src/options.js
+++ /dev/null
@@ -1,81 +0,0 @@
-// @flow
-const editorconfig = require('editorconfig');
-
-const { getCurrentFilePath, getConfigOption } = require('./helpers');
-
-const getPrettierOption = (key: string) => getConfigOption(`prettierOptions.${key}`);
-
-const getPrettierEslintOption = (key: string) => getConfigOption(`prettierEslintOptions.${key}`);
-
-const shouldUseEslint = () => getConfigOption('useEslint');
-
-const shouldDisplayErrors = () => !getConfigOption('silenceErrors');
-
-const getAtomTabLength = (editor: TextEditor) =>
-  atom.config.get('editor.tabLength', { scope: editor.getLastCursor().getScopeDescriptor() });
-
-const useAtomTabLengthIfAuto = (editor, tabLength) =>
-  tabLength === 'auto' ? getAtomTabLength(editor) : Number(tabLength);
-
-const mapEditorConfigOptions = (options: Object) => {
-  const { indent_style: indentStyle, tab_width: tabWidth, max_line_length: printWidth } = options;
-  return {
-    ...(tabWidth ? { tabWidth } : null),
-    ...(printWidth ? { printWidth } : null),
-    ...(indentStyle ? { useTabs: indentStyle === 'tab' } : null),
-  };
-};
-
-const getEditorConfigOptions = (file: FilePath) => {
-  const options = editorconfig.parseSync(file);
-  return options ? mapEditorConfigOptions(options) : null;
-};
-
-const getPrettierOptions = (editor: TextEditor) => {
-  const filePath = getCurrentFilePath(editor);
-  const optionsFromSettings = {
-    printWidth: getPrettierOption('printWidth'),
-    tabWidth: useAtomTabLengthIfAuto(editor, getPrettierOption('tabWidth')),
-    parser: getPrettierOption('parser'),
-    singleQuote: getPrettierOption('singleQuote'),
-    trailingComma: getPrettierOption('trailingComma'),
-    bracketSpacing: getPrettierOption('bracketSpacing'),
-    semi: getPrettierOption('semi'),
-    useTabs: getPrettierOption('useTabs'),
-    jsxBracketSameLine: getPrettierOption('jsxBracketSameLine'),
-  };
-  if (!filePath || !getConfigOption('useEditorConfig')) {
-    return optionsFromSettings;
-  }
-  return {
-    ...optionsFromSettings,
-    ...getEditorConfigOptions(filePath),
-  };
-};
-
-const getPrettierEslintOptions = () => ({
-  prettierLast: getPrettierEslintOption('prettierLast'),
-});
-
-const isFormatOnSaveEnabled = () => getConfigOption('formatOnSaveOptions.enabled');
-
-const shouldRespectEslintignore = () => getConfigOption('formatOnSaveOptions.respectEslintignore');
-
-const isLinterEslintAutofixEnabled = () => atom.config.get('linter-eslint.fixOnSave');
-
-const isWhitelistProvided = () => getConfigOption('formatOnSaveOptions.whitelistedGlobs').length > 0;
-
-module.exports = {
-  getConfigOption,
-  getEditorConfigOptions,
-  getPrettierOption,
-  getPrettierOptions,
-  getPrettierEslintOption,
-  getPrettierEslintOptions,
-  shouldUseEslint,
-  shouldDisplayErrors,
-  isWhitelistProvided,
-  isFormatOnSaveEnabled,
-  isLinterEslintAutofixEnabled,
-  shouldRespectEslintignore,
-};
diff --git a/src/statusTile.js b/src/statusTile.js
deleted file mode 100644
index bf2f877..0000000
--- a/src/statusTile.js
+++ /dev/null
@@ -1,43 +0,0 @@
-// @flow
-const { getConfigOption } = require('./helpers');
-const toggleFormatOnSave = require('./toggleFormatOnSave');
-
-let tooltip: Atom$Disposable = null;
-
-const getFormatOnSaveStatus = () => (getConfigOption('formatOnSaveOptions.enabled') ? 'enabled' : 'disabled');
-
-const createStatusTile = () => {
-  const element = document.createElement('div');
-  element.classList.add('prettier-atom-status-tile');
-  element.classList.add('inline-block');
-  element.dataset.formatOnSave = getFormatOnSaveStatus();
-  element.addEventListener('click', toggleFormatOnSave);
-
-  element.appendChild(document.createTextNode('Prettier'));
-
-  return element;
-};
-
-const disposeTooltip = () => {
-  if (tooltip) {
-    tooltip.dispose();
-  }
-};
-
-const updateStatusTile = (disposable: Atom$Disposable, element: HTMLElement) => {
-  // eslint-disable-next-line no-param-reassign
-  element.dataset.formatOnSave = getFormatOnSaveStatus();
-
-  disposeTooltip();
-
-  tooltip = atom.tooltips.add(element, { title: `Format on Save: ${getFormatOnSaveStatus()}` });
-  disposable.add(tooltip);
-
-  return tooltip;
-};
-
-module.exports = {
-  createStatusTile,
-  updateStatusTile,
-  disposeTooltip,
-};
diff --git a/src/statusTile/getFormatOnSaveStatus.js b/src/statusTile/getFormatOnSaveStatus.js
new file mode 100644
index 0000000..aacfe9b
--- /dev/null
+++ b/src/statusTile/getFormatOnSaveStatus.js
@@ -0,0 +1,6 @@
+// @flow
+const { isFormatOnSaveEnabled } = require('../atomInterface');
+
+const getFormatOnSaveStatus = () => (isFormatOnSaveEnabled() ? 'enabled' : 'disabled');
+
+module.exports = getFormatOnSaveStatus;
diff --git a/src/statusTile/index.js b/src/statusTile/index.js
new file mode 100644
index 0000000..d61c788
--- /dev/null
+++ b/src/statusTile/index.js
@@ -0,0 +1,10 @@
+// @flow
+const createStatusTile = require('./createStatusTile');
+const updateStatusTile = require('./updateStatusTile');
+const { disposeTooltip } = require('./tooltip');
+
+module.exports = {
+  createStatusTile,
+  updateStatusTile,
+  disposeTooltip,
+};
diff --git a/src/statusTile/tooltip.js b/src/statusTile/tooltip.js
new file mode 100644
index 0000000..6e63795
--- /dev/null
+++ b/src/statusTile/tooltip.js
@@ -0,0 +1,13 @@
+// @flow
+let tooltip: ?Atom$Disposable = null;
+
+const disposeTooltip = () => tooltip && tooltip.dispose;
+
+const setTooltip = (nextTooltip: Atom$Disposable) => {
+  tooltip = nextTooltip;
+};
+
+module.exports = {
+  disposeTooltip,
+  setTooltip,
+};
diff --git a/src/toggleFormatOnSave.js b/src/toggleFormatOnSave.js
deleted file mode 100644
index d87112d..0000000
--- a/src/toggleFormatOnSave.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// @flow
-const { getConfigOption, setConfigOption } = require('./helpers');
-
-const FORMAT_ON_SAVE = 'formatOnSaveOptions.enabled';
-
-const toggleFormatOnSave = () => {
-  setConfigOption(FORMAT_ON_SAVE, !getConfigOption(FORMAT_ON_SAVE));
-};
-
-module.exports = toggleFormatOnSave;
diff --git a/src/warnAboutLinterEslintFixOnSave.js b/src/warnAboutLinterEslintFixOnSave/index.js
similarity index 60%
rename from src/warnAboutLinterEslintFixOnSave.js
rename to src/warnAboutLinterEslintFixOnSave/index.js
index 40a6cf2..298e76f 100644
--- a/src/warnAboutLinterEslintFixOnSave.js
+++ b/src/warnAboutLinterEslintFixOnSave/index.js
@@ -1,14 +1,12 @@
 // @flow
-const { shouldUseEslint, isLinterEslintAutofixEnabled } = require('./options');
+const { shouldUseEslint, isLinterEslintAutofixEnabled, addWarningNotification } = require('../atomInterface');
 
-const message =
+const MESSAGE =
   "prettier-atom: linter-eslint's `Fix on Save` feature is currently enabled. " +
   'We recommend disabling this feature when using the ESlint integration from prettier-atom';
+const OPTIONS = { dismissable: true };
 
-const options = { dismissable: true };
-
-// $FlowFixMe
-const displayWarning = () => atom.notifications.addWarning(message, options);
+const displayWarning = () => addWarningNotification(MESSAGE, OPTIONS);
 
 const warnAboutLinterEslintFixOnSave = () =>
   shouldUseEslint() && isLinterEslintAutofixEnabled() && displayWarning();
diff --git a/yarn.lock b/yarn.lock
index 19e539f..a2a5d1f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -57,7 +57,7 @@ ansi-escapes@^1.1.0, ansi-escapes@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
 
-ansi-regex@^2.0.0:
+ansi-regex@^2.0.0, ansi-regex@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
 
@@ -157,7 +157,7 @@ async-each@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
 
-async@^1.4.0, async@^1.4.2:
+async@^1.4.0:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
@@ -381,13 +381,13 @@ babel-helpers@^6.24.1:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-jest@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-19.0.0.tgz#59323ced99a3a84d359da219ca881074ffc6ce3f"
+babel-jest@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-20.0.1.tgz#9cbe9a15bbe3f1ca1b727dc8e45a4161771d3655"
   dependencies:
     babel-core "^6.0.0"
     babel-plugin-istanbul "^4.0.0"
-    babel-preset-jest "^19.0.0"
+    babel-preset-jest "^20.0.1"
 
 babel-messages@^6.23.0:
   version "6.23.0"
@@ -409,9 +409,9 @@ babel-plugin-istanbul@^4.0.0:
     istanbul-lib-instrument "^1.4.2"
     test-exclude "^4.0.0"
 
-babel-plugin-jest-hoist@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-19.0.0.tgz#4ae2a04ea612a6e73651f3fde52c178991304bea"
+babel-plugin-jest-hoist@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-20.0.1.tgz#1b9cc322cff704d3812d1bca8dccd12205eedfd5"
 
 babel-plugin-syntax-async-functions@^6.8.0:
   version "6.13.0"
@@ -729,11 +729,11 @@ babel-preset-env@^1.4.0:
     browserslist "^1.4.0"
     invariant "^2.2.2"
 
-babel-preset-jest@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-19.0.0.tgz#22d67201d02324a195811288eb38294bb3cac396"
+babel-preset-jest@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-20.0.1.tgz#8a9e23ce8a0f0c49835de53ed73ecf75dd6daa2e"
   dependencies:
-    babel-plugin-jest-hoist "^19.0.0"
+    babel-plugin-jest-hoist "^20.0.1"
 
 babel-preset-stage-2@^6.24.1:
   version "6.24.1"
@@ -1041,9 +1041,9 @@ code-point-at@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
 
-codecov@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/codecov/-/codecov-2.1.0.tgz#25f48f9e9aa7473b61c5a9a934d595420a71cade"
+codecov@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/codecov/-/codecov-2.2.0.tgz#2d06817ceb8891eca6368836d4fb6bf6cc04ffd1"
   dependencies:
     argv "0.0.2"
     request "2.79.0"
@@ -1135,7 +1135,7 @@ conventional-commit-types@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/conventional-commit-types/-/conventional-commit-types-2.1.0.tgz#45d860386c9a2e6537ee91d8a1b61bd0411b3d04"
 
-convert-source-map@^1.1.0:
+convert-source-map@^1.1.0, convert-source-map@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.4.0.tgz#e3dad195bf61bfe13a7a3c73e9876ec14a0268f3"
 
@@ -1213,6 +1213,12 @@ debug@^2.1.1, debug@^2.2.0:
   dependencies:
     ms "0.7.2"
 
+debug@^2.6.3:
+  version "2.6.6"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.6.tgz#a9fa6fbe9ca43cf1e79f73b75c0189cbb7d6db5a"
+  dependencies:
+    ms "0.7.3"
+
 decamelize@^1.0.0, decamelize@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -1267,7 +1273,7 @@ detect-indent@4.0.0, detect-indent@^4.0.0:
   dependencies:
     repeating "^2.0.0"
 
-diff@^3.0.0:
+diff@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
 
@@ -1440,9 +1446,9 @@ eslint-plugin-import@^2.2.0:
     minimatch "^3.0.3"
     pkg-up "^1.0.0"
 
-eslint-plugin-jest@^19.0.1:
-  version "19.0.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-19.0.1.tgz#42a420e90e81aa74e162c16166e43a31b890eece"
+eslint-plugin-jest@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-20.0.1.tgz#ddb8a7d30da81e8839e2b4e25907d2ac45d27aa4"
 
 eslint@^3.19.0:
   version "3.19.0"
@@ -1706,9 +1712,9 @@ flat-cache@^1.2.1:
     graceful-fs "^4.1.2"
     write "^0.2.1"
 
-flow-bin@^0.45.0:
-  version "0.45.0"
-  resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.45.0.tgz#009dd0f577a3f665c74ca8be827ae8c2dd8fd6b5"
+flow-bin@^0.46.0:
+  version "0.46.0"
+  resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.46.0.tgz#06ad7fe19dddb1042264438064a2a32fee12b872"
 
 flow-parser@0.45.0:
   version "0.45.0"
@@ -1933,7 +1939,7 @@ globby@^5.0.0:
     pify "^2.0.0"
     pinkie-promise "^2.0.0"
 
-graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
+graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
 
@@ -2047,9 +2053,9 @@ iconv-lite@0.4.13:
   version "0.4.13"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
 
-ignore@^3.2.0, ignore@^3.2.7:
-  version "3.2.7"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.7.tgz#4810ca5f1d8eca5595213a34b94f2eb4ed926bbd"
+ignore@^3.2.0, ignore@^3.2.7, ignore@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.0.tgz#3812d22cbe9125f2c2b4915755a1b8abd745a001"
 
 imurmurhash@^0.1.4:
   version "0.1.4"
@@ -2163,7 +2169,7 @@ is-builtin-module@^1.0.0:
   dependencies:
     builtin-modules "^1.0.0"
 
-is-ci@^1.0.9:
+is-ci@^1.0.10, is-ci@^1.0.9:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e"
   dependencies:
@@ -2300,33 +2306,33 @@ isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
 
-istanbul-api@^1.1.0-alpha.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.1.tgz#d36e2f1560d1a43ce304c4ff7338182de61c8f73"
+istanbul-api@^1.1.1:
+  version "1.1.8"
+  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.8.tgz#a844e55c6f9aeee292e7f42942196f60b23dc93e"
   dependencies:
     async "^2.1.4"
     fileset "^2.0.2"
-    istanbul-lib-coverage "^1.0.0"
-    istanbul-lib-hook "^1.0.0"
-    istanbul-lib-instrument "^1.3.0"
-    istanbul-lib-report "^1.0.0-alpha.3"
-    istanbul-lib-source-maps "^1.1.0"
-    istanbul-reports "^1.0.0"
+    istanbul-lib-coverage "^1.1.0"
+    istanbul-lib-hook "^1.0.6"
+    istanbul-lib-instrument "^1.7.1"
+    istanbul-lib-report "^1.1.0"
+    istanbul-lib-source-maps "^1.2.0"
+    istanbul-reports "^1.1.0"
     js-yaml "^3.7.0"
     mkdirp "^0.5.1"
     once "^1.4.0"
 
-istanbul-lib-coverage@^1.0.0, istanbul-lib-coverage@^1.0.0-alpha, istanbul-lib-coverage@^1.0.0-alpha.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.1.tgz#f263efb519c051c5f1f3343034fc40e7b43ff212"
+istanbul-lib-coverage@^1.0.0, istanbul-lib-coverage@^1.0.0-alpha.0, istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.0.tgz#caca19decaef3525b5d6331d701f3f3b7ad48528"
 
-istanbul-lib-hook@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.0.tgz#fc5367ee27f59268e8f060b0c7aaf051d9c425c5"
+istanbul-lib-hook@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.6.tgz#c0866d1e81cf2d5319249510131fc16dee49231f"
   dependencies:
     append-transform "^0.4.0"
 
-istanbul-lib-instrument@^1.1.1, istanbul-lib-instrument@^1.3.0, istanbul-lib-instrument@^1.4.2:
+istanbul-lib-instrument@^1.4.2:
   version "1.4.2"
   resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.4.2.tgz#0e2fdfac93c1dabf2e31578637dc78a19089f43e"
   dependencies:
@@ -2338,15 +2344,25 @@ istanbul-lib-instrument@^1.1.1, istanbul-lib-instrument@^1.3.0, istanbul-lib-ins
     istanbul-lib-coverage "^1.0.0"
     semver "^5.3.0"
 
-istanbul-lib-report@^1.0.0-alpha.3:
-  version "1.0.0-alpha.3"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.0.0-alpha.3.tgz#32d5f6ec7f33ca3a602209e278b2e6ff143498af"
+istanbul-lib-instrument@^1.7.1:
+  version "1.7.1"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.1.tgz#169e31bc62c778851a99439dd99c3cc12184d360"
+  dependencies:
+    babel-generator "^6.18.0"
+    babel-template "^6.16.0"
+    babel-traverse "^6.18.0"
+    babel-types "^6.18.0"
+    babylon "^6.13.0"
+    istanbul-lib-coverage "^1.1.0"
+    semver "^5.3.0"
+
+istanbul-lib-report@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.0.tgz#444c4ecca9afa93cf584f56b10f195bf768c0770"
   dependencies:
-    async "^1.4.2"
-    istanbul-lib-coverage "^1.0.0-alpha"
+    istanbul-lib-coverage "^1.1.0"
     mkdirp "^0.5.1"
     path-parse "^1.0.5"
-    rimraf "^2.4.3"
     supports-color "^3.1.2"
 
 istanbul-lib-source-maps@^1.1.0:
@@ -2358,108 +2374,128 @@ istanbul-lib-source-maps@^1.1.0:
     rimraf "^2.4.4"
     source-map "^0.5.3"
 
-istanbul-reports@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.0.1.tgz#9a17176bc4a6cbebdae52b2f15961d52fa623fbc"
+istanbul-lib-source-maps@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.0.tgz#8c7706d497e26feeb6af3e0c28fd5b0669598d0e"
+  dependencies:
+    debug "^2.6.3"
+    istanbul-lib-coverage "^1.1.0"
+    mkdirp "^0.5.1"
+    rimraf "^2.6.1"
+    source-map "^0.5.3"
+
+istanbul-reports@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.0.tgz#1ef3b795889219cfb5fad16365f6ce108d5f8c66"
   dependencies:
     handlebars "^4.0.3"
 
-jest-changed-files@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-19.0.2.tgz#16c54c84c3270be408e06d2e8af3f3e37a885824"
+jest-changed-files@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-20.0.1.tgz#ba9bd42c3fddb1b7c4ae40065199b44a2335e152"
 
-jest-cli@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-19.0.2.tgz#cc3620b62acac5f2d93a548cb6ef697d4ec85443"
+jest-cli@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-20.0.1.tgz#86ca0bc2e47215ad8e7dc85455c0210f86648502"
   dependencies:
     ansi-escapes "^1.4.0"
     callsites "^2.0.0"
-    chalk "^1.1.1"
-    graceful-fs "^4.1.6"
-    is-ci "^1.0.9"
-    istanbul-api "^1.1.0-alpha.1"
-    istanbul-lib-coverage "^1.0.0"
-    istanbul-lib-instrument "^1.1.1"
-    jest-changed-files "^19.0.2"
-    jest-config "^19.0.2"
-    jest-environment-jsdom "^19.0.2"
-    jest-haste-map "^19.0.0"
-    jest-jasmine2 "^19.0.2"
-    jest-message-util "^19.0.0"
-    jest-regex-util "^19.0.0"
-    jest-resolve-dependencies "^19.0.0"
-    jest-runtime "^19.0.2"
-    jest-snapshot "^19.0.2"
-    jest-util "^19.0.2"
+    chalk "^1.1.3"
+    graceful-fs "^4.1.11"
+    is-ci "^1.0.10"
+    istanbul-api "^1.1.1"
+    istanbul-lib-coverage "^1.0.1"
+    istanbul-lib-instrument "^1.4.2"
+    istanbul-lib-source-maps "^1.1.0"
+    jest-changed-files "^20.0.1"
+    jest-config "^20.0.1"
+    jest-docblock "^20.0.1"
+    jest-environment-jsdom "^20.0.1"
+    jest-haste-map "^20.0.1"
+    jest-jasmine2 "^20.0.1"
+    jest-message-util "^20.0.1"
+    jest-regex-util "^20.0.1"
+    jest-resolve-dependencies "^20.0.1"
+    jest-runtime "^20.0.1"
+    jest-snapshot "^20.0.1"
+    jest-util "^20.0.1"
     micromatch "^2.3.11"
-    node-notifier "^5.0.1"
+    node-notifier "^5.0.2"
+    pify "^2.3.0"
     slash "^1.0.0"
     string-length "^1.0.1"
     throat "^3.0.0"
-    which "^1.1.1"
+    which "^1.2.12"
     worker-farm "^1.3.1"
-    yargs "^6.3.0"
+    yargs "^7.0.2"
 
-jest-config@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-19.0.2.tgz#1b9bd2db0ddd16df61c2b10a54009e1768da6411"
+jest-config@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-20.0.1.tgz#c6934f585c3e1775c96133efb302f986c3909ad8"
   dependencies:
-    chalk "^1.1.1"
-    jest-environment-jsdom "^19.0.2"
-    jest-environment-node "^19.0.2"
-    jest-jasmine2 "^19.0.2"
-    jest-regex-util "^19.0.0"
-    jest-resolve "^19.0.2"
-    jest-validate "^19.0.2"
-    pretty-format "^19.0.0"
-
-jest-diff@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-19.0.0.tgz#d1563cfc56c8b60232988fbc05d4d16ed90f063c"
+    chalk "^1.1.3"
+    glob "^7.1.1"
+    jest-environment-jsdom "^20.0.1"
+    jest-environment-node "^20.0.1"
+    jest-jasmine2 "^20.0.1"
+    jest-matcher-utils "^20.0.1"
+    jest-regex-util "^20.0.1"
+    jest-resolve "^20.0.1"
+    jest-validate "^20.0.1"
+    pretty-format "^20.0.1"
+
+jest-diff@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-20.0.1.tgz#2567c80c324243328321386f8871a28ec9d350ac"
   dependencies:
     chalk "^1.1.3"
-    diff "^3.0.0"
-    jest-matcher-utils "^19.0.0"
-    pretty-format "^19.0.0"
+    diff "^3.2.0"
+    jest-matcher-utils "^20.0.1"
+    pretty-format "^20.0.1"
 
-jest-environment-jsdom@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-19.0.2.tgz#ceda859c4a4b94ab35e4de7dab54b926f293e4a3"
-  dependencies:
-    jest-mock "^19.0.0"
-    jest-util "^19.0.2"
-    jsdom "^9.11.0"
+jest-docblock@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.1.tgz#055e0bbcb76798198479901f92d2733bf619f854"
 
-jest-environment-node@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-19.0.2.tgz#6e84079db87ed21d0c05e1f9669f207b116fe99b"
+jest-environment-jsdom@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-20.0.1.tgz#2d29f81368987d387c70ce4f500c6aa560f9b4f7"
   dependencies:
-    jest-mock "^19.0.0"
-    jest-util "^19.0.2"
+    jest-mock "^20.0.1"
+    jest-util "^20.0.1"
+    jsdom "^9.12.0"
 
-jest-file-exists@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-file-exists/-/jest-file-exists-19.0.0.tgz#cca2e587a11ec92e24cfeab3f8a94d657f3fceb8"
+jest-environment-node@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-20.0.1.tgz#75ab5358072ee1efebc54f43474357d7b3d674c7"
+  dependencies:
+    jest-mock "^20.0.1"
+    jest-util "^20.0.1"
 
-jest-haste-map@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-19.0.0.tgz#adde00b62b1fe04432a104b3254fc5004514b55e"
+jest-haste-map@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-20.0.1.tgz#e6ba4db99ab512e7c081a5b0a0af731d0e193d56"
   dependencies:
     fb-watchman "^2.0.0"
-    graceful-fs "^4.1.6"
+    graceful-fs "^4.1.11"
+    jest-docblock "^20.0.1"
     micromatch "^2.3.11"
-    sane "~1.5.0"
+    sane "~1.6.0"
     worker-farm "^1.3.1"
 
-jest-jasmine2@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-19.0.2.tgz#167991ac825981fb1a800af126e83afcca832c73"
+jest-jasmine2@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-20.0.1.tgz#675772b1fd32ad74e92e8ae8282f8ea71d1de168"
   dependencies:
-    graceful-fs "^4.1.6"
-    jest-matcher-utils "^19.0.0"
-    jest-matchers "^19.0.0"
-    jest-message-util "^19.0.0"
-    jest-snapshot "^19.0.2"
+    chalk "^1.1.3"
+    graceful-fs "^4.1.11"
+    jest-diff "^20.0.1"
+    jest-matcher-utils "^20.0.1"
+    jest-matchers "^20.0.1"
+    jest-message-util "^20.0.1"
+    jest-snapshot "^20.0.1"
+    once "^1.4.0"
+    p-map "^1.1.1"
 
 jest-matcher-utils@^19.0.0:
   version "19.0.0"
@@ -2468,87 +2504,93 @@ jest-matcher-utils@^19.0.0:
     chalk "^1.1.3"
     pretty-format "^19.0.0"
 
-jest-matchers@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-matchers/-/jest-matchers-19.0.0.tgz#c74ecc6ebfec06f384767ba4d6fa4a42d6755754"
+jest-matcher-utils@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-20.0.1.tgz#31aef67f59535af3c2271a3a3685db604dbd1622"
   dependencies:
-    jest-diff "^19.0.0"
-    jest-matcher-utils "^19.0.0"
-    jest-message-util "^19.0.0"
-    jest-regex-util "^19.0.0"
+    chalk "^1.1.3"
+    pretty-format "^20.0.1"
 
-jest-message-util@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-19.0.0.tgz#721796b89c0e4d761606f9ba8cb828a3b6246416"
+jest-matchers@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-matchers/-/jest-matchers-20.0.1.tgz#053b7654ce60129268f39992886e987a5201bb90"
   dependencies:
-    chalk "^1.1.1"
+    jest-diff "^20.0.1"
+    jest-matcher-utils "^20.0.1"
+    jest-message-util "^20.0.1"
+    jest-regex-util "^20.0.1"
+
+jest-message-util@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-20.0.1.tgz#ac21cb055a6a5786b7f127ac7e705df5ffb1c335"
+  dependencies:
+    chalk "^1.1.3"
     micromatch "^2.3.11"
+    slash "^1.0.0"
 
-jest-mock@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-19.0.0.tgz#67038641e9607ab2ce08ec4a8cb83aabbc899d01"
+jest-mock@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-20.0.1.tgz#f4cca2e87e441b66fabe4ead6a6d61773ec0773a"
 
-jest-regex-util@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-19.0.0.tgz#b7754587112aede1456510bb1f6afe74ef598691"
+jest-regex-util@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-20.0.1.tgz#ecbcca8fbe4e217bca7f6f42a9b831d051224dc4"
 
-jest-resolve-dependencies@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-19.0.0.tgz#a741ad1fa094140e64ecf2642a504f834ece22ee"
+jest-resolve-dependencies@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-20.0.1.tgz#38fc012191775b0b277fabebb37aa8282e26846f"
   dependencies:
-    jest-file-exists "^19.0.0"
+    jest-regex-util "^20.0.1"
 
-jest-resolve@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-19.0.2.tgz#5793575de4f07aec32f7d7ff0c6c181963eefb3c"
+jest-resolve@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-20.0.1.tgz#cace553663f25c703dc977a4ce176e29eda92772"
   dependencies:
     browser-resolve "^1.11.2"
-    jest-haste-map "^19.0.0"
-    resolve "^1.2.0"
+    is-builtin-module "^1.0.0"
+    resolve "^1.3.2"
 
-jest-runtime@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-19.0.2.tgz#d9a43e72de416d27d196fd9c7940d98fe6685407"
+jest-runtime@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-20.0.1.tgz#384f9298b8e8a177870c6d9ad0023db10ddcaedc"
   dependencies:
     babel-core "^6.0.0"
-    babel-jest "^19.0.0"
+    babel-jest "^20.0.1"
     babel-plugin-istanbul "^4.0.0"
     chalk "^1.1.3"
-    graceful-fs "^4.1.6"
-    jest-config "^19.0.2"
-    jest-file-exists "^19.0.0"
-    jest-haste-map "^19.0.0"
-    jest-regex-util "^19.0.0"
-    jest-resolve "^19.0.2"
-    jest-util "^19.0.2"
+    convert-source-map "^1.4.0"
+    graceful-fs "^4.1.11"
+    jest-config "^20.0.1"
+    jest-haste-map "^20.0.1"
+    jest-regex-util "^20.0.1"
+    jest-resolve "^20.0.1"
+    jest-util "^20.0.1"
     json-stable-stringify "^1.0.1"
     micromatch "^2.3.11"
     strip-bom "3.0.0"
-    yargs "^6.3.0"
+    yargs "^7.0.2"
 
-jest-snapshot@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-19.0.2.tgz#9c1b216214f7187c38bfd5c70b1efab16b0ff50b"
+jest-snapshot@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-20.0.1.tgz#3704c599705042f20ec7c95ba76a4524c744dfac"
   dependencies:
     chalk "^1.1.3"
-    jest-diff "^19.0.0"
-    jest-file-exists "^19.0.0"
-    jest-matcher-utils "^19.0.0"
-    jest-util "^19.0.2"
+    jest-diff "^20.0.1"
+    jest-matcher-utils "^20.0.1"
+    jest-util "^20.0.1"
     natural-compare "^1.4.0"
-    pretty-format "^19.0.0"
+    pretty-format "^20.0.1"
 
-jest-util@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-19.0.2.tgz#e0a0232a2ab9e6b2b53668bdb3534c2b5977ed41"
+jest-util@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-20.0.1.tgz#a3e7afb67110b2c3ac77b82e9a51ca57f4ff72a1"
   dependencies:
-    chalk "^1.1.1"
-    graceful-fs "^4.1.6"
-    jest-file-exists "^19.0.0"
-    jest-message-util "^19.0.0"
-    jest-mock "^19.0.0"
-    jest-validate "^19.0.2"
-    leven "^2.0.0"
+    chalk "^1.1.3"
+    graceful-fs "^4.1.11"
+    jest-message-util "^20.0.1"
+    jest-mock "^20.0.1"
+    jest-validate "^20.0.1"
+    leven "^2.1.0"
     mkdirp "^0.5.1"
 
 jest-validate@19.0.0:
@@ -2560,14 +2602,14 @@ jest-validate@19.0.0:
     leven "^2.0.0"
     pretty-format "^19.0.0"
 
-jest-validate@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.2.tgz#dc534df5f1278d5b63df32b14241d4dbf7244c0c"
+jest-validate@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-20.0.1.tgz#a236c29e3c29e9b92a1e5da211a732f0238da928"
   dependencies:
-    chalk "^1.1.1"
-    jest-matcher-utils "^19.0.0"
-    leven "^2.0.0"
-    pretty-format "^19.0.0"
+    chalk "^1.1.3"
+    jest-matcher-utils "^20.0.1"
+    leven "^2.1.0"
+    pretty-format "^20.0.1"
 
 jodid25519@^1.0.0:
   version "1.0.2"
@@ -2590,9 +2632,9 @@ jsbn@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
 
-jsdom@^9.11.0:
-  version "9.11.0"
-  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.11.0.tgz#a95b0304e521a2ca5a63c6ea47bf7708a7a84591"
+jsdom@^9.12.0:
+  version "9.12.0"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4"
   dependencies:
     abab "^1.0.3"
     acorn "^4.0.4"
@@ -2684,7 +2726,7 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
-leven@^2.0.0:
+leven@^2.0.0, leven@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
 
@@ -2755,11 +2797,11 @@ lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
 
-lodash@4.17.2, lodash@^4.0.0, lodash@^4.11.2, lodash@^4.15.0, lodash@^4.2.0:
+lodash@4.17.2:
   version "4.17.2"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.2.tgz#34a3055babe04ce42467b607d700072c7ff6bf42"
 
-lodash@^4.14.0, lodash@^4.17.4, lodash@^4.3.0:
+lodash@^4.0.0, lodash@^4.11.2, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0:
   version "4.17.4"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
 
@@ -2915,6 +2957,10 @@ ms@0.7.2:
   version "0.7.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
 
+ms@0.7.3:
+  version "0.7.3"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.3.tgz#708155a5e44e33f5fd0fc53e81d0d40a91be1fff"
+
 mute-stream@0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
@@ -2947,9 +2993,9 @@ node-int64@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
 
-node-notifier@^5.0.1:
-  version "5.0.2"
-  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.0.2.tgz#4438449fe69e321f941cef943986b0797032701b"
+node-notifier@^5.0.2:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff"
   dependencies:
     growly "^1.3.0"
     semver "^5.3.0"
@@ -3131,6 +3177,10 @@ p-locate@^2.0.0:
   dependencies:
     p-limit "^1.1.0"
 
+p-map@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a"
+
 pad-right@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/pad-right/-/pad-right-0.2.2.tgz#6fbc924045d244f2a2a244503060d3bfc6009774"
@@ -3247,9 +3297,9 @@ preserve@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
 
-prettier-eslint-cli@^3.4.2:
-  version "3.4.2"
-  resolved "https://registry.yarnpkg.com/prettier-eslint-cli/-/prettier-eslint-cli-3.4.2.tgz#93644ca20acc2ac7098831e98550bb0c0d13abdf"
+prettier-eslint-cli@^3.4.3:
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/prettier-eslint-cli/-/prettier-eslint-cli-3.4.3.tgz#bf96116f76ae4b938c9ce878997ca9a7a9475504"
   dependencies:
     arrify "^1.0.1"
     babel-runtime "^6.23.0"
@@ -3269,9 +3319,9 @@ prettier-eslint-cli@^3.4.2:
     rxjs "^5.3.0"
     yargs "^7.1.0"
 
-prettier-eslint@^6.0.0, prettier-eslint@^6.1.2:
-  version "6.1.2"
-  resolved "https://registry.yarnpkg.com/prettier-eslint/-/prettier-eslint-6.1.2.tgz#97984cbeb668d3b748cda92983898b2fa4850006"
+prettier-eslint@^6.0.0:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/prettier-eslint/-/prettier-eslint-6.2.0.tgz#1b5c9ea695be120fb2332ff81d6ae1c945ddbf06"
   dependencies:
     common-tags "^1.4.0"
     dlv "^1.1.0"
@@ -3279,13 +3329,27 @@ prettier-eslint@^6.0.0, prettier-eslint@^6.1.2:
     indent-string "^3.1.0"
     lodash.merge "^4.6.0"
     loglevel-colored-level-prefix "^1.0.0"
-    prettier "^1.0.2"
+    prettier "^1.3.0"
     pretty-format "^19.0.0"
     require-relative "^0.8.7"
 
-prettier@^1.0.2, prettier@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.3.0.tgz#cb02314f1ae2a99e951c17acb77a4096a1060ac2"
+prettier-eslint@^6.2.1:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/prettier-eslint/-/prettier-eslint-6.2.1.tgz#d5d6e3b09228dfbc7cc00e2448e11c9db0190068"
+  dependencies:
+    common-tags "^1.4.0"
+    dlv "^1.1.0"
+    eslint "^3.19.0"
+    indent-string "^3.1.0"
+    lodash.merge "^4.6.0"
+    loglevel-colored-level-prefix "^1.0.0"
+    prettier "^1.3.0"
+    pretty-format "^19.0.0"
+    require-relative "^0.8.7"
+
+prettier@^1.3.0, prettier@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.3.1.tgz#fa0ea84b45ac0ba6de6a1e4cecdcff900d563151"
   dependencies:
     ast-types "0.9.8"
     babel-code-frame "6.22.0"
@@ -3304,6 +3368,13 @@ pretty-format@^19.0.0:
   dependencies:
     ansi-styles "^3.0.0"
 
+pretty-format@^20.0.1:
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-20.0.1.tgz#ba95329771907c189643dd251e244061ff642350"
+  dependencies:
+    ansi-regex "^2.1.1"
+    ansi-styles "^3.0.0"
+
 private@^0.1.6:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
@@ -3568,7 +3639,7 @@ resolve@1.1.7:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
 
-resolve@^1.1.6, resolve@^1.2.0:
+resolve@^1.1.6, resolve@^1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.2.tgz#1f0442c9e0cbb8136e87b9305f932f46c7f28235"
   dependencies:
@@ -3598,7 +3669,7 @@ right-pad@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0"
 
-rimraf@2, rimraf@^2.2.8, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@^2.6.0, rimraf@^2.6.1:
+rimraf@2, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@^2.6.0, rimraf@^2.6.1:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
   dependencies:
@@ -3636,9 +3707,9 @@ rxjs@^5.3.0:
   dependencies:
     symbol-observable "^1.0.1"
 
-sane@~1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/sane/-/sane-1.5.0.tgz#a4adeae764d048621ecb27d5f9ecf513101939f3"
+sane@~1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/sane/-/sane-1.6.0.tgz#9610c452307a135d29c1fdfe2547034180c46775"
   dependencies:
     anymatch "^1.3.0"
     exec-sh "^0.2.0"
@@ -4114,7 +4185,7 @@ which-module@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
 
-which@^1.1.1, which@^1.2.11, which@^1.2.12:
+which@^1.2.11, which@^1.2.12:
   version "1.2.12"
   resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192"
   dependencies:
@@ -4228,7 +4299,7 @@ yargs@^4.2.0, yargs@^4.7.0:
     y18n "^3.2.1"
     yargs-parser "^2.4.1"
 
-yargs@^6.3.0, yargs@^6.6.0:
+yargs@^6.6.0:
   version "6.6.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
   dependencies:
@@ -4246,7 +4317,7 @@ yargs@^6.3.0, yargs@^6.6.0:
     y18n "^3.2.1"
     yargs-parser "^4.2.0"
 
-yargs@^7.1.0:
+yargs@^7.0.2, yargs@^7.1.0:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
   dependencies:


Test patch:
diff --git a/dist/statusTile/createStatusTile.js b/dist/statusTile/createStatusTile.js
new file mode 100644
index 0000000..282feef
--- /dev/null
+++ b/dist/statusTile/createStatusTile.js
@@ -0,0 +1,21 @@
+'use strict';
+
+var getFormatOnSaveStatus = require('./getFormatOnSaveStatus');
+
+var _require = require('../atomInterface'),
+    toggleFormatOnSave = _require.toggleFormatOnSave;
+
+var createStatusTile = function createStatusTile() {
+  var element = document.createElement('div');
+  var prettierTextNode = document.createTextNode('Prettier');
+
+  element.appendChild(prettierTextNode);
+  element.classList.add('prettier-atom-status-tile');
+  element.classList.add('inline-block');
+  element.dataset.formatOnSave = getFormatOnSaveStatus();
+  element.addEventListener('click', toggleFormatOnSave);
+
+  return element;
+};
+
+module.exports = createStatusTile;
\ No newline at end of file
diff --git a/dist/statusTile/updateStatusTile.js b/dist/statusTile/updateStatusTile.js
new file mode 100644
index 0000000..ee36280
--- /dev/null
+++ b/dist/statusTile/updateStatusTile.js
@@ -0,0 +1,25 @@
+'use strict';
+
+var _require = require('./tooltip'),
+    disposeTooltip = _require.disposeTooltip,
+    setTooltip = _require.setTooltip;
+
+var getFormatOnSaveStatus = require('./getFormatOnSaveStatus');
+
+var _require2 = require('../atomInterface'),
+    addTooltip = _require2.addTooltip;
+
+var updateStatusTile = function updateStatusTile(disposable, element) {
+  disposeTooltip();
+
+  element.dataset.formatOnSave = getFormatOnSaveStatus(); // eslint-disable-line no-param-reassign
+
+  var newTooltip = addTooltip(element, { title: 'Format on Save: ' + getFormatOnSaveStatus() });
+
+  setTooltip(newTooltip);
+  disposable.add(newTooltip);
+
+  return newTooltip;
+};
+
+module.exports = updateStatusTile;
\ No newline at end of file
diff --git a/src/__snapshots__/options.test.js.snap b/src/__snapshots__/options.test.js.snap
deleted file mode 100644
index 7cca5fa..0000000
--- a/src/__snapshots__/options.test.js.snap
+++ /dev/null
@@ -1,35 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`getPrettierEslintOptions returns all prettier-eslint options 1`] = `
-Object {
-  "prettierLast": true,
-}
-`;
-
-exports[`getPrettierOptions editorconfig integration uses editorconfig options when enabled in config 1`] = `
-Object {
-  "bracketSpacing": true,
-  "jsxBracketSameLine": true,
-  "parser": "flow",
-  "printWidth": 100,
-  "semi": true,
-  "singleQuote": true,
-  "tabWidth": 4,
-  "trailingComma": true,
-  "useTabs": false,
-}
-`;
-
-exports[`getPrettierOptions returns all prettier options 1`] = `
-Object {
-  "bracketSpacing": true,
-  "jsxBracketSameLine": true,
-  "parser": "flow",
-  "printWidth": 80,
-  "semi": true,
-  "singleQuote": true,
-  "tabWidth": 2,
-  "trailingComma": true,
-  "useTabs": true,
-}
-`;
diff --git a/src/atomInterface/__snapshots__/index.test.js.snap b/src/atomInterface/__snapshots__/index.test.js.snap
new file mode 100644
index 0000000..efe51ad
--- /dev/null
+++ b/src/atomInterface/__snapshots__/index.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`getPrettierOptions() returns all prettier options 1`] = `undefined`;
diff --git a/src/atomInterface/index.test.js b/src/atomInterface/index.test.js
new file mode 100644
index 0000000..eab8e91
--- /dev/null
+++ b/src/atomInterface/index.test.js
@@ -0,0 +1,140 @@
+const {
+  runLinter,
+  shouldUseEslint,
+  shouldDisplayErrors,
+  getPrettierOptions,
+  getPrettierEslintOptions,
+  isLinterEslintAutofixEnabled,
+  toggleFormatOnSave,
+} = require('./index');
+const textEditor = require('../../tests/mocks/textEditor');
+
+describe('runLinter()', () => {
+  it('runs `linter:lint` command', () => {
+    const editor = textEditor();
+    const viewMock = { isViewMock: true };
+    const commandsMock = [{ name: 'linter:lint', displayName: 'Linter: Lint' }];
+    atom = {
+      commands: { dispatch: jest.fn(), findCommands: jest.fn(() => commandsMock) },
+      views: { getView: jest.fn(() => viewMock) },
+    };
+
+    runLinter(editor);
+
+    expect(atom.views.getView).toHaveBeenCalledWith(editor);
+    expect(atom.commands.dispatch).toHaveBeenCalledWith(viewMock, 'linter:lint');
+  });
+
+  it('does nothing if `linter:lint` command does not exist', () => {
+    const editor = textEditor();
+    const viewMock = { isViewMock: true };
+    const commandsMock = [];
+    atom = {
+      commands: { dispatch: jest.fn(), findCommands: jest.fn(() => commandsMock) },
+      views: { getView: jest.fn(() => viewMock) },
+    };
+
+    runLinter(editor);
+
+    expect(atom.commands.findCommands).toHaveBeenCalledWith({ target: viewMock });
+    expect(atom.commands.dispatch).not.toHaveBeenCalled();
+  });
+});
+
+describe('shouldUseEslint()', () => {
+  it('is true if the config option is enabled', () => {
+    const mockGet = jest.fn(() => true);
+    atom = { config: { get: mockGet } };
+
+    const actual = shouldUseEslint();
+
+    expect(mockGet).toHaveBeenCalledWith('prettier-atom.useEslint');
+    expect(actual).toBe(true);
+  });
+
+  it('is false if the config option is not enabled', () => {
+    const mockGet = jest.fn(() => false);
+    atom = { config: { get: mockGet } };
+
+    const actual = shouldUseEslint();
+
+    expect(mockGet).toHaveBeenCalledWith('prettier-atom.useEslint');
+    expect(actual).toBe(false);
+  });
+});
+
+describe('shouldDisplayErrors()', () => {
+  it('is true if the user has chosen to silence errors', () => {
+    const mockGet = jest.fn(() => true);
+    atom = { config: { get: mockGet } };
+
+    const actual = shouldDisplayErrors();
+
+    expect(mockGet).lastCalledWith('prettier-atom.silenceErrors');
+    expect(actual).toBe(false);
+  });
+});
+
+describe('getPrettierEslintOptions()', () => {
+  it('retrieves the given prettier-eslint options from the prettier-atom config', () => {
+    const mockGet = jest.fn(() => true);
+    atom = { config: { get: mockGet } };
+
+    const actual = getPrettierEslintOptions();
+
+    expect(mockGet).lastCalledWith('prettier-atom.prettierEslintOptions');
+    expect(actual).toBe(true);
+  });
+});
+
+describe('isLinterEslintAutofixEnabled()', () => {
+  it('returns the value from the linter-eslint config', () => {
+    atom = { config: { get: jest.fn(() => true) } };
+
+    const actual = isLinterEslintAutofixEnabled();
+
+    expect(atom.config.get).toHaveBeenCalledWith('linter-eslint.fixOnSave');
+    expect(actual).toBe(true);
+  });
+});
+
+describe('getPrettierOptions()', () => {
+  it('returns all prettier options', () => {
+    const mockGet = option =>
+      ({
+        'prettier-atom.prettierOptions.printWidth': 80,
+        'prettier-atom.prettierOptions.tabWidth': 2,
+        'prettier-atom.prettierOptions.parser': 'flow',
+        'prettier-atom.prettierOptions.singleQuote': true,
+        'prettier-atom.prettierOptions.trailingComma': true,
+        'prettier-atom.prettierOptions.bracketSpacing': true,
+        'prettier-atom.prettierOptions.semi': true,
+        'prettier-atom.prettierOptions.useTabs': true,
+        'prettier-atom.prettierOptions.jsxBracketSameLine': true,
+      }[option]);
+    atom = { config: { get: mockGet } };
+    const editor = textEditor();
+
+    const actual = getPrettierOptions(editor);
+
+    expect(actual).toMatchSnapshot();
+  });
+});
+
+describe('toggleFormatOnSave()', () => {
+  it('sets formatOnSaveOptions.enabled to false if it was true', () => {
+    atom = { config: { get: jest.fn(() => true), set: jest.fn() } };
+
+    toggleFormatOnSave();
+
+    expect(atom.config.set).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.enabled', false);
+  });
+
+  it('sets formatOnSaveOptions.enabled to true if it was false', () => {
+    atom = { config: { get: jest.fn(() => false), set: jest.fn() } };
+
+    toggleFormatOnSave();
+
+    expect(atom.config.set).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.enabled', true);
+  });
+});
diff --git a/src/displayDebugInfo.test.js b/src/displayDebugInfo.test.js
deleted file mode 100644
index 07656b2..0000000
--- a/src/displayDebugInfo.test.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// @flow
-const displayDebugInfo = require('./displayDebugInfo');
-
-test('it displays a notification on Atom with package information', () => {
-  let title;
-  let options;
-
-  atom = {
-    getVersion: jest.fn(),
-    config: {
-      get: jest.fn(),
-    },
-    notifications: {
-      addInfo: jest.fn((msg, opts) => {
-        title = msg;
-        options = opts;
-      }),
-    },
-  };
-
-  displayDebugInfo();
-  expect(atom.notifications.addInfo).toHaveBeenCalled();
-  expect(title).toContain('prettier-atom');
-  // $FlowFixMe
-  expect(typeof options.detail).toBe('string');
-  // $FlowFixMe
-  expect(options.detail.length).toBeGreaterThan(0);
-  // $FlowFixMe
-  expect(options.dismissable).toBe(true);
-});
diff --git a/src/displayDebugInfo/__snapshots__/index.test.js.snap b/src/displayDebugInfo/__snapshots__/index.test.js.snap
new file mode 100644
index 0000000..0dd755e
--- /dev/null
+++ b/src/displayDebugInfo/__snapshots__/index.test.js.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`it displays a notification on Atom with package information 1`] = `
+"Atom version: FAKE_ATOM_VERSION
+prettier-atom version: FAKE_PACKAGE_VERSION
+prettier version: FAKE_PACKAGE_VERSION
+prettier-eslint version: FAKE_PACKAGE_VERSION
+prettier-atom configuration: \\"FAKE_PRETTIER_ATOM_CONFIG\\""
+`;
diff --git a/src/displayDebugInfo/index.test.js b/src/displayDebugInfo/index.test.js
new file mode 100644
index 0000000..6054b41
--- /dev/null
+++ b/src/displayDebugInfo/index.test.js
@@ -0,0 +1,24 @@
+jest.mock('read-pkg');
+jest.mock('../atomInterface');
+
+const readPkg = require('read-pkg');
+const displayDebugInfo = require('./index');
+const { getAtomVersion, getPrettierAtomConfig, addInfoNotification } = require('../atomInterface');
+
+test('it displays a notification on Atom with package information', () => {
+  let title;
+  let options;
+  addInfoNotification.mockImplementation((_title, _options) => {
+    title = _title;
+    options = _options;
+  });
+  readPkg.sync.mockImplementation(() => ({ version: 'FAKE_PACKAGE_VERSION' }));
+  getAtomVersion.mockImplementation(() => 'FAKE_ATOM_VERSION');
+  getPrettierAtomConfig.mockImplementation(() => 'FAKE_PRETTIER_ATOM_CONFIG');
+
+  displayDebugInfo();
+
+  expect(title).toContain('prettier-atom');
+  expect(options.detail).toMatchSnapshot();
+  expect(options.dismissable).toBe(true);
+});
diff --git a/src/editorInterface/index.test.js b/src/editorInterface/index.test.js
new file mode 100644
index 0000000..69ac1ff
--- /dev/null
+++ b/src/editorInterface/index.test.js
@@ -0,0 +1,67 @@
+const buildMockTextEditor = require('../../tests/mocks/textEditor');
+const {
+  getBufferRange,
+  getCurrentScope,
+  isCurrentScopeEmbeddedScope,
+  getCurrentFilePath,
+} = require('./index');
+
+describe('getBufferRange()', () => {
+  it('gets the buffer range from the editor', () => {
+    const editor = buildMockTextEditor();
+
+    const actual = getBufferRange(editor);
+
+    expect(actual).toEqual('FAKE BUFFER RANGE');
+  });
+});
+
+describe('getCurrentScope()', () => {
+  it('gets the current scope from the editor', () => {
+    const editor = buildMockTextEditor();
+
+    const actual = getCurrentScope(editor);
+
+    expect(actual).toEqual('FAKE SCOPE NAME');
+  });
+});
+
+describe('isCurrentScopeEmbeddedScope', () => {
+  it('returns true if the current scope is an embedded scope type', () => {
+    const scopeName = 'text.html.basic';
+    const editor = buildMockTextEditor({ getGrammar: () => ({ scopeName }) });
+
+    const actual = isCurrentScopeEmbeddedScope(editor);
+
+    expect(actual).toBe(true);
+  });
+
+  it('returns false if the current scope is not an embedded scope type', () => {
+    const scopeName = 'source.js.jsx';
+    const editor = buildMockTextEditor({ getGrammar: () => ({ scopeName }) });
+
+    const actual = isCurrentScopeEmbeddedScope(editor);
+
+    expect(actual).toBe(false);
+  });
+});
+
+describe('getCurrentFilePath()', () => {
+  it('returns the current file path if there is one', () => {
+    const file = { path: 'xyz.js' };
+    const editor = buildMockTextEditor({ buffer: { file } });
+
+    const actual = getCurrentFilePath(editor);
+
+    expect(actual).toEqual('xyz.js');
+  });
+
+  it('returns undefined if there is no current file', () => {
+    const file = null;
+    const editor = buildMockTextEditor({ buffer: { file } });
+
+    const actual = getCurrentFilePath(editor);
+
+    expect(actual).toBeUndefined();
+  });
+});
diff --git a/src/executePrettier.test.js b/src/executePrettier.test.js
deleted file mode 100644
index d587ed7..0000000
--- a/src/executePrettier.test.js
+++ /dev/null
@@ -1,150 +0,0 @@
-// @flow
-const prettierEslint = require('prettier-eslint');
-
-const { executePrettierOnBufferRange, executePrettierOnEmbeddedScripts } = require('./executePrettier');
-const textEditor = require('../tests/mocks/textEditor');
-const helpers = require('./helpers');
-const options = require('./options');
-
-jest.mock('./helpers');
-jest.mock('./options');
-jest.mock('prettier-eslint');
-
-let editor;
-const bufferRangeFixture = { start: { column: 0, row: 0 }, end: { column: 20, row: 0 } };
-
-const prettier = { format: jest.fn(() => 'some transformed text') };
-
-beforeEach(() => {
-  // $FlowFixMe
-  helpers.getPrettier.mockImplementation(() => prettier);
-  prettier.format.mockImplementation(() => 'some transformed text');
-  prettierEslint.mockImplementation(() => 'some transformed text');
-  editor = textEditor({ getTextInBufferRange: jest.fn(() => 'untransformed text') });
-});
-
-describe('executePrettierOnBufferRange()', () => {
-  test('transforms the given buffer range using prettier', () => {
-    // $FlowFixMe
-    options.getPrettierOptions.mockImplementation(() => ({ option: 'fake prettier option' }));
-    executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-    expect(prettier.format).toHaveBeenCalledWith('untransformed text', { option: 'fake prettier option' });
-  });
-
-  test('sets the transformed text in the buffer range', () => {
-    executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-    expect(editor.setTextInBufferRange).toHaveBeenCalledWith(bufferRangeFixture, 'some transformed text');
-  });
-
-  test('sets the cursor position back to the beginning', () => {
-    const startCursorScreenPosition = { column: 5, row: 0 };
-    editor.getCursorScreenPosition.mockImplementation(() => startCursorScreenPosition);
-
-    executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-    expect(editor.setCursorScreenPosition).toHaveBeenCalledWith(startCursorScreenPosition);
-  });
-
-  test('runs linter:lint if available to refresh linter highlighting', () => {
-    executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-    expect(helpers.runLinter).toHaveBeenCalled();
-  });
-
-  test('transforms the given buffer range using prettier-eslint if config enables it', () => {
-    // $FlowFixMe
-    options.shouldUseEslint.mockImplementation(() => true);
-    // $FlowFixMe
-    options.getPrettierEslintOptions.mockImplementation(() => ({ prettierLast: true }));
-    // $FlowFixMe
-    options.getPrettierOptions.mockImplementation(() => ({ semi: true }));
-    // $FlowFixMe
-    helpers.getCurrentFilePath.mockImplementation(() => 'foo.js');
-
-    executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-    expect(prettierEslint).toHaveBeenCalledWith({
-      filePath: 'foo.js',
-      text: 'untransformed text',
-      prettierLast: true,
-      fallbackPrettierOptions: {
-        semi: true,
-      },
-    });
-  });
-
-  describe('when text in buffer range is already pretty', () => {
-    beforeEach(() => {
-      prettier.format.mockImplementation(() => 'untransformed text');
-    });
-
-    test("does not change the text in the editor's buffer range", () => {
-      executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-      expect(editor.setTextInBufferRange).not.toHaveBeenCalled();
-    });
-
-    test("does not change the editor's cursor position", () => {
-      executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-      expect(editor.setCursorScreenPosition).not.toHaveBeenCalled();
-    });
-  });
-
-  describe('when prettier throws an error', () => {
-    beforeEach(() => {
-      prettier.format.mockImplementation(() => {
-        throw new Error();
-      });
-      atom = { notifications: { addError: jest.fn() } };
-    });
-
-    test('displays an error', () => {
-      // $FlowFixMe
-      options.shouldDisplayErrors.mockImplementation(() => true);
-
-      executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-      expect(atom.notifications.addError).toHaveBeenCalled();
-    });
-
-    test('skips displaying an error if user chose to silence them', () => {
-      // $FlowFixMe
-      options.shouldDisplayErrors.mockImplementation(() => false);
-
-      executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-      expect(atom.notifications.addError).not.toHaveBeenCalled();
-    });
-
-    test("does not change the text in the editor's buffer range", () => {
-      executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-      expect(editor.setTextInBufferRange).not.toHaveBeenCalled();
-    });
-
-    test("does not change the editor's cursor position", () => {
-      executePrettierOnBufferRange(editor, bufferRangeFixture);
-
-      expect(editor.setCursorScreenPosition).not.toHaveBeenCalled();
-    });
-  });
-});
-
-describe('executePrettierOnEmbeddedScripts()', () => {
-  test('finds embedded scripts in buffer and transforms each', () => {
-    // $FlowFixMe
-    options.getPrettierOptions.mockImplementation(() => ({ option: 'fake prettier option' }));
-    const fileBufferRange = { range: { start: { row: 0, column: 0 }, end: { row: 4, column: 5 } } };
-    editor.getBuffer.mockImplementation(() => ({ getRange: () => fileBufferRange }));
-    editor.backwardsScanInBufferRange.mockImplementation((regex, range, iterator) =>
-      iterator(fileBufferRange),
-    );
-
-    executePrettierOnEmbeddedScripts(editor);
-
-    expect(prettier.format).toHaveBeenCalledWith('untransformed text', { option: 'fake prettier option' });
-  });
-});
diff --git a/src/executePrettier/buildEditorConfigOptions.test.js b/src/executePrettier/buildEditorConfigOptions.test.js
new file mode 100644
index 0000000..0ae725c
--- /dev/null
+++ b/src/executePrettier/buildEditorConfigOptions.test.js
@@ -0,0 +1,90 @@
+jest.mock('editorconfig');
+
+const editorconfig = require('editorconfig');
+const buildEditorConfigOptions = require('./buildEditorConfigOptions');
+
+describe('tabWidth', () => {
+  it("is set to the tab_width if defined and indent_style is set to 'tab'", () => {
+    editorconfig.parseSync.mockImplementation(() => ({ indent_style: 'tab', tab_width: 2 }));
+
+    const actual = buildEditorConfigOptions();
+
+    expect(actual).toHaveProperty('tabWidth', 2);
+  });
+
+  it("is set to the tab_width if indent_size is set to 'tab'", () => {
+    editorconfig.parseSync.mockImplementation(() => ({ tab_width: 2, indent_size: 'tab' }));
+
+    const actual = buildEditorConfigOptions();
+
+    expect(actual).toHaveProperty('tabWidth', 2);
+  });
+
+  it("is set to the indent_size if is number and indent_style is not 'tab'", () => {
+    editorconfig.parseSync.mockImplementation(() => ({ indent_size: 2 }));
+
+    const actual = buildEditorConfigOptions();
+
+    expect(actual).toHaveProperty('tabWidth', 2);
+  });
+
+  it('is not set if indent_size is not defined', () => {
+    editorconfig.parseSync.mockImplementation(() => ({}));
+
+    const actual = buildEditorConfigOptions().tabWidth;
+
+    expect(actual).toBeUndefined();
+  });
+});
+
+describe('printWidth', () => {
+  it('is set to the max_line_length if it is a number', () => {
+    editorconfig.parseSync.mockImplementation(() => ({ max_line_length: 80 }));
+
+    const actual = buildEditorConfigOptions();
+
+    expect(actual).toHaveProperty('printWidth', 80);
+  });
+
+  it("is not set if max_line_length is set to 'off'", () => {
+    editorconfig.parseSync.mockImplementation(() => ({ max_line_length: 'off' }));
+
+    const actual = buildEditorConfigOptions();
+
+    expect(actual).not.toHaveProperty('printWidth');
+  });
+
+  it('is not set if max_line_length is not defined', () => {
+    editorconfig.parseSync.mockImplementation(() => ({}));
+
+    const actual = buildEditorConfigOptions();
+
+    expect(actual).not.toHaveProperty('printWidth');
+  });
+});
+
+describe('useTabs', () => {
+  it('is false if indent_style is space', () => {
+    editorconfig.parseSync.mockImplementation(() => ({ indent_style: 'space' }));
+
+    const actual = buildEditorConfigOptions();
+
+    expect(actual).toHaveProperty('useTabs', false);
+  });
+
+  it('is true if indent_style is tab', () => {
+    editorconfig.parseSync.mockImplementation(() => ({ indent_style: 'tab' }));
+
+    const actual = buildEditorConfigOptions();
+
+    expect(actual).toHaveProperty('useTabs', true);
+  });
+
+  it('does not get set if indent_style is not defined', () => {
+    editorconfig.parseSync.mockImplementation(() => ({}));
+
+    const actual = buildEditorConfigOptions();
+
+    expect(actual).not.toHaveProperty('useTabs');
+  });
+});
diff --git a/src/executePrettier/buildPrettierEslintOptions.test.js b/src/executePrettier/buildPrettierEslintOptions.test.js
new file mode 100644
index 0000000..d4ae805
--- /dev/null
+++ b/src/executePrettier/buildPrettierEslintOptions.test.js
@@ -0,0 +1,29 @@
+jest.mock('./buildPrettierOptions');
+jest.mock('../atomInterface');
+jest.mock('../editorInterface');
+
+const buildMockEditor = require('../../tests/mocks/textEditor');
+const { getCurrentFilePath } = require('../editorInterface');
+const { getPrettierEslintOptions } = require('../atomInterface');
+const buildPrettierOptions = require('./buildPrettierOptions');
+const buildPrettierEslintOptions = require('./buildPrettierEslintOptions');
+
+it('returns options for running prettier eslint', () => {
+  buildPrettierOptions.mockImplementation(() => ({ tabWidth: 2 }));
+  getPrettierEslintOptions.mockImplementation(() => ({ prettierLast: true }));
+  getCurrentFilePath.mockImplementation(() => 'xyz.js');
+  const editor = buildMockEditor();
+  const text = 'const foo = null;';
+
+  const actual = buildPrettierEslintOptions(editor, text);
+  const expected = {
+    text,
+    prettierLast: true,
+    filePath: 'xyz.js',
+    fallbackPrettierOptions: {
+      tabWidth: 2,
+    },
+  };
+
+  expect(actual).toEqual(expected);
+});
diff --git a/src/executePrettier/buildPrettierOptions.test.js b/src/executePrettier/buildPrettierOptions.test.js
new file mode 100644
index 0000000..95dffbb
--- /dev/null
+++ b/src/executePrettier/buildPrettierOptions.test.js
@@ -0,0 +1,51 @@
+jest.mock('../atomInterface');
+jest.mock('./buildEditorConfigOptions');
+
+const { getPrettierOptions, getAtomTabLength, shouldUseEditorConfig } = require('../atomInterface');
+const buildEditorConfigOptions = require('./buildEditorConfigOptions');
+const buildMockEditor = require('../../tests/mocks/textEditor');
+const buildPrettierOptions = require('./buildPrettierOptions');
+
+it('returns prettier options', () => {
+  const editor = buildMockEditor();
+  const fakePrettierOptions = { fakeOption: 'fake value' };
+  getPrettierOptions.mockImplementation(() => fakePrettierOptions);
+
+  const actual = buildPrettierOptions(editor);
+
+  expect(actual).toEqual(fakePrettierOptions);
+});
+
+it('uses the atom tab length if the tabWidth option is set to "auto"', () => {
+  const editor = buildMockEditor();
+  const fakePrettierOptions = { tabWidth: 'auto' };
+  getPrettierOptions.mockImplementation(() => fakePrettierOptions);
+  getAtomTabLength.mockImplementation(() => 2);
+
+  const actual = buildPrettierOptions(editor);
+
+  expect(actual).toEqual({ tabWidth: 2 });
+});
+
+it('does not use editorconfig options if that setting is not enabled', () => {
+  const editor = buildMockEditor();
+  getPrettierOptions.mockImplementation(() => ({}));
+  shouldUseEditorConfig.mockImplementation(() => false);
+
+  buildPrettierOptions(editor);
+
+  expect(buildEditorConfigOptions).not.toHaveBeenCalled();
+});
+
+it('overrides values with editorconfig values if editor config is enabled', () => {
+  const editor = buildMockEditor();
+  const fakePrettierOptions = { tabWidth: 2, printWidth: 80, useTabs: false };
+  const fakeEditorConfigOptions = { tabWidth: 4, printWidth: 100, useTabs: true };
+  getPrettierOptions.mockImplementation(() => fakePrettierOptions);
+  buildEditorConfigOptions.mockImplementation(() => fakeEditorConfigOptions);
+  shouldUseEditorConfig.mockImplementation(() => true);
+
+  const actual = buildPrettierOptions(editor);
+
+  expect(actual).toEqual(fakeEditorConfigOptions);
+});
diff --git a/src/executePrettier/executePrettierOnBufferRange.test.js b/src/executePrettier/executePrettierOnBufferRange.test.js
new file mode 100644
index 0000000..ee9c476
--- /dev/null
+++ b/src/executePrettier/executePrettierOnBufferRange.test.js
@@ -0,0 +1,119 @@
+jest.mock('prettier-eslint');
+jest.mock('prettier');
+jest.mock('../atomInterface');
+jest.mock('../editorInterface');
+jest.mock('./getPrettierInstance');
+jest.mock('./buildPrettierOptions');
+jest.mock('./buildPrettierEslintOptions');
+jest.mock('./handleError');
+
+const prettier = require('prettier');
+const prettierEslint = require('prettier-eslint');
+const { shouldUseEslint, runLinter } = require('../atomInterface');
+const { getCurrentFilePath } = require('../editorInterface');
+const getPrettierInstance = require('./getPrettierInstance');
+const buildPrettierEslintOptions = require('./buildPrettierEslintOptions');
+const buildPrettierOptions = require('./buildPrettierOptions');
+const handleError = require('./handleError');
+const buildMockTextEditor = require('../../tests/mocks/textEditor');
+const executePrettierOnBufferRange = require('./executePrettierOnBufferRange');
+
+let editor;
+const bufferRangeFixture = { start: { column: 0, row: 0 }, end: { column: 20, row: 0 } };
+
+beforeEach(() => {
+  editor = buildMockTextEditor();
+  editor.getTextInBufferRange.mockImplementation(() => 'const foo = (2);');
+  prettier.format.mockImplementation(() => 'const foo = 2;');
+  buildPrettierOptions.mockImplementation(() => ({ useTabs: false }));
+  getPrettierInstance.mockImplementation(() => prettier);
+});
+
+it('sets the transformed text in the buffer range', () => {
+  executePrettierOnBufferRange(editor, bufferRangeFixture);
+
+  expect(prettier.format).toHaveBeenCalledWith('const foo = (2);', { useTabs: false });
+  expect(editor.setTextInBufferRange).toHaveBeenCalledWith(bufferRangeFixture, 'const foo = 2;');
+});
+
+it('runs linter:lint if available to refresh linter highlighting', () => {
+  executePrettierOnBufferRange(editor, bufferRangeFixture);
+
+  expect(runLinter).toHaveBeenCalledWith(editor);
+});
+
+it('sets the cursor position back to the beginning', () => {
+  const cursorPositionPriorToFormat = { start: { column: 0, row: 0 }, end: { column: 10, row: 1 } };
+  editor.getCursorScreenPosition.mockImplementation(() => cursorPositionPriorToFormat);
+  executePrettierOnBufferRange(editor, bufferRangeFixture);
+
+  expect(editor.setCursorScreenPosition).toHaveBeenCalledWith(cursorPositionPriorToFormat);
+});
+
+it('transforms the given buffer range using prettier-eslint if config enables it', () => {
+  shouldUseEslint.mockImplementation(() => true);
+  buildPrettierEslintOptions.mockImplementation((_editor, text) => ({
+    text,
+    filePath: 'foo.js',
+    prettierLast: true,
+    fallbackPrettierOptions: { useTabs: false },
+  }));
+  getCurrentFilePath.mockImplementation(() => 'foo.js');
+
+  executePrettierOnBufferRange(editor, bufferRangeFixture);
+
+  expect(prettierEslint).toHaveBeenCalledWith({
+    filePath: 'foo.js',
+    text: 'const foo = (2);',
+    prettierLast: true,
+    fallbackPrettierOptions: {
+      useTabs: false,
+    },
+  });
+});
+
+describe('when text in buffer range is already pretty', () => {
+  beforeEach(() => {
+    editor.getTextInBufferRange.mockImplementation(() => 'const foo = 2;');
+  });
+
+  it("does not change the text in the editor's buffer range", () => {
+    executePrettierOnBufferRange(editor, bufferRangeFixture);
+
+    expect(editor.setTextInBufferRange).not.toHaveBeenCalled();
+  });
+
+  it("does not change the editor's cursor position", () => {
+    executePrettierOnBufferRange(editor, bufferRangeFixture);
+
+    expect(editor.setCursorScreenPosition).not.toHaveBeenCalled();
+  });
+});
+
+describe('when prettier throws an error', () => {
+  const error = new Error();
+
+  beforeEach(() => {
+    prettier.format.mockImplementation(() => {
+      throw error;
+    });
+  });
+
+  it('handles the error', () => {
+    executePrettierOnBufferRange(editor, bufferRangeFixture);
+
+    expect(handleError).toHaveBeenCalledWith(error);
+  });
+
+  it("does not change the text in the editor's buffer range", () => {
+    executePrettierOnBufferRange(editor, bufferRangeFixture);
+
+    expect(editor.setTextInBufferRange).not.toHaveBeenCalled();
+  });
+
+  it("does not change the editor's cursor position", () => {
+    executePrettierOnBufferRange(editor, bufferRangeFixture);
+
+    expect(editor.setCursorScreenPosition).not.toHaveBeenCalled();
+  });
+});
diff --git a/src/executePrettier/executePrettierOnEmbeddedScripts.test.js b/src/executePrettier/executePrettierOnEmbeddedScripts.test.js
new file mode 100644
index 0000000..83714b3
--- /dev/null
+++ b/src/executePrettier/executePrettierOnEmbeddedScripts.test.js
@@ -0,0 +1,21 @@
+jest.mock('./executePrettierOnBufferRange');
+
+const buildMockTextEditor = require('../../tests/mocks/textEditor');
+const executePrettierOnBufferRange = require('./executePrettierOnBufferRange');
+const executePrettierOnEmbeddedScripts = require('./executePrettierOnEmbeddedScripts');
+
+test('retrieves buffer ranges for embedded scripts and executes prettier on them', () => {
+  const scriptRange = { start: { row: 2, column: 0 }, end: { row: 4, column: 5 } };
+  const entireFileRange = {
+    constructor: () => scriptRange,
+    start: { row: 0, column: 0 },
+    end: { row: 4, column: 5 },
+  };
+  const editor = buildMockTextEditor({
+    backwardsScanInBufferRange: (regex, range, iterator) => iterator({ range: entireFileRange }),
+  });
+
+  executePrettierOnEmbeddedScripts(editor);
+
+  expect(executePrettierOnBufferRange).toHaveBeenCalledWith(editor, scriptRange);
+});
diff --git a/src/executePrettier/getPrettierInstance.test.js b/src/executePrettier/getPrettierInstance.test.js
new file mode 100644
index 0000000..5ec1fcd
--- /dev/null
+++ b/src/executePrettier/getPrettierInstance.test.js
@@ -0,0 +1,48 @@
+jest.mock('atom-linter');
+
+const prettier = require('prettier');
+const path = require('path');
+const atomLinter = require('atom-linter');
+const createMockTextEditor = require('../../tests/mocks/textEditor');
+const getPrettierInstance = require('./getPrettierInstance');
+
+test("returns user's project's local prettier instance if it exists", () => {
+  const filePath = path.join(__dirname, '..', '..', 'tests', 'fixtures', 'sourceFile.js');
+  const editor = createMockTextEditor({ buffer: { file: { path: filePath } } });
+  const prettierLib = path.join(__dirname, '..', '..', 'tests', 'fixtures', 'prettier.js');
+  atomLinter.findCached.mockImplementation(() => prettierLib);
+
+  const actual = getPrettierInstance(editor);
+
+  expect(actual).toEqual(require(prettierLib)); // eslint-disable-line
+  expect(atomLinter.findCached).toHaveBeenCalledWith(
+    path.join(__dirname, '..', '..', 'tests', 'fixtures'),
+    path.join('node_modules', 'prettier', 'index.js'),
+  );
+});
+
+test("returns bundled prettier if user's project has no local prettier package", () => {
+  atomLinter.findCached.mockImplementation(() => undefined);
+  const filePath = path.join(__dirname, 'sourceFile.js');
+  const editor = createMockTextEditor({ buffer: { file: { path: filePath } } });
+
+  const actual = getPrettierInstance(editor);
+
+  expect(actual).toEqual(prettier);
+});
+
+test('returns bundled prettier if filePath is null', () => {
+  const editor = createMockTextEditor({ buffer: { file: { path: null } } });
+
+  const actual = getPrettierInstance(editor);
+
+  expect(actual).toEqual(prettier);
+});
+
+test('returns bundled prettier if filePath has no parent directory', () => {
+  const editor = createMockTextEditor({ buffer: { file: { path: 'foo.js' } } });
+
+  const actual = getPrettierInstance(editor);
+
+  expect(actual).toEqual(prettier);
+});
diff --git a/src/executePrettier/handleError.test.js b/src/executePrettier/handleError.test.js
new file mode 100644
index 0000000..54c10d7
--- /dev/null
+++ b/src/executePrettier/handleError.test.js
@@ -0,0 +1,27 @@
+jest.mock('../atomInterface');
+
+const { addErrorNotification, shouldDisplayErrors } = require('../atomInterface');
+const handleError = require('./handleError');
+
+it('displays an error notification is shouldDisplayErrors is true', () => {
+  const error = new Error('fake error');
+  shouldDisplayErrors.mockImplementation(() => true);
+
+  handleError(error);
+
+  const message = addErrorNotification.mock.calls[0][0];
+  const options = addErrorNotification.mock.calls[0][1];
+
+  expect(message).toMatch('prettier-atom');
+  expect(options.dismissable).toBe(true);
+  expect(options.detail).toEqual(error.toString());
+});
+
+it('does not notify if shouldDisplayErrors is false', () => {
+  const error = new Error('fake error');
+  shouldDisplayErrors.mockImplementation(() => false);
+
+  handleError(error);
+
+  expect(addErrorNotification).not.toHaveBeenCalled();
+});
diff --git a/src/format.test.js b/src/format.test.js
deleted file mode 100644
index df1f996..0000000
--- a/src/format.test.js
+++ /dev/null
@@ -1,48 +0,0 @@
-// @flow
-/* eslint-disable global-require */
-const textEditor = require('../tests/mocks/textEditor');
-const format = require('./format');
-const { executePrettierOnBufferRange, executePrettierOnEmbeddedScripts } = require('./executePrettier');
-const helpers = require('./helpers');
-
-jest.mock('./executePrettier');
-jest.mock('./helpers');
-
-let editor;
-
-beforeEach(() => {
-  editor = textEditor();
-  atom = {};
-});
-
-test('executes prettier on selected buffer ranges if there is text selected', () => {
-  const bufferRange = {};
-  editor.getSelectedText.mockImplementation(() => 'some selected text');
-  editor.getSelectedBufferRanges.mockImplementation(() => [{}]);
-
-  format(editor);
-
-  expect(editor.getSelectedText).toHaveBeenCalled();
-  expect(executePrettierOnBufferRange).toHaveBeenCalledWith(editor, bufferRange);
-});
-
-test('executes prettier on embedded scripts if is embedded scope', () => {
-  // $FlowFixMe
-  helpers.isCurrentScopeEmbeddedScope.mockImplementation(() => true);
-
-  format(editor);
-
-  expect(helpers.isCurrentScopeEmbeddedScope).toHaveBeenCalledWith(editor);
-  expect(executePrettierOnEmbeddedScripts).toHaveBeenCalledWith(editor);
-});
-
-test('executes prettier on buffer range', () => {
-  // $FlowFixMe
-  helpers.isCurrentScopeEmbeddedScope.mockImplementation(() => false);
-  const bufferRange = {};
-  editor.getBuffer.mockImplementation(() => ({ getRange: () => bufferRange }));
-
-  format(editor);
-
-  expect(executePrettierOnBufferRange).toHaveBeenCalledWith(editor, bufferRange);
-});
diff --git a/src/formatOnSave.test.js b/src/formatOnSave.test.js
deleted file mode 100644
index 75164a1..0000000
--- a/src/formatOnSave.test.js
+++ /dev/null
@@ -1,148 +0,0 @@
-// @flow
-const helpers = require('./helpers');
-const options = require('./options');
-const formatOnSave = require('./formatOnSave');
-const textEditor = require('../tests/mocks/textEditor');
-const { executePrettierOnBufferRange, executePrettierOnEmbeddedScripts } = require('./executePrettier');
-
-jest.mock('./executePrettier');
-jest.mock('./helpers');
-jest.mock('./options');
-
-let editor;
-const filePathFixture = 'foo.js';
-const rangeFixture = {};
-
-beforeEach(() => {
-  editor = textEditor();
-
-  // Defaults for running on buffer range
-  // $FlowFixMe
-  options.isFormatOnSaveEnabled.mockImplementation(() => true);
-  // $FlowFixMe
-  helpers.getConfigOption.mockImplementation(() => true);
-  // $FlowFixMe
-  helpers.isInScope.mockImplementation(() => true);
-  // $FlowFixMe
-  helpers.isFilePathEslintignored.mockImplementation(() => false);
-  // $FlowFixMe
-  helpers.isCurrentScopeEmbeddedScope.mockImplementation(() => false);
-  // $FlowFixMe
-  helpers.getCurrentFilePath.mockImplementation(() => filePathFixture);
-  editor.getBuffer.mockImplementation(() => ({ getRange: jest.fn(() => rangeFixture) }));
-});
-
-test('it executes prettier on buffer range', () => {
-  formatOnSave(editor);
-
-  expect(executePrettierOnBufferRange).toHaveBeenCalledWith(editor, rangeFixture);
-});
-
-test('it executes prettier on buffer range if file is whitelisted regardless of exclusions or scopes', () => {
-  // $FlowFixMe
-  options.isWhitelistProvided.mockImplementation(() => true);
-  // $FlowFixMe
-  helpers.isInScope.mockImplementation(() => false);
-  // $FlowFixMe
-  helpers.isFilePathExcluded.mockImplementation(() => true);
-  // $FlowFixMe
-  helpers.isFilePathWhitelisted.mockImplementation(() => true);
-
-  formatOnSave(editor);
-
-  expect(executePrettierOnBufferRange).toHaveBeenCalledWith(editor, rangeFixture);
-});
-
-test('it does not execute prettier if whitelist is provided and file is not whitelisted', () => {
-  // $FlowFixMe
-  options.isWhitelistProvided.mockImplementation(() => true);
-  // $FlowFixMe
-  helpers.isInScope.mockImplementation(() => true);
-  // $FlowFixMe
-  helpers.isFilePathExcluded.mockImplementation(() => false);
-  // $FlowFixMe
-  helpers.isFilePathWhitelisted.mockImplementation(() => false);
-
-  formatOnSave(editor);
-
-  expect(executePrettierOnBufferRange).toHaveBeenCalledTimes(0);
-});
-
-test('it executes prettier on embedded scripts if scope is an embedded scope', () => {
-  // $FlowFixMe
-  helpers.isCurrentScopeEmbeddedScope.mockImplementation(() => true);
-
-  formatOnSave(editor);
-
-  expect(executePrettierOnEmbeddedScripts).toHaveBeenCalledWith(editor);
-});
-
-test('it does nothing if formatOnSave is not enabled', () => {
-  // $FlowFixMe
-  options.isFormatOnSaveEnabled.mockImplementation(() => false);
-
-  formatOnSave(editor);
-
-  expect(options.isFormatOnSaveEnabled).toHaveBeenCalled();
-  expect(executePrettierOnBufferRange).not.toHaveBeenCalled();
-  expect(executePrettierOnEmbeddedScripts).not.toHaveBeenCalled();
-});
-
-test('it does nothing if file is saved for the first time', () => {
-  // $FlowFixMe
-  helpers.getCurrentFilePath.mockImplementation(() => undefined);
-
-  formatOnSave(editor);
-
-  expect(options.isFormatOnSaveEnabled).toHaveBeenCalled();
-  expect(helpers.getCurrentFilePath).toHaveBeenCalled();
-  expect(executePrettierOnBufferRange).not.toHaveBeenCalled();
-  expect(executePrettierOnEmbeddedScripts).not.toHaveBeenCalled();
-});
-
-test('it does nothing if not a valid scope according to config', () => {
-  // $FlowFixMe
-  helpers.isInScope.mockImplementation(() => false);
-
-  formatOnSave(editor);
-
-  expect(helpers.isInScope).toHaveBeenCalled();
-  expect(executePrettierOnBufferRange).not.toHaveBeenCalled();
-  expect(executePrettierOnEmbeddedScripts).not.toHaveBeenCalled();
-});
-
-test('it does nothing if filePath matches an excluded glob', () => {
-  // $FlowFixMe
-  helpers.isFilePathExcluded.mockImplementation(() => true);
-
-  formatOnSave(editor);
-
-  expect(helpers.isFilePathExcluded).toHaveBeenCalled();
-  expect(executePrettierOnBufferRange).not.toHaveBeenCalled();
-  expect(executePrettierOnEmbeddedScripts).not.toHaveBeenCalled();
-});
-
-test('it does nothing if config says to respectEslintignore and file is matched by eslintignore', () => {
-  // $FlowFixMe
-  helpers.getCurrentFilePath.mockImplementation(() => filePathFixture);
-  // $FlowFixMe
-  options.shouldRespectEslintignore.mockImplementation(() => true);
-  // $FlowFixMe
-  helpers.isFilePathEslintignored.mockImplementation(() => true);
-
-  formatOnSave(editor);
-
-  expect(options.shouldRespectEslintignore).toHaveBeenCalled();
-  expect(helpers.isFilePathEslintignored).toHaveBeenCalledWith('foo.js');
-  expect(executePrettierOnBufferRange).not.toHaveBeenCalled();
-  expect(executePrettierOnEmbeddedScripts).not.toHaveBeenCalled();
-});
-
-test('does not attempt to check filePath matches .eslintignore if `currentFilePath` is null', () => {
-  // $FlowFixMe
-  helpers.getCurrentFilePath.mockImplementation(() => null);
-
-  formatOnSave(editor);
-
-  expect(options.shouldRespectEslintignore).not.toHaveBeenCalled();
-});
diff --git a/src/formatOnSave/index.test.js b/src/formatOnSave/index.test.js
new file mode 100644
index 0000000..d661e08
--- /dev/null
+++ b/src/formatOnSave/index.test.js
@@ -0,0 +1,42 @@
+jest.mock('../executePrettier');
+jest.mock('../editorInterface');
+jest.mock('./shouldFormatOnSave');
+
+const { executePrettierOnBufferRange, executePrettierOnEmbeddedScripts } = require('../executePrettier');
+const { isCurrentScopeEmbeddedScope, getBufferRange } = require('../editorInterface');
+const createMockTextEditor = require('../../tests/mocks/textEditor');
+const shouldFormatOnSave = require('./shouldFormatOnSave');
+const formatOnSave = require('./index');
+
+it('executes prettier on the buffer range if appropriate and scope is not embedded', () => {
+  const editor = createMockTextEditor();
+  const mockRange = { start: [0, 0], end: [0, 1] };
+  getBufferRange.mockImplementation(() => mockRange);
+  shouldFormatOnSave.mockImplementation(() => true);
+  isCurrentScopeEmbeddedScope.mockImplementation(() => false);
+
+  formatOnSave(editor);
+
+  expect(executePrettierOnBufferRange).toHaveBeenCalledWith(editor, mockRange);
+});
+
+it('executes prettier on the embedded scripts if appropriate and scope is embedded', () => {
+  const editor = createMockTextEditor();
+  shouldFormatOnSave.mockImplementation(() => true);
+  isCurrentScopeEmbeddedScope.mockImplementation(() => true);
+
+  formatOnSave(editor);
+
+  expect(executePrettierOnEmbeddedScripts).toHaveBeenCalledWith(editor);
+});
+
+it('does nothing if it should not format on save', () => {
+  const editor = createMockTextEditor();
+  shouldFormatOnSave.mockImplementation(() => false);
+
+  formatOnSave(editor);
+
+  expect(shouldFormatOnSave).toHaveBeenCalledWith(editor);
+  expect(executePrettierOnBufferRange).not.toHaveBeenCalled();
+  expect(executePrettierOnEmbeddedScripts).not.toHaveBeenCalled();
+});
diff --git a/src/formatOnSave/isFilePathEslintIgnored.test.js b/src/formatOnSave/isFilePathEslintIgnored.test.js
new file mode 100644
index 0000000..fde51c9
--- /dev/null
+++ b/src/formatOnSave/isFilePathEslintIgnored.test.js
@@ -0,0 +1,26 @@
+const path = require('path');
+const isFilePathEslintIgnored = require('./isFilePathEslintIgnored');
+
+it('returns true if the filepath is eslintignored', () => {
+  const filePath = path.join(__dirname, '..', '..', 'tests', 'fixtures', 'matchesEslintignore.js');
+
+  const actual = isFilePathEslintIgnored(filePath);
+
+  expect(actual).toBe(true);
+});
+
+it('returns false if the filepath is not eslintignored', () => {
+  const filePath = path.join(__dirname, '..', '..', 'tests', 'fixtures', 'doesNotMatchEslintignore.js');
+
+  const actual = isFilePathEslintIgnored(filePath);
+
+  expect(actual).toBe(false);
+});
+
+it('returns false if no .eslintignore file can be found', () => {
+  const filePath = '';
+
+  const actual = isFilePathEslintIgnored(filePath);
+
+  expect(actual).toBe(false);
+});
diff --git a/src/formatOnSave/shouldFormatOnSave.test.js b/src/formatOnSave/shouldFormatOnSave.test.js
new file mode 100644
index 0000000..a06a52e
--- /dev/null
+++ b/src/formatOnSave/shouldFormatOnSave.test.js
@@ -0,0 +1,88 @@
+jest.mock('../editorInterface');
+jest.mock('../atomInterface');
+jest.mock('./isFilePathEslintIgnored');
+
+const createMockTextEditor = require('../../tests/mocks/textEditor');
+const {
+  isFormatOnSaveEnabled,
+  getExcludedGlobs,
+  getWhitelistedGlobs,
+  getScopes,
+} = require('../atomInterface');
+const { getCurrentScope, getCurrentFilePath } = require('../editorInterface');
+const isFilePathEslintIgnored = require('./isFilePathEslintIgnored');
+const shouldFormatOnSave = require('./shouldFormatOnSave');
+
+const fakeCurrentFilePath = 'foo.js';
+const callShouldFormatOnSave = () => shouldFormatOnSave(createMockTextEditor());
+
+beforeEach(() => {
+  isFormatOnSaveEnabled.mockImplementation(() => true);
+  getScopes.mockImplementation(() => ['js', 'jsx']);
+  getCurrentScope.mockImplementation(() => 'js');
+  getCurrentFilePath.mockImplementation(() => fakeCurrentFilePath);
+  isFilePathEslintIgnored.mockImplementation(() => false);
+});
+
+it('returns true if should format on save', () => {
+  const actual = callShouldFormatOnSave();
+
+  expect(actual).toBe(true);
+});
+
+it('returns false if formatOnSave is not enabled', () => {
+  isFormatOnSaveEnabled.mockImplementation(() => false);
+
+  const actual = callShouldFormatOnSave();
+
+  expect(actual).toBe(false);
+});
+
+it('returns false if there is no filePath', () => {
+  getCurrentFilePath.mockImplementation(() => undefined);
+
+  const actual = callShouldFormatOnSave();
+
+  expect(actual).toBe(false);
+});
+
+it('returns false if the filepath is blacklisted and not whitelisted', () => {
+  getExcludedGlobs.mockImplementation(() => [fakeCurrentFilePath]);
+
+  const actual = callShouldFormatOnSave();
+
+  expect(actual).toBe(false);
+});
+
+it('returns true if the filepath is blacklisted and also whitelisted', () => {
+  getExcludedGlobs.mockImplementation(() => [fakeCurrentFilePath]);
+  getWhitelistedGlobs.mockImplementation(() => [fakeCurrentFilePath]);
+
+  const actual = callShouldFormatOnSave();
+
+  expect(actual).toBe(true);
+});
+
+it('returns false if whitelist globs exist but the filepath does not match them', () => {
+  getWhitelistedGlobs.mockImplementation(() => ['foo.ruby']);
+
+  const actual = callShouldFormatOnSave();
+
+  expect(actual).toBe(false);
+});
+
+it('returns false if the filepath is not in scope', () => {
+  getCurrentScope.mockImplementation(() => 'ruby');
+
+  const actual = callShouldFormatOnSave();
+
+  expect(actual).toBe(false);
+});
+
+it('returns false if the filepath is eslintignored', () => {
+  isFilePathEslintIgnored.mockImplementation(() => true);
+
+  const actual = callShouldFormatOnSave();
+
+  expect(actual).toBe(false);
+});
diff --git a/src/helpers.test.js b/src/helpers.test.js
deleted file mode 100644
index b0d033e..0000000
--- a/src/helpers.test.js
+++ /dev/null
@@ -1,272 +0,0 @@
-// @flow
-const path = require('path');
-const atomLinter = require('atom-linter');
-const prettier = require('prettier');
-
-const textEditor = require('../tests/mocks/textEditor');
-const {
-  getConfigOption,
-  getCurrentFilePath,
-  getPrettier,
-  isInScope,
-  isFilePathEslintignored,
-  isFilePathExcluded,
-  isFilePathWhitelisted,
-  isCurrentScopeEmbeddedScope,
-  runLinter,
-  getDebugInfo,
-} = require('./helpers');
-
-jest.mock('atom-linter');
-jest.mock('prettier');
-
-describe('getConfigOption', () => {
-  test('retrieves a config option from the prettier-atom config', () => {
-    const mockGet = jest.fn(() => 'foo');
-    atom = { config: { get: mockGet } };
-
-    const actual = getConfigOption('foo');
-    const expected = 'foo';
-
-    expect(mockGet).lastCalledWith('prettier-atom.foo');
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('getCurrentFilePath', () => {
-  test("returns the editor's current filePath", () => {
-    const editor = textEditor();
-
-    const actual = getCurrentFilePath(editor);
-    const expected = 'xyz.js';
-
-    expect(actual).toBe(expected);
-  });
-
-  test('returns undefined if there is no file', () => {
-    const editor = textEditor({ buffer: { file: null } });
-
-    const actual = getCurrentFilePath(editor);
-    const expected = undefined;
-
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('getPrettier', () => {
-  test('returns default prettier if no prettier package can be found', () => {
-    atomLinter.findCached.mockImplementation(() => undefined);
-    const filePath = path.join(__dirname, 'sourceFile.js');
-
-    const actual = getPrettier(filePath);
-    const expected = prettier;
-
-    expect(actual).toEqual(expected);
-  });
-
-  test('returns local prettier instance when it exists', () => {
-    const prettierLib = path.join(__dirname, '..', 'tests', 'fixtures', 'prettier.js');
-    atomLinter.findCached.mockImplementation(() => prettierLib);
-
-    const filePath = path.join(__dirname, '..', 'tests', 'fixtures', 'sourceFile.js');
-
-    const actual = getPrettier(filePath);
-    // $FlowFixMe
-    const expected = require(prettierLib); // eslint-disable-line
-
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('isInScope', () => {
-  test("returns true if the editor's current scope is listed among the config's scopes", () => {
-    const mockGet = jest.fn(() => ['source.js.jsx', 'text.html.vue']);
-    atom = { config: { get: mockGet } };
-    const mockGetGrammar = jest.fn(() => ({ scopeName: 'source.js.jsx' }));
-    const editor = textEditor({ getGrammar: mockGetGrammar });
-
-    const actual = isInScope(editor);
-    const expected = true;
-
-    expect(mockGet).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.scopes');
-    expect(mockGetGrammar).toHaveBeenCalled();
-    expect(actual).toBe(expected);
-  });
-
-  test("returns false if the editor's current scope is not listed among the config's scopes", () => {
-    const mockGet = jest.fn(() => ['source.js.jsx', 'text.html.vue']);
-    atom = { config: { get: mockGet } };
-    const mockGetGrammar = jest.fn(() => ({ scopeName: 'source.foo' }));
-    const editor = textEditor({ getGrammar: mockGetGrammar });
-
-    const actual = isInScope(editor);
-    const expected = false;
-
-    expect(mockGetGrammar).toHaveBeenCalled();
-    expect(mockGet).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.scopes');
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('isFilePathEslintignored', () => {
-  test('is false if no .eslintignore file can be found', () => {
-    atomLinter.findCached.mockImplementation(() => null);
-    const filePath = path.join(__dirname, '..', 'tests', 'fixtures', 'matchesEslintignore.js');
-
-    const actual = isFilePathEslintignored(filePath);
-    const expected = false;
-
-    expect(actual).toBe(expected);
-  });
-
-  test('is false if the filePath does not match a glob in the nearest eslintignore', () => {
-    atomLinter.findCached.mockImplementation(() =>
-      path.join(__dirname, '..', 'tests', 'fixtures', '.eslintignore'),
-    );
-    const filePath = path.join(__dirname, '..', 'tests', 'fixtures', 'doesNotMatchEslintignore.js');
-
-    const actual = isFilePathEslintignored(filePath);
-    const expected = false;
-
-    expect(atomLinter.findCached).toHaveBeenCalledWith(
-      path.join(__dirname, '..', 'tests', 'fixtures'),
-      '.eslintignore',
-    );
-    expect(actual).toBe(expected);
-  });
-
-  test('is true if the filePath does match a glob in the nearest eslintignore', () => {
-    atomLinter.findCached.mockImplementation(() =>
-      path.join(__dirname, '..', 'tests', 'fixtures', '.eslintignore'),
-    );
-    const filePath = path.join(__dirname, '..', 'tests', 'fixtures', 'matchesEslintignore.js');
-
-    const actual = isFilePathEslintignored(filePath);
-    const expected = true;
-
-    expect(atomLinter.findCached).toHaveBeenCalledWith(
-      path.join(__dirname, '..', 'tests', 'fixtures'),
-      '.eslintignore',
-    );
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('isFilePathExcluded', () => {
-  test('returns false if filePath is not listed in the globs', () => {
-    atom = { config: { get: jest.fn(() => []) } };
-
-    const actual = isFilePathExcluded('foo.js');
-    const expected = false;
-
-    expect(atom.config.get).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.excludedGlobs');
-    expect(actual).toBe(expected);
-  });
-
-  test('returns true if filePath is listed in the globs', () => {
-    atom = { config: { get: jest.fn(() => ['*.js']) } };
-
-    const actual = isFilePathExcluded('foo.js');
-    const expected = true;
-
-    expect(atom.config.get).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.excludedGlobs');
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('isFilePathWhitelisted', () => {
-  test('returns false if filePath is not listed in the globs', () => {
-    atom = { config: { get: jest.fn(() => []) } };
-
-    const actual = isFilePathWhitelisted('foo.js');
-    const expected = false;
-
-    expect(atom.config.get).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.whitelistedGlobs');
-    expect(actual).toBe(expected);
-  });
-
-  test('returns true if filePath is not listed in the globs', () => {
-    atom = { config: { get: jest.fn(() => ['*.js']) } };
-
-    const actual = isFilePathWhitelisted('foo.js');
-    const expected = true;
-
-    expect(atom.config.get).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.whitelistedGlobs');
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('isCurrentScopeEmbeddedScope', () => {
-  test('returns true if current scope is embedded scope', () => {
-    const editor = textEditor({ getGrammar: () => ({ scopeName: 'text.html.vue' }) });
-
-    const actual = isCurrentScopeEmbeddedScope(editor);
-
-    expect(actual).toBe(true);
-  });
-
-  test('returns false if the current scope is not embedded scope', () => {
-    const editor = textEditor({ getGrammar: () => ({ scopeName: 'source.js.jsx' }) });
-
-    const actual = isCurrentScopeEmbeddedScope(editor);
-
-    expect(actual).toBe(false);
-  });
-});
-
-describe('runLinter()', () => {
-  test('runs `linter:lint` command', () => {
-    const editor = textEditor();
-    const viewMock = { isViewMock: true };
-    const commandsMock = [{ name: 'linter:lint', displayName: 'Linter: Lint' }];
-    atom = {
-      commands: { dispatch: jest.fn(), findCommands: jest.fn(() => commandsMock) },
-      views: { getView: jest.fn(() => viewMock) },
-    };
-
-    runLinter(editor);
-
-    expect(atom.views.getView).toHaveBeenCalledWith(editor);
-    expect(atom.commands.dispatch).toHaveBeenCalledWith(viewMock, 'linter:lint');
-  });
-
-  test('does nothing if `linter:lint` command does not exist', () => {
-    const editor = textEditor();
-    const viewMock = { isViewMock: true };
-    const commandsMock = [];
-    atom = {
-      commands: { dispatch: jest.fn(), findCommands: jest.fn(() => commandsMock) },
-      views: { getView: jest.fn(() => viewMock) },
-    };
-
-    runLinter(editor);
-
-    expect(atom.commands.findCommands).toHaveBeenCalledWith({ target: viewMock });
-    expect(atom.commands.dispatch).not.toHaveBeenCalled();
-  });
-});
-
-describe('getDebugInfo()', () => {
-  test('returns versions of prettier-atom + dependencies, and its configuration', () => {
-    const expectedAtomVersion = '4.44.44';
-    const expectedConfig = 'config';
-    const mockGetVersion = jest.fn(() => expectedAtomVersion);
-    const mockGet = jest.fn(() => expectedConfig);
-    atom = {
-      getVersion: mockGetVersion,
-      config: { get: mockGet },
-    };
-
-    const minVersionLength = '0.0.0'.length;
-    const info = getDebugInfo();
-
-    expect(info.atomVersion).toBe(expectedAtomVersion);
-    expect(typeof info.prettierVersion).toBe('string');
-    expect(typeof info.prettierAtomVersion).toBe('string');
-    expect(typeof info.prettierESLintVersion).toBe('string');
-    expect(info.prettierVersion.length).toBeGreaterThanOrEqual(minVersionLength);
-    expect(info.prettierAtomVersion.length).toBeGreaterThanOrEqual(minVersionLength);
-    expect(info.prettierESLintVersion.length).toBeGreaterThanOrEqual(minVersionLength);
-    expect(info.prettierAtomConfig).toBe(expectedConfig);
-  });
-});
diff --git a/src/manualFormat/index.test.js b/src/manualFormat/index.test.js
new file mode 100644
index 0000000..86bdf44
--- /dev/null
+++ b/src/manualFormat/index.test.js
@@ -0,0 +1,40 @@
+jest.mock('../executePrettier');
+jest.mock('../editorInterface');
+
+const textEditor = require('../../tests/mocks/textEditor');
+const { executePrettierOnBufferRange, executePrettierOnEmbeddedScripts } = require('../executePrettier');
+const { isCurrentScopeEmbeddedScope, getBufferRange } = require('../editorInterface');
+const manualFormat = require('./index');
+
+it('executes prettier on buffer range', () => {
+  const editor = textEditor();
+  const bufferRange = {};
+  getBufferRange.mockImplementation(() => bufferRange);
+
+  manualFormat(editor);
+
+  expect(executePrettierOnBufferRange).toHaveBeenCalledWith(editor, bufferRange);
+});
+
+it('executes prettier on selected buffer ranges if there is text selected', () => {
+  const bufferRange = {};
+  const editor = textEditor({
+    getSelectedText: jest.fn(() => 'const foo = 2'),
+    getSelectedBufferRanges: jest.fn(() => [{}]),
+  });
+
+  manualFormat(editor);
+
+  expect(editor.getSelectedText).toHaveBeenCalled();
+  expect(executePrettierOnBufferRange).toHaveBeenCalledWith(editor, bufferRange);
+});
+
+it('executes prettier on embedded scripts if is embedded scope', () => {
+  const editor = textEditor();
+  isCurrentScopeEmbeddedScope.mockImplementation(() => true);
+
+  manualFormat(editor);
+
+  expect(isCurrentScopeEmbeddedScope).toHaveBeenCalledWith(editor);
+  expect(executePrettierOnEmbeddedScripts).toHaveBeenCalledWith(editor);
+});
diff --git a/src/options.test.js b/src/options.test.js
deleted file mode 100644
index d8c5de5..0000000
--- a/src/options.test.js
+++ /dev/null
@@ -1,267 +0,0 @@
-const editorconfig = require('editorconfig');
-const textEditor = require('../tests/mocks/textEditor');
-
-const {
-  shouldUseEslint,
-  shouldDisplayErrors,
-  getPrettierOption,
-  getPrettierOptions,
-  getPrettierEslintOption,
-  getPrettierEslintOptions,
-  getEditorConfigOptions,
-  isWhitelistProvided,
-  isLinterEslintAutofixEnabled,
-} = require('./options');
-
-jest.mock('editorconfig');
-
-describe('shouldUseEslint', () => {
-  test('is true if the config option is enabled', () => {
-    const mockGet = jest.fn(() => true);
-    atom = { config: { get: mockGet } };
-
-    const actual = shouldUseEslint();
-    const expected = true;
-
-    expect(mockGet).toHaveBeenCalledWith('prettier-atom.useEslint');
-    expect(actual).toBe(expected);
-  });
-
-  test('is false if the config option is not enabled', () => {
-    const mockGet = jest.fn(() => false);
-    atom = { config: { get: mockGet } };
-
-    const actual = shouldUseEslint();
-    const expected = false;
-
-    expect(mockGet).toHaveBeenCalledWith('prettier-atom.useEslint');
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('shouldDisplayErrors', () => {
-  test('is true if the user has chosen to silence errors', () => {
-    const mockGet = jest.fn(() => true);
-    atom = { config: { get: mockGet } };
-
-    const actual = shouldDisplayErrors();
-    const expected = false;
-
-    expect(mockGet).lastCalledWith('prettier-atom.silenceErrors');
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('getPrettierOption', () => {
-  test('retrieves the given prettier option from the prettier-atom config', () => {
-    const mockGet = jest.fn(() => 'auto');
-    atom = { config: { get: mockGet } };
-
-    const actual = getPrettierOption('tabWidth');
-    const expected = 'auto';
-
-    expect(mockGet).lastCalledWith('prettier-atom.prettierOptions.tabWidth');
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('getPrettierEslintOption', () => {
-  test('retrieves the given prettier-eslint option from the prettier-atom config', () => {
-    const mockGet = jest.fn(() => true);
-    atom = { config: { get: mockGet } };
-
-    const actual = getPrettierEslintOption('prettierLast');
-    const expected = true;
-
-    expect(mockGet).lastCalledWith('prettier-atom.prettierEslintOptions.prettierLast');
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('getPrettierOptions', () => {
-  test('returns all prettier options', () => {
-    const mockGet = option =>
-      ({
-        'prettier-atom.prettierOptions.printWidth': 80,
-        'prettier-atom.prettierOptions.tabWidth': 2,
-        'prettier-atom.prettierOptions.parser': 'flow',
-        'prettier-atom.prettierOptions.singleQuote': true,
-        'prettier-atom.prettierOptions.trailingComma': true,
-        'prettier-atom.prettierOptions.bracketSpacing': true,
-        'prettier-atom.prettierOptions.semi': true,
-        'prettier-atom.prettierOptions.useTabs': true,
-        'prettier-atom.prettierOptions.jsxBracketSameLine': true,
-      }[option]);
-    atom = { config: { get: mockGet } };
-    const editor = textEditor();
-
-    const actual = getPrettierOptions(editor);
-
-    expect(actual).toMatchSnapshot();
-  });
-
-  test('uses the editor tab width if config is set to "auto"', () => {
-    const mockGet = option =>
-      option === 'editor.tabLength'
-        ? 8
-        : {
-          'prettier-atom.prettierOptions.printWidth': 80,
-          'prettier-atom.prettierOptions.tabWidth': 'auto',
-          'prettier-atom.prettierOptions.parser': 'flow',
-          'prettier-atom.prettierOptions.singleQuote': true,
-          'prettier-atom.prettierOptions.trailingComma': true,
-          'prettier-atom.prettierOptions.bracketSpacing': true,
-          'prettier-atom.prettierOptions.semi': true,
-          'prettier-atom.prettierOptions.useTabs': true,
-          'prettier-atom.prettierOptions.jsxBracketSameLine': true,
-        }[option];
-    atom = { config: { get: mockGet } };
-    const editor = textEditor({ getLastCursor: () => ({ getScopeDescriptor: () => 'source.js.jsx' }) });
-
-    const actual = getPrettierOptions(editor).tabWidth;
-    const expected = 8;
-
-    expect(actual).toEqual(expected);
-  });
-
-  describe('editorconfig integration', () => {
-    test('uses editorconfig options when enabled in config', () => {
-      const mockGet = option =>
-        ({
-          'prettier-atom.useEditorConfig': true,
-          'prettier-atom.prettierOptions.printWidth': 80,
-          'prettier-atom.prettierOptions.tabWidth': 2,
-          'prettier-atom.prettierOptions.parser': 'flow',
-          'prettier-atom.prettierOptions.singleQuote': true,
-          'prettier-atom.prettierOptions.trailingComma': true,
-          'prettier-atom.prettierOptions.bracketSpacing': true,
-          'prettier-atom.prettierOptions.semi': true,
-          'prettier-atom.prettierOptions.useTabs': true,
-          'prettier-atom.prettierOptions.jsxBracketSameLine': true,
-        }[option]);
-      atom = { config: { get: mockGet } };
-      editorconfig.parseSync.mockImplementation(() => ({
-        tab_width: 4,
-        max_line_length: 100,
-        indent_style: 'space',
-      }));
-      const editor = textEditor();
-
-      const actual = getPrettierOptions(editor);
-      expect(actual).toMatchSnapshot();
-    });
-
-    test('ignores editorconfig when disabled in config', () => {
-      const mockGet = option =>
-        ({
-          'prettier-atom.useEditorConfig': false,
-        }[option]);
-      atom = { config: { get: mockGet } };
-      const editor = textEditor();
-
-      getPrettierOptions(editor);
-      expect(editorconfig.parseSync).not.toHaveBeenCalled();
-    });
-
-    test('ignores editorconfig for unsaved files', () => {
-      const mockGet = option =>
-        ({
-          'prettier-atom.useEditorConfig': true,
-        }[option]);
-      atom = { config: { get: mockGet } };
-      const editor = textEditor({
-        buffer: {
-          file: {
-            path: '',
-          },
-        },
-      });
-
-      getPrettierOptions(editor);
-      expect(editorconfig.parseSync).not.toHaveBeenCalled();
-    });
-  });
-});
-
-describe('getEditorConfigOptions', () => {
-  test('maps editorconfig options', () => {
-    editorconfig.parseSync.mockImplementation(() => ({
-      indent_size: 4,
-      end_of_line: 'lf',
-      charset: 'utf-8',
-      trim_trailing_whitespace: true,
-      insert_final_newline: true,
-      tab_width: 2,
-      max_line_length: 100,
-    }));
-    const actual = getEditorConfigOptions('filename.js');
-    const expected = {
-      tabWidth: 2,
-      printWidth: 100,
-    };
-    expect(actual).toEqual(expected);
-    expect(editorconfig.parseSync).toHaveBeenCalledWith('filename.js');
-  });
-
-  describe('returns boolean value for useTabs option', () => {
-    test('when indent style is tab', () => {
-      editorconfig.parseSync.mockImplementation(() => ({
-        indent_style: 'tab',
-      }));
-      const actual = getEditorConfigOptions('filename.js').useTabs;
-      const expected = true;
-      expect(actual).toEqual(expected);
-    });
-    test('when indent style is space', () => {
-      editorconfig.parseSync.mockImplementation(() => ({
-        indent_style: 'space',
-      }));
-      const actual = getEditorConfigOptions('filename.js').useTabs;
-      const expected = false;
-      expect(actual).toEqual(expected);
-    });
-  });
-});
-
-describe('getPrettierEslintOptions', () => {
-  test('returns all prettier-eslint options', () => {
-    const mockGet = option =>
-      ({
-        'prettier-atom.prettierEslintOptions.prettierLast': true,
-      }[option]);
-    atom = { config: { get: mockGet } };
-    const actual = getPrettierEslintOptions();
-
-    expect(actual).toMatchSnapshot();
-  });
-});
-
-describe('isLinterEslintAutofixEnabled', () => {
-  test('returns the value from the linter-eslint config', () => {
-    atom = { config: { get: jest.fn(() => true) } };
-
-    const actual = isLinterEslintAutofixEnabled();
-    const expected = true;
-
-    expect(atom.config.get).toHaveBeenCalledWith('linter-eslint.fixOnSave');
-    expect(actual).toBe(expected);
-  });
-});
-
-describe('isWhitelistProvided', () => {
-  test('returns true if there are whitelist items provided', () => {
-    atom = { config: { get: jest.fn(() => ['*.js']) } };
-    const actual = isWhitelistProvided();
-    const expected = true;
-    expect(atom.config.get).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.whitelistedGlobs');
-    expect(actual).toBe(expected);
-  });
-
-  test('returns false if the whitelist is empty', () => {
-    atom = { config: { get: jest.fn(() => []) } };
-    const actual = isWhitelistProvided();
-    const expected = false;
-    expect(atom.config.get).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.whitelistedGlobs');
-    expect(actual).toBe(expected);
-  });
-});
diff --git a/src/statusTile.test.js b/src/statusTile.test.js
deleted file mode 100644
index a96752a..0000000
--- a/src/statusTile.test.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// @flow
-const { createStatusTile, updateStatusTile } = require('./statusTile');
-
-beforeEach(() => {
-  atom = {
-    config: { get: jest.fn(() => true) },
-    tooltips: { add: jest.fn(() => ({ dispose: jest.fn() })) },
-  };
-});
-
-describe('createStatusTile()', () => {
-  it('creates a div with "Prettier" and a tooltip indicating formatOnSave status', () => {
-    global.document = {
-      createElement: jest.fn(() => ({
-        classList: {
-          add: jest.fn(),
-        },
-        dataset: {},
-        appendChild: jest.fn(),
-        addEventListener: jest.fn(),
-      })),
-      createTextNode: jest.fn(arg => arg),
-    };
-
-    const div = createStatusTile();
-
-    expect(div.dataset.formatOnSave).toBe('enabled');
-    expect(div.classList.add).toHaveBeenCalledWith('prettier-atom-status-tile');
-    expect(div.appendChild).toHaveBeenCalledWith('Prettier');
-  });
-});
-
-describe('updateStatusTile()', () => {
-  it('disposes any existing tooltip and adds a new one with the current formatOnSave status', () => {
-    const div = { dataset: {} };
-    const disposable = { add: jest.fn() };
-
-    // $FlowFixMe
-    const tooltip = updateStatusTile(disposable, div);
-    expect(atom.tooltips.add).toHaveBeenCalledWith(div, { title: 'Format on Save: enabled' });
-
-    // $FlowFixMe
-    updateStatusTile(disposable, div);
-    expect(tooltip.dispose).toHaveBeenCalled();
-  });
-});
diff --git a/src/statusTile/createStatusTile.js b/src/statusTile/createStatusTile.js
new file mode 100644
index 0000000..c6ea1ba
--- /dev/null
+++ b/src/statusTile/createStatusTile.js
@@ -0,0 +1,18 @@
+// @flow
+const getFormatOnSaveStatus = require('./getFormatOnSaveStatus');
+const { toggleFormatOnSave } = require('../atomInterface');
+
+const createStatusTile = () => {
+  const element = document.createElement('div');
+  const prettierTextNode = document.createTextNode('Prettier');
+
+  element.appendChild(prettierTextNode);
+  element.classList.add('prettier-atom-status-tile');
+  element.classList.add('inline-block');
+  element.dataset.formatOnSave = getFormatOnSaveStatus();
+  element.addEventListener('click', toggleFormatOnSave);
+
+  return element;
+};
+
+module.exports = createStatusTile;
diff --git a/src/statusTile/createStatusTile.test.js b/src/statusTile/createStatusTile.test.js
new file mode 100644
index 0000000..e4bd958
--- /dev/null
+++ b/src/statusTile/createStatusTile.test.js
@@ -0,0 +1,30 @@
+jest.mock('./getFormatOnSaveStatus');
+jest.mock('../atomInterface');
+
+const getFormatOnSaveStatus = require('./getFormatOnSaveStatus');
+const { toggleFormatOnSave } = require('../atomInterface');
+const createStatusTile = require('./createStatusTile');
+
+beforeEach(() => {
+  getFormatOnSaveStatus.mockImplementation(() => 'enabled');
+  global.document = {
+    createElement: jest.fn(() => ({
+      classList: {
+        add: jest.fn(),
+      },
+      dataset: {},
+      appendChild: jest.fn(),
+      addEventListener: jest.fn(),
+    })),
+    createTextNode: jest.fn(arg => arg),
+  };
+});
+
+it('creates a div with "Prettier" and a tooltip indicating formatOnSave status', () => {
+  const div = createStatusTile();
+
+  expect(div.dataset.formatOnSave).toBe('enabled');
+  expect(div.classList.add).toHaveBeenCalledWith('prettier-atom-status-tile');
+  expect(div.appendChild).toHaveBeenCalledWith('Prettier');
+  expect(div.addEventListener).toHaveBeenCalledWith('click', toggleFormatOnSave);
+});
diff --git a/src/statusTile/getFormatOnSaveStatus.test.js b/src/statusTile/getFormatOnSaveStatus.test.js
new file mode 100644
index 0000000..5ffc3fe
--- /dev/null
+++ b/src/statusTile/getFormatOnSaveStatus.test.js
@@ -0,0 +1,20 @@
+jest.mock('../atomInterface');
+
+const { isFormatOnSaveEnabled } = require('../atomInterface');
+const getFormatOnSaveStatus = require('./getFormatOnSaveStatus');
+
+it('returns "disabled" if formatOnSave is disabled', () => {
+  isFormatOnSaveEnabled.mockImplementation(() => false);
+
+  const actual = getFormatOnSaveStatus();
+
+  expect(actual).toEqual('disabled');
+});
+
+it('returns "enabled" if formatOnSave is enabled', () => {
+  isFormatOnSaveEnabled.mockImplementation(() => true);
+
+  const actual = getFormatOnSaveStatus();
+
+  expect(actual).toEqual('enabled');
+});
diff --git a/src/statusTile/updateStatusTile.js b/src/statusTile/updateStatusTile.js
new file mode 100644
index 0000000..cf198d9
--- /dev/null
+++ b/src/statusTile/updateStatusTile.js
@@ -0,0 +1,19 @@
+// @flow
+const { disposeTooltip, setTooltip } = require('./tooltip');
+const getFormatOnSaveStatus = require('./getFormatOnSaveStatus');
+const { addTooltip } = require('../atomInterface');
+
+const updateStatusTile = (disposable: Atom$Disposable, element: HTMLElement) => {
+  disposeTooltip();
+
+  element.dataset.formatOnSave = getFormatOnSaveStatus(); // eslint-disable-line no-param-reassign
+
+  const newTooltip = addTooltip(element, { title: `Format on Save: ${getFormatOnSaveStatus()}` });
+
+  setTooltip(newTooltip);
+  disposable.add(newTooltip);
+
+  return newTooltip;
+};
+
+module.exports = updateStatusTile;
diff --git a/src/statusTile/updateStatusTile.test.js b/src/statusTile/updateStatusTile.test.js
new file mode 100644
index 0000000..b1e0e68
--- /dev/null
+++ b/src/statusTile/updateStatusTile.test.js
@@ -0,0 +1,50 @@
+jest.mock('./tooltip');
+jest.mock('./getFormatOnSaveStatus');
+
+const { setTooltip, disposeTooltip } = require('./tooltip');
+const updateStatusTile = require('./updateStatusTile');
+const getFormatOnSaveStatus = require('./getFormatOnSaveStatus');
+
+const callUpdateStatusTile = () => {
+  const div = { dataset: {} };
+  const disposable = { add: jest.fn() };
+
+  updateStatusTile(disposable, div);
+
+  return { div, disposable };
+};
+
+beforeEach(() => {
+  atom = { tooltips: { add: jest.fn(x => x) } };
+});
+
+it('disposes existing tooltip', () => {
+  disposeTooltip.mockImplementation(require.requireActual('./tooltip').disposeTooltip);
+
+  callUpdateStatusTile();
+
+  expect(disposeTooltip).toHaveBeenCalled();
+});
+
+it('adds the new tooltip with the updated formatOnSave status to atom', () => {
+  getFormatOnSaveStatus.mockImplementation(() => 'enabled');
+
+  const { div } = callUpdateStatusTile();
+
+  expect(atom.tooltips.add).toHaveBeenCalledWith(div, { title: 'Format on Save: enabled' });
+  expect(div.dataset.formatOnSave).toEqual('enabled');
+});
+
+it('updates our reference to the new tooltip so that we may dispose it manually', () => {
+  setTooltip.mockImplementation(require.requireActual('./tooltip').setTooltip);
+
+  const { div } = callUpdateStatusTile();
+
+  expect(setTooltip).toHaveBeenCalledWith(div);
+});
+
+it("registers the new tooltip with given disposable so it's disposed when package is deactivated", () => {
+  const { disposable, div } = callUpdateStatusTile();
+
+  expect(disposable.add).toHaveBeenCalledWith(div);
+});
diff --git a/src/toggleFormatOnSave.test.js b/src/toggleFormatOnSave.test.js
deleted file mode 100644
index 3c486da..0000000
--- a/src/toggleFormatOnSave.test.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// @flow
-const toggleFormatOnSave = require('./toggleFormatOnSave');
-
-test('it sets formatOnSaveOptions.enabled to false if it was true', () => {
-  atom = {
-    config: {
-      get: jest.fn(() => true),
-      set: jest.fn(),
-    },
-  };
-
-  toggleFormatOnSave();
-
-  expect(atom.config.set).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.enabled', false);
-});
-
-test('it sets formatOnSaveOptions.enabled to true if it was false', () => {
-  atom = {
-    config: {
-      get: jest.fn(() => false),
-      set: jest.fn(),
-    },
-  };
-
-  toggleFormatOnSave();
-
-  expect(atom.config.set).toHaveBeenCalledWith('prettier-atom.formatOnSaveOptions.enabled', true);
-});
diff --git a/src/warnAboutLinterEslintFixOnSave.test.js b/src/warnAboutLinterEslintFixOnSave.test.js
deleted file mode 100644
index fc21bab..0000000
--- a/src/warnAboutLinterEslintFixOnSave.test.js
+++ /dev/null
@@ -1,40 +0,0 @@
-// @flow
-const options = require('./options');
-const warnAboutLinterEslintFixOnSave = require('./warnAboutLinterEslintFixOnSave');
-
-jest.mock('./options');
-
-beforeEach(() => {
-  atom = { notifications: { addWarning: jest.fn() } };
-  // $FlowFixMe
-  options.isLinterEslintAutofixEnabled.mockImplementation(() => true);
-  // $FlowFixMe
-  options.shouldUseEslint.mockImplementation(() => true);
-});
-
-test('it warns about not having eslint autofix on', () => {
-  warnAboutLinterEslintFixOnSave();
-
-  // $FlowFixMe
-  expect(atom.notifications.addWarning).toHaveBeenCalled();
-});
-
-test('it does not warn if eslint autofix is disabled', () => {
-  // $FlowFixMe
-  options.isLinterEslintAutofixEnabled.mockImplementation(() => false);
-
-  warnAboutLinterEslintFixOnSave();
-
-  // $FlowFixMe
-  expect(atom.notifications.addWarning).not.toHaveBeenCalled();
-});
-
-test('it does not warn if eslint integration is disabled', () => {
-  // $FlowFixMe
-  options.shouldUseEslint.mockImplementation(() => false);
-
-  warnAboutLinterEslintFixOnSave();
-
-  // $FlowFixMe
-  expect(atom.notifications.addWarning).not.toHaveBeenCalled();
-});
diff --git a/src/warnAboutLinterEslintFixOnSave/index.test.js b/src/warnAboutLinterEslintFixOnSave/index.test.js
new file mode 100644
index 0000000..6ab535b
--- /dev/null
+++ b/src/warnAboutLinterEslintFixOnSave/index.test.js
@@ -0,0 +1,31 @@
+jest.mock('../atomInterface');
+
+const { isLinterEslintAutofixEnabled, shouldUseEslint, addWarningNotification } = require('../atomInterface');
+const warnAboutLinterEslintFixOnSave = require('./index');
+
+test('it warns about not having eslint autofix on', () => {
+  isLinterEslintAutofixEnabled.mockImplementation(() => true);
+  shouldUseEslint.mockImplementation(() => true);
+
+  warnAboutLinterEslintFixOnSave();
+
+  expect(addWarningNotification).toHaveBeenCalled();
+});
+
+test('it does not warn if eslint autofix is disabled', () => {
+  isLinterEslintAutofixEnabled.mockImplementation(() => false);
+  shouldUseEslint.mockImplementation(() => true);
+
+  warnAboutLinterEslintFixOnSave();
+
+  expect(addWarningNotification).not.toHaveBeenCalled();
+});
+
+test('it does not warn if eslint integration is disabled', () => {
+  isLinterEslintAutofixEnabled.mockImplementation(() => true);
+  shouldUseEslint.mockImplementation(() => false);
+
+  warnAboutLinterEslintFixOnSave();
+
+  expect(addWarningNotification).not.toHaveBeenCalled();
+});
diff --git a/tests/mocks/textEditor.js b/tests/mocks/textEditor.js
index ab2ae03..9a92145 100644
--- a/tests/mocks/textEditor.js
+++ b/tests/mocks/textEditor.js
@@ -1,25 +1,25 @@
 // @flow
-
-const textEditorFactory = (overrides?: {} = {}): TextEditor => Object.assign(
-  {},
-  {
-    backwardsScanInBufferRange: jest.fn(),
-    buffer: {
-      file: {
-        path: 'xyz.js',
+const textEditorFactory = (overrides?: {} = {}): TextEditor =>
+  Object.assign(
+    {},
+    {
+      backwardsScanInBufferRange: jest.fn(),
+      buffer: {
+        file: {
+          path: 'xyz.js',
+        },
       },
+      getBuffer: jest.fn(() => ({ getRange: jest.fn(() => 'FAKE BUFFER RANGE') })),
+      getGrammar: jest.fn(() => ({ scopeName: 'FAKE SCOPE NAME' })),
+      getSelectedText: jest.fn(),
+      getLastCursor: jest.fn(),
+      getCursorScreenPosition: jest.fn(),
+      getTextInBufferRange: jest.fn(),
+      setTextInBufferRange: jest.fn(),
+      getSelectedBufferRanges: jest.fn(),
+      setCursorScreenPosition: jest.fn(),
     },
-    getBuffer: jest.fn(),
-    getGrammar: jest.fn(),
-    getSelectedText: jest.fn(),
-    getLastCursor: jest.fn(),
-    getCursorScreenPosition: jest.fn(),
-    getTextInBufferRange: jest.fn(),
-    setTextInBufferRange: jest.fn(),
-    getSelectedBufferRanges: jest.fn(),
-    setCursorScreenPosition: jest.fn(),
-  },
-  overrides,
-);
+    overrides,
+  );
 
 module.exports = textEditorFactory;