Repository: prettier/prettier-atom (js)

Issue:
RangeError: path should be a `path.relative()`d string when saving the user stylesheet (`styles.less`) in Atom with Prettier on Save enabled
<!--
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>
-->

When saving the user stylesheet (`styles.less`) in Atom with Prettier on Save enabled, this error pops up:

```
RangeError: path should be a `path.relative()`d string, but got "/Users/sompylasar/.atom/styles.less"
    at throwError (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/ignore/index.js:354:9)
    at checkPath (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/ignore/index.js:375:12)
    at Ignore._test (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/ignore/index.js:483:5)
    at Ignore.ignores (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/ignore/index.js:518:17)
    at someGlobsMatchFilePath (/Users/sompylasar/.atom/packages/prettier-atom/dist/helpers/general.js:10:96)
    at _.flow.filePath (/Users/sompylasar/.atom/packages/prettier-atom/dist/formatOnSave/shouldFormatOnSave.js:30:101)
    at /Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:49:144
    at n (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:5:89)
    at /Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:51:79
    at h (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:7:166)
    at /Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:51:56
    at n (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:5:89)
    at /Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:63:257
    at n (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:5:89)
    at /Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:51:79
    at u (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:5:521)
    at /Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:51:56
    at n (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:5:89)
    at /Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:63:257
    at n (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:5:89)
    at /Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:97:406
    at n (/Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:5:89)
    at /Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:63:257
    at /Users/sompylasar/.atom/packages/prettier-atom/node_modules/lodash/lodash.min.js:49:144
    at attemptWithErrorNotification (/Users/sompylasar/.atom/packages/prettier-atom/dist/formatOnSave/index.js:14:84)
    at /Users/sompylasar/.atom/packages/prettier-atom/dist/atomInterface/index.js:59:13
    at Generator.next (<anonymous>)
    at step (/Users/sompylasar/.atom/packages/autocomplete-flow/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
    at /Users/sompylasar/.atom/packages/autocomplete-flow/node_modules/babel-runtime/helpers/asyncToGenerator.js:35:14
    at new Promise (<anonymous>)
    at new F (/Users/sompylasar/.atom/packages/autocomplete-flow/node_modules/core-js/library/modules/_export.js:36:28)
    at /Users/sompylasar/.atom/packages/autocomplete-flow/node_modules/babel-runtime/helpers/asyncToGenerator.js:14:12
    at attemptWithErrorNotification (/Users/sompylasar/.atom/packages/prettier-atom/dist/atomInterface/index.js:67:17)
    at safeFormatOnSaveIfAppropriate (/Users/sompylasar/.atom/packages/prettier-atom/dist/formatOnSave/index.js:14:49)
    at /Users/sompylasar/.atom/packages/prettier-atom/dist/main.js:38:23
    at Generator.next (<anonymous>)
    at step (/Users/sompylasar/.atom/packages/autocomplete-flow/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
    at /Users/sompylasar/.atom/packages/autocomplete-flow/node_modules/babel-runtime/helpers/asyncToGenerator.js:35:14
    at new Promise (<anonymous>)
    at new F (/Users/sompylasar/.atom/packages/autocomplete-flow/node_modules/core-js/library/modules/_export.js:36:28)
    at /Users/sompylasar/.atom/packages/autocomplete-flow/node_modules/babel-runtime/helpers/asyncToGenerator.js:14:12
    at lazyFormatOnSave (/Users/sompylasar/.atom/packages/prettier-atom/dist/main.js:42:17)
    at subscriptions.add.editor.getBuffer.onWillSave (/Users/sompylasar/.atom/packages/prettier-atom/dist/main.js:111:119)
    at Function.simpleDispatch (/Applications/Atom.app/Contents/Resources/app/static/<embedded>:11:1190583)
    at /Applications/Atom.app/Contents/Resources/app/static/<embedded>:11:1192208
    at Array.map (<anonymous>)
    at Emitter.emitAsync (/Applications/Atom.app/Contents/Resources/app/static/<embedded>:11:1192171)
    at TextBuffer.saveTo (/Applications/Atom.app/Contents/Resources/app/static/<embedded>:11:496823)
```

`Prettier: Debug`
```
Atom version: 1.33.0
prettier-atom version: 0.56.0
prettier: bundled
prettier version: 1.15.2
prettier-eslint version: 8.8.2
prettier-atom configuration: {
  "formatOnSaveOptions": {
    "enabled": true,
    "excludedGlobs": [
      "*.json"
    ],
    "jsonScopes": [
      "source.json-"
    ],
    "showInStatusBar": true,
    "respectEslintignore": true,
    "whitelistedGlobs": [],
    "isDisabledIfNotInPackageJson": false,
    "isDisabledIfNoConfigFile": false
  },
  "prettierOptions": {
    "printWidth": 120,
    "singleQuote": true,
    "trailingComma": "all"
  },
  "useEslint": false,
  "useStylelint": false,
  "prettierEslintOptions": {
    "prettierLast": false
  }
}
```

