Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect EditorConfig settings #2760

Merged
merged 61 commits into from Nov 8, 2017
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
f3a6ecb
[WIP] Respect EditorConfig settings
josephfrazier Sep 3, 2017
6897f6b
Merge branch 'master' into editorconfig
josephfrazier Sep 6, 2017
24821d5
Add test for more specific .editorconfig glob
josephfrazier Sep 6, 2017
9a7a17a
Update config-resolution test snapshots
josephfrazier Sep 6, 2017
7d92baf
Fix lint
josephfrazier Sep 6, 2017
2a1ae88
fixup! Update config-resolution test snapshots
josephfrazier Sep 6, 2017
54a5dcb
Ignore absent EditorConfig settings
josephfrazier Sep 7, 2017
4f52651
Remove unnecessary eslint-disable comments
josephfrazier Sep 7, 2017
ad69825
Merge branch 'master' into editorconfig
josephfrazier Sep 9, 2017
bf534cd
WIP extract helper function
josephfrazier Sep 9, 2017
53e6f17
Move editorconfig parsing outside helper
josephfrazier Sep 12, 2017
4f660e6
use async editorconfig
josephfrazier Sep 12, 2017
ccd03c6
parallelize async resolveConfig
josephfrazier Sep 12, 2017
5e6ef51
Merge branch 'master' into editorconfig
josephfrazier Sep 12, 2017
a42fa75
Add caching for editorconfig
josephfrazier Sep 12, 2017
f123c64
Fix syntax for Node v4
josephfrazier Sep 12, 2017
c4bb231
Honor editorconfig even if no prettier config is found
josephfrazier Sep 12, 2017
8404ee0
Remove unnecessary braces from arrow function
josephfrazier Sep 12, 2017
85e350b
Return null from resolveConfig() when filePath isn't provided
josephfrazier Sep 12, 2017
148d3bf
rename helper function
josephfrazier Sep 12, 2017
cfdb146
Test that prettier.clearConfigCache() doesn't throw
josephfrazier Sep 13, 2017
efed301
Use mem.clear() instead of .clear() method
josephfrazier Sep 13, 2017
5c9c3d9
Put `filePath` in front of `arr[x]`
josephfrazier Sep 13, 2017
f69d367
Make variable names explicit
josephfrazier Sep 13, 2017
e422fc0
Merge branch 'master' into editorconfig
josephfrazier Sep 14, 2017
489fc24
WIP Test that .editorconfig is overridden by other configs
josephfrazier Sep 14, 2017
f859805
Merge branch 'master' into editorconfig
josephfrazier Sep 14, 2017
7c2b545
Disregard .editorconfig when `--config` is passed
josephfrazier Sep 14, 2017
e2bc881
Regenerate README TOC
josephfrazier Sep 14, 2017
a9a8b57
Add EditorConfig info to README
josephfrazier Sep 14, 2017
6efbf89
Pin dependency versions
josephfrazier Sep 14, 2017
0ff7bec
Merge branch 'master' into editorconfig
josephfrazier Oct 8, 2017
cd1e260
Use editorconfig fork with Rollup fix
josephfrazier Oct 8, 2017
59adb59
Always return a Promise from `editorconfigAsyncNoCache`
josephfrazier Oct 8, 2017
6a95dc2
Add getLoadEditorConfigFunction like getLoadFunction
josephfrazier Oct 8, 2017
99b9619
Pass opts.config to loadEditorConfig() like load()
josephfrazier Oct 8, 2017
30c619f
Extract loadOpts
josephfrazier Oct 8, 2017
48abd24
Add sync argument to resolveConfig, use in resolveConfig.sync
josephfrazier Oct 8, 2017
f07c166
Move EditorConfig stuff into own file
josephfrazier Oct 8, 2017
619ae2e
Remove unnecessary `.js` from `require()`
josephfrazier Oct 9, 2017
f5f382b
Don't expose `sync` parameter of resolveConfig
josephfrazier Oct 9, 2017
e583cc1
Simplify resolveConfig.sync definition
josephfrazier Oct 9, 2017
734d5ad
Switch back to upstream editorconfig
josephfrazier Oct 9, 2017
3fef30d
Merge branch 'master' into editorconfig
josephfrazier Oct 12, 2017
ccc3b01
Clarify variable name: editorConfigged -> editorConfigured
josephfrazier Oct 12, 2017
8776fa6
Pass path root to editorconfig
josephfrazier Oct 12, 2017
e5e8da0
Merge branch 'master' into editorconfig
josephfrazier Oct 15, 2017
1b4b1b4
mergeEditorConfig: Ensure mergeOverrides argument is defined
josephfrazier Oct 15, 2017
128e6e8
Dedupe conditional editorconfig parsing
josephfrazier Oct 15, 2017
e9c22f9
Make test .editorconfig not set config for all file extensions
josephfrazier Oct 17, 2017
6ac1ba7
Make test .prettierrc not set config for all file extensions
josephfrazier Oct 17, 2017
49a19fe
Return null from resolveConfig when no config is found
josephfrazier Oct 17, 2017
d44420c
Clarify when resolveConfig returns null
josephfrazier Oct 18, 2017
db9877c
editorconfig: test indent_style = tab overriding indent_size with tab…
josephfrazier Nov 2, 2017
a9bc09e
editorconfig: prioritize tab_width over indent_size
josephfrazier Nov 1, 2017
26f49a4
editorconfig: let indent_style force either tab_width or indent_size …
josephfrazier Nov 1, 2017
bf282f9
editorconfig: test/fix indent_size = tab
josephfrazier Nov 2, 2017
27254d4
Move editorConfigToPrettier into separate package
josephfrazier Nov 2, 2017
5526de0
Merge branch 'master' into editorconfig
josephfrazier Nov 6, 2017
664dced
Merge branch 'master' into editorconfig
josephfrazier Nov 6, 2017
47e4b62
Merge branch 'master' into josephfrazier-editorconfig
josephfrazier Nov 8, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -17,13 +17,15 @@
"cosmiconfig": "davidtheclark/cosmiconfig",
"dashify": "0.2.2",
"diff": "3.2.0",
"editorconfig": "^0.14.1",
"esutils": "2.0.2",
"flow-parser": "0.51.0",
"get-stream": "3.0.0",
"globby": "^6.1.0",
"graphql": "0.10.1",
"ignore": "^3.3.3",
"jest-validate": "20.0.3",
"mem": "^1.1.0",
"minimatch": "3.0.4",
"minimist": "1.2.0",
"parse5": "3.0.2",
Expand Down
57 changes: 51 additions & 6 deletions src/resolve-config.js
@@ -1,31 +1,76 @@
"use strict";

