Repository: prettier/prettier-atom (js)

Issue:
Can't save file
<!--
Before you post an issue, please ensure that your issue is not with the
[prettier](https://github.com/prettier/prettier) or
[prettier-eslint](https://github.com/prettier/prettier-eslint) packages. This is just the
atom plugin that calls these packages under the hood.

Describe your issue here with as many details as possible
-->

<!--
Paste the output of the `Prettier: Debug` command below.
Protip: you can use a <details> tag to hide or show really large elements.

For example:
<details>
  A really long text
</details>
-->

![screen shot 2017-06-09 at 1 54 23 pm](https://user-images.githubusercontent.com/2771466/26960509-477a1532-4d1b-11e7-9457-c7c1c17dac33.png)


[Enter steps to reproduce:]

1. Have outdated prettier-atom
2. Update prettier-atom
3. Try to save file

Prettier-atom is asking me to restart atom to get updates, but it won't let me save my file so I can restart.

I have run on save disabled.

I had to disable the extension to be able to save my file.

Can prettier-atom operations be wrapped in a try catch to prevent them from bringing down core editor functions?

**Atom**: 1.17.2 x64
**Electron**: 1.3.15
**OS**: Mac OS X 10.12.5
**Thrown From**: [prettier-atom](https://github.com/prettier/prettier-atom) package 0.32.0


### Stack Trace

Uncaught TypeError: Expected a function

```
At /Applications/Atom.app/Contents/Resources/app/src/pane.js:1095

TypeError: Expected a function
    at Function.su (/packages/prettier-atom/node_modules/lodash/lodash.min.js:70:197)
    at /packages/prettier-atom/dist/formatOnSave/shouldFormatOnSave.js:42:241)
    at Module.get_Module._compile (/Applications/Atom.app/Contents/Resources/app/src/native-compile-cache.js:106:1)
    at Object.value [as .js] (/Applications/Atom.app/Contents/Resources/app/src/compile-cache.js:230:1)
    at Module.load (module.js:473:32)
    at tryModuleLoad (module.js:432:12)
    at Function.Module._load (module.js:424:3)
    at Module.require (Applications/Atom.app/Contents/Resources/app/static/index.js:47:45)
    at require (/Applications/Atom.app/Contents/Resources/app/src/native-compile-cache.js:66:1)
    at /packages/prettier-atom/dist/formatOnSave/index.js:11:26)
    at Module.get_Module._compile (/Applications/Atom.app/Contents/Resources/app/src/native-compile-cache.js:106:1)
    at Object.value [as .js] (/Applications/Atom.app/Contents/Resources/app/src/compile-cache.js:230:1)
    at Module.load (module.js:473:32)
    at tryModuleLoad (module.js:432:12)
    at Function.Module._load (module.js:424:3)
    at Module.require (Applications/Atom.app/Contents/Resources/app/static/index.js:47:45)
    at require (/Applications/Atom.app/Contents/Resources/app/src/native-compile-cache.js:66:1)
    at lazyFormatOnSave (/packages/prettier-atom/dist/main.js:37:37)
    at /packages/prettier-atom/dist/main.js:101:14
    at Function.module.exports.Emitter.simpleDispatch (/Applications/Atom.app/Contents/Resources/app/node_modules/event-kit/lib/emitter.js:25:1)
    at Emitter.module.exports.Emitter.emit (/Applications/Atom.app/Contents/Resources/app/node_modules/event-kit/lib/emitter.js:141:1)
    at TextBuffer.module.exports.TextBuffer.saveAs (/Applications/Atom.app/Contents/Resources/app/node_modules/text-buffer/lib/text-buffer.js:1119:1)
    at TextBuffer.module.exports.TextBuffer.save (/Applications/Atom.app/Contents/Resources/app/node_modules/text-buffer/lib/text-buffer.js:1108:1)
    at TextEditor.module.exports.TextEditor.save (/Applications/Atom.app/Contents/Resources/app/src/text-editor.js:925:1)
    at Pane.module.exports.Pane.saveItem (/Applications/Atom.app/Contents/Resources/app/src/pane.js:780:1)
    at Pane.saveItem (/Applications/Atom.app/Contents/Resources/app/src/pane.js:3:1)
    at Pane.module.exports.Pane.saveActiveItem (/Applications/Atom.app/Contents/Resources/app/src/pane.js:763:1)
    at Workspace.saveActivePaneItem (/Applications/Atom.app/Contents/Resources/app/src/workspace.js:1498:1)
    at atom-workspace.core:save (/Applications/Atom.app/Contents/Resources/app/src/register-default-commands.js:236:1)
    at CommandRegistry.module.exports.CommandRegistry.handleCommandEvent (/Applications/Atom.app/Contents/Resources/app/src/command-registry.js:265:1)
    at /Applications/Atom.app/Contents/Resources/app/src/command-registry.js:3:1
    at KeymapManager.module.exports.KeymapManager.dispatchCommandEvent (/Applications/Atom.app/Contents/Resources/app/node_modules/atom-keymap/lib/keymap-manager.js:610:1)
    at KeymapManager.module.exports.KeymapManager.handleKeyboardEvent (/Applications/Atom.app/Contents/Resources/app/node_modules/atom-keymap/lib/keymap-manager.js:401:1)
    at WindowEventHandler.module.exports.WindowEventHandler.handleDocumentKeyEvent (/Applications/Atom.app/Contents/Resources/app/src/window-event-handler.js:100:1)
    at HTMLDocument.<anonymous> (/Applications/Atom.app/Contents/Resources/app/src/window-event-handler.js:3:1)
```

### Commands

```
     -0:38 editor:newline (input.hidden-input)
  3x -0:33.5.0 core:save (input.hidden-input)
```

### Non-Core Packages

```
activate-power-mode 2.0.0 
atom-autocomplete-php 0.22.2 
atom-beautify 0.29.26 
atom-typescript 11.0.3 
busy-signal 1.4.3 
color-picker 2.2.5 
file-icons 2.1.7 
go-debug 1.4.3 
go-plus 5.5.5 
go-signature-statusbar 1.2.0 
hyperclick 0.0.40 
ide-purescript 0.17.2 
intentions 1.1.2 
language-docker 1.1.8 
language-elm 1.5.0 
language-javascript-jsx 0.3.7 
language-protobuf 0.7.1 
language-purescript 0.8.2 
language-rust 0.4.10 
linter 2.1.4 
linter-eslint 8.2.1 
linter-flow 5.6.0 
linter-php 1.3.2 
linter-rust 0.8.0 
linter-ui-default 1.6.0 
nuclide 0.232.0 
php-twig 4.0.0 
pigments 0.39.1 
prettier-atom 0.32.0 
racer 0.20.0 
react 0.16.2 
```




Pull request description:
fix(formatOnSave): don't prevent user from saving if uncaught error
In unexpected situations where errors other than parsing errors during formatting are raised (the
latter already being handled), users are unable to save without disabling prettier because the Error
prevents the save from executing. Now we will wrap the logic inside of `formatOnSave` with a
try/catch that will catch this error and show a notification. This will inform the user that an
error occurred, but won't prevent saving their file.

Resolves #190

Patch:
diff --git a/dist/atomInterface/index.js b/dist/atomInterface/index.js
index 49b1751..eee26fa 100644
--- a/dist/atomInterface/index.js
+++ b/dist/atomInterface/index.js
@@ -117,6 +117,18 @@ var addErrorNotification = function addErrorNotification(message, options) {
   return atom.notifications.addError(message, options);
 };
 
+var attemptWithErrorNotification = function attemptWithErrorNotification(func) {
+  for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+    args[_key - 1] = arguments[_key];
+  }
+
+  try {
+    func.apply(undefined, args);
+  } catch (e) {
+    addErrorNotification(e.message, { dismissable: true, detail: e.stack });
+  }
+};
+
 var runLinter = function runLinter(editor) {
   return isLinterLintCommandDefined(editor) && atom.commands.dispatch(atom.views.getView(editor), LINTER_LINT_COMMAND);
 };
@@ -146,5 +158,6 @@ module.exports = {
   shouldRespectEslintignore: shouldRespectEslintignore,
   shouldUseEditorConfig: shouldUseEditorConfig,
   shouldUseEslint: shouldUseEslint,
-  toggleFormatOnSave: toggleFormatOnSave
+  toggleFormatOnSave: toggleFormatOnSave,
+  attemptWithErrorNotification: attemptWithErrorNotification
 };
\ No newline at end of file
diff --git a/dist/formatOnSave/index.js b/dist/formatOnSave/index.js
index 8e77340..f9b9c68 100644
--- a/dist/formatOnSave/index.js
+++ b/dist/formatOnSave/index.js
@@ -1,5 +1,7 @@
 'use strict';
 
+var _ = require('lodash/fp');
+
 var _require = require('../editorInterface'),
     isCurrentScopeEmbeddedScope = _require.isCurrentScopeEmbeddedScope,
     getBufferRange = _require.getBufferRange;
@@ -8,10 +10,21 @@ var _require2 = require('../executePrettier'),
     executePrettierOnBufferRange = _require2.executePrettierOnBufferRange,
     executePrettierOnEmbeddedScripts = _require2.executePrettierOnEmbeddedScripts;
 
+var _require3 = require('../atomInterface'),
+    attemptWithErrorNotification = _require3.attemptWithErrorNotification;
+
 var shouldFormatOnSave = require('./shouldFormatOnSave');
 
-var formatOnSaveIfAppropriate = function formatOnSaveIfAppropriate(editor) {
-  return shouldFormatOnSave(editor) && (isCurrentScopeEmbeddedScope(editor) ? executePrettierOnEmbeddedScripts(editor) : executePrettierOnBufferRange(editor, getBufferRange(editor)));
+var callAppropriatePrettierExecutor = function callAppropriatePrettierExecutor(editor) {
+  return isCurrentScopeEmbeddedScope(editor) ? executePrettierOnEmbeddedScripts(editor) : executePrettierOnBufferRange(editor, getBufferRange(editor));
+};
+
+var formatOnSaveIfAppropriate = _.cond([[shouldFormatOnSave, callAppropriatePrettierExecutor]]);
+
+var safeFormatOnSaveIfAppropriate = function safeFormatOnSaveIfAppropriate(editor) {
+  return attemptWithErrorNotification(function () {
+    return formatOnSaveIfAppropriate(editor);
+  });
 };
 
-module.exports = formatOnSaveIfAppropriate;
\ No newline at end of file
+module.exports = safeFormatOnSaveIfAppropriate;
\ No newline at end of file
diff --git a/src/atomInterface/index.js b/src/atomInterface/index.js
index 58f4beb..8bde804 100644
--- a/src/atomInterface/index.js
+++ b/src/atomInterface/index.js
@@ -74,6 +74,14 @@ const addWarningNotification = (message: string, options?: Atom$Notifications$Op
 const addErrorNotification = (message: string, options?: Atom$Notifications$Options) =>
   atom.notifications.addError(message, options);
 
+const attemptWithErrorNotification = (func: Function, ...args: Array<any>) => {
+  try {
+    func(...args);
+  } catch (e) {
+    addErrorNotification(e.message, { dismissable: true, detail: e.stack });
+  }
+};
+
 const runLinter = (editor: TextEditor) =>
   isLinterLintCommandDefined(editor) &&
   atom.commands.dispatch(atom.views.getView(editor), LINTER_LINT_COMMAND);
@@ -104,4 +112,5 @@ module.exports = {
   shouldUseEditorConfig,
   shouldUseEslint,
   toggleFormatOnSave,
+  attemptWithErrorNotification,
 };
diff --git a/src/formatOnSave/index.js b/src/formatOnSave/index.js
index aaf8418..f72e705 100644
--- a/src/formatOnSave/index.js
+++ b/src/formatOnSave/index.js
@@ -1,12 +1,20 @@
 // @flow
+const _ = require('lodash/fp');
 const { isCurrentScopeEmbeddedScope, getBufferRange } = require('../editorInterface');
 const { executePrettierOnBufferRange, executePrettierOnEmbeddedScripts } = require('../executePrettier');
+const { attemptWithErrorNotification } = require('../atomInterface');
 const shouldFormatOnSave = require('./shouldFormatOnSave');
 
-const formatOnSaveIfAppropriate = (editor: TextEditor) =>
-  shouldFormatOnSave(editor) &&
-  (isCurrentScopeEmbeddedScope(editor)
+const callAppropriatePrettierExecutor = (editor: TextEditor) =>
+  isCurrentScopeEmbeddedScope(editor)
     ? executePrettierOnEmbeddedScripts(editor)
-    : executePrettierOnBufferRange(editor, getBufferRange(editor)));
+    : executePrettierOnBufferRange(editor, getBufferRange(editor));
 
-module.exports = formatOnSaveIfAppropriate;
+const formatOnSaveIfAppropriate: TextEditor => void = _.cond([
+  [shouldFormatOnSave, callAppropriatePrettierExecutor],
+]);
+
+const safeFormatOnSaveIfAppropriate = (editor: TextEditor) =>
+  attemptWithErrorNotification(() => formatOnSaveIfAppropriate(editor));
+
+module.exports = safeFormatOnSaveIfAppropriate;


Test patch:
diff --git a/src/formatOnSave/index.test.js b/src/formatOnSave/index.test.js
index d661e08..1cf85f3 100644
--- a/src/formatOnSave/index.test.js
+++ b/src/formatOnSave/index.test.js
@@ -40,3 +40,16 @@ it('does nothing if it should not format on save', () => {
   expect(executePrettierOnBufferRange).not.toHaveBeenCalled();
   expect(executePrettierOnEmbeddedScripts).not.toHaveBeenCalled();
 });
+
+it('catches uncaught errors so that the user is not prevented from saving', () => {
+  const editor = createMockTextEditor();
+  const fakeError = new Error('fake error');
+  atom = { notifications: { addError: jest.fn() } };
+  shouldFormatOnSave.mockImplementation(() => {
+    throw fakeError;
+  });
+
+  formatOnSave(editor);
+
+  expect(atom.notifications.addError).toHaveBeenCalled();
+});