Pull request description:
fix: ensure we always pass a relative path to `ignore` pkg
`atom.project.relativizePath` returns an absolute path when no Atom project can be determined (such
as when editing your Atom config file and many other cases). The `ignore` package will then
hard-error because it is now very strict about getting only relative paths (they assume that the
filenames we are comparing the globs to should be relative to the file in which the globs live, but
we actually hold the globs in the prettier-atom options so this concept doesn't really apply so we
just arbitrarily choose to make relativize paths from the project's root dir). To fix, we relativize
to the parent dir of the file, which is somewhat arbitrary but should be fine.

Fixes #473

Patch:
diff --git a/dist/atomInterface/index.js b/dist/atomInterface/index.js
index e1d62ff..c04f02d 100644
--- a/dist/atomInterface/index.js
+++ b/dist/atomInterface/index.js
@@ -6,6 +6,7 @@ var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+const path = require('path');
 const _ = require('lodash/fp');
 
 // constants
@@ -70,7 +71,12 @@ const attemptWithErrorNotification = (() => {
 
 const runLinter = editor => isLinterLintCommandDefined(editor) && atom.commands.dispatch(atom.views.getView(editor), LINTER_LINT_COMMAND);
 
-const relativizePathFromAtomProject = path => path ? _.get('[1]', atom.project.relativizePath(path)) : null;
+const invokeAtomRelativizePath = _.flow(filePath => atom.project.relativizePath(filePath), // NOTE: fat arrow necessary for `this`
+_.get('[1]'));
+
+const relativizePathToDirname = filePath => path.relative(path.dirname(filePath), filePath);
+
+const relativizePathFromAtomProject = _.cond([[_.isNil, _.constant(null)], [_.flow(invokeAtomRelativizePath, path.isAbsolute), relativizePathToDirname], [_.stubTrue, invokeAtomRelativizePath]]);
 
 module.exports = {
   addErrorNotification,
diff --git a/src/atomInterface/index.js b/src/atomInterface/index.js
index e150893..2dff6e9 100644
--- a/src/atomInterface/index.js
+++ b/src/atomInterface/index.js
@@ -1,4 +1,5 @@
 // @flow
+const path = require('path');
 const _ = require('lodash/fp');
 
 // constants
@@ -68,8 +69,24 @@ const runLinter = (editor: TextEditor) =>
   isLinterLintCommandDefined(editor) &&
   atom.commands.dispatch(atom.views.getView(editor), LINTER_LINT_COMMAND);
 
-const relativizePathFromAtomProject = (path: ?string) =>
-  path ? _.get('[1]', atom.project.relativizePath(path)) : null;
+const invokeAtomRelativizePath = _.flow(
+  filePath => atom.project.relativizePath(filePath), // NOTE: fat arrow necessary for `this`
+  _.get('[1]'),
+);
+
+const relativizePathToDirname = (filePath: string) => path.relative(path.dirname(filePath), filePath);
+
+const relativizePathFromAtomProject: (filePath: ?string) => ?string = _.cond([
+  [_.isNil, _.constant(null)],
+  [
+    _.flow(
+      invokeAtomRelativizePath,
+      path.isAbsolute,
+    ),
+    relativizePathToDirname,
+  ],
+  [_.stubTrue, invokeAtomRelativizePath],
+]);
 
 module.exports = {
   addErrorNotification,


Test patch:
diff --git a/src/atomInterface/index.test.js b/src/atomInterface/index.test.js
index 35e9406..1d064c7 100644
--- a/src/atomInterface/index.test.js
+++ b/src/atomInterface/index.test.js
@@ -4,10 +4,43 @@ const {
   shouldUseStylelint,
   getPrettierEslintOptions,
   isLinterEslintAutofixEnabled,
+  relativizePathFromAtomProject,
   toggleFormatOnSave,
 } = require('./index');
 const textEditor = require('../../tests/mocks/textEditor');
 
+describe('relativizePathFromAtomProject()', () => {
+  it('runs `atom.project.relativizePath` if the filepath and project are present', () => {
+    const absoluteFilePath = '/Users/johndoe/src/main.js';
+    const relativeFilePath = 'main.js';
+    atom = { project: { relativizePath: jest.fn(() => [null, relativeFilePath]) } };
+
+    const actual = relativizePathFromAtomProject(absoluteFilePath);
+
+    expect(actual).toEqual(relativeFilePath);
+    expect(atom.project.relativizePath).toHaveBeenCalledWith(absoluteFilePath);
+  });
+
+  it('relativizes the path to the parent dir if the filepath is present but project is missing', () => {
+    const absoluteFilePath = '/Users/johndoe/src/main.js';
+    const relativeFilePath = 'main.js';
+    atom = { project: { relativizePath: jest.fn(() => [null, absoluteFilePath]) } };
+
+    const actual = relativizePathFromAtomProject(absoluteFilePath);
+
+    expect(actual).toEqual(relativeFilePath);
+    expect(atom.project.relativizePath).toHaveBeenCalledWith(absoluteFilePath);
+  });
+
+  it('returns null if no filepath is present', () => {
+    const filePath = null;
+
+    const actual = relativizePathFromAtomProject(filePath);
+
+    expect(actual).toEqual(null);
+  });
+});
+
 describe('runLinter()', () => {
   it('runs `linter:lint` command', () => {
     const editor = textEditor();