const cosmiconfig = require("cosmiconfig");
const editorconfig = require("editorconfig");
const mem = require("mem");
const minimatch = require("minimatch");

const asyncWithCache = cosmiconfig("prettier");
const asyncNoCache = cosmiconfig("prettier", { cache: false });
const syncWithCache = cosmiconfig("prettier", { sync: true });
const syncNoCache = cosmiconfig("prettier", { cache: false, sync: true });

const editorconfigAsyncNoCache = filePath =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is starting to blow up combinatorially. Any way we can apply some polymorphism magic to clean it up a bit?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

const asyncWithCache = configgers.cosmiconfig.async.cache;
const asyncNoCache = configgers.cosmiconfig.async.noCache;
const syncWithCache = configgers.cosmiconfig.sync.cache;
const syncNoCache = configgers.cosmiconfig.sync.noCache;
const editorconfigAsyncWithCache = configgers.editorconfig.async.cache;
const editorconfigAsyncNoCache = configgers.editorconfig.async.noCache;
const editorconfigSyncWithCache = configgers.editorconfig.sync.cache;
const editorconfigSyncNoCache = configgers.editorconfig.sync.noCache;
const configgers = createConfiggers([
  {
    cosmiconfig: { type: "cosmiconfig" },
    editorconfig: { type: "editorconfig" }
  },
  {
    sync: { sync: true },
    async: { sync: false }
  },
  {
    cache: { cache: true },
    noCache: { cache: false }
  }
]);

function createConfiggers(configgerArgs) {
  const createdConfiggers = {};

  recursive(null, createdConfiggers, configgerArgs, []);

  return createdConfiggers;

  function recursive(parent, target, args, stack) {
    if (args.length !== 0) {
      Object.keys(args[0]).forEach(key => {
        const option = args[0][key];
        const subTarget = (target[key] = {});
        recursive(
          target,
          subTarget,
          args.slice(1),
          stack.concat({ key, option })
        );
      });
    } else {
      const options = stack.reduce(
        (current, element) => Object.assign(current, element.option),
        {}
      );
      const lastKey = stack[stack.length - 1].key;
      parent[lastKey] = createConfigger(
        options.type,
        options.sync,
        options.cache
      );
    }
  }
}

function createConfigger(type, sync, cache) {
  if (type === "cosmiconfig") {
    return cosmiconfig("prettier", { sync, cache });
  }
  const editorConfigger = !sync
    ? filePath =>
        filePath && editorconfig.parse(filePath).then(editorConfigToPrettier)
    : filePath =>
        filePath && editorConfigToPrettier(editorconfig.parseSync(filePath));
  return cache ? mem(editorConfigger) : editorConfigger;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ikatyang I think the "manual" way is better in this case :)

filePath && editorconfig.parse(filePath).then(editorConfigToPrettier);
const editorconfigAsyncWithCache = mem(editorconfigAsyncNoCache);
const editorconfigSyncNoCache = filePath =>
filePath && editorConfigToPrettier(editorconfig.parseSync(filePath));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This arrow function has no braces while the above when has.

const editorconfigSyncWithCache = mem(editorconfigSyncNoCache);

function editorConfigToPrettier(editorConfig) {
const result = {};

if (editorConfig.indent_style) {
result.useTabs = editorConfig.indent_style === "tab";
}

const tabWidth = editorConfig.indent_size || editorConfig.tab_width;
if (tabWidth) {
result.tabWidth = tabWidth;
}

if (editorConfig.max_line_length) {
result.printWidth = editorConfig.max_line_length;
}

Copy link
Member

@lydell lydell Sep 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it important that the .useTabs, .tabWidth and .printWidth properties of result are missing rather than present with the value undefined if the corresponding EditorConfig setting is missing?

Couldn't we just do:

const editorConfig = editorconfig.parseSync(filePath);
return {
  useTabs: ...,
  tabWidth: ...,
  printWidth: ...
}

While being an odd configuration, isn't it valid to set indent_size, tab_width and max_line_length to 0, and as such we should respect that? Note that 0 if a falsy value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it important that the .useTabs, .tabWidth and .printWidth properties of result are missing rather than present with the value undefined if the corresponding EditorConfig setting is missing?

Yes, I think that's what fixed this test: https://travis-ci.org/prettier/prettier/jobs/272596635#L379

While being an odd configuration, isn't it valid to set indent_size, tab_width and max_line_length to 0, and as such we should respect that? Note that 0 if a falsy value.

Hmm, it looks like tab_width and max_line_length must be positive. I suppose someone could set indent_size to 0, but I'm not sure if that edge case is worth supporting right now. At least, I haven't had time to put effort into it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's all fine then 👍

return result;
}

function resolveConfig(filePath, opts) {
const useCache = !(opts && opts.useCache === false);
return (useCache ? asyncWithCache : asyncNoCache)
.load(filePath)
.then(result => {
return !result ? null : mergeOverrides(result.config, filePath);
});
return Promise.all([
(useCache ? asyncWithCache : asyncNoCache).load(filePath),
(useCache ? editorconfigAsyncWithCache : editorconfigAsyncNoCache)(filePath)
]).then((arr /* [result, editorConfigged] */) =>
mergeEditorConfig(arr[0], filePath, arr[1])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put filePath arg in front of arr[x]? as it looks more comfortable and reasonable (result and editorConfigged are config-related).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, fixed in 5c9c3d9. I also made the variable names explicit in f69d367

);
}

resolveConfig.sync = (filePath, opts) => {
const useCache = !(opts && opts.useCache === false);
const result = (useCache ? syncWithCache : syncNoCache).load(filePath);
return !result ? null : mergeOverrides(result.config, filePath);
const editorConfigged = (useCache
? editorconfigSyncWithCache
: editorconfigSyncNoCache)(filePath);
return mergeEditorConfig(result, filePath, editorConfigged);
};

function mergeEditorConfig(result, filePath, editorConfigged) {
if (!filePath) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null should represent the config file not being found, so I guess both editor config and cosmiconfig failed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think I had to add that branch in to keep the pre-existing tests passing. (related to #2997)

return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated: it'd be better to add a comment to describe why we return null here since our docs now says filePath is a required parameter.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out we could get rid of this altogether. Fixed in 49a19fe

}

const config = result && result.config;

return Object.assign({}, editorConfigged, mergeOverrides(config, filePath));
}

function clearCache() {
syncWithCache.clearCaches();
asyncWithCache.clearCaches();

editorconfigSyncWithCache.clear();
editorconfigAsyncWithCache.clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mem docs say mem.clear(fn)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we need the expect(() => clearCache()).not.toThrowError() test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps time to add tests for the clearCache function, then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that when I originally added it but I was having issues with jest module mocks so I gave up. We should look at it again.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I added (and fixed) a test to ensure it doesn't throw. Haven't yet gotten to testing that the cache actually clears.

}

function resolveConfigFile(filePath) {
Expand Down
@@ -1,7 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`CLI overrides take precedence 1`] = `
"console.log(
"function f() {
console.log(
\\"should have tab width 8\\"
)
}
function f() {
console.log(
\\"should have space width 2\\"
)
}
console.log(
\\"should have semi\\"
);
console.log(
Expand Down Expand Up @@ -56,7 +66,13 @@ exports[`resolves configuration file with --find-config-path file 1`] = `
`;

exports[`resolves configuration from external files 1`] = `
"console.log(\\"should have semi\\");
"function f() {
console.log(\\"should have tab width 8\\")
}
function f() {
console.log(\\"should have space width 2\\")
}
console.log(\\"should have semi\\");
console.log(\\"should not have semi\\")
console.log(\\"should have semi\\");
function f() {
Expand Down
Expand Up @@ -41,7 +41,17 @@ module.exports = {
`;

exports[`CLI overrides take precedence with --config-precedence cli-override 1`] = `
"console.log(
"function f() {
console.log(
\\"should have tab width 8\\"
)
}
function f() {
console.log(
\\"should have space width 2\\"
)
}
console.log(
\\"should have semi\\"
);
console.log(
Expand Down Expand Up @@ -84,7 +94,17 @@ function f() {
`;

exports[`CLI overrides take precedence without --config-precedence 1`] = `
"console.log(
"function f() {
console.log(
\\"should have tab width 8\\"
)
}
function f() {
console.log(
\\"should have space width 2\\"
)
}
console.log(
\\"should have semi\\"
);
console.log(
Expand Down
48 changes: 48 additions & 0 deletions tests_integration/__tests__/config-resolution.js
Expand Up @@ -93,3 +93,51 @@ test("API resolveConfig.sync with file arg and extension override", () => {
semi: true
});
});

test("API resolveConfig with file arg and .editorconfig", () => {
const file = path.resolve(
path.join(__dirname, "../cli/config/editorconfig/file.js")
);
return prettier.resolveConfig(file).then(result => {
expect(result).toMatchObject({
useTabs: true,
tabWidth: 8,
printWidth: 100
});
});
});

test("API resolveConfig.sync with file arg and .editorconfig", () => {
const file = path.resolve(
path.join(__dirname, "../cli/config/editorconfig/file.js")
);
expect(prettier.resolveConfig.sync(file)).toMatchObject({
useTabs: true,
tabWidth: 8,
printWidth: 100
});
});

test("API resolveConfig with nested file arg and .editorconfig", () => {
const file = path.resolve(
path.join(__dirname, "../cli/config/editorconfig/lib/file.js")
);
return prettier.resolveConfig(file).then(result => {
expect(result).toMatchObject({
useTabs: false,
tabWidth: 2,
printWidth: 100
});
});
});

test("API resolveConfig.sync with nested file arg and .editorconfig", () => {
const file = path.resolve(
path.join(__dirname, "../cli/config/editorconfig/lib/file.js")
);
expect(prettier.resolveConfig.sync(file)).toMatchObject({
useTabs: false,
tabWidth: 2,
printWidth: 100
});
});
11 changes: 11 additions & 0 deletions tests_integration/cli/config/editorconfig/.editorconfig
@@ -0,0 +1,11 @@
root = true

[*]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should test a more specific glob as well, with different settings, like [lib/**.js] (copied from http://editorconfig.org/).

indent_style = tab
tab_width = 8
max_line_length = 100

# Indentation override for all JS under lib directory
[lib/**.js]
indent_style = space
indent_size = 2
3 changes: 3 additions & 0 deletions tests_integration/cli/config/editorconfig/file.js
@@ -0,0 +1,3 @@
function f() {
console.log("should have tab width 8");
}
3 changes: 3 additions & 0 deletions tests_integration/cli/config/editorconfig/lib/file.js
@@ -0,0 +1,3 @@
function f() {
console.log("should have space width 2");
}
34 changes: 32 additions & 2 deletions yarn.lock
Expand Up @@ -721,6 +721,10 @@ block-stream@*:
dependencies:
inherits "~2.0.0"

bluebird@^3.0.5:
version "3.5.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"

bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.6"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215"
Expand Down Expand Up @@ -1242,6 +1246,16 @@ ecc-jsbn@~0.1.1:
dependencies:
jsbn "~0.1.0"

editorconfig@^0.14.1:
version "0.14.1"
resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.14.1.tgz#a7b3ce821e34250d5988c50946453d68d5e8fc3b"
dependencies:
bluebird "^3.0.5"
commander "^2.9.0"
lru-cache "^3.2.0"
semver "^5.1.0"
sigmund "^1.0.1"

elliptic@^6.0.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
Expand Down Expand Up @@ -2616,6 +2630,12 @@ loose-envify@^1.0.0:
dependencies:
js-tokens "^3.0.0"

lru-cache@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-3.2.0.tgz#71789b3b7f5399bec8565dda38aa30d2a097efee"
dependencies:
pseudomap "^1.0.1"

lru-cache@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
Expand Down Expand Up @@ -2668,6 +2688,12 @@ markdown-toc@1.1.0:
repeat-string "^1.6.1"
strip-color "^0.1.0"

mem@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
dependencies:
mimic-fn "^1.0.0"

memory-fs@^0.4.0, memory-fs@~0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
Expand Down Expand Up @@ -3180,7 +3206,7 @@ prr@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"

pseudomap@^1.0.2:
pseudomap@^1.0.1, pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"

Expand Down Expand Up @@ -3582,7 +3608,7 @@ sax@^1.2.1:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"

semver@5.4.1:
semver@5.4.1, semver@^5.1.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"

Expand Down Expand Up @@ -3636,6 +3662,10 @@ shellwords@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.0.tgz#66afd47b6a12932d9071cbfd98a52e785cd0ba14"

sigmund@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"

signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
Expand